Vai al contenuto

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_view rimane per registrare la view FAB; un adapter la associa al registry;
  • add_view_no_menu diventa una voce legacy esplicita nel catalogo;
  • add_link può 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.