Vai al contenuto

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 ad AssertionError): 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 failed includevano 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.

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

  1. Isolamento e documenti: A01 e A05/A06, con test negativi tra tenant e aziende per tutte le superfici.
  2. Registro e giornate: A02–A04; definire il rapporto corso/edizione/giornata prima di ulteriori import massivi. Aggiornare l'helper insieme alla correzione.
  3. Integrità dei processi: A07/A08/A11, con riconciliazione dei residui di test e criteri di completamento concordati.
  4. Operatività: A09/A10/A12, profiling, esiti job affidabili e conversione delle azioni GET.
  5. 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