Audit del modulo Formazione — 12 settembre 2026¶
Esito: modulo operativo, con un difetto critico di isolamento tenant e cinque difetti ad alta priorità da correggere. Le pagine principali funzionano e i servizi automatici risultano attivi; questo non equivale a una validazione completa dei controlli di accesso e dei processi di iscrizione/firma.
Raccolta iniziata il 10 settembre, controlli dati e servizi aggiornati il 12 settembre. Verifica principale su Besant; confronto dei dati con Retra e Fornautic. Nessun rilascio, import definitivo, invio email, sincronizzazione manuale o modifica dei dati operativi effettuato durante questo audit.
Perimetro ed evidenze¶
- Inventario statico: 15 file Python, 24.522 righe e 140 route esplicite; revisione mirata anche dell'import in Anagrafiche, dei filtri FAB installati e dei template. L'inventario non include le route CRUD generate automaticamente da Flask-AppBuilder.
- I 15 file censiti sono identici fra produzione e sviluppo e non sono cambiati durante la raccolta. Questo confronto non certifica l'allineamento di tutto il repository.
- 35 template Jinja analizzati senza errori sintattici.
- 28 richieste HTTP iniziali: 22 pagine amministrative operative, 2 tentativi su corso di altro tenant, 3 controlli anonimi e un token tablet invalido. Successiva verifica mirata con un operatore Formazione non amministratore globale.
- 57 test esistenti superati; 2 ulteriori test positivi su PDF e ZIP superati. Cinque prove di invarianti desiderati falliscono in modo atteso (
xfail, limitato adAssertionError): riproducono difetti presenti, non sono correzioni né test superati. - Test eseguiti con SQLite in memoria e caricamento delle credenziali operative disabilitato. Il bootstrap emette avvisi su DDL MySQL non compatibile con SQLite: non vengono interpretati come errori della produzione. Non è un collaudo del database MySQL o del deploy.
- Dati: query aggregate in transazione di sola lettura su MySQL
appdb. Nessun nominativo, credenziale, firma o documento reale copiato nel rapporto. - Servizi e journal: lettura degli ultimi sette giorni. I conteggi preliminari basati sulla parola
failedincludevano anche lo stato ordine WooCommerce omonimo: sono stati esclusi dai risultati conclusivi.
Evidenze riproducibili: cartella audit, inventario, HTTP, controllo permessi, dati, integrità aggiuntiva, servizi, test, riproduzioni sintetiche.
Problemi e priorità¶
P0 = critico, intervento prioritario; P1 = alto, prima di ampliare l'utilizzo operativo; P2 = medio, consolidamento funzionale o tecnico. Le priorità non sono punteggi CVSS.
| ID | Priorità | Riscontro | Evidenza |
|---|---|---|---|
| A01 | P0 | Un operatore Besant legge un corso Retra tramite la pagina CRUD standard | HTTP con profilo operatore_formazione, titolo univoco visibile |
| A02 | P1 | La revoca del link tablet non viene verificata dal registro pubblico | Codice e riproduzione sintetica F02 |
| A03 | P1 | Le date selezionate nell'import non limitano i partecipanti della giornata | Codice e riproduzione F01 |
| A04 | P1 | Un invio parziale del registro tablet può cancellare firme esistenti | Riproduzione F05 |
| A05 | P1 | L'import CSV può aggiornare un discente fuori dal perimetro visibile | Riproduzione F03 |
| A06 | P1 | Un attestato individuale viene associato anche alle aziende di altri partecipanti | Riproduzione F04 e revisione del fascicolo |
| A07 | P2 | Due partecipanti risultano convertiti ma senza iscrizione collegata | Query aggregate; residui coerenti con la pulizia documentata |
| A08 | P2 | Completamento e validazione non applicano tutti i controlli del percorso dichiarato | Revisione delle azioni e della documentazione |
| A09 | P2 | I job WooCommerce possono terminare con successo anche dopo errori applicativi | Revisione CLI; non osservato un guasto corrente nei log esaminati |
| A10 | P2 | Clienti e Discenti hanno tempi di risposta elevati | Campione HTTP e caricamento completo prima della paginazione |
| A11 | P2 | L'import diretto non applica la verifica di capienza usata dall'iscrizione manuale | Confronto dei percorsi di scrittura |
| A12 | P2 | Alcune azioni modificano dati o sistemi esterni tramite GET | Revisione route, senza invocarle |
A01 — Isolamento tenant nelle pagine standard¶
Con un utente attivo assegnato a Besant con profilo operatore_formazione, privo del ruolo globale Admin, la richiesta /corsoview/show/18 restituisce HTTP 200 e il titolo univoco di un corso Retra. La lista moderna /formazione-corsi/ non contiene quel titolo; /corsoview/calendario/18 respinge la richiesta con redirect. Il titolo scelto non coincide con alcun corso Besant: il controllo evita il falso positivo di un codice numerico presente genericamente nell'HTML.
La classe _TenantScopedModelView implementa get_query/get_count_query, ma il Flask-AppBuilder installato usa datamodel.query(... _base_filters ...) e, per show/edit/delete, datamodel.get(pk, self._base_filters). Quegli override non proteggono tali percorsi. Il problema riguarda il modello comune usato da più anagrafiche Formazione; la lettura del corso è confermata live, le scritture fuori tenant non sono state esercitate.
Intervento: applicare il filtro tenant nel datamodel/base filters effettivamente utilizzato da FAB e nelle relazioni selezionabili; verificare tutte le operazioni standard e le azioni su ID. L'assenza del tenant operativo deve produrre una decisione esplicita, non una query globale implicita.
Accettazione: con utenti dei tenant A e B, lista/show/edit/delete/export/azioni devono negare gli ID estranei; il controllo va ripetuto sulle pagine moderne e su quelle standard. È la prima correzione raccomandata.
A02 — Revoca tablet inefficace sul link già distribuito¶
revoca_link_tablet registra revoked_at in formazione_tablet_register_access. La dashboard nasconde le date revocate, ma _load_training_register_context non consulta questa tabella. Il possessore del link conserva quindi il percorso pubblico finché il token resta valido. Il loader verifica firma/scadenza del token e appartenenza corso/data; la route verifica anche l'abilitazione del modulo, ma non la revoca.
Nel database esaminato non ci sono revoche registrate: è un difetto confermato del controllo, non un episodio di accesso abusivo osservato. La prova F02 mostra l'assenza della verifica di revoca nel percorso di caricamento.
Intervento e accettazione: controllo server condiviso per GET e POST, con test di link valido, scaduto, revocato e di altro corso. Revoca e riemissione devono avere una semantica esplicita. Aggiornare l'helper del pulsante Revoca.
A03 — Iscrizione al corso e partecipazione alla giornata non sono separate¶
La selezione appena pubblicata valida le date e prepara righe DipendenteCorsoFirma; l'iscrizione rimane unica per coppia (corso_id, dipendente_id). Il registro tablet carica tutte le iscrizioni del corso e filtra per giornata soltanto le firme. Il PDF del registro parte anch'esso da tutti i partecipanti del corso.
Quindi selezionare una giornata nell'import non equivale ancora a prenotare il discente esclusivamente per quella giornata. La verifica è importante soprattutto quando uno stesso corso di catalogo contiene più edizioni alternative. La prova F01 riproduce due iscritti mostrati nella giornata di uno solo.
Intervento: definire un'associazione esplicita iscrizione–edizione/giornate, distinta dalle presenze, e usarla per registro, tablet, PDF e capienza. Chiarire anche il caso di frequenza ripetuta dello stesso corso. Migrare i dati preservando firme e storico.
Accettazione: discenti di giornate/edizioni diverse compaiono solo nei rispettivi registri; corsi realmente multigiornata mantengono l'intero gruppo previsto. Correggere contestualmente l'helper dell'import.
A04 — Salvataggio tablet distruttivo per campi assenti¶
training_register_tablet itera su tutti gli iscritti e sugli slot della giornata. Un campo assente viene interpretato come falso e può azzerare firmato_at, firmato_by e firma_data_url. La prova F05 invia un POST vuoto su un registro sintetico già firmato: la firma viene cancellata.
L'effetto può riguardare invii parziali o aggiornamenti concorrenti da due dispositivi; non è stato provocato su firme reali. Distinguere campo non inviato da cancellazione esplicita, adottare aggiornamenti mirati e controllo di versione/concorrenza. La rimozione di una firma deve essere un'azione autorizzata e tracciata. Testare anche il registro amministrativo, che ha logica analoga.
A05 — Matching CSV più ampio dell'anteprima¶
L'anteprima usa _scoped_query; _import_rows cerca invece in Dipendente senza filtro visibilità/tenant, prima per CF e poi per nome/cognome, eventualmente data di nascita. Può quindi aggiornare email e altri dati di una persona non visibile all'operatore e collegarla al nuovo cliente. La prova F03 riproduce l'aggiornamento con oggetti sintetici appartenenti a tenant diversi.
La condivisione intenzionale di un'identità fra più aziende richiede una policy esplicita e non giustifica una modifica automatica di dati fuori perimetro. Unificare risoluzione e scrittura, bloccare omonimie ambigue e chiedere una scelta esplicita quando il CF manca. Proteggere anche il matching analogo presente nel flusso WooCommerce.
A06 — Destinatari degli attestati e fascicolo individuale troppo ampi¶
genera_attestato_iscrizione usa _resolve_clienti_for_document, che raccoglie le aziende del corso e di tutti i partecipanti. La prova F04 genera un documento sintetico per una persona dell'azienda A e ne osserva l'associazione anche all'azienda B.
Inoltre _docs_by_corso, usato dal fascicolo discente, recupera i documenti dei corsi senza distinguere il titolare dei singoli attestati. Questo rende possibile includere nel fascicolo di un discente gli attestati di altri iscritti dello stesso corso.
Intervento: collegamento esplicito documento–iscrizione/discente, aziende destinatarie limitate a quelle pertinenti e distinzione fra documenti comuni del corso e individuali. Verificare sia i download documentali sia lo ZIP. L'audit dimostra l'associazione errata con dati sintetici; non certifica che un cliente reale abbia già scaricato documenti altrui.
A07–A12 — Consolidamento operativo¶
| ID | Dettaglio e intervento |
|---|---|
| A07 | Due FormazioneQuoteParticipant Besant hanno stato converted_to_enrollment e iscrizione nulla/non esistente. La pulizia del 2 settembre documenta la cancellazione delle partecipazioni e lo scollegamento di riferimenti di test. Non è prova di perdita recente. Archiviare o riallineare gli stati residui dopo confronto con backup; non ricreare iscrizioni alla cieca. |
| A08 | completa imposta esito/data/scadenza senza verificare ore, firme o valutazione; la validazione corso richiede un documento di evidenza ma non tutti gli step del percorso documentato. Due iscrizioni Retra completate non hanno firme digitali; una prova esterna può legittimamente sostituirle. Definire quali controlli sono obbligatori e quali eccezioni documentabili, poi centralizzarli. |
| A09 | Nel job CLI, gli errori di recupero ordini vengono stampati e ignorati prima del riepilogo; anche errori di push vengono contati senza exit non-zero. Il filtro after riguarda la creazione degli ordini: verificare il recupero delle modifiche a ordini vecchi tramite webhook o riconciliazione. Rendere l'esito rilevabile da systemd/monitoring. Nei log conclusivi esaminati non è stato osservato un errore effettivo di recupero. |
| A10 | Nel campione HTTP: Clienti 37,47 s, Discenti 27,70 s; altre pagine prevalentemente 3–6 s. È un singolo campione con massimo tre richieste concorrenti, non un benchmark/SLA. In _portfolio_context si caricano tutti i clienti e si attraversano relazioni prima della paginazione. Spostare filtri/paginazione in SQL, aggregare i contatori e profilare le query; ripetere la misura prima/dopo. |
| A11 | L'import CSV crea DipendenteCorso senza eseguire la verifica di capienza presente in DipendenteCorsoView.pre_add. Portare prenotazione e controllo posti in un servizio comune, con transazione e gestione della concorrenza. Verificare capienza per edizione/giornata, non solo totale di catalogo. Nessun overbooking reale è stato creato nell'audit. |
| A12 | create_or_link_discente, sync-variation e generazione registro hanno URL GET con effetti di scrittura locali o esterni. Usare POST con controllo CSRF e permesso adeguato. Le azioni non sono state invocate durante l'audit. |
Stato dati e integrazioni¶
Snapshot del 12 settembre su appdb:
| Indicatore | Besant | Retra | Fornautic |
|---|---|---|---|
| Corsi attivi | 34 | 19 | 1 |
| Date calendario | 99 | 2 | 0 |
| Iscrizioni operative | 0 | 4 | 1 |
| Corsi attivi senza date | 7 | 18 | 1 |
| Corsi attivi senza docenti associati | 6 | 18 | 1 |
| Iscrizioni completate | 0 | 2 | 0 |
Besant ha 1.060 record di storico formativo, 6 mappe di richiesta/iscrizione WooCommerce, 4 prenotazioni e 5 partecipanti a preventivo. L'assenza di iscrizioni operative è coerente con la pulizia documentata e non significa assenza di storico. Sei edizioni Besant attive non hanno giornate; sette corsi hanno capienza non positiva. I corsi di catalogo non ancora pianificati vanno mostrati come tali, senza equipararli a corsi pronti.
Controlli positivi: nessun CF non vuoto duplicato nel controllo normalizzato per maiuscole/spazi esterni; nessuna iscrizione orfana, nessuna firma riferita a una data di altro corso, nessuna associazione edizione/data su corsi diversi. Date presenti senza anomalie di orari incompleti/invertiti o assenza di aula nei controlli eseguiti. Nessun documento corso con chiave storage pending/nulla; l'esistenza dell'oggetto nello storage non è stata verificata.
Le due iscrizioni Retra prive di collegamento aziendale nel tenant richiedono classificazione: partecipanti privati o associazioni mancanti. Non sono automaticamente una violazione di accesso.
Database con collazioni miste (utf8mb4_unicode_ci / utf8mb4_0900_ai_ci): una query diagnostica di confronto tenant ha restituito errore 1267. Il controllo è stato ripetuto con confronto binario, senza anomalie trovate. È debito tecnico da normalizzare, non prova di una pagina oggi guasta.
La combo dell'import pubblicato restituisce 2.998 aziende, 34 corsi e 99 opzioni data. La correzione della precedenza amministratore/tecnico risulta efficace nel controllo effettuato.
Web, Celery, beat e i tre timer WooCommerce sono attivi all'ultima verifica. I servizi oneshot WooCommerce sono normalmente inactive/dead dopo conclusione con Result=success: non è un guasto. Ultimo full sync del 12 settembre completato; il log applicativo registra sincronizzazioni catalogo, varianti e disponibilità. Segreti necessari a webhook e token configurati: sono state verificate solo presenza e logica, mai riportati i valori. Firma HMAC e casi di normalizzazione webhook sono coperti dai test esistenti.
Verifiche riuscite e limiti¶
Registri e attestati sintetici vengono generati come PDF leggibili; lo ZIP sintetico contiene manifest e nomi percorso sanitizzati. Artefatti: registro, attestato. Non è stato svolto un collaudo grafico su tutte le varianti di attestato o su un registro reale con firme.
Non sono stati eseguiti: invii email, pagamenti/rimborsi, scritture WooCommerce, emissione attestati reali, caricamento di documenti personali, prove distruttive o ripristino di backup. Non è stata cambiata la configurazione dei ruoli per costruire una matrice completa: il controllo live usa profili esistenti. La tenuta completa dello stato modulo readonly, ogni ruolo cliente/docente e le relazioni fra database centrale e database dedicati restano da collaudare in ambiente isolato.
L'audit offre una copertura trasversale di codice, pagine, dati, servizi e processi; non certifica la conformità normativa dei corsi né sostituisce una prova completa con un corso reale dall'acquisto all'archiviazione.
Piano di intervento¶
- Isolamento e documenti: A01 e A05/A06, con test negativi tra tenant e aziende per tutte le superfici.
- Registro e giornate: A02–A04; definire il rapporto corso/edizione/giornata prima di ulteriori import massivi. Aggiornare l'helper insieme alla correzione.
- Integrità dei processi: A07/A08/A11, con riconciliazione dei residui di test e criteri di completamento concordati.
- Operatività: A09/A10/A12, profiling, esiti job affidabili e conversione delle azioni GET.
- Collaudo finale in sviluppo: corso monoaziendale e multiaziendale, edizioni alternative e più giornate, pagamento/richiesta preventivo simulati, import con omonimie, capienza piena, tablet concorrenti, revoca, documenti individuali, attestazione, scadenza e ZIP. Ripetere i test di isolamento prima della promozione.
Raccomandazione: risolvere A01 prima di nuove estensioni operative e completare i controlli P1 prima di considerare concluso il flusso giornate–registro–attestati. Nessuna correzione applicativa è inclusa in questo audit.
Governance del task di audit¶
change_summary: audit completo del perimetro Formazione e rapporto con evidenze
user_impact: nessun comportamento applicativo modificato durante l'audit
roles_impacted: nessuno modificato; analizzati admin, operatore formazione e anonimo
surfaces_impacted: documentazione audit, prove isolate e report
behavior_change: nessuno
permission_change: nessuna
operator_action_required: pianificare correzioni A01–A12 secondo priorità
helper_impact: no_helper_impact
helper_content_type: troubleshooting
release_severity: L0
helper_ready_for_release: true
block_release: false
block_reason: nessun rilascio applicativo in questo task; criticità del modulo descritte nel rapporto
review_helper_impact_ok: true
review_helper_severity_ok: true
review_missing_helper_update: aggiornare helper contestuali insieme alle future correzioni A02 e A03
review_notes: esito dell'audit non equivale ad approvazione dei flussi affetti da P0/P1