Skip to content

fix(iam): close authorization gaps in core controllers - #278

Merged
roncodes merged 2 commits into
release/v1.6.65from
fix/permission-enforcement
Sep 28, 2026
Merged

roncodes merged 2 commits into
release/v1.6.65from
fix/permission-enforcement

Conversation

@roncodes

@roncodes roncodes commented Sep 27, 2026 •

Copy link
Copy Markdown
Member

Summary

An authorization audit found several core endpoints that skip permission checks or resolve to permission names that don't exist, so AuthorizationGuard let every user through.

Area Before After
Organization update (PUT companies/{id}) Any member could update the organization, including owner_uuid (verified locally: a dispatcher made themselves owner), Stripe ids, plan and status Requires the owner, the Administrator role or a system admin. owner_uuid, Stripe ids, plan, status, trial_ends_at and type are ignored for non-admin updates; ownership still moves through the transfer endpoint
Organization 2FA policy Any member could disable enforcement Owner, Administrator role or system admin
System 2FA policy (POST two-fa/config) Any signed-in user could change it for every organization System admins only (reading it stays open; the account and settings pages use it)
Admin platform metrics Any user could read platform-wide figures System admins only
IAM and developer metrics Unchecked iam list user / developers list api-key
Reports ReportController resolved to the fleetbase service, so no permission matched and every report endpoint was open iam service. Direct query execution requires iam execute report; export and download require iam export report
API credentials, webhooks, API events, request logs Resolved to api-credential, webhook-endpoint, … which no permission uses, so they were unguarded (any user could list and create API keys) Controllers declare $permissionResource (api-key, webhook, event, log), matching the Developers schema and the dev console

How

  • HasApiControllerBehavior::getPermissionResourceName() and Auth::getPermissionResourceFromController() let a controller name the schema resource its permissions use. They default to the model's singular name, so nothing else changes.
  • Auth::cannotUnlessAdmin() supports explicit checks outside the guard.
  • The new checks are registered as controller middleware scoped with ->only(), so behaviour through the router changes and existing unit tests that call methods directly still exercise the endpoint logic.

Behaviour changes to note

Test plan

  • New tests: permission resource alias resolution; cannotUnlessAdmin; organization-manager middleware (member refused; owner, Administrator role and system admin allowed); owner_uuid/status ignored on update; 2FA and report middleware registration and refusal.
  • tests/Unit: 1831 passed. One failure, UtilsTest › utils reads composer package keywords, also fails on main locally.
  • Staging: an org member without the Administrator role gets 401 on PUT companies/{id}; a dispatcher gets 401 on GET api-credentials.

- Organization settings: any member could update the company, including
  owner_uuid (take ownership) and billing/lifecycle fields, and change the
  organization 2FA policy. Updates and the 2FA policy now require the owner,
  the Administrator role or a system admin; owner, Stripe ids, plan, status,
  trial and type are ignored for non-admin updates (ownership keeps its
  transfer endpoint).
- System-wide 2FA policy save is restricted to system admins.
- Admin platform metrics are restricted to system admins; IAM and developer
  metrics require iam list user / developers list api-key.
- Reports resolved to the "fleetbase" service, so no permission ever matched
  and every report endpoint was open. ReportController now uses the iam
  service, and direct query execution/export/download require iam
  execute/export report.
- API credentials, webhooks, API events and request logs resolved to
  resource names (api-credential, webhook-endpoint, ...) that no permission
  uses, leaving them unguarded. Controllers can now declare
  $permissionResource, used by Auth when resolving permissions; these map to
  the Developers schema resources (api-key, webhook, event, log).
- Auth::cannotUnlessAdmin() for explicit checks outside AuthorizationGuard.
@roncodes
roncodes merged commit 0a084e1 into release/v1.6.65 Sep 28, 2026
2 of 3 checks passed
@roncodes
roncodes deleted the fix/permission-enforcement branch September 28, 2026 03:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant