Skip to content

Add Built-In Role-Based Access Control (RBAC) with Roles, Permissions, and Assignments #237

Open

Description

@null-ed

✅ Problem

The project currently lacks a reusable and structured RBAC (Role-Based Access Control) system, with the following issues:

  1. No Role model or user-role mapping
    Users cannot have multiple roles, and roles cannot share permissions in a reusable way.

  2. No standard way to define permissions
    There is no centralized permission list or front-end-friendly permission structure.

  3. No permission-checking mechanism
    Access control relies only on is_superuser or resource ownership, which is insufficient for enterprise multi-role, multi-admin systems.

  4. No API, dependencies, admin panel, or documentation for RBAC
    Teams must implement their own solutions, leading to duplication and inconsistency.


✅ Proposal

Introduce a clean and extensible RBAC system without a database Permission model:


1. Permissions as Code Constants

Define all permissions centrally as code constants:

class PermissionNames:
    user = "user"
    user_create = f"{user}.create"
    user_delete = f"{user}.delete"

    book = "book"
    book_edit = f"{book}.edit"

Benefits:

  • Centralized and maintainable
  • No database table needed
  • Avoids duplicate or stale data
  • Adding new permissions only requires code changes

2. Permission Tree for Hierarchy and UI

Use a dedicated class PermissionNode to build hierarchical structures:

PermissionNode(
    name=PermissionNames.user,
    children=[
        PermissionNode(name=PermissionNames.user_create),
        PermissionNode(name=PermissionNames.user_delete),
    ]
)

Purpose:

  • Render permission tree in admin/front-end UI
  • Allow administrators to select permissions
  • Single source of truth for permission hierarchy

3. Data Models (Simplified)

  • roles: id, name, description, timestamps
  • role_permission: role_id, permission_name (string constant)
  • user_role: user_id, role_id

No separate Permission model is needed, keeping the database simple.


4. API / CRUD

Provide standard endpoints:

  • Create/update/delete roles
  • Assign/remove permissions to roles (string constants)
  • Assign/remove roles to users
  • Query a user’s effective permissions

5. Permission Checks

Use FastAPI dependencies:

require_permissions(PermissionNames.user_create,PermissionNames.user_delete)

Logic:

  • Aggregate permissions from all roles assigned to the user
  • Superusers bypass checks
  • Supports any/all permission modes

6. Admin Panel Integration

  • Role management UI includes permission assignment
  • Permission tree automatically rendered from PermissionNode hierarchy

7. Documentation and Testing

  • Document how to define permissions, build the permission tree, and protect endpoints
  • Test role assignment and permission checks

📌 Summary

Problem: The project lacks RBAC, making role management and permission control difficult.
Proposal: Add Role, UserRole, and RolePermission models; define permissions as code constants with a hierarchical tree; implement require_permission checks; provide complete API, admin UI, docs, and tests.

Activity

  1. ayushGadag commented on Aug 8, 2026

    @ayushGadag

    Hi! I’m an engineering student and I’m interested in working on this issue as part of my 2–3 month college engineering project.

    I’ve studied the issue description and I’m interested in implementing the RBAC functionality, including the database models/migrations, role and permission APIs, permission-checking dependencies, tests, and documentation.

    Before starting implementation, I wanted to ask whether the maintainers would be comfortable with me taking this issue up as my academic project.

    I will follow the project’s contribution guidelines, discuss the implementation/design with you before making major changes, and submit proper tests and documentation through the normal PR process.

    I’m not asking for any special treatment or guaranteed acceptance. I simply want to confirm that you’re comfortable with me working on this issue as part of my college project.

    If this scope looks reasonable, I’d be happy to prepare a short implementation plan before beginning.

    Thank you!

  2. mo-hossam-stack commented on Aug 12, 2026

    @mo-hossam-stack

    Is any one working on this issue? if not i would like to work on it

  3. sdrarunvarshan commented on Aug 13, 2026

    @sdrarunvarshan

    I would also like to contribute

  4. emiliano-go commented on Sep 19, 2026

    @emiliano-go
    Collaborator

    RBAC is one feature, so I'm folding #275 (the admin panel work) into this issue and closing it. The admin views, user-role assignment, effective-permissions display, tests and docs it describes are copied below. One issue, one thread.

    Prior art: #278 (closed, unmerged) sketched the role module and the three admin views, but it has no tests and no migration, and it predates the recent auth and error-handling refactors. Use it as a reference, not a branch to revive.

    These three decisions are settled, not up for debate:

    • Permissions are code constants (PermissionNames plus PERMISSION_TREE); there is no DB Permission table.
    • No Alembic revision for this work. The project ships without an initial revision and creates tables on startup (CREATE_TABLES_ON_STARTUP=true, and Base.metadata.create_all runs in the lifespan), so the role tables come from the models. When someone adds the first revision, import_models("src.modules") picks them up automatically.
    • Admin role assignment starts as a flat multi-select. The hierarchical checkbox tree in interfaces/admin/widgets.py is a later, optional addition.

    The work, as separate pieces you can pick from:

    1. Core models and permissions: Role, RolePermission and UserRole, the PermissionNames constants and PERMISSION_TREE, and the User.user_roles relationship.
    2. Role schemas, service and CRUD.
    3. Role API routes.
    4. require_permissions dependency, with the superuser bypass and all/any modes, plus the effective-permissions query.
    5. Admin views: RoleAdmin, RolePermissionAdmin and UserRoleAdmin, registered with the existing views.
    6. UserAdmin roles and effective permissions (read only) and role assignment.
    7. Tests under tests/integration/admin/ and wherever the API work needs them.
    8. Docs in docs/user-guide/admin-panel/.
    9. Optional, later: the permission-tree widget.

    Some pieces depend on others: 1 lands first, and 2 through 6 build on it. Reply with the piece you want and a few lines on how you'd approach it. The three decisions above are fixed, so don't plan around changing them unless you've got a good reason.

    How we'll coordinate:

    @ayushGadag @mo-hossam-stack @sdrarunvarshan @zayed95 if you want to take a piece, reply and claim it.

  5. self-assigned this
    on Sep 19, 2026
  6. ayushGadag commented on Sep 20, 2026

    @ayushGadag

    Hi @emiliano-go, I'd like to take piece #5 — Admin views.

    I'll work on implementing RoleAdmin, RolePermissionAdmin, and UserRoleAdmin, and register them with the existing admin views. I'll follow the project's existing SQLAdmin patterns and build on the RBAC models and services provided by the earlier pieces.

    I'll keep the changes focused on the admin integration described in this issue.

  7. Krishgit2005 commented on Sep 24, 2026

    @Krishgit2005

    Hi @emiliano-go , I'd like to take up an available piece that is relatively easy to start with, preferably Piece #8 — Documentation or another suitable beginner-friendly task.

    I'm interested in contributing to the RBAC implementation and will follow the project's existing patterns and guidelines. Please let me know which piece would be suitable for me to work on.

    I'll keep my changes focused on the assigned task and coordinate with the other contributors.

  8. emiliano-go commented on Sep 24, 2026

    @emiliano-go
    Collaborator

    @Krishgit2005 point 8 is a good start, but we are still waiting on an implemention of point 1, as everything depends on it. I may scaffold it later today as for you guys to keep working on this.

    Ping me if needed.

  9. Vins01-07 commented on Sep 24, 2026

    @Vins01-07
    Contributor

    Hi @emiliano-go , I'd like to take up Piece #1 — Core models and permissions.

    I'm interested in contributing to the RBAC implementation, specifically the Role, RolePermission, and UserRole models, the PermissionNames constants and PERMISSION_TREE, and the User.user_roles relationship.

    I'll follow the project's existing patterns and guidelines and keep my changes focused on this piece. Please let me know if there are any specific considerations or dependencies I should be aware of.

  10. emiliano-go commented on Sep 24, 2026

    @emiliano-go
    Collaborator

    @Vins01-07 nice! That is the base where everyone will build on, so I'll probably review it harder than the rest. Feel free to ama, I'll wait for your PR.

  11. Piyagaikwad commented on Sep 25, 2026

    @Piyagaikwad

    Hi @emiliano-go I’d like to take piece #2 : Role schemas, service and CRUD.

    I’ll build the Pydantic schemas and service/CRUD operations around the Role, RolePermission, and UserRole models from piece 1, following the project’s existing service and schema patterns. I’ll keep the scope limited to the role module so that the API routes and permission dependency can build on top of it separately.

  12. mo-hossam-stack commented on Sep 25, 2026

    @mo-hossam-stack

    hi @emiliano-go i would like to take piece #3 - Role API routes
    I’ll implement the role-related API endpoints on top of the schemas, service, and CRUD from Piece #2, following the project’s existing FastAPI routing patterns.

    I’ll keep the scope focused on the API routes and coordinate with the contributors working on the core models and role service.

  13. Vins01-07 commented on Sep 25, 2026

    @Vins01-07
    Contributor

    Hi @emiliano-go , I’ve submitted PR #302 for Piece 1 (RBAC core models and permissions). Please take a look when you have time and merge it if everything looks good. Thanks!

  14. emiliano-go commented on Sep 25, 2026

    @emiliano-go
    Collaborator

    @mo-hossam-stack @Piyagaikwad nice. Feel free to AMA or @ me if you have any issues.

    @Vins01-07 looking at your PR rn.

  15. igorbenav commented on Sep 25, 2026

    @igorbenav
    Collaborator

    RBAC spec

    Thanks @null-ed for the original proposal, and @Vins01-07 for starting on it in #302. The direction in this issue is right: three tables and permissions defined in code. Before we build the rest in pieces, this comment pins down the details so every PR works from the same spec. Comments are welcome, especially on the open questions at the end.

    Goals

    • Users can have multiple roles, and a role is a named set of permissions.
    • Permissions are defined in code, so the code is the single source of truth. The database stores only which roles hold which permissions.
    • Any module, including ones added by projects built on the boilerplate, can declare permissions without editing the role module.
    • Routes are protected with a single FastAPI dependency.
    • The frontend and admin UI can list the available permissions, grouped by resource, and see a user's effective permissions.

    Not in scope for v1

    • Per-object permissions ("can edit this post"). Ownership checks stay in services, as they do today.
    • Role inheritance or role hierarchies.
    • Caching permissions across requests. They're loaded once per request, so a revoked permission stops working on the very next request.
    • Replacing is_superuser. It stays and bypasses every permission check.

    1. Permission format

    A permission is a flat string, "<resource>.<action>", e.g. tier.read or role.assign.

    • Flat, no tree. Django (app_label.codename) and Kubernetes (resources × verbs) both work this way. The UI grouping is "group by resource prefix", so nobody maintains a tree by hand.
    • Lowercase, one dot. If an action needs more words, use underscores: billing.invoice_read.
    • Same format as scopes. It matches crudauth's Principal.scopes, so token and API-key scopes can later use the same vocabulary (see §6).

    2. Declaring permissions

    Each module that owns a resource declares its permissions in its own permissions.py, as a StrEnum (the same style as api_keys/enums.py):

    # src/modules/tier/permissions.py
    from enum import StrEnum
    
    from ..role.registry import register_permissions
    
    
    @register_permissions("tier", label="Tiers")
    class TierPermission(StrEnum):
        READ = "tier.read"
        CREATE = "tier.create"
        UPDATE = "tier.update"
        DELETE = "tier.delete"

    src/modules/role/registry.py provides:

    Function Purpose
    register_permissions(resource, label=None) Class decorator. Rejects a permission that doesn't start with f"{resource}." and rejects the same resource registered twice.
    all_permissions() -> frozenset[str] Every permission that currently exists. Used for validation.
    permission_groups() -> list[PermissionGroup] {resource, label, permissions}, for the UI and API.
    is_known_permission(name) -> bool Convenience check.

    Discovery: at startup, walk src.modules.*.permissions, the same way migrations/env.py already finds models. A new module is picked up automatically.

    Built-in permissions: user.*, role.* (read, create, update, delete, assign) and tier.*.

    3. Data model

    roles
      id           int pk
      name         varchar(50) unique not null
      description  text null
      created_at / updated_at
    
    role_permissions
      role_id      int fk -> roles.id  on delete cascade
      permission   varchar(100) not null
      pk (role_id, permission)
    
    user_roles
      user_id      int fk -> user.id   on delete cascade
      role_id      int fk -> roles.id  on delete cascade
      pk (user_id, role_id)
      index (role_id)
    
    • Roles are hard-deleted, so no SoftDeleteMixin. FastCRUD soft-deletes any model with is_deleted, so a soft-deleted role would keep its assignments, keep granting access, and keep its name taken. Deleting a role must revoke access.
    • Composite primary keys on the join tables. No surrogate id and no extra unique constraint.
    • Constraint and index names come from the shared naming convention (pinned in test: guard the crudauth follow-up behaviors #300).
    • Permission names are validated on write. RolePermission.permission is checked against all_permissions() whenever it's set through the ORM. Reads never assume a stored string is still valid (see §7).
    • Soft-deleted users keep their user_roles rows, so restoring a user restores their roles. Authentication already rejects soft-deleted users, so the leftover rows grant nothing.

    4. Checking permissions

    One dependency, built on crudauth's check= hook so there's no second auth path:

    # src/modules/role/dependencies.py
    def require_permissions(*needed: str):
        async def check(principal: Principal) -> bool:
            if principal.is_superuser:
                return True
            return set(needed) <= await get_effective_permissions(principal)
    
        return auth.current_user(check=check)
    
    
    @router.post("/tiers", dependencies=[Depends(require_permissions(TierPermission.CREATE))])
    async def create_tier(...): ...
    • All-of semantics. Every listed permission is required. If anyone needs any-of, it can be a separate require_any_permission later.
    • get_effective_permissions runs one query (user_roles → role_permissions), drops unrecognised strings, and caches the result on request.state for the rest of the request.
    • Failure responses. A missing permission returns 403 through crudauth's existing ForbiddenException; no credentials returns 401 as it does today.

    5. Rules

    1. Superuser bypasses permission checks, the same as today with get_current_superuser.
    2. Permissions are global, not per-object. user.update means "update any user". Editing your own profile stays the existing ownership rule in UserService, not a permission.
    3. No privilege escalation, following Kubernetes' escalate / bind rules:
      • Editing a role: creating or updating a role requires role.create / role.update, and the actor must already hold every permission they add.
      • Assigning a role: assigning or unassigning a role requires role.assign, and the actor must already hold every permission that role grants.
      • Superuser exception: superusers are exempt.
    4. Removing permissions from code needs no migration. Stored strings that are no longer registered are ignored at check time, and the admin UI marks them as stale.

    6. API keys and tokens (follow-up, but decided now)

    When a credential carries scopes, what it can do is the owner's effective permissions narrowed by those scopes. Session credentials carry no scopes, so they get the full set. Moving KeyPermission (the resource/action enums) onto these strings can be a separate follow-up PR. It isn't part of v1, but because the formats match, it won't need a redesign.

    7. API surface (Piece 2)

    Method & path Permission
    GET /permissions (grouped registry) authenticated
    GET /users/me/permissions (effective set, for frontends) authenticated
    GET/POST /roles, GET/PATCH/DELETE /roles/{id} role.read / create / update / delete
    PUT /roles/{id}/permissions role.update + escalation rule
    POST/DELETE /users/{username}/roles/{role_id} role.assign + escalation rule

    8. Delivery plan

    1. Piece 1: core. Registry, per-module enums for user / role / tier, the three models, require_permissions + get_effective_permissions, and one existing route switched over (e.g. tier create) to prove it end to end. Tests:

      • registry validation;
      • effective permissions with multiple roles;
      • superuser bypass;
      • deleting a role revokes access;
      • unknown stored strings are ignored.

      The cascade test must run against Postgres in CI.

    2. Piece 2: management API. Schemas, CRUD and routes from §7, including the escalation rules and their tests.

    3. Piece 3: admin. A crudadmin view for roles, with permission checkboxes grouped by resource from permission_groups(), and a user → roles editor.

    4. Piece 4: adoption and docs. Move routes that should be delegable from get_current_superuser to permissions, and document how to declare permissions in a new module.

    Some open questions for us to discuss

    • Seed a default role? Should the setup script create one (e.g. admin with every permission), or is is_superuser enough for bootstrapping? I lean towards no seeding in v1.
    • Separator: is . fine, or do we want : to match OAuth/GitHub-style scopes (tier:read)? I lean towards . since it's already used in feat: Piece 01: add RBAC core models and permissions #302.
    • Stale permission cleanup: is "ignore at check time + flag in admin" enough, or do we want a cleanup script?
  16. ayushGadag commented on Sep 26, 2026

    @ayushGadag

    Hi @emiliano-go @igorbenav, I previously claimed Piece #5 (Admin views) under the earlier plan. With the updated RBAC plan, I’d like to go with Piece #3 — Admin instead.

    I’ll work on the SQLAdmin admin views for roles, permissions grouped by resource using permission_groups(), and the user → roles editor, following the existing admin patterns.

    Please let me know if that’s okay.

  17. Krishgit2005 commented on Sep 26, 2026

    @Krishgit2005

    Hi @emiliano-go @igorbenav, I’d like to take up Piece #2 — Management API.

    I’ll implement the schemas, CRUD/service layer, and role management API routes according to the updated RBAC specification, including the required permission and escalation checks. I’ll follow the project’s existing FastAPI patterns and keep the changes focused on Piece 2.

    Please let me know if there are any specific considerations I should be aware of before starting.

  18. Piyagaikwad commented on Sep 26, 2026

    @Piyagaikwad

    Hi @emiliano-go @igorbenav, I’d like to take up Piece #4 — Adoption and docs.
    I’ll review the existing routes that currently rely on get_current_superuser, move the appropriate ones to the new permission-based dependency, and document how new modules can declare and use permissions with the RBAC registry. I’ll follow the existing project patterns and keep the changes focused on Piece 4.

  19. emiliano-go commented on Sep 26, 2026

    @emiliano-go
    Collaborator

    @ayushGadag @Krishgit2005 @Piyagaikwad nice!

    Thanks for the contributions, and @ me if you need anything.

  20. Krishgit2005 commented on Sep 30, 2026

    @Krishgit2005

    Hi @emiliano-go @igorbenav,

    I’d like to start working on Piece #2 — Management API.

    I’ve reviewed the latest RBAC changes and the updated requirements, including the permission/escalation rules and the remaining Piece 2 responsibilities.

    Should I go ahead and start implementing Piece 2 now, or is there anything you’d like me to clarify or coordinate before I begin?

    Thanks!

  21. igorbenav commented on Sep 30, 2026

    @igorbenav
    Collaborator

    @Krishgit2005 don't start it yet please, there's a huge refactor happening and it will probably conflict. Once I merge #305 you're good to go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requesthelp wantedExtra attention is needed

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions