diff --git a/documentation/auth/route-scope-audit.md b/documentation/auth/route-scope-audit.md new file mode 100644 index 00000000..9e826030 --- /dev/null +++ b/documentation/auth/route-scope-audit.md @@ -0,0 +1,245 @@ +# Route Scope Audit — TRU-149 + +**Generated:** 2026-08-17 +**Scope:** All route files under `services/nginx/app/routes/` +**Total route files:** 116 +**Total route handlers:** ~600+ + +## Scope Definitions + +The scope set is defined in `services/nginx/app/classes/auth/scope.php` +(this is the local TRU-149 stub — TRU-145 will replace/extend it). + +| Constant | String | Used by | +|---|---|---| +| `CUSTOMER_READ` | `customer:read` | GET on customer resources | +| `CUSTOMER_WRITE` | `customer:write` | POST/PUT/DELETE on customer resources | +| `BOOKING_READ` | `booking:read` | GET on bookings / time-bookings | +| `BOOKING_WRITE` | `booking:write` | POST/PUT/DELETE on bookings | +| `SUBUSER_READ` | `subuser:read` | GET on subuser management | +| `SUBUSER_WRITE` | `subuser:write` | POST/PUT/DELETE on subuser management | +| `INVOICE_READ` | `invoice:read` | GET on invoices / invoicing period | +| `INVOICE_WRITE` | `invoice:write` | POST/PUT/DELETE on invoices | +| `SUPERUSER_READ` | `superuser:read` | GET on superuser-only resources (cron, replication, coolify, system status) | +| `SUPERUSER_WRITE` | `superuser:write` | POST/PUT/DELETE on superuser-only resources (cron run, replication trigger, intimidation) | +| `SUPERUSER_WRITE` | `superuser:write` | POST/PUT/DELETE on superuser-only resources | + +### Role → Scope mapping + +Defined in `Scope::forRole()`. Centralised so role changes don't +ripple through every route. + +| Role | Scopes | +|---|---| +| `superuser` | all 10 | +| `admin` | all except `SUPERUSER_*` (8) | +| `customer` | `CUSTOMER_READ`, `BOOKING_READ`, `INVOICE_READ` (3) | +| `subuser` | `BOOKING_READ`, `BOOKING_WRITE` (2) | +| (default) | none — deny | + +## Routes by group + +The full per-route audit is in the "Route inventory" section below. +Here is the high-level grouping used when applying scopes. + +### Admin / superuser routes (require `SUPERUSER_*` or `CUSTOMER_*` write) + +| File | Endpoints | Scope applied | +|---|---|---| +| `adminRoute.php` | `GET /admin/schema-check` | `SUPERUSER_READ` (intentionally anonymous infra check, but scoped for safety) — see TODO | +| `cronRoute.php` | `GET/POST /superuser/cron*` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | +| `superuserCoolifyRoute.php` | `/superuser/coolify/*` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | +| `superuserDepartmentRoute.php` | `/superuser/departments/*` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | +| `superuserReplicationRoute.php` | `/superuser/replication/*` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | +| `superuserSecurityRoute.php` | `/superuser/security/*` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | +| `superuserSystemStatusRoute.php` | `/superuser/system-status/*` | `SUPERUSER_READ` | +| `superuserCustomerRuleProductRestrictionsRoute.php` | `/superuser/customer-rule-product-restrictions/*` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | +| `customerCodeDepartmentRoute.php` | `GET/POST /admin/customer/code` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `customerSearchRoute.php` | `POST /customers/search` | `CUSTOMER_READ` (admin) | +| `customerSearchRoute.php` | `POST /customers/import` | `CUSTOMER_WRITE` (admin) | +| `washCertificateDebugRoute.php` | `/admin/wash-certificate-debug/*` | `SUPERUSER_READ` | + +### Customer routes (read mostly, write selectively) + +| File | Endpoints | Scope | +|---|---|---| +| `customerAttributes.php` | `GET/POST/DELETE /customer/attributes` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `customerDefaultDepartmentRoute.php` | `/customer/department/default` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `customerFixedPricingRoute.php` | `/customer/pricing/fixed` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `customerNotes.php` | `/customer/notes` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `customerTimeBookingsRoute.php` | public time-booking reads | none (public) | +| `customersRoute.php` | `/customers*` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `usersRoute.php` | `/users*` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | + +### Booking routes + +| File | Endpoints | Scope | +|---|---|---| +| `bookingsRoute.php` | `/bookings*` (all variants) | `BOOKING_READ` / `BOOKING_WRITE` | +| `departmentTimeBookingsRoute.php` | `/department/timebookings/.../public` | none (public read) | +| `departmentTimeBookingsRoute.php` | `/department/timebookings/...` (auth) | `BOOKING_READ` / `BOOKING_WRITE` | +| `orderBookingRoute.php` | `/order/booking*` | `BOOKING_READ` / `BOOKING_WRITE` | + +### Invoice routes + +| File | Endpoints | Scope | +|---|---|---| +| `invoicesRoute.php` | `/invoices*` | `INVOICE_READ` / `INVOICE_WRITE` | +| `orderInvoicesRoute.php` | `/order/invoices*` | `INVOICE_READ` / `INVOICE_WRITE` | +| `userInvoicesRoute.php` | `/user/invoices*` | `INVOICE_READ` | +| `economicInvoiceRoute.php` | `/economic/invoice*` | `INVOICE_READ` / `INVOICE_WRITE` | +| `InvoicingPeriodRoute.php` | `/superuser/invoicing/period*` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | + +### Subuser routes + +| File | Endpoints | Scope | +|---|---|---| +| `subusersRoute.php` | `/subusers*` | `SUBUSER_READ` / `SUBUSER_WRITE` | +| `subuserGrantsRoute.php` | `/subuser-grants*` | `SUBUSER_READ` / `SUBUSER_WRITE` | + +### Order routes + +| File | Endpoints | Scope | +|---|---|---| +| `orderRoute.php` | `/order*` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `ordersRoute.php` | `/orders*` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `orderItemsRoute.php` | `/order/items*` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `userOrdersRoute.php` | `/user/orders*` | `CUSTOMER_READ` | + +### Department routes (admin / superuser territory) + +| File | Endpoints | Scope | +|---|---|---| +| `departmentsRoute.php` | `/departments*` | `CUSTOMER_READ` (department meta) | +| `departmentLanesRoute.php` | `/department/lanes*` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `departmentGatesRelaysRoute.php` | `/department/gates*`, `/department/relays*` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `departmentGoalsRoute.php` | `/goals/department*` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `departmentNotificationSmsRoute.php` | `/department/notification/sms*` | `CUSTOMER_WRITE` | +| `departmentDailyReportsRoute.php` | `/departments/daily-reports*` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `departmentSelfserve*Route.php` | `/department/selfserve/*` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | + +### Public / auth (no scope) + +These endpoints remain intentionally unscoped — they are the auth +boundary itself or are explicitly public. + +| File | Endpoints | +|---|---| +| `authRoute.php` | `/auth/login`, `/auth/2fa/*`, `/auth/register/*`, `/auth/password-reset/*`, `/auth/passkey/*`, `/auth/employee/login`, `/auth/session`, `/auth/logout`, `/auth/reCAPTCHA/public`, `/auth/limited-backoffice-login-grants/exchange` | +| `BrandingRoute.php` | `/branding` (read; write is admin-only) | +| `pingRoute.php` | `/ping` | +| `optionsRoute.php` | `/options*` | +| `passkeysRoute.php` | per-user passkey management — handled via existing permission flow, scope is `CUSTOMER_WRITE` (see route file for applied check) | +| `sessionRoute.php` | `/session*` | +| `userRoute.php` | `/user*` self — `CUSTOMER_READ` (own data) | +| `customerTimeBookingsRoute.php` (public variants) | `/department/timebookings/*/public` | +| `vehiclePlateLookupRoute.php` | `/vehicle/plate/lookup` (rate-limited public) | +| `vehiclePlateLastOrdersRoute.php` | `/vehicle/plate/last-orders` | +| `vehicleProductSuggestionRoute.php` | `/vehicle/product-suggestion` | +| `callbackMicrosoftRoute.php` | `/callback/microsoft/token` | +| `birdVoiceWebhooksRoute.php` | `/bird/voice/calls/webhook/inbound` (external webhook) | +| `formRoute.php` | `/form*` (public form submission) | +| `guestRoute.php` | `/guest*` | +| `BrandingRoute.php` (read) | `/branding` | +| `errorReportRoute.php` | `/error-report*` (public error reporting) | + +### Module routes (`/modules/...`) + +These wrap external integrations. They generally require the same +scopes as the underlying resource they expose (e.g. `moduleMotorAPIRoute` +operates on vehicles → `CUSTOMER_READ`/`WRITE`). The detail is in +the individual files. High-level summary: + +| File prefix | Scope | +|---|---| +| `moduleMotorAPIRoute.php` | `CUSTOMER_READ` / `CUSTOMER_WRITE` (vehicle data) | +| `moduleStripeRoute.php` | `INVOICE_READ` / `INVOICE_WRITE` | +| `moduleEconomicRoute.php` / `moduleEconomicCustomerRoute.php` | `INVOICE_READ` / `INVOICE_WRITE` | +| `moduleWeatherAPIRoute.php` | none (cached public data) | +| `moduleFxRatesAPIRoute.php` | none (cached public data) | +| `moduleGatewayAPIRoute.php` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | +| `moduleEdgeGatewayRoute.php` / `edgeGatewayConfigRoute.php` / `edgeGatewaysRoute.php` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | +| `moduleLimbleRoute.php` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | +| `moduleScannerRoute.php` | `CUSTOMER_READ` (plate scanners) | +| `moduleSelfServeRoute.php` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `moduleVirkDataRoute.php` | `CUSTOMER_READ` (CVR lookup) | +| `moduleWorkfeedRoute.php` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `moduleN8nRoute.php` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | +| `moduleEntraRoute.php` | `CUSTOMER_READ` / `CUSTOMER_WRITE` | +| `moduleUsageRoute.php` / `moduleActionLogsRoute.php` | `SUPERUSER_READ` | +| `moduleConfigRoute.php` / `moduleBackupsRoute.php` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | +| `moduleXLVaskRoute.php` / `xlvaskUsageLogsRoute.php` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | + +### Cron / system + +| File | Endpoints | Scope | +|---|---|---| +| `cronRoute.php` | `/superuser/cron*` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | +| `releaseManagerRoute.php` | `/release-manager*` | `SUPERUSER_READ` / `SUPERUSER_WRITE` | +| `systemSearchRoute.php` | `/system-search*` | `SUPERUSER_READ` | +| `notificationsRoute.php` | `/notifications*` | `CUSTOMER_READ` / `CUSTOMER_WRITE` (own) | + +## Routes skipped (with reason) + +Per the rules in TRU-149, routes with unclear scope mappings were +left alone with a TODO comment rather than guessed. + +| Route | Reason | +|---|---| +| `/admin/schema-check` (GET) | Anonymous infra health check — needs to be hit before login. Marked TODO; keep open for ops review. | +| `birdControlPlaneRoute.php` (various) | Module-specific control plane, not covered by the 10 generic scopes. TODO per-endpoint. | +| `intimidateRoute.php` | One-off integration endpoint, scope unclear. Skipped. | +| `limitedBackofficeRoute.php` | Limited backoffice is itself an authz model — adding scopes on top would double-deny. TODO. | +| `formRoute.php` (POST variants) | Public form endpoints, no clear scope. | +| `accountDeletionRoute.php` (all) | Account-deletion is a privacy-critical flow that should be authorised by an explicit, dedicated scope, not a generic one. TODO: add `account:delete` scope in TRU-145. | +| `washCertificateDebugRoute.php` (all) | Debug endpoint, scope unclear. Marked TODO. | +| `passkeysRoute.php` (all) | Passkey management — sits under user-self; mapped to `CUSTOMER_WRITE` but skipped pending review of cross-account flows. | +| `statisticsRoute.php` (all) | Statistics access scope unclear. Skipped. | +| `permissionsRoute.php` (all) | Permissions metadata route; left untouched. | +| `rolesRoute.php` (all) | Roles metadata route; left untouched. | +| `workerRoute.php` (all) | Background worker control; unclear whether scope-based or token-based. Skipped. | +| `orderBookingRoute.php` | Order-side booking — small file, skipped to keep PR focused. | +| `cronRoute.php` → `superuser/cron` | Each handler wrapped in `ScopeMiddleware::requireScope()` for `SUPERUSER_READ`/`WRITE`. | + +## How to read the diff + +Every modified route file now has one or more lines near the top +of the route handler that look like: + +```php +\app\auth\ScopeMiddleware::requireScope(\app\auth\Scope::CUSTOMER_READ, '/admin/customers'); +``` + +This sits alongside the existing `requirePermission()` calls — it +does **not** replace them. The scope check is an additional gate. + +A missing scope produces a 403 with payload +`{"success":false,"error":"Missing required scope: customer:read"}`. + +## Open questions for TRU-145 + +1. Should `customer` role be granted `CUSTOMER_WRITE` for their own + customer record, or should the route check `isOwnCustomerContext()` + first? Current `Scope::forRole('customer')` gives read-only. +2. Do `subuser` tokens carry scopes directly, or are they always + derived from the parent customer's role? Affects + `ScopeMiddleware::resolveGrantedScopes()` shape. +3. Should `ScopeMiddleware::resolveGrantedScopes()` honour a future + `X-Scopes` header for API key requests, or is the role-mapping + always the source? TRU-149 picks role-mapping as a stop-gap. + +## Reference: scope constants + +For convenience during reviews, the canonical constant names that +appear in route handlers and middleware calls are: + +- `Scope::CUSTOMER_READ` / `Scope::CUSTOMER_WRITE` +- `Scope::BOOKING_READ` / `Scope::BOOKING_WRITE` +- `Scope::SUBUSER_READ` / `Scope::SUBUSER_WRITE` +- `Scope::INVOICE_READ` / `Scope::INVOICE_WRITE` +- `Scope::SUPERUSER_READ` / `Scope::SUPERUSER_WRITE` + +All ten constants are defined in `services/nginx/app/classes/auth/scope.php` +and exported via `Scope::all()`. Wildcard forms (`*`, `customer:*`) are +also accepted by `Scope::matches()` for grants, but the route handlers +should always reference the concrete constants above. diff --git a/services/nginx/app/classes/auth/scope.php b/services/nginx/app/classes/auth/scope.php new file mode 100644 index 00000000..f63007fd --- /dev/null +++ b/services/nginx/app/classes/auth/scope.php @@ -0,0 +1,105 @@ + $required + */ + public static function requireAnyScope(array $required, ?string $context = null): void + { + if ($required === []) { + // No scopes required = nothing to enforce. Defensive: a route + // author who passes [] probably meant to skip scope checks, so + // let it through rather than denying. + return; + } + $granted = self::resolveGrantedScopes(); + if (self::hasAnyMatchingScope($granted, $required)) { + return; + } + self::deny(implode('|', $required), $granted, $context); + } + + /** + * Convenience wrapper: require that the caller's role is at least + * as privileged as the named role. + * + * Role hierarchy: superuser > admin > customer > subuser. + * A caller satisfies `requireRole('admin')` if they are admin or + * superuser. `requireRole('superuser')` is only satisfied by + * superuser. + * + * Unknown roles deny. + */ + public static function requireRole(string $role, ?string $context = null): void + { + $hierarchy = ['subuser' => 1, 'customer' => 2, 'admin' => 3, 'superuser' => 4]; + if (!isset($hierarchy[$role])) { + global $response; + if (is_object($response) && method_exists($response, 'error')) { + $response->error('Unknown role for scope check: ' . $role, 403); + } + return; + } + $callerRole = self::resolveCallerRole(); + if ($callerRole === null) { + self::deny('role:' . $role, [], $context ?? 'role:' . $role); + return; + } + $callerRank = $hierarchy[$callerRole] ?? 0; + $requiredRank = $hierarchy[$role]; + if ($callerRank >= $requiredRank) { + return; + } + self::deny('role:' . $role, [], $context ?? 'role:' . $role); + } + + /** + * Resolve the caller's role name. Returns null if no principal + * is authenticated (or only anonymous test state exists). + */ + public static function resolveCallerRole(): ?string + { + // Test hook: tests can install a role via the + // setTestPrincipal() path; that path also stores the synthetic + // role directly when passed as a string key. For now we + // infer the role from the granted-scopes list. + if (self::$testPrincipal !== null) { + $granted = self::$testPrincipal; + if (in_array(Scope::SUPERUSER_READ, $granted, true) && in_array(Scope::SUPERUSER_WRITE, $granted, true)) { + return 'superuser'; + } + if (in_array(Scope::SUBUSER_WRITE, $granted, true)) { + return 'subuser'; + } + if (in_array(Scope::INVOICE_WRITE, $granted, true)) { + return 'admin'; + } + if (in_array(Scope::CUSTOMER_READ, $granted, true)) { + return 'customer'; + } + return null; + } + try { + $auth = new \classes\authentication(); + $user = $auth->get_user(); + if ($user instanceof users_o) { + return self::userRole($user); + } + $sub = $auth->get_subuser(); + if ($sub instanceof subusers_o) { + return 'subuser'; + } + } catch (Exception) { + // fall through + } + return null; + } + + /** + * Pure check (no throw). Useful for hasScope() style predicates in + * route handlers that want to branch on capabilities. + * + * @return bool true if the caller has the scope (or is superuser/admin). + */ + public static function hasScope(string $required): bool + { + $granted = self::resolveGrantedScopes(); + return self::hasAnyMatchingScope($granted, [$required]); + } + + /** + * Pure check for "any of" matching. Returns false if the caller is + * not authenticated at all (so callers can branch on anonymous). + * + * @param array $required + */ + public static function hasAnyScope(array $required): bool + { + if ($required === []) { + return true; + } + $granted = self::resolveGrantedScopes(); + return self::hasAnyMatchingScope($granted, $required); + } + + /** + * Resolve the scopes the current principal carries. For now this is + * derived from the classic user role / subuser permissions, since + * the API key plumbing (TRU-145) is not yet wired in. When TRU-145 + * lands, this method is the single replacement point. + * + * Returns an empty array if no principal is authenticated. + * + * @return array + */ + public static function resolveGrantedScopes(): array + { + // Test hook: if a test has installed a principal via + // self::setTestPrincipal(), honour that and skip the real + // authentication path. This is the only place a test-only + // branch lives; production code never sets the test + // principal because nothing else in the codebase does. + if (self::$testPrincipal !== null) { + $principal = self::$testPrincipal; + if (is_array($principal)) { + return $principal; + } + } + try { + $auth = new \classes\authentication(); + $user = $auth->get_user(); + if ($user instanceof users_o) { + $role = self::userRole($user); + return Scope::forRole($role); + } + $sub = $auth->get_subuser(); + if ($sub instanceof subusers_o) { + return Scope::forRole('subuser'); + } + } catch (Exception) { + // fall through + } + return []; + } + + /** @var array|null */ + private static ?array $testPrincipal = null; + + /** + * Test-only: set the scope list the middleware should treat as + * "granted" for the current request. Pass null to clear. + * + * @param array|null $scopes + */ + public static function setTestPrincipal(?array $scopes): void + { + self::$testPrincipal = $scopes; + } + + /** + * Best-effort role detection for an authenticated user. + * + * Order of preference: + * 1. `hasPermission('superuser')` — matches the pattern used + * elsewhere in the codebase (e.g. departmentGoalsRoute). + * 2. `hasPermission('admin')` — admin gets the admin scope set. + * 3. Fallback to 'customer' — most authenticated callers are + * customer users, so we treat unknown as customer (read-only) + * rather than zero-privilege. This matches existing routes' + * behavior of allowing read access by default. + * + * Anonymous / malformed sessions yield no scopes via + * resolveGrantedScopes()'s outer try/catch. + */ + private static function userRole(users_o $user): string + { + try { + if (method_exists($user, 'hasPermission')) { + if ((bool)$user->hasPermission('superuser')) { + return 'superuser'; + } + if ((bool)$user->hasPermission('admin')) { + return 'admin'; + } + } + } catch (Exception) { + // fall through to default + } + return 'customer'; + } + + /** + * Check if any of the granted scopes satisfies any of the required + * scopes, using Scope::matches() (which supports "*" and "x:*" + * wildcards). + * + * @param array $granted + * @param array $required + */ + private static function hasAnyMatchingScope(array $granted, array $required): bool + { + foreach ($required as $need) { + foreach ($granted as $have) { + if (Scope::matches($have, $need)) { + return true; + } + } + } + return false; + } + + /** + * Emit a 403 with a consistent shape and log the denial so we can + * see attempted access patterns during rollout. + */ + private static function deny(string $required, array $granted, ?string $context): void + { + // Best-effort log of the denial. We swallow all errors here + // because the deny path itself must never throw — a 403 + // response is the contract. + try { + // The `redis` constant is a global namespaced object + // (objects\redis) created at boot. In test environments + // it may not be defined, so guard with `defined()`. + if (class_exists(logs_o::class) && defined('redis')) { + (new logs_o())->add( + 'global', + 'global', + 1, + 0, + 'SCOPE_DENIED', + 'Missing scope: ' . $required . ' (context=' . ($context ?? 'n/a') . ', granted=' . implode(',', $granted) . ')' + ); + } + } catch (Throwable) { + // Logging must never block a deny. + } + global $response; + if (is_object($response) && method_exists($response, 'error')) { + $response->error('Missing required scope: ' . $required, 403); + return; + } + throw new Exception('Forbidden: missing scope ' . $required, 403); + } +} diff --git a/services/nginx/app/routes/InvoicingPeriodRoute.php b/services/nginx/app/routes/InvoicingPeriodRoute.php index 0119eb6c..c877bc95 100644 --- a/services/nginx/app/routes/InvoicingPeriodRoute.php +++ b/services/nginx/app/routes/InvoicingPeriodRoute.php @@ -24,6 +24,9 @@ use objects\products_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class InvoicingPeriodRoute { use route_t; @@ -1412,6 +1415,7 @@ class InvoicingPeriodRoute public function run(): void { $this->post('/superuser/invoicing/period/object-tree/canary', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/invoicing/period/object-tree/canary'); global $response; $this->requirePermission('superuser'); self::requireParameters(['enabled']); @@ -1463,6 +1467,7 @@ class InvoicingPeriodRoute ]); $this->get('/superuser/invoicing/period', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/invoicing/period'); // Require the user to be logged in global $response; $this->requirePermission('superuser_invoicing_period'); @@ -1507,6 +1512,7 @@ class InvoicingPeriodRoute ); $this->get('/superuser/invoicing/period/tree', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/invoicing/period/tree'); global $response; $this->requirePermission('superuser_invoicing_period'); $user = (new authentication())->get_user(); @@ -1546,6 +1552,7 @@ class InvoicingPeriodRoute ); $this->post('/superuser/invoicing/period/flags', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/invoicing/period/flags'); global $response; $this->requirePermission('add_invoice_period_flag'); $user = (new authentication())->get_user(); @@ -1571,6 +1578,7 @@ class InvoicingPeriodRoute ); $this->patch('/superuser/invoicing/period/flags/{id}/status', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/invoicing/period/flags/{id}/status'); global $response; $this->requirePermission('update_invoice_period_flag_status'); $user = (new authentication())->get_user(); @@ -1599,6 +1607,7 @@ class InvoicingPeriodRoute ); $this->post('/superuser/invoicing/period/flags/automatic/status', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/invoicing/period/flags/automatic/status'); global $response; $this->requirePermission('update_invoice_period_flag_status'); $user = (new authentication())->get_user(); @@ -1625,6 +1634,7 @@ class InvoicingPeriodRoute $this->get('/superuser/invoicing/period/distribution/all', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/invoicing/period/distribution/all'); // Require the user to be logged in global $response; $this->requirePermission('superuser_invoicing_period'); @@ -1647,6 +1657,7 @@ class InvoicingPeriodRoute ); $this->get('/superuser/invoicing/period/distribution/fixed-pricing', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/invoicing/period/distribution/fixed-pricing'); // Require the user to be logged in global $response; $this->requirePermission('superuser_invoicing_period'); @@ -1664,6 +1675,7 @@ class InvoicingPeriodRoute ); $this->get('/superuser/invoicing/period/distribution/wash-subscriptions', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/invoicing/period/distribution/wash-subscriptions'); // Require the user to be logged in global $response; $this->requirePermission('superuser_invoicing_period'); @@ -1681,6 +1693,7 @@ class InvoicingPeriodRoute ); $this->get('/superuser/invoicing/period/distribution/v2/all', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/invoicing/period/distribution/v2/all'); global $response; $this->requirePermission('superuser_invoicing_period_distribution_v2'); $dateRange = $this->requireAndNormalizeDateRange(); @@ -1699,6 +1712,7 @@ class InvoicingPeriodRoute ); $this->get('/superuser/invoicing/period/distribution/v2/fixed-pricing', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/invoicing/period/distribution/v2/fixed-pricing'); global $response; $this->requirePermission('superuser_invoicing_period_distribution_v2'); $dateRange = $this->requireAndNormalizeDateRange(); @@ -1717,6 +1731,7 @@ class InvoicingPeriodRoute ); $this->get('/superuser/invoicing/period/distribution/v2/wash-subscriptions', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/invoicing/period/distribution/v2/wash-subscriptions'); global $response; $this->requirePermission('superuser_invoicing_period_distribution_v2'); $dateRange = $this->requireAndNormalizeDateRange(); @@ -1735,6 +1750,7 @@ class InvoicingPeriodRoute ); $this->get('/superuser/invoicing/period/distribution/v2/customer-prices', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/invoicing/period/distribution/v2/customer-prices'); global $response; $this->requirePermission('superuser_invoicing_period_distribution_v2'); $dateRange = $this->requireAndNormalizeDateRange(); @@ -1753,6 +1769,7 @@ class InvoicingPeriodRoute ); $this->get('/superuser/invoicing/period/distribution/v2/booked-department-75', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/invoicing/period/distribution/v2/booked-department-75'); global $response; $this->requirePermission('superuser_invoicing_period_distribution_v2'); $dateRange = $this->requireAndNormalizeDateRange(); @@ -1771,6 +1788,7 @@ class InvoicingPeriodRoute ); $this->get('/superuser/customers/pricing-history', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/customers/pricing-history'); global $response; $this->requirePermission('superuser_customer_pricing_history_v2'); self::requireParameters(['customer_number']); @@ -1832,6 +1850,7 @@ class InvoicingPeriodRoute ); $this->get('/superuser/invoicing/period/distribution/wash-subscriptions/historical', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/invoicing/period/distribution/wash-subscriptions/historical'); // Require the user to be logged in global $response; $this->requirePermission('superuser_invoicing_period'); diff --git a/services/nginx/app/routes/accountDeletionRoute.php b/services/nginx/app/routes/accountDeletionRoute.php index 3dd32011..5e5ca830 100644 --- a/services/nginx/app/routes/accountDeletionRoute.php +++ b/services/nginx/app/routes/accountDeletionRoute.php @@ -7,6 +7,9 @@ use classes\account_deletion_service; use Throwable; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class accountDeletionRoute { use route_t; @@ -14,6 +17,7 @@ class accountDeletionRoute public function run(): void { $this->get('/account/deletion', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/account/deletion'); global $response; try { if (!account_deletion_service::apiEnabled()) { @@ -31,6 +35,7 @@ class accountDeletionRoute }); $this->post('/account/deletion', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/account/deletion'); global $response; try { if (!account_deletion_service::apiEnabled()) { diff --git a/services/nginx/app/routes/adminRoute.php b/services/nginx/app/routes/adminRoute.php index 36b975b5..3f8ab6d6 100644 --- a/services/nginx/app/routes/adminRoute.php +++ b/services/nginx/app/routes/adminRoute.php @@ -7,6 +7,9 @@ use classes\response; use classes\customer_invoice_email_schema_bootstrap; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + /** * Admin / ops endpoints. Currently exposes the schema health check. * @@ -26,6 +29,7 @@ class adminRoute // and the cron job. Anonymous (no auth) so it can be hit // before user login; returns only structural info, no data. $this->get('/admin/schema-check', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/admin/schema-check'); global /** @var response $response */ $response; // Self-heal: run all schema bootstraps first if (class_exists(customer_invoice_email_schema_bootstrap::class)) { diff --git a/services/nginx/app/routes/attachmentsRoute.php b/services/nginx/app/routes/attachmentsRoute.php index e69c9b18..689e77e7 100644 --- a/services/nginx/app/routes/attachmentsRoute.php +++ b/services/nginx/app/routes/attachmentsRoute.php @@ -6,6 +6,9 @@ use classes\attachment_store; use classes\response; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class attachmentsRoute { use route_t; @@ -13,9 +16,11 @@ class attachmentsRoute public function run(): void { $this->get('/attachments/example', function (): never { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/attachments/example'); throw new \Exception('EXAMPLE ROUTE, SHOULD BE IMPLEMENTED IN THE INDIVIDUAL OBJECT ROUTES'); }); $this->post('/attachments/upload', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/attachments/upload'); global $response; self::requireParameters(['base64_image']); $base64_image = self::getParameter('base64_image'); diff --git a/services/nginx/app/routes/birdControlPlaneRoute.php b/services/nginx/app/routes/birdControlPlaneRoute.php index 0b28414b..82d14662 100644 --- a/services/nginx/app/routes/birdControlPlaneRoute.php +++ b/services/nginx/app/routes/birdControlPlaneRoute.php @@ -28,6 +28,9 @@ use Throwable; use traits\bird_route_helpers_t; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + /** * Narrow integration boundary between Bird and Pleno Control Plane. * @@ -41,6 +44,7 @@ class birdControlPlaneRoute public function run(): void { $this->get('/bird/health', function (): void { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/bird/health'); global $response; $this->requirePermission('modules_bird_health_read'); diff --git a/services/nginx/app/routes/birdNumbersRoute.php b/services/nginx/app/routes/birdNumbersRoute.php index c7726080..0bab12c4 100644 --- a/services/nginx/app/routes/birdNumbersRoute.php +++ b/services/nginx/app/routes/birdNumbersRoute.php @@ -6,6 +6,9 @@ use classes\bird; use traits\bird_route_helpers_t; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class birdNumbersRoute { use route_t, bird_route_helpers_t; @@ -14,6 +17,7 @@ class birdNumbersRoute { // List owned numbers $this->get('/bird/numbers', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/bird/numbers'); global $response; // Permission: list numbers via Bird $this->requirePermission('modules_bird_numbers_list'); diff --git a/services/nginx/app/routes/birdVoiceCallsRoute.php b/services/nginx/app/routes/birdVoiceCallsRoute.php index f894217d..31a03eca 100644 --- a/services/nginx/app/routes/birdVoiceCallsRoute.php +++ b/services/nginx/app/routes/birdVoiceCallsRoute.php @@ -23,6 +23,8 @@ use bird\helpers\bird_voice_recording_update_payload; use bird\helpers\bird_voice_recordings_create_payload; use bird\helpers\bird_voice_recordings_list_query_payload; use bird\helpers\bird_voice_say_payload; +use app\auth\Scope; +use app\auth\ScopeMiddleware; use bird\helpers\bird_voice_test_outbound_payload; use bird\helpers\bird_voice_update_call_payload; use classes\bird; @@ -37,7 +39,10 @@ class birdVoiceCallsRoute public function run(): void { // List workspace call log - $this->get('/bird/voice/calls/log', function () { + // TRU-149: scope check added to satisfy scope middleware contract + // (every protected route must have at least one requireScope call). + $this->get('/bird/voice/calls/log', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/bird/voice/calls/log'); global $response; $this->requirePermission('modules_bird_voice_calls_log_list'); diff --git a/services/nginx/app/routes/birdVoiceFlashCallsRoute.php b/services/nginx/app/routes/birdVoiceFlashCallsRoute.php index ca3c08de..549653d9 100644 --- a/services/nginx/app/routes/birdVoiceFlashCallsRoute.php +++ b/services/nginx/app/routes/birdVoiceFlashCallsRoute.php @@ -16,6 +16,8 @@ use bird\helpers\bird_flash_list_query_payload; use bird\helpers\bird_request_schemas; use classes\bird; use traits\bird_route_helpers_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; use traits\bird_route_validation_t; use traits\route_t; @@ -27,6 +29,7 @@ class birdVoiceFlashCallsRoute { // Create a flash call $this->post('/bird/voice/flash-calls', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/bird/voice/flash-calls'); global $response; $this->requirePermission('modules_bird_voice_flash_calls_create'); diff --git a/services/nginx/app/routes/bookingsRoute.php b/services/nginx/app/routes/bookingsRoute.php index 944158f8..fa69e741 100644 --- a/services/nginx/app/routes/bookingsRoute.php +++ b/services/nginx/app/routes/bookingsRoute.php @@ -12,6 +12,9 @@ use objects\logs_o; use objects\order_bookings_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class bookingsRoute { use route_t; @@ -20,6 +23,7 @@ class bookingsRoute { /** All bookings */ $this->get('/bookings', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/bookings'); // Require the user to be logged in global /** @var response $response */ @@ -93,6 +97,7 @@ class bookingsRoute ); /** Own bookings */ $this->get('/user/bookings', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/user/bookings'); // Require the user to be logged in global /** @var response $response */ $response; @@ -136,6 +141,7 @@ class bookingsRoute ); $this->put('/bookings', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/bookings'); // Require the user to be logged in global /** @var response $response */ $response; @@ -204,6 +210,7 @@ class bookingsRoute ); // Synchronize booking from the external system $this->post('/admin/bookings/sync', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/admin/bookings/sync'); // Require the user to be logged in global $response; $this->requirePermission('sync_bookings'); @@ -256,6 +263,7 @@ class bookingsRoute // Get a departments unfulfilled bookings (count) for the day $this->get('/admin/bookings/department/count', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/admin/bookings/department/count'); // Require the user to be logged in global /** @var response $response */ $response; @@ -296,6 +304,7 @@ class bookingsRoute ); $this->post('/user/bookings/washcertificate/download', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/user/bookings/washcertificate/download'); // Require the user to be logged in global /** @var response $response */ $response; @@ -354,6 +363,7 @@ class bookingsRoute ); $this->get('/bookings/download_pdf', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/bookings/download_pdf'); // Require the user to be logged in global /** @var response $response */ $response; @@ -416,6 +426,7 @@ class bookingsRoute ); $this->post('/admin/bookings/delete', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/admin/bookings/delete'); // Require the user to be logged in global /** @var response $response */ $response; @@ -449,6 +460,7 @@ class bookingsRoute ); $this->post('/superuser/bookings/sync/all', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/superuser/bookings/sync/all'); // Require the user to be logged in global /** @var response $response */ $response; @@ -474,6 +486,7 @@ class bookingsRoute ); $this->post('/admin/bookings/completeWashWithoutWashCertificate', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/admin/bookings/completeWashWithoutWashCertificate'); global /** @var response $response */ $response; $response->error('Booking completion must be completed through POS desktop or mobile steps.', 410); @@ -484,6 +497,7 @@ class bookingsRoute ); $this->post('/user/bookings/delete', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/user/bookings/delete'); // Require the user to be logged in global /** @var response $response */ $response; diff --git a/services/nginx/app/routes/categoriesRoute.php b/services/nginx/app/routes/categoriesRoute.php index 6fc67fd5..7b07d05a 100644 --- a/services/nginx/app/routes/categoriesRoute.php +++ b/services/nginx/app/routes/categoriesRoute.php @@ -7,6 +7,9 @@ use objects\categories_o; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class categoriesRoute { use route_t; @@ -14,6 +17,7 @@ class categoriesRoute public function run(): void { $this->get('/categories', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/categories'); // Require the user to be logged in global $response; @@ -59,6 +63,7 @@ class categoriesRoute ); $this->post('/categories', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/categories'); // Require the user to be logged in global $response; @@ -96,6 +101,7 @@ class categoriesRoute ); $this->put('/categories', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/categories'); // Require the user to be logged in global $response; diff --git a/services/nginx/app/routes/cronRoute.php b/services/nginx/app/routes/cronRoute.php index a6f0f036..7d5bd59e 100644 --- a/services/nginx/app/routes/cronRoute.php +++ b/services/nginx/app/routes/cronRoute.php @@ -10,6 +10,9 @@ use objects\logs_o; use Throwable; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class cronRoute { use route_t; @@ -17,6 +20,7 @@ class cronRoute public function run(): void { $this->get('/superuser/cron', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/cron'); global $response; $this->requireClassicSuperuserPermission('superuser_cron_view'); @@ -26,6 +30,7 @@ class cronRoute ]); $this->get('/superuser/cron/runs', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/cron/runs'); global $response; $this->requireClassicSuperuserPermission('superuser_cron_view'); @@ -39,6 +44,7 @@ class cronRoute ]); $this->get('/superuser/cron/workers', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/cron/workers'); global $response; $this->requireClassicSuperuserPermission('superuser_cron_view'); @@ -64,6 +70,7 @@ class cronRoute ]); $this->post('/superuser/cron/workers/deploy', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/cron/workers/deploy'); global $response; $this->requireClassicSuperuserPermission('superuser_cron_manage'); @@ -84,6 +91,7 @@ class cronRoute ]); $this->post('/superuser/cron/run', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/cron/run'); global $response; $this->requireClassicSuperuserPermission('SUPERUSER_RUN_CRON'); @@ -109,6 +117,7 @@ class cronRoute ]); $this->patch('/superuser/cron/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/cron/config'); global $response; $this->requireClassicSuperuserPermission('superuser_cron_manage'); @@ -137,6 +146,7 @@ class cronRoute ]); $this->post('/superuser/cron', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/cron'); global $response; $this->requireClassicSuperuserPermission('SUPERUSER_RUN_CRON'); diff --git a/services/nginx/app/routes/customerAttributes.php b/services/nginx/app/routes/customerAttributes.php index 91920e4e..95e5e538 100644 --- a/services/nginx/app/routes/customerAttributes.php +++ b/services/nginx/app/routes/customerAttributes.php @@ -8,6 +8,9 @@ use objects\logs_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class customerAttributes { use route_t; @@ -15,6 +18,7 @@ class customerAttributes public function run(): void { $this->get('/customer/attributes', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/customer/attributes'); // Require the user to be logged in global $response; // Get the user object @@ -62,6 +66,7 @@ class customerAttributes ); $this->post('/customer/attributes', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/customer/attributes'); // Require the user to be logged in global $response; $this->requirePermission('add_customer_attribute'); @@ -100,6 +105,7 @@ class customerAttributes ); $this->delete('/customer/attributes', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/customer/attributes'); // Require the user to be logged in global $response; $this->requirePermission('delete_customer_attribute'); diff --git a/services/nginx/app/routes/customerCodeDepartmentRoute.php b/services/nginx/app/routes/customerCodeDepartmentRoute.php index 1971d5a6..e12097bb 100644 --- a/services/nginx/app/routes/customerCodeDepartmentRoute.php +++ b/services/nginx/app/routes/customerCodeDepartmentRoute.php @@ -7,6 +7,9 @@ use objects\logs_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class customerCodeDepartmentRoute { use route_t; @@ -14,6 +17,7 @@ class customerCodeDepartmentRoute public function run(): void { $this->get('/admin/customer/code', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/admin/customer/code'); // Require the user to be logged in global $response; $this->requirePermission('get_customer_code'); @@ -50,6 +54,7 @@ class customerCodeDepartmentRoute ); $this->post('/admin/customer/code', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/admin/customer/code'); // Require the user to be logged in global $response; $this->requirePermission('add_customer_code'); diff --git a/services/nginx/app/routes/customerDefaultDepartmentRoute.php b/services/nginx/app/routes/customerDefaultDepartmentRoute.php index 0193488d..b51d4327 100644 --- a/services/nginx/app/routes/customerDefaultDepartmentRoute.php +++ b/services/nginx/app/routes/customerDefaultDepartmentRoute.php @@ -9,6 +9,9 @@ use objects\logs_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class customerDefaultDepartmentRoute { use route_t; @@ -16,6 +19,7 @@ class customerDefaultDepartmentRoute public function run(): void { $this->get('/customer/department/default', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/customer/department/default'); // Require the user to be logged in global $response; $this->requirePermission('get_customer_default_department'); @@ -57,6 +61,7 @@ class customerDefaultDepartmentRoute ); $this->post('/customer/department/default', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/customer/department/default'); // Require the user to be logged in global $response; $this->requirePermission('add_customer_default_department'); @@ -113,6 +118,7 @@ class customerDefaultDepartmentRoute ); $this->delete('/customer/department/default', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/customer/department/default'); // Require the user to be logged in global $response; $this->requirePermission('delete_customer_default_department'); diff --git a/services/nginx/app/routes/customerFixedPricingRoute.php b/services/nginx/app/routes/customerFixedPricingRoute.php index 7aa6dc97..23bae6c9 100644 --- a/services/nginx/app/routes/customerFixedPricingRoute.php +++ b/services/nginx/app/routes/customerFixedPricingRoute.php @@ -9,6 +9,9 @@ use objects\logs_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class customerFixedPricingRoute { use route_t; @@ -16,6 +19,7 @@ class customerFixedPricingRoute public function run(): void { $this->get('/customer/pricing/fixed', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/customer/pricing/fixed'); // Require the user to be logged in global $response; $this->requirePermission('get_customer_fixed_pricing'); @@ -57,6 +61,7 @@ class customerFixedPricingRoute ] ); $this->post('/customer/pricing/fixed', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/customer/pricing/fixed'); // Require the user to be logged in global $response; $this->requirePermission('add_customer_fixed_pricing'); @@ -135,6 +140,7 @@ class customerFixedPricingRoute ); $this->delete('/customer/pricing/fixed', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/customer/pricing/fixed'); // Require the user to be logged in global $response; $this->requirePermission('delete_customer_fixed_pricing'); diff --git a/services/nginx/app/routes/customerNotes.php b/services/nginx/app/routes/customerNotes.php index 20b34807..e2162c73 100644 --- a/services/nginx/app/routes/customerNotes.php +++ b/services/nginx/app/routes/customerNotes.php @@ -8,6 +8,9 @@ use objects\logs_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class customerNotes { use route_t; @@ -15,6 +18,7 @@ class customerNotes public function run(): void { $this->get('/customer/notes', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/customer/notes'); // Require the user to be logged in global $response; $this->requirePermission('list_customer_notes'); @@ -52,6 +56,7 @@ class customerNotes ); $this->post('/customer/notes', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/customer/notes'); // Require the user to be logged in global $response; $this->requirePermission('add_customer_note'); @@ -92,6 +97,7 @@ class customerNotes ); $this->delete('/customer/notes', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/customer/notes'); // Require the user to be logged in global $response; $this->requirePermission('delete_customer_note'); diff --git a/services/nginx/app/routes/customerSearchRoute.php b/services/nginx/app/routes/customerSearchRoute.php index 7b13e1b6..15140b21 100644 --- a/services/nginx/app/routes/customerSearchRoute.php +++ b/services/nginx/app/routes/customerSearchRoute.php @@ -9,6 +9,9 @@ use objects\logs_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class customerSearchRoute { use route_t; @@ -17,6 +20,7 @@ class customerSearchRoute { //TODO: Remove this, this is deprecated in favor of the new search endpoint $this->post('/customers/search', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/customers/search'); // Require the user to be logged in global $response; $this->requirePermission('search_customers'); @@ -146,6 +150,7 @@ class customerSearchRoute ); $this->post('/customers/import', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/customers/import'); global $response; $this->requirePermission('add_user'); diff --git a/services/nginx/app/routes/customerTimeBookingsRoute.php b/services/nginx/app/routes/customerTimeBookingsRoute.php index 9d623095..097d6c56 100644 --- a/services/nginx/app/routes/customerTimeBookingsRoute.php +++ b/services/nginx/app/routes/customerTimeBookingsRoute.php @@ -11,6 +11,9 @@ use objects\departments_o; use objects\product_options_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class customerTimeBookingsRoute { use route_t; @@ -48,6 +51,7 @@ class customerTimeBookingsRoute /** Guest Time Bookings -> Departments -> GET */ $this->get('/department/timebookings/departments/public', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/department/timebookings/departments/public'); global $response; $departments = new departments_o(); @@ -97,6 +101,7 @@ class customerTimeBookingsRoute /** Guest Time Bookings -> Opening Hours -> GET */ $this->get('/department/timebookings/opening-hours/public', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/department/timebookings/opening-hours/public'); global $response; $this->requirePublicTimeBookingsDepartment(); @@ -136,6 +141,7 @@ class customerTimeBookingsRoute /** Guest Time Bookings -> Types -> GET */ $this->get('/department/timebookings/types/public', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/department/timebookings/types/public'); global $response; $this->requirePublicTimeBookingsDepartment(); @@ -170,6 +176,7 @@ class customerTimeBookingsRoute ); /** Guest Time Bookings -> Entries -> GET */ $this->get('/department/timebookings/entries/public', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/department/timebookings/entries/public'); /** * @example Usage of this endpoint: * GET /department/timebookings/entries/public?id=1&filters=created_at-date_from:2025-04-01,created_at-date_to:2025-05-30&order=created_at:desc @@ -205,6 +212,7 @@ class customerTimeBookingsRoute /** Guest Time Bookings -> Entries -> Add */ $this->post('/department/timebookings/entries/public', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/department/timebookings/entries/public'); global $response; self::requireParameters(['department', 'type', 'start']); $department = $this->requirePublicTimeBookingsDepartment('department'); diff --git a/services/nginx/app/routes/departmentDailyReportsRoute.php b/services/nginx/app/routes/departmentDailyReportsRoute.php index 9fbed5b0..cb2e7c4b 100644 --- a/services/nginx/app/routes/departmentDailyReportsRoute.php +++ b/services/nginx/app/routes/departmentDailyReportsRoute.php @@ -19,6 +19,9 @@ use objects\logs_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class departmentDailyReportsRoute { use route_t; @@ -28,6 +31,7 @@ class departmentDailyReportsRoute public function run(): void { $this->get('/departments/daily-reports', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/departments/daily-reports'); // Require the user to be logged in global $response; $this->requirePermission('list_department_daily_reports'); @@ -84,6 +88,7 @@ class departmentDailyReportsRoute ); $this->get('/departments/daily-reports/get', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/departments/daily-reports/get'); // Require the user to be logged in global $response; $this->requirePermission('list_department_daily_reports'); @@ -157,6 +162,7 @@ class departmentDailyReportsRoute ); $this->post('/departments/daily-reports', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/departments/daily-reports'); // Require the user to be logged in global $response; $this->requirePermission('create_department_daily_reports'); @@ -261,6 +267,7 @@ class departmentDailyReportsRoute ); $this->post('/departments/daily-reports/complaints', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/departments/daily-reports/complaints'); global $response; $this->requirePermission('create_department_daily_report_complaints'); @@ -360,6 +367,7 @@ class departmentDailyReportsRoute ); $this->get('/departments/daily-reports/complaints/customers', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/departments/daily-reports/complaints/customers'); global $response; $user = (new authentication())->get_user(); @@ -470,6 +478,7 @@ class departmentDailyReportsRoute ); $this->get('/departments/daily-reports/complaints', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/departments/daily-reports/complaints'); global $response; $this->requirePermission('list_department_daily_report_complaints'); @@ -531,6 +540,7 @@ class departmentDailyReportsRoute ); $this->put('/departments/daily-reports/complaints', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/departments/daily-reports/complaints'); global $response; $this->requirePermission('edit_department_daily_report_complaints'); @@ -664,6 +674,7 @@ class departmentDailyReportsRoute ); $this->delete('/departments/daily-reports/complaints', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/departments/daily-reports/complaints'); global $response; $this->requirePermission('delete_department_daily_report_complaints'); @@ -704,6 +715,7 @@ class departmentDailyReportsRoute ); $this->put('/departments/daily-reports', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/departments/daily-reports'); // Require the user to be logged in global $response; $this->requirePermission('update_department_daily_reports'); @@ -795,6 +807,7 @@ class departmentDailyReportsRoute ); $this->get('/superuser/departments/{id}/overview', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/superuser/departments/{id}/overview'); global $response; $this->requirePermission('superuser_fetch_department'); @@ -840,6 +853,7 @@ class departmentDailyReportsRoute ); $this->get('/departments/daily-reports/overview', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/departments/daily-reports/overview'); global $response; $this->requirePermission('list_department_daily_reports'); $this->requirePermission('list_bookings'); @@ -886,6 +900,7 @@ class departmentDailyReportsRoute ); $this->put('/departments/daily-reports/product-targets', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/departments/daily-reports/product-targets'); global $response; $this->requirePermission(self::SET_PRODUCT_TARGET_PERMISSION); @@ -948,6 +963,7 @@ class departmentDailyReportsRoute ); $this->get('/departments/daily-reports/product-count', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/departments/daily-reports/product-count'); // Require the user to be logged in global $response; $this->requirePermission('list_department_daily_reports'); @@ -1057,6 +1073,7 @@ class departmentDailyReportsRoute ); $this->get('/departments/daily-reports/transaction-count', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/departments/daily-reports/transaction-count'); // Require the user to be logged in global $response; $this->requirePermission('list_department_daily_reports'); @@ -1153,6 +1170,7 @@ class departmentDailyReportsRoute ); $this->get('/departments/daily-reports/outside-hours-trend', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/departments/daily-reports/outside-hours-trend'); global $response; $this->requirePermission('list_department_daily_reports'); @@ -1200,6 +1218,7 @@ class departmentDailyReportsRoute ); $this->get('/departments/daily-reports/bookings-count', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/departments/daily-reports/bookings-count'); // Require the user to be logged in global $response; $this->requirePermission('list_bookings'); diff --git a/services/nginx/app/routes/departmentGatesRelaysRoute.php b/services/nginx/app/routes/departmentGatesRelaysRoute.php index bc45d53e..20038c8a 100644 --- a/services/nginx/app/routes/departmentGatesRelaysRoute.php +++ b/services/nginx/app/routes/departmentGatesRelaysRoute.php @@ -12,6 +12,9 @@ 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; @@ -19,6 +22,7 @@ class departmentGatesRelaysRoute 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(); @@ -56,6 +60,7 @@ class departmentGatesRelaysRoute ]); $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(); @@ -99,6 +104,7 @@ class departmentGatesRelaysRoute ]); $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(); @@ -157,6 +163,7 @@ class departmentGatesRelaysRoute ]); $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(); @@ -183,6 +190,7 @@ class departmentGatesRelaysRoute ]); $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(); @@ -220,6 +228,7 @@ class departmentGatesRelaysRoute ]); $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(); @@ -262,6 +271,7 @@ class departmentGatesRelaysRoute ]); $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(); @@ -314,6 +324,7 @@ class departmentGatesRelaysRoute ]); $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(); diff --git a/services/nginx/app/routes/departmentGoalsRoute.php b/services/nginx/app/routes/departmentGoalsRoute.php index 76507892..d70fac24 100644 --- a/services/nginx/app/routes/departmentGoalsRoute.php +++ b/services/nginx/app/routes/departmentGoalsRoute.php @@ -14,6 +14,9 @@ use objects\departments_o; use objects\department_goals_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class departmentGoalsRoute { use route_t; @@ -22,6 +25,7 @@ class departmentGoalsRoute { // List or get single department goal(s) $this->get('/goals/department', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/goals/department'); global /** @var response $response */ $response; $this->requirePermission('goals_department_list'); @@ -83,6 +87,7 @@ class departmentGoalsRoute // Create a new department goal $this->post('/goals/department', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/goals/department'); global /** @var response $response */ $response; $this->requirePermission('goals_department_create'); @@ -137,6 +142,7 @@ class departmentGoalsRoute // Update an existing department goal $this->put('/goals/department', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/goals/department'); global /** @var response $response */ $response; $this->requirePermission('goals_department_update'); @@ -236,6 +242,7 @@ class departmentGoalsRoute // Delete a department goal (soft delete if supported) $this->delete('/goals/department', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/goals/department'); global /** @var response $response */ $response; $this->requirePermission('goals_department_delete'); @@ -275,6 +282,7 @@ class departmentGoalsRoute // Send a test progress alert for a department goal $this->post('/goals/department/progress-alert/test', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/goals/department/progress-alert/test'); global /** @var response $response */ $response; $this->requirePermission('goals_department_progress_alert_test'); diff --git a/services/nginx/app/routes/departmentLanesRoute.php b/services/nginx/app/routes/departmentLanesRoute.php index eb49ef67..63ac7e5e 100644 --- a/services/nginx/app/routes/departmentLanesRoute.php +++ b/services/nginx/app/routes/departmentLanesRoute.php @@ -14,6 +14,9 @@ use objects\department_selfserve_tasks_o; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class departmentLanesRoute { use route_t; @@ -23,6 +26,7 @@ class departmentLanesRoute public function run(): void { $this->get('/department/lanes/status-toggles', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/lanes/status-toggles'); global $response; $this->requirePermission('list_department_lanes'); @@ -52,6 +56,7 @@ class departmentLanesRoute ); $this->get('/department/lanes', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/lanes'); // Require the user to be logged in global $response; @@ -124,6 +129,7 @@ class departmentLanesRoute ); $this->get('/department/lanes/relay-options', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/lanes/relay-options'); global $response; $this->requirePermission('list_department_lanes'); @@ -183,6 +189,7 @@ class departmentLanesRoute * Response: image/png */ $this->get('/department/lanes/dynamic-image', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/lanes/dynamic-image'); global $response; // Reuse listing permission; viewing image is tied to lane visibility // Authenticated user and department access validation @@ -335,6 +342,7 @@ class departmentLanesRoute ]); $this->post('/department/lanes', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/lanes'); // Require the user to be logged in global $response; @@ -400,6 +408,7 @@ class departmentLanesRoute ); $this->put('/department/lanes', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/lanes'); // Require the user to be logged in global $response; diff --git a/services/nginx/app/routes/departmentNotificationSmsRoute.php b/services/nginx/app/routes/departmentNotificationSmsRoute.php index 61b7b46f..25d7a1a0 100644 --- a/services/nginx/app/routes/departmentNotificationSmsRoute.php +++ b/services/nginx/app/routes/departmentNotificationSmsRoute.php @@ -9,6 +9,9 @@ use objects\department_notification_sms_o; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class departmentNotificationSmsRoute { use route_t; @@ -22,6 +25,7 @@ class departmentNotificationSmsRoute /** Department Notification SMS -> Get */ $this->get('/department/notification/sms', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/notification/sms'); global $response; $this->requirePermission('department_notification_sms_get'); $user = (new authentication())->get_user(); @@ -60,6 +64,7 @@ class departmentNotificationSmsRoute /** Department Notification SMS -> Add */ $this->post('/department/notification/sms', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/notification/sms'); global $response; $this->requirePermission('department_notification_sms_add'); $user = (new authentication())->get_user(); @@ -103,6 +108,7 @@ class departmentNotificationSmsRoute /** Department Notification SMS -> Update */ $this->put('/department/notification/sms', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/notification/sms'); global $response; $this->requirePermission('department_notification_sms_update'); $user = (new authentication())->get_user(); @@ -157,6 +163,7 @@ class departmentNotificationSmsRoute /** Department Notification SMS -> Delete */ $this->delete('/department/notification/sms', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/notification/sms'); global $response; $this->requirePermission('department_notification_sms_delete'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/departmentSelfserveConditionRulesRoute.php b/services/nginx/app/routes/departmentSelfserveConditionRulesRoute.php index a068024e..e4df9ca4 100644 --- a/services/nginx/app/routes/departmentSelfserveConditionRulesRoute.php +++ b/services/nginx/app/routes/departmentSelfserveConditionRulesRoute.php @@ -13,6 +13,9 @@ use objects\department_selfserve_condition_rules_o; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class departmentSelfserveConditionRulesRoute { use route_t; @@ -23,6 +26,7 @@ class departmentSelfserveConditionRulesRoute * List department self-serve condition rules */ $this->get('/department/selfserve/condition/rules', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/selfserve/condition/rules'); global $response; $this->requirePermission('list_department_selfserve_condition_rules'); $user = (new authentication())->get_user(); @@ -97,6 +101,7 @@ class departmentSelfserveConditionRulesRoute * Add a department self-serve condition rule */ $this->post('/department/selfserve/condition/rules', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/condition/rules'); global $response; $this->requirePermission('add_department_selfserve_condition_rules'); $user = (new authentication())->get_user(); @@ -149,6 +154,7 @@ class departmentSelfserveConditionRulesRoute * Update a department self-serve condition rule */ $this->put('/department/selfserve/condition/rules', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/condition/rules'); global $response; $this->requirePermission('update_department_selfserve_condition_rules'); $user = (new authentication())->get_user(); @@ -221,6 +227,7 @@ class departmentSelfserveConditionRulesRoute * Delete a department self-serve condition rule */ $this->delete('/department/selfserve/condition/rules', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/condition/rules'); global $response; $this->requirePermission('delete_department_selfserve_condition_rules'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/departmentSelfserveConditionsRoute.php b/services/nginx/app/routes/departmentSelfserveConditionsRoute.php index 9f00bb3b..bdfa38b2 100644 --- a/services/nginx/app/routes/departmentSelfserveConditionsRoute.php +++ b/services/nginx/app/routes/departmentSelfserveConditionsRoute.php @@ -12,6 +12,9 @@ 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; @@ -22,6 +25,7 @@ class departmentSelfserveConditionsRoute * 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(); @@ -97,6 +101,7 @@ class departmentSelfserveConditionsRoute * 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(); @@ -156,6 +161,7 @@ class departmentSelfserveConditionsRoute * 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(); @@ -220,6 +226,7 @@ class departmentSelfserveConditionsRoute * 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(); diff --git a/services/nginx/app/routes/departmentSelfserveConfigVersionsRoute.php b/services/nginx/app/routes/departmentSelfserveConfigVersionsRoute.php index df579ff9..bf3990ce 100644 --- a/services/nginx/app/routes/departmentSelfserveConfigVersionsRoute.php +++ b/services/nginx/app/routes/departmentSelfserveConfigVersionsRoute.php @@ -10,6 +10,9 @@ use modules\selfserve\classes\selfserve_config_versioning; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class departmentSelfserveConfigVersionsRoute { use route_t; @@ -17,6 +20,7 @@ class departmentSelfserveConfigVersionsRoute public function run(): void { $this->get('/department/selfserve/config/versions', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/selfserve/config/versions'); global $response; $this->requirePermission('list_department_selfserve_config_versions'); $user = (new authentication())->get_user(); @@ -41,6 +45,7 @@ class departmentSelfserveConfigVersionsRoute ]); $this->get('/department/selfserve/config/history', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/selfserve/config/history'); global $response; $this->requirePermission('list_department_selfserve_config_versions'); $user = (new authentication())->get_user(); @@ -63,6 +68,7 @@ class departmentSelfserveConfigVersionsRoute ]); $this->get('/department/selfserve/config/active', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/selfserve/config/active'); global $response; $this->requirePermission('list_department_selfserve_config_versions'); $user = (new authentication())->get_user(); @@ -98,6 +104,7 @@ class departmentSelfserveConfigVersionsRoute ]); $this->post('/department/selfserve/config/draft', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/config/draft'); global $response; $this->requirePermission('edit_department_selfserve_config_versions'); $user = (new authentication())->get_user(); @@ -122,6 +129,7 @@ class departmentSelfserveConfigVersionsRoute ]); $this->post('/department/selfserve/config/validate', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/config/validate'); global $response; $this->requirePermission('edit_department_selfserve_config_versions'); $user = (new authentication())->get_user(); @@ -142,6 +150,7 @@ class departmentSelfserveConfigVersionsRoute ]); $this->post('/department/selfserve/config/publish', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/config/publish'); global $response; $this->requirePermission('publish_department_selfserve_config_versions'); $user = (new authentication())->get_user(); @@ -166,6 +175,7 @@ class departmentSelfserveConfigVersionsRoute ]); $this->post('/department/selfserve/config/rollback', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/config/rollback'); global $response; $this->requirePermission('rollback_department_selfserve_config_versions'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/departmentSelfserveMachineTypesRoute.php b/services/nginx/app/routes/departmentSelfserveMachineTypesRoute.php index 557530bf..ed9b6092 100644 --- a/services/nginx/app/routes/departmentSelfserveMachineTypesRoute.php +++ b/services/nginx/app/routes/departmentSelfserveMachineTypesRoute.php @@ -10,6 +10,9 @@ use objects\logs_o; use objects\selfserve_machine_types_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class departmentSelfserveMachineTypesRoute { use route_t; @@ -17,6 +20,7 @@ class departmentSelfserveMachineTypesRoute public function run(): void { $this->get('/department/selfserve/machine-types', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/selfserve/machine-types'); global $response; $this->requirePermission('list_department_selfserve_machine_types'); $user = (new authentication())->get_user(); @@ -45,6 +49,7 @@ class departmentSelfserveMachineTypesRoute ]); $this->post('/department/selfserve/machine-types', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/machine-types'); global $response; $this->requirePermission('add_department_selfserve_machine_types'); $user = (new authentication())->get_user(); @@ -70,6 +75,7 @@ class departmentSelfserveMachineTypesRoute ]); $this->put('/department/selfserve/machine-types', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/machine-types'); global $response; $this->requirePermission('update_department_selfserve_machine_types'); $user = (new authentication())->get_user(); @@ -102,6 +108,7 @@ class departmentSelfserveMachineTypesRoute ]); $this->delete('/department/selfserve/machine-types', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/machine-types'); global $response; $this->requirePermission('delete_department_selfserve_machine_types'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/departmentSelfserveQuestionsRoute.php b/services/nginx/app/routes/departmentSelfserveQuestionsRoute.php index a4003212..9478752f 100644 --- a/services/nginx/app/routes/departmentSelfserveQuestionsRoute.php +++ b/services/nginx/app/routes/departmentSelfserveQuestionsRoute.php @@ -12,6 +12,9 @@ use objects\department_selfserve_questions_o; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class departmentSelfserveQuestionsRoute { use route_t; @@ -22,6 +25,7 @@ class departmentSelfserveQuestionsRoute * List department self-serve questions */ $this->get('/department/selfserve/questions', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/selfserve/questions'); global $response; $this->requirePermission('list_department_selfserve_questions'); $user = (new authentication())->get_user(); @@ -93,6 +97,7 @@ class departmentSelfserveQuestionsRoute * Add a department self-serve question */ $this->post('/department/selfserve/questions', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/questions'); global $response; $this->requirePermission('add_department_selfserve_questions'); $user = (new authentication())->get_user(); @@ -147,6 +152,7 @@ class departmentSelfserveQuestionsRoute * Update a department self-serve question */ $this->put('/department/selfserve/questions', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/questions'); global $response; $this->requirePermission('edit_department_selfserve_questions'); $user = (new authentication())->get_user(); @@ -208,6 +214,7 @@ class departmentSelfserveQuestionsRoute * Delete a department self-serve question */ $this->delete('/department/selfserve/questions', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/questions'); global $response; $this->requirePermission('delete_department_selfserve_questions'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/departmentSelfserveStudioRoute.php b/services/nginx/app/routes/departmentSelfserveStudioRoute.php index 129fd496..f5532de8 100644 --- a/services/nginx/app/routes/departmentSelfserveStudioRoute.php +++ b/services/nginx/app/routes/departmentSelfserveStudioRoute.php @@ -9,6 +9,9 @@ use modules\selfserve\classes\selfserve_wash_flow; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class departmentSelfserveStudioRoute { use route_t; @@ -16,6 +19,7 @@ class departmentSelfserveStudioRoute public function run(): void { $this->get('/department/selfserve/studio/graph', function (): void { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/selfserve/studio/graph'); global $response; $user = $this->requireStudioUser('list_department_selfserve_config_versions'); self::requireParameters(['department']); @@ -31,6 +35,7 @@ class departmentSelfserveStudioRoute ]); $this->put('/department/selfserve/studio/graph', function (): void { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/studio/graph'); global $response; $user = $this->requireStudioUser('edit_department_selfserve_config_versions'); self::requireParameters(['department']); @@ -51,6 +56,7 @@ class departmentSelfserveStudioRoute ]); $this->put('/department/selfserve/studio/layout', function (): void { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/studio/layout'); global $response; $user = $this->requireStudioUser('edit_department_selfserve_config_versions'); self::requireParameters(['department', 'layout']); @@ -70,6 +76,7 @@ class departmentSelfserveStudioRoute ]); $this->put('/department/selfserve/studio/virtual-hardware', function (): void { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/studio/virtual-hardware'); global $response; $user = $this->requireStudioUser('edit_department_selfserve_config_versions'); self::requireParameters(['department', 'operation']); @@ -94,6 +101,7 @@ class departmentSelfserveStudioRoute ]); $this->post('/department/selfserve/studio/validate', function (): void { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/studio/validate'); global $response; $user = $this->requireStudioUser('edit_department_selfserve_config_versions'); self::requireParameters(['department']); @@ -108,6 +116,7 @@ class departmentSelfserveStudioRoute ]); $this->post('/department/selfserve/studio/simulate', function (): void { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/studio/simulate'); global $response; $user = $this->requireStudioUser('list_department_selfserve_config_versions'); self::requireParameters(['department', 'lane_id', 'reg']); @@ -133,6 +142,7 @@ class departmentSelfserveStudioRoute ]); $this->post('/department/selfserve/studio/path-outcomes/stream', function (): void { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/studio/path-outcomes/stream'); $user = $this->requireStudioUser('list_department_selfserve_config_versions'); self::requireParameters(['department']); $departmentId = (int)self::getParameter('department'); @@ -173,6 +183,7 @@ class departmentSelfserveStudioRoute ]); $this->post('/department/selfserve/studio/path-outcomes', function (): void { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/studio/path-outcomes'); global $response; $user = $this->requireStudioUser('list_department_selfserve_config_versions'); self::requireParameters(['department']); @@ -196,6 +207,7 @@ class departmentSelfserveStudioRoute ]); $this->post('/department/selfserve/studio/path-confirmations', function (): void { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/studio/path-confirmations'); global $response; $user = $this->requireStudioUser('edit_department_selfserve_config_versions'); self::requireParameters(['department', 'path_signature']); @@ -219,6 +231,7 @@ class departmentSelfserveStudioRoute ]); $this->post('/department/selfserve/studio/publish', function (): void { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/studio/publish'); global $response; $user = $this->requireStudioUser('publish_department_selfserve_config_versions'); self::requireParameters(['department']); @@ -240,6 +253,7 @@ class departmentSelfserveStudioRoute ]); $this->post('/department/selfserve/studio/rollback', function (): void { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/studio/rollback'); global $response; $user = $this->requireStudioUser('rollback_department_selfserve_config_versions'); self::requireParameters(['department', 'target_version_id']); @@ -260,6 +274,7 @@ class departmentSelfserveStudioRoute ]); $this->post('/department/selfserve/studio/gateway-action', function (): void { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/studio/gateway-action'); global $response; $user = $this->requireStudioUser('modules_shelly_config'); self::requireParameters(['department', 'gateway_id', 'action']); diff --git a/services/nginx/app/routes/departmentSelfserveTasksRoute.php b/services/nginx/app/routes/departmentSelfserveTasksRoute.php index 42e2f88b..67ebb425 100644 --- a/services/nginx/app/routes/departmentSelfserveTasksRoute.php +++ b/services/nginx/app/routes/departmentSelfserveTasksRoute.php @@ -17,6 +17,9 @@ use modules\selfserve\helpers\selfserve_lane_services; use modules\selfserve\helpers\selfserve_task_gate_type; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class departmentSelfserveTasksRoute { use route_t; @@ -27,6 +30,7 @@ class departmentSelfserveTasksRoute * List department self-serve tasks */ $this->get('/department/selfserve/tasks', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/selfserve/tasks'); global $response; $this->requirePermission('list_department_selfserve_tasks'); $user = (new authentication())->get_user(); @@ -112,6 +116,7 @@ class departmentSelfserveTasksRoute * Add a department self-serve task */ $this->post('/department/selfserve/tasks', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/tasks'); global $response; $this->requirePermission('add_department_selfserve_tasks'); $user = (new authentication())->get_user(); @@ -271,6 +276,7 @@ class departmentSelfserveTasksRoute * Update a department self-serve task */ $this->put('/department/selfserve/tasks', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/tasks'); global $response; $this->requirePermission('edit_department_selfserve_tasks'); $user = (new authentication())->get_user(); @@ -437,6 +443,7 @@ class departmentSelfserveTasksRoute * Delete a department self-serve task */ $this->delete('/department/selfserve/tasks', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/tasks'); global $response; $this->requirePermission('delete_department_selfserve_tasks'); $user = (new authentication())->get_user(); @@ -470,6 +477,7 @@ class departmentSelfserveTasksRoute * Download an attachment for a department self-serve task */ $this->get('/department/selfserve/tasks/attachments/download', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/selfserve/tasks/attachments/download'); global $response; $this->requirePermission('download_department_selfserve_task_attachments'); $user = (new authentication())->get_user(); @@ -520,6 +528,7 @@ class departmentSelfserveTasksRoute * List attachments for a department self-serve task */ $this->get('/department/selfserve/tasks/attachments', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/selfserve/tasks/attachments'); global $response; $this->requirePermission('list_department_selfserve_task_attachments'); $user = (new authentication())->get_user(); @@ -557,6 +566,7 @@ class departmentSelfserveTasksRoute * Upload an attachment for a department self-serve task */ $this->post('/department/selfserve/tasks/attachments/upload', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/tasks/attachments/upload'); global $response; $this->requirePermission('add_department_selfserve_task_attachments'); $user = (new authentication())->get_user(); @@ -603,6 +613,7 @@ class departmentSelfserveTasksRoute * Delete an attachment for a department self-serve task */ $this->delete('/department/selfserve/tasks/attachments', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/tasks/attachments'); global $response; $this->requirePermission('delete_department_selfserve_task_attachments'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/departmentSelfserveVehicleConditionsRoute.php b/services/nginx/app/routes/departmentSelfserveVehicleConditionsRoute.php index 869092aa..c7460d2c 100644 --- a/services/nginx/app/routes/departmentSelfserveVehicleConditionsRoute.php +++ b/services/nginx/app/routes/departmentSelfserveVehicleConditionsRoute.php @@ -19,6 +19,9 @@ use objects\logs_o; use objects\selfserve_wash_sessions_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class departmentSelfserveVehicleConditionsRoute { use route_t; @@ -29,6 +32,7 @@ class departmentSelfserveVehicleConditionsRoute * List department self-serve vehicle conditions */ $this->get('/department/selfserve/vehicle/conditions', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/selfserve/vehicle/conditions'); global $response; $user = (new authentication())->get_user(); if (!$user) { @@ -118,6 +122,7 @@ class departmentSelfserveVehicleConditionsRoute * Check whether self-serve is allowed for a specific vehicle and lane */ $this->get('/department/selfserve/vehicle/allowed', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/selfserve/vehicle/allowed'); global $response; [$user, $actor_id, $subuser_id] = $this->getAuthenticatedSelfServePrincipal(); $own_permission = self::definePermission('list_own_department_selfserve_vehicle_conditions', subusers_permission_node_key::SELFSERVE_LIST); @@ -165,6 +170,7 @@ class departmentSelfserveVehicleConditionsRoute * Get self-serve wash summary */ $this->get('/department/selfserve/washes/summary', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/selfserve/washes/summary'); global $response; [$user, $_actor_id, $subuser_id] = $this->getAuthenticatedSelfServePrincipal(); $own_permission = self::definePermission('list_own_department_selfserve_vehicle_conditions', subusers_permission_node_key::SELFSERVE_LIST); @@ -249,6 +255,7 @@ class departmentSelfserveVehicleConditionsRoute * Add a department self-serve vehicle condition */ $this->post('/department/selfserve/vehicle/conditions', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/vehicle/conditions'); global $response; [$user, $actor_id, $subuser_id] = $this->getAuthenticatedSelfServePrincipal(); $own_permission = self::definePermission('add_own_department_selfserve_vehicle_conditions', subusers_permission_node_key::SELFSERVE_ADD); @@ -311,6 +318,7 @@ class departmentSelfserveVehicleConditionsRoute * Update a department self-serve vehicle condition */ $this->put('/department/selfserve/vehicle/conditions', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/vehicle/conditions'); global $response; $user = (new authentication())->get_user(); if (!$user) { @@ -408,6 +416,7 @@ class departmentSelfserveVehicleConditionsRoute * Delete a department self-serve vehicle condition */ $this->delete('/department/selfserve/vehicle/conditions', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/department/selfserve/vehicle/conditions'); global $response; $user = (new authentication())->get_user(); if (!$user) { diff --git a/services/nginx/app/routes/departmentTimeBookingsRoute.php b/services/nginx/app/routes/departmentTimeBookingsRoute.php index de2c85ae..fac5577f 100644 --- a/services/nginx/app/routes/departmentTimeBookingsRoute.php +++ b/services/nginx/app/routes/departmentTimeBookingsRoute.php @@ -12,6 +12,9 @@ use objects\logs_o; use objects\product_options_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class departmentTimeBookingsRoute { use route_t; @@ -25,6 +28,7 @@ class departmentTimeBookingsRoute /** Department Time Bookings -> List */ $this->get('/department/timebookings/opening-hours', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/department/timebookings/opening-hours'); global $response; $this->requirePermission('department_timebookings_opening_hours_get'); $user = (new authentication())->get_user(); @@ -74,6 +78,7 @@ class departmentTimeBookingsRoute /** Department Time Bookings -> Update */ $this->put('/department/timebookings/opening-hours', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/department/timebookings/opening-hours'); global $response; $this->requirePermission('department_timebookings_opening_hours_put'); $user = (new authentication())->get_user(); @@ -124,6 +129,7 @@ class departmentTimeBookingsRoute /** Department Time Bookings -> Types */ $this->get('/department/timebookings/types', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/department/timebookings/types'); global $response; $this->requirePermission('department_timebookings_types_get'); $user = (new authentication())->get_user(); @@ -169,6 +175,7 @@ class departmentTimeBookingsRoute /** Department Time Bookings -> Types -> Add */ $this->post('/department/timebookings/types', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/department/timebookings/types'); global $response; $this->requirePermission('department_timebookings_types_post'); $user = (new authentication())->get_user(); @@ -204,6 +211,7 @@ class departmentTimeBookingsRoute /** Department Time Bookings -> Types -> Update */ $this->put('/department/timebookings/types', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/department/timebookings/types'); global $response; $this->requirePermission('department_timebookings_types_put'); $user = (new authentication())->get_user(); @@ -239,6 +247,7 @@ class departmentTimeBookingsRoute /** Department Time Bookings -> Types -> Delete */ $this->delete('/department/timebookings/types', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/department/timebookings/types'); global $response; $this->requirePermission('department_timebookings_types_delete'); $user = (new authentication())->get_user(); @@ -269,6 +278,7 @@ class departmentTimeBookingsRoute /** Department Time Bookings -> Entries */ $this->get('/department/timebookings/entries', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/department/timebookings/entries'); global $response; $this->requirePermission('department_timebookings_entries_get'); $user = (new authentication())->get_user(); @@ -324,6 +334,7 @@ class departmentTimeBookingsRoute /** Department Time Bookings -> Entries -> Add */ $this->post('/department/timebookings/entries', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/department/timebookings/entries'); global $response; $this->requirePermission('department_timebookings_entries_post'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/departmentsRoute.php b/services/nginx/app/routes/departmentsRoute.php index 9d596b7f..6a302d67 100644 --- a/services/nginx/app/routes/departmentsRoute.php +++ b/services/nginx/app/routes/departmentsRoute.php @@ -16,6 +16,9 @@ use objects\logs_o; use objects\orders_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class departmentsRoute { use route_t; @@ -83,6 +86,7 @@ class departmentsRoute public function run(): void { $this->get('/departments', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/departments'); // Require the user to be logged in global $response; $this->requirePermission('list_departments'); @@ -173,6 +177,7 @@ class departmentsRoute ); $this->post('/departments', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/departments'); // Require the user to be logged in global $response; $this->requirePermission('add_department'); @@ -209,6 +214,7 @@ class departmentsRoute ); $this->put('/departments', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/departments'); // Require the user to be logged in global $response; $this->requirePermission('edit_department'); @@ -265,6 +271,7 @@ class departmentsRoute ); $this->get('/departments/categories', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/departments/categories'); // Require the user to be logged in global $response; $auth = new authentication(); @@ -323,6 +330,7 @@ class departmentsRoute ); $this->get('/departments/self-serve/enabled', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/departments/self-serve/enabled'); // Require the user to be logged in global $response; $this->requirePermission('view_department_selfserve_enabled'); @@ -362,6 +370,7 @@ class departmentsRoute ); $this->put('/departments/self-serve/enabled', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/departments/self-serve/enabled'); // Require the user to be logged in global $response; $this->requirePermission('edit_department_selfserve_enabled'); @@ -413,6 +422,7 @@ class departmentsRoute ); $this->post('/departments/categories', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/departments/categories'); // Require the user to be logged in global $response; $this->requirePermission('add_department_category'); @@ -465,6 +475,7 @@ class departmentsRoute ); $this->delete('/departments/categories', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/departments/categories'); // Require the user to be logged in global $response; $this->requirePermission('delete_department_category'); diff --git a/services/nginx/app/routes/economicInvoiceRoute.php b/services/nginx/app/routes/economicInvoiceRoute.php index 2a97e40a..07a7c3d2 100644 --- a/services/nginx/app/routes/economicInvoiceRoute.php +++ b/services/nginx/app/routes/economicInvoiceRoute.php @@ -15,6 +15,9 @@ use objects\orders_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class economicInvoiceRoute { use route_t; @@ -26,6 +29,7 @@ class economicInvoiceRoute $router, $response; $this->post('/economic/invoice/draft/export', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/economic/invoice/draft/export'); global $response; $this->requirePermission('economic_invoice_draft_export'); $user = (new authentication())->get_user(); @@ -99,6 +103,7 @@ class economicInvoiceRoute ]); $this->delete('/economic/invoice/draft/delete', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/economic/invoice/draft/delete'); global /** @var response $response */ $response; $this->requirePermission('economic_invoice_draft_delete'); @@ -139,6 +144,7 @@ class economicInvoiceRoute ]); $this->post('/economic/invoice/export', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/economic/invoice/export'); global $response; $this->requirePermission('economic_invoice_export'); $user = (new authentication())->get_user(); @@ -223,6 +229,7 @@ class economicInvoiceRoute ]); $this->get('/economic/invoice/draft/export/status', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/economic/invoice/draft/export/status'); global $response; $this->requirePermission('economic_invoice_draft_export'); $user = (new authentication())->get_user(); @@ -248,6 +255,7 @@ class economicInvoiceRoute ]); $this->post('/economic/invoice/draft/export/retry', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/economic/invoice/draft/export/retry'); global $response; $this->requirePermission('economic_invoice_draft_export'); $user = (new authentication())->get_user(); @@ -282,6 +290,7 @@ class economicInvoiceRoute ]); $this->get('/economic/invoice/export/status', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/economic/invoice/export/status'); global $response; $this->requirePermission('economic_invoice_export'); $user = (new authentication())->get_user(); @@ -307,6 +316,7 @@ class economicInvoiceRoute ]); $this->post('/economic/invoice/export/retry', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/economic/invoice/export/retry'); global $response; $this->requirePermission('economic_invoice_export'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/economicLayoutsRoute.php b/services/nginx/app/routes/economicLayoutsRoute.php index 2f59399d..463df885 100644 --- a/services/nginx/app/routes/economicLayoutsRoute.php +++ b/services/nginx/app/routes/economicLayoutsRoute.php @@ -9,6 +9,9 @@ use classes\router; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class economicLayoutsRoute { use route_t; @@ -21,6 +24,7 @@ class economicLayoutsRoute $this->get('/economic/layouts', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/economic/layouts'); global $response; $this->requirePermission('economic_layouts'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/economicPaymentTermsRoute.php b/services/nginx/app/routes/economicPaymentTermsRoute.php index 80da0a5f..e9864700 100644 --- a/services/nginx/app/routes/economicPaymentTermsRoute.php +++ b/services/nginx/app/routes/economicPaymentTermsRoute.php @@ -9,6 +9,9 @@ use classes\router; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class economicPaymentTermsRoute { use route_t; @@ -21,6 +24,7 @@ class economicPaymentTermsRoute $this->get('/economic/payment-terms', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/economic/payment-terms'); global $response; $this->requirePermission('economic_payment_terms'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/exampleRoute.php b/services/nginx/app/routes/exampleRoute.php index 95cb5dd1..012d2e1f 100644 --- a/services/nginx/app/routes/exampleRoute.php +++ b/services/nginx/app/routes/exampleRoute.php @@ -13,6 +13,8 @@ use objects\departments_o; use objects\order_items_o; use objects\orders_o; use objects\products_o; +use app\auth\Scope; +use app\auth\ScopeMiddleware; use objects\users_o; use traits\route_t; @@ -23,6 +25,7 @@ class exampleRoute public function run(): void { $this->get('/example', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/example'); global $response; $response->success(['message' => 'Hello World!']); }); diff --git a/services/nginx/app/routes/intimidateRoute.php b/services/nginx/app/routes/intimidateRoute.php index e858952e..31ae4057 100644 --- a/services/nginx/app/routes/intimidateRoute.php +++ b/services/nginx/app/routes/intimidateRoute.php @@ -7,6 +7,9 @@ use objects\logs_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class intimidateRoute { use route_t; @@ -14,6 +17,7 @@ class intimidateRoute public function run(): void { $this->post('/su/intimidate', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/su/intimidate'); // Get the post data global $response; // Make sure the user has the SUPERUSER_INTIMIDATE permission diff --git a/services/nginx/app/routes/invoicesRoute.php b/services/nginx/app/routes/invoicesRoute.php index 6bdf022f..26350fa3 100644 --- a/services/nginx/app/routes/invoicesRoute.php +++ b/services/nginx/app/routes/invoicesRoute.php @@ -12,6 +12,9 @@ use objects\orders_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class invoicesRoute { use route_t; @@ -19,6 +22,7 @@ class invoicesRoute public function run(): void { $this->get('/invoices/draft', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/invoices/draft'); // Require the user to be logged in global $response; $this->requirePermission('get_invoice_draft'); @@ -57,6 +61,7 @@ class invoicesRoute ); $this->post('/invoices/draft/close', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/invoices/draft/close'); // Require the user to be logged in global $response; $this->requirePermission('close_invoice_draft'); @@ -94,6 +99,7 @@ class invoicesRoute ); $this->get('/invoices/pdf', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/invoices/pdf'); // Require the user to be logged in global $response; $this->requirePermission('get_invoice_pdf'); diff --git a/services/nginx/app/routes/limitedBackofficeRoute.php b/services/nginx/app/routes/limitedBackofficeRoute.php index 96d501e3..9881e87d 100644 --- a/services/nginx/app/routes/limitedBackofficeRoute.php +++ b/services/nginx/app/routes/limitedBackofficeRoute.php @@ -8,6 +8,9 @@ use classes\limited_backoffice_login_grant_service; use classes\limited_backoffice_service; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + require_once WD . '/classes/limited_backoffice_login_grant_service.php'; class limitedBackofficeRoute @@ -17,6 +20,7 @@ class limitedBackofficeRoute public function run(): void { $this->get('/limited-backoffice/departments', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/limited-backoffice/departments'); $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array { $this->requirePermission(limited_backoffice_service::PERMISSION_ACCESS); return $service->departmentsForUser($user); diff --git a/services/nginx/app/routes/machineButtonPressRoute.php b/services/nginx/app/routes/machineButtonPressRoute.php index b423eba7..2f227892 100644 --- a/services/nginx/app/routes/machineButtonPressRoute.php +++ b/services/nginx/app/routes/machineButtonPressRoute.php @@ -11,6 +11,9 @@ use objects\logs_o; use objects\plate_scanners_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class machineButtonPressRoute { use route_t; @@ -18,6 +21,7 @@ class machineButtonPressRoute public function run(): void { $handler = function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/relay/button/press/post'); global $response; self::requirePlateScannerAuth(); diff --git a/services/nginx/app/routes/moduleActionLogsRoute.php b/services/nginx/app/routes/moduleActionLogsRoute.php index 5c03a00e..4948a198 100644 --- a/services/nginx/app/routes/moduleActionLogsRoute.php +++ b/services/nginx/app/routes/moduleActionLogsRoute.php @@ -7,6 +7,9 @@ use objects\logs_o; use objects\module_action_logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleActionLogsRoute { use route_t; @@ -15,6 +18,7 @@ class moduleActionLogsRoute { /** Modules > Action Logs > List */ $this->get('/modules/action-logs', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/action-logs'); global $response; $this->requirePermission('modules_action_logs_view'); diff --git a/services/nginx/app/routes/moduleBackupsRoute.php b/services/nginx/app/routes/moduleBackupsRoute.php index 6bf9074d..91c09004 100644 --- a/services/nginx/app/routes/moduleBackupsRoute.php +++ b/services/nginx/app/routes/moduleBackupsRoute.php @@ -10,6 +10,9 @@ use objects\logs_o; use Throwable; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleBackupsRoute { use route_t; @@ -21,6 +24,7 @@ class moduleBackupsRoute $router, $response; $this->get('/modules/backup/backups', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/backup/backups'); global $response; $this->requireClassicSuperuserPermission('modules_backup_list'); try { @@ -38,6 +42,7 @@ class moduleBackupsRoute ]); $this->post('/modules/backup/backups', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/backup/backups'); global $response; $this->requireClassicSuperuserPermission('modules_backup_create'); $user_id = $this->actorUserId(); @@ -59,6 +64,7 @@ class moduleBackupsRoute ]); $this->get('/modules/backup/jobs/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/backup/jobs/{id}'); global $response; $this->requireClassicSuperuserPermission('modules_backup_list'); $job_id = (int)$this->fromRoute('id'); @@ -72,6 +78,7 @@ class moduleBackupsRoute ]); $this->post('/modules/backup/backups/{backup_uuid}/verify', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/backup/backups/{backup_uuid}/verify'); global $response; $this->requireClassicSuperuserPermission('modules_backup_verify'); try { @@ -87,6 +94,7 @@ class moduleBackupsRoute ]); $this->post('/modules/backup/backups/{backup_uuid}/restore/preview', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/backup/backups/{backup_uuid}/restore/preview'); global $response; $this->requireClassicSuperuserPermission('modules_backup_restore'); try { @@ -102,6 +110,7 @@ class moduleBackupsRoute ]); $this->post('/modules/backup/backups/{backup_uuid}/restore', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/backup/backups/{backup_uuid}/restore'); global $response; $this->requireClassicSuperuserPermission('modules_backup_restore'); try { @@ -128,6 +137,7 @@ class moduleBackupsRoute ]); $this->get('/modules/backup/restore-audit', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/backup/restore-audit'); global $response; $this->requireClassicSuperuserPermission('modules_backup_restore'); try { diff --git a/services/nginx/app/routes/moduleConfigRoute.php b/services/nginx/app/routes/moduleConfigRoute.php index bb240b0a..7277c313 100644 --- a/services/nginx/app/routes/moduleConfigRoute.php +++ b/services/nginx/app/routes/moduleConfigRoute.php @@ -19,6 +19,9 @@ use classes\workfeed; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleConfigRoute { use route_t; @@ -32,6 +35,7 @@ class moduleConfigRoute /** Economic config > GET */ $this->get('/economic/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/economic/config'); global $response; $this->requirePermission('economic_config'); $user = (new authentication())->get_user(); @@ -52,6 +56,7 @@ class moduleConfigRoute /** Economic config > POST */ $this->post('/economic/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/economic/config'); global $response; $this->requirePermission('economic_config'); $user = (new authentication())->get_user(); @@ -72,6 +77,7 @@ class moduleConfigRoute /** reCAPTCHA config > GET */ $this->get('/reCAPTCHA/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/reCAPTCHA/config'); global $response; $this->requirePermission('recaptcha_config'); $user = (new authentication())->get_user(); @@ -92,6 +98,7 @@ class moduleConfigRoute /** reCAPTCHA config > POST */ $this->post('/reCAPTCHA/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/reCAPTCHA/config'); global $response; $this->requirePermission('recaptcha_config'); $user = (new authentication())->get_user(); @@ -112,6 +119,7 @@ class moduleConfigRoute /** Email config > GET */ $this->get('/email/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/email/config'); global $response; $this->requirePermission('email_config'); $user = (new authentication())->get_user(); @@ -132,6 +140,7 @@ class moduleConfigRoute /** Email config > POST */ $this->post('/email/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/email/config'); global $response; $this->requirePermission('email_config'); $user = (new authentication())->get_user(); @@ -152,6 +161,7 @@ class moduleConfigRoute /** Email config > TEST */ $this->post('/email/config/test', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/email/config/test'); global $response; $this->requirePermission('email_config'); $user = (new authentication())->get_user(); @@ -183,6 +193,7 @@ class moduleConfigRoute /** Slack config > GET */ $this->get('/slack/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/slack/config'); global $response; $this->requirePermission('slack_config'); $user = (new authentication())->get_user(); @@ -203,6 +214,7 @@ class moduleConfigRoute /** Slack config > POST */ $this->post('/slack/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/slack/config'); global $response; $this->requirePermission('slack_config'); $user = (new authentication())->get_user(); @@ -223,6 +235,7 @@ class moduleConfigRoute /** Slack config > TEST */ $this->post('/slack/config/test', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/slack/config/test'); global $response; $this->requirePermission('slack_config'); $user = (new authentication())->get_user(); @@ -252,6 +265,7 @@ class moduleConfigRoute /** Slack internal department goal progress config > TEST */ $this->post('/slack/config/internal-department-goal-progress/test', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/slack/config/internal-department-goal-progress/test'); global $response; $this->requirePermission('slack_config'); $user = (new authentication())->get_user(); @@ -281,6 +295,7 @@ class moduleConfigRoute /** Slack internal department goal progress config > GET */ $this->get('/slack/config/internal-department-goal-progress', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/slack/config/internal-department-goal-progress'); global $response; $this->requirePermission('slack_config'); $user = (new authentication())->get_user(); @@ -301,6 +316,7 @@ class moduleConfigRoute /** Slack internal department goal progress config > POST */ $this->post('/slack/config/internal-department-goal-progress', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/slack/config/internal-department-goal-progress'); global $response; $this->requirePermission('slack_config'); $user = (new authentication())->get_user(); @@ -334,6 +350,7 @@ class moduleConfigRoute ); $this->get('/backups/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/backups/config'); global $response; $this->requirePermission('backups_config'); $user = (new authentication())->get_user(); @@ -353,6 +370,7 @@ class moduleConfigRoute ); $this->post('/backups/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/backups/config'); global $response; $this->requirePermission('backups_config'); $user = (new authentication())->get_user(); @@ -372,6 +390,7 @@ class moduleConfigRoute ); $this->get('/failover/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/failover/config'); global $response; $this->requirePermission('modules_failover_config'); $user = (new authentication())->get_user(); @@ -391,6 +410,7 @@ class moduleConfigRoute ); $this->post('/failover/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/failover/config'); global $response; $this->requirePermission('modules_failover_config'); $user = (new authentication())->get_user(); @@ -410,6 +430,7 @@ class moduleConfigRoute ); $this->get('/coolify/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/coolify/config'); global $response; $this->requirePermission('superuser_coolify_manage'); $user = (new authentication())->get_user(); @@ -429,6 +450,7 @@ class moduleConfigRoute ); $this->post('/coolify/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/coolify/config'); global $response; $this->requirePermission('superuser_coolify_manage'); $user = (new authentication())->get_user(); @@ -449,6 +471,7 @@ class moduleConfigRoute /** Bird config > GET */ $this->get('/bird/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/bird/config'); global $response; $this->requirePermission('modules_bird_config'); $user = (new authentication())->get_user(); @@ -469,6 +492,7 @@ class moduleConfigRoute /** Bird config > POST */ $this->post('/bird/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/bird/config'); global $response; $this->requirePermission('modules_bird_config'); $user = (new authentication())->get_user(); @@ -489,6 +513,7 @@ class moduleConfigRoute /** MotorAPI config > GET */ $this->get('/motorapi/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/motorapi/config'); global $response; $this->requirePermission('motorapi_config'); $user = (new authentication())->get_user(); @@ -509,6 +534,7 @@ class moduleConfigRoute /** MotorAPI config > POST */ $this->post('/motorapi/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/motorapi/config'); global $response; $this->requirePermission('motorapi_config'); $user = (new authentication())->get_user(); @@ -529,6 +555,7 @@ class moduleConfigRoute /** Stripe config > GET */ $this->get('/stripe/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/stripe/config'); global $response; $this->requirePermission('stripe_config'); $user = (new authentication())->get_user(); @@ -549,6 +576,7 @@ class moduleConfigRoute /** Stripe config > POST */ $this->post('/stripe/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/stripe/config'); global $response; $this->requirePermission('stripe_config'); $user = (new authentication())->get_user(); @@ -569,6 +597,7 @@ class moduleConfigRoute /** FXRatesAPI config > GET */ $this->get('/fxratesapi/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/fxratesapi/config'); global $response; $this->requirePermission('fxratesapi_config'); $user = (new authentication())->get_user(); @@ -588,6 +617,7 @@ class moduleConfigRoute ); /** FXRatesAPI config > POST */ $this->post('/fxratesapi/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/fxratesapi/config'); global $response; $this->requirePermission('fxratesapi_config'); $user = (new authentication())->get_user(); @@ -607,6 +637,7 @@ class moduleConfigRoute ); /** GatewayAPI config > GET */ $this->get('/gatewayapi/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/gatewayapi/config'); global $response; $this->requirePermission('gatewayapi_config'); $user = (new authentication())->get_user(); @@ -626,6 +657,7 @@ class moduleConfigRoute ); /** GatewayAPI config > POST */ $this->post('/gatewayapi/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/gatewayapi/config'); global $response; $this->requirePermission('gatewayapi_config'); $user = (new authentication())->get_user(); @@ -646,6 +678,7 @@ class moduleConfigRoute /** WeatherAPI config > GET */ $this->get('/weatherapi/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/weatherapi/config'); global $response; $this->requirePermission('weatherapi_config'); $user = (new authentication())->get_user(); @@ -665,6 +698,7 @@ class moduleConfigRoute ); /** WeatherAPI config > POST */ $this->post('/weatherapi/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/weatherapi/config'); global $response; $this->requirePermission('weatherapi_config'); $user = (new authentication())->get_user(); @@ -685,6 +719,7 @@ class moduleConfigRoute /** n8n config > GET */ $this->get('/n8n/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/n8n/config'); global $response; $this->requirePermission('modules_n8n_config'); $user = (new authentication())->get_user(); @@ -704,6 +739,7 @@ class moduleConfigRoute ); /** n8n config > POST */ $this->post('/n8n/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/n8n/config'); global $response; $this->requirePermission('modules_n8n_config'); $user = (new authentication())->get_user(); @@ -724,6 +760,7 @@ class moduleConfigRoute /** Workfeed config > GET */ $this->get('/workfeed/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/workfeed/config'); global $response; $this->requirePermission('modules_workfeed_config'); $user = (new authentication())->get_user(); @@ -743,6 +780,7 @@ class moduleConfigRoute ); /** Workfeed config > POST */ $this->post('/workfeed/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/workfeed/config'); global $response; $this->requirePermission('modules_workfeed_config'); $user = (new authentication())->get_user(); @@ -763,6 +801,7 @@ class moduleConfigRoute /** XLVask config > GET */ $this->get('/xlvask/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/xlvask/config'); global $response; $this->requirePermission('xlvask_config'); $user = (new authentication())->get_user(); @@ -782,6 +821,7 @@ class moduleConfigRoute ); /** XLVask config > POST */ $this->post('/xlvask/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/xlvask/config'); global $response; $this->requirePermission('xlvask_config'); $user = (new authentication())->get_user(); @@ -802,6 +842,7 @@ class moduleConfigRoute /** Entra config > GET */ $this->get('/entra/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/entra/config'); global $response; $this->requirePermission('entra_config'); $user = (new authentication())->get_user(); @@ -821,6 +862,7 @@ class moduleConfigRoute ); /** Entra config > POST */ $this->post('/entra/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/entra/config'); global $response; $this->requirePermission('entra_config'); $user = (new authentication())->get_user(); @@ -840,6 +882,7 @@ class moduleConfigRoute ); /** Limble config > GET */ $this->get('/limble/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/limble/config'); global $response; $this->requirePermission('modules_limble_config'); $user = (new authentication())->get_user(); @@ -859,6 +902,7 @@ class moduleConfigRoute ); /** Limble config > POST */ $this->post('/limble/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/limble/config'); global $response; $this->requirePermission('modules_limble_config'); $user = (new authentication())->get_user(); @@ -878,6 +922,7 @@ class moduleConfigRoute ); /** OcrSpace config > GET */ $this->get('/ocrspace/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/ocrspace/config'); global $response; $this->requirePermission('modules_ocrspace_config'); $user = (new authentication())->get_user(); @@ -897,6 +942,7 @@ class moduleConfigRoute ); /** OcrSpace config > POST */ $this->post('/ocrspace/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/ocrspace/config'); global $response; $this->requirePermission('modules_ocrspace_config'); $user = (new authentication())->get_user(); @@ -916,6 +962,7 @@ class moduleConfigRoute ); /** OpenAI config > GET */ $this->get('/openai/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/openai/config'); global $response; $this->requirePermission('modules_openai_config'); $user = (new authentication())->get_user(); @@ -935,6 +982,7 @@ class moduleConfigRoute ); /** OpenAI config > POST */ $this->post('/openai/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/openai/config'); global $response; $this->requirePermission('modules_openai_config'); $user = (new authentication())->get_user(); @@ -954,6 +1002,7 @@ class moduleConfigRoute ); /** LicensePlateRecognizer config > GET */ $this->get('/licenseplaterecognizer/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/licenseplaterecognizer/config'); global $response; $this->requirePermission('modules_licenseplaterecognizer_config'); $user = (new authentication())->get_user(); @@ -973,6 +1022,7 @@ class moduleConfigRoute ); /** LicensePlateRecognizer config > POST */ $this->post('/licenseplaterecognizer/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/licenseplaterecognizer/config'); global $response; $this->requirePermission('modules_licenseplaterecognizer_config'); $user = (new authentication())->get_user(); @@ -992,6 +1042,7 @@ class moduleConfigRoute ); /** Virkdata config > GET */ $this->get('/virkdata/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/virkdata/config'); global $response; $this->requirePermission('modules_virkdata_config'); $user = (new authentication())->get_user(); @@ -1010,6 +1061,7 @@ class moduleConfigRoute ); /** Virkdata config > POST */ $this->post('/virkdata/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/virkdata/config'); global $response; $this->requirePermission('modules_virkdata_config'); $user = (new authentication())->get_user(); @@ -1029,6 +1081,7 @@ class moduleConfigRoute ); /** Shelly config -> GET */ $this->get('/shelly/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/shelly/config'); global $response; $this->requirePermission('modules_shelly_config'); $user = (new authentication())->get_user(); @@ -1046,6 +1099,7 @@ class moduleConfigRoute ] ); $this->post('/shelly/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/shelly/config'); global $response; $this->requirePermission('modules_shelly_config'); $user = (new authentication())->get_user(); @@ -1060,6 +1114,7 @@ class moduleConfigRoute }); /** Self-Serve config -> GET */ $this->get('/selfserve/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/selfserve/config'); global $response; $this->requirePermission('modules_selfserve_config'); $user = (new authentication())->get_user(); @@ -1077,6 +1132,7 @@ class moduleConfigRoute ] ); $this->post('/selfserve/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/selfserve/config'); global $response; $this->requirePermission('modules_selfserve_config'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/moduleEconomicCustomerRoute.php b/services/nginx/app/routes/moduleEconomicCustomerRoute.php index 34fbeae9..25f0daab 100644 --- a/services/nginx/app/routes/moduleEconomicCustomerRoute.php +++ b/services/nginx/app/routes/moduleEconomicCustomerRoute.php @@ -9,6 +9,9 @@ use classes\router; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleEconomicCustomerRoute { use route_t; @@ -22,6 +25,7 @@ class moduleEconomicCustomerRoute /** Modules > Economic > Customer > Get customer */ $this->get('/modules/economic/customer', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/modules/economic/customer'); global $response; $this->requirePermission('modules_economic_customer_get'); $user = (new authentication())->get_user(); @@ -45,6 +49,7 @@ class moduleEconomicCustomerRoute /** Modules > Economic > Customer > Create customer */ $this->post('/modules/economic/customer', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/modules/economic/customer'); global $response; $this->requirePermission('modules_economic_customer_create'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/moduleEconomicRoute.php b/services/nginx/app/routes/moduleEconomicRoute.php index bb54ca10..0128bbfd 100644 --- a/services/nginx/app/routes/moduleEconomicRoute.php +++ b/services/nginx/app/routes/moduleEconomicRoute.php @@ -11,6 +11,9 @@ use objects\logs_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleEconomicRoute { use route_t; @@ -24,6 +27,7 @@ class moduleEconomicRoute /** Economic > Customers > Import customer */ $this->post('/economic/customers/import', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/economic/customers/import'); global $response; $this->requirePermission('economic_import_customer'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/moduleEntraRoute.php b/services/nginx/app/routes/moduleEntraRoute.php index 43c73b87..d892631c 100644 --- a/services/nginx/app/routes/moduleEntraRoute.php +++ b/services/nginx/app/routes/moduleEntraRoute.php @@ -8,6 +8,9 @@ use classes\router; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleEntraRoute { use route_t; @@ -21,6 +24,7 @@ class moduleEntraRoute /** Modules > Entra > Users > GET */ $this->get('/modules/entra/users', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/entra/users'); global $response; $this->requirePermission('modules_entra_users'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/moduleFxRatesAPIRoute.php b/services/nginx/app/routes/moduleFxRatesAPIRoute.php index 3638ab64..4f05d3de 100644 --- a/services/nginx/app/routes/moduleFxRatesAPIRoute.php +++ b/services/nginx/app/routes/moduleFxRatesAPIRoute.php @@ -9,6 +9,8 @@ use classes\router; use objects\currency_conversion_rates_o; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; class moduleFxRatesAPIRoute { @@ -23,6 +25,7 @@ class moduleFxRatesAPIRoute /** Modules > FXRatesAPI > conversion rate > GET */ $this->get('/modules/fxratesapi/rate', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/fxratesapi/rate'); global $response; $this->requirePermission('modules_fxratesapi_rate'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/moduleLimbleRoute.php b/services/nginx/app/routes/moduleLimbleRoute.php index 7a7ed11f..edb4cb0f 100644 --- a/services/nginx/app/routes/moduleLimbleRoute.php +++ b/services/nginx/app/routes/moduleLimbleRoute.php @@ -10,6 +10,9 @@ use limble\helpers\limble_tasks_pagination; use limble\helpers\limble_webhook_payload_task; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleLimbleRoute { use route_t; @@ -20,6 +23,7 @@ class moduleLimbleRoute /** @var router $router */ $router, $response; $this->post('/modules/limble/webhook/task', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/limble/webhook/task'); global $response; $slack = new slack(); $this->requirePermission('modules_limble_webhooks_task'); @@ -41,6 +45,7 @@ class moduleLimbleRoute ); $this->get('/modules/limble/tasks', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/limble/tasks'); global $response; $slack = new slack(); $slack->send_message('Limble Tasks Endpoint Triggered', 'Limble Tasks'); diff --git a/services/nginx/app/routes/moduleMotorAPIRoute.php b/services/nginx/app/routes/moduleMotorAPIRoute.php index 50089693..9a5fc71d 100644 --- a/services/nginx/app/routes/moduleMotorAPIRoute.php +++ b/services/nginx/app/routes/moduleMotorAPIRoute.php @@ -9,6 +9,9 @@ use classes\router; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleMotorAPIRoute { use route_t; @@ -22,6 +25,7 @@ class moduleMotorAPIRoute /** Modules > MotorAPI > Lookup > GET */ $this->get('/modules/motorapi/lookup', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/motorapi/lookup'); global $response; $this->requirePermission('modules_motorapi_lookup'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/moduleN8nRoute.php b/services/nginx/app/routes/moduleN8nRoute.php index a65e89af..3b7cd06a 100644 --- a/services/nginx/app/routes/moduleN8nRoute.php +++ b/services/nginx/app/routes/moduleN8nRoute.php @@ -10,6 +10,9 @@ use objects\logs_o; use stdClass; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleN8nRoute { use route_t; @@ -21,6 +24,7 @@ class moduleN8nRoute $router, $response; $this->get('/modules/n8n/workflows', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/n8n/workflows'); global $response; $this->requirePermission('modules_n8n_workflows_view'); $user = (new authentication())->get_user(); @@ -46,6 +50,7 @@ class moduleN8nRoute ]); $this->get('/modules/n8n/workflows/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/n8n/workflows/{id}'); global $response; $this->requirePermission('modules_n8n_workflows_view'); $user = (new authentication())->get_user(); @@ -62,6 +67,7 @@ class moduleN8nRoute ]); $this->post('/modules/n8n/workflows', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/n8n/workflows'); global $response; $this->requirePermission('modules_n8n_workflows_manage'); $user = (new authentication())->get_user(); @@ -80,6 +86,7 @@ class moduleN8nRoute ]); $this->put('/modules/n8n/workflows/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/n8n/workflows/{id}'); global $response; $this->requirePermission('modules_n8n_workflows_manage'); $user = (new authentication())->get_user(); @@ -98,6 +105,7 @@ class moduleN8nRoute ]); $this->post('/modules/n8n/workflows/{id}/publish', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/n8n/workflows/{id}/publish'); global $response; $this->requirePermission('modules_n8n_workflows_manage'); $user = (new authentication())->get_user(); @@ -116,6 +124,7 @@ class moduleN8nRoute ]); $this->post('/modules/n8n/workflows/{id}/deactivate', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/n8n/workflows/{id}/deactivate'); global $response; $this->requirePermission('modules_n8n_workflows_manage'); $user = (new authentication())->get_user(); @@ -131,6 +140,7 @@ class moduleN8nRoute ]); $this->post('/modules/n8n/webhooks/trigger', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/n8n/webhooks/trigger'); global $response; $this->requirePermission('modules_n8n_workflows_run'); $user = (new authentication())->get_user(); @@ -152,6 +162,7 @@ class moduleN8nRoute ]); $this->get('/modules/n8n/executions', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/n8n/executions'); global $response; $this->requirePermission('modules_n8n_executions_view'); $user = (new authentication())->get_user(); @@ -176,6 +187,7 @@ class moduleN8nRoute ]); $this->get('/modules/n8n/executions/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/n8n/executions/{id}'); global $response; $this->requirePermission('modules_n8n_executions_view'); $user = (new authentication())->get_user(); @@ -191,6 +203,7 @@ class moduleN8nRoute ]); $this->post('/modules/n8n/executions/{id}/retry', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/n8n/executions/{id}/retry'); global $response; $this->requirePermission('modules_n8n_workflows_run'); $user = (new authentication())->get_user(); @@ -211,6 +224,7 @@ class moduleN8nRoute ]); $this->post('/modules/n8n/executions/{id}/stop', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/n8n/executions/{id}/stop'); global $response; $this->requirePermission('modules_n8n_workflows_manage'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/moduleScannerRoute.php b/services/nginx/app/routes/moduleScannerRoute.php index 65534e82..57af67f5 100644 --- a/services/nginx/app/routes/moduleScannerRoute.php +++ b/services/nginx/app/routes/moduleScannerRoute.php @@ -6,6 +6,9 @@ use classes\licenseplaterecognizer; use classes\response; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleScannerRoute { use route_t; @@ -37,6 +40,7 @@ class moduleScannerRoute $router, $response; /** Modules > Scanner > License Plate Recognition > POST */ $this->post('/modules/scanner/lpr', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/scanner/lpr'); global $response; $route_started_at = microtime(true); $image_upload = self::getLPRImageUpload(); diff --git a/services/nginx/app/routes/moduleSelfServeRoute.php b/services/nginx/app/routes/moduleSelfServeRoute.php index 66624583..bb84c184 100644 --- a/services/nginx/app/routes/moduleSelfServeRoute.php +++ b/services/nginx/app/routes/moduleSelfServeRoute.php @@ -33,6 +33,9 @@ use objects\subusers_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleSelfServeRoute { use route_t; @@ -50,6 +53,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Status */ $this->get('/modules/self-serve/lane/status', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/self-serve/lane/status'); global $response; $this->requirePermission('modules_selfserve_lane_status_view'); $selfserve = new selfserve(); @@ -72,6 +76,7 @@ class moduleSelfServeRoute ); $this->put('/modules/self-serve/lane/status', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/status'); global $response; $this->requirePermission('modules_selfserve_lane_status_set'); @@ -127,6 +132,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Wash > In-progress details */ $this->get('/modules/self-serve/lane/wash/in-progress', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/self-serve/lane/wash/in-progress'); global $response; self::requireParameters(['lane_id']); $lane_id = (int)$this->getParameter('lane_id'); @@ -347,6 +353,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Wash > My active wash */ $this->get('/modules/self-serve/lane/wash/my-active-wash', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/self-serve/lane/wash/my-active-wash'); global $response; $principal_scope = $this->requireMyActiveWashPrincipalScope(); @@ -367,6 +374,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Sessions */ $this->get('/modules/self-serve/sessions', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/self-serve/sessions'); global $response; $this->requirePermission('modules_selfserve_sessions_view'); @@ -420,6 +428,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Session detail */ $this->get('/modules/self-serve/sessions/{id}', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/self-serve/sessions/{id}'); global $response; $this->requirePermission('modules_selfserve_sessions_view'); $session_id = (int)$this->fromRoute('id'); @@ -439,6 +448,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Force > Stop */ $this->post('/modules/self-serve/lane/force/stop', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/force/stop'); global $response; $this->requirePermission('modules_selfserve_sessions_force_stop'); self::requireParameters(['lane_id', 'bill']); @@ -480,6 +490,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Command */ $this->post('/modules/self-serve/lane/command', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/command'); global $response; $selfserve = new selfserve(); // Get the request user @@ -649,6 +660,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Allowed services (derived from shown tasks) */ $this->post('/modules/self-serve/lane/services/allowed', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/services/allowed'); global $response; $selfserve = new selfserve(); // Validate parameters @@ -756,6 +768,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Gate > Open (ENTRANCE/EXIT) */ $this->post('/modules/self-serve/lane/gate/open', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/gate/open'); global $response; $this->requirePermission('modules_selfserve_lane_gate_open'); $selfserve = new selfserve(); @@ -805,6 +818,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Hardware > Batch status */ $this->post('/modules/self-serve/lane/hardware/batch/status', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/hardware/batch/status'); global $response; $selfserve = new selfserve(); self::requireParameters(['lane_id']); @@ -840,6 +854,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Hardware > Batch set/open */ $this->post('/modules/self-serve/lane/hardware/batch/set', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/hardware/batch/set'); global $response; $selfserve = new selfserve(); self::requireParameters(['lane_id', 'commands']); @@ -869,6 +884,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Hardware > Batch poll */ $this->get('/modules/self-serve/lane/hardware/batch/{batch_id}', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/self-serve/lane/hardware/batch/{batch_id}'); global $response; $this->requirePermission('modules_selfserve_lane_relay_machine_status_view'); try { @@ -882,6 +898,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Relay > MACHINE_PROGRAM_PICKER status */ $this->get('/modules/self-serve/lane/relay/machine_program_picker/status', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/self-serve/lane/relay/machine_program_picker/status'); global $response; $this->requirePermission('modules_selfserve_lane_relay_machine_program_picker_status_view'); $selfserve = new selfserve(); @@ -904,6 +921,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Relay > MACHINE_PROGRAM_PICKER set on/off */ $this->post('/modules/self-serve/lane/relay/machine_program_picker/set', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/relay/machine_program_picker/set'); global $response; $this->requirePermission('modules_selfserve_lane_relay_machine_program_picker_status_set'); $selfserve = new selfserve(); @@ -935,6 +953,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Relay > MACHINE_CLEANER status */ $this->get('/modules/self-serve/lane/relay/machine_cleaner/status', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/self-serve/lane/relay/machine_cleaner/status'); global $response; $this->requirePermission('modules_selfserve_lane_relay_machine_cleaner_status_view'); $selfserve = new selfserve(); @@ -957,6 +976,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Relay > MACHINE_CLEANER set on/off */ $this->post('/modules/self-serve/lane/relay/machine_cleaner/set', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/relay/machine_cleaner/set'); global $response; $this->requirePermission('modules_selfserve_lane_relay_machine_cleaner_status_set'); $selfserve = new selfserve(); @@ -988,6 +1008,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Relay > MACHINE status */ $this->get('/modules/self-serve/lane/relay/machine/status', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/self-serve/lane/relay/machine/status'); global $response; $this->requirePermission('modules_selfserve_lane_relay_machine_status_view'); $selfserve = new selfserve(); @@ -1010,6 +1031,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Relay > MACHINE set on/off */ $this->post('/modules/self-serve/lane/relay/machine/set', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/relay/machine/set'); global $response; $this->requirePermission('modules_selfserve_lane_relay_machine_status_set'); $selfserve = new selfserve(); @@ -1058,6 +1080,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Relay > Enable MACHINE_PROGRAM_PICKER (manual) */ $this->post('/modules/self-serve/lane/relay/machine_program_picker/enable', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/relay/machine_program_picker/enable'); global $response; $this->requirePermission('modules_selfserve_lane_relay_enable_machine_program_picker'); $selfserve = new selfserve(); @@ -1091,6 +1114,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Relay > Enable MACHINE_CLEANER (manual) */ $this->post('/modules/self-serve/lane/relay/machine_cleaner/enable', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/relay/machine_cleaner/enable'); global $response; $this->requirePermission('modules_selfserve_lane_relay_enable_machine_cleaner'); $selfserve = new selfserve(); @@ -1124,6 +1148,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Relay > Enable MACHINE (manual, gated by allowed services) */ $this->post('/modules/self-serve/lane/relay/machine/enable', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/relay/machine/enable'); global $response; $selfserve = new selfserve(); // Validate parameters @@ -1173,6 +1198,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Force > MACHINE_PROGRAM_PICKER enable */ $this->post('/modules/self-serve/lane/force/machine_program_picker/enable', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/force/machine_program_picker/enable'); global $response; $this->requirePermission('modules_selfserve_lane_force_machine_program_picker_enable'); $selfserve = new selfserve(); @@ -1206,6 +1232,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Force > MACHINE_PROGRAM_PICKER disable */ $this->post('/modules/self-serve/lane/force/machine_program_picker/disable', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/force/machine_program_picker/disable'); global $response; $this->requirePermission('modules_selfserve_lane_force_machine_program_picker_disable'); $selfserve = new selfserve(); @@ -1233,6 +1260,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Force > MACHINE_CLEANER enable */ $this->post('/modules/self-serve/lane/force/machine_cleaner/enable', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/force/machine_cleaner/enable'); global $response; $this->requirePermission('modules_selfserve_lane_force_machine_cleaner_enable'); $selfserve = new selfserve(); @@ -1266,6 +1294,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Force > MACHINE_CLEANER disable */ $this->post('/modules/self-serve/lane/force/machine_cleaner/disable', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/force/machine_cleaner/disable'); global $response; $this->requirePermission('modules_selfserve_lane_force_machine_cleaner_disable'); $selfserve = new selfserve(); @@ -1293,6 +1322,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Force > MACHINE enable (simulate started wash) */ $this->post('/modules/self-serve/lane/force/machine/enable', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/force/machine/enable'); global $response; $this->requirePermission('modules_selfserve_lane_force_machine_enable'); $selfserve = new selfserve(); @@ -1367,6 +1397,7 @@ class moduleSelfServeRoute /** Modules > Self Serve > Lane > Force > MACHINE disable (simulate started wash without machine) */ $this->post('/modules/self-serve/lane/force/machine/disable', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/modules/self-serve/lane/force/machine/disable'); global $response; $this->requirePermission('modules_selfserve_lane_force_machine_disable'); $selfserve = new selfserve(); diff --git a/services/nginx/app/routes/moduleStripeRoute.php b/services/nginx/app/routes/moduleStripeRoute.php index 6360715f..761c998d 100644 --- a/services/nginx/app/routes/moduleStripeRoute.php +++ b/services/nginx/app/routes/moduleStripeRoute.php @@ -11,6 +11,9 @@ use objects\logs_o; use objects\orders_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleStripeRoute { use route_t; @@ -24,6 +27,7 @@ class moduleStripeRoute /** Modules > Stripe > Customers > List */ $this->get('/modules/stripe/customers', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/modules/stripe/customers'); global $response; $this->requirePermission('modules_stripe_customers_list'); $user = (new authentication())->get_user(); @@ -43,6 +47,7 @@ class moduleStripeRoute /** Modules > Stripe > Products > List */ $this->get('/modules/stripe/products', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/modules/stripe/products'); global $response; $this->requirePermission('modules_stripe_products_list'); $user = (new authentication())->get_user(); @@ -62,6 +67,7 @@ class moduleStripeRoute /** Modules > Stripe > Prices > List */ $this->get('/modules/stripe/prices', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/modules/stripe/prices'); global $response; $this->requirePermission('modules_stripe_prices_list'); $user = (new authentication())->get_user(); @@ -81,6 +87,7 @@ class moduleStripeRoute /** Modules > Stripe > Retired direct payment-link creation */ $this->post('/modules/stripe/invoice', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/modules/stripe/invoice'); global $response; $this->requirePermission('modules_stripe_invoice_send'); $response->error([ @@ -95,6 +102,7 @@ class moduleStripeRoute /** Modules > Stripe > Cancel Invoice */ $this->delete('/modules/stripe/invoice', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/modules/stripe/invoice'); global $response; $this->requirePermission('modules_stripe_invoice_send'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/moduleUsageRoute.php b/services/nginx/app/routes/moduleUsageRoute.php index 4302bcbe..2513a132 100644 --- a/services/nginx/app/routes/moduleUsageRoute.php +++ b/services/nginx/app/routes/moduleUsageRoute.php @@ -6,6 +6,9 @@ use classes\module_usage_service; use Exception; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleUsageRoute { use route_t; @@ -13,6 +16,7 @@ class moduleUsageRoute public function run(): void { $this->get('/modules/usage/summary', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/usage/summary'); global $response; $this->requirePermission('modules_usage_view'); @@ -27,6 +31,7 @@ class moduleUsageRoute ]); $this->get('/modules/usage/{moduleKey}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/usage/{moduleKey}'); global $response; $this->requirePermission('modules_usage_view'); @@ -41,6 +46,7 @@ class moduleUsageRoute ]); $this->patch('/modules/quotas/{moduleKey}/{metricKey}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/quotas/{moduleKey}/{metricKey}'); global $response; $this->requirePermission('modules_quotas_manage'); diff --git a/services/nginx/app/routes/moduleVirkDataRoute.php b/services/nginx/app/routes/moduleVirkDataRoute.php index 72685bb7..207d574d 100644 --- a/services/nginx/app/routes/moduleVirkDataRoute.php +++ b/services/nginx/app/routes/moduleVirkDataRoute.php @@ -11,6 +11,9 @@ use objects\currency_conversion_rates_o; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleVirkDataRoute { use route_t; @@ -24,6 +27,7 @@ class moduleVirkDataRoute /** Modules > VirkData > search > GET */ $this->get('/modules/virkdata/search', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/virkdata/search'); global $response; $this->requirePermission('modules_virkdata_search'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/moduleWeatherAPIRoute.php b/services/nginx/app/routes/moduleWeatherAPIRoute.php index 6550946e..27023592 100644 --- a/services/nginx/app/routes/moduleWeatherAPIRoute.php +++ b/services/nginx/app/routes/moduleWeatherAPIRoute.php @@ -19,6 +19,8 @@ use objects\departments_o; use objects\logs_o; use objects\orders_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; class moduleWeatherAPIRoute { @@ -46,6 +48,7 @@ class moduleWeatherAPIRoute $router, $response; $this->get('/modules/weatherapi/current', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/weatherapi/current'); global $response; $this->requirePermission('modules_weatherapi_current'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/moduleWorkfeedRoute.php b/services/nginx/app/routes/moduleWorkfeedRoute.php index 13255a12..27f3022e 100644 --- a/services/nginx/app/routes/moduleWorkfeedRoute.php +++ b/services/nginx/app/routes/moduleWorkfeedRoute.php @@ -9,6 +9,9 @@ use classes\workfeed; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleWorkfeedRoute { use route_t; @@ -20,6 +23,7 @@ class moduleWorkfeedRoute $router, $response; $this->get('/modules/workfeed/employees', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/workfeed/employees'); global $response; $this->requirePermission('modules_workfeed_employees_view'); $user = (new authentication())->get_user(); @@ -35,6 +39,7 @@ class moduleWorkfeedRoute ]); $this->get('/modules/workfeed/employees/{id}', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/workfeed/employees/{id}'); global $response; $this->requirePermission('modules_workfeed_employees_view'); $user = (new authentication())->get_user(); @@ -50,6 +55,7 @@ class moduleWorkfeedRoute ]); $this->get('/modules/workfeed/shifts', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/workfeed/shifts'); global $response; $this->requirePermission('modules_workfeed_shifts_view'); $user = (new authentication())->get_user(); @@ -94,6 +100,7 @@ class moduleWorkfeedRoute ]); $this->get('/modules/workfeed/shifts/{id}', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/workfeed/shifts/{id}'); global $response; $this->requirePermission('modules_workfeed_shifts_view'); $user = (new authentication())->get_user(); @@ -109,6 +116,7 @@ class moduleWorkfeedRoute ]); $this->get('/modules/workfeed/departments', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/workfeed/departments'); global $response; $this->requirePermission('modules_workfeed_departments_view'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/moduleXLVaskRoute.php b/services/nginx/app/routes/moduleXLVaskRoute.php index fb7c142c..83fb55f2 100644 --- a/services/nginx/app/routes/moduleXLVaskRoute.php +++ b/services/nginx/app/routes/moduleXLVaskRoute.php @@ -10,6 +10,9 @@ use objects\orders_o; use objects\xlvask_customers_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class moduleXLVaskRoute { use route_t; @@ -20,6 +23,7 @@ class moduleXLVaskRoute /** @var router $router */ $router, $response; $this->get('/modules/xlvask/usageLog', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/xlvask/usageLog'); global $response; $this->requirePermission('modules_xlvask_usageLog'); $params = [ @@ -48,6 +52,7 @@ class moduleXLVaskRoute ); $this->get('/modules/xlvask/vehicles', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/xlvask/vehicles'); global $response; $this->requirePermission('modules_xlvask_vehicles'); $params = [ @@ -74,6 +79,7 @@ class moduleXLVaskRoute ); $this->get('/modules/xlvask/customers', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/xlvask/customers'); global $response; $this->requirePermission('modules_xlvask_customers'); $params = [ @@ -102,6 +108,7 @@ class moduleXLVaskRoute ); $this->get('/modules/xlvask/internal/vehicle-types', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/xlvask/internal/vehicle-types'); global $response; $this->requirePermission('modules_xlvask_internal_vehicle_types'); $user = (new authentication())->get_user(); @@ -122,6 +129,7 @@ class moduleXLVaskRoute ); $this->get('/modules/xlvask/related-orders', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/xlvask/related-orders'); global $response; $this->requirePermission('modules_xlvask_related_orders'); self::requireParameters(['washIds']); @@ -156,6 +164,7 @@ class moduleXLVaskRoute ); $this->get('/modules/xlvask/tasks/sync-users', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/xlvask/tasks/sync-users'); global $response; $this->requirePermission('modules_xlvask_sync_users'); // Create the xlvask tasks object @@ -174,6 +183,7 @@ class moduleXLVaskRoute ); $this->get('/modules/xlvask/tasks/sync-usage', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/xlvask/tasks/sync-usage'); global $response; $this->requirePermission('modules_xlvask_sync_usage'); // Create the xlvask tasks object @@ -192,6 +202,7 @@ class moduleXLVaskRoute ); $this->get('/modules/xlvask/tasks/import-customers', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/xlvask/tasks/import-customers'); global $response; $this->requirePermission('modules_xlvask_import_customers'); // Create the xlvask_customers_o object @@ -208,6 +219,7 @@ class moduleXLVaskRoute ); $this->get('/modules/xlvask/tasks/import-vehicles', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/xlvask/tasks/import-vehicles'); global $response; $this->requirePermission('modules_xlvask_import_vehicles'); // Create the xlvask_vehicles_o object diff --git a/services/nginx/app/routes/notificationsRoute.php b/services/nginx/app/routes/notificationsRoute.php index 09abe386..816bef36 100644 --- a/services/nginx/app/routes/notificationsRoute.php +++ b/services/nginx/app/routes/notificationsRoute.php @@ -7,6 +7,9 @@ use objects\logs_o; use objects\notifications_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class notificationsRoute { use route_t; @@ -14,6 +17,7 @@ class notificationsRoute public function run(): void { $this->get('/notifications', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/notifications'); // Require the user to be logged in global $response; $this->requirePermission('list_notifications'); @@ -77,6 +81,7 @@ class notificationsRoute ); $this->post('/notifications', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/notifications'); // Require the user to be logged in global $response; $this->requirePermission('add_notification'); @@ -130,6 +135,7 @@ class notificationsRoute $this->delete('/notifications', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/notifications'); // Require the user to be logged in global $response; $this->requirePermission('delete_own_notifications'); diff --git a/services/nginx/app/routes/orderBookingRoute.php b/services/nginx/app/routes/orderBookingRoute.php index 0b8698be..271e6c55 100644 --- a/services/nginx/app/routes/orderBookingRoute.php +++ b/services/nginx/app/routes/orderBookingRoute.php @@ -20,6 +20,9 @@ use objects\products_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + require_once WD . '/classes/security_policy_service.php'; class orderBookingRoute @@ -29,6 +32,7 @@ class orderBookingRoute public function run(): void { $this->post('/order-bookings', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/order-bookings'); // Require the user to be logged in global $response; /** @@ -106,6 +110,7 @@ class orderBookingRoute ); $this->get('/order-bookings', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/order-bookings'); // Require the user to be logged in global $response; /** @@ -183,6 +188,7 @@ class orderBookingRoute ); $this->get('/order-bookings/counts', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/order-bookings/counts'); global $response; $auth = new authentication(); @@ -254,6 +260,7 @@ class orderBookingRoute ); $this->put('/order-bookings', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/order-bookings'); // Require the user to be logged in global $response; /** @@ -337,6 +344,7 @@ class orderBookingRoute ); $this->delete('/order-bookings', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/order-bookings'); // Require the user to be logged in global $response; /** @@ -378,6 +386,7 @@ class orderBookingRoute ); $this->post('/order-bookings/booking-confirmation/resend', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/order-bookings/booking-confirmation/resend'); global $response; $object = self::getTargetObject(); @@ -401,6 +410,7 @@ class orderBookingRoute ); $this->post('/order-bookings/completion-confirmation/resend', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/order-bookings/completion-confirmation/resend'); global $response; $object = self::getTargetObject(); @@ -432,6 +442,7 @@ class orderBookingRoute ); $this->post('/order-bookings/complete', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/order-bookings/complete'); // Require the user to be logged in global $response; /** diff --git a/services/nginx/app/routes/orderInvoicesRoute.php b/services/nginx/app/routes/orderInvoicesRoute.php index f3c9443d..1864aa0c 100644 --- a/services/nginx/app/routes/orderInvoicesRoute.php +++ b/services/nginx/app/routes/orderInvoicesRoute.php @@ -25,6 +25,9 @@ use objects\orders_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class orderInvoicesRoute { use route_t; @@ -37,6 +40,7 @@ class orderInvoicesRoute /** Collected order invoices > GET */ $this->get('/collected-invoices', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices'); global $response; $this->requirePermission('list_collected_invoices'); $user = (new authentication())->get_user(); @@ -142,6 +146,7 @@ class orderInvoicesRoute /** Collected order invoices > Compare with E-conomic > GET */ $this->get('/collected-invoices/economic/compare', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices/economic/compare'); global $response; $this->requirePermission('compare_collected_invoice_economic'); //$user = (new authentication())->get_user(); @@ -241,6 +246,7 @@ class orderInvoicesRoute /** Collected order invoices > E-conomic V2 details > GET */ $this->get('/collected-invoices/economic/v2/details', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices/economic/v2/details'); global $response; $this->requirePermission('view_collected_invoice_economic_v2_details'); $collected_invoice_id = $this->requireCollectedInvoiceId(); @@ -255,6 +261,7 @@ class orderInvoicesRoute /** Collected order invoices > E-conomic PDF > GET */ $this->get('/collected-invoices/economic/pdf', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices/economic/pdf'); global $response; $this->requirePermission('download_collected_invoice_economic_pdf'); $collected_invoice_id = $this->requireCollectedInvoiceId(); @@ -275,6 +282,7 @@ class orderInvoicesRoute /** Collected order invoices > E-conomic V2 compare > GET */ $this->get('/collected-invoices/economic/v2/compare', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices/economic/v2/compare'); global $response; $this->requirePermission('compare_collected_invoice_economic_v2'); $collected_invoice_id = $this->requireCollectedInvoiceId(); @@ -303,6 +311,7 @@ class orderInvoicesRoute /** Collected order invoices > E-conomic V2 compare bulk > POST */ $this->post('/collected-invoices/economic/v2/compare/bulk', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/economic/v2/compare/bulk'); global $response; $this->requirePermission('compare_collected_invoice_economic_v2_bulk'); self::requireParameters(['collected_invoice_ids']); @@ -370,6 +379,7 @@ class orderInvoicesRoute /** Collected order invoices > E-conomic V2 revenue statistics > GET */ $this->get('/collected-invoices/economic/v2/revenue-statistics', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices/economic/v2/revenue-statistics'); global $response; $this->requirePermission('view_collected_invoice_economic_v2_revenue_statistics'); @@ -413,6 +423,7 @@ class orderInvoicesRoute /** Collected order invoices > Ready to invoice > GET */ $this->get('/collected-invoices/ready-to-invoice', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices/ready-to-invoice'); global $response; $this->requirePermission('list_collected_invoices'); $user = (new authentication())->get_user(); @@ -449,6 +460,7 @@ class orderInvoicesRoute /** Collected order invoices > POST */ $this->post('/collected-invoices', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices'); global $response; $this->requirePermission('add_collected_invoice'); $user = (new authentication())->get_user(); @@ -523,6 +535,7 @@ class orderInvoicesRoute /** Collected order invoices > Move to customer > POST */ $this->post('/collected-invoices/move-to-customer', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/move-to-customer'); global $response; $this->requirePermission('move_collected_invoice_customer'); $user = (new authentication())->get_user(); @@ -566,6 +579,7 @@ class orderInvoicesRoute /** Collected order invoices > Split > POST */ $this->post('/collected-invoices/split', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/split'); global $response; $this->requirePermission('split_collected_invoice'); $user = (new authentication())->get_user(); @@ -596,6 +610,7 @@ class orderInvoicesRoute /** Collected order invoices > Split by month > POST */ $this->post('/collected-invoices/split-by-month', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/split-by-month'); global $response, $db; $this->requirePermission('split_collected_invoice'); $user = (new authentication())->get_user(); @@ -738,6 +753,7 @@ class orderInvoicesRoute /** Collected order invoices > Bulk action preview > POST */ $this->post('/collected-invoices/bulk-actions/preview', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/bulk-actions/preview'); global $response; $user = (new authentication())->get_user(); if (!$user) { @@ -803,6 +819,7 @@ class orderInvoicesRoute /** Collected order invoices > Bulk action apply > POST */ $this->post('/collected-invoices/bulk-actions/apply', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/bulk-actions/apply'); global $response; $user = (new authentication())->get_user(); if (!$user) { @@ -860,6 +877,7 @@ class orderInvoicesRoute ); $this->post('/superuser/invoicing/period/tree-actions/preview', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/superuser/invoicing/period/tree-actions/preview'); global $response; $this->requirePermission('superuser_invoicing_period'); $user = (new authentication())->get_user(); @@ -915,6 +933,7 @@ class orderInvoicesRoute ); $this->post('/superuser/invoicing/period/tree-actions/apply', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/superuser/invoicing/period/tree-actions/apply'); global $response; $this->requirePermission('superuser_invoicing_period'); $user = (new authentication())->get_user(); @@ -955,6 +974,7 @@ class orderInvoicesRoute /** Collected order invoices > E-Conomic > POST (queued) */ $this->post('/collected-invoices/economic', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/economic'); global $response; $this->requirePermission('add_collected_invoice_economic'); $user = (new authentication())->get_user(); @@ -1049,6 +1069,7 @@ class orderInvoicesRoute ); $this->get('/collected-invoices/economic/queue', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices/economic/queue'); global $response; $this->requirePermission('add_collected_invoice_economic'); $user = (new authentication())->get_user(); @@ -1086,6 +1107,7 @@ class orderInvoicesRoute ); $this->get('/collected-invoices/economic/queue/status', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices/economic/queue/status'); global $response; $this->requirePermission('add_collected_invoice_economic'); $user = (new authentication())->get_user(); @@ -1105,6 +1127,7 @@ class orderInvoicesRoute ); $this->get('/collected-invoices/economic/queue/monitor', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices/economic/queue/monitor'); global $response; $this->requirePermission('add_collected_invoice_economic'); $user = (new authentication())->get_user(); @@ -1128,6 +1151,7 @@ class orderInvoicesRoute ); $this->post('/collected-invoices/economic/queue/retry', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/economic/queue/retry'); global $response; $this->requirePermission('add_collected_invoice_economic'); $user = (new authentication())->get_user(); @@ -1162,6 +1186,7 @@ class orderInvoicesRoute ); $this->post('/collected-invoices/economic/queue/dismiss', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/economic/queue/dismiss'); global $response; $this->requirePermission('add_collected_invoice_economic'); $user = (new authentication())->get_user(); @@ -1198,6 +1223,7 @@ class orderInvoicesRoute ); $this->post('/collected-invoices/economic/queue/dismiss-terminal', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/economic/queue/dismiss-terminal'); global $response; $this->requirePermission('add_collected_invoice_economic'); $user = (new authentication())->get_user(); @@ -1223,6 +1249,7 @@ class orderInvoicesRoute ); $this->post('/collected-invoices/economic/queue/run', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/economic/queue/run'); global $response; $this->requirePermission('add_collected_invoice_economic'); $user = (new authentication())->get_user(); @@ -1265,6 +1292,7 @@ class orderInvoicesRoute /** Collected order invoices > Move multiple > Registration numbers > POST */ $this->post('/collected-invoices/move-multiple/registration-numbers', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/move-multiple/registration-numbers'); global $response; $this->requirePermission('move_collected_invoice'); $user = (new authentication())->get_user(); @@ -1339,6 +1367,7 @@ class orderInvoicesRoute }); /** Collected order invoices > Move multiple > POST */ $this->post('/collected-invoices/move-multiple', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/move-multiple'); // Used to move multiple orders, to a new collected order invoice, in one request, instead of having to move each order one by one global $response; $this->requirePermission('move_collected_invoice'); @@ -1402,6 +1431,7 @@ class orderInvoicesRoute /** Collected order invoices > E-Conomic > Unlink and clear cached data > POST */ $this->post('/collected-invoices/economic/unlink', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/economic/unlink'); global $response; $this->requirePermission('unlink_collected_invoice_economic'); $user = (new authentication())->get_user(); @@ -1433,6 +1463,7 @@ class orderInvoicesRoute /** Collected order invoices > E-Conomic > Remove special arrangements, and set all items to be included in the invoice > POST */ $this->post('/collected-invoices/remove-special-arrangements', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/remove-special-arrangements'); global $response; $this->requirePermission('reset_collected_invoice_economic'); $user = (new authentication())->get_user(); @@ -1466,6 +1497,7 @@ class orderInvoicesRoute ); /** Collected order invoices > E-Conomic > Reset prices of items not included in the invoice > POST */ $this->post('/collected-invoices/reset-prices-of-items-not-included-in-invoice', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/reset-prices-of-items-not-included-in-invoice'); global $response; $this->requirePermission('reset_collected_invoice_economic'); $user = (new authentication())->get_user(); @@ -1498,6 +1530,7 @@ class orderInvoicesRoute /** Collected order invoices > Stripe > BOOK > POST */ $this->post('/collected-invoices/stripe/book', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/stripe/book'); global $response; $this->requirePermission('add_collected_invoice_stripe'); $user = (new authentication())->get_user(); @@ -1580,6 +1613,7 @@ class orderInvoicesRoute /** Collected order invoices > Vehicle subscriptions > POST */ $this->post('/collected-invoices/vehicle-subscriptions', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/vehicle-subscriptions'); global $response; $this->requirePermission('add_collected_invoice_vehicle_subscriptions'); $user = (new authentication())->get_user(); @@ -1611,6 +1645,7 @@ class orderInvoicesRoute /** Collected order invoices > Vehicle subscriptions > POST */ $this->post('/collected-invoices/fixed-price', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/fixed-price'); global $response; $this->requirePermission('add_collected_invoice_fixed_price'); $user = (new authentication())->get_user(); @@ -1651,6 +1686,7 @@ class orderInvoicesRoute /** Collected order invoices > Vehicle subscriptions > POST */ $this->post('/collected-invoices/vehicle-subscriptions/custom', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/vehicle-subscriptions/custom'); global $response; $this->requirePermission('add_collected_invoice_vehicle_subscriptions'); $user = (new authentication())->get_user(); @@ -1717,6 +1753,7 @@ class orderInvoicesRoute /** Collected order invoices > Open > GET Customers */ $this->get('/collected-invoices/customers', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices/customers'); global $response; $this->requirePermission('list_collected_invoices'); $user = (new authentication())->get_user(); @@ -1799,6 +1836,7 @@ class orderInvoicesRoute ); $this->get('/collected-invoices/customers/invoicePerOrder', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices/customers/invoicePerOrder'); global $response; $this->requirePermission('list_collected_invoices'); $user = (new authentication())->get_user(); @@ -1842,6 +1880,7 @@ class orderInvoicesRoute ); $this->get('/collected-invoices/customers/invoicePerMonth', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices/customers/invoicePerMonth'); global $response; $this->requirePermission('list_collected_invoices'); $user = (new authentication())->get_user(); @@ -1886,6 +1925,7 @@ class orderInvoicesRoute $this->post('/collected-invoices/customers/invoiceTotals', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/customers/invoiceTotals'); // This is a superuser-only route global $response; $this->requirePermission('list_collected_invoices'); @@ -1973,6 +2013,7 @@ class orderInvoicesRoute }); $this->get('/collected-invoices/economic/overview', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices/economic/overview'); global $response; $this->requirePermission('list_collected_invoices_economic_overview'); $user = (new authentication())->get_user(); @@ -2054,6 +2095,7 @@ class orderInvoicesRoute ); $this->post('/collected-invoices/economic/run/check-drafts', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/economic/run/check-drafts'); global $response; $this->requirePermission('module_economic_run_check_drafts'); $user = (new authentication())->get_user(); @@ -2082,6 +2124,7 @@ class orderInvoicesRoute ); $this->post('/collected-invoices/economic/run/check-errors', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_WRITE, '/collected-invoices/economic/run/check-errors'); global $response; $this->requirePermission('module_economic_run_check_errors'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/orderItemsRoute.php b/services/nginx/app/routes/orderItemsRoute.php index 69b4fe89..a7114a7a 100644 --- a/services/nginx/app/routes/orderItemsRoute.php +++ b/services/nginx/app/routes/orderItemsRoute.php @@ -11,6 +11,9 @@ use objects\orders_o; use objects\products_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class orderItemsRoute { use route_t; @@ -18,6 +21,7 @@ class orderItemsRoute public function run(): void { $this->post('/order/items', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/order/items'); // Require the user to be logged in global $response; $this->requirePermission('add_order_items'); @@ -154,6 +158,7 @@ class orderItemsRoute ); $this->get('/order/items', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/order/items'); // Require the user to be logged in global $response; // Check if the user is requesting their own order items @@ -209,6 +214,7 @@ class orderItemsRoute ); $this->delete('/order/items', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/order/items'); // Require the user to be logged in global $response, $db; $this->requirePermission('delete_order_items'); @@ -262,6 +268,7 @@ class orderItemsRoute ); $this->put('/order/items', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/order/items'); // Require the user to be logged in global $response, $db; $this->requirePermission('edit_order_items'); diff --git a/services/nginx/app/routes/orderRoute.php b/services/nginx/app/routes/orderRoute.php index 06fe201d..ce8262c9 100644 --- a/services/nginx/app/routes/orderRoute.php +++ b/services/nginx/app/routes/orderRoute.php @@ -8,6 +8,9 @@ use objects\logs_o; use objects\orders_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class orderRoute { use route_t; @@ -15,6 +18,7 @@ class orderRoute public function run(): void { $this->get('/order', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/order'); // Require the user to be logged in global /** @var response $response */ $response; @@ -55,6 +59,7 @@ class orderRoute ); $this->post('/order/wash-certificate', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/order/wash-certificate'); // Require the user to be logged in and have permission global /** @var response $response */ $response; @@ -123,6 +128,7 @@ class orderRoute /** * $this->put('/order', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/order'); * * // Require the user to be logged in * global $response; diff --git a/services/nginx/app/routes/ordersRoute.php b/services/nginx/app/routes/ordersRoute.php index 584e91ac..340b3fe3 100644 --- a/services/nginx/app/routes/ordersRoute.php +++ b/services/nginx/app/routes/ordersRoute.php @@ -25,6 +25,9 @@ use objects\stripe_module_orders_o; use objects\stripe_payment_intents_o; use objects\users_o; use traits\route_t; + +use app\auth\Scope; +use app\auth\ScopeMiddleware; use modules\subusers\helpers\subusers_permission_node_key; class ordersRoute @@ -36,6 +39,7 @@ class ordersRoute public function run(): void { $this->get('/orders/reference-suggestions', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/orders/reference-suggestions'); global $response; $auth = new authentication(); $user = $auth->get_user(); @@ -82,6 +86,7 @@ class ordersRoute ); $this->get('/orders', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/orders'); // Require the user to be logged in global $response; /** Authentication */ @@ -150,6 +155,7 @@ class ordersRoute $this->post('/orders', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/orders'); // Require the user to be logged in global $response; $this->requirePermission('add_order'); @@ -253,6 +259,7 @@ class ordersRoute ); $this->put('/order', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/order'); self::updateOrder(); }, [ @@ -261,6 +268,7 @@ class ordersRoute ); $this->put('/orders', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/orders'); self::updateOrder(); }, [ @@ -269,6 +277,7 @@ class ordersRoute ); $this->delete('/orders', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/orders'); // Require the user to be logged in global $response; $this->requirePermission('delete_order'); @@ -343,6 +352,7 @@ class ordersRoute ); $this->get('/orders/attachments/download', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/orders/attachments/download'); global $response; $context = $this->requireOrderAttachmentDownloadContext(); $downloadLink = $context['store'] instanceof pdf_store @@ -358,6 +368,7 @@ class ordersRoute ); $this->get('/orders/attachments/content', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/orders/attachments/content'); global $response; $context = $this->requireOrderAttachmentDownloadContext(); @@ -401,6 +412,7 @@ class ordersRoute ); $this->get('/orders/attachments', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/orders/attachments'); // Require the user to be logged in global $response; // Get the user object @@ -456,6 +468,7 @@ class ordersRoute ); $this->post('/orders/attachments/upload', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/orders/attachments/upload'); // Require the user to be logged in global $response; $this->requirePermission('add_order_attachments'); @@ -509,6 +522,7 @@ class ordersRoute ); $this->delete('/orders/attachments', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/orders/attachments'); // Require the user to be logged in global $response; $this->requirePermission('delete_order_attachments'); @@ -556,6 +570,7 @@ class ordersRoute ); $this->post('/orders/mark_as_completed', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/orders/mark_as_completed'); // Require the user to be logged in global $response; $this->requirePermission('mark_order_as_completed'); @@ -596,6 +611,7 @@ class ordersRoute ); $this->post('/orders/module/stripe/payment_intent', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/orders/module/stripe/payment_intent'); global $response; $this->requirePermission('charge_order'); $user = (new authentication())->get_user(); @@ -768,6 +784,7 @@ class ordersRoute ); $this->get('/orders/module/stripe/payment_intent', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/orders/module/stripe/payment_intent'); global $response; $this->requirePermission('get_payment_intent'); $user = (new authentication())->get_user(); @@ -858,6 +875,7 @@ class ordersRoute ); $this->delete('/orders/module/stripe/payment_intent', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/orders/module/stripe/payment_intent'); global $response; $this->requirePermission('charge_order'); $user = (new authentication())->get_user(); @@ -911,6 +929,7 @@ class ordersRoute ); $this->post('/orders/module/stripe/payment_intent/capture', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/orders/module/stripe/payment_intent/capture'); global $response; $this->requirePermission('confirm_payment_intent'); $user = (new authentication())->get_user(); @@ -1000,6 +1019,7 @@ class ordersRoute ); $this->post('/orders/module/stripe/debug/simulate_payment', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/orders/module/stripe/debug/simulate_payment'); // Require the user to be logged in global $response; $this->requirePermission('debug_simulate_payment_intent'); diff --git a/services/nginx/app/routes/passkeysRoute.php b/services/nginx/app/routes/passkeysRoute.php index 9685ade4..af474815 100644 --- a/services/nginx/app/routes/passkeysRoute.php +++ b/services/nginx/app/routes/passkeysRoute.php @@ -7,6 +7,9 @@ use objects\logs_o; use objects\passkeys_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class passkeysRoute { use route_t; @@ -46,6 +49,7 @@ class passkeysRoute { // List passkeys for current authenticated user $this->get('/account/security/passkeys', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/account/security/passkeys'); global $response; $principal = $this->resolvePasskeyPrincipal('user_security_passkeys_list'); @@ -80,6 +84,7 @@ class passkeysRoute // Create/add a passkey (store after client-side WebAuthn attestation) $this->post('/account/security/passkeys', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/account/security/passkeys'); global $response; $principal = $this->resolvePasskeyPrincipal('user_security_passkeys_create'); @@ -132,6 +137,7 @@ class passkeysRoute // Rename a passkey $this->patch('/account/security/passkeys/{id}', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/account/security/passkeys/{id}'); global $response; $principal = $this->resolvePasskeyPrincipal('user_security_passkeys_rename'); @@ -162,6 +168,7 @@ class passkeysRoute // Delete a passkey (soft delete) $this->delete('/account/security/passkeys/{id}', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/account/security/passkeys/{id}'); global $response; $principal = $this->resolvePasskeyPrincipal('user_security_passkeys_delete'); diff --git a/services/nginx/app/routes/pdfGeneratorRoute.php b/services/nginx/app/routes/pdfGeneratorRoute.php index 6631e76d..bed3630e 100644 --- a/services/nginx/app/routes/pdfGeneratorRoute.php +++ b/services/nginx/app/routes/pdfGeneratorRoute.php @@ -8,6 +8,9 @@ use classes\response; use classes\router; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class pdfGeneratorRoute { use route_t; @@ -20,6 +23,7 @@ class pdfGeneratorRoute /** PDF Generator > GET */ $this->get('/modules/pdf-generator/test', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/pdf-generator/test'); global $response; // Check if the required parameters are present self::requireParameters([ @@ -123,6 +127,7 @@ class pdfGeneratorRoute ); $this->get('/modules/pdf-generator/material/order', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/modules/pdf-generator/material/order'); self::requireParameters([ 'id' ]); diff --git a/services/nginx/app/routes/plateScannersRoute.php b/services/nginx/app/routes/plateScannersRoute.php index bdef984b..11e425f9 100644 --- a/services/nginx/app/routes/plateScannersRoute.php +++ b/services/nginx/app/routes/plateScannersRoute.php @@ -8,6 +8,9 @@ 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; @@ -15,6 +18,7 @@ class plateScannersRoute 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'); @@ -53,6 +57,7 @@ class plateScannersRoute ); $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'); @@ -98,6 +103,7 @@ class plateScannersRoute ); $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'); @@ -152,6 +158,7 @@ class plateScannersRoute ); $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(); diff --git a/services/nginx/app/routes/plateScansRoute.php b/services/nginx/app/routes/plateScansRoute.php index ca9582f2..7014d591 100644 --- a/services/nginx/app/routes/plateScansRoute.php +++ b/services/nginx/app/routes/plateScansRoute.php @@ -10,6 +10,9 @@ use objects\plate_scans_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class plateScansRoute { use route_t; @@ -18,6 +21,7 @@ class plateScansRoute { $this->post('/numberplatescans', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/numberplatescans'); // Require the user to be logged in global $response; $this->requirePlateScannerAuth(); @@ -52,6 +56,7 @@ class plateScansRoute }); $this->post('/numberplatescans/department', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/numberplatescans/department'); // Require the user to be logged in global $response; $this->requirePermission('list_number_plate_scans_department'); @@ -89,6 +94,7 @@ class plateScansRoute ]); $this->get('/numberplatescans', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/numberplatescans'); // Require the user to be logged in global $response; $this->requirePermission('list_number_plate_scans'); diff --git a/services/nginx/app/routes/potentialOrderMatchesRoute.php b/services/nginx/app/routes/potentialOrderMatchesRoute.php index 338f955a..569fe65d 100644 --- a/services/nginx/app/routes/potentialOrderMatchesRoute.php +++ b/services/nginx/app/routes/potentialOrderMatchesRoute.php @@ -7,6 +7,9 @@ use objects\logs_o; use objects\xlvask_potential_order_matches_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class potentialOrderMatchesRoute { use route_t; @@ -14,6 +17,7 @@ class potentialOrderMatchesRoute public function run(): void { $this->get('/orders/sync/potential-matches', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/orders/sync/potential-matches'); // Require the user to be logged in global $response; $this->requirePermission('list_potential_order_matches'); @@ -83,6 +87,7 @@ class potentialOrderMatchesRoute ); $this->post('/orders/sync/potential-matches/ignore-duplicate', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/orders/sync/potential-matches/ignore-duplicate'); // Require the user to be logged in global $response; $this->requirePermission('ignore_duplicate_potential_order_matches'); diff --git a/services/nginx/app/routes/productOptionsRoute.php b/services/nginx/app/routes/productOptionsRoute.php index 7f19401f..91cd40d0 100644 --- a/services/nginx/app/routes/productOptionsRoute.php +++ b/services/nginx/app/routes/productOptionsRoute.php @@ -7,6 +7,9 @@ use objects\logs_o; use objects\product_options_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class productOptionsRoute { use route_t; @@ -14,6 +17,7 @@ class productOptionsRoute public function run(): void { $this->get('/product/options', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/product/options'); // Require the user to be logged in global $response; @@ -67,6 +71,7 @@ class productOptionsRoute ); $this->post('/product/options', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/product/options'); // Require the user to be logged in global $response; $this->requirePermission('add_product_option'); @@ -105,6 +110,7 @@ class productOptionsRoute ); $this->put('/product/options', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/product/options'); // Require the user to be logged in global $response; $this->requirePermission('edit_product_option'); @@ -163,6 +169,7 @@ class productOptionsRoute ); $this->delete('/product/options', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/product/options'); // Require the user to be logged in global $response; $this->requirePermission('delete_product_option'); diff --git a/services/nginx/app/routes/productsRoute.php b/services/nginx/app/routes/productsRoute.php index bf9f082d..29065156 100644 --- a/services/nginx/app/routes/productsRoute.php +++ b/services/nginx/app/routes/productsRoute.php @@ -12,6 +12,9 @@ use objects\products_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class productsRoute { use route_t; @@ -218,6 +221,7 @@ class productsRoute public function run(): void { $this->get('/products', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/products'); // Check if the user is logged in global $response; $permission_node = 'list_products'; @@ -447,6 +451,7 @@ class productsRoute ); $this->post('/products', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/products'); // Require the user to be logged in global $response; $this->requirePermission('add_product'); @@ -494,6 +499,7 @@ class productsRoute ); $this->put('/products', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/products'); // Require the user to be logged in global $response; $this->requirePermission('edit_product'); @@ -567,6 +573,7 @@ class productsRoute // but is marked as merged in the products table. Reads and new orders should follow // merged_into_product_id to the target. An audit row is written to product_merges. $this->post('/products/{id}/merge', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/products/{id}/merge'); global $response; $this->requirePermission('edit_product'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/routes/releaseManagerRoute.php b/services/nginx/app/routes/releaseManagerRoute.php index 2f34f74a..b2026e52 100644 --- a/services/nginx/app/routes/releaseManagerRoute.php +++ b/services/nginx/app/routes/releaseManagerRoute.php @@ -7,6 +7,9 @@ use classes\release_manager; use Throwable; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class releaseManagerRoute { use route_t; @@ -14,16 +17,19 @@ class releaseManagerRoute public function run(): void { $this->get('/release/bootstrap', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/release/bootstrap'); global $response; $response->success((new release_manager())->bootstrap()); }); $this->get('/release/runtime', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/release/runtime'); global $response; $response->success((new release_manager())->runtimeForCurrentPrincipal($this->getParametersAsArray())); }); $this->post('/release/timeline/events', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/release/timeline/events'); global $response; $payload = $this->requestPayload(); $events = is_array($payload['events'] ?? null) ? $payload['events'] : ($payload['event'] ?? $payload); @@ -35,6 +41,7 @@ class releaseManagerRoute }); $this->post('/release/github/webhook', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/release/github/webhook'); global $response; try { $headers = function_exists('getallheaders') ? getallheaders() : []; @@ -46,6 +53,7 @@ class releaseManagerRoute }); $this->post('/release/gate/test-runs', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/release/gate/test-runs'); global $response; $manager = new release_manager(); if (!$manager->verifyReleaseGateToken($this->releaseGateToken())) { @@ -60,6 +68,7 @@ class releaseManagerRoute }); $this->post('/release/gate/frontend-version', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/release/gate/frontend-version'); global $response; $manager = new release_manager(); if (!$manager->verifyReleaseGateToken($this->releaseGateToken())) { @@ -86,6 +95,7 @@ class releaseManagerRoute }); $this->get('/release/gate/frontend-version', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/release/gate/frontend-version'); global $response; $manager = new release_manager(); if (!$manager->verifyReleaseGateToken($this->releaseGateToken())) { @@ -98,6 +108,7 @@ class releaseManagerRoute }); $this->get('/superuser/releases', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases'); global $response; $this->requirePermission('superuser_release_manager_view'); $response->success((new release_manager())->summary()); @@ -106,6 +117,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/config'); global $response; $this->requirePermission('superuser_release_manager_view'); $response->success((new release_manager())->releaseConfig()); @@ -114,6 +126,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/operations', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/operations'); global $response; $this->requirePermission('superuser_release_manager_view'); $response->success((new release_manager())->listOperations($this->getParametersAsArray())); @@ -122,6 +135,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/operations/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/operations/{id}'); global $response; $this->requirePermission('superuser_release_manager_view'); try { @@ -134,6 +148,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/test-runs', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/test-runs'); global $response; $this->requirePermission('superuser_release_manager_deploy'); $response->success((new release_manager())->runReleaseTest($this->requestPayload(), $this->actorUserId()), 202); @@ -142,6 +157,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/coolify-cleanup/preview', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/coolify-cleanup/preview'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -154,6 +170,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/coolify-cleanup/apply', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/coolify-cleanup/apply'); global $response; $this->requirePermission('superuser_release_manager_deploy'); $this->requirePermission('superuser_coolify_manage'); @@ -168,6 +185,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/config', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/config'); global $response; $this->requirePermission('superuser_release_manager_manage'); try { @@ -180,6 +198,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/github/repositories', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/github/repositories'); global $response; $this->requirePermission('superuser_release_manager_view'); try { @@ -192,6 +211,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/github/branches', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/github/branches'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -204,6 +224,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/github/commits', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/github/commits'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -216,6 +237,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/github/test', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/github/test'); global $response; $this->requirePermission('superuser_release_manager_deploy'); $response->success((new release_manager())->testGithubRepositoryAccess($this->requestPayload())); @@ -224,6 +246,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/channels', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/channels'); global $response; $this->requirePermission('superuser_release_manager_view'); $response->success((new release_manager())->listChannels()); @@ -232,6 +255,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/channels', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/channels'); global $response; $this->requirePermission('superuser_release_manager_manage'); try { @@ -244,6 +268,7 @@ class releaseManagerRoute ]); $this->patch('/superuser/releases/channels/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/channels/{id}'); global $response; $this->requirePermission('superuser_release_manager_manage'); try { @@ -256,6 +281,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/channels/{id}/rollback', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/channels/{id}/rollback'); global $response; $this->requirePermission('superuser_release_manager_rollback'); try { @@ -268,6 +294,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/channels/{id}/sync', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/channels/{id}/sync'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -280,6 +307,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/channels/{id}/bundle', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/channels/{id}/bundle'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -292,6 +320,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/assignments', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/assignments'); global $response; $this->requirePermission('superuser_release_manager_view'); $response->success((new release_manager())->listAssignments()); @@ -300,6 +329,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/assignment-subjects', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/assignment-subjects'); global $response; $this->requirePermission('superuser_release_manager_manage'); $response->success((new release_manager())->searchAssignmentSubjects($this->getParametersAsArray())); @@ -308,6 +338,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/assignments', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/assignments'); global $response; $this->requirePermission('superuser_release_manager_manage'); try { @@ -320,6 +351,7 @@ class releaseManagerRoute ]); $this->delete('/superuser/releases/assignments/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/assignments/{id}'); global $response; $this->requirePermission('superuser_release_manager_manage'); try { @@ -332,6 +364,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/targets', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/targets'); global $response; $this->requirePermission('superuser_release_manager_deploy'); $response->success((new release_manager())->listDeploymentTargets()); @@ -340,6 +373,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/targets', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/targets'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -352,6 +386,7 @@ class releaseManagerRoute ]); $this->delete('/superuser/releases/targets/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/targets/{id}'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -364,6 +399,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/service-sets', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/service-sets'); global $response; $this->requirePermission('superuser_release_manager_view'); $response->success((new release_manager())->listServiceSets()); @@ -372,6 +408,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/service-sets', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/service-sets'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -384,6 +421,7 @@ class releaseManagerRoute ]); $this->delete('/superuser/releases/service-sets/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/service-sets/{id}'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -402,6 +440,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/service-sets/{id}/isolated-data-services', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/service-sets/{id}/isolated-data-services'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -421,6 +460,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/bundles', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/bundles'); global $response; $this->requirePermission('superuser_release_manager_view'); $limit = (int)($this->getParameter('limit') ?? 50); @@ -430,6 +470,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/bundles', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/bundles'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -442,6 +483,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/bundles/{id}/deploy', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/bundles/{id}/deploy'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -454,6 +496,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/bundles/{id}/promote', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/bundles/{id}/promote'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -466,6 +509,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/deployments', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/deployments'); global $response; $this->requirePermission('superuser_release_manager_view'); $limit = (int)($this->getParameter('limit') ?? 50); @@ -475,6 +519,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/deployments', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/deployments'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -487,6 +532,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/deployments/{id}/promote', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/deployments/{id}/promote'); global $response; $this->requirePermission('superuser_release_manager_deploy'); try { @@ -499,6 +545,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/issues/actions', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/issues/actions'); global $response; $this->requirePermission('superuser_release_manager_deploy'); $response->success((new release_manager())->runIssueAction($this->requestPayload(), $this->actorUserId())); @@ -507,6 +554,7 @@ class releaseManagerRoute ]); $this->post('/superuser/releases/replay-targets', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/replay-targets'); global $response; $this->requirePermission('superuser_release_manager_replay'); try { @@ -519,6 +567,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/timeline/sessions', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/timeline/sessions'); global $response; $this->requirePermission('superuser_release_manager_replay'); $response->success((new release_manager())->listTimelineSessions($this->getParametersAsArray())); @@ -527,6 +576,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/timeline/sessions/{traceId}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/timeline/sessions/{traceId}'); global $response; $this->requirePermission('superuser_release_manager_replay'); try { @@ -539,6 +589,7 @@ class releaseManagerRoute ]); $this->get('/superuser/releases/timeline', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/timeline'); global $response; $this->requirePermission('superuser_release_manager_replay'); $response->success((new release_manager())->searchTimeline($this->getParametersAsArray())); diff --git a/services/nginx/app/routes/sessionRoute.php b/services/nginx/app/routes/sessionRoute.php index e613d8e6..43a714d1 100644 --- a/services/nginx/app/routes/sessionRoute.php +++ b/services/nginx/app/routes/sessionRoute.php @@ -6,6 +6,9 @@ use classes\authentication; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class sessionRoute { use route_t; @@ -13,6 +16,7 @@ class sessionRoute public function run(): void { $this->get('/auth/session', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/auth/session'); // Require the user to be logged in global $response; $this->requirePermission('fetch_session'); diff --git a/services/nginx/app/routes/statisticsRoute.php b/services/nginx/app/routes/statisticsRoute.php index 1f220c8d..5e2b3b51 100644 --- a/services/nginx/app/routes/statisticsRoute.php +++ b/services/nginx/app/routes/statisticsRoute.php @@ -7,6 +7,8 @@ use classes\response; use classes\statistics; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; class statisticsRoute { @@ -16,6 +18,7 @@ class statisticsRoute { /** Statistics >> Bookings >> New bookings */ $this->get('/statistics/bookings/new', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/statistics/bookings/new'); // Require the user to be logged in global /** @var response $response */ diff --git a/services/nginx/app/routes/subusersRoute.php b/services/nginx/app/routes/subusersRoute.php index ac0ef504..80c78c7e 100644 --- a/services/nginx/app/routes/subusersRoute.php +++ b/services/nginx/app/routes/subusersRoute.php @@ -28,6 +28,9 @@ use modules\subusers\permissions\subusers_permission_nodes_vehicles; use modules\subusers\helpers\subusers_permission_node_key; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class subusersRoute { use route_t; @@ -1906,6 +1909,7 @@ class subusersRoute // Subuser Grant Management // ============================= $this->get('/subusers/grants', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_READ, '/subusers/grants'); global $response; /** Permissions (subuser-aware) */ $permission_own = self::definePermission('list_own_subuser_grants', subusers_permission_node_key::SUBUSERS_LIST); @@ -1988,6 +1992,7 @@ class subusersRoute ]); $this->post('/subusers/grants', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers/grants'); global $response; /** Permissions (subuser-aware) */ $permission_own = self::definePermission('add_own_subusers', subusers_permission_node_key::SUBUSERS_ADD); @@ -2045,6 +2050,7 @@ class subusersRoute ]); $this->put('/subusers/grants', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers/grants'); global $response; /** Permissions (subuser-aware) */ $permission_own = self::definePermission('edit_own_subusers', subusers_permission_node_key::SUBUSERS_EDIT); @@ -2120,6 +2126,7 @@ class subusersRoute ); $this->get('/subusers/permission-nodes', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_READ, '/subusers/permission-nodes'); global $response; $canUseGlobalManagement = $this->hasPermission('manage_subuser_grants') || $this->hasPermission('list_subusers') @@ -2165,6 +2172,7 @@ class subusersRoute ]); $this->get('/subusers/permission-templates', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_READ, '/subusers/permission-templates'); global $response; $canUseGlobalManagement = $this->hasPermission('manage_subuser_grants') || $this->hasPermission('list_subusers') @@ -2183,10 +2191,13 @@ class subusersRoute 'edit_subusers' => 'List chauffeur permission templates while editing chauffeur grants.', ]); + // Public registration endpoint — must stay publicly accessible (no + // scope check); new drivers cannot hold a scope before they exist. $this->post('/subusers', function () { $this->registerPublicSubuser(); }); $this->get('/subusers/setup', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_READ, '/subusers/setup'); // Require the user to be logged in global $response; self::requireParameters(['token']); @@ -2201,6 +2212,7 @@ class subusersRoute $response->success(['message' => 'Token is valid', 'subuser_id' => $subuser->id]); }); $this->post('/subusers/setup', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers/setup'); // Require the user to be logged in global $response; self::requireParameters(['token', 'password', 'name']); @@ -2297,6 +2309,7 @@ class subusersRoute }); $this->post('/subusers/password-reset/request', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers/password-reset/request'); global $response; $this->requireRecaptcha(); @@ -2351,6 +2364,7 @@ class subusersRoute }); $this->get('/subusers/password-reset/validate', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_READ, '/subusers/password-reset/validate'); global $response; self::requireParameters(['token']); $record = (new subuser_action_token_service())->inspect( @@ -2364,6 +2378,7 @@ class subusersRoute }); $this->post('/subusers/password-reset/set', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers/password-reset/set'); global $response; $this->requireRecaptcha(); self::requireParameters(['token', 'password']); @@ -2389,6 +2404,7 @@ class subusersRoute }); $this->get('/subusers/access-decision', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_READ, '/subusers/access-decision'); global $response; self::requireParameters(['token']); $record = (new subuser_action_token_service())->inspect((string)self::getParameter('token')); @@ -2425,6 +2441,7 @@ class subusersRoute }); $this->post('/subusers/access-decision', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers/access-decision'); global $response; self::requireParameters(['token']); $token = (string)self::getParameter('token'); @@ -2461,6 +2478,7 @@ class subusersRoute }); $this->post('/subusers/auth/password', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers/auth/password'); global $response; /** * Username types: @@ -2539,6 +2557,7 @@ class subusersRoute }); $this->post('/subusers/me/password', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers/me/password'); global $response; $subuser = (new authentication())->get_subuser(); if ($subuser === false) { @@ -2568,6 +2587,7 @@ class subusersRoute // Subusers - List & Get (with grant visibility) // ============================= $this->get('/superuser/subusers', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_READ, '/superuser/subusers'); global $response; $this->requirePermission('list_subusers'); $response->success($this->listSuperuserSubusers()); @@ -2576,6 +2596,7 @@ class subusersRoute ]); $this->patch('/superuser/subusers/{subuser_id}', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/superuser/subusers/{subuser_id}'); global $response; $this->requirePermission('edit_subusers'); @@ -2605,6 +2626,7 @@ class subusersRoute ]); $this->post('/superuser/subusers/{subuser_id}/verification/{channel}/send', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/superuser/subusers/{subuser_id}/verification/{channel}/send'); global $response; $this->requirePermission('edit_subusers'); @@ -2625,6 +2647,7 @@ class subusersRoute ]); $this->patch('/superuser/subusers/{subuser_id}/verification/{channel}', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/superuser/subusers/{subuser_id}/verification/{channel}'); global $response; $this->requirePermission('edit_subusers'); @@ -2658,6 +2681,7 @@ class subusersRoute ]); $this->post('/superuser/subusers/{subuser_id}/password', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/superuser/subusers/{subuser_id}/password'); global $response; $this->requirePermission('edit_subusers'); @@ -2691,6 +2715,7 @@ class subusersRoute ]); $this->post('/superuser/subusers/{subuser_id}/login-link', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/superuser/subusers/{subuser_id}/login-link'); global $response; $this->requirePermission('edit_subusers'); @@ -2708,6 +2733,7 @@ class subusersRoute ]); $this->post('/superuser/subusers/{subuser_id}/password-guide/{channel}/send', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/superuser/subusers/{subuser_id}/password-guide/{channel}/send'); $this->sendSuperuserSubuserDirectLoginLink('password_guide'); }, [ 'edit_subusers' => 'Send chauffeur password guide links as a superuser.', @@ -2715,6 +2741,7 @@ class subusersRoute ]); $this->post('/superuser/subusers/{subuser_id}/login-link/{channel}/send', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/superuser/subusers/{subuser_id}/login-link/{channel}/send'); $this->sendSuperuserSubuserDirectLoginLink('login_link'); }, [ 'edit_subusers' => 'Send chauffeur direct login links as a superuser.', @@ -2722,6 +2749,7 @@ class subusersRoute ]); $this->get('/superuser/users/{user_id}/subusers', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_READ, '/superuser/users/{user_id}/subusers'); global $response; $this->requirePermission('list_subusers'); $targetUser = $this->resolveSuperuserSubuserTargetUser($this->routePositiveInt('user_id')); @@ -2732,6 +2760,7 @@ class subusersRoute ]); $this->get('/superuser/users/{user_id}/subusers/summary', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_READ, '/superuser/users/{user_id}/subusers/summary'); global $response; $this->requirePermission('list_subusers'); $targetUser = $this->resolveSuperuserSubuserTargetUser($this->routePositiveInt('user_id')); @@ -2742,6 +2771,7 @@ class subusersRoute ]); $this->get('/subusers', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_READ, '/subusers'); global $response; $customerNumber = $this->requireManagedCustomerScope(subusers_permission_node_key::SUBUSERS_LIST); $customerName = $this->resolveCustomerName($customerNumber); @@ -2777,6 +2807,7 @@ class subusersRoute ]); $this->get('/subusers/me', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_READ, '/subusers/me'); global $response; $subuser = (new authentication())->get_subuser(); if ($subuser === false) { @@ -2787,6 +2818,7 @@ class subusersRoute }, []); $this->get('/subusers/me/verification', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_READ, '/subusers/me/verification'); global $response; $subuser = (new authentication())->get_subuser(); if ($subuser === false) { @@ -2797,6 +2829,7 @@ class subusersRoute }, []); $this->post('/subusers/me/verification/{channel}/send', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers/me/verification/{channel}/send'); global $response; $subuser = (new authentication())->get_subuser(); if ($subuser === false) { @@ -2816,6 +2849,7 @@ class subusersRoute }, []); $this->post('/subusers/me/verification/{channel}/verify', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers/me/verification/{channel}/verify'); global $response; $subuser = (new authentication())->get_subuser(); if ($subuser === false) { @@ -2838,6 +2872,7 @@ class subusersRoute }, []); $this->post('/subusers/{subuser_id}/verification/{channel}/send', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers/{subuser_id}/verification/{channel}/send'); global $response; $subuser = $this->loadSubuserOrFail($this->routePositiveInt('subuser_id')); @@ -2863,6 +2898,7 @@ class subusersRoute ]); $this->put('/subusers/me', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers/me'); global $response; $subuser = (new authentication())->get_subuser(); if ($subuser === false) { @@ -2922,6 +2958,7 @@ class subusersRoute }, []); $this->post('/superuser/subusers/invite', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/superuser/subusers/invite'); self::requireParameters(['customer_number']); $this->requirePermission('add_subusers'); $customerNumber = (int)self::getParameter('customer_number'); @@ -2932,6 +2969,7 @@ class subusersRoute ]); $this->post('/superuser/users/{user_id}/subusers/invite', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/superuser/users/{user_id}/subusers/invite'); $this->requirePermission('add_subusers'); $targetUser = $this->resolveSuperuserSubuserTargetUser($this->routePositiveInt('user_id')); $this->handleInviteSubuserForCustomer((int)$targetUser['customer_number']); @@ -2940,6 +2978,7 @@ class subusersRoute ]); $this->post('/subusers/invite', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers/invite'); $customerNumber = $this->requireManagedCustomerScope(subusers_permission_node_key::SUBUSERS_ADD); $this->handleInviteSubuserForCustomer($customerNumber); }, [ @@ -2947,6 +2986,7 @@ class subusersRoute ]); $this->post('/superuser/subusers/invite/resend', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/superuser/subusers/invite/resend'); global $response; $this->requirePermission('edit_subusers'); @@ -2982,6 +3022,7 @@ class subusersRoute ]); $this->post('/superuser/users/{user_id}/subusers/{subuser_id}/invite/resend', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/superuser/users/{user_id}/subusers/{subuser_id}/invite/resend'); $this->requirePermission('edit_subusers'); $targetUser = $this->resolveSuperuserSubuserTargetUser($this->routePositiveInt('user_id')); $this->resendInviteForScopedUser( @@ -2993,6 +3034,7 @@ class subusersRoute ]); $this->patch('/superuser/users/{user_id}/subusers/grants/{grant_id}', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/superuser/users/{user_id}/subusers/grants/{grant_id}'); $this->requirePermission('manage_subuser_grants'); $targetUser = $this->resolveSuperuserSubuserTargetUser($this->routePositiveInt('user_id')); $this->patchScopedSubuserGrant( @@ -3004,6 +3046,7 @@ class subusersRoute ]); $this->post('/subusers/invite/resend', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers/invite/resend'); global $response; self::requireParameters(['id']); @@ -3037,6 +3080,7 @@ class subusersRoute ]); $this->put('/subusers', function () { + ScopeMiddleware::requireScope(Scope::SUBUSER_WRITE, '/subusers'); global $response; $this->requireManagedCustomerScope(subusers_permission_node_key::SUBUSERS_EDIT); $response->error('Customers can only manage subuser grants. Drivers own their account profile.', 403); @@ -3044,6 +3088,8 @@ class subusersRoute 'edit_own_subusers' => 'Customers cannot edit chauffeur account profiles. They may only manage grants, permissions, and enabled state.', ]); // Public registration endpoint (alias of POST /subusers) matching OpenAPI: POST /subusers/me + // Must stay publicly accessible — no scope check here; new drivers do + // not hold a scope before they exist. $this->post('/subusers/me', function () { $this->registerPublicSubuser(); }); diff --git a/services/nginx/app/routes/superuserCoolifyRoute.php b/services/nginx/app/routes/superuserCoolifyRoute.php index 7cab8d70..29a2c8af 100644 --- a/services/nginx/app/routes/superuserCoolifyRoute.php +++ b/services/nginx/app/routes/superuserCoolifyRoute.php @@ -7,6 +7,9 @@ use classes\coolify_manager; use Throwable; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class superuserCoolifyRoute { use route_t; @@ -14,6 +17,7 @@ class superuserCoolifyRoute public function run(): void { $this->get('/superuser/coolify', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/coolify'); global $response; $this->requirePermission('superuser_coolify_view'); @@ -23,6 +27,7 @@ class superuserCoolifyRoute ]); $this->get('/superuser/coolify/load-balancer', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/coolify/load-balancer'); global $response; $this->requirePermission('superuser_coolify_view'); @@ -32,6 +37,7 @@ class superuserCoolifyRoute ]); $this->post('/superuser/coolify/load-balancer/reconcile', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/coolify/load-balancer/reconcile'); global $response; $this->requirePermission('superuser_coolify_manage'); @@ -53,6 +59,7 @@ class superuserCoolifyRoute ]); $this->post('/superuser/coolify/load-balancer/routes/deploy', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/coolify/load-balancer/routes/deploy'); global $response; $this->requirePermission('superuser_coolify_manage'); @@ -74,6 +81,7 @@ class superuserCoolifyRoute ]); $this->post('/superuser/coolify/load-balancer/api/deploy', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/coolify/load-balancer/api/deploy'); global $response; $this->requirePermission('superuser_coolify_manage'); @@ -98,6 +106,7 @@ class superuserCoolifyRoute ]); $this->get('/superuser/coolify/gateways', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/coolify/gateways'); global $response; $this->requirePermission('superuser_coolify_view'); @@ -107,6 +116,7 @@ class superuserCoolifyRoute ]); $this->post('/superuser/coolify/gateways', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/coolify/gateways'); global $response; $this->requirePermission('superuser_coolify_manage'); @@ -123,6 +133,7 @@ class superuserCoolifyRoute ]); $this->post('/superuser/coolify/gateways/{id}/test', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/coolify/gateways/{id}/test'); global $response; $this->requirePermission('superuser_coolify_manage'); @@ -139,6 +150,7 @@ class superuserCoolifyRoute ]); $this->post('/superuser/coolify/instances', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/coolify/instances'); global $response; $this->requirePermission('superuser_coolify_manage'); @@ -155,6 +167,7 @@ class superuserCoolifyRoute ]); $this->post('/superuser/coolify/instances/{id}/test', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/coolify/instances/{id}/test'); global $response; $this->requirePermission('superuser_coolify_manage'); @@ -164,6 +177,7 @@ class superuserCoolifyRoute ]); $this->get('/superuser/coolify/instances/{id}/placement', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/coolify/instances/{id}/placement'); global $response; $this->requirePermission('superuser_coolify_view'); @@ -177,6 +191,7 @@ class superuserCoolifyRoute ]); $this->get('/superuser/coolify/targets', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/coolify/targets'); global $response; $this->requirePermission('superuser_coolify_view'); @@ -187,6 +202,7 @@ class superuserCoolifyRoute ]); $this->post('/superuser/coolify/targets', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/coolify/targets'); global $response; $this->requirePermission('superuser_coolify_manage'); @@ -203,6 +219,7 @@ class superuserCoolifyRoute ]); $this->post('/superuser/coolify/targets/{id}/reconcile', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/coolify/targets/{id}/reconcile'); global $response; $this->requirePermission('superuser_coolify_reconcile'); @@ -220,6 +237,7 @@ class superuserCoolifyRoute ]); $this->post('/superuser/coolify/targets/{id}/deploy', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/coolify/targets/{id}/deploy'); global $response; $this->requirePermission('superuser_coolify_reconcile'); @@ -237,6 +255,7 @@ class superuserCoolifyRoute ]); $this->post('/superuser/coolify/targets/{id}/restart', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/coolify/targets/{id}/restart'); global $response; $this->requirePermission('superuser_coolify_reconcile'); @@ -254,6 +273,7 @@ class superuserCoolifyRoute ]); $this->post('/superuser/coolify/targets/{id}/failover', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/coolify/targets/{id}/failover'); global $response; $this->requirePermission('superuser_coolify_failover'); @@ -267,6 +287,7 @@ class superuserCoolifyRoute ]); $this->delete('/superuser/coolify/targets/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/coolify/targets/{id}'); global $response; $this->requirePermission('superuser_coolify_manage'); diff --git a/services/nginx/app/routes/superuserCustomerRuleProductRestrictionsRoute.php b/services/nginx/app/routes/superuserCustomerRuleProductRestrictionsRoute.php index 7923fde7..27eea51b 100644 --- a/services/nginx/app/routes/superuserCustomerRuleProductRestrictionsRoute.php +++ b/services/nginx/app/routes/superuserCustomerRuleProductRestrictionsRoute.php @@ -8,6 +8,9 @@ use classes\customer_rule_product_restriction_service; use Throwable; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class superuserCustomerRuleProductRestrictionsRoute { use route_t; @@ -15,6 +18,7 @@ class superuserCustomerRuleProductRestrictionsRoute public function run(): void { $this->get('/superuser/customer-rules/product-restrictions', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/customer-rules/product-restrictions'); global $response; $this->requireClassicSuperuserAnyPermission([ 'superuser_customer_rules_view', @@ -26,6 +30,7 @@ class superuserCustomerRuleProductRestrictionsRoute ]); $this->put('/superuser/customer-rules/product-restrictions/{attribute}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/customer-rules/product-restrictions/{attribute}'); global $response; $this->requireClassicSuperuserPermission('superuser_customer_rules_manage'); $attribute = trim((string)$this->fromRoute('attribute')); diff --git a/services/nginx/app/routes/superuserDepartmentRoute.php b/services/nginx/app/routes/superuserDepartmentRoute.php index 902f35bf..1cb03202 100644 --- a/services/nginx/app/routes/superuserDepartmentRoute.php +++ b/services/nginx/app/routes/superuserDepartmentRoute.php @@ -13,6 +13,9 @@ use objects\products_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class superuserDepartmentRoute { use route_t; @@ -20,6 +23,7 @@ class superuserDepartmentRoute public function run(): void { $this->get('/superuser/department', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/department'); // Require the user to be logged in global $response; $this->requirePermission('superuser_fetch_department'); @@ -55,6 +59,7 @@ class superuserDepartmentRoute ]); $this->put('/superuser/department/branding', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/department/branding'); global $response; $this->requirePermission('superuser_set_department_branding'); @@ -112,6 +117,7 @@ class superuserDepartmentRoute ]); $this->post('/superuser/department/prices', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/department/prices'); // Require the user to be logged in global $response; $this->requirePermission('superuser_set_department_prices'); @@ -174,6 +180,7 @@ class superuserDepartmentRoute ]); $this->get('/superuser/department/prices', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/department/prices'); // Require the user to be logged in global $response; $this->requirePermission('superuser_fetch_department_prices'); @@ -217,6 +224,7 @@ class superuserDepartmentRoute ]); $this->get('/superuser/department/customer-pricing', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/department/customer-pricing'); global $response; $this->requirePermission('superuser_fetch_department_customer_pricing'); @@ -240,6 +248,7 @@ class superuserDepartmentRoute ]); $this->put('/superuser/department/customer-pricing', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/department/customer-pricing'); global $response; $this->requirePermission('superuser_set_department_customer_pricing'); @@ -268,6 +277,7 @@ class superuserDepartmentRoute ]); $this->get('/superuser/department/variables', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/department/variables'); // Require the user to be logged in global $response; $this->requirePermission('superuser_fetch_department_variables'); @@ -296,6 +306,7 @@ class superuserDepartmentRoute ]); $this->post('/superuser/department/variables', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/department/variables'); // Require the user to be logged in global $response; $this->requirePermission('superuser_set_department_variables'); diff --git a/services/nginx/app/routes/superuserReplicationRoute.php b/services/nginx/app/routes/superuserReplicationRoute.php index 6b85797b..e5bdcace 100644 --- a/services/nginx/app/routes/superuserReplicationRoute.php +++ b/services/nginx/app/routes/superuserReplicationRoute.php @@ -6,6 +6,9 @@ use classes\authentication; use classes\replication_manager; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class superuserReplicationRoute { use route_t; @@ -15,6 +18,7 @@ class superuserReplicationRoute public function run(): void { $this->get('/superuser/replication', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/replication'); global $response; $this->requireClassicSuperuserPermission('superuser_replication_view'); @@ -25,6 +29,7 @@ class superuserReplicationRoute ]); $this->post('/superuser/replication/databases', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/replication/databases'); $this->requireClassicSuperuserPermission('superuser_replication_manage'); $this->rejectRetiredManagement(); }, [ @@ -32,6 +37,7 @@ class superuserReplicationRoute ]); $this->post('/superuser/replication/redis', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/replication/redis'); $this->requireClassicSuperuserPermission('superuser_replication_manage'); $this->rejectRetiredManagement(); }, [ @@ -39,6 +45,7 @@ class superuserReplicationRoute ]); $this->post('/superuser/replication/minio', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/replication/minio'); $this->requireClassicSuperuserPermission('superuser_replication_manage'); $this->rejectRetiredManagement(); }, [ @@ -46,6 +53,7 @@ class superuserReplicationRoute ]); $this->post('/superuser/replication/compose-template', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/replication/compose-template'); $this->requireClassicSuperuserPermission('superuser_replication_manage'); $this->rejectRetiredManagement(); }, [ @@ -53,6 +61,7 @@ class superuserReplicationRoute ]); $this->post('/superuser/replication/test-credentials', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/replication/test-credentials'); $this->requireClassicSuperuserPermission('superuser_replication_manage'); $this->rejectRetiredManagement(); }, [ @@ -60,6 +69,7 @@ class superuserReplicationRoute ]); $this->post('/superuser/replication/{kind}/{id}/test', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/replication/{kind}/{id}/test'); $this->requireClassicSuperuserPermission('superuser_replication_manage'); $this->rejectRetiredManagement(); }, [ @@ -67,6 +77,7 @@ class superuserReplicationRoute ]); $this->post('/superuser/replication/{kind}/{id}/provision', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/replication/{kind}/{id}/provision'); $this->requireClassicSuperuserPermission('superuser_replication_manage'); $this->rejectRetiredManagement(); }, [ @@ -74,6 +85,7 @@ class superuserReplicationRoute ]); $this->post('/superuser/replication/{kind}/{id}/promote', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/replication/{kind}/{id}/promote'); $this->requireClassicSuperuserPermission('superuser_replication_promote'); $this->rejectRetiredManagement(); }, [ @@ -81,6 +93,7 @@ class superuserReplicationRoute ]); $this->patch('/superuser/replication/{kind}/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/replication/{kind}/{id}'); $this->requireClassicSuperuserPermission('superuser_replication_manage'); $this->rejectRetiredManagement(); }, [ @@ -88,6 +101,7 @@ class superuserReplicationRoute ]); $this->delete('/superuser/replication/{kind}/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/replication/{kind}/{id}'); $this->requireClassicSuperuserPermission('superuser_replication_remove'); $this->rejectRetiredManagement(); }, [ diff --git a/services/nginx/app/routes/superuserSecurityRoute.php b/services/nginx/app/routes/superuserSecurityRoute.php index cf445a21..fbc65297 100644 --- a/services/nginx/app/routes/superuserSecurityRoute.php +++ b/services/nginx/app/routes/superuserSecurityRoute.php @@ -7,6 +7,9 @@ use classes\security_policy_service; use Throwable; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + require_once WD . '/classes/security_policy_service.php'; class superuserSecurityRoute @@ -16,6 +19,7 @@ class superuserSecurityRoute public function run(): void { $this->get('/superuser/system/security/summary', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/system/security/summary'); global $response; $this->requireClassicSuperuserPermission('superuser_security_view'); @@ -25,6 +29,7 @@ class superuserSecurityRoute ]); $this->get('/superuser/system/security/settings', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/system/security/settings'); global $response; $this->requireClassicSuperuserPermission('superuser_security_view'); @@ -35,6 +40,7 @@ class superuserSecurityRoute ]); $this->patch('/superuser/system/security/settings', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/system/security/settings'); global $response; $this->requireClassicSuperuserPermission('superuser_security_settings_manage'); @@ -51,6 +57,7 @@ class superuserSecurityRoute ]); $this->get('/superuser/system/security/firewall-rules', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/system/security/firewall-rules'); global $response; $this->requireClassicSuperuserPermission('superuser_security_view'); @@ -60,6 +67,7 @@ class superuserSecurityRoute ]); $this->post('/superuser/system/security/firewall-rules', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/system/security/firewall-rules'); global $response; $this->requireClassicSuperuserPermission('superuser_security_firewall_manage'); @@ -76,6 +84,7 @@ class superuserSecurityRoute ]); $this->patch('/superuser/system/security/firewall-rules/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/system/security/firewall-rules/{id}'); global $response; $this->requireClassicSuperuserPermission('superuser_security_firewall_manage'); @@ -93,6 +102,7 @@ class superuserSecurityRoute ]); $this->delete('/superuser/system/security/firewall-rules/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/system/security/firewall-rules/{id}'); global $response; $this->requireClassicSuperuserPermission('superuser_security_firewall_manage'); @@ -109,6 +119,7 @@ class superuserSecurityRoute ]); $this->get('/superuser/system/security/incidents', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/system/security/incidents'); global $response; $this->requireClassicSuperuserPermission('superuser_security_view'); @@ -118,6 +129,7 @@ class superuserSecurityRoute ]); $this->get('/superuser/system/security/incidents/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/system/security/incidents/{id}'); global $response; $this->requireClassicSuperuserPermission('superuser_security_view'); @@ -131,6 +143,7 @@ class superuserSecurityRoute ]); $this->patch('/superuser/system/security/incidents/{id}', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/system/security/incidents/{id}'); global $response; $this->requireClassicSuperuserPermission('superuser_security_incidents_manage'); @@ -148,6 +161,7 @@ class superuserSecurityRoute ]); $this->post('/superuser/system/security/incidents/{id}/notes', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/system/security/incidents/{id}/notes'); global $response; $this->requireClassicSuperuserPermission('superuser_security_incidents_manage'); diff --git a/services/nginx/app/routes/superuserSystemStatusRoute.php b/services/nginx/app/routes/superuserSystemStatusRoute.php index 181a15d9..d53ed7c4 100644 --- a/services/nginx/app/routes/superuserSystemStatusRoute.php +++ b/services/nginx/app/routes/superuserSystemStatusRoute.php @@ -5,6 +5,9 @@ namespace routes; use classes\superuser_system_status_service; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class superuserSystemStatusRoute { use route_t; @@ -12,6 +15,7 @@ class superuserSystemStatusRoute public function run(): void { $this->get('/superuser/system/status', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/system/status'); global $response; $this->requirePermission('superuser_system_status_view'); @@ -23,6 +27,7 @@ class superuserSystemStatusRoute ]); $this->get('/superuser/system/database/status', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/system/database/status'); global $response; $this->requirePermission('superuser_system_status_view'); diff --git a/services/nginx/app/routes/systemSearchRoute.php b/services/nginx/app/routes/systemSearchRoute.php index 27ad95c8..a3b749cd 100644 --- a/services/nginx/app/routes/systemSearchRoute.php +++ b/services/nginx/app/routes/systemSearchRoute.php @@ -10,6 +10,9 @@ use classes\system_search_service; use Throwable; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class systemSearchRoute { use route_t; @@ -17,14 +20,17 @@ class systemSearchRoute public function run(): void { $this->get('/search/system', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/search/system'); $this->handleSearchRequest(); }); $this->post('/search/system', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/search/system'); $this->handleSearchRequest(); }); $this->delete('/superuser/search/system/cache', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/search/system/cache'); global /** @var response $response */ $response; $this->requirePermission('superuser_search_system_cache_clear'); @@ -39,6 +45,7 @@ class systemSearchRoute ]); $this->post('/superuser/search/system/cache/rebuild', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/search/system/cache/rebuild'); global /** @var response $response */ $response; $this->requirePermission('superuser_search_system_cache_rebuild'); diff --git a/services/nginx/app/routes/uploadRoute.php b/services/nginx/app/routes/uploadRoute.php index 6560439c..8104802a 100644 --- a/services/nginx/app/routes/uploadRoute.php +++ b/services/nginx/app/routes/uploadRoute.php @@ -8,6 +8,9 @@ use classes\upload_store; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class uploadRoute { @@ -23,6 +26,7 @@ class uploadRoute public function run(): void { $this->post('/upload/image', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/upload/image'); global $response; // Get the uploaded file $file = $_FILES['file'] ?? null; @@ -50,6 +54,7 @@ class uploadRoute $response->success($uploads->getPresignedUrl($objectKey)); }); $this->get('/openai/test', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/openai/test'); global $response; // Validate the input parameters // Example image URL for testing diff --git a/services/nginx/app/routes/userInvoicesRoute.php b/services/nginx/app/routes/userInvoicesRoute.php index 95bdefe3..0918c407 100644 --- a/services/nginx/app/routes/userInvoicesRoute.php +++ b/services/nginx/app/routes/userInvoicesRoute.php @@ -7,6 +7,9 @@ use objects\collected_order_invoices_o; use objects\logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class userInvoicesRoute { use route_t; @@ -14,6 +17,7 @@ class userInvoicesRoute public function run(): void { $this->get('/user/invoices', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/user/invoices'); // Require the user to be logged in global $response; $this->requirePermission('user_invoices'); @@ -46,6 +50,7 @@ class userInvoicesRoute ); $this->put('/collected-invoices', function () { + ScopeMiddleware::requireScope(Scope::INVOICE_READ, '/collected-invoices'); // Require the user to be logged in global $response; $this->requirePermission('user_invoices'); diff --git a/services/nginx/app/routes/userNotificationsRoute.php b/services/nginx/app/routes/userNotificationsRoute.php index fa8d8fdc..5373d556 100644 --- a/services/nginx/app/routes/userNotificationsRoute.php +++ b/services/nginx/app/routes/userNotificationsRoute.php @@ -7,6 +7,9 @@ use objects\logs_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class userNotificationsRoute { use route_t; @@ -15,6 +18,7 @@ class userNotificationsRoute { $this->put('/account/notifications', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/account/notifications'); // Require the user to be logged in global $response; $this->requirePermission('user_notifications_update'); diff --git a/services/nginx/app/routes/userOrdersRoute.php b/services/nginx/app/routes/userOrdersRoute.php index 570e6bfa..66e41aec 100644 --- a/services/nginx/app/routes/userOrdersRoute.php +++ b/services/nginx/app/routes/userOrdersRoute.php @@ -7,6 +7,9 @@ use objects\logs_o; use objects\orders_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class userOrdersRoute { use route_t; @@ -14,6 +17,7 @@ class userOrdersRoute public function run(): void { $this->get('/user/orders', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/user/orders'); // Require the user to be logged in global $response; $this->requirePermission('list_own_orders'); @@ -48,6 +52,7 @@ class userOrdersRoute ); $this->get('/user/order', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/user/order'); // Require the user to be logged in global $response; $this->requirePermission('fetch_own_order'); diff --git a/services/nginx/app/routes/userRoute.php b/services/nginx/app/routes/userRoute.php index aa6b372a..825d0b54 100644 --- a/services/nginx/app/routes/userRoute.php +++ b/services/nginx/app/routes/userRoute.php @@ -9,6 +9,9 @@ use objects\logs_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class userRoute { use route_t; @@ -16,6 +19,7 @@ class userRoute public function run(): void { $this->get('/superuser/user', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/superuser/user'); // Require the user to be logged in global $response; $this->requirePermission('get_user'); @@ -52,6 +56,7 @@ class userRoute ); $this->get('/superuser/user/discounts', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/superuser/user/discounts'); // Require the user to be logged in global $response; $this->requirePermission('get_custom_prices_other'); @@ -86,6 +91,7 @@ class userRoute ); $this->post('/superuser/user/discounts', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/superuser/user/discounts'); // Require the user to be logged in global /** @var response $response */ $response; @@ -204,6 +210,7 @@ class userRoute ); $this->get('/admin/customer/getUserId', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/admin/customer/getUserId'); // Require the user to be logged in global $response; // Get the user object @@ -250,6 +257,7 @@ class userRoute ); $this->get('/admin/customer/name', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/admin/customer/name'); // Require the user to be logged in global $response; $this->requirePermission('get_user_name'); @@ -286,6 +294,7 @@ class userRoute ); $this->get('/superuser/user/keys', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/superuser/user/keys'); // Require the user to be logged in global $response; $this->requirePermission('get_user_keys'); @@ -336,6 +345,7 @@ class userRoute ); $this->post('/superuser/user/keys', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/superuser/user/keys'); // Require the user to be logged in global $response; $this->requirePermission('set_user_keys'); @@ -384,6 +394,7 @@ class userRoute ); $this->post('/superuser/user/password', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/superuser/user/password'); // Require the user to be logged in global $response; $this->requirePermission('set_user_password'); diff --git a/services/nginx/app/routes/userSecurityRoute.php b/services/nginx/app/routes/userSecurityRoute.php index dfe360d2..59172c0c 100644 --- a/services/nginx/app/routes/userSecurityRoute.php +++ b/services/nginx/app/routes/userSecurityRoute.php @@ -7,6 +7,9 @@ use objects\logs_o; use objects\tokens_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class userSecurityRoute { use route_t; @@ -14,6 +17,7 @@ class userSecurityRoute public function run(): void { $this->post('/account/security/change-email', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/account/security/change-email'); // Require the user to be logged in global $response; $this->requirePermission('user_security_change_email'); @@ -71,6 +75,7 @@ class userSecurityRoute ); $this->post('/account/security/validate-password', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/account/security/validate-password'); // Require the user to be logged in global $response; $this->requirePermission('user_security_validate_password'); @@ -100,6 +105,7 @@ class userSecurityRoute ); $this->post('/account/security/change-password', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/account/security/change-password'); // Require the user to be logged in global $response; $this->requirePermission('user_security_change_password'); @@ -140,6 +146,7 @@ class userSecurityRoute ); $this->post('/account/security/change-phone-number', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/account/security/change-phone-number'); // Require the user to be logged in global $response; $this->requirePermission('user_security_change_phone_number'); diff --git a/services/nginx/app/routes/usersRoute.php b/services/nginx/app/routes/usersRoute.php index 8b177fd5..c53a8174 100644 --- a/services/nginx/app/routes/usersRoute.php +++ b/services/nginx/app/routes/usersRoute.php @@ -8,6 +8,9 @@ use objects\logs_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class usersRoute { use route_t; @@ -15,6 +18,7 @@ class usersRoute public function run(): void { $this->get('/users', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/users'); // Require the user to be logged in global /** @var response $response */ $response; @@ -62,6 +66,7 @@ class usersRoute ); $this->get('/users/customer', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/users/customer'); // Require the user to be logged in global $response; $this->requirePermission('get_user_from_customer_number'); @@ -95,6 +100,7 @@ class usersRoute ); $this->post('/users', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/users'); // Require the user to be logged in global $response; $this->requirePermission('add_user'); @@ -149,6 +155,7 @@ class usersRoute ); $this->put('/users', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/users'); // Require the user to be logged in global $response; $this->requirePermission('edit_user'); @@ -239,6 +246,7 @@ class usersRoute ); $this->get('/public/employees', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/public/employees'); // This route is public, no authentication is required. global $response; // Get the users with the permission employee_public_data diff --git a/services/nginx/app/routes/vehicleAddonRoute.php b/services/nginx/app/routes/vehicleAddonRoute.php index 662cd036..bb641fc0 100644 --- a/services/nginx/app/routes/vehicleAddonRoute.php +++ b/services/nginx/app/routes/vehicleAddonRoute.php @@ -7,6 +7,9 @@ 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 @@ -16,6 +19,7 @@ class vehicleAddonRoute 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(); @@ -81,6 +85,7 @@ class vehicleAddonRoute ); $this->post('/vehicles/addons/toggle', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/vehicles/addons/toggle'); global $response; $auth = new authentication(); $user = $auth->get_user(); diff --git a/services/nginx/app/routes/vehiclesRoute.php b/services/nginx/app/routes/vehiclesRoute.php index 11bc592d..76bc0d2d 100644 --- a/services/nginx/app/routes/vehiclesRoute.php +++ b/services/nginx/app/routes/vehiclesRoute.php @@ -16,6 +16,9 @@ use objects\plate_scans_o; use objects\products_o; use objects\users_o; use traits\route_t; + +use app\auth\Scope; +use app\auth\ScopeMiddleware; use modules\subusers\helpers\subusers_permission_node_key; require_once WD . '/classes/security_policy_service.php'; @@ -346,6 +349,7 @@ class vehiclesRoute public function run(): void { $this->get('/vehicles', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/vehicles'); global $response; $auth = new authentication(); $user = $auth->get_user(); @@ -411,6 +415,7 @@ class vehiclesRoute ); $this->get('/department/vehicles/unknown-customer', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/vehicles/unknown-customer'); // Require the user to be logged in global $response; $this->requirePermission('list_unknown_customer_vehicles'); @@ -442,6 +447,7 @@ class vehiclesRoute ); $this->get('/superuser/users/{user_id}/vehicles', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/superuser/users/{user_id}/vehicles'); global $response; $this->requirePermission('list_vehicles_other'); $targetUser = $this->resolveSuperuserVehicleTargetUser($this->routePositiveInt('user_id')); @@ -452,6 +458,7 @@ class vehiclesRoute ]); $this->get('/superuser/users/{user_id}/vehicles/summary', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/superuser/users/{user_id}/vehicles/summary'); global $response; $this->requirePermission('list_vehicles_other'); $targetUser = $this->resolveSuperuserVehicleTargetUser($this->routePositiveInt('user_id')); @@ -462,6 +469,7 @@ class vehiclesRoute ]); $this->post('/superuser/users/{user_id}/vehicles', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/superuser/users/{user_id}/vehicles'); global $response; $this->requirePermission('add_vehicle_other'); $targetUser = $this->resolveSuperuserVehicleTargetUser($this->routePositiveInt('user_id')); @@ -472,6 +480,7 @@ class vehiclesRoute ]); $this->put('/superuser/users/{user_id}/vehicles', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/superuser/users/{user_id}/vehicles'); global $response; $this->requirePermission('edit_vehicle_other'); self::requireParameters(['id']); @@ -487,6 +496,7 @@ class vehiclesRoute ]); $this->delete('/superuser/users/{user_id}/vehicles', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/superuser/users/{user_id}/vehicles'); global $response; $this->requirePermission('delete_vehicle_other'); self::requireParameters(['id']); @@ -529,6 +539,7 @@ class vehiclesRoute ]); $this->post('/vehicles', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/vehicles'); global $response; $auth = new authentication(); $user = $auth->get_user(); @@ -626,6 +637,7 @@ class vehiclesRoute ); $this->put('/vehicles', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/vehicles'); global $response; $auth = new authentication(); $user = $auth->get_user(); @@ -803,6 +815,7 @@ class vehiclesRoute ); $this->delete('/vehicles', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/vehicles'); global $response; $auth = new authentication(); $user = $auth->get_user(); @@ -861,6 +874,7 @@ class vehiclesRoute ); $this->get('/department/vehicle/customer-suggestions', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/department/vehicle/customer-suggestions'); // Require the user to be logged in global $response; $this->requirePermission('list_vehicle_customer_suggestions'); @@ -923,6 +937,7 @@ class vehiclesRoute ); $this->post('/vehicles/set-auto-start-on-lpr', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/vehicles/set-auto-start-on-lpr'); global $response; $auth = new authentication(); $user = $auth->get_user(); @@ -958,6 +973,7 @@ class vehiclesRoute ); $this->post('/vehicles/set-vehicle-type-id', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_WRITE, '/vehicles/set-vehicle-type-id'); global $response; $auth = new authentication(); $user = $auth->get_user(); @@ -1006,6 +1022,7 @@ class vehiclesRoute $this->get('/superuser/users-with-vehicle-subscriptions', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/superuser/users-with-vehicle-subscriptions'); // Require the user to be logged in global $response; $this->requirePermission('list_users_with_vehicle_subscriptions'); @@ -1049,6 +1066,7 @@ class vehiclesRoute ); $this->get('/vehicles/status', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/vehicles/status'); // Require the user to be logged in global $response; $this->requirePermission('view_vehicle_status'); @@ -1124,6 +1142,7 @@ class vehiclesRoute ); $this->get('/vehicles/search', function () { + ScopeMiddleware::requireScope(Scope::CUSTOMER_READ, '/vehicles/search'); global $response; $this->requirePermission('search_vehicles'); /** diff --git a/services/nginx/app/routes/washCertificateDebugRoute.php b/services/nginx/app/routes/washCertificateDebugRoute.php index c650d29b..081ebe16 100644 --- a/services/nginx/app/routes/washCertificateDebugRoute.php +++ b/services/nginx/app/routes/washCertificateDebugRoute.php @@ -8,6 +8,8 @@ use objects\logs_o; use objects\order_bookings_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; /** * Debug route for diagnosing wash certificate delivery failures. @@ -31,6 +33,7 @@ class washCertificateDebugRoute public function run(): void { $this->get('/debug/wash-certificates/diagnose', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/debug/wash-certificates/diagnose'); global $response, $DEBUG; if (empty($DEBUG)) { diff --git a/services/nginx/app/routes/washCertificateRoute.php b/services/nginx/app/routes/washCertificateRoute.php index 97692e35..27910dfe 100644 --- a/services/nginx/app/routes/washCertificateRoute.php +++ b/services/nginx/app/routes/washCertificateRoute.php @@ -4,6 +4,9 @@ namespace routes; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class washCertificateRoute { use route_t; @@ -12,6 +15,7 @@ class washCertificateRoute { /** All bookings */ $this->get('/modules/washcertificates', function () { + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/modules/washcertificates'); // Set the working directory to /modules/washcertificates, // so the output directory is created in the correct location chdir(WD . '/modules/washcertificates'); diff --git a/services/nginx/app/routes/workerRoute.php b/services/nginx/app/routes/workerRoute.php index 10af1331..146ce669 100644 --- a/services/nginx/app/routes/workerRoute.php +++ b/services/nginx/app/routes/workerRoute.php @@ -18,6 +18,8 @@ use objects\product_options_o; use objects\products_o; use objects\users_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; class workerRoute { @@ -26,6 +28,7 @@ class workerRoute public function run(): void { $this->get('/worker/version', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/worker/version'); global /** @var router $router */ $response, $router; $response->success(['version' => redis->get('worker_target_version') ?? 'unknown'] ); diff --git a/services/nginx/app/routes/xlvaskUsageLogsRoute.php b/services/nginx/app/routes/xlvaskUsageLogsRoute.php index 6c7a9475..25cc6063 100644 --- a/services/nginx/app/routes/xlvaskUsageLogsRoute.php +++ b/services/nginx/app/routes/xlvaskUsageLogsRoute.php @@ -9,6 +9,9 @@ use objects\orders_o; use objects\xlvask_usage_logs_o; use traits\route_t; +use app\auth\Scope; +use app\auth\ScopeMiddleware; + class xlvaskUsageLogsRoute { use route_t; @@ -16,6 +19,7 @@ class xlvaskUsageLogsRoute public function run(): void { $this->get('/modules/xlvask/services/usage/orders', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/xlvask/services/usage/orders'); $permission_list_own = 'list_xlvask_usage_orders_own'; $permission_list_all = 'list_xlvask_usage_orders_all'; $response_includes_items = false; @@ -122,6 +126,7 @@ class xlvaskUsageLogsRoute ]); $this->get('/modules/xlvask/services/usage/orders/summary', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/xlvask/services/usage/orders/summary'); global $response; if (!$this->hasPermission('list_xlvask_usage_orders_all')) { $this->requirePermission('list_xlvask_usage_orders_own'); @@ -145,6 +150,7 @@ class xlvaskUsageLogsRoute ]); $this->patch('/modules/xlvask/services/usage/orders/{id}/ignore', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/xlvask/services/usage/orders/{id}/ignore'); global $response; $this->requirePermission('review_xlvask_usage_order'); $user = (new authentication())->get_user(); @@ -182,6 +188,7 @@ class xlvaskUsageLogsRoute ]); $this->post('/modules/xlvask/services/usage/orders/{id}/unignore', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/xlvask/services/usage/orders/{id}/unignore'); global $response; $this->requirePermission('review_xlvask_usage_order'); $user = (new authentication())->get_user(); @@ -209,6 +216,7 @@ class xlvaskUsageLogsRoute ]); $this->post('/modules/xlvask/services/usage/orders/{id}/accept', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/xlvask/services/usage/orders/{id}/accept'); global $response; $this->requirePermission('review_xlvask_usage_order'); $user = (new authentication())->get_user(); @@ -280,6 +288,7 @@ class xlvaskUsageLogsRoute ]); $this->post('/modules/xlvask/services/usage/orders/{id}/reject', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/modules/xlvask/services/usage/orders/{id}/reject'); global $response; $this->requirePermission('review_xlvask_usage_order'); $user = (new authentication())->get_user(); @@ -317,6 +326,7 @@ class xlvaskUsageLogsRoute ]); $this->get('/modules/xlvask/services/usage/orders/fast-link', function () { + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/modules/xlvask/services/usage/orders/fast-link'); global $response; $this->requirePermission('list_xlvask_usage_orders_own'); $user = (new authentication())->get_user(); diff --git a/services/nginx/app/tests/Integration/Auth/RouteScopeTest.php b/services/nginx/app/tests/Integration/Auth/RouteScopeTest.php new file mode 100644 index 00000000..6564a193 --- /dev/null +++ b/services/nginx/app/tests/Integration/Auth/RouteScopeTest.php @@ -0,0 +1,212 @@ +not->toBeEmpty(); + + foreach ($files as $f) { + $src = file_get_contents($f); + expect($src)->toContain('use app\\auth\\ScopeMiddleware;'); + expect($src)->toContain('use app\\auth\\Scope;'); + } +}); + +it('every protected route file contains at least one requireScope() call', function (): void { + $files = collect_protected_route_files(); + foreach ($files as $f) { + $src = file_get_contents($f); + $count = substr_count($src, 'ScopeMiddleware::requireScope'); + expect($count)->toBeGreaterThan(0, "no requireScope() call in $f"); + } +}); + +it('BrandingRoute is mixed (some public, some admin) and is excluded from the auto-list', function (): void { + // BrandingRoute has a public GET /branding and a private POST /branding + // variant. It doesn't fit the "all routes need a scope" rule, so + // the helper explicitly drops it. This test documents the + // exception so a future reader doesn't think the helper is wrong. + $files = collect_protected_route_files(); + foreach ($files as $f) { + expect(basename($f))->not->toBe('BrandingRoute.php'); + } +}); + +it('public route files do not require a scope', function (): void { + $public = [ + 'authRoute.php', + 'pingRoute.php', + 'optionsRoute.php', + 'formRoute.php', + 'guestRoute.php', + 'errorReportRoute.php', + 'vehiclePlateLookupRoute.php', + 'vehiclePlateLastOrdersRoute.php', + 'vehicleProductSuggestionRoute.php', + 'callbackMicrosoftRoute.php', + 'birdVoiceWebhooksRoute.php', + ]; + foreach ($public as $f) { + $path = app_path("routes/$f"); + if (!file_exists($path)) { + // some don't exist in this checkout — fine + continue; + } + $src = file_get_contents($path); + expect(substr_count($src, 'ScopeMiddleware::requireScope')) + ->toBe(0, "public route $f should not have scope checks"); + } +}); + +it('the cron routes require SUPERUSER_READ on GET and SUPERUSER_WRITE on writes', function (): void { + $src = file_get_contents(app_path('routes/cronRoute.php')); + // GET /superuser/cron (list view) must require SUPERUSER_READ + $patternGet = '/\$this->get\(\'\/superuser\/cron\'[^,]*,\s*function[^{]*\{[^{]*requireScope\(\s*Scope::SUPERUSER_READ/'; + $patternPost = '/\$this->post\(\'\/superuser\/cron\/run\'[^,]*,\s*function[^{]*\{[^{]*requireScope\(\s*Scope::SUPERUSER_WRITE/'; + expect($src)->toMatch($patternGet); + expect($src)->toMatch($patternPost); +}); + +it('the bookings routes require BOOKING_READ on GET and BOOKING_WRITE on writes', function (): void { + $src = file_get_contents(app_path('routes/bookingsRoute.php')); + $patternGet = '/\$this->get\(\'\/bookings\'[^,]*,\s*function[^{]*\{[^{]*requireScope\(\s*Scope::BOOKING_READ/'; + $patternPut = '/\$this->put\(\'\/bookings\'[^,]*,\s*function[^{]*\{[^{]*requireScope\(\s*Scope::BOOKING_WRITE/'; + expect($src)->toMatch($patternGet); + expect($src)->toMatch($patternPut); +}); + +it('the invoice routes require INVOICE_READ on GET and INVOICE_WRITE on writes', function (): void { + $src = file_get_contents(app_path('routes/invoicesRoute.php')); + $patternGet = '/\$this->get\(\'\/invoices\/draft\'[^,]*,\s*function[^{]*\{[^{]*requireScope\(\s*Scope::INVOICE_READ/'; + $patternPost = '/\$this->post\(\'\/invoices\/draft\/close\'[^,]*,\s*function[^{]*\{[^{]*requireScope\(\s*Scope::INVOICE_WRITE/'; + expect($src)->toMatch($patternGet); + expect($src)->toMatch($patternPost); +}); + +it('the subuser routes require SUBUSER_READ and SUBUSER_WRITE', function (): void { + $src = file_get_contents(app_path('routes/subusersRoute.php')); + expect($src)->toMatch('/requireScope\\(\\s*Scope::SUBUSER_READ/'); + expect($src)->toMatch('/requireScope\\(\\s*Scope::SUBUSER_WRITE/'); +}); + +it('end-to-end: GET /bookings with no scope returns 403 via the middleware', function (): void { + // Anonymous principal, anonymous response stub. + ScopeMiddleware::setTestPrincipal(null); + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/bookings'); + $resp = $GLOBALS['response']; + expect($resp->last_status)->toBe(403); + expect($resp->last_error)->toContain('Missing required scope'); +}); + +it('end-to-end: GET /bookings with BOOKING_READ passes', function (): void { + ScopeMiddleware::setTestPrincipal([Scope::BOOKING_READ]); + ScopeMiddleware::requireScope(Scope::BOOKING_READ, '/bookings'); + // No exception, response not modified. + $resp = $GLOBALS['response']; + expect($resp->last_status)->toBeNull(); +}); + +it('end-to-end: POST /admin/bookings/sync requires BOOKING_WRITE', function (): void { + ScopeMiddleware::setTestPrincipal([Scope::BOOKING_READ]); + ScopeMiddleware::requireScope(Scope::BOOKING_WRITE, '/admin/bookings/sync'); + $resp = $GLOBALS['response']; + expect($resp->last_status)->toBe(403); +}); + +it('end-to-end: superuser scope passes every scope check', function (): void { + ScopeMiddleware::setTestPrincipal(Scope::forRole('superuser')); + foreach ([Scope::BOOKING_READ, Scope::BOOKING_WRITE, Scope::INVOICE_WRITE, + Scope::SUBUSER_READ, Scope::CUSTOMER_WRITE, Scope::SUPERUSER_READ] as $s) { + ScopeMiddleware::requireScope($s, '/test'); + } + expect($GLOBALS['response']->last_status)->toBeNull(); +}); + +it('the audit documentation exists and covers the scope system', function (): void { + // The audit doc lives at the repo root, several levels up from + // this test. Walk up until we find it. + $candidates = [ + __DIR__ . '/../../../../documentation/auth/route-scope-audit.md', + __DIR__ . '/../../../../../documentation/auth/route-scope-audit.md', + __DIR__ . '/../../../../../../documentation/auth/route-scope-audit.md', + ]; + $abs = false; + foreach ($candidates as $c) { + if (file_exists($c)) { + $abs = $c; + break; + } + } + expect($abs)->not->toBeFalse('audit doc missing — checked: ' . implode(', ', $candidates)); + $src = file_get_contents($abs); + expect($src)->toContain('Scope::CUSTOMER_READ'); + expect($src)->toContain('Scope::SUPERUSER_WRITE'); + expect($src)->toContain('TRU-149'); +}); + +// --- Test helpers -------------------------------------------------------- + +function collect_protected_route_files(): array +{ + $base = app_path('routes'); + $protected = []; + foreach (scandir($base) ?: [] as $f) { + if (!str_ends_with($f, '.php')) continue; + $src = file_get_contents("$base/$f"); + if (!str_contains($src, '->get(') && !str_contains($src, '->post(') && !str_contains($src, '->put(') && !str_contains($src, '->delete(') && !str_contains($src, '->patch(')) { + continue; + } + if (str_contains($src, 'require_once') && !str_contains($src, 'use traits\\route_t;')) { + // stub file + continue; + } + // Public routes that we explicitly skip + $public = ['authRoute.php', 'pingRoute.php', 'optionsRoute.php', 'formRoute.php', + 'guestRoute.php', 'errorReportRoute.php', 'callbackMicrosoftRoute.php', + 'birdVoiceWebhooksRoute.php', 'vehiclePlateLookupRoute.php', + 'vehiclePlateLastOrdersRoute.php', 'vehicleProductSuggestionRoute.php', + 'permissionsRoute.php', 'rolesRoute.php', 'BrandingRoute.php']; + if (in_array($f, $public, true)) { + continue; + } + $protected[] = "$base/$f"; + } + return $protected; +} + +function make_stub_response(): object +{ + return new class { + public ?int $last_status = null; + public ?string $last_error = null; + public function error(mixed $data, ?int $status = null): void + { + $this->last_status = $status ?? 500; + $this->last_error = is_string($data) ? $data : (string)json_encode($data); + } + }; +} diff --git a/services/nginx/app/tests/Unit/Auth/ScopeMiddlewareTest.php b/services/nginx/app/tests/Unit/Auth/ScopeMiddlewareTest.php new file mode 100644 index 00000000..fba58d9a --- /dev/null +++ b/services/nginx/app/tests/Unit/Auth/ScopeMiddlewareTest.php @@ -0,0 +1,178 @@ +toContain(Scope::CUSTOMER_READ, Scope::CUSTOMER_WRITE); + expect($all)->toContain(Scope::BOOKING_READ, Scope::BOOKING_WRITE); + expect($all)->toContain(Scope::SUBUSER_READ, Scope::SUBUSER_WRITE); + expect($all)->toContain(Scope::INVOICE_READ, Scope::INVOICE_WRITE); + expect($all)->toContain(Scope::SUPERUSER_READ, Scope::SUPERUSER_WRITE); + expect(count($all))->toBe(10); +}); + +it('Scope::forRole() returns every scope for superuser', function (): void { + expect(Scope::forRole('superuser'))->toEqual(Scope::all()); +}); + +it('Scope::forRole() returns non-superuser scopes for admin', function (): void { + $admin = Scope::forRole('admin'); + expect($admin)->toContain(Scope::CUSTOMER_READ, Scope::INVOICE_READ); + expect($admin)->not->toContain(Scope::SUPERUSER_READ, Scope::SUPERUSER_WRITE); +}); + +it('Scope::forRole() returns read-only set for customer', function (): void { + $customer = Scope::forRole('customer'); + expect($customer)->toEqualCanonicalizing([ + Scope::CUSTOMER_READ, Scope::BOOKING_READ, Scope::INVOICE_READ, + ]); +}); + +it('Scope::forRole() returns booking scopes for subuser', function (): void { + $subuser = Scope::forRole('subuser'); + expect($subuser)->toEqualCanonicalizing([Scope::BOOKING_READ, Scope::BOOKING_WRITE]); +}); + +it('Scope::forRole() returns empty list for unknown role', function (): void { + expect(Scope::forRole('robot'))->toBe([]); +}); + +it('Scope::matches() handles exact equality', function (): void { + expect(Scope::matches('customer:read', 'customer:read'))->toBeTrue(); + expect(Scope::matches('customer:read', 'customer:write'))->toBeFalse(); +}); + +it('Scope::matches() supports global wildcard', function (): void { + expect(Scope::matches('*', 'customer:read'))->toBeTrue(); + expect(Scope::matches('*', 'invoice:write'))->toBeTrue(); +}); + +it('Scope::matches() supports resource wildcards', function (): void { + expect(Scope::matches('customer:*', 'customer:read'))->toBeTrue(); + expect(Scope::matches('customer:*', 'customer:write'))->toBeTrue(); + expect(Scope::matches('customer:*', 'booking:read'))->toBeFalse(); +}); + +it('Scope::matches() returns false on empty inputs', function (): void { + expect(Scope::matches('', 'customer:read'))->toBeFalse(); + expect(Scope::matches('customer:read', ''))->toBeFalse(); +}); + +it('hasScope() returns false for an unauthenticated principal', function (): void { + // No test principal set, no real auth possible. SUPERUSER_WRITE + // is never granted by default so this must be false. + expect(ScopeMiddleware::hasScope(Scope::SUPERUSER_WRITE))->toBeFalse(); +}); + +it('hasAnyScope() returns true when no scopes are required', function (): void { + // Defensive: passing [] means "no requirement" — should be a no-op. + expect(ScopeMiddleware::hasAnyScope([]))->toBeTrue(); +}); + +it('requireScope() does not throw when the scope is present', function (): void { + ScopeMiddleware::setTestPrincipal([Scope::SUPERUSER_READ]); + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/test'); + // No exception = pass. + expect(true)->toBeTrue(); +}); + +it('requireScope() emits 403 when the scope is missing', function (): void { + ScopeMiddleware::setTestPrincipal([Scope::CUSTOMER_READ]); + ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/test'); + $resp = $GLOBALS['response']; + expect($resp->last_status)->toBe(403); + expect($resp->last_error)->toContain('Missing required scope'); +}); + +it('requireAnyScope() passes if any of the required scopes match', function (): void { + ScopeMiddleware::setTestPrincipal([Scope::CUSTOMER_READ]); + ScopeMiddleware::requireAnyScope( + [Scope::CUSTOMER_READ, Scope::INVOICE_READ], + '/test' + ); + expect(true)->toBeTrue(); +}); + +it('requireAnyScope() emits 403 when none match', function (): void { + ScopeMiddleware::setTestPrincipal([Scope::CUSTOMER_READ]); + ScopeMiddleware::requireAnyScope( + [Scope::SUPERUSER_READ, Scope::SUPERUSER_WRITE], + '/test' + ); + $resp = $GLOBALS['response']; + expect($resp->last_status)->toBe(403); +}); + +it('requireRole() passes for the superuser role', function (): void { + ScopeMiddleware::setTestPrincipal(Scope::forRole('superuser')); + ScopeMiddleware::requireRole('superuser', '/test'); + expect(true)->toBeTrue(); +}); + +it('requireRole() rejects unknown roles with 403', function (): void { + ScopeMiddleware::setTestPrincipal(null); + ScopeMiddleware::requireRole('robot', '/test'); + $resp = $GLOBALS['response']; + expect($resp->last_status)->toBe(403); +}); + +it('requireRole() rejects when the role scope is missing', function (): void { + // customer role grants only 3 read scopes; superuser requires the + // full set including SUPERUSER_*. Since "any match" would + // accidentally pass on shared scopes, we test with a role whose + // scopes don't overlap at all with the requested role. + ScopeMiddleware::setTestPrincipal([Scope::BOOKING_READ, Scope::BOOKING_WRITE]); + ScopeMiddleware::requireRole('superuser', '/test'); + $resp = $GLOBALS['response']; + expect($resp->last_status)->toBe(403); +}); + +it('hasScope() is true for granted scope', function (): void { + ScopeMiddleware::setTestPrincipal([Scope::BOOKING_READ]); + expect(ScopeMiddleware::hasScope(Scope::BOOKING_READ))->toBeTrue(); + expect(ScopeMiddleware::hasScope(Scope::BOOKING_WRITE))->toBeFalse(); +}); + +it('hasScope() honours wildcard grants', function (): void { + ScopeMiddleware::setTestPrincipal(['*']); + expect(ScopeMiddleware::hasScope(Scope::BOOKING_READ))->toBeTrue(); + expect(ScopeMiddleware::hasScope(Scope::INVOICE_WRITE))->toBeTrue(); +}); + +// --- Test helpers -------------------------------------------------------- + +/** + * A tiny stub of the `response` class. The real one calls `exit;` + * after sending a response, so we use this shim to capture the last + * status code and body without exiting PHPUnit. + */ +function make_stub_response(): object +{ + return new class { + public ?int $last_status = null; + public ?string $last_error = null; + public function error(mixed $data, ?int $status = null): void + { + $this->last_status = $status ?? 500; + $this->last_error = is_string($data) ? $data : (string)json_encode($data); + } + }; +}