From 16094575a7a0165e42a1c02d4e062a3346745d39 Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Thu, 12 Feb 2026 15:52:37 +0100 Subject: [PATCH] Refactor `ordersRoute`, `vehiclesRoute`, and related routes for subuser permission handling - Integrate `subusers_permission_node_key` for dynamic subuser-specific permission checks. - Refactor authentication and permission logic to streamline checks for own vs. department-level access. - Simplify error handling and enforce scoped permissions for vehicles, orders, and their attachments. - Localize permission labels and descriptions to Danish for relevant modules. --- .../subusers_permission_nodes_vehicles.php | 12 +- services/nginx/app/routes/ordersRoute.php | 265 ++++---- .../nginx/app/routes/vehicleAddonRoute.php | 53 +- services/nginx/app/routes/vehiclesRoute.php | 571 +++++++----------- 4 files changed, 387 insertions(+), 514 deletions(-) diff --git a/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_vehicles.php b/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_vehicles.php index 6bc72117..3432364a 100644 --- a/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_vehicles.php +++ b/services/nginx/app/modules/subusers/permissions/subusers_permission_nodes_vehicles.php @@ -11,13 +11,13 @@ class subusers_permission_nodes_vehicles extends subusers_permission_nodes public function __construct() { parent::__construct( - 'Vehicles', - 'Vehicles management permissions' + 'Køretøjer', + 'Tilladelser til håndtering af køretøjer' ); - $this->addNode(subusers_permission_node_key::VEHICLES_LIST, 'Vehicles List', 'View vehicles list', subusers_permission_type::VIEW, false); - $this->addNode(subusers_permission_node_key::VEHICLES_EDIT, 'Vehicles Edit', 'Edit vehicles', subusers_permission_type::EDIT, false); - $this->addNode(subusers_permission_node_key::VEHICLES_DELETE, 'Vehicles Delete', 'Delete vehicles', subusers_permission_type::DELETE, false); - $this->addNode(subusers_permission_node_key::VEHICLES_ADD, 'Vehicles Add', 'Add vehicles', subusers_permission_type::ADD, false); + $this->addNode(subusers_permission_node_key::VEHICLES_LIST, 'Vis køretøjer', 'Giver brugeren mulighed for at se listen over køretøjer', subusers_permission_type::VIEW, false); + $this->addNode(subusers_permission_node_key::VEHICLES_EDIT, 'Rediger køretøjer', 'Giver brugeren mulighed for at redigere køretøjer', subusers_permission_type::EDIT, false); + $this->addNode(subusers_permission_node_key::VEHICLES_DELETE, 'Slet køretøjer', 'Giver brugeren mulighed for at slette køretøjer', subusers_permission_type::DELETE, false); + $this->addNode(subusers_permission_node_key::VEHICLES_ADD, 'Tilføj køretøjer', 'Giver brugeren mulighed for at tilføje nye køretøjer', subusers_permission_type::ADD, false); } } \ No newline at end of file diff --git a/services/nginx/app/routes/ordersRoute.php b/services/nginx/app/routes/ordersRoute.php index 83a721a6..42941746 100644 --- a/services/nginx/app/routes/ordersRoute.php +++ b/services/nginx/app/routes/ordersRoute.php @@ -18,6 +18,7 @@ use objects\stripe_module_orders_o; use objects\stripe_payment_intents_o; use objects\users_o; use traits\route_t; +use modules\subusers\helpers\subusers_permission_node_key; class ordersRoute { @@ -28,100 +29,98 @@ class ordersRoute $this->get('/orders', function () { // Require the user to be logged in global $response; - $restrict_only_own = false; - // Get the user object - $user = (new authentication())->get_user(); - // Check if the request was successful - if ($user) { - // Check if the user has the permission to list their own orders - $can_list_own_orders = $this->hasPermission('list_own_orders'); - $can_list_all_orders = $this->hasPermission('list_orders'); - if (!$can_list_own_orders && !$can_list_all_orders) { - $response->error('You do not have permission to list orders, neither your own nor all orders', 403); - }; - if ($can_list_own_orders && !$can_list_all_orders) { - $restrict_only_own = true; - } - // Log the incident - (new logs_o())->add('orders', 'global', 1, $user->id, 'LIST_ORDERS', 'Successfully listed orders'); - // Create economic_module_orders object - //$economic_module_orders = new economic_module_orders(); - $orders = new orders_o(); - if (!$restrict_only_own) { - $department_ids = $user->getGroup()->getDepartments(); - } else { - $department_ids = []; - } - if (self::isParametersSet(['show_wash_subscription'])) { - // Check if the boolean is true - if (self::getParameter('show_wash_subscription') === 'true') { - // add the '10' to the department_ids - $department_ids[] = '10'; - } - } - // Return the list of departments - $orders->setView('orders_with_invoice_collections'); - $response->success( - $orders->listObjectsWithPaginationIfSet( - function ($order) { - $order_obj = new orders_o(); - // Get the order object - $order_obj->select((int)$order['id']); - // Add the invoice status to the order - $order['economic_invoice_module'] = (new economic_module_orders())->getByOrderId($order['id'])->asArray(); - // Add the total amount to the order - $order['total_net_amount'] = $order_obj->getNetAmount(); - // Add the stripe status to the order - $stripe_module_orders = (new stripe_module_orders_o())->select($order['id']); - if ($stripe_module_orders->exists()) { - $order['stripe_invoice_module'] = $stripe_module_orders->asArray(); - } - // If the invoice collection is set, add it to the order - if (!empty($order['invoice_collection_id'])) { - $collected_order_invoices_obj = new collected_order_invoices_o(); - $collected_order_invoices_obj->select((int)$order['invoice_collection_id']); - $order['invoice_collection'] = [ - 'id' => $order['invoice_collection_id'], - 'closed_at' => $collected_order_invoices_obj->closed_at->value(), - 'booked_invoice_id' => $collected_order_invoices_obj->booked_invoice_id->value() ?? null, - 'processor' => (int)$collected_order_invoices_obj->processor->value() ?? null, - ]; - } - // Get the customer - $tmp_customer = (new users_o())->getUserByCustomerNumber((int)$order['customer_id']); - // Add the customer name to the order - $order['customer_name'] = (new users_o())->getCustomerName((int)$tmp_customer->customer_number->value()); - $order['user_id'] = (int)$tmp_customer->id; - // Add the cashier name to the order - $order['cashier_name'] = (new users_o())->getCashierName((int)$order['cashier_id']); - $order['pending_handheld'] = $order_obj->isPendingHandheld(); - $order['attachments'] = $order_obj->listAttachments(); - $order['po'] = $order['po'] ?? null; - $order['lane'] = $order['lane'] ?? null; - /** @var array $order */ - return $order; - }, - $orders->forceRestrictFilters( - [ - // This makes sure that the user can only see orders from the departments they explicitly have access to - 'department_id' => $department_ids, - ...($restrict_only_own ? [ - 'customer_id' => $user->customer_number->value(), - ] : []), - ] - ) - ) - ); - } else { - // Log the incident + /** Authentication */ + $auth = new authentication(); + $user = $auth->get_user(); + if ($user === false) { (new logs_o())->add('orders', 'global', 1, 0, 'LIST_ORDERS', 'No user found, or invalid session'); - // Return an error $response->error('Invalid session', 400); } + /** Permissions (subuser-aware) */ + $permission_own = self::definePermission('list_own_orders', subusers_permission_node_key::ORDERS_LIST); + $permission_other = self::definePermission('list_orders'); + $has_permission_other = self::hasPermission($permission_other); + self::allowOwnOrDepartmentAccess( + $permission_own, + $permission_other, + null, + null, + null, + 'You do not have permission to list orders.' + ); + + // Log the incident + (new logs_o())->add('orders', 'global', 1, $user->id, 'LIST_ORDERS', 'Successfully listed orders'); + + // Build department filter when listing as department/admin + $department_ids = []; + if ($has_permission_other) { + $department_ids = $user->getGroup()->getDepartments(); + } + if (self::isParametersSet(['show_wash_subscription'])) { + if (self::getParameter('show_wash_subscription') === 'true') { + $department_ids[] = '10'; + } + } + + $orders = new orders_o(); + $orders->setView('orders_with_invoice_collections'); + $effectiveCustomer = self::resolveEffectiveCustomerNumber(); + + $response->success( + $orders->listObjectsWithPaginationIfSet( + function ($order) { + $order_obj = new orders_o(); + // Get the order object + $order_obj->select((int)$order['id']); + // Add the invoice status to the order + $order['economic_invoice_module'] = (new economic_module_orders())->getByOrderId($order['id'])->asArray(); + // Add the total amount to the order + $order['total_net_amount'] = $order_obj->getNetAmount(); + // Add the stripe status to the order + $stripe_module_orders = (new stripe_module_orders_o())->select($order['id']); + if ($stripe_module_orders->exists()) { + $order['stripe_invoice_module'] = $stripe_module_orders->asArray(); + } + // If the invoice collection is set, add it to the order + if (!empty($order['invoice_collection_id'])) { + $collected_order_invoices_obj = new collected_order_invoices_o(); + $collected_order_invoices_obj->select((int)$order['invoice_collection_id']); + $order['invoice_collection'] = [ + 'id' => $order['invoice_collection_id'], + 'closed_at' => $collected_order_invoices_obj->closed_at->value(), + 'booked_invoice_id' => $collected_order_invoices_obj->booked_invoice_id->value() ?? null, + 'processor' => (int)$collected_order_invoices_obj->processor->value() ?? null, + ]; + } + // Get the customer + $tmp_customer = (new users_o())->getUserByCustomerNumber((int)$order['customer_id']); + // Add the customer name to the order + $order['customer_name'] = (new users_o())->getCustomerName((int)$tmp_customer->customer_number->value()); + $order['user_id'] = (int)$tmp_customer->id; + // Add the cashier name to the order + $order['cashier_name'] = (new users_o())->getCashierName((int)$order['cashier_id']); + $order['pending_handheld'] = $order_obj->isPendingHandheld(); + $order['attachments'] = $order_obj->listAttachments(); + $order['po'] = $order['po'] ?? null; + $order['lane'] = $order['lane'] ?? null; + /** @var array $order */ + return $order; + }, + $orders->forceRestrictFilters([ + ...($has_permission_other ? [ + 'department_id' => $department_ids + ] : []), + ...(!$has_permission_other && $effectiveCustomer !== null ? [ + 'customer_id' => [(int)$effectiveCustomer] + ] : []), + ]) + ) + ); }, [ 'list_orders' => 'List all orders', - 'list_own_orders' => 'List own orders', + 'list_own_orders' => 'List own orders. Subusers require node: ORDERS_LIST and X-Customer-Number header.', ] ); @@ -272,11 +271,12 @@ class ordersRoute $this->get('/orders/attachments/download', function () { // Require the user to be logged in global $response; - $isCustomerAccess = ($this->hasPermission('user') && !($this->hasPermission('download_order_attachments'))); - if (!$isCustomerAccess) { - $this->requirePermission('download_order_attachments'); - } else { - $this->requirePermission('download_order_attachments_own'); + // Permissions (subuser-aware) + $permission_own = self::definePermission('download_order_attachments_own', subusers_permission_node_key::ORDERS_LIST); + $permission_other = self::definePermission('download_order_attachments'); + $has_permission_other = self::hasPermission($permission_other); + if (!$has_permission_other) { + self::requirePermission($permission_own); } // Get the user object $user = (new authentication())->get_user(); @@ -301,10 +301,11 @@ class ordersRoute if (!$order->exists()) { $response->error('Order not found', 400); } - // Check if the user has customer edit access - if ($isCustomerAccess) { - if ($order->isOwnOrder($user->customer_number->value()) === false) { - $response->error('You do not have permission to download this order', 400); + // If operating under own-scope (classic user or subuser), ensure the order belongs to the effective customer context + if (!$has_permission_other) { + $effectiveCustomer = self::resolveEffectiveCustomerNumber(); + if ($effectiveCustomer === null || (int)$order->customer_id->value() !== (int)$effectiveCustomer) { + $response->error('You do not have permission to download this order attachment', 403); } } // Get the attachment @@ -333,7 +334,7 @@ class ordersRoute }, [ 'download_order_attachments' => 'Download attachments for an order', - 'download_order_attachments_own' => 'Download attachments for an order (Only for own orders)' + 'download_order_attachments_own' => 'Download attachments for an order (Only for own orders). Subusers require node: ORDERS_LIST and X-Customer-Number header.' ] ); @@ -358,22 +359,17 @@ class ordersRoute if (!$order->exists()) { $response->error('Order not found', 400); } - /** - * Permissions - */ - $hasPermissionOwn = self::hasPermission('list_own_order_attachments'); - $hasPermissionAll = self::hasPermission('list_order_attachments'); - $hasPermission = false; - if ($hasPermissionOwn || !$hasPermissionAll) { - // Check if the order belongs to the user - if ($order->isOwnOrder($user->customer_number->value())) { - $hasPermission = true; - self::requirePermission('list_own_order_attachments'); + // Permissions (subuser-aware) + $permission_own = self::definePermission('list_own_order_attachments', subusers_permission_node_key::ORDERS_LIST); + $permission_other = self::definePermission('list_order_attachments'); + $has_permission_other = self::hasPermission($permission_other); + if (!$has_permission_other) { + self::requirePermission($permission_own); + $effectiveCustomer = self::resolveEffectiveCustomerNumber(); + if ($effectiveCustomer === null || (int)$order->customer_id->value() !== (int)$effectiveCustomer) { + $response->error('You do not have permission to view attachments for this order.', 403); } } - if (!$hasPermission) { - self::requirePermission('list_order_attachments'); - } // Get the attachments $attachments = $order->listAttachments(); // Log the incident @@ -389,7 +385,7 @@ class ordersRoute }, [ 'list_order_attachments' => 'List attachments for an order', - 'list_own_order_attachments' => 'List attachments for an order (Only for own orders)' + 'list_own_order_attachments' => 'List attachments for an order (Only for own orders). Subusers require node: ORDERS_LIST and X-Customer-Number header.' ] ); @@ -847,15 +843,21 @@ class ordersRoute { // Require the user to be logged in global $response; - // Check if the user has permission to partially edit the order - $isCustomerAccess = ((new authentication())->get_user()->hasPermission('user') && !((new authentication())->get_user()->hasPermission('edit_order'))); - if (!$isCustomerAccess) { - $this->requirePermission('edit_order'); - } else { - $this->requirePermission('user'); // This is used to allow the user to edit their own order + // Auth & permissions (subuser-aware) + $auth = new authentication(); + $user = $auth->get_user(); + $permission_own = self::definePermission('edit_own_orders', subusers_permission_node_key::ORDERS_EDIT); + $permission_other = self::definePermission('edit_order'); + $has_permission_other = self::hasPermission($permission_other); + // Classic user own-edit path (legacy behaviour) + $classic_own_path = ($user !== false && $user->hasPermission('user') && !$has_permission_other); + // Subuser own-edit path via node ORDERS_EDIT + $subuser_own_path = (!$has_permission_other && self::hasPermission($permission_own)); + $isOwnPath = $classic_own_path || $subuser_own_path; + if (!$isOwnPath && !$has_permission_other) { + // Neither own nor admin permission — deny via admin requirement to unify error shape + self::requirePermission($permission_other); } - // Get the user object - $user = (new authentication())->get_user(); // Check if the request was successful if ($user) { // Get the post data @@ -873,10 +875,21 @@ class ordersRoute if (!$order->exists()) { $response->error('Order not found', 400); } - // Check if the user has customer edit access - if ($isCustomerAccess) { - if ($order->customer_id->value() !== $user->customer_number->value()) { - $response->error('You do not have permission to edit this order', 400); + // Own path (classic or subuser) — limited field edits only + if ($isOwnPath) { + // Validate that the order belongs to the effective customer context + if ($subuser_own_path) { + $effectiveCustomer = self::resolveEffectiveCustomerNumber(); + if ($effectiveCustomer === null || (int)$order->customer_id->value() !== (int)$effectiveCustomer) { + $response->error('You do not have permission to edit this order', 403); + } + } else { + // Classic own path — compare against authenticated user customer number + if ($order->customer_id->value() !== $user->customer_number->value()) { + $response->error('You do not have permission to edit this order', 403); + } + // Ensure classic own-path requires user permission + $this->requirePermission('user'); } // Allowed to edit list $allowed_to_edit = [ @@ -918,6 +931,8 @@ class ordersRoute // Return a success message $response->success($order->asArray()); } + // Admin/department path (requires edit_order) + self::requirePermission($permission_other); /** Departmental access */ // If the customer ID is set, validate it if (isset($data['customer_id'])) { diff --git a/services/nginx/app/routes/vehicleAddonRoute.php b/services/nginx/app/routes/vehicleAddonRoute.php index c1cb1ffa..662cd036 100644 --- a/services/nginx/app/routes/vehicleAddonRoute.php +++ b/services/nginx/app/routes/vehicleAddonRoute.php @@ -7,6 +7,7 @@ use objects\customer_vehicles_addons_o; use objects\customer_vehicles_o; use objects\product_options_o; use traits\route_t; +use modules\subusers\helpers\subusers_permission_node_key; class vehicleAddonRoute { @@ -15,13 +16,11 @@ class vehicleAddonRoute public function run(): void { $this->get('/vehicles/addons/available', function () { - // Require the user to be logged in global $response; - $this->requirePermission('list_vehicle_addon_own'); - $user = (new authentication())->get_user(); - if (!$user) { - $response->error('Permission denied, invalid user.', 403); - } + $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'); @@ -31,13 +30,15 @@ class vehicleAddonRoute if (!$vehicle->exists()) { $response->error('Vehicle not found.', 404); } - // Check if the vehicle belongs to the user - if ($vehicle->customer_id->value() !== $user->customer_number->value()) { - // Check if the user has the permission to add addons to other users vehicles - if (!$this->hasPermission('list_vehicle_addon_other')) { - $response->error('Vehicle does not belong to the user.', 403); - } - } + // 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 @@ -80,13 +81,11 @@ class vehicleAddonRoute ); $this->post('/vehicles/addons/toggle', function () { - // Require the user to be logged in global $response; - $this->requirePermission('toggle_vehicle_addon_own'); - $user = (new authentication())->get_user(); - if (!$user) { - $response->error('Permission denied, invalid user.', 403); - } + $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'); @@ -96,13 +95,15 @@ class vehicleAddonRoute if (!$vehicle->exists()) { $response->error('Vehicle not found.', 404); } - // Check if the vehicle belongs to the user - if ($vehicle->customer_id->value() !== $user->customer_number->value()) { - // Check if the user has the permission to add addons to other users vehicles - if (!$this->hasPermission('toggle_vehicle_addon_other')) { - $response->error('Vehicle does not belong to the user.', 403); - } - } + // 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); diff --git a/services/nginx/app/routes/vehiclesRoute.php b/services/nginx/app/routes/vehiclesRoute.php index b63ec0cb..b89ba0f4 100644 --- a/services/nginx/app/routes/vehiclesRoute.php +++ b/services/nginx/app/routes/vehiclesRoute.php @@ -14,6 +14,7 @@ use objects\plate_scans_o; use objects\products_o; use objects\users_o; use traits\route_t; +use modules\subusers\helpers\subusers_permission_node_key; class vehiclesRoute { @@ -22,73 +23,52 @@ class vehiclesRoute public function run(): void { $this->get('/vehicles', function () { - // Require the user to be logged in global $response; - $this->requirePermission('list_own_vehicles'); - // Get the user object - $user = (new authentication())->get_user(); - // Check if the request was successful - if ($user) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'LIST_OWN_VEHICLES', 'Successfully listed own vehicles'); - // Check if the id parameter is set - if ($this->isParametersSet(['id'])) { - // Get the id parameter - $id = (int)$this->getParameter('id'); - $this->requireType($id, self::type_int()); - $this->requireMinValue($id, 1); - $this->requireMaxValue($id, 9999999999); - // Get the vehicle object - $vehicle = (new customer_vehicles_o())->select($id); - // Check if the vehicle exists - if (!$vehicle->exists()) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'LIST_OWN_VEHICLES', 'Vehicle not found'); - // Return an error - $response->error('Vehicle not found', 404); - } - // Check if the user is allowed to list the vehicle - if ((int)$vehicle->customer_id->value() !== (int)$user->customer_number->value()) { - // Check if the user has permission to list other users vehicles - if (!$user->hasPermission('list_vehicles_other')) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'LIST_OWN_VEHICLES', 'User tried to list a vehicle from another user'); - // Return an error - $response->error('You are not allowed to list vehicles from other users', 403); - } - } - // Return the vehicle as an array - $response->success( - [...$vehicle->asArray()] - ); + $auth = new authentication(); + $user = $auth->get_user(); + // Define permissions with subuser node linkage + $permission_own = self::definePermission('list_own_vehicles', subusers_permission_node_key::VEHICLES_LIST); + $permission_other = self::definePermission('list_vehicles_other'); + $has_permission_other = self::hasPermission($permission_other); + + // If a specific ID is requested, validate access against that vehicle's customer context + if ($this->isParametersSet(['id'])) { + $id = (int)$this->getParameter('id'); + $this->requireType($id, self::type_int()); + $this->requireMinValue($id, 1); + $this->requireMaxValue($id, 9999999999); + $vehicle = (new customer_vehicles_o())->select($id); + if (!$vehicle->exists()) { + (new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'LIST_OWN_VEHICLES', 'Vehicle not found'); + $response->error('Vehicle not found', 404); } - // Return the list of the user's vehicles - $vehicles_o = new customer_vehicles_o(); - // Check if the user is allowed to list other user's vehicles - if (!$user->hasPermission('list_vehicles_other')) { - $restrict = [ - 'customer_id' => (int)$user->customer_number->value(), - ]; - } - $response->success( - $vehicles_o->listObjectsWithPaginationIfSet( - function ($vehicle) use ($user) { - // Return the object as an array - return [ - ...(new customer_vehicles_o())->select($vehicle['id'])->asArray(), - ]; - }, - $vehicles_o->forceRestrictFilters([ - ...$restrict ?? [] - ]) - ) + $targetCustomer = (int)$vehicle->customer_id->value(); + self::allowOwnOrDepartmentAccess( + $permission_own, + $permission_other, + $targetCustomer, + null, + null, + 'You do not have permission to view this vehicle.' ); - } else { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, 0, 'LIST_OWN_VEHICLES', 'No user found, or invalid session'); - // Return an error - $response->error('Invalid session', 400); + $response->success([...$vehicle->asArray()]); } + + // Listing: restrict to effective customer when lacking the broader permission + $vehicles_o = new customer_vehicles_o(); + $effectiveCustomer = self::resolveEffectiveCustomerNumber(); + $response->success( + $vehicles_o->listObjectsWithPaginationIfSet( + function ($vehicle) { + return [...(new customer_vehicles_o())->select($vehicle['id'])->asArray()]; + }, + $vehicles_o->forceRestrictFilters([ + ...(!$has_permission_other && $effectiveCustomer !== null ? [ + 'customer_id' => [(int)$effectiveCustomer] + ] : []) + ]) + ) + ); }, [ 'list_own_vehicles' => 'List own vehicles', @@ -128,95 +108,70 @@ class vehiclesRoute ); $this->post('/vehicles', function () { - // Require the user to be logged in global $response; - $this->requirePermission('add_vehicle'); - // Get the user object - $user = (new authentication())->get_user(); - // Check if the request was successful - if ($user) { - // Require the parameters - self::requireParameters([ - 'type', - 'reg', - 'wash_subscription', - ]); - // Set the customer_id to the one from the user - $target_user = $user; - // Check if customer_id is set - if (self::isParametersSet([ - 'customer_id', - ])) { - // Check if the customer_id is the same as the current user - if ((int)$user->customer_number->value() !== (int)self::getParameter('customer_id')) { - // Check if the user has permission to add vehicles to other users - if (!$user->hasPermission('add_vehicle_other')) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'ADD_VEHICLE', 'User tried to add a vehicle to another user'); - // Return an error - $response->error('You are not allowed to add vehicles to other users', 403); - } else { - // Set the customer_id to the one from the request - $target_user = (new users_o()); - $target_user->getUserByCustomerNumber((int)self::getParameter('customer_id')); - } - } - } - $reference = null; - // Check if the reference is set - if (self::isParametersSet([ - 'reference', - ])) { - // If the reference is not empty, check if it is valid - if (!empty(self::getParameter('reference'))) { - // Check if the reference is valid - $reference = (string)self::getParameter('reference'); - self::requireType($reference, self::type_string()); - self::requireMinLength('reference', 1); - self::requireMaxLength('reference', 255); - } - } - // Validate the parameters - self::requireType( - self::getParameter('reg'), - self::type_string() - ); - self::requireType( - self::getParameter('type'), - self::type_int() - ); - self::requireType( - self::getParameter('wash_subscription'), - self::type_bool() - ); - // Get the parameters - $reg = (string)self::getParameter('reg'); - $type = (int)self::getParameter('type'); - $subscription = (bool)self::getParameter('wash_subscription'); - // Strip the registration number of whitespace - $reg = trim($reg); - // Create a new vehicle - $vehicle = new customer_vehicles_o(); - $vehicle->add( - $target_user->customer_number->value(), - $type, - $reg, - $subscription ? 1 : 0, - $reference - ); + $auth = new authentication(); + $user = $auth->get_user(); - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'ADD_VEHICLE', 'Successfully added vehicle'); - // Return the new vehicle - $response->success( - $vehicle->asArray() - ); - } else { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, 0, 'ADD_VEHICLE', 'No user found, or invalid session'); - // Return an error - $response->error('Invalid session', 401); + // Define permissions with subuser node linkage + $permission_own = self::definePermission('add_vehicle', subusers_permission_node_key::VEHICLES_ADD); + $permission_other = self::definePermission('add_vehicle_other'); + + // Require the parameters + self::requireParameters([ + 'type', + 'reg', + 'wash_subscription', + ]); + + // Determine target customer + $targetCustomer = self::isParametersSet(['customer_id']) + ? (int)self::getParameter('customer_id') + : (int)(self::resolveEffectiveCustomerNumber() ?? 0); + self::requireType($targetCustomer, self::type_int()); + self::requireMinValue($targetCustomer, 1); + self::requireMaxValue($targetCustomer, 9999999999); + + // Enforce access (own vs broader) + self::allowOwnOrDepartmentAccess( + $permission_own, + $permission_other, + $targetCustomer, + null, + null, + 'You are not allowed to add vehicles to this customer' + ); + + $reference = null; + if (self::isParametersSet(['reference'])) { + if (!empty(self::getParameter('reference'))) { + $reference = (string)self::getParameter('reference'); + self::requireType($reference, self::type_string()); + self::requireMinLength('reference', 1); + self::requireMaxLength('reference', 255); + } } + // Validate the parameters + self::requireType(self::getParameter('reg'), self::type_string()); + self::requireType(self::getParameter('type'), self::type_int()); + self::requireType(self::getParameter('wash_subscription'), self::type_bool()); + // Get the parameters + $reg = (string)self::getParameter('reg'); + $type = (int)self::getParameter('type'); + $subscription = (bool)self::getParameter('wash_subscription'); + $reg = trim($reg); + + // Create a new vehicle + $vehicle = new customer_vehicles_o(); + $vehicle->add( + $targetCustomer, + $type, + $reg, + $subscription ? 1 : 0, + $reference + ); + + (new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'ADD_VEHICLE', 'Successfully added vehicle'); + $response->success($vehicle->asArray()); }, [ 'add_vehicle' => 'Add a vehicle to own vehicles', @@ -225,39 +180,28 @@ class vehiclesRoute ); $this->put('/vehicles', function () { - // Require the user to be logged in global $response; - $this->requirePermission('edit_vehicle'); - // Get the user object - $user = (new authentication())->get_user(); - // Check if the request was successful - if ($user) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'EDIT_VEHICLE', 'User edited a vehicle'); - // Get the request data - self::requireParameters([ - 'id' - ]); - $id = (int)self::getParameter('id'); - // Get the vehicle object - $vehicle = (new customer_vehicles_o())->select($id); - // Check if the vehicle exists - if (!$vehicle->exists()) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'EDIT_VEHICLE', 'Vehicle not found'); - // Return an error - $response->error('Vehicle not found', 404); - } - // Check if the user is allowed to edit the vehicle - if ((int)$vehicle->customer_id->value() !== (int)$user->customer_number->value()) { - // Check if the user has permission to edit other users vehicles - if (!$user->hasPermission('edit_vehicle_other')) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'EDIT_VEHICLE', 'User tried to edit a vehicle from another user'); - // Return an error - $response->error('You are not allowed to edit vehicles from other users', 403); - } - } + $auth = new authentication(); + $user = $auth->get_user(); + $permission_own = self::definePermission('edit_vehicle', subusers_permission_node_key::VEHICLES_EDIT); + $permission_other = self::definePermission('edit_vehicle_other'); + // Get the request data + self::requireParameters(['id']); + $id = (int)self::getParameter('id'); + $vehicle = (new customer_vehicles_o())->select($id); + if (!$vehicle->exists()) { + (new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'EDIT_VEHICLE', 'Vehicle not found'); + $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 vehicles from other users' + ); // Check all the fields, and if they are set, validate and set them if (self::isParametersSet(['type'])) { $type = (int)self::getParameter('type'); @@ -326,15 +270,7 @@ class vehiclesRoute } $vehicle->objectChanged(); // Return the vehicle - $response->success( - $vehicle->asArray() - ); - } else { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, 0, 'EDIT_VEHICLE', 'No user found, or invalid session'); - // Return an error - $response->error('Invalid session', 401); - } + $response->success($vehicle->asArray()); }, [ 'edit_vehicle' => 'Edit a vehicle', @@ -343,54 +279,34 @@ class vehiclesRoute ); $this->delete('/vehicles', function () { - // Require the user to be logged in global $response; - $this->requirePermission('delete_vehicle'); - // Get the user object - $user = (new authentication())->get_user(); - // Check if the request was successful - if ($user) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'DELETE_VEHICLE', 'User deleted a vehicle'); - // Get the request data - self::requireParameters([ - 'id' - ]); - $id = (int)self::getParameter('id'); - // Get the vehicle object - $vehicle = (new customer_vehicles_o())->select($id); - // Check if the vehicle exists - if (!$vehicle->exists()) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'DELETE_VEHICLE', 'Vehicle not found'); - // Return an error - $response->error('Vehicle not found', 404); - } - // Check if the user is allowed to delete the vehicle - if ((int)$vehicle->customer_id->value() !== (int)$user->customer_number->value()) { - // Check if the user has permission to delete other users vehicles - if (!$user->hasPermission('delete_vehicle_other')) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'DELETE_VEHICLE', 'User tried to delete a vehicle from another user'); - // Return an error - $response->error('You are not allowed to delete vehicles from other users', 403); - } - } - // Delete the vehicle - $vehicle->delete(); - // Return success - $response->success( - [ - 'success' => true, - 'message' => 'Vehicle deleted successfully' - ] - ); - } else { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, 0, 'DELETE_VEHICLE', 'No user found, or invalid session'); - // Return an error - $response->error('Invalid session', 401); + $auth = new authentication(); + $user = $auth->get_user(); + $permission_own = self::definePermission('delete_vehicle', subusers_permission_node_key::VEHICLES_DELETE); + $permission_other = self::definePermission('delete_vehicle_other'); + // Get the request data + self::requireParameters(['id']); + $id = (int)self::getParameter('id'); + $vehicle = (new customer_vehicles_o())->select($id); + if (!$vehicle->exists()) { + (new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'DELETE_VEHICLE', 'Vehicle not found'); + $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 delete vehicles from other users' + ); + // Delete the vehicle + $vehicle->delete(); + $response->success([ + 'success' => true, + 'message' => 'Vehicle deleted successfully' + ]); }, [ 'delete_vehicle' => 'Delete a vehicle', @@ -461,60 +377,33 @@ class vehiclesRoute ); $this->post('/vehicles/set-auto-start-on-lpr', function () { - // Require the user to be logged in global $response; - $this->requirePermission('set_auto_start_on_lpr'); - // Get the user object - $user = (new authentication())->get_user(); - // Check if the request was successful - if ($user) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'SET_AUTO_START_ON_LPR', 'User set auto start on LPR'); - // Get the request data - self::requireParameters([ - 'id', - 'active', - ]); - $id = (int)self::getParameter('id'); - self::requireType($id, self::type_int()); - self::requireMinValue($id, 1); - self::requireMaxValue($id, 9999999999); - // Validate the autoStartOnLpr (active) parameter - $autoStartOnLpr = (bool)self::getParameter('active'); - self::requireType($autoStartOnLpr, self::type_bool()); - // Get the vehicle object - $vehicle = (new customer_vehicles_o())->select($id); - // Check if the vehicle exists - if (!$vehicle->exists()) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'SET_AUTO_START_ON_LPR', 'Vehicle not found'); - // Return an error - $response->error('Vehicle not found', 404); - } - // Check if the user is allowed to edit the vehicle - if ((int)$vehicle->customer_id->value() !== (int)$user->customer_number->value()) { - // Check if the user has permission to edit other users vehicles - if (!$user->hasPermission('edit_vehicle_other')) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'SET_AUTO_START_ON_LPR', 'User tried to set auto start on LPR from another user'); - // Return an error - $response->error('You are not allowed to edit vehicles from other users', 403); - } - } - // Set the auto start on LPR - $vehicle->setAutoStartOnLpr( - $autoStartOnLpr - ); - // Return the vehicle as an array - $response->success( - [...$vehicle->asArray()] - ); - } else { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, 0, 'SET_AUTO_START_ON_LPR', 'No user found, or invalid session'); - // Return an error - $response->error('Invalid session', 401); + $auth = new authentication(); + $user = $auth->get_user(); + $permission_own = self::definePermission('set_auto_start_on_lpr', subusers_permission_node_key::VEHICLES_EDIT); + $permission_other = self::definePermission('set_auto_start_on_lpr_other'); + self::requireParameters(['id', 'active']); + $id = (int)self::getParameter('id'); + self::requireType($id, self::type_int()); + self::requireMinValue($id, 1); + self::requireMaxValue($id, 9999999999); + $autoStartOnLpr = (bool)self::getParameter('active'); + self::requireType($autoStartOnLpr, self::type_bool()); + $vehicle = (new customer_vehicles_o())->select($id); + if (!$vehicle->exists()) { + (new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'SET_AUTO_START_ON_LPR', 'Vehicle not found'); + $response->error('Vehicle not found', 404); } + self::allowOwnOrDepartmentAccess( + $permission_own, + $permission_other, + (int)$vehicle->customer_id->value(), + null, + null, + 'You are not allowed to edit vehicles from other users' + ); + $vehicle->setAutoStartOnLpr($autoStartOnLpr); + $response->success([...$vehicle->asArray()]); }, [ 'set_auto_start_on_lpr' => 'Set auto start on LPR', @@ -523,77 +412,45 @@ class vehiclesRoute ); $this->post('/vehicles/set-vehicle-type-id', function () { - // Require the user to be logged in global $response; - $this->requirePermission('set_vehicle_type_id'); - // Get the user object - $user = (new authentication())->get_user(); - // Check if the request was successful - if ($user) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'SET_VEHICLE_TYPE_ID', 'User set vehicle type ID'); - // Get the request data - self::requireParameters([ - 'id', - 'vehicleTypeId', - ]); - $id = (int)self::getParameter('id'); - self::requireType($id, self::type_int()); - self::requireMinValue($id, 1); - self::requireMaxValue($id, 9999999999); - // Validate the vehicleTypeId - $vehicleTypeId = (string)self::getParameter('vehicleTypeId'); - self::requireType($vehicleTypeId, self::type_string()); - self::requireMinLength('vehicleTypeId', 1); - self::requireMaxLength('vehicleTypeId', 50); - // Get the vehicle object - $vehicle = (new customer_vehicles_o())->select($id); - // Check if the vehicle exists - if (!$vehicle->exists()) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'SET_VEHICLE_TYPE_ID', 'Vehicle not found'); - // Return an error - $response->error('Vehicle not found', 404); - } - // Check if the user is allowed to edit the vehicle - if ((int)$vehicle->customer_id->value() !== (int)$user->customer_number->value()) { - // Check if the user has permission to edit other users vehicles - if (!$user->hasPermission('edit_vehicle_other')) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'SET_VEHICLE_TYPE_ID', 'User tried to set vehicle type ID from another user'); - // Return an error - $response->error('You are not allowed to edit vehicles from other users', 403); - } - } - // Get the customer object - $customer = (new users_o())->getUserByCustomerNumber((int)$vehicle->customer_id->value()); - // Check if the vehicle is registered in the XL Vask system - if (!$vehicle->hasXLVask()) { - // Check if the customer has an XL Vask customer account - if (!$customer->hasXLVaskCustomerAccount()) { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, $user->id, 'SET_VEHICLE_TYPE_ID', 'User tried to set vehicle type ID on a vehicle that is not registered in the XL Vask system, without a customer account'); - // Return an error - $response->error('Vehicle is not registered in the XL Vask system', 400); - } else { - $vehicle->createXLVaskVehicle($vehicleTypeId); - } - } else { - // Set the vehicle type ID - $vehicle->setVehicleTypeId( - $vehicleTypeId - ); - } - // Return the vehicle as an array - $response->success( - [...$vehicle->asArray()] - ); - } else { - // Log the incident - (new logs_o())->add('vehicles', 'global', 1, 0, 'SET_VEHICLE_TYPE_ID', 'No user found, or invalid session'); - // Return an error - $response->error('Invalid session', 401); + $auth = new authentication(); + $user = $auth->get_user(); + $permission_own = self::definePermission('set_vehicle_type_id', subusers_permission_node_key::VEHICLES_EDIT); + $permission_other = self::definePermission('set_vehicle_type_id_other'); + self::requireParameters(['id', 'vehicleTypeId']); + $id = (int)self::getParameter('id'); + self::requireType($id, self::type_int()); + self::requireMinValue($id, 1); + self::requireMaxValue($id, 9999999999); + $vehicleTypeId = (string)self::getParameter('vehicleTypeId'); + self::requireType($vehicleTypeId, self::type_string()); + self::requireMinLength('vehicleTypeId', 1); + self::requireMaxLength('vehicleTypeId', 50); + $vehicle = (new customer_vehicles_o())->select($id); + if (!$vehicle->exists()) { + (new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'SET_VEHICLE_TYPE_ID', 'Vehicle not found'); + $response->error('Vehicle not found', 404); } + self::allowOwnOrDepartmentAccess( + $permission_own, + $permission_other, + (int)$vehicle->customer_id->value(), + null, + null, + 'You are not allowed to edit vehicles from other users' + ); + $customer = (new users_o())->getUserByCustomerNumber((int)$vehicle->customer_id->value()); + if (!$vehicle->hasXLVask()) { + if (!$customer->hasXLVaskCustomerAccount()) { + (new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'SET_VEHICLE_TYPE_ID', 'Attempt to set vehicle type ID without XL Vask registration and no customer account'); + $response->error('Vehicle is not registered in the XL Vask system', 400); + } else { + $vehicle->createXLVaskVehicle($vehicleTypeId); + } + } else { + $vehicle->setVehicleTypeId($vehicleTypeId); + } + $response->success([...$vehicle->asArray()]); }, [ 'set_vehicle_type_id' => 'Set vehicle type ID',