Skip to content

Role-based access control

cadence:* permissions, BuiltInRBACProvider, Redis cache, and route dependencies.

Intended audience: Stakeholders, Business analysts, Solution architects, Developers, Testers

Learning outcomes by role

Stakeholders

  • Relate roles and permissions to auditability and separation of duties for platform versus org admins.

Business analysts

  • Map personas to roles and expected 403 outcomes when permissions are missing.

Solution architects

  • Explain Postgres plus Redis caching for RBAC and implications for stale permission windows.

Developers

  • Use permission constants, BuiltInRBACProvider, and route dependencies (roles_allowed) consistently.

Testers

  • Build matrices of roles versus endpoints using cadence:* strings and wildcard behavior.

Users receive roles; roles carry permission strings (for example cadence:org:read). Handlers and dependencies evaluate those strings before sensitive work. Map personas to roles and expect 403 when a permission is missing; implementation centers on permission constants, BuiltInRBACProvider loading and cache, and HTTP dependencies in authorization middleware.

  • Governance — Permissions are explicit cadence:* strings attached to roles; platform-wide power is concentrated in cadence:system:* capabilities.
  • Personas — Map product roles (org member, org admin, sys admin) to cadence:* sets when writing acceptance criteria for 403 versus wrong org.
  • Audit — Permission changes should trigger cache invalidation expectations in defect reports when behavior lags edits.

Cadence RBAC uses PostgreSQL for roles and assignments and Redis to cache computed permission sets (see BuiltInRBACProvider TTL and authz:perms:* keys). Interactive (JWT) sessions store global_permissions and org_permissions in Redis when the session is created or refreshed. API keys use a flat scope list from the key row, with the riskiest platform permissions removed automatically.

Constants live in cadence.core.authorization.permissions. Examples:

  • System: cadence:system:admin, cadence:system:settings:read, …
  • Org: cadence:org:read, cadence:org:orchestrators:write, …
  • User/chat: cadence:chat:use, cadence:profile:write, …

SYSTEM_ADMIN (cadence:system:admin) in global_permissions marks a platform superuser (BearerTokenSession.is_sys_admin).

Wildcard expansionexpand_wildcard_permissions and has_permission implement hierarchical checks (see has_permission in the permissions module).

BuiltInRBACProvider:

  • Loads user → role assignments via UserRoleRepository.list_for_user.
  • For each assignment, loads role → permission strings via RoleRepository.list_permissions_for_role.
  • Merges global (org_id is None) vs org-scoped rows when org_id is passed to effective_permissions(user_id, org_id).
  • Caches merged sets in Redis under authz:perms:{user_id}:{org_id} for 300 seconds (CACHE_TTL_SECONDS).
  • invalidate_cache(user_id) scans and deletes keys when roles change.
  • Depends(roles_allowed(PERM)) — User must be logged in and satisfy every listed permission according to authorization middleware (_holds_role / has_permission).
  • Depends(authenticated) — User must be logged in; no extra permission check.
  • require_platform_sys_admin / require_admin — Narrow entry points for platform or admin operations.

Many org-scoped handlers then call org_context from the shared route helpers so the user is a member of the org being touched.

sanitize_api_key_flat_scopes removes SYSTEM_ADMIN and system API-key management permissions from key scopes (API_KEY_SESSION_STRIPPED_PERMISSIONS in the permissions module).

  • Cache staleness — Up to 5 minutes before Redis effective-permission cache matches DB after role edits, unless invalidate_cache is called on the relevant code paths.
  • Session snapshot — Interactive JWT sessions store permissions at login/refresh; changing roles in DB does not update Redis until the user refreshes the token or re-authenticates.