diff --git a/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_bookings.php b/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_bookings.php index 2d437176..f337db04 100644 --- a/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_bookings.php +++ b/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_bookings.php @@ -11,13 +11,13 @@ class subusers_permission_nodes_bookings extends subusers_permission_nodes public function __construct() { parent::__construct( - 'Bookings', - 'Bookings management permissions' + 'Bookinger', + 'Tilladelser til håndtering af bookinger' ); - $this->addNode(subusers_permission_node_key::BOOKINGS_LIST, 'List Bookings', 'Allows the user to view the list of bookings', subusers_permission_type::VIEW, false); - $this->addNode(subusers_permission_node_key::BOOKINGS_EDIT, 'Edit Bookings', 'Allows the user to edit existing bookings', subusers_permission_type::EDIT, false); - $this->addNode(subusers_permission_node_key::BOOKINGS_DELETE, 'Delete Bookings', 'Allows the user to delete existing bookings', subusers_permission_type::DELETE, false); - $this->addNode(subusers_permission_node_key::BOOKINGS_ADD, 'Add Bookings', 'Allows the user to add new bookings', subusers_permission_type::ADD, false); + $this->addNode(subusers_permission_node_key::BOOKINGS_LIST, 'Vis bookinger', 'Giver brugeren mulighed for at se listen over bookinger', subusers_permission_type::VIEW, false); + $this->addNode(subusers_permission_node_key::BOOKINGS_EDIT, 'Rediger bookinger', 'Giver brugeren mulighed for at redigere eksisterende bookinger', subusers_permission_type::EDIT, false); + $this->addNode(subusers_permission_node_key::BOOKINGS_DELETE, 'Slet bookinger', 'Giver brugeren mulighed for at slette eksisterende bookinger', subusers_permission_type::DELETE, false); + $this->addNode(subusers_permission_node_key::BOOKINGS_ADD, 'Tilføj bookinger', 'Giver brugeren mulighed for at tilføje nye bookinger', subusers_permission_type::ADD, false); } } \ No newline at end of file diff --git a/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_orders.php b/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_orders.php index f7dd417a..12f696e9 100644 --- a/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_orders.php +++ b/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_orders.php @@ -11,11 +11,11 @@ class subusers_permission_nodes_orders extends subusers_permission_nodes public function __construct() { parent::__construct( - 'Orders', - 'Orders management permissions' + 'Ordrer', + 'Tilladelser til håndtering af ordrer' ); - $this->addNode(subusers_permission_node_key::ORDERS_LIST, 'View Orders', 'Allows the user to view the list of orders and order details', subusers_permission_type::VIEW, false); - $this->addNode(subusers_permission_node_key::ORDERS_EDIT, 'Edit Orders', 'Allows the user to edit existing orders', subusers_permission_type::EDIT, false); + $this->addNode(subusers_permission_node_key::ORDERS_LIST, 'Vis ordrer', 'Giver brugeren mulighed for at se listen over ordrer og ordredetaljer', subusers_permission_type::VIEW, false); + $this->addNode(subusers_permission_node_key::ORDERS_EDIT, 'Rediger ordrer', 'Giver brugeren mulighed for at redigere eksisterende ordrer', subusers_permission_type::EDIT, false); } } \ No newline at end of file diff --git a/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_selfserve.php b/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_selfserve.php index 8a8fe2cc..661f050e 100644 --- a/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_selfserve.php +++ b/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_selfserve.php @@ -11,10 +11,10 @@ class subusers_permission_nodes_selfserve extends subusers_permission_nodes public function __construct() { parent::__construct( - 'Selfserve', - 'Self Serve Permissions', + 'Selvbetjening', + 'Tilladelser til selvbetjening', ); - $this->addNode(subusers_permission_node_key::SELFSERVE_ADD, 'Self Serve Add', 'Add self serve', subusers_permission_type::ADD, false); + $this->addNode(subusers_permission_node_key::SELFSERVE_ADD, 'Tilføj selvbetjening', 'Tilføj selvbetjening', subusers_permission_type::ADD, false); } } \ No newline at end of file diff --git a/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_subusers.php b/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_subusers.php index 5a945005..898d557b 100644 --- a/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_subusers.php +++ b/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_subusers.php @@ -11,13 +11,13 @@ class subusers_permission_nodes_subusers extends subusers_permission_nodes public function __construct() { parent::__construct( - 'Subusers', - 'Set permissions related to subusers, including managing subusers and their permissions' + 'Underbrugere', + 'Angiv tilladelser relateret til underbrugere, herunder administration af underbrugere og deres tilladelser' ); - $this->addNode(subusers_permission_node_key::SUBUSERS_LIST, 'List Subusers', 'Allows the user to view the list of subusers', subusers_permission_type::VIEW, false); - $this->addNode(subusers_permission_node_key::SUBUSERS_EDIT, 'Edit Subusers', 'Allows the user to edit existing subusers', subusers_permission_type::EDIT, false); - $this->addNode(subusers_permission_node_key::SUBUSERS_DELETE, 'Delete Subusers', 'Allows the user to delete existing subusers', subusers_permission_type::DELETE, false); - $this->addNode(subusers_permission_node_key::SUBUSERS_ADD, 'Add Subusers', 'Allows the user to add new subusers', subusers_permission_type::ADD, false); + $this->addNode(subusers_permission_node_key::SUBUSERS_LIST, 'Vis underbrugere', 'Giver brugeren mulighed for at se listen over underbrugere', subusers_permission_type::VIEW, false); + $this->addNode(subusers_permission_node_key::SUBUSERS_EDIT, 'Rediger underbrugere', 'Giver brugeren mulighed for at redigere eksisterende underbrugere', subusers_permission_type::EDIT, false); + $this->addNode(subusers_permission_node_key::SUBUSERS_DELETE, 'Slet underbrugere', 'Giver brugeren mulighed for at slette eksisterende underbrugere', subusers_permission_type::DELETE, false); + $this->addNode(subusers_permission_node_key::SUBUSERS_ADD, 'Tilføj underbrugere', 'Giver brugeren mulighed for at tilføje nye underbrugere', subusers_permission_type::ADD, false); } } \ No newline at end of file diff --git a/services/nginx/app/routes/orderBookingRoute.php b/services/nginx/app/routes/orderBookingRoute.php index 09dd627d..f7eae14c 100644 --- a/services/nginx/app/routes/orderBookingRoute.php +++ b/services/nginx/app/routes/orderBookingRoute.php @@ -37,45 +37,18 @@ class orderBookingRoute $pickup = self::getTargetPickup(); // Bool | Null $items = self::getTargetItems(); // Array of order_items_o objects /** - * Authentication & Permissions + * Permissions (clean helper) */ - $auth = new authentication(); - $user = $auth->get_user(); // Classic user (may be false if a subuser token is used) - $permission_own = self::definePermission('add_own_bookings', subusers_permission_node_key::BOOKINGS_ADD); // Subuser-aware: can add for own customer only - $permission_other = self::definePermission('add_bookings'); // Admin/department scope: can add for any customer within permitted departments - $has_permission_own = self::hasPermission($permission_own); - $has_permission_other = self::hasPermission($permission_other); - $has_permission = false; // Updated below - - // Own booking creation (either classic user for own customer or subuser with proper node on target customer) - if ($has_permission_own) { - $targetCustomer = (int)$customer_number->customer_number->value(); - $subuser = $auth->get_subuser(); - if ($subuser !== false) { - // For subusers ensure provided customer_number matches X-Customer-Number target context - $sub_target = $auth->get_subuser_customer_number_target(); - if ((int)$sub_target !== $targetCustomer) { - $response->error('You do not have permission to create bookings for this customer.', 403); - } - $has_permission = true; // Node permission already validated via hasPermission above - } else { - // Classic user: ensure creating for own customer only in the "own" permission path - if ($user !== false && (int)$user->customer_number->value() === $targetCustomer) { - $has_permission = true; - } - } - } - - // Department/admin booking creation for other customers - if (!$has_permission && $has_permission_other) { - // Must be within permitted department - self::requireDepartmentAccess((int)$department->id); - $has_permission = true; - } - - if (!$has_permission) { - $response->error('You do not have permission to create this order booking.', 403); - } + $permission_own = self::definePermission('add_own_bookings', subusers_permission_node_key::BOOKINGS_ADD); + $permission_other = self::definePermission('add_bookings'); + self::allowOwnOrDepartmentAccess( + $permission_own, + $permission_other, + (int)$customer_number->customer_number->value(), + (int)$department->id, + null, + 'You do not have permission to create this order booking.' + ); /** * Input data */ @@ -112,45 +85,25 @@ class orderBookingRoute * Parameters */ $object = self::getTargetObject(); - /** - * Authentication - */ + /** Authentication */ $auth = new authentication(); $user = $auth->get_user(); /** * Permissions */ - $permission_own = self::definePermission('list_own_bookings', subusers_permission_node_key::BOOKINGS_LIST); // Only permits access to bookings with the user's customer number - $permission_other = self::definePermission('list_bookings'); // Requires access to the department as an admin to view other users' bookings - $has_permission_own = self::hasPermission($permission_own); + $permission_own = self::definePermission('list_own_bookings', subusers_permission_node_key::BOOKINGS_LIST); + $permission_other = self::definePermission('list_bookings'); $has_permission_other = self::hasPermission($permission_other); - $has_permission = false; // Updated below - // Check if the user has permission to view their own bookings - if ($has_permission_own) { - if (!$object) { - $has_permission = true; - } else { - $subuser = $auth->get_subuser(); - if ($subuser !== false) { - $sub_target = $auth->get_subuser_customer_number_target(); - if ((int)$object->customer_number->value() === (int)$sub_target) { - $has_permission = true; - } - } elseif ($user !== false && (int)$object->customer_number->value() === (int)$user->customer_number->value()) { - $has_permission = true; - } - } - } - // Check if the user has permission to view other users' bookings - if (!$has_permission && $has_permission_other) { - if (!empty($object)) self::requireDepartmentAccess((int)$object->department->value()); - // Check if the user has access to the department - $has_permission = true; - } - // If the user does not have permission, return an error - if (!$has_permission) { - $response->error('You do not have permission to view this order booking.', 403); - } + $targetCustomer = !empty($object) ? (int)$object->customer_number->value() : null; + $deptId = !empty($object) ? (int)$object->department->value() : null; + self::allowOwnOrDepartmentAccess( + $permission_own, + $permission_other, + $targetCustomer, + $deptId, + null, + 'You do not have permission to view this order booking.' + ); /** * Return the object */ @@ -159,14 +112,15 @@ class orderBookingRoute * Return the filtered list of objects */ $object = new order_bookings_o(); // New object for listing + $effectiveCustomer = self::resolveEffectiveCustomerNumber(); $response->success($object->listObjectsWithPaginationIfSet( function ($booking) { return (new order_bookings_o())->select((int)$booking['id'])->asArray(); }, $object->forceRestrictFilters([ - ...($has_permission_other ? [ + ...($has_permission_other && $user !== false ? [ 'department' => $user->getGroup()->getDepartments() ] : []), - ...(!$has_permission_other ? [ - 'customer_number' => [(int)$user->customer_number->value()] + ...(!$has_permission_other && $effectiveCustomer !== null ? [ + 'customer_number' => [(int)$effectiveCustomer] ] : []) ]) )); @@ -196,9 +150,7 @@ class orderBookingRoute $pickup = self::getTargetPickup(false); // Bool | Null $items = self::getTargetItems(false); // Array of order_items_o objects $order_id = self::getTargetOrderId(false); // Int | Null - /** - * Authentication - */ + /** Authentication */ $auth = new authentication(); $user = $auth->get_user(); /** @@ -207,40 +159,21 @@ class orderBookingRoute if (!$object || !$object->exists()) { $response->error('Order booking does not exist.', 400); } - $permission_own = self::definePermission('edit_own_bookings', subusers_permission_node_key::BOOKINGS_EDIT); // Only permits editing bookings with the user's customer number (subusers node aware) - $permission_other = self::definePermission('edit_bookings'); // Requires access to the department as an admin to edit other users' bookings - $has_permission_own = self::hasPermission($permission_own); + $permission_own = self::definePermission('edit_own_bookings', subusers_permission_node_key::BOOKINGS_EDIT); + $permission_other = self::definePermission('edit_bookings'); $has_permission_other = self::hasPermission($permission_other); - $has_permission = false; // Updated below - // Check if the user has permission to edit their own bookings - if ($has_permission_own) { - $isOwn = false; - $subuser = $auth->get_subuser(); - if ($subuser !== false) { - $sub_target = $auth->get_subuser_customer_number_target(); - $isOwn = ((int)$object->customer_number->value() === (int)$sub_target); - } elseif ($user !== false) { - $isOwn = ((int)$object->customer_number->value() === (int)$user->customer_number->value()); - } - if ($isOwn) { - // Check if the booking has a transaction associated with it - // If this is the case, prevent user from editing the booking - if (!$has_permission_other && $object->hasTransaction()) { - $response->error('You cannot edit this order booking because it has a transaction associated with it.', 403); - } - $has_permission = true; - } - } - // Check if the user has permission to edit other users' bookings - if (!$has_permission && $has_permission_other) { - self::requireDepartmentAccess((int)$object->department->value()); - // Check if the user has access to the department - $has_permission = true; - } - // If the user does not have permission, return an error - if (!$has_permission) { - $response->error('You do not have permission to edit this order booking.', 403); - } + $ownGuard = function () use ($has_permission_other, $object) { + // Block own edits when a transaction exists, unless admin/department permission is present + return $has_permission_other || !$object->hasTransaction(); + }; + self::allowOwnOrDepartmentAccess( + $permission_own, + $permission_other, + (int)$object->customer_number->value(), + (int)$object->department->value(), + $ownGuard, + 'You do not have permission to edit this order booking.' + ); /** * Update the object */ @@ -279,9 +212,7 @@ class orderBookingRoute * Parameters */ $object = self::getTargetObject(); - /** - * Authentication - */ + /** Authentication */ $auth = new authentication(); $user = $auth->get_user(); /** @@ -290,33 +221,16 @@ class orderBookingRoute if (!$object || !$object->exists()) { $response->error('Order booking does not exist.', 400); } - $permission_own = self::definePermission('delete_own_bookings', subusers_permission_node_key::BOOKINGS_DELETE); // Only permits deleting bookings with the user's customer number (subusers node aware) - $permission_other = self::definePermission('delete_bookings'); // Requires access to the department as an admin to delete other users' bookings - $has_permission_own = self::hasPermission($permission_own); - $has_permission_other = self::hasPermission($permission_other); - $has_permission = false; // Updated below - // Check if the user has permission to delete their own bookings - if ($has_permission_own) { - $subuser = $auth->get_subuser(); - if ($subuser !== false) { - $sub_target = $auth->get_subuser_customer_number_target(); - if ((int)$object->customer_number->value() === (int)$sub_target) { - $has_permission = true; - } - } elseif ($user !== false && (int)$object->customer_number->value() === (int)$user->customer_number->value()) { - $has_permission = true; - } - } - // Check if the user has permission to delete other users' bookings - if (!$has_permission && $has_permission_other) { - self::requireDepartmentAccess((int)$object->department->value()); - // Check if the user has access to the department - $has_permission = true; - } - // If the user does not have permission, return an error - if (!$has_permission) { - $response->error('You do not have permission to delete this order booking.', 403); - } + $permission_own = self::definePermission('delete_own_bookings', subusers_permission_node_key::BOOKINGS_DELETE); + $permission_other = self::definePermission('delete_bookings'); + self::allowOwnOrDepartmentAccess( + $permission_own, + $permission_other, + (int)$object->customer_number->value(), + (int)$object->department->value(), + null, + 'You do not have permission to delete this order booking.' + ); /** * Delete the object */ diff --git a/services/nginx/app/traits/route_t.php b/services/nginx/app/traits/route_t.php index 6d03652e..84f3a275 100644 --- a/services/nginx/app/traits/route_t.php +++ b/services/nginx/app/traits/route_t.php @@ -395,6 +395,106 @@ trait route_t return $this->evaluatePermission($permission, $customer_number, false); } + /** + * Determine if the current principal (classic user or subuser) acts on their own customer context. + * - For classic users: compares against the authenticated user's customer_number + * - For subusers: compares against X-Customer-Number target header + */ + public function isOwnCustomerContext(int $targetCustomerNumber): bool + { + try { + $auth = new authentication(); + $sub = $auth->get_subuser(); + if ($sub !== false) { + $tgt = $auth->get_subuser_customer_number_target(); + return ((int)$tgt === (int)$targetCustomerNumber); + } + $user = $auth->get_user(); + if ($user !== false) { + return ((int)$user->customer_number->value() === (int)$targetCustomerNumber); + } + } catch (Exception) { + // fall through + } + return false; + } + + /** + * Resolve the most relevant customer number for the current request context. + * Priority: classic user -> X-Customer-Number header -> query/body customer_number + */ + public function resolveEffectiveCustomerNumber(): ?int + { + try { + $auth = new authentication(); + $user = $auth->get_user(); + if ($user !== false && isset($user->customer_number)) { + return (int)$user->customer_number->value(); + } + $sub = $auth->get_subuser(); + if ($sub !== false) { + $tgt = $auth->get_subuser_customer_number_target(); + if ($tgt !== false && $tgt !== null) return (int)$tgt; + } + // Fallbacks + if (isset($_GET['customer_number'])) return (int)$_GET['customer_number']; + if (isset($_POST['customer_number'])) return (int)$_POST['customer_number']; + } catch (Exception) { + // ignore + } + return null; + } + + /** + * Common pattern helper: allow via own-permission (with optional guard) or via department/admin permission. + * If neither path allows, respond with 403 and provided message. + * + * @param string|permission_node $permissionOwn Own-scope permission (often linked to a subuser node) + * @param string|permission_node $permissionOther Department/admin permission + * @param int|null $targetCustomerNumber Customer number to validate "own" scope against (null means list context) + * @param int|null $departmentId Department id for admin path (will be validated when provided) + * @param callable|null $ownGuard Optional additional guard for own path. Return true to allow, false to deny own-path. + * @param string|null $denyMessage Message to return on deny (defaults to generic) + * @return bool True if access is allowed (also throws on deny) + */ + public function allowOwnOrDepartmentAccess( + string|permission_node $permissionOwn, + string|permission_node $permissionOther, + ?int $targetCustomerNumber, + ?int $departmentId, + ?callable $ownGuard = null, + ?string $denyMessage = null + ): bool { + global $response; + $allowed = false; + $hasOwn = $this->hasPermission($permissionOwn); + $hasOther = $this->hasPermission($permissionOther); + + // Try own path first + if ($hasOwn) { + $isOwnContext = ($targetCustomerNumber === null) ? true : $this->isOwnCustomerContext((int)$targetCustomerNumber); + if ($isOwnContext) { + $guardOk = $ownGuard ? (bool)call_user_func($ownGuard) : true; + if ($guardOk) { + $allowed = true; + } + } + } + + // Fallback to department/admin path + if (!$allowed && $hasOther) { + if ($departmentId !== null) { + $this->requireDepartmentAccess((int)$departmentId); + } + $allowed = true; + } + + if (!$allowed) { + $response->error($denyMessage ?? 'Permission denied.', 403); + } + return $allowed; + } + /** * Require parameter to be a positive integer * @param int $value The value to check