Skip to content

RBAC Architecture

Role-Based Access Control system design and implementation.


Overview

MBPanel implements a team-scoped RBAC system with:

  • Flexible permissions: String-based slugs for fine-grained control
  • Wildcard support: * grants all permissions
  • Team isolation: Permissions scoped to team membership
  • Custom roles: Teams can create custom roles (except Owner)
  • Default roles: Owner, Manager, Developer seeded on team creation

Data Model

Entities

Permission (global, seeded)
├── slug (PK): "site.create"
├── description: "Create new sites"
└── category: "sites"

Role (team-scoped)
├── id (PK)
├── team_id (FK)
├── name: "Developer"
├── is_editable: true
└── has_many → RolePermission

RolePermission (many-to-many)
├── role_id (FK)
├── permission_slug (FK)
└── links Role ↔ Permission

TeamMember (user-team association)
├── user_id (FK)
├── team_id (FK)
├── role_id (FK)
└── unique(user_id, team_id)

User
└── has_many → TeamMember

Schema Relationships

User ─────────────────────────────┐
  │                                │
  │ 1:N                            │ N:1
  │                                │
  TeamMember ──────────────── Team
  │                                │
  │ N:1                             │ 1:N
  │                                │
  Role ──────────────────────┐
  │                            │
  │ N:M                        │ N:1
  │                            │
  Permission ←───────────── RolePermission

Permission Model

Permission Categories

Category Prefix Example Permissions
Team team. team.manage, team.invite, team.view
Sites site. site.create, site.edit, site.delete, site.view
Environments env. env.create, env.deploy, env.delete, env.view
Backups backup. backup.create, backup.restore, backup.delete, backup.view
Servers server. server.create, server.manage, server.view
Users user. user.manage, user.view
Billing billing. billing.manage, billing.view
System system. system.admin, events.read

Wildcard Permission

The * permission grants all permissions:

# backend/app/core/permissions.py
WILDCARD_PERMISSION = "*"

def has_permission(user: AuthenticatedUser, permission: str) -> bool:
    if WILDCARD_PERMISSION in user.permissions:
        return True  # Wildcard grants all
    return permission in user.permissions

Assigned to: Owner role only


Default Roles

Owner

Description: Full control over team, including billing

Permissions: * (wildcard)

Is Editable: No (cannot be modified)

Capabilities: - All team management operations - Billing management - Virtuozzo session refresh - Ownership transfer - All resource operations

Manager

Description: Team management without billing

Permissions:

team.manage
team.invite
site.create
site.edit
site.delete
site.view
env.create
env.deploy
env.delete
env.view
backup.create
backup.restore
backup.delete
backup.view
server.create
server.manage
server.view
user.manage
user.view
events.read

Is Editable: Yes (can be modified)

Developer

Description: Resource access only

Permissions:

site.create
site.edit
site.view
env.create
env.deploy
env.view
backup.create
backup.restore
backup.view
server.view
events.read

Is Editable: Yes (can be modified)


Permission Checking

Dependency-Based Authorization

# backend/app/core/dependencies.py
def require_permission(permission: str):
    def dependency(
        request: Request,
        user: AuthenticatedUser = Depends(get_current_user)
    ) -> AuthenticatedUser:
        if not has_permission(user, permission):
            log_auth_violation(
                request=request,
                violation="missing_permission",
                detail=f"Missing permission: {permission}"
            )
            raise HTTPException(status_code=403, detail="Missing permission")
        return user
    return dependency

Usage in Endpoints

# backend/app/modules/sites/router.py
@router.post("/sites")
async def create_site(
    # ...
    _: None = Depends(require_permission("site.create"))
):
    # Only users with site.create can execute
    pass

Team Scoping

Permissions are automatically scoped to the user's current team:

# User context includes team_id
@user_permissions.team_id == @resource.team_id

Cross-team access is blocked at the middleware level.


Custom Roles

Creating Custom Roles

Teams can create custom roles with any permission combination:

# Example: "Deployment Manager" role
custom_role = Role(
    team_id=team.id,
    name="Deployment Manager",
    is_editable=True,
    permissions=[
        "env.create",
        "env.deploy",
        "env.delete",
        "env.view",
        "site.view",
        "backup.view",
        "events.read"
    ]
)

Constraints

Constraint Rule
Owner role Cannot be edited (is_editable=False)
Team scope Roles belong to a single team
Permission existence Must reference seeded permissions
Name uniqueness Role names must be unique per team

Permission Seeding

Seed Permissions

Permissions are seeded during database migrations:

# backend/app/infrastructure/database/models/rbac.py
PERMISSIONS = [
    # Team
    Permission(slug="team.manage", description="Manage team settings"),
    Permission(slug="team.invite", description="Invite team members"),
    Permission(slug="team.view", description="View team information"),

    # Sites
    Permission(slug="site.create", description="Create sites"),
    Permission(slug="site.edit", description="Edit sites"),
    Permission(slug="site.delete", description="Delete sites"),
    Permission(slug="site.view", description="View sites"),

    # Environments
    Permission(slug="env.create", description="Create environments"),
    Permission(slug="env.deploy", description="Deploy to environments"),
    Permission(slug="env.delete", description="Delete environments"),
    Permission(slug="env.view", description="View environments"),

    # Backups
    Permission(slug="backup.create", description="Create backups"),
    Permission(slug="backup.restore", description="Restore backups"),
    Permission(slug="backup.delete", description="Delete backups"),
    Permission(slug="backup.view", description="View backups"),

    # Servers
    Permission(slug="server.create", description="Create servers"),
    Permission(slug="server.manage", description="Manage servers"),
    Permission(slug="server.view", description="View servers"),

    # Users
    Permission(slug="user.manage", description="Manage team users"),
    Permission(slug="user.view", description="View team users"),

    # Billing
    Permission(slug="billing.manage", description="Manage billing"),
    Permission(slug="billing.view", description="View billing"),

    # System
    Permission(slug="system.admin", description="System administration"),
    Permission(slug="events.read", description="Read event streams"),
]

Seed Default Roles

Default roles are created when a team is created:

# On team creation
for role_name in ["Owner", "Manager", "Developer"]:
    role = Role(
        team_id=team.id,
        name=role_name,
        is_editable=(role_name != "Owner"),
        permissions=DEFAULT_ROLE_PERMISSIONS[role_name]
    )

Permission Matrix

Resource Action Owner Manager Developer
Team View
Team Manage
Team Invite
Sites Create
Sites Edit
Sites Delete
Environments Create
Environments Deploy
Environments Delete
Backups Create
Backups Restore
Servers Create
Servers Manage
Users Manage
Billing Manage

Ownership Transfer

Flow

┌──────────────┐                  ┌──────────────┐                  ┌──────────────┐
│   CURRENT    │                  │   BACKEND    │                  │   NEW OWNER  │
│   OWNER      │                  │              │                  │              │
└──────┬───────┘                  └──────┬───────┘                  └──────┬───────┘
       │                                 │                                 │
       │  1. Initiate Transfer            │                                 │
       │  (target_user_id)                │                                 │
       ├─────────────────────────────────>│                                 │
       │                                 │ 2. Generate transfer token       │
       │                                 │ 3. Send email to new owner        │
       │                                 ├─────────────────────────────────>│
       │                                 │                                 │
       │  4. Confirmation                │                                 │
       │<─────────────────────────────────┤                                 │
       │                                 │                                 │
       │                                 │ 5. New owner accepts             │
       │                                 │<─────────────────────────────────┤
       │                                 │                                 │
       │                                 │ 6. Transfer ownership            │
       │                                 │ 7. Update roles                  │
       │                                 │                                 │
       │  8. Transfer Complete            │                                 │
       │<─────────────────────────────────┤                                 │
       │                                 │                                 │

Constraints

Constraint Rule
Initiator Must be current Owner
Recipient Must be existing TeamMember
Token TTL 24 hours
Old Owner Becomes Manager after transfer
New Owner Gets Owner role and permissions
Virtuozzo New Owner can refresh VZ session