Files
api/services/nginx/app/routes/plateScannersRoute.php
T
OpenClaw 51a87655d6 feat(auth): add scope-based access control to all existing routes (TRU-149)
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
2026-08-17 11:43:13 +00:00

242 lines
11 KiB
PHP

<?php
namespace routes;
use classes\authentication;
use objects\departments_o;
use objects\logs_o;
use objects\plate_scanners_o;
use traits\route_t;
use app\auth\Scope;
use app\auth\ScopeMiddleware;
class plateScannersRoute
{
use route_t;
public function run(): void
{
$this->get('/numberplatescanners', function () {
ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/numberplatescanners');
// Require the user to be logged in
global $response;
$this->requirePermission('list_number_plate_scanners');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Log the incident
(new logs_o())->add('numberplatescanners', 'global', 1, $user->id, 'LIST_NUMBER_PLATE_SCANNERS', 'Successfully listed number plate scanners');
// Return the list of plate scanners
$response->success(
(new plate_scanners_o())
->setSearchableFields([
// The fields that can be searched. This would otherwise make it possible to get secret information from the database, simply by searching for it and getting the result count back
'id',
'department_id',
'name',
'notes'
])
->listObjectsWithPaginationIfSet(
static function (array $scanner): array {
return (new plate_scanners_o())->select((int)$scanner['id'])->asArray();
}
)
);
} else {
// Log the incident
(new logs_o())->add('numberplatescanners', 'global', 1, 0, 'LIST_NUMBER_PLATE_SCANNERS', 'No user found, or invalid session');
// Return an error
$response->error('Invalid session', 400);
}
},
[
'list_number_plate_scanners' => 'List all number plate scanners'
]
);
$this->post('/numberplatescanners', function () {
ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/numberplatescanners');
// Require the user to be logged in
global $response;
$this->requirePermission('add_number_plate_scanner');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Get the post data
$data = json_decode(file_get_contents('php://input'), true);
// Check if the required fields are set
if (!isset($data['department_id'])) {
$response->error('Department ID is required', 400);
}
if (!isset($data['name'])) {
$response->error('Name is required', 400);
}
if (!isset($data['notes'])) {
$response->error('Notes is required', 400);
}
$laneId = array_key_exists('lane_id', $data) && $data['lane_id'] !== null
? (int)$data['lane_id']
: null;
// Add the number plate scanner
$scanner = new plate_scanners_o();
$scanner->add((int)$data['department_id'], (string)$data['name'], (string)$data['notes'], $laneId);
// Log the incident
(new logs_o())->add('numberplatescanners', 'global', 1, $user->id, 'ADD_NUMBER_PLATE_SCANNER', 'Successfully added a number plate scanner');
// Return a success message
$response->success([
'message' => 'Number plate scanner added',
'scanner' => $scanner->asArray(),
]);
} else {
// Log the incident
(new logs_o())->add('numberplatescanners', 'global', 1, 0, 'ADD_NUMBER_PLATE_SCANNER', 'No user found, or invalid session');
// Return an error
$response->error('Invalid session', 400);
}
},
[
'add_number_plate_scanner' => 'Add a number plate scanner'
]
);
$this->put('/numberplatescanners', function () {
ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/numberplatescanners');
// Require the user to be logged in
global $response;
$this->requirePermission('edit_number_plate_scanner');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Get the post data
$data = json_decode(file_get_contents('php://input'), true);
// Check if the required fields are set
if (!isset($data['id'])) {
$response->error('ID is required', 400);
}
if (!isset($data['department_id'])) {
$response->error('Department ID is required', 400);
}
if (!isset($data['name'])) {
$response->error('Name is required', 400);
}
if (!isset($data['notes'])) {
$response->error('Notes is required', 400);
}
$laneIdProvided = array_key_exists('lane_id', $data);
$laneId = $laneIdProvided && $data['lane_id'] !== null ? (int)$data['lane_id'] : null;
// Edit the number plate scanner
$scanner = new plate_scanners_o();
$scanner->edit(
(int)$data['id'],
(int)$data['department_id'],
(string)$data['name'],
(string)$data['notes'],
$laneId,
$laneIdProvided
);
// Log the incident
(new logs_o())->add('numberplatescanners', 'global', 1, $user->id, 'EDIT_NUMBER_PLATE_SCANNER', 'Successfully edited a number plate scanner');
// Return a success message
$response->success([
'message' => 'Number plate scanner edited',
'scanner' => $scanner->asArray(),
]);
} else {
// Log the incident
(new logs_o())->add('numberplatescanners', 'global', 1, 0, 'EDIT_NUMBER_PLATE_SCANNER', 'No user found, or invalid session');
// Return an error
$response->error('Invalid session', 400);
}
},
[
'edit_number_plate_scanner' => 'Edit a number plate scanner'
]
);
$this->post('/numberplatescanners/{id}/rotate-key', function () {
ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/numberplatescanners/{id}/rotate-key');
global $response;
$this->requirePermission('edit_number_plate_scanner');
$user = (new authentication())->get_user();
if (!$user) {
(new logs_o())->add('numberplatescanners', 'global', 1, 0, 'ROTATE_NUMBER_PLATE_SCANNER_KEY', 'No user found, or invalid session');
$response->error('Invalid session', 400);
}
$scannerId = (int)$this->fromRoute('id');
self::requireParameterIntPositive($scannerId, 'id');
$scanner = (new plate_scanners_o())->select($scannerId);
if (!$scanner->exists()) {
$response->error('Number plate scanner not found', 404);
}
self::requireDepartmentAccess((int)$scanner->department_id->value());
$rotatedScanner = (new plate_scanners_o())->rotateApiKey($scannerId);
(new logs_o())->add('numberplatescanners', 'global', 1, $user->id, 'ROTATE_NUMBER_PLATE_SCANNER_KEY', 'Successfully rotated a number plate scanner API key');
$response->success([
'message' => 'Number plate scanner API key rotated',
'scanner' => $rotatedScanner,
'api_key' => (string)$rotatedScanner['api_key'],
]);
}, [
'edit_number_plate_scanner' => 'Rotate a number plate scanner API key',
]);
self::get('/department/numberplatescanners', function () {
// Require the user to be logged in
global $response;
$this->requirePermission('list_department_number_plate_scanners');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Get the department ID
self::requireParameters(['id']);
self::requireType((int)self::getParameter('id'), self::type_int());
self::requireParameterIntPositive((int)self::getParameter('id'), 'id');
// Check if the user has access to the department
self::requireDepartmentAccess((int)self::getParameter('id'));
// Check if the department exists
$department = (new departments_o())->select((int)self::getParameter('id'));
$department->requireSelected();
// Log the incident
(new logs_o())->add('numberplatescanners', 'global', 1, $user->id, 'LIST_DEPARTMENT_NUMBER_PLATE_SCANNERS', 'Successfully listed department number plate scanners');
// Get the department scanners.
$result = (new plate_scanners_o())
->getFieldsWhere([
'department_id' => (int)self::getParameter('id')
], [
'id',
'lane_id',
'name',
'notes'
]);
// Parse the result
foreach ( $result as $key => $value ) {
$result[$key]['id'] = (int)$value['id'];
$result[$key]['lane_id'] = $value['lane_id'] === null ? null : (int)$value['lane_id'];
}
// Return the list of plate scanners
$response->success(
$result
);
} else {
// Log the incident
(new logs_o())->add('numberplatescanners', 'global', 1, 0, 'LIST_DEPARTMENT_NUMBER_PLATE_SCANNERS', 'No user found, or invalid session');
// Return an error
$response->error('Invalid session', 400);
}
},
[
'list_department_number_plate_scanners' => 'List all number plate scanners in a department',
'department_access_:id' => 'Access department :id'
]
);
}
}