Refactor route_t and orderBookingRoute to simplify permission handling
- Add reusable helpers: `isOwnCustomerContext`, `resolveEffectiveCustomerNumber`, and `allowOwnOrDepartmentAccess` in `route_t` for streamlined permission checks. - Replace inlined permission logic in `orderBookingRoute` with common helpers for creating, viewing, editing, and deleting bookings. - Localize permission names, descriptions, and labels to Danish across relevant modules. - Improve error handling and simplify context resolution for subuser and department-level permissions.
This commit is contained in:
+6
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -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);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user