This commit is contained in:
2026-03-03 14:02:18 -07:00
parent f10b15d91b
commit 3b46f48655
13 changed files with 1041 additions and 99 deletions

View File

@@ -0,0 +1,63 @@
"""
Definición y helpers de roles para el sistema multi-tenant.
Fuente única: UserRole en app.models.user.
Este módulo expone conjuntos de roles y helpers de verificación
para usarse en deps.py y en los endpoints.
Roles globales (staff interno — alcance multi-tenant):
ADMIN → control total sobre todos los tenants
SUPPORT_MANAGER → gestiona equipos y SLAs de todos los tenants
AGENT → atiende tickets de cualquier tenant
AUDITOR → auditoría de solo lectura en todos los tenants
Roles de cliente (alcance limitado al propio tenant):
CLIENT_ADMIN → administra organización: usuarios, configuración, tickets
CLIENT_USER → crea y sigue sus propios tickets
"""
from app.models.user import UserRole
# ── Conjuntos de roles ──────────────────────────────────────────────────────
GLOBAL_ROLES: frozenset[UserRole] = frozenset({
UserRole.ADMIN,
UserRole.SUPPORT_MANAGER,
UserRole.AGENT,
UserRole.AUDITOR,
})
CLIENT_ROLES: frozenset[UserRole] = frozenset({
UserRole.CLIENT_ADMIN,
UserRole.CLIENT_USER,
})
# ── Permisos por rol ────────────────────────────────────────────────────────
ROLE_PERMISSIONS: dict[UserRole, list[str]] = {
# Staff global
UserRole.ADMIN: ["manage_all", "view_all", "audit_all"],
UserRole.SUPPORT_MANAGER: ["manage_teams", "view_all_tickets", "manage_sla"],
UserRole.AGENT: ["view_all_tickets", "update_any_ticket"],
UserRole.AUDITOR: ["view_all", "audit_all"],
# Clientes (acotados al tenant)
UserRole.CLIENT_ADMIN: ["manage_tenant", "manage_tenant_users", "view_tenant_tickets"],
UserRole.CLIENT_USER: ["create_ticket", "view_own_tickets"],
}
# ── Helpers ─────────────────────────────────────────────────────────────────
def is_global_staff(role: UserRole) -> bool:
"""Retorna True si el rol tiene alcance global (staff interno)."""
return role.is_global
def is_client_role(role: UserRole) -> bool:
"""Retorna True si el rol está acotado al tenant del usuario."""
return role.is_client
def has_permission(role: UserRole, permission: str) -> bool:
"""Verifica si un rol tiene un permiso específico."""
return permission in ROLE_PERMISSIONS.get(role, [])

View File

@@ -27,6 +27,24 @@ class UserRole(str, enum.Enum):
CLIENT_ADMIN = "CLIENT_ADMIN" # Admin de organización cliente
CLIENT_USER = "CLIENT_USER" # Usuario final cliente
@property
def is_global(self) -> bool:
"""True si el rol tiene alcance global (staff interno cross-tenant)."""
return self in (
UserRole.ADMIN,
UserRole.SUPPORT_MANAGER,
UserRole.AGENT,
UserRole.AUDITOR,
)
@property
def is_client(self) -> bool:
"""True si el rol está acotado al tenant del usuario."""
return self in (
UserRole.CLIENT_ADMIN,
UserRole.CLIENT_USER,
)
class User(Base):
"""Modelo de Usuario."""
@@ -113,11 +131,8 @@ class User(Base):
@property
def is_client(self) -> bool:
"""Check if user is a client."""
return self.role in [
UserRole.CLIENT_ADMIN,
UserRole.CLIENT_USER
]
"""Check if user is a client (rol acotado al propio tenant)."""
return self.role.is_client
@property
def can_manage_users(self) -> bool:
@@ -125,7 +140,7 @@ class User(Base):
return self.role in [
UserRole.ADMIN,
UserRole.SUPPORT_MANAGER,
UserRole.CLIENT_ADMIN
UserRole.CLIENT_ADMIN,
]
@property
@@ -134,7 +149,7 @@ class User(Base):
return self.role in [
UserRole.ADMIN,
UserRole.SUPPORT_MANAGER,
UserRole.AGENT
UserRole.AGENT,
]
@property