Harden Sæby demo registration and department scope (#335)

Complete and secure public customer/driver registration, authoritative limited-backoffice department scope, one-time employee QR login, and pricing concurrency for the Sæby demo.
This commit is contained in:
Jeppe B
2026-08-02 11:50:56 +02:00
committed by GitHub
parent 4587bdfb06
commit 1e0e051775
34 changed files with 1853 additions and 474 deletions
+83
View File
@@ -267,6 +267,18 @@ trait route_t
*/
public function requireDepartmentAccess(string $department, string|null $permission = null): void
{
global $response;
$departmentId = (int)$department;
$authenticatedUser = (new authentication())->get_user();
if ($authenticatedUser instanceof \objects\users_o) {
$managedScope = (new \classes\limited_backoffice_service())
->managedEmployeeDepartmentIds($authenticatedUser);
if ($managedScope !== null && !in_array($departmentId, $managedScope, true)) {
$response->error('Department resource not found', 404);
}
}
self::requirePermission('department_access_' . $department . ($permission ? '_' . $permission : ''));
}
@@ -279,9 +291,80 @@ trait route_t
*/
public function hasDepartmentAccess(string $department, string|null $permission = null): bool
{
$departmentId = (int)$department;
$authenticatedUser = (new authentication())->get_user();
if ($authenticatedUser instanceof \objects\users_o) {
$managedScope = (new \classes\limited_backoffice_service())
->managedEmployeeDepartmentIds($authenticatedUser);
if ($managedScope !== null && !in_array($departmentId, $managedScope, true)) {
return false;
}
}
return self::hasPermission('department_access_' . $department . ($permission ? '_' . $permission : ''));
}
/**
* Applies department scoping only to users managed by limited backoffice.
* Other user types retain the route's existing permission contract.
*
* @return array<int, int>|null
*/
public function limitedBackofficeDepartmentScope(object $user): ?array
{
if (!$user instanceof \objects\users_o) {
return null;
}
return (new \classes\limited_backoffice_service())->managedEmployeeDepartmentIds($user);
}
/**
* Returns the authoritative managed assignment for limited-backoffice
* employees and preserves the legacy group-derived scope for every other
* account type.
*
* @return array<int, int>
*/
public function effectiveDepartmentIds(object $user): array
{
$managedScope = $this->limitedBackofficeDepartmentScope($user);
if ($managedScope !== null) {
$departmentIds = array_values(array_unique(array_filter(
array_map('intval', $managedScope),
static fn (int $departmentId): bool => $departmentId > 0
)));
// Empty filter arrays are treated as "no filter" by legacy query
// helpers. A corrupt or missing managed assignment must deny all,
// never widen a limited employee to global department access.
return $departmentIds === [] ? [0] : $departmentIds;
}
if (!method_exists($user, 'getGroup')) {
return [];
}
return array_values(array_unique(array_map(
'intval',
(array)$user->getGroup()->getDepartments()
)));
}
public function requireLimitedBackofficeDepartmentAccess(object $user, int $departmentId): void
{
global $response;
$departmentScope = $this->limitedBackofficeDepartmentScope($user);
if ($departmentScope === null) {
return;
}
if (!in_array($departmentId, $departmentScope, true)) {
$response->error('Department resource not found', 404);
}
}
/**
* Get parameters as an array from the request
* @return array