Skip to content

Map and enforce scopes based on permissions - To be parked and open for discussion #7035

@Zaimwa9

Description

@Zaimwa9

Context

We need a scope layer for OAuth tokens without duplicating the permission system.

We map scopes to existing RBAC permissions.

A new OAuthScopePermission class checks whether the token's scopes cover the view's required RBAC permission. Unmapped permissions are denied by default


Post grooming

  • One global scope that ultimately ties to the user permissions
  • No mapping here and park it for later

Enforcement rule

Effective permissions is the intersection between the user permissions and scopes granted

What to do

  • Define the SCOPE_PERMISSION_MAP:
    SCOPE_PERMISSION_MAP = {
        "flag:read": {VIEW_PROJECT, VIEW_FEATURE_STATE, VIEW_ENVIRONMENT},
        "flag:write": {CREATE_FEATURE, UPDATE_FEATURE_STATE, DELETE_FEATURE},
        # ...
    }
  • Build OAuthScopePermission class:
    • If request is not an OAuth token → return True (no-op for session users)
    • If the view has no declared RBAC permission → deny (default deny)
    • Look up which scopes cover the required RBAC permission → check the token has one
  • Add OAuthScopePermission to the permission classes:
    permission_classes = [IsAuthenticated, OAuthScopePermission, ProjectPermission]
  • Define scope list in DOT settings (OAUTH2_PROVIDER["SCOPES"])

Scope-to-permission mapping

Scope RBAC Permissions MCP Endpoints
organisation:read (org membership) GET /organisations/, /organisations/{id}/groups/, /organisations/{id}/invites/
organisation:write CREATE_PROJECT, MANAGE_USER_GROUPS POST /organisations/{id}/invites/
project:read VIEW_PROJECT, MANAGE_TAGS GET /organisations/{id}/projects/, /projects/{id}/, /projects/{id}/environments/
project:write CREATE_ENVIRONMENT PUT /projects/{id}/
environment:read VIEW_ENVIRONMENT GET /environments/
environment:write MANAGE_SEGMENT_OVERRIDES
flag:read VIEW_PROJECT, VIEW_ENVIRONMENT GET /features/, /features/{id}/, /evaluation-data/, /code-references/, /feature-external-resources/, /mv-options/, /versions/, /featurestates/, /change-requests/, /list-change-requests/
flag:write CREATE_FEATURE, EDIT_FEATURE, DELETE_FEATURE, UPDATE_FEATURE_STATE, CREATE_CHANGE_REQUEST, APPROVE_CHANGE_REQUEST, MANAGE_PROJECT_LEVEL_CHANGE_REQUESTS, APPROVE_PROJECT_LEVEL_CHANGE_REQUESTS, CREATE_PROJECT_LEVEL_CHANGE_REQUESTS POST/PUT/DELETE on features, MV options, versions, feature states, change requests
segment:read VIEW_PROJECT GET /segments/, /segments/{id}/
segment:write MANAGE_SEGMENTS POST /segments/, PUT /segments/{id}/
identity:read VIEW_IDENTITIES
identity:write MANAGE_IDENTITIES
release_pipeline:read VIEW_PROJECT GET /release-pipelines/, /release-pipelines/{id}/
release_pipeline:write (project admin) POST /release-pipelines/{id}/add-feature/
feature_health:read VIEW_PROJECT GET /feature-health/events/
audit_log:read VIEW_AUDIT_LOG
webhook:read (env/project membership)
webhook:write (env/project admin)
role:read (org admin)
role:write (org admin)
integration:read (project/env membership)
integration:write (project/env admin)
SCOPE_PERMISSION_MAP = {
    # Organisation
    "organisation:read": set(),  # org membership check
    "organisation:write": {CREATE_PROJECT, MANAGE_USER_GROUPS},
    # Project
    "project:read": {VIEW_PROJECT, MANAGE_TAGS},
    "project:write": {CREATE_ENVIRONMENT},
    # Environment
    "environment:read": {VIEW_ENVIRONMENT},
    "environment:write": {MANAGE_SEGMENT_OVERRIDES},
    # Flags (includes change requests)
    "flag:read": {VIEW_PROJECT, VIEW_ENVIRONMENT},
    "flag:write": {
        CREATE_FEATURE, EDIT_FEATURE, DELETE_FEATURE, UPDATE_FEATURE_STATE,
        CREATE_CHANGE_REQUEST, APPROVE_CHANGE_REQUEST,
        MANAGE_PROJECT_LEVEL_CHANGE_REQUESTS, APPROVE_PROJECT_LEVEL_CHANGE_REQUESTS,
        CREATE_PROJECT_LEVEL_CHANGE_REQUESTS,
    },
    # Segments
    "segment:read": {VIEW_PROJECT},
    "segment:write": {MANAGE_SEGMENTS},
    # Identities
    "identity:read": {VIEW_IDENTITIES},
    "identity:write": {MANAGE_IDENTITIES},
    # Release pipelines
    "release_pipeline:read": {VIEW_PROJECT},
    "release_pipeline:write": set(),  # project admin check
    # Feature health
    "feature_health:read": {VIEW_PROJECT},
    # Audit log
    "audit_log:read": {VIEW_AUDIT_LOG},
    # Webhooks
    "webhook:read": set(),  # env/project membership check
    "webhook:write": set(),  # env/project admin check
    # Roles
    "role:read": set(),  # org admin check
    "role:write": set(),  # org admin check
    # Integrations
    "integration:read": set(),  # project/env membership check
    "integration:write": set(),  # project/env admin check
}

Definition of done

  • Scope enforcement active for OAuth tokens
  • Session users unaffected
  • Unmapped RBAC permissions are denied for OAuth tokens
  • A token with the relevant scopes can access the resources (e.g flag:read can read features but not create them)
  • A token without the required scope gets 403 insufficient_scope

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions