Skip to content

[RBAC PR 3] Add configurable default-access role to RBAC#2233

Open
philipfweiss wants to merge 3 commits into
DataJunction:mainfrom
philipfweiss:rbac-default-access-role
Open

[RBAC PR 3] Add configurable default-access role to RBAC#2233
philipfweiss wants to merge 3 commits into
DataJunction:mainfrom
philipfweiss:rbac-default-access-role

Conversation

@philipfweiss

@philipfweiss philipfweiss commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Tracking: #2234 (step 3 of the RBAC enablement sequence).

Stacked on #2232.

With default_access_policy=restrictive, every reques is denied unless an explicit user or group grant matches. That leaves no way to express a baseline like "everyone can read" short of flipping the whole policy back to permissive or hand-granting every principal.

This adds a configurable default-access role:

  • New default_access_role setting (default None).
  • AuthContext.from_user loads the named role's scopes into AuthContext.default_scopes (empty when the setting is unset or the role does not exist).
  • RBACAuthorizationService resolves the principal's own scopes and the default-role scopes together; if none grant the request, it falls back to default_access_policy.
  • No behavior change by default: with no default role configured, authorization is unchanged.

How I verifie:

Run against the PR branch with a restrictive RBAC config and the seeded Roads data. The user dj is a normal non-admin.

1. Started the PR server, restrictive policy, no default role.

AUTHORIZATION_PROVIDER=rbac \
DEFAULT_ACCESS_POLICY=restrictive \
uvicorn datajunction_server.api.main:app --port 8010   # DB env omitted

GET /health/ -> HTTP_STATUS:200

2. Logged in as non-admin dj; baseline denies both read and write.

POST /basic/login/ -> HTTP_STATUS:200
GET  /whoami/ -> {"username":"dj","is_admin":false} HTTP_STATUS:200
GET  /nodes/default.avg_repair_price/ -> HTTP_STATUS:403
POST /nodes/source/ (default namespace) -> HTTP_STATUS:403
     {"message":"Access denied to 1 resource(s): default"}

3. Created a default-access role that grants read broadly.

POST /roles/
{
  "name": "global-viewer",
  "scopes": [
    {"action": "read", "scope_type": "node", "scope_value": "*"},
    {"action": "read", "scope_type": "namespace", "scope_value": "*"}
  ]
}
-> HTTP_STATUS:201

4. Restarted the server with the role wired in as the default.

AUTHORIZATION_PROVIDER=rbac \
DEFAULT_ACCESS_POLICY=restrictive \
DEFAULT_ACCESS_ROLE=global-viewer \
uvicorn datajunction_server.api.main:app --port 8010   # DB env omitted

5. Same non-admin user now gets fallback read, while writes stay denied.

POST /basic/login/ -> HTTP_STATUS:200
GET  /nodes/default.avg_repair_price/ -> HTTP_STATUS:200
GET  /nodes/default.repair_orders/    -> HTTP_STATUS:200
GET  /nodes/default.contractors/      -> HTTP_STATUS:200
POST /nodes/source/ (default namespace) -> HTTP_STATUS:403
     {"message":"Access denied to 1 resource(s): default"}

The read flips from 403 to 200 once the default role is configured; the write stays 403 because the role grants only READ. Unsetting default_access_role, or pointing it at a role that does not exist, loads no scopes and falls back to default_access_policy.

Thread is_admin into AuthContext and short-circuit RBAC authorization for
admins, approving all requests. The bypass is a single explicit check and
is logged for audit, so it is easy to find and to later scope down if
admins should still respect some constraints.
@netlify

netlify Bot commented Jun 5, 2026

Copy link
Copy Markdown

Deploy Preview for thriving-cassata-78ae72 canceled.

Name Link
🔨 Latest commit 76df04f
🔍 Latest deploy log https://app.netlify.com/projects/thriving-cassata-78ae72/deploys/6a4c4b703da1fc00084c55e9

@philipfweiss philipfweiss changed the title Add configurable default-access role to RBAC [RBAC PR 3] Add configurable default-access role to RBAC Jul 6, 2026
@philipfweiss philipfweiss force-pushed the rbac-default-access-role branch from 6da138a to e44c260 Compare July 6, 2026 23:34
Add a DEFAULT_ACCESS_ROLE setting whose scopes are evaluated as a fallback
when no explicit grant matches, so deployments can express graceful
defaults (e.g. read on *) without flipping the whole policy to permissive.

The default role's scopes are pre-loaded into AuthContext and evaluated
together with the principal's own scopes: _make_decision now gathers all
candidate scopes (explicit grants + default role) and resolves them in one
step, then falls back to default_access_policy. Collecting candidates
before deciding (rather than a nested allow-ladder) keeps the door open for
future deny/precedence rules.
@philipfweiss philipfweiss force-pushed the rbac-default-access-role branch 2 times, most recently from 6da138a to 64822f3 Compare July 7, 2026 00:15
@philipfweiss philipfweiss marked this pull request as ready for review July 7, 2026 00:42

@shangyian shangyian left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚢

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.

2 participants