Skip to content

Persisted Actor Role Operating Model

Rodrigo Silveira edited this page Jul 7, 2026 · 1 revision
  1. Confirm the bootstrap administrator still works

Start the application and open:

http://localhost:5173/admin/authorization

Confirm:

The page loads without an authorization error. bootstrap-admin appears as an active persisted actor. It has the APPLICATION_ADMIN role with tenant scope *. The page identifies it as the currently operating actor. Effective permissions include *. The current actor cannot deactivate itself.

Also call:

curl -sS
-H "X-Actor-ID: bootstrap-admin"
-H "X-Tenant-ID: default"
http://localhost:8080/api/v1/authz/current-actor | python3 -m json.tool

Expected:

HTTP 200 actor key bootstrap-admin active actor role APPLICATION_ADMIN effective scope * 2. Confirm header-supplied wildcard permissions no longer bypass persisted actors

Use an actor that does not exist:

curl -sS -i
-H "X-Actor-ID: bite27b-fake-admin"
-H "X-Tenant-ID: default"
-H "X-Actor-Permissions: *"
http://localhost:8080/api/v1/authz/current-actor

Expected:

HTTP 401

The response should identify the actor as missing, unknown, or inactive.

This is the most important Bite 27B test. X-Actor-Permissions: * must not turn an unknown actor into an administrator.

  1. Confirm the frontend no longer sends wildcard permissions

Open the browser developer tools:

Select the Network tab. Reload the Authorization page. Inspect requests to /api/v1/....

Expected request headers:

X-Actor-ID: bootstrap-admin X-Tenant-ID: default

The request must not contain:

X-Actor-Permissions: *

Repeat this check on:

/admin/reference-data /expenses /work-periods /receipts/outstanding 4. Create a temporary tenant administrator

In the Authorization page, create an actor such as:

Actor key: bite27b-tenant-admin Display name: Bite 27B Tenant Admin Active: Yes

Grant:

Role: TENANT_ADMIN Tenant: default

Confirm the actor appears active and its effective role and tenant scope are shown correctly.

Switch the operating actor to bite27b-tenant-admin.

Expected:

/api/v1/authz/current-actor identifies the new actor. Tenant scope is default. It can open tenant administrative screens. It does not gain global * scope. It cannot administer another tenant through a forged X-Tenant-ID.

Return to bootstrap-admin after this test.

  1. Test an operational role

Create:

Actor key: bite27b-expense-operator Display name: Bite 27B Expense Operator

Grant:

Role: EXPENSE_OPERATOR Tenant: default

Switch to that actor.

Expected allowed behavior:

Open the New Expense page. Read collaborators needed for expense preparation. Read Reference Data required by the expense form. Read the Current Account information allowed to the Expenses role. Create an expense, using disposable local test data.

Expected denied behavior:

Cannot open or operate the Authorization administration page. Cannot grant or revoke roles. Cannot update second-person approval policy. Cannot close a Journey. Cannot perform unrestricted sensitive settlement operations.

A denied action should return 403, not 401, because this is a valid persisted actor with insufficient permission.

  1. Test actor deactivation

Return to bootstrap-admin.

Deactivate bite27b-expense-operator.

Then call:

curl -sS -i
-H "X-Actor-ID: bite27b-expense-operator"
-H "X-Tenant-ID: default"
http://localhost:8080/api/v1/authz/current-actor

Expected:

HTTP 401

Also confirm that switching to the inactive actor is no longer offered in the UI.

Reactivate the actor and repeat the request.

Expected after reactivation:

HTTP 200

Its original persisted role grant should remain effective unless you explicitly removed the grant.

  1. Test self-lockout protection

Switch to bite27b-tenant-admin.

Attempt to:

deactivate bite27b-tenant-admin; revoke its own active role grant.

Expected:

The operation is rejected. The actor remains active. Its role grant remains present. The response is a validation or conflict error, normally 400 or 409, rather than an unexpected 500.

Return to bootstrap-admin.

  1. Test last Application Administrator protection

Create a second application administrator:

Actor key: bite27b-backup-app-admin Role: APPLICATION_ADMIN Tenant scope: *

Confirm it can be selected as the operating actor and has global scope.

While operating as bootstrap-admin, test that the backup administrator can be deactivated and reactivated because another active Application Administrator remains.

Then ensure there is only one active Application Administrator and attempt to:

deactivate that final active Application Administrator; revoke its global APPLICATION_ADMIN grant.

Expected:

Both operations are rejected. ERS retains at least one active persisted Application Administrator. No administrator lockout occurs.

Leave both administrators active when the test is complete.

  1. Test role-scope validation

Attempt these invalid grants through the Authorization UI.

Invalid Application Administrator scope Role: APPLICATION_ADMIN Tenant: default

Expected: rejected because Application Administrator requires scope *.

Invalid tenant role scope Role: TENANT_ADMIN Tenant: *

Expected: rejected because tenant roles require a specific tenant.

Repeat with an operational tenant role, such as:

EXPENSE_OPERATOR + tenant *

Expected: rejected.

Then verify these valid grants:

APPLICATION_ADMIN + * TENANT_ADMIN + default EXPENSE_OPERATOR + default PERSON + default 10. Confirm current-actor permission reporting

For each test actor, call:

curl -sS
-H "X-Actor-ID: "
-H "X-Tenant-ID: default"
http://localhost:8080/api/v1/authz/current-actor | python3 -m json.tool

Confirm the response accurately reports:

actor key; persisted actor ID; active roles; selected tenant; effective scope; effective permissions; linked Person or Collaborator IDs, when present.

Permissions should come from persisted grants, not from caller-provided permission headers.

  1. Confirm malformed and missing identities fail safely

Missing actor:

curl -sS -i
-H "X-Tenant-ID: default"
http://localhost:8080/api/v1/tenants/current

Expected: 401.

Unknown actor:

curl -sS -i
-H "X-Actor-ID: does-not-exist"
-H "X-Tenant-ID: default"
http://localhost:8080/api/v1/tenants/current

Expected: 401.

Active actor without permission:

curl -sS -i
-H "X-Actor-ID: bite27b-expense-operator"
-H "X-Tenant-ID: default"
http://localhost:8080/api/v1/authz/actors

Expected: 403.

  1. Confirm health checks remain public curl -sS -i http://localhost:8080/api/v1/healthz curl -sS -i http://localhost:8080/healthz

Expected: 200 without authorization headers.

  1. Final browser regression pass

As bootstrap-admin, confirm these pages still load and function:

/admin/authorization /admin/reference-data /admin/current-account-settings /admin/audit-logs /people /collaborators /work-periods /expenses /receipts/outstanding

Also complete one harmless normal workflow:

Open a collaborator. Open Current Account. Open an existing receipt. Open Work Period planning. Open New Expense without submitting a permanent test expense.

Clone this wiki locally