Revisione decisionale audit menu, permessi e ACL SafeOps¶
Data: 2026-09-29
Documento letto integralmente: docs/tecnico/audit/SAFEOPS_MENU_PERMISSION_AUDIT.md
Perimetro: checkout DEV e PROD. Nessuna modifica a codice, dati, configurazione, database, menu, permessi o template.
1. Stato reale attuale¶
Il menu nasce soprattutto nel bootstrap app/__init__.py, con circa 349 chiamate add_view, add_view_no_menu, add_link, add_separator e add_category. app/views.py conserva una registrazione FAB storica. Nel bootstrap sono presenti anche blueprint per gateway AI, chat, documenti, lead, mobile e portale formazione.
FAB costruisce l'albero iniziale appbuilder.menu. Successivamente app/modules/iam/menu_service.py legge policy utente/gruppo/ruolo/tenant e strutture persistite; menu_runtime.py applica entitlements e regole speciali; menu_filter.py filtra l'albero; patch in app/__init__.py modificano il comportamento FAB; infine app/templates/appbuilder/_safeops_menu_tree.html riclassifica e nasconde voci in Jinja. _safeops_sidebar.html aggiunge anche shortcut amministrativi hardcoded.
Categorie e label sono quindi distribuite tra registrazioni FAB, link manuali e template. I path /formazione-* e /corsoview/*, le coppie Corsi/ CorsoView, le view no_menu e le label duplicate rappresentano il percorso legacy. Gli helper IAM, le policy menu persistite, gli entitlements tenant_module_entitlement e i diagnostici sono il modello più recente, ma non hanno sostituito il legacy.
FAB fornisce SecurityManager, ModelView/BaseView, ViewMenu, PermissionView e permessi CRUD. SafeOps aggiunge monkey patch, policy IAM, entitlements e template. Le permission usano @has_access, @has_access_api, base_permissions, method_permission_name, class_permission_name, is_accessible e controlli manuali su utente, ruolo, tenant e cliente.
app/modules/iam/policy.py centralizza current_role_names, is_platform_admin, tenant_context, effective_tenant_key e can_choose_tenant. Anagrafiche, Documentale, Formazione, Commerciale, DVR, Ticket e Tecnici mantengono però policy locali (_visible_*, _effective_tenant_key, set di ruoli e filtri query). Ruolo FAB, profilo tenant, tenant, cliente, entitlements e bypass platform-admin interagiscono senza un contratto unico certificato per view, API e dati.
La catena effettiva è:
registrazioni FAB/blueprint
-> menu_service/menu_runtime/policy DB/patch FAB
-> template Jinja e JavaScript
-> permission e ACL applicate dalle singole view/API/moduli
La catena che dovrà essere verificata è invece:
identità + ruolo + tenant + cliente
-> permission/capability server-side
-> ACL query
-> view/API
-> navigation filtrata
-> template passivo
2. Top problemi prioritari¶
Critical/High¶
| Problema | Evidenza | Impatto | Cosa blocca | Dipendenze | Rischio se non risolto |
|---|---|---|---|---|---|
| Menu e accesso endpoint non sono la stessa cosa | add_view_no_menu, menu_runtime.py display-only, API/blueprint con gate propri |
URL/API possono avere comportamento diverso dal menu | nuovo template come controllo di sicurezza | FAB, endpoint, ACL modulo | accesso diretto a funzioni non visibili |
| ACL tenant/cliente distribuite | iam/policy.py insieme alle policy locali di sette moduli; mapping user_cliente/cliente_user |
regole diverse per stessa identità | permission refactor e UI affidabile | tenant, cliente, platform admin, query | isolamento cross-tenant/cross-cliente non uniforme |
| Pipeline menu sovrapposte | FAB, menu_service, menu_runtime, menu_filter, patch bootstrap, Jinja |
precedenze non deterministiche e diagnosi difficile | sostituzione menu o rimozione legacy | label/URL, policy DB, menu_keys |
regressioni silenziose |
| Fallback menu potenzialmente fail-open | ramo MENU_FILTER_FAIL_OPEN in menu_service.py |
errore di policy può mostrare il menu completo | considerare menu come superficie sicura | configurazione, policy DB, gate FAB | esposizione di percorsi/metadati |
| Bootstrap con side effect | app/__init__.py, helper _ensure_*, DB_AUTO_CREATE |
startup, schema, permission e menu sono accoppiati | refactoring senza baseline stabile | Alembic, seed, DEV/PROD | differenze o alterazioni all'avvio |
Medium¶
| Problema | Evidenza | Impatto | Cosa blocca | Dipendenze | Rischio se non risolto |
|---|---|---|---|---|---|
| Catalogo FAB storico sovradimensionato | 625 ViewMenu, 1.422 PermissionView, 221 permission | duplicati e permission obsolete | rinomina/rimozione view | SecurityManager, seed, label | orphan e regressioni |
| Policy basate su label | 140 menu policy, 62 user policy, zero group policy attive, un override | una rinomina può scollegare policy | registry stabile | menu_keys, precedenza policy |
policy ignorate o applicate male |
| Drift dei profili | operatore_osm e operatore_amministro nel DB, non nei default Python osservati |
comportamento dipendente dalla storia | normalizzazione ruoli | seed, entitlement, tenant | differenze tra ambienti |
| Autorizzazione nella UI | baselayout.html, _safeops_sidebar.html, _safeops_menu_tree.html |
Jinja riclassifica/nasconde e duplica RBAC | nuovo template passivo | navigation backend, capability | divergenza UI/backend |
| API e UI senza contratto comune | blueprint, has_access_api e ModelView separati |
stessa funzione può avere policy diversa | capability unica | endpoint, serializer, ACL | bypass tra canali |
3. Sicurezza prima della UI¶
I domini sono ordinati così:
A. Security/Authorization: FAB e gate manuali decidono se una view/metodo/API è invocabile.
B. Data ACL/Tenant isolation: il modulo limita i dati al tenant/cliente dopo avere risolto il contesto.
C. Navigation/Menu: rappresenta la decisione già presa e non concede accesso.
D. Template/UI: rende l'albero e lo stato.
Quindi la dipendenza è:
ACL/contesto -> permission/capability -> view/API -> navigation -> template
Non è sicuro iniziare dal nuovo template come primo intervento sulla navigazione. È possibile progettare la forma visuale in astratto, ma l'implementazione deve attendere almeno baseline endpoint/ACL e un contratto backend per l'albero autorizzato.
4. Modello target¶
L'ipotesi MODULE DEFINITION -> NAVIGATION REGISTRY -> PERMISSION/CAPABILITY -> ACL/TENANT FILTER -> VIEW/API -> SAFEOPS TEMPLATE è adatta come direzione con una correzione: il registry descrive cosa esiste e quale capability richiede; non deve diventare un secondo authorization engine.
FAB può continuare a fornire SecurityManager, ModelView/BaseView, CRUD, ViewMenu e PermissionView. SafeOps deve centralizzare chiavi capability, metadata navigation, contesto tenant/cliente, decisioni di visibilità e reason diagnostica. Non va reinventata la parte RBAC/CRUD di FAB. Nei moduli devono rimanere le regole domain-specifiche, ma su un contesto ACL comune.
5. Permission model¶
Le permission FAB devono essere mantenute per compatibilità e per proteggere concretamente le view. can_edit descrive però un metodo tecnico, non sempre un'operazione business. Capability come documents.manage, ras.compile, courses.manage, commercial.offers e users.admin migliorerebbero la stabilità tra modulo, menu e API, se collegate alle permission FAB.
La scelta raccomandata è ibrida:
- FAB resta il gate tecnico;
- SafeOps introduce capability nominate e stabili come contratto descrittivo;
- ogni capability dichiara le view/API e permission FAB che copre;
- capability assente significa legacy da verificare, non accesso implicito;
- ACL tenant/cliente resta separata dalla capability.
Un secondo catalogo non collegato a FAB aggiungerebbe solo complessità e va evitato.
6. Navigation Registry¶
Un modello concettuale con chiave stabile, modulo, endpoint, parent, icona, ordine e capability è adatto, ma non deve sostituire subito tutte le registrazioni.
Sostituirebbe progressivamente label, parent, ordine e visibilità sparsi. Non sostituirebbe ModelView, BaseView, SecurityManager, route Flask, permission FAB o regole domain-specifiche.
add_viewrimane per registrare la view FAB; un adapter la associa al registry;add_view_no_menudiventa una voce legacy esplicita nel catalogo;add_linkpuò essere rappresentato come item con URL e capability;- il menu legacy resta fallback tramite adapter;
- il registry referenzia capability, ma non concede accesso;
- il template riceve l'albero filtrato e non conosce gli internals FAB.
Il registry va introdotto in shadow mode, con chiavi stabili e confronto old/new.
7. Compatibilità legacy¶
La convivenza temporanea dovrebbe essere:
FAB/menu legacy -> compatibility adapter -> registry per gli item mappati
-> marker legacy/new e confronto snapshot
Il layer deve indicare l'origine di ogni item e lasciare il legacy come fallback esplicito. Non deve introdurre una seconda decisione silenziosa né modificare permission o ACL.
8. Nuovo template SafeOps¶
Il template dovrebbe ricevere:
current_user
navigation_tree
current_section
breadcrumbs
notifications
tenant_context
Può ricevere capability già risolte per badge o diagnostica, ma non deve decidere permission, ACL, tenant visibility o business authorization. Non deve interrogare dati per concedere voci e JavaScript non deve essere un gate.
L'impostazione è confermata dall'audit: _safeops_menu_tree.html e _safeops_sidebar.html fanno oggi troppo; il nuovo template deve limitarsi alla presentazione.
9. Ordine tecnico sicuro¶
STEP A — Baseline congelata. Identificare snapshot DEV/PROD, cataloghi menu/permission, endpoint e ACL. È il primo passo perché entrambi i working tree sono dirty. Abilita test ripetibili. Rischio basso.
STEP B — Matrice endpoint/ruolo/tenant/cliente. Verificare accesso diretto, API, lettura e scrittura, non solo menu. Viene prima del registry perché sicurezza e isolamento dati sono prerequisiti. Abilita capability. Rischio medio: può rivelare blocchi senza correggerli.
STEP C — Contratto capability descrittivo. Associare capability alle permission FAB senza cambiare i gate. Abilita registry e diagnostica. Rischio medio per mapping incompleti.
STEP D — Registry/adapter shadow. Produrre un albero parallelo senza cambiare menu o accessi. Abilita migrazione per modulo. Rischio medio per duplicati e ordine import.
STEP E — Template. Renderizzare l'albero già autorizzato. Solo qui si affronta il nuovo template. Rischio medio/alto se si uniscono ACL o permission.
10. Cosa non toccare ora¶
Non includere RAS/DVR, Documentale, Cliente.users e mapping user_cliente/cliente_user, is_platform_admin, API e blueprint, Formazione/Commerciale legacy, SecurityManager FAB, migration/database/seed, template e JavaScript. Queste aree devono essere catalogate e testate prima di qualunque migrazione.
11. Prima fase implementativa proposta¶
SCOPE: registry read-only in shadow mode per un piccolo sottoinsieme non sensibile, con export old/new e capability descrittive. Nessun cambiamento al menu visibile o all'autorizzazione.
FILE PROBABILI: nuovo modulo sotto app/modules/iam/ o app/modules/navigation/, adapter di lettura accanto a menu_service.py, test isolati. app/__init__.py solo se serve una registrazione disattivabile.
NUOVI COMPONENTI: schema immutabile NavigationItem, registry in-memory, adapter FAB/legacy, diagnostica differenze e flag shadow-mode.
COMPONENTI DA NON TOCCARE: menu_runtime decisionale, menu_filter di produzione, SecurityManager, policy.py, ACL moduli, database, migration, template, JavaScript, menu visibile e permission persistite.
TEST DA SCRIVERE PRIMA: determinismo, chiavi duplicate, parent mancanti, item legacy, URL/endpoint, capability mancanti, fixture DEV/PROD congelate.
TEST DI REGRESSIONE: snapshot menu per admin e utente tenant; accesso diretto a endpoint rappresentativo; prova che shadow mode non cambi g.menu_data_filtered, status, permission o query.
ROLLBACK: disattivare il flag e rimuovere il solo adapter; nessuna modifica dati o migration. FAB e menu attuale devono restare invariati.
DEFINITION OF DONE: export stabile; differenze classificate; nessun cambiamento a HTML, ACL, permission, endpoint o DB; test verdi; modifiche separate dalle preesistenti.
12. Verdetto tecnico finale¶
CURRENT_ARCHITECTURE: FAB come registro tecnico iniziale; menu_service/menu_runtime/policy DB come filtri; Jinja come classificatore ulteriore; ACL distribuite nei moduli.
MAIN_STRUCTURAL_PROBLEM: assenza di una sorgente unica per capability, navigation, permission e ACL.
SECURITY_BLOCKER: YES — menu nascosto e add_view_no_menu non certificano il diniego endpoint.
ACL_BLOCKER: YES — regole tenant/cliente/platform-admin distribuite e non certificate uniformemente.
NAVIGATION_BLOCKER: YES — pipeline multiple, duplicati, policy basate su label e working tree DEV/PROD dirty.
TEMPLATE_BLOCKER: CONDITIONAL — può essere solo grafico, ma l'attuale template contiene decisioni RBAC/tenant.
CAN_START_UI_RESTYLING: CONDITIONAL — solo isolato e senza cambiare autorizzazione.
CAN_START_NAVIGATION_REFACTOR: CONDITIONAL — solo dopo baseline e matrice endpoint/ACL; prima shadow registry.
CAN_START_PERMISSION_REFACTOR: NO — mancano mapping FAB/capability e test negativi.
RECOMMENDED_FIRST_IMPLEMENTATION_PHASE: registry navigation shadow, read-only, piccolo, con adapter FAB/legacy e snapshot.
RISK_LEVEL: alto per modifiche congiunte; basso-medio per registry shadow reversibile.
REASONING: SafeOps può evolvere senza riscrivere FAB o tutti i moduli, ma deve separare prima decisione server-side, ACL dati e presentazione.
Tracciabilità¶
change_summary: creata una revisione decisionale separata basata sul report esistente.
root_cause: trasformare l'audit in decisioni e prerequisiti.
files_changed: docs/tecnico/audit/SAFEOPS_MENU_PERMISSION_DECISION.md
user_impact: nessun cambiamento runtime.
roles_impacted: nessuno.
surfaces_impacted: sola documentazione interna.
behavior_change: nessuno.
permission_change: nessuno.
operator_action_required: autorizzare separatamente la prima fase proposta.
application_code_changed: no.
database_schema_changed: no.
migrations_changed: no.
acl_changed: no.
api_contract_changed: no.
targeted_tests: NOT_RUN — attività documentale.
python_tests: NOT_RUN.
javascript_tests: NOT_RUN.
runtime_smoke: NOT_RUN.
data_preservation: dati e database non modificati.
helper_impact: no_helper_impact.
helper_content_type: NOT_APPLICABLE.
helper_destination: NOT_APPLICABLE.
release_severity: L0.
helper_ready_for_release: NOT_APPLICABLE.
block_release: true — nessuna implementazione autorizzata.
block_reason: revisione decisionale in attesa di autorizzazione.
blockers_remaining: baseline DEV/PROD, matrice endpoint/ACL, capability e registry.
ready_for_dev_test: no.
ready_for_release_candidate: no.
prod_impact: nessuno.
commit_created: no.
tag_created: no.
push_performed: no.
Revisione terminata. Nessuna implementazione eseguita.