Files
api/services/nginx/app/routes/rolesRoute.php
T
Jeppe B ab6c3ba5b6 Fix route permission instance calls (#344)
## Root cause

`route_t::hasPermission()` and `requirePermission()` are instance
methods. Route code was invoking them with `self::`; the new XL Vask
hall-scope helper made that call from a genuinely static context,
causing PHP to throw:

`Non-static method routes\\xlvaskUsageLogsRoute::hasPermission() cannot
be called statically`

## Changes

- Invoke route permission methods through `$this` across all 273
executable legacy calls in 45 route classes.
- Make `xlvaskUsageLogsRoute::allowedHallIdsForUser()` an instance
helper and update all 13 callers.
- Preserve the existing all-scope and own-scope hall selection rules.
- Add a token-aware regression test that rejects executable
`self::hasPermission()` and `self::requirePermission()` calls, while
ignoring comments.
- Add focused XL Vask tests for global scanner hall scope and
group-limited own scope.
- Update affected route contract assertions to the instance-call form.

## Verification

- PHP lint: all 53 changed PHP files
- Focused PHPStan: changed XL Vask route and both new regression tests —
clean
- Focused regression slice: 58 passed, 748 assertions
- Full local unit suite: 1,300 passed, 9,442 assertions (1 unrelated
existing warning, 1 environment skip)
- Full local API suite: 285 passed, 11,704 assertions
- Exact-SHA GitHub Tests workflow: all 7 jobs passed (unit, API,
integration, legacy, edge gateway, and supporting checks)
- Independent exact-SHA QA gate: PASS, no findings
- Independent exact-SHA security gate: PASS, no findings
- Independent exact-SHA reviewer gate: PASS, no findings
- Remote comparison: exactly one commit ahead of
`40b104abed7723a7d1b7028190ecda0e7aeef829`; all 53 remote blob hashes
matched the reviewed worktree

## Delivery state

Draft only for human review. No merge or deployment is included. Qodana
is skipped while the PR remains draft and is therefore not represented
as a passed gate.
2026-08-04 16:04:41 +02:00

206 lines
9.1 KiB
PHP

<?php
namespace routes;
use classes\authentication;
use classes\limited_backoffice_service;
use objects\groups_o;
use objects\logs_o;
use traits\route_t;
class rolesRoute
{
use route_t;
public function run(): void
{
self::get('/roles', function () {
// Require the user to be logged in
global $response;
$this->requirePermission('list_roles');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('roles', 'global', 1, 0, 'ROLES', 'User accessed the roles list');
$groups = new groups_o();
// Check if the id parameter is set
if (self::isParametersSet(['id'])) {
self::requireType((int)self::getParameter('id'), self::type_int());
$groups->select((int)self::getParameter('id'));
$groups->requireSelected();
$response->success($groups->asArray());
}
// Return the list of roles
$response->success($groups->listObjectsWithPaginationIfSet(
function ($group) {
return [
'id' => (int)$group['id'],
'name' => (string)$group['name'],
'description' => (string)$group['description'],
'created_at' => (string)$group['created_at'],
'permissions' => (new groups_o())->select((int)$group['id'])->getPermissions()
];
}
));
} else {
(new logs_o())->add('roles', 'global', 0, 0, 'ROLES', 'User tried to access the roles list without a valid session');
$response->error('Invalid session', 400);
}
},
[
'list_roles' => 'List all roles'
]
);
self::post('/roles', function () {
// Require the user to be logged in
global $response;
$this->requirePermission('add_role');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('roles', 'global', 1, 0, 'ROLES', 'User added a role');
self::requireParameters(['name', 'description']);
self::requireType((string)self::getParameter('name'), 'string');
self::requireType((string)self::getParameter('description'), 'string');
$group = new groups_o();
$group->add(
(string)self::getParameter('name'),
(string)self::getParameter('description')
);
$response->success($group->asArray());
} else {
(new logs_o())->add('roles', 'global', 0, 0, 'ROLES', 'User tried to add a role without a valid session');
$response->error('Invalid session', 400);
}
},
[
'add_role' => 'Add a role'
]
);
self::put('/roles', function () {
// Require the user to be logged in
global $response;
$this->requirePermission('edit_role');
$user = (new authentication())->get_user();
if ($user) {
self::requireParameters(['id']);
self::requireType((int)self::getParameter('id'), self::type_int());
$group = new groups_o();
$group->select((int)self::getParameter('id'));
$group->requireSelected();
// Check which parameters are set and update them
if (self::isParametersSet(['name'])) {
$group->name->set((string)self::getParameter('name'));
}
if (self::isParametersSet(['description'])) {
$group->description->set((string)self::getParameter('description'));
}
(new logs_o())->add('roles', 'global', 1, $user->id, 'ROLES', 'User edited a role');
$response->success($group->asArray());
} else {
(new logs_o())->add('roles', 'global', 0, 0, 'ROLES', 'User tried to edit a role without a valid session');
$response->error('Invalid session', 400);
}
},
[
'edit_role' => 'Edit a role'
]
);
self::get('/roles/limited-backoffice-permission-templates', function () {
global $response;
$this->requirePermission('superuser');
$this->requirePermission('add_role_permission');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('roles', 'global', 1, $user->id, 'ROLES', 'User accessed limited backoffice role permission templates');
$response->success((new limited_backoffice_service())->rolePermissionTemplates());
} else {
(new logs_o())->add('roles', 'global', 0, 0, 'ROLES', 'User tried to access limited backoffice role permission templates without a valid session');
$response->error('Invalid session', 400);
}
},
[
'superuser' => 'Access the superuser interface',
'add_role_permission' => 'Add a permission to a role'
]
);
self::post('/roles/permissions', function () {
// Require the user to be logged in
global $response;
$this->requirePermission('add_role_permission');
$user = (new authentication())->get_user();
if ($user) {
self::requireParameters(['group_id', 'permission_id']);
self::requireType((int)self::getParameter('group_id'), self::type_int());
self::requireType((string)self::getParameter('permission_id'), 'string');
$group = new groups_o();
$group->select((int)self::getParameter('group_id'));
$group->requireSelected();
$group->addPermission((string)self::getParameter('permission_id'));
(new logs_o())->add('roles', 'global', 1, $user->id, 'ROLES', 'User added a permission to a role');
$response->success($group->asArray());
} else {
(new logs_o())->add('roles', 'global', 0, 0, 'ROLES', 'User tried to add a permission to a role without a valid session');
$response->error('Invalid session', 400);
}
},
[
'add_role_permission' => 'Add a permission to a role'
]
);
self::delete('/roles/permissions', function () {
// Require the user to be logged in
global $response;
$this->requirePermission('delete_role_permission');
$user = (new authentication())->get_user();
if ($user) {
self::requireParameters(['group_id', 'permission_id']);
self::requireType((int)self::getParameter('group_id'), self::type_int());
self::requireMinLength('group_id', 1);
self::requireType((string)self::getParameter('permission_id'), 'string');
self::requireMinLength('permission_id', 1);
$group = new groups_o();
$group->select((int)self::getParameter('group_id'));
$group->requireSelected();
$group->removePermission((string)self::getParameter('permission_id'));
(new logs_o())->add('roles', 'global', 1, $user->id, 'ROLES', 'User deleted a permission from a role');
$response->success($group->asArray());
} else {
(new logs_o())->add('roles', 'global', 0, 0, 'ROLES', 'User tried to delete a permission from a role without a valid session');
$response->error('Invalid session', 400);
}
},
[
'delete_role_permission' => 'Remove a permission from a role'
]
);
self::post('/roles/clone', function () {
// Require the user to be logged in
global $response;
$this->requirePermission('clone_role');
$user = (new authentication())->get_user();
if ($user) {
self::requireParameters(['id']);
self::requireType((int)self::getParameter('id'), self::type_int());
$group = new groups_o();
$group->select((int)self::getParameter('id'));
$group->requireSelected();
$group->clone($group->name->value() . ' - Klon', (string)$group->description->value());
(new logs_o())->add('roles', 'global', 1, $user->id, 'ROLES', 'User cloned a role');
$response->success($group->asArray());
} else {
(new logs_o())->add('roles', 'global', 0, 0, 'ROLES', 'User tried to clone a role without a valid session');
$response->error('Invalid session', 400);
}
},
[
'clone_role' => 'Clone a role'
]
);
}
}