136 lines
5.1 KiB
Python
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})>"
|
|
)
|