Files
service-finder/backend/app/models/system/rbac.py

136 lines
5.1 KiB
Python

# /opt/docker/dev/service_finder/backend/app/models/system/rbac.py
"""
RBAC Phase 1: Database-Driven Role-Based Access Control models.
These models replace the hardcoded SYSTEM_CAPABILITIES_MATRIX and UserRole enum
with fully database-driven roles, permissions, and role-permission mappings.
Schema: system
Tables:
- system.roles
- system.permissions
- system.role_permissions
"""
from __future__ import annotations
from datetime import datetime
from typing import Optional, List, TYPE_CHECKING
from sqlalchemy import String, Integer, Boolean, DateTime, ForeignKey, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql import func
from app.database import Base
if TYPE_CHECKING:
pass
class SystemRole(Base):
"""
System-level roles for the entire platform.
Replaces the hardcoded UserRole enum. Each role has a hierarchical rank
(100=SUPERADMIN, 0=USER) for quick privilege comparisons.
System roles are protected (is_system=True) and cannot be deleted via admin UI.
"""
__tablename__ = "roles"
__table_args__ = (
{"schema": "system", "extend_existing": True}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
rank: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
is_system: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), nullable=False
)
updated_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), onupdate=func.now(), nullable=True
)
# --- RELATIONSHIPS ---
permissions: Mapped[List["SystemRolePermission"]] = relationship(
"SystemRolePermission", back_populates="role", cascade="all, delete-orphan"
)
def __repr__(self) -> str:
return f"<SystemRole(id={self.id}, name='{self.name}', rank={self.rank})>"
class SystemPermission(Base):
"""
Granular permission codes for the entire platform.
Format: domain:action (e.g., fleet:view, user:create, finance:approve)
Domains: fleet, user, finance, system, org, reports, audit, settings
Actions: view, create, edit, delete, approve, manage, export, refund, manage-roles
"""
__tablename__ = "permissions"
__table_args__ = (
{"schema": "system", "extend_existing": True}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
code: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, index=True)
domain: Mapped[str] = mapped_column(String(50), nullable=False, index=True)
action: Mapped[str] = mapped_column(String(50), nullable=False)
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
is_system: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), nullable=False
)
# --- RELATIONSHIPS ---
role_assignments: Mapped[List["SystemRolePermission"]] = relationship(
"SystemRolePermission", back_populates="permission", cascade="all, delete-orphan"
)
def __repr__(self) -> str:
return f"<SystemPermission(id={self.id}, code='{self.code}')>"
class SystemRolePermission(Base):
"""
Many-to-many mapping between roles and permissions with explicit grant/deny.
The `granted` boolean allows explicit denial of a permission for a role,
which can override inherited permissions in hierarchical role structures.
"""
__tablename__ = "role_permissions"
__table_args__ = (
UniqueConstraint("role_id", "permission_id", name="uix_role_permission"),
{"schema": "system", "extend_existing": True}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
role_id: Mapped[int] = mapped_column(
Integer, ForeignKey("system.roles.id", ondelete="CASCADE"), nullable=False
)
permission_id: Mapped[int] = mapped_column(
Integer, ForeignKey("system.permissions.id", ondelete="CASCADE"), nullable=False
)
granted: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), nullable=False
)
# --- RELATIONSHIPS ---
role: Mapped["SystemRole"] = relationship("SystemRole", back_populates="permissions")
permission: Mapped["SystemPermission"] = relationship(
"SystemPermission", back_populates="role_assignments"
)
def __repr__(self) -> str:
return (
f"<SystemRolePermission(id={self.id}, "
f"role_id={self.role_id}, "
f"permission_id={self.permission_id}, "
f"granted={self.granted})>"
)