# P0: Fully Database-Driven RBAC - Audit & Architectural Proposal **Datum:** 2026-06-25 **Status:** DRAFT - Waiting for Architect Approval **Prioritás:** P0 (Critical) --- ## 1. Executive Summary A jelenlegi Service Finder RBAC rendszer **két rétegből** áll, amelyek közül az egyik részben adatbázis-vezérelt, a másik viszont **teljesen hardcoded**: | Réteg | Státusz | Részletek | |-------|---------|-----------| | **Org-level (fleet.org_roles)** | Részben DB-driven | `OrgRole` modell JSONB `permissions` oszloppal, 5 szerepkör seedelve | | **System-level (SYSTEM_CAPABILITIES_MATRIX)** | Teljesen hardcoded | 200+ soros Python dict, szerepkörönként 25+ capability bool-lal | | **Endpoint authorization** | Hardcoded | `RequireRole([UserRole.SUPERADMIN, UserRole.ADMIN])` - enum-függő | | **Frontend permission UI** | Hardcoded | Statikus JavaScript array-ek superadmin/garage perms-ekkel | **Következmény:** Minden új szerepkör vagy permission hozzáadása kódmódosítást igényel. Nincs admin UI a permission-ök kezelésére. --- ## 2. Audit Findings - 8 Hardcoded Problématerület ### F1: SYSTEM_CAPABILITIES_MATRIX - 200+ soros hardcoded dict **Fájl:** `backend/app/core/capabilities.py` A `SYSTEM_CAPABILITIES_MATRIX` egy hatalmas, teljesen hardcoded Python dict, amely minden UserRole-hoz hozzárendeli a capability-ket. A `Capability` osztály 25+ string konstanst definiál. Helper függvények: `get_capabilities_for_role()`, `role_has_capability()`. ### F2: UserRole - Hardcoded Python Enum **Fájl:** `backend/app/models/identity/identity.py:24` 6 érték: SUPERADMIN, ADMIN, MODERATOR, SALES_REP, SERVICE_MGR, USER. PG_ENUM-ként tárolva - új szerepkör ALTER TYPE-t igényel. ### F3: get_current_admin() - Hardcoded Role Set **Fájl:** `backend/app/api/deps.py:160` ```python allowed_roles = {UserRole.SUPERADMIN, UserRole.ADMIN, UserRole.MODERATOR} ``` 30+ endpoint használja ezt a dependency-t. ### F4: RequireRole() - Enum-függő Dependency **Fájl:** `backend/app/api/deps.py:213` 7+ endpoint fájl használja. Példák endpoint-szintű hardcoded check-ekre: - `analytics.py:40`: `if current_user.role == "superadmin":` - `finance_admin.py:27`: `if current_user.role not in [UserRole.SUPERADMIN, UserRole.ADMIN]:` - `security.py:39`: `if current_user.role not in [UserRole.ADMIN, UserRole.SUPERADMIN]:` - `system_parameters.py:125`: `if current_user.role not in (UserRole.SUPERADMIN, UserRole.ADMIN):` ### F5: RequireSystemCapability() - Matrix Dependency **Fájl:** `backend/app/api/deps.py:278` A `role_has_capability()` a `SYSTEM_CAPABILITIES_MATRIX` hardcoded dict-be indexel. ### F6: rbac_service.py - ROLE_ACTIONS Hardcoded Set-ek **Fájl:** `backend/app/services/rbac_service.py:132` Minden UserRole-hoz külön hardcoded Set[str] action-ök. A `check_admin_access()` explicit SUPERADMIN bypass-t tartalmaz. ### F7: 45 darab endpoint-szintű if role == check Összesen 45 helyen található hardcoded role ellenőrzés a kódban (search_files eredmény). ### F8: Frontend Static Permission Array-ek **Fájl:** `frontend_admin/pages/permissions/index.vue:272-286` Statikus `superadminPerms` és `garagePerms` JavaScript array-ek. Ha a backend új permission-t vezet be, a frontend UI nem frissül. --- ## 3. Proposed Architecture ### 3.1 New Database Tables (system schema) #### system.roles | Mező | Típus | Megjegyzés | |------|-------|-----------| | id | int PK | | | name | varchar(50) UK | pl. SUPERADMIN, ADMIN | | description | text | Opcionális leírás | | rank | int | Hierarchikus rang (100=SUPERADMIN, 0=USER) | | is_system | bool | Rendszer által védett (nem törölhető) | | is_active | bool | Aktív-e | | created_at | datetime | | | updated_at | datetime | | #### system.permissions | Mező | Típus | Megjegyzés | |------|-------|-----------| | id | int PK | | | code | varchar(100) UK | Format: domain:action (pl. fleet:view) | | domain | varchar(50) | fleet, user, finance, system, org, reports, audit, settings | | action | varchar(50) | view, create, edit, delete, approve, manage, export | | description | text | Opcionális leírás | | is_system | bool | Rendszer által védett | | created_at | datetime | | #### system.role_permissions | Mező | Típus | Megjegyzés | |------|-------|-----------| | id | int PK | | | role_id | int FK -> system.roles.id | | | permission_id | int FK -> system.permissions.id | | | granted | bool | Explicit grant (true) vagy deny (false) | | created_at | datetime | | Unique constraint: (role_id, permission_id) ### 3.2 Seed Data - 28 Permission Codes | Code | SUPERADMIN | ADMIN | MODERATOR | SALES_REP | SERVICE_MGR | USER | |------|------------|-------|-----------|-----------|-------------|------| | fleet:view | Y | Y | Y | Y | Y | Y | | fleet:create | Y | Y | N | Y | N | N | | fleet:edit | Y | Y | N | N | N | N | | fleet:delete | Y | N | N | N | N | N | | fleet:approve | Y | Y | N | N | N | N | | user:view | Y | Y | Y | N | N | N | | user:create | Y | Y | N | N | N | N | | user:edit | Y | Y | N | N | N | N | | user:delete | Y | N | N | N | N | N | | user:manage-roles | Y | N | N | N | N | N | | finance:view | Y | Y | Y | N | N | N | | finance:edit | Y | Y | N | N | N | N | | finance:approve | Y | N | N | N | N | N | | finance:refund | Y | N | N | N | N | N | | system:view | Y | Y | Y | N | N | N | | system:edit | Y | Y | N | N | N | N | | system:manage | Y | N | N | N | N | N | | org:view | Y | Y | Y | Y | Y | N | | org:edit | Y | Y | N | N | N | N | | org:delete | Y | N | N | N | N | N | | org:manage-members | Y | Y | N | N | N | N | | reports:view | Y | Y | Y | Y | Y | N | | reports:export | Y | Y | Y | N | N | N | | reports:manage | Y | N | N | N | N | N | | audit:view | Y | N | N | N | N | N | | audit:export | Y | N | N | N | N | N | | settings:view | Y | Y | N | N | N | N | | settings:edit | Y | Y | N | N | N | N | ### 3.3 FastAPI Dependency: RequirePermission() ```python from functools import lru_cache from redis.asyncio import Redis class RBACService: """Database-driven RBAC service with Redis caching.""" def __init__(self, redis: Redis): self.redis = redis self.cache_ttl = 300 # 5 minutes async def get_role_permissions(self, db, role_id): """Get all permission codes for a role, with Redis caching.""" cache_key = f"rbac:role:{role_id}:perms" # Try cache first cached = await self.redis.smembers(cache_key) if cached: return set(cached) # Query database result = await db.execute( select(SystemPermission.code) .join(SystemRolePermission) .where( SystemRolePermission.role_id == role_id, SystemRolePermission.granted == True, SystemRole.is_active == True, ) .join(SystemRole) ) codes = {row[0] for row in result.fetchall()} # Cache the result if codes: await self.redis.sadd(cache_key, *codes) await self.redis.expire(cache_key, self.cache_ttl) return codes async def invalidate_cache(self, role_id): await self.redis.delete(f"rbac:role:{role_id}:perms") def RequirePermission(permission_code: str): """ Database-driven permission checker dependency. Usage: @router.get("/fleet") async def list_fleet( user = Depends(RequirePermission("fleet:view")) ): ... """ async def permission_checker( current_user = Depends(get_current_active_user), db = Depends(get_db), rbac = Depends(get_rbac_service), ): # SUPERADMIN bypass (rank threshold) if current_user.role_id == SUPERADMIN_ROLE_ID: return current_user role_perms = await rbac.get_role_permissions(db, current_user.role_id) if permission_code not in role_perms: raise HTTPException(status_code=403, detail=f"Missing permission: {permission_code}") return current_user return permission_checker ``` ### 3.4 Refactoring Dependency Map | Jelenlegi dependency | Új permission code | Érintett endpointok | |---------------------|--------------------|--------------------| | get_current_admin() | fleet:view (min) | 30+ admin endpoint | | RequireRole([SUPERADMIN, ADMIN]) | user:view, finance:view | analytics, finance_admin | | RequireSystemCapability(can_manage_users) | user:manage-roles | admin endpoints | | check_admin_access(action) | fleet:edit, fleet:view | rbac_service használat | | if role == superadmin | system:manage | security.py, system_parameters.py | | Frontend superadminPerms | GET /admin/permissions (API) | permissions/index.vue | ### 3.5 fleet.org_roles Normalizálás A jelenlegi JSONB permissions-t normalizálni kell, hogy ugyanazokat a permission code namespace-eket használja: ``` # Jelenleg (JSONB): {"can_add_expense": true, "can_approve_expense": false} # Új (normalizált): {"fleet:view": true, "finance:view": true, "finance:approve": false} ``` --- ## 4. Migration Plan - 5 Fázis ### Phase 1: Foundation (Backend DB Schema) 1. Create system.roles, system.permissions, system.role_permissions SQLAlchemy models 2. Run sync_engine.py to create tables 3. Create seed script: insert current 6 roles + 28 permissions + mappings 4. Add role_id FK column to identity.users (nullable initially, migrate, then NOT NULL) ### Phase 2: Backend Service Layer 1. Implement RBACService with Redis caching 2. Implement RequirePermission() FastAPI dependency 3. Implement admin API endpoints for CRUD on roles/permissions 4. Implement cache invalidation on role/permission changes ### Phase 3: Endpoint Migration 1. Replace get_current_admin() with RequirePermission(fleet:view) in 30+ endpoints 2. Replace RequireRole([...]) with RequirePermission(domain:action) in 7+ endpoint files 3. Replace RequireSystemCapability(...) with RequirePermission(domain:action) 4. Remove hardcoded if role == checks from 5+ endpoint files 5. Remove or deprecate rbac_service.py ROLE_ACTIONS ### Phase 4: Frontend Integration 1. Add GET /admin/permissions endpoint that returns available permissions from DB 2. Update frontend_admin to fetch permissions dynamically 3. Remove hardcoded superadminPerms and garagePerms arrays ### Phase 5: Cleanup 1. Remove SYSTEM_CAPABILITIES_MATRIX from capabilities.py 2. Remove ROLE_ACTIONS from rbac_service.py 3. Deprecate old RequireRole(), RequireSystemCapability(), get_current_admin() 4. Update tests 5. Update documentation --- ## 5. Risk Assessment | Kockázat | Valószínűség | Hatás | Mitigáció | |----------|-------------|-------|-----------| | Permission cache stale | Low | Medium | 5 perces TTL + manual invalidation endpoint | | Migration data loss | Low | Critical | Add role_id as nullable first, migrate, then NOT NULL | | Frontend backward compat | Medium | Medium | Keep old endpoints during transition period | | Redis dependency | Low | Low | Fallback to DB query if Redis unavailable | | Performance overhead | Low | Low | Redis cache + lazy loading | --- ## 6. Recommendation **APPROVE** with P0 (Critical) priority. A javasolt rendszer: - Teljesen adatbázis-vezérelt (nincs hardcoded permission logika) - Redis cache-elés (5 perces TTL) a teljesítményért - Explicit grant/deny mechanizmus (granted boolean) - Admin UI-kompatibilis (CRUD endpointok a role-ok és permission-ök kezelésére) - Normál fleet.org_roles JSONB permissions (ugyanaz a namespace) - Fokozatos migráció (5 fázis, minden fázis külön deploy-olható) **DO NOT IMPLEMENT YET.** Wait for explicit approval before starting Phase 1.