93 lines
2.9 KiB
PHP
93 lines
2.9 KiB
PHP
<?php
|
|
|
|
app_require('traits/route_t.php');
|
|
|
|
use traits\route_t;
|
|
|
|
class AllowOwnOrDepartmentAccessForbiddenHost
|
|
{
|
|
use route_t;
|
|
|
|
/** @var array<string, bool> */
|
|
public array $permissionsByKey = [];
|
|
public bool $ownContext = true;
|
|
/** @var array<int, string>|null */
|
|
public ?array $lastForbidden = null;
|
|
public bool $departmentAccessChecked = false;
|
|
|
|
public function __construct()
|
|
{
|
|
// no-op for unit tests
|
|
}
|
|
|
|
public function hasPermission(string|\classes\permission_node $permission, int $customer_number = null): bool
|
|
{
|
|
$key = is_string($permission) ? $permission : (string)$permission->permission;
|
|
return (bool)($this->permissionsByKey[$key] ?? false);
|
|
}
|
|
|
|
public function isOwnCustomerContext(int $targetCustomerNumber): bool
|
|
{
|
|
return $this->ownContext;
|
|
}
|
|
|
|
public function requireDepartmentAccess(string $department, string|null $permission = null): void
|
|
{
|
|
$this->departmentAccessChecked = true;
|
|
}
|
|
|
|
protected function emitForbidden(array $permissions): void
|
|
{
|
|
$normalized = [];
|
|
foreach ($permissions as $permission) {
|
|
$key = is_string($permission) ? trim($permission) : trim((string)$permission->permission);
|
|
if ($key !== '') {
|
|
$normalized[] = $key;
|
|
}
|
|
}
|
|
$this->lastForbidden = array_values(array_unique($normalized));
|
|
throw new RuntimeException('forbidden');
|
|
}
|
|
}
|
|
|
|
it('returns both own and elevated permissions when both are missing', function (): void {
|
|
$host = new AllowOwnOrDepartmentAccessForbiddenHost();
|
|
$host->permissionsByKey = [
|
|
'perm_own' => false,
|
|
'perm_other' => false,
|
|
];
|
|
|
|
expect(fn() => $host->allowOwnOrDepartmentAccess('perm_own', 'perm_other', 101, null))
|
|
->toThrow(RuntimeException::class, 'forbidden');
|
|
|
|
expect($host->lastForbidden)->toBe(['perm_own', 'perm_other']);
|
|
});
|
|
|
|
it('returns only elevated permission when own permission exists but own context fails', function (): void {
|
|
$host = new AllowOwnOrDepartmentAccessForbiddenHost();
|
|
$host->permissionsByKey = [
|
|
'perm_own' => true,
|
|
'perm_other' => false,
|
|
];
|
|
$host->ownContext = false;
|
|
|
|
expect(fn() => $host->allowOwnOrDepartmentAccess('perm_own', 'perm_other', 202, null))
|
|
->toThrow(RuntimeException::class, 'forbidden');
|
|
|
|
expect($host->lastForbidden)->toBe(['perm_other']);
|
|
});
|
|
|
|
it('allows elevated permission path without forbidden and validates department access', function (): void {
|
|
$host = new AllowOwnOrDepartmentAccessForbiddenHost();
|
|
$host->permissionsByKey = [
|
|
'perm_own' => false,
|
|
'perm_other' => true,
|
|
];
|
|
|
|
$allowed = $host->allowOwnOrDepartmentAccess('perm_own', 'perm_other', 303, 77);
|
|
|
|
expect($allowed)->toBeTrue();
|
|
expect($host->departmentAccessChecked)->toBeTrue();
|
|
expect($host->lastForbidden)->toBeNull();
|
|
});
|