12 KiB
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
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()
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)
- Create system.roles, system.permissions, system.role_permissions SQLAlchemy models
- Run sync_engine.py to create tables
- Create seed script: insert current 6 roles + 28 permissions + mappings
- Add role_id FK column to identity.users (nullable initially, migrate, then NOT NULL)
Phase 2: Backend Service Layer
- Implement RBACService with Redis caching
- Implement RequirePermission() FastAPI dependency
- Implement admin API endpoints for CRUD on roles/permissions
- Implement cache invalidation on role/permission changes
Phase 3: Endpoint Migration
- Replace get_current_admin() with RequirePermission(fleet:view) in 30+ endpoints
- Replace RequireRole([...]) with RequirePermission(domain:action) in 7+ endpoint files
- Replace RequireSystemCapability(...) with RequirePermission(domain:action)
- Remove hardcoded if role == checks from 5+ endpoint files
- Remove or deprecate rbac_service.py ROLE_ACTIONS
Phase 4: Frontend Integration
- Add GET /admin/permissions endpoint that returns available permissions from DB
- Update frontend_admin to fetch permissions dynamically
- Remove hardcoded superadminPerms and garagePerms arrays
Phase 5: Cleanup
- Remove SYSTEM_CAPABILITIES_MATRIX from capabilities.py
- Remove ROLE_ACTIONS from rbac_service.py
- Deprecate old RequireRole(), RequireSystemCapability(), get_current_admin()
- Update tests
- 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.