Files
service-finder/docs/p0_full_db_driven_rbac_audit_proposal.md

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)

  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.