Skip to content

refactor: scope-based authorization (roles are scope bundles)#56

Open
PenguinzTech wants to merge 1 commit into
feature/nts-wire-interopfrom
feature/scope-bundle-authz
Open

refactor: scope-based authorization (roles are scope bundles)#56
PenguinzTech wants to merge 1 commit into
feature/nts-wire-interopfrom
feature/scope-bundle-authz

Conversation

@PenguinzTech

@PenguinzTech PenguinzTech commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Stacked on #55. Fourth branch in the chain.

Brings the manager API into line with the security standard: authorization decisions on scopes only, never role names. Roles are expanded into concrete resource:action scope bundles at token issuance.

Changes

  • app/services/scopes.py (new) — central ROLE_SCOPES bundles, expand_scopes()/scope_string(), SUPERADMIN_SCOPE sentinel. Bundles reproduce the pre-refactor access matrix exactly (SystemAdmin ⊇ OrgAdmin/UserManager privileges; Viewer read-only).
  • auth_service.create_access_token — emits the scope claim (external verifiers already read scope, so they now receive meaningful scopes).
  • middleware/auth.pyget_current_user exposes scope.
  • middleware/rbac.py — new requires_scope decorator; SystemAdmin bypasses in team/zone helpers now check the admin:super scope; can_manage_users/can_manage_teams check scopes; requires_system_admin/requires_admin repointed to scopes.
  • Migrated every endpoint decorator (users, dns_servers, teams, time, dhcp, ioc_feeds) from @requires_role(...)@requires_scope(...).
  • global_role/team_roles retained for audit + per-team membership only.

Access matrix (preserved)

Role Bundle
SystemAdmin every *:write/*:admin + admin:super + reads
OrgAdmin servers/teams/time/dhcp:write + reads
UserManager users:write + reads
Viewer reads only

Tests

New test_scope_authz.py (15): expansion, matrix preservation, scope emission on issued tokens, requires_scope 401/403/200 + any-of semantics, scope-based helpers. conftest token factory emits scope. Full manager suite: 108 passing. flake8 clean.

Documented in docs/ENTERPRISE_SECURITY.md.

🤖 Generated with Claude Code

Summary by Sourcery

Switch manager API authorization from role-based checks to scope-based bundles while preserving the existing access matrix.

New Features:

  • Introduce centralized scope-bundle definitions and role-to-scope expansion service, including a super-admin scope sentinel.
  • Add scope claims to issued access tokens and expose them through authentication middleware for downstream authorization checks.
  • Provide a scope-based authorization decorator for endpoints that gate access on concrete resource:action scopes rather than role names.

Enhancements:

  • Update RBAC helpers and admin checks to rely on scopes instead of global role names while retaining roles for audit and team membership.
  • Migrate all relevant manager endpoints from role-based decorators to scope-based authorization, aligning with the security standard.

Documentation:

  • Extend enterprise security documentation to describe scope-based authorization, role bundles, and their impact on endpoint access.

Tests:

  • Add a dedicated scope-authorization test suite covering role-to-scope expansion, token scope emission, decorator behavior, and matrix preservation.

Authorization decisions now use scopes only, never role names (security
standard). Roles are expanded into concrete resource:action scope bundles
at token issuance and carried in the token's scope claim.

- app/services/scopes.py: central ROLE_SCOPES bundles + expand_scopes()/
  scope_string(); SUPERADMIN_SCOPE sentinel. Bundles reproduce the prior
  access matrix exactly (SystemAdmin ⊇ OrgAdmin/UserManager; Viewer reads).
- auth_service.create_access_token: emits the scope claim.
- middleware/auth.py: get_current_user exposes scope.
- middleware/rbac.py: new requires_scope decorator; SystemAdmin bypasses
  (team/zone helpers) now check admin:super scope; can_manage_users/teams
  check scopes; requires_system_admin/requires_admin repointed to scopes.
- Migrated every endpoint decorator across users/dns_servers/teams/time/
  dhcp/ioc_feeds from @requires_role(...) to @requires_scope(...).
- global_role/team_roles retained for audit + per-team membership only.

Tests: new test_scope_authz.py (15) — expansion, matrix preservation,
scope emission, decorator 401/403/200, scope-based helpers. conftest token
factory emits scope. Full manager suite: 108 passing. flake8 clean.
Documented in docs/ENTERPRISE_SECURITY.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors manager authorization to be scope-based instead of role-name–based by introducing centralized scope bundles, emitting scopes in tokens, wiring middleware and helpers to read scopes, and migrating endpoint decorators and docs/tests accordingly, while preserving the existing access matrix.

Sequence diagram for scope-based authorization during request handling

sequenceDiagram
    actor User
    participant AuthService
    participant scopes_service as scopes_py
    participant AuthMiddleware as auth_token_required
    participant RBACMiddleware as rbac_requires_scope
    participant Endpoint

    User->>AuthService: create_access_token(user_id, username, global_role, team_roles)
    AuthService->>scopes_service: scope_string(global_role, team_roles)
    scopes_service-->>AuthService: "users:write servers:write ..."
    AuthService-->>User: JWT { scope, global_role, team_roles }

    User->>AuthMiddleware: HTTP request with Authorization header
    AuthMiddleware->>AuthMiddleware: decode JWT
    AuthMiddleware->>AuthMiddleware: build current_user with scope
    AuthMiddleware-->>RBACMiddleware: current_user available via get_current_user

    RBACMiddleware->>RBACMiddleware: requires_scope(required_scopes)
    RBACMiddleware->>RBACMiddleware: _user_scopes(current_user)
    alt no intersection
        RBACMiddleware-->>User: 403 Insufficient permissions
    else has required scope
        RBACMiddleware->>Endpoint: call protected handler
        Endpoint-->>User: 200 OK
    end
Loading

File-Level Changes

Change Details Files
Introduce centralized scope-bundle definitions and role→scope expansion utilities used at token issuance.
  • Add SUPERADMIN_SCOPE sentinel and ROLE_SCOPES mapping capturing the existing access matrix.
  • Implement expand_scopes() to derive sorted, de-duped scope bundles from a global role.
  • Implement scope_string() to produce RFC-style space-delimited scope claims for tokens.
manager/backend/app/services/scopes.py
Emit scope claims in access tokens and test tokens so middleware can authorize on scopes instead of role names.
  • Update create_access_token() to compute scope from global_role/team_roles via scope_string() and include it in the JWT payload.
  • Update test token factory to include a scope claim consistent with ROLE_SCOPES.
manager/backend/app/services/auth_service.py
manager/backend/tests/conftest.py
Expose token scopes in authentication middleware and add scope-based RBAC helpers and decorators.
  • Extend get_current_user payload to carry the raw scope string from tokens.
  • Add _user_scopes() and _is_superadmin() helpers that interpret scope values, replacing SystemAdmin role-name checks.
  • Introduce requires_scope() decorator that enforces any-of semantics over required scopes and returns structured 401/403 responses.
  • Rewrite can_manage_users(), can_manage_teams(), and team/zone access helpers to use scopes and SUPERADMIN_SCOPE instead of global_role comparisons.
  • Repoint requires_system_admin() and requires_admin() to be thin wrappers around scope checks.
manager/backend/app/middleware/auth.py
manager/backend/app/middleware/rbac.py
Migrate all manager API endpoints from role-based decorators to scope-based decorators aligned with the preserved access matrix.
  • Update DHCP endpoints to require dhcp:write or dhcp:admin depending on operation.
  • Update DNS server endpoints to require servers:write or servers:admin for destructive/admin actions.
  • Update user management endpoints to require users:write and users:admin where necessary.
  • Update IOC feeds endpoints to require ioc:admin instead of SystemAdmin role.
  • Update time server and sync endpoints to use time:write/time:admin scopes.
  • Update team management endpoints to use teams:write scopes for create/update/delete operations.
manager/backend/app/blueprints/dhcp.py
manager/backend/app/blueprints/dns_servers.py
manager/backend/app/blueprints/users.py
manager/backend/app/blueprints/ioc_feeds.py
manager/backend/app/blueprints/time.py
manager/backend/app/blueprints/teams.py
Document the scope-based authorization model and add tests that lock in scope bundles, token claims, and middleware behavior.
  • Extend ENTERPRISE_SECURITY.md with a description of scope-based authz, role bundles, and central scope definitions.
  • Add test_scope_authz.py covering ROLE_SCOPES behavior, SUPERADMIN_SCOPE, scope_string formatting, token scope emission, requires_scope 401/403/200 behavior including any-of semantics, and preservation of the pre-refactor access matrix.
  • Ensure the suite validates that global_role/team_roles remain for audit and per-team membership while scopes drive authz.
docs/ENTERPRISE_SECURITY.md
manager/backend/tests/test_scope_authz.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • In expand_scopes/scope_string, the team_roles parameter is accepted but never used; consider removing it or adding a brief comment at the call sites to avoid confusion about whether per-team roles should affect global scopes.
  • The repeated inline imports of scope_string in auth_service.create_access_token and the test token factory add indirection and could mask import-time issues; it would be clearer to move these imports to the module top level if circular dependencies are not a concern.
  • The _user_scopes helper assumes a space-delimited string and silently treats malformed scope values as an empty set; if tokens may be manipulated or evolve, consider adding minimal validation or logging when unexpected formats are encountered to ease debugging of authorization issues.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `expand_scopes`/`scope_string`, the `team_roles` parameter is accepted but never used; consider removing it or adding a brief comment at the call sites to avoid confusion about whether per-team roles should affect global scopes.
- The repeated inline imports of `scope_string` in `auth_service.create_access_token` and the test token factory add indirection and could mask import-time issues; it would be clearer to move these imports to the module top level if circular dependencies are not a concern.
- The `_user_scopes` helper assumes a space-delimited string and silently treats malformed `scope` values as an empty set; if tokens may be manipulated or evolve, consider adding minimal validation or logging when unexpected formats are encountered to ease debugging of authorization issues.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

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