jogosultsági szintek RBAC beállítva tesztelve
This commit is contained in:
@@ -44,6 +44,7 @@ from .identity.social import ServiceProvider, Vote, Competition, UserScore, Serv
|
||||
from .gamification.gamification import PointRule, LevelConfig, UserStats, Badge, UserBadge, PointsLedger, UserContribution, Season
|
||||
|
||||
from .system.system import SystemParameter, ParameterScope, InternalNotification, SystemServiceStaging
|
||||
from .system.rbac import SystemRole, SystemPermission, SystemRolePermission
|
||||
|
||||
from .system.document import Document
|
||||
from .system.translation import Translation
|
||||
@@ -96,4 +97,6 @@ __all__ = [
|
||||
"Campaign", "Creative", "Placement", "CampaignCreative",
|
||||
"CampaignPlacement", "AdImpression", "AdClick",
|
||||
"CampaignStatus", "CreativeType", "PlacementType",
|
||||
# RBAC Phase 1
|
||||
"SystemRole", "SystemPermission", "SystemRolePermission",
|
||||
]
|
||||
|
||||
@@ -20,6 +20,7 @@ if TYPE_CHECKING:
|
||||
from .payment import PaymentIntent, WithdrawalRequest
|
||||
from .social import ServiceReview, SocialAccount
|
||||
from ..marketplace.service_request import ServiceRequest
|
||||
from ..system.rbac import SystemRole
|
||||
|
||||
class UserRole(str, enum.Enum):
|
||||
"""Rendszerszintű (System-level) szerepkörök.
|
||||
@@ -132,6 +133,15 @@ class User(Base):
|
||||
email: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False)
|
||||
hashed_password: Mapped[Optional[str]] = mapped_column(String)
|
||||
|
||||
# RBAC Phase 1: DB-driven role FK (nullable initially for migration safety)
|
||||
role_id: Mapped[Optional[int]] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("system.roles.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
index=True
|
||||
)
|
||||
|
||||
# Legacy: Keep UserRole enum column for backward compatibility during migration
|
||||
role: Mapped[UserRole] = mapped_column(
|
||||
PG_ENUM(UserRole, name="userrole", schema="identity"),
|
||||
default=UserRole.USER
|
||||
@@ -188,10 +198,17 @@ class User(Base):
|
||||
|
||||
# --- KAPCSOLATOK ---
|
||||
|
||||
# RBAC Phase 1: DB-driven role relationship
|
||||
system_role: Mapped[Optional["SystemRole"]] = relationship(
|
||||
"SystemRole",
|
||||
foreign_keys=[role_id],
|
||||
lazy="joined"
|
||||
)
|
||||
|
||||
# JAVÍTÁS 4: Itt is explicit megadjuk, hogy melyik kulcs köti az emberhez
|
||||
person: Mapped[Optional["Person"]] = relationship(
|
||||
"Person",
|
||||
foreign_keys=[person_id],
|
||||
"Person",
|
||||
foreign_keys=[person_id],
|
||||
back_populates="users"
|
||||
)
|
||||
|
||||
|
||||
@@ -29,12 +29,15 @@ class OrgUserRole(str, enum.Enum):
|
||||
|
||||
Ezek a szerepkörök a szervezeten/flottán belüli jogosultságokat határozzák meg.
|
||||
Minden szerepkörhöz egyedi JSON permissions objektum tartozik a fleet.org_roles táblában.
|
||||
|
||||
Megjegyzés: Az értékek szinkronban kell lenniük a PostgreSQL fleet.orguserrole enum típussal.
|
||||
Jelenlegi DB enum: OWNER, ADMIN, MANAGER, MEMBER, AGENT
|
||||
"""
|
||||
OWNER = "OWNER"
|
||||
ADMIN = "ADMIN"
|
||||
ACCOUNTANT = "ACCOUNTANT"
|
||||
DRIVER = "DRIVER"
|
||||
VIEWER = "VIEWER"
|
||||
MANAGER = "MANAGER"
|
||||
MEMBER = "MEMBER"
|
||||
AGENT = "AGENT"
|
||||
|
||||
class OrgRole(Base):
|
||||
"""
|
||||
|
||||
@@ -4,9 +4,11 @@ from .audit import SecurityAuditLog, OperationalLog, ProcessLog, FinancialLedger
|
||||
from .document import Document
|
||||
from .translation import Translation
|
||||
from .legal import LegalDocument, LegalAcceptance
|
||||
from .rbac import SystemRole, SystemPermission, SystemRolePermission
|
||||
|
||||
__all__ = [
|
||||
"SystemParameter", "InternalNotification", "SystemServiceStaging",
|
||||
"SecurityAuditLog", "ProcessLog", "FinancialLedger", "WalletType", "LedgerStatus", "LedgerEntryType",
|
||||
"Document", "Translation", "LegalDocument", "LegalAcceptance"
|
||||
"Document", "Translation", "LegalDocument", "LegalAcceptance",
|
||||
"SystemRole", "SystemPermission", "SystemRolePermission",
|
||||
]
|
||||
|
||||
135
backend/app/models/system/rbac.py
Normal file
135
backend/app/models/system/rbac.py
Normal file
@@ -0,0 +1,135 @@
|
||||
# /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})>"
|
||||
)
|
||||
Reference in New Issue
Block a user