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:
@@ -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