diff --git a/HANDOFF.md b/HANDOFF.md index 918375f..6887b0d 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -127,6 +127,32 @@ email di prova e di conferma arrivate, link aperto senza login, account sbloccato; riquadro dopo l'assegnazione con WhatsApp e scelta. Configurazione e dati di prova rimossi dopo. +### Informativa privacy aggiornata e dinamica (19 ottobre 2026) + +Richiesta dell'utente: aggiornare la sezione privacy con i dati attuali e +prendere i parametri dalle impostazioni. Il testo era fermo a una versione +precedente (solo nome e cognome, "tre ruoli: Admin, Servitore di Territorio, +Proclamatore Semplificato" che non esistono). + +- `App\Livewire\Privacy` legge al momento: congregazione, + `audit_retention_days`, `giorni_per_smarrito` (scadenza del link), + `pdf_viewer_show_download`, server di posta (`ConfigurazionePosta`), + `config('app.cipher')`, `config('session.lifetime')`, `APP_URL` (avvisa se + non è HTTPS), i ruoli con i permessi veri (`Privacy::PERMESSI` li spiega) e + quanti dati sono conservati (contati in SQL: i campi cifrati vuoti restano + NULL, non serve decifrare). `Privacy::REVISIONE` è la data del testo. +- Aggiunte: email/telefono cifrati, link alla piantina, invio via WhatsApp + (parte dal telefono di chi assegna) ed email (server di posta configurato), + mappe OpenStreetMap, conferma email, blocco dopo troppi tentativi. +- Titolare e base giuridica sono rimasti com'erano: sono valutazioni legali, + da far rivedere a chi ne ha la competenza. +- **Aperto — backup in chiaro**: il pacchetto di backup usa l'export XML, che + legge i proclamatori dal modello e quindi esporta nome, cognome, email e + telefono **decifrati**. L'informativa ora lo dice; valutare se cifrare + l'archivio (per esempio con una password scelta al download). +- `PrivacyTest` verifica che i valori cambino con le impostazioni, i ruoli, + i conteggi senza nomi e l'accesso solo amministratore. + ### Pagina Assegna più leggera; "Vedi tutti" in testata nella Home (19 ottobre 2026) Segnalazione dell'utente: la pagina Assegnazioni era lenta. Misurato su dev diff --git a/README.md b/README.md index 3282ff3..392acd0 100644 --- a/README.md +++ b/README.md @@ -794,8 +794,8 @@ una revoca ha effetto immediato senza bisogno di ricaricare la pagina. ## Sicurezza e GDPR ### Sicurezza -- **Cifratura a riposo**: nome e cognome dei proclamatori cifrati con AES-256-CBC - tramite `APP_KEY` +- **Cifratura a riposo**: nome, cognome e i facoltativi email e telefono dei + proclamatori cifrati con AES-256-CBC tramite `APP_KEY` - **Password**: hash bcrypt - **RBAC**: middleware `permission:` sulle rotte, riapplicato anche alle richieste Livewire @@ -811,13 +811,24 @@ una revoca ha effetto immediato senza bisogno di ricaricare la pagina. restituisce 404 ### GDPR -- **Minimizzazione**: dei proclamatori si conservano solo nome, cognome e stato +- **Informativa** (menu *Privacy*, solo amministratore): i valori che possono + cambiare — congregazione, conservazione del log, scadenza dei link, + download del PDF, server di posta, durata della sessione, indirizzo e HTTPS, + ruoli con i loro permessi, quanti dati sono conservati — si leggono al + momento da impostazioni e configurazione (`App\Livewire\Privacy`) +- **Minimizzazione**: dei proclamatori si conservano nome, cognome, stato e, + solo se servono per mandare il link, email e telefono - **Cifratura a riposo**: i dati sono illeggibili anche con accesso diretto al DB - **Audit trail**: registro completo delle operazioni -- **Diritto all'oblio**: funzione di anonimizzazione irreversibile +- **Diritto all'oblio**: funzione di anonimizzazione irreversibile, che + cancella anche email e telefono - **Retention**: cancellazione automatica dei log oltre il periodo configurato - **No dati sensibili nei log**: l'audit registra gli identificativi, non i nomi - in chiaro + in chiaro; l'email con il link parte subito, mai in coda, perché il job + salverebbe l'indirizzo in chiaro +- **Attenzione ai backup**: il pacchetto `.zip` e l'export XML contengono i dati + dei proclamatori **in chiaro** (l'export li legge decifrati). Vanno custoditi + in un luogo protetto e cancellati quando non servono --- diff --git a/app/Livewire/Privacy.php b/app/Livewire/Privacy.php index a9cb2f0..a97aaf0 100644 --- a/app/Livewire/Privacy.php +++ b/app/Livewire/Privacy.php @@ -2,18 +2,86 @@ namespace App\Livewire; -use Livewire\Component; +use App\Models\Assegnazione; +use App\Models\Proclamatore; use App\Models\Setting; +use App\Models\User; +use App\Services\ConfigurazionePosta; +use Livewire\Component; +use Spatie\Activitylog\Models\Activity; +use Spatie\Permission\Models\Role; +/** + * Informativa privacy (solo amministratore). + * + * Tutto ciò che può cambiare si legge qui, al momento: impostazioni + * (congregazione, conservazione del log, scadenza dei link, download del + * PDF, server di posta), configurazione (indirizzo, cifratura, durata della + * sessione), ruoli con i loro permessi reali e quanti dati sono conservati. + * Il testo resta nella vista; i numeri non vanno mai scritti a mano. + */ class Privacy extends Component { + /** Data dell'ultima revisione del testo dell'informativa. */ + public const REVISIONE = '19 ottobre 2026'; + + /** I permessi spiegati a chi legge l'informativa. */ + public const PERMESSI = [ + 'settings.manage' => 'impostazioni, utenti, backup e import', + 'proclamatori.manage' => 'anagrafica dei proclamatori, contatti compresi', + 'territori.manage' => 'gestione dei territori e delle piantine', + 'campagne.manage' => 'campagne', + 'registro.view' => 'consultazione del registro delle assegnazioni', + 'registro.export' => 'esportazione del registro', + 'territori.assign' => 'assegnazione dei territori', + 'territori.return' => 'rientro dei territori', + 'audit.view' => 'consultazione del registro delle attività', + 'audit.export' => 'esportazione del registro delle attività', + ]; + public function render() { - $settings = Setting::instance(); + $impostazioni = Setting::instance(); + $posta = app(ConfigurazionePosta::class); + + $hostPosta = $posta->configurataNellApp() + ? (string) $impostazioni->mail_host + : (string) config('mail.mailers.smtp.host'); + + $indirizzo = (string) config('app.url'); return view('livewire.privacy', [ - 'congregazione' => $settings->congregazione_nome ?? 'Congregazione', - 'auditRetention' => $settings->audit_retention_days ?? 365, + 'revisione' => self::REVISIONE, + 'congregazione' => $impostazioni->congregazione_nome ?: 'Congregazione', + 'indirizzo' => $indirizzo, + 'https' => str_starts_with($indirizzo, 'https://'), + 'cifratura' => (string) config('app.cipher'), + 'durataSessione' => (int) config('session.lifetime'), + 'auditRetention' => (int) ($impostazioni->audit_retention_days ?: 365), + 'scadenzaLink' => max(1, (int) ($impostazioni->giorni_per_smarrito ?: 120)), + 'downloadPdf' => (bool) $impostazioni->pdf_viewer_show_download, + 'postaAttiva' => $posta->funzionante(), + 'hostPosta' => $hostPosta !== '' ? $hostPosta : null, + 'ruoli' => Role::with('permissions')->orderBy('name')->get()->map(fn (Role $r) => [ + 'nome' => $r->name, + 'permessi' => $r->permissions->pluck('name') + ->map(fn ($p) => self::PERMESSI[$p] ?? $p) + ->all(), + ]), + // Quanti dati sono conservati adesso. I campi cifrati restano NULL + // quando sono vuoti, quindi si contano in SQL senza decifrare. + 'conteggi' => [ + 'proclamatori' => Proclamatore::count(), + 'proclamatoriAttivi' => Proclamatore::where('attivo', true)->count(), + 'conEmail' => Proclamatore::whereNotNull('email')->count(), + 'conTelefono' => Proclamatore::whereNotNull('telefono')->count(), + 'anonimizzati' => Proclamatore::onlyTrashed()->count(), + 'utenti' => User::count(), + 'assegnazioni' => Assegnazione::count(), + 'assegnazioniAperte' => Assegnazione::aperte()->count(), + 'attivita' => Activity::count(), + 'attivitaDal' => Activity::min('created_at'), + ], ]); } } diff --git a/resources/views/livewire/privacy.blade.php b/resources/views/livewire/privacy.blade.php index f5a4ceb..a35e388 100644 --- a/resources/views/livewire/privacy.blade.php +++ b/resources/views/livewire/privacy.blade.php @@ -1,6 +1,9 @@
Testo rivisto il {{ $revisione }} · valori aggiornati in tempo reale dalle impostazioni
I dati personali sono trattati esclusivamente per:
Il trattamento è basato sul legittimo interesse dell'organizzazione religiosa nella gestione delle proprie attività interne (Art. 6(1)(f) GDPR) e sull'appartenenza volontaria dei proclamatori alla congregazione (Art. 9(2)(d) GDPR).
+L'accesso è riservato agli utenti dell'applicazione, ciascuno secondo il proprio ruolo:
+Il proclamatore non ha un account: riceve solo il link alla piantina del territorio che gli è assegnato.
+{{ $indirizzo }}@if($https), su connessione cifrata (HTTPS)@else. Attenzione: l'indirizzo non usa HTTPS, le comunicazioni non sono cifrate.@endif.{{ $hostPosta ?? 'configurato' }}, che riceve l'indirizzo e il nome del proclamatore.
+ @else
+ nessun server di posta configurato: l'applicazione non invia email.
+ @endif
+ Le richieste possono essere inoltrate al Servitore di Territorio o all'Amministratore dell'applicazione.
+Le richieste vanno rivolte agli amministratori dell'applicazione della congregazione {{ $congregazione }}.