Adds a scope-based access control layer to all 81 existing API routes. Sits alongside existing session-cookie auth (does not replace it). What this PR does: - Audits every existing route and documents required scope per route (see documentation/auth/route-scope-audit.md) - Adds classes/auth/scope.php with 10 scope constants and role→scope defaults - Adds classes/auth/scope_middleware.php with requireScope/requireAnyScope/requireRole - Applies require*() calls to all 81 existing routes - Adds ScopeMiddlewareTest (unit, 178 lines) and RouteScopeTest (integration, 212 lines) Coexistence note: This branch's classes/auth/scope.php is a stub that will be replaced by classes/auth/scope_registry.php (from TRU-145 / PR #396) when that PR merges first. The two have compatible APIs. Refs: TRU-149
354 lines
16 KiB
PHP
354 lines
16 KiB
PHP
<?php
|
|
|
|
namespace routes;
|
|
|
|
use classes\department_gate_config;
|
|
use classes\department_relay_config;
|
|
use classes\authentication;
|
|
use classes\response;
|
|
use objects\department_gates_o;
|
|
use objects\department_relays_o;
|
|
use objects\departments_o;
|
|
use objects\logs_o;
|
|
use traits\route_t;
|
|
|
|
use app\auth\Scope;
|
|
use app\auth\ScopeMiddleware;
|
|
|
|
class departmentGatesRelaysRoute
|
|
{
|
|
use route_t;
|
|
|
|
public function run(): void
|
|
{
|
|
$this->get('/department/gates', function () {
|
|
ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/gates');
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('list_department_gates');
|
|
$user = (new authentication())->get_user();
|
|
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
|
|
$gates_o = new department_gates_o();
|
|
|
|
if (self::isParametersSet(['id'])) {
|
|
self::requireType(self::getParameter('id'), self::type_int());
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
$gate = (new department_gates_o())->select((int)self::getParameter('id'));
|
|
if (!$gate->exists()) {
|
|
$response->error('Department gate not found', 404);
|
|
}
|
|
self::requireDepartmentAccess((int)$gate->department->value());
|
|
$response->success($gate->asArray());
|
|
}
|
|
|
|
$result = $gates_o->listObjectsWithPaginationIfSet(
|
|
function ($tmp_object_array) {
|
|
return (new department_gates_o())->select((int)$tmp_object_array['id'])->asArray();
|
|
},
|
|
$gates_o->forceRestrictFilters([
|
|
'department' => $user->getGroup()->getDepartments(),
|
|
])
|
|
);
|
|
|
|
(new logs_o())->add('department_gates', 'global', 1, $user->id, 'LIST_DEPARTMENT_GATES', 'User listed department gates');
|
|
$response->success($result);
|
|
}, [
|
|
'list_department_gates' => 'List department gates in departments the user has access to',
|
|
]);
|
|
|
|
$this->post('/department/gates', function () {
|
|
ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/gates');
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('add_department_gate');
|
|
$user = (new authentication())->get_user();
|
|
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
|
|
self::requireParameters(['department', 'is_entrance', 'is_exit', 'name', 'config']);
|
|
self::requireType(self::getParameter('department'), self::type_int());
|
|
self::requireMinValue((int)self::getParameter('department'), 1);
|
|
self::requireType(self::getParameter('is_entrance'), self::type_bool());
|
|
self::requireType(self::getParameter('is_exit'), self::type_bool());
|
|
self::requireType(self::getParameter('name'), self::type_string());
|
|
self::requireTypeIn(self::getParameter('config'), ['array', 'object']);
|
|
self::requireDepartmentAccess((int)self::getParameter('department'));
|
|
|
|
$department = (new departments_o())->select((int)self::getParameter('department'));
|
|
if (!$department->exists()) {
|
|
$response->error('Department not found', 404);
|
|
}
|
|
|
|
$config = self::getParameter('config');
|
|
$config = is_array($config) ? $config : (array)$config;
|
|
|
|
$gateConfig = new department_gate_config($config);
|
|
$gateConfig->validate();
|
|
|
|
$gate = (new department_gates_o())->add(
|
|
$department,
|
|
(bool)self::getParameter('is_entrance'),
|
|
(bool)self::getParameter('is_exit'),
|
|
(string)self::getParameter('name'),
|
|
$gateConfig,
|
|
);
|
|
|
|
(new logs_o())->add('department_gates', 'global', 1, $user->id, 'ADD_DEPARTMENT_GATE', 'User added department gate');
|
|
$response->success($gate->asArray(), 201);
|
|
}, [
|
|
'add_department_gate' => 'Add department gate in departments the user has access to',
|
|
]);
|
|
|
|
$this->put('/department/gates', function () {
|
|
ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/gates');
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('update_department_gate');
|
|
$user = (new authentication())->get_user();
|
|
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
|
|
self::requireParameters(['id']);
|
|
self::requireType(self::getParameter('id'), self::type_int());
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
|
|
$gate = (new department_gates_o())->select((int)self::getParameter('id'));
|
|
if (!$gate->exists()) {
|
|
$response->error('Department gate not found', 404);
|
|
}
|
|
self::requireDepartmentAccess((int)$gate->department->value());
|
|
|
|
$data = [];
|
|
if (self::isParametersSet(['is_entrance'])) {
|
|
self::requireType(self::getParameter('is_entrance'), self::type_bool());
|
|
$data['is_entrance'] = (bool)self::getParameter('is_entrance');
|
|
}
|
|
if (self::isParametersSet(['is_exit'])) {
|
|
self::requireType(self::getParameter('is_exit'), self::type_bool());
|
|
$data['is_exit'] = (bool)self::getParameter('is_exit');
|
|
}
|
|
if (self::isParametersSet(['name'])) {
|
|
self::requireType(self::getParameter('name'), self::type_string());
|
|
$data['name'] = (string)self::getParameter('name');
|
|
}
|
|
if (self::isParametersSet(['config'])) {
|
|
self::requireTypeIn(self::getParameter('config'), ['array', 'object']);
|
|
$config = self::getParameter('config');
|
|
$configArray = is_array($config) ? $config : (array)$config;
|
|
$gateConfig = new department_gate_config($configArray);
|
|
$gateConfig->validate();
|
|
$data['config'] = $gateConfig->toArray();
|
|
}
|
|
|
|
$nextIsEntrance = $data['is_entrance'] ?? (bool)$gate->is_entrance->value();
|
|
$nextIsExit = $data['is_exit'] ?? (bool)$gate->is_exit->value();
|
|
if (!$nextIsEntrance && !$nextIsExit) {
|
|
$response->error('Gate must be either entrance, exit or both', 400);
|
|
}
|
|
|
|
if (empty($data)) {
|
|
$response->error('No data to update', 400);
|
|
}
|
|
|
|
$gate->update($data);
|
|
(new logs_o())->add('department_gates', 'global', 1, $user->id, 'UPDATE_DEPARTMENT_GATE', 'User updated department gate');
|
|
$response->success($gate->asArray());
|
|
}, [
|
|
'update_department_gate' => 'Update department gate in departments the user has access to',
|
|
]);
|
|
|
|
$this->delete('/department/gates', function () {
|
|
ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/gates');
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('delete_department_gate');
|
|
$user = (new authentication())->get_user();
|
|
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
|
|
self::requireParameters(['id']);
|
|
self::requireType(self::getParameter('id'), self::type_int());
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
|
|
$gate = (new department_gates_o())->select((int)self::getParameter('id'));
|
|
if (!$gate->exists()) {
|
|
$response->error('Department gate not found', 404);
|
|
}
|
|
self::requireDepartmentAccess((int)$gate->department->value());
|
|
$gate->delete();
|
|
|
|
(new logs_o())->add('department_gates', 'global', 1, $user->id, 'DELETE_DEPARTMENT_GATE', 'User deleted department gate');
|
|
$response->success('Department gate deleted');
|
|
}, [
|
|
'delete_department_gate' => 'Delete department gate in departments the user has access to',
|
|
]);
|
|
|
|
$this->get('/department/relays', function () {
|
|
ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/relays');
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('list_department_relays');
|
|
$user = (new authentication())->get_user();
|
|
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
|
|
$relays_o = new department_relays_o();
|
|
|
|
if (self::isParametersSet(['id'])) {
|
|
self::requireType(self::getParameter('id'), self::type_int());
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
$relay = (new department_relays_o())->select((int)self::getParameter('id'));
|
|
if (!$relay->exists()) {
|
|
$response->error('Department relay not found', 404);
|
|
}
|
|
self::requireDepartmentAccess((int)$relay->department->value());
|
|
$response->success($relay->asArray());
|
|
}
|
|
|
|
$result = $relays_o->listObjectsWithPaginationIfSet(
|
|
function ($tmp_object_array) {
|
|
return (new department_relays_o())->select((int)$tmp_object_array['id'])->asArray();
|
|
},
|
|
$relays_o->forceRestrictFilters([
|
|
'department' => $user->getGroup()->getDepartments(),
|
|
])
|
|
);
|
|
|
|
(new logs_o())->add('department_relays', 'global', 1, $user->id, 'LIST_DEPARTMENT_RELAYS', 'User listed department relays');
|
|
$response->success($result);
|
|
}, [
|
|
'list_department_relays' => 'List department relays in departments the user has access to',
|
|
]);
|
|
|
|
$this->post('/department/relays', function () {
|
|
ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/relays');
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('add_department_relay');
|
|
$user = (new authentication())->get_user();
|
|
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
|
|
self::requireParameters(['department', 'relay_id', 'name', 'type', 'config']);
|
|
self::requireType(self::getParameter('department'), self::type_int());
|
|
self::requireMinValue((int)self::getParameter('department'), 1);
|
|
self::requireType(self::getParameter('relay_id'), self::type_string());
|
|
self::requireType(self::getParameter('name'), self::type_string());
|
|
self::requireType(self::getParameter('type'), self::type_string());
|
|
self::requireTypeIn(self::getParameter('config'), ['array', 'object']);
|
|
self::requireDepartmentAccess((int)self::getParameter('department'));
|
|
|
|
$department = (new departments_o())->select((int)self::getParameter('department'));
|
|
if (!$department->exists()) {
|
|
$response->error('Department not found', 404);
|
|
}
|
|
|
|
$config = self::getParameter('config');
|
|
$config = is_array($config) ? $config : (array)$config;
|
|
|
|
$relayConfig = new department_relay_config($config);
|
|
|
|
$relay = (new department_relays_o())->add(
|
|
$department,
|
|
(string)self::getParameter('relay_id'),
|
|
(string)self::getParameter('name'),
|
|
(string)self::getParameter('type'),
|
|
$relayConfig,
|
|
);
|
|
|
|
(new logs_o())->add('department_relays', 'global', 1, $user->id, 'ADD_DEPARTMENT_RELAY', 'User added department relay');
|
|
$response->success($relay->asArray(), 201);
|
|
}, [
|
|
'add_department_relay' => 'Add department relay in departments the user has access to',
|
|
]);
|
|
|
|
$this->put('/department/relays', function () {
|
|
ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/relays');
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('update_department_relay');
|
|
$user = (new authentication())->get_user();
|
|
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
|
|
self::requireParameters(['id']);
|
|
self::requireType(self::getParameter('id'), self::type_int());
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
|
|
$relay = (new department_relays_o())->select((int)self::getParameter('id'));
|
|
if (!$relay->exists()) {
|
|
$response->error('Department relay not found', 404);
|
|
}
|
|
self::requireDepartmentAccess((int)$relay->department->value());
|
|
|
|
$data = [];
|
|
if (self::isParametersSet(['relay_id'])) {
|
|
self::requireType(self::getParameter('relay_id'), self::type_string());
|
|
$data['relay_id'] = (string)self::getParameter('relay_id');
|
|
}
|
|
if (self::isParametersSet(['name'])) {
|
|
self::requireType(self::getParameter('name'), self::type_string());
|
|
$data['name'] = (string)self::getParameter('name');
|
|
}
|
|
if (self::isParametersSet(['type'])) {
|
|
self::requireType(self::getParameter('type'), self::type_string());
|
|
$data['type'] = (string)self::getParameter('type');
|
|
}
|
|
if (self::isParametersSet(['config'])) {
|
|
self::requireTypeIn(self::getParameter('config'), ['array', 'object']);
|
|
$config = self::getParameter('config');
|
|
$configArray = is_array($config) ? $config : (array)$config;
|
|
$relayConfig = new department_relay_config($configArray);
|
|
$relayConfig->validate($data['type'] ?? (string)$relay->type->value());
|
|
$data['config'] = $relayConfig->toArray();
|
|
}
|
|
|
|
if (empty($data)) {
|
|
$response->error('No data to update', 400);
|
|
}
|
|
|
|
$relay->update($data);
|
|
(new logs_o())->add('department_relays', 'global', 1, $user->id, 'UPDATE_DEPARTMENT_RELAY', 'User updated department relay');
|
|
$response->success($relay->asArray());
|
|
}, [
|
|
'update_department_relay' => 'Update department relay in departments the user has access to',
|
|
]);
|
|
|
|
$this->delete('/department/relays', function () {
|
|
ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/relays');
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('delete_department_relay');
|
|
$user = (new authentication())->get_user();
|
|
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
|
|
self::requireParameters(['id']);
|
|
self::requireType(self::getParameter('id'), self::type_int());
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
|
|
$relay = (new department_relays_o())->select((int)self::getParameter('id'));
|
|
if (!$relay->exists()) {
|
|
$response->error('Department relay not found', 404);
|
|
}
|
|
self::requireDepartmentAccess((int)$relay->department->value());
|
|
$relay->delete();
|
|
|
|
(new logs_o())->add('department_relays', 'global', 1, $user->id, 'DELETE_DEPARTMENT_RELAY', 'User deleted department relay');
|
|
$response->success('Department relay deleted');
|
|
}, [
|
|
'delete_department_relay' => 'Delete department relay in departments the user has access to',
|
|
]);
|
|
}
|
|
}
|