Files
api/services/nginx/app/routes/departmentSelfserveConditionsRoute.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

267 lines
13 KiB
PHP

<?php
/**
* Route for department self-serve conditions
*/
namespace routes;
use classes\authentication;
use modules\selfserve\classes\selfserve_config_versioning;
use classes\response;
use objects\department_selfserve_conditions_o;
use objects\logs_o;
use traits\route_t;
use app\auth\Scope;
use app\auth\ScopeMiddleware;
class departmentSelfserveConditionsRoute
{
use route_t;
public function run(): void
{
/**
* List department self-serve conditions
*/
$this->get('/department/selfserve/conditions', function () {
ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/selfserve/conditions');
global $response;
$this->requirePermission('list_department_selfserve_conditions');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('department_selfserve_conditions', 'global', 1, $user->id, 'LIST_CONDITIONS', 'User listed department self-serve conditions');
$conditions_o = new department_selfserve_conditions_o();
$authorized_department_ids = $user->getGroup()->getDepartments();
$has_view_all_permission = $this->hasPermission('view_all_department_selfserve_conditions');
// If an ID is provided, return that specific condition
if (self::isParametersSet(['id'])) {
$conditions_o->select((int)self::getParameter('id'));
if ($conditions_o->exists()) {
if (!$this->canAccessDepartment($authorized_department_ids, (int)$conditions_o->department->value())) {
if (!$has_view_all_permission) {
$this->forbidDepartmentAccess((int)$conditions_o->department->value(), ['view_all_department_selfserve_conditions']);
}
}
$response->success($conditions_o->asArray());
} else {
$response->error('Condition not found', 404);
}
}
$filters = [];
if (self::isParametersSet(['department'])) {
$requested_department = (int)self::getParameter('department');
if (!$this->canAccessDepartment($authorized_department_ids, $requested_department) && !$has_view_all_permission) {
$this->forbidDepartmentAccess($requested_department, ['view_all_department_selfserve_conditions']);
}
$filters['department'] = $requested_department;
} else {
if (!$has_view_all_permission) {
if (empty($authorized_department_ids)) {
$authorized_department_ids = [0];
} else {
$authorized_department_ids[] = 0;
}
$filters['department'] = $authorized_department_ids;
}
}
if (self::isParametersSet(['lane'])) {
$filters['lane'] = (int)self::getParameter('lane');
}
if (self::isParametersSet(['product'])) {
$filters['product'] = (int)self::getParameter('product');
}
if (self::isParametersSet(['machine_type_id'])) {
$filters['machine_type_id'] = (int)self::getParameter('machine_type_id');
}
$response->success(
$conditions_o->setSearchableFields(['id', 'department', 'lane', 'product', 'machine_type_id', 'condition_id', 'name', 'description', 'deleted_at'])
->listObjectsWithPaginationIfSet(function ($condition) {
$c = new department_selfserve_conditions_o();
$c->select((int)$condition['id']);
return $c->asArray();
}, $conditions_o->forceRestrictFilters($filters))
);
} else {
$response->error('Invalid session', 400);
}
}, [
'list_department_selfserve_conditions' => 'List all department self-serve conditions',
'view_all_department_selfserve_conditions' => 'View all department self-serve conditions'
]);
/**
* Add a department self-serve condition
*/
$this->post('/department/selfserve/conditions', function () {
ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/conditions');
global $response;
$this->requirePermission('add_department_selfserve_conditions');
$user = (new authentication())->get_user();
if ($user) {
$department = $response->isRequestParameterSet('department') ? (int)$response->getRequestParameter('department') : 0;
$lane = $response->isRequestParameterSet('lane') ? (int)$response->getRequestParameter('lane') : 0;
$product = $response->isRequestParameterSet('product') ? (int)$response->getRequestParameter('product') : 0;
$machine_type_id = null;
if ($response->isRequestParameterSet('machine_type_id')) {
$machine_type_param = $response->getRequestParameter('machine_type_id');
if (!is_null($machine_type_param) && $machine_type_param !== '' && $machine_type_param !== 'null') {
$machine_type_id = (int)$machine_type_param;
}
}
$condition_id = $response->getRequestParameter('condition_id'); // parent condition id for nested condition trees
$condition_id = is_null($condition_id) || $condition_id === 'null' ? null : (int)$condition_id;
$name = (string)$response->getRequestParameter('name');
$description = (string)$response->getRequestParameter('description');
if (!$name || !$description) {
$response->error('Missing required fields: name and description', 400);
}
if ($machine_type_id === null && (!$department || !$lane || !$product)) {
$response->error('Missing required fields: either machine_type_id or department, lane, and product', 400);
}
$authorized_department_ids = $user->getGroup()->getDepartments();
if (!$this->canAccessDepartment($authorized_department_ids, $department)) {
$this->forbidDepartmentAccess($department);
}
try {
$condition_o = (new department_selfserve_conditions_o())->add(
$department,
$lane,
$product,
$name,
$description,
$condition_id,
$machine_type_id
);
(new selfserve_config_versioning())->syncDraftFromLegacyForDepartment($department);
(new logs_o())->add('department_selfserve_conditions', 'global', 1, $user->id, 'ADD_CONDITION', 'User added department self-serve condition ' . $condition_o->id);
$response->success($condition_o->asArray());
} catch (\Exception $e) {
$response->error($e->getMessage(), 500);
}
} else {
$response->error('Invalid session', 400);
}
}, [
'add_department_selfserve_conditions' => 'Add a department self-serve condition'
]);
/**
* Update a department self-serve condition
*/
$this->put('/department/selfserve/conditions', function () {
ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/conditions');
global $response;
$this->requirePermission('update_department_selfserve_conditions');
$user = (new authentication())->get_user();
if ($user) {
$id = (int)$response->getRequestParameter('id');
if (!$id) {
$response->error('Missing required fields: id', 400);
}
$condition_o = new department_selfserve_conditions_o();
$condition_o->select($id);
if (!$condition_o->exists()) {
$response->error('Condition not found', 404);
}
$originalDepartment = (int)$condition_o->department->value();
$authorized_department_ids = $user->getGroup()->getDepartments();
if (!$this->canAccessDepartment($authorized_department_ids, (int)$condition_o->department->value())) {
$this->forbidDepartmentAccess((int)$condition_o->department->value());
}
if ($response->isRequestParameterSet('department')) {
$new_department = (int)$response->getRequestParameter('department');
if (!$this->canAccessDepartment($authorized_department_ids, $new_department)) {
$this->forbidDepartmentAccess($new_department);
}
$condition_o->department->update($new_department);
}
if ($response->isRequestParameterSet('lane')) {
$condition_o->lane->update((int)$response->getRequestParameter('lane'));
}
if ($response->isRequestParameterSet('product')) {
$condition_o->product->update((int)$response->getRequestParameter('product'));
}
if ($response->isRequestParameterSet('machine_type_id')) {
$machine_type_id = $response->getRequestParameter('machine_type_id');
$condition_o->machine_type_id->update(is_null($machine_type_id) || $machine_type_id === '' || $machine_type_id === 'null' ? null : (int)$machine_type_id);
}
if ($response->isRequestParameterSet('condition_id')) {
$condition_id = $response->getRequestParameter('condition_id'); // parent condition id for nested condition trees
$condition_o->condition_id->update(is_null($condition_id) || $condition_id === 'null' ? null : (int)$condition_id);
}
if ($response->isRequestParameterSet('name')) {
$condition_o->name->update((string)$response->getRequestParameter('name'));
}
if ($response->isRequestParameterSet('description')) {
$condition_o->description->update((string)$response->getRequestParameter('description'));
}
(new selfserve_config_versioning())->syncDraftFromLegacyForDepartment($originalDepartment);
(new selfserve_config_versioning())->syncDraftFromLegacyForDepartment((int)$condition_o->department->value());
(new logs_o())->add('department_selfserve_conditions', 'global', 1, $user->id, 'UPDATE_CONDITION', 'User updated department self-serve condition ' . $id);
$response->success($condition_o->asArray());
} else {
$response->error('Invalid session', 400);
}
}, [
'update_department_selfserve_conditions' => 'Update a department self-serve condition'
]);
/**
* Delete a department self-serve condition
*/
$this->delete('/department/selfserve/conditions', function () {
ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/conditions');
global $response;
$this->requirePermission('delete_department_selfserve_conditions');
$user = (new authentication())->get_user();
if ($user) {
$id = (int)$response->getRequestParameter('id');
if (!$id) {
$response->error('Missing required fields: id', 400);
}
$condition_o = new department_selfserve_conditions_o();
$condition_o->select($id);
if (!$condition_o->exists()) {
$response->error('Condition not found', 404);
}
$authorized_department_ids = $user->getGroup()->getDepartments();
if (!$this->canAccessDepartment($authorized_department_ids, (int)$condition_o->department->value())) {
$this->forbidDepartmentAccess((int)$condition_o->department->value());
}
$condition_o->delete();
(new selfserve_config_versioning())->syncDraftFromLegacyForDepartment((int)$condition_o->department->value());
(new logs_o())->add('department_selfserve_conditions', 'global', 1, $user->id, 'DELETE_CONDITION', 'User deleted department self-serve condition ' . $id);
$response->success('Condition deleted');
} else {
$response->error('Invalid session', 400);
}
}, [
'delete_department_selfserve_conditions' => 'Delete a department self-serve condition'
]);
}
private function canAccessDepartment(array $authorizedDepartmentIds, int $departmentId): bool
{
return $departmentId === 0 || in_array($departmentId, $authorizedDepartmentIds, true);
}
}