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:
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 |
Related Documentation¶
- Architecture Overview - System architecture
- Security Architecture - Security details
- API Reference - Endpoint permissions