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

148 lines
5.9 KiB
PHP

<?php
namespace routes;
use classes\authentication;
use objects\customer_vehicles_addons_o;
use objects\customer_vehicles_o;
use objects\product_options_o;
use traits\route_t;
use app\auth\Scope;
use app\auth\ScopeMiddleware;
use modules\subusers\helpers\subusers_permission_node_key;
class vehicleAddonRoute
{
use route_t;
public function run(): void
{
$this->get('/vehicles/addons/available', function () {
ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/vehicles/addons/available');
global $response;
$auth = new authentication();
$user = $auth->get_user();
$permission_own = self::definePermission('list_vehicle_addon_own', subusers_permission_node_key::VEHICLES_LIST);
$permission_other = self::definePermission('list_vehicles_addon_other');
// Require the parameter vehicle_id
self::requireParameters(['id']);
$vehicle_id = (int)self::getParameter('id');
self::requireMinValue($vehicle_id, 1);
// Check if the vehicle exists
$vehicle = (new customer_vehicles_o())->select($vehicle_id);
if (!$vehicle->exists()) {
$response->error('Vehicle not found.', 404);
}
// Enforce access (own vs broader)
self::allowOwnOrDepartmentAccess(
$permission_own,
$permission_other,
(int)$vehicle->customer_id->value(),
null,
null,
'You are not allowed to view addons for vehicles from other users'
);
// Get the addons for the vehicle
$available_addons = (new product_options_o())->getProductOptions((int)$vehicle->type->value());
// Filter the addons, to only show the ones that has "subscription_allowed" set to true
$available_addons = array_filter($available_addons, function ($addon) {
return (bool)$addon['product']['subscription_allowed'];
});
// Get the addons for the vehicle (Currently selected)
$enabled_addons = (new customer_vehicles_addons_o())->getFieldsWhere([
'vehicle_id' => $vehicle_id,
], [
'id',
'addon_id',
'amount',
]);
// Create a list of available addons, with the amount of each addon
$available_addons = array_map(function ($addon) use ($enabled_addons) {
$addon['amount'] = 0;
foreach ( $enabled_addons as $enabled_addon ) {
if ((int)$enabled_addon['addon_id'] === (int)$addon['id']) {
$addon['amount'] = (int)$enabled_addon['amount'];
}
}
return $addon;
}, $available_addons);
// Sort the addons by name
usort($available_addons, function ($a, $b) {
return strcmp($a['name'], $b['name']);
});
// Return the addons
$response->success(
$available_addons,
);
},
[
'list_vehicle_addon_own' => 'List own vehicles addons',
'list_vehicles_addon_other' => 'List other vehicles addons',
]
);
$this->post('/vehicles/addons/toggle', function () {
ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/vehicles/addons/toggle');
global $response;
$auth = new authentication();
$user = $auth->get_user();
$permission_own = self::definePermission('toggle_vehicle_addon_own', subusers_permission_node_key::VEHICLES_EDIT);
$permission_other = self::definePermission('toggle_vehicle_addon_other');
// Require the parameter vehicle_id
self::requireParameters(['vehicle_id', 'addon_id']);
$vehicle_id = (int)self::getParameter('vehicle_id');
self::requireMinValue($vehicle_id, 1);
// Check if the vehicle exists
$vehicle = (new customer_vehicles_o())->select($vehicle_id);
if (!$vehicle->exists()) {
$response->error('Vehicle not found.', 404);
}
// Enforce access (own vs broader)
self::allowOwnOrDepartmentAccess(
$permission_own,
$permission_other,
(int)$vehicle->customer_id->value(),
null,
null,
'You are not allowed to edit addons for vehicles from other users'
);
// Get the addon id
$addon_id = (int)self::getParameter('addon_id');
self::requireMinValue($addon_id, 1);
// Check if the addon exists
$addon = (new product_options_o())->select($addon_id);
if (!$addon->exists()) {
$response->error('Addon not found.', 404);
}
// Check if the addon is already added to the vehicle
$existing_addon = (new customer_vehicles_addons_o())->getFieldsWhere([
'vehicle_id' => $vehicle_id,
'addon_id' => $addon_id,
], ['id']);
if (count($existing_addon) > 0) {
// If it exists, remove it
$tmp_addon = (new customer_vehicles_addons_o())->select($existing_addon[0]['id']);
$tmp_addon->delete();
$response->success([
'message' => 'Addon removed from vehicle.',
]);
}
// If it doesn't exist, add it
(new customer_vehicles_addons_o())->add($vehicle_id, $addon_id);
$response->success([
'message' => 'Addon added to vehicle.',
]);
},
[
'toggle_vehicle_addon_own' => 'Toggle own vehicles addons',
'toggle_vehicle_addon_other' => 'Toggle other vehicles addons',
]
);
}
}