jogosultsági szintek RBAC beállítva tesztelve
This commit is contained in:
308
docs/p0_full_db_driven_rbac_audit_proposal.md
Normal file
308
docs/p0_full_db_driven_rbac_audit_proposal.md
Normal file
@@ -0,0 +1,308 @@
|
||||
# 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.
|
||||
|
||||
Reference in New Issue
Block a user