++ informativa privacy aggiornata, valori dalle impostazioni

- Dati trattati attuali: email e telefono cifrati, link alla piantina,
  stato dell'invio, conferma email, log senza nomi.
- Valori letti al momento: congregazione, conservazione del log, scadenza
  del link, download del PDF, server di posta, cifratura, durata della
  sessione, indirizzo/HTTPS, ruoli con i permessi veri, conteggi dei dati.
- Servizi esterni: WhatsApp, posta, OpenStreetMap.
- Dichiarato che backup ed export XML contengono i dati in chiaro.
README e HANDOFF aggiornati.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Francesco Picone
2026-10-19 15:58:35 +02:00
parent 840fbe4c16
commit 034329471b
5 changed files with 291 additions and 24 deletions

View File

@@ -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 sbloccato; riquadro dopo l'assegnazione con WhatsApp e scelta. Configurazione
e dati di prova rimossi dopo. 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) ### Pagina Assegna più leggera; "Vedi tutti" in testata nella Home (19 ottobre 2026)
Segnalazione dell'utente: la pagina Assegnazioni era lenta. Misurato su dev Segnalazione dell'utente: la pagina Assegnazioni era lenta. Misurato su dev

View File

@@ -794,8 +794,8 @@ una revoca ha effetto immediato senza bisogno di ricaricare la pagina.
## Sicurezza e GDPR ## Sicurezza e GDPR
### Sicurezza ### Sicurezza
- **Cifratura a riposo**: nome e cognome dei proclamatori cifrati con AES-256-CBC - **Cifratura a riposo**: nome, cognome e i facoltativi email e telefono dei
tramite `APP_KEY` proclamatori cifrati con AES-256-CBC tramite `APP_KEY`
- **Password**: hash bcrypt - **Password**: hash bcrypt
- **RBAC**: middleware `permission:` sulle rotte, riapplicato anche alle - **RBAC**: middleware `permission:` sulle rotte, riapplicato anche alle
richieste Livewire richieste Livewire
@@ -811,13 +811,24 @@ una revoca ha effetto immediato senza bisogno di ricaricare la pagina.
restituisce 404 restituisce 404
### GDPR ### 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 - **Cifratura a riposo**: i dati sono illeggibili anche con accesso diretto al DB
- **Audit trail**: registro completo delle operazioni - **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 - **Retention**: cancellazione automatica dei log oltre il periodo configurato
- **No dati sensibili nei log**: l'audit registra gli identificativi, non i nomi - **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
--- ---

View File

@@ -2,18 +2,86 @@
namespace App\Livewire; namespace App\Livewire;
use Livewire\Component; use App\Models\Assegnazione;
use App\Models\Proclamatore;
use App\Models\Setting; 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 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() 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', [ return view('livewire.privacy', [
'congregazione' => $settings->congregazione_nome ?? 'Congregazione', 'revisione' => self::REVISIONE,
'auditRetention' => $settings->audit_retention_days ?? 365, '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'),
],
]); ]);
} }
} }

View File

@@ -1,6 +1,9 @@
<div> <div>
{{-- Ogni valore che può cambiare arriva da Privacy::render() (impostazioni,
configurazione, ruoli, conteggi): qui non si scrivono numeri a mano. --}}
<div class="max-w-3xl mx-auto"> <div class="max-w-3xl mx-auto">
<h1 class="text-2xl font-bold text-gray-900 mb-6">Informativa Privacy</h1> <h1 class="text-2xl font-bold text-gray-900 mb-1">Informativa Privacy</h1>
<p class="text-xs text-gray-500 mb-6">Testo rivisto il {{ $revisione }} · valori aggiornati in tempo reale dalle impostazioni</p>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 md:p-8 space-y-6 text-sm text-gray-700 leading-relaxed"> <div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 md:p-8 space-y-6 text-sm text-gray-700 leading-relaxed">
@@ -13,10 +16,21 @@
<section> <section>
<h2 class="text-lg font-semibold text-gray-900 mb-2">Dati trattati</h2> <h2 class="text-lg font-semibold text-gray-900 mb-2">Dati trattati</h2>
<ul class="list-disc list-inside space-y-1"> <ul class="list-disc list-inside space-y-1">
<li><strong>Proclamatori</strong>: nome e cognome — conservati in forma crittografata (AES-256-CBC) nel database.</li> <li><strong>Proclamatori</strong>: nome e cognome; facoltativamente email e numero di telefono, usati solo per mandare il link della piantina. Tutti cifrati nel database ({{ $cifratura }}).</li>
<li><strong>Territori</strong>: numero, località, zona, tipologia, mappe PDF — nessun dato personale.</li> <li><strong>Territori e piantine</strong>: numero, zona, tipologia, confini, mappe e PDF — nessun dato personale.</li>
<li><strong>Assegnazioni</strong>: collegamento tra proclamatore e territorio con date di assegnazione e rientro.</li> <li><strong>Assegnazioni</strong>: collegamento tra proclamatore e territorio, con date di assegnazione e rientro, conteggio nelle campagne, codice del link alla piantina e stato del suo invio (inviato o no, e con quale canale).</li>
<li><strong>Utenti</strong>: nome, email e ruolo per l'accesso all'applicazione.</li> <li><strong>Utenti dell'applicazione</strong>: nome, email (che va confermata), password conservata solo come impronta non reversibile, ruolo.</li>
<li><strong>Registro delle attività</strong>: chi ha fatto cosa e quando. I proclamatori vi compaiono solo con un identificativo numerico, mai con nome, numero o email.</li>
</ul>
</section>
<section>
<h2 class="text-lg font-semibold text-gray-900 mb-2">Dati conservati oggi</h2>
<ul class="list-disc list-inside space-y-1">
<li>{{ $conteggi['proclamatori'] }} proclamatori, di cui {{ $conteggi['proclamatoriAttivi'] }} attivi; {{ $conteggi['conEmail'] }} con email e {{ $conteggi['conTelefono'] }} con numero di telefono; {{ $conteggi['anonimizzati'] }} nel cestino o anonimizzati.</li>
<li>{{ $conteggi['assegnazioni'] }} assegnazioni nello storico, di cui {{ $conteggi['assegnazioniAperte'] }} in corso.</li>
<li>{{ $conteggi['utenti'] }} utenti dell'applicazione.</li>
<li>{{ $conteggi['attivita'] }} voci nel registro delle attività@if($conteggi['attivitaDal']), dal {{ \Illuminate\Support\Carbon::parse($conteggi['attivitaDal'])->format('d/m/Y') }}@endif.</li>
</ul> </ul>
</section> </section>
@@ -25,6 +39,7 @@
<p>I dati personali sono trattati esclusivamente per:</p> <p>I dati personali sono trattati esclusivamente per:</p>
<ul class="list-disc list-inside space-y-1"> <ul class="list-disc list-inside space-y-1">
<li>Organizzare l'assegnazione e il rientro dei territori di predicazione.</li> <li>Organizzare l'assegnazione e il rientro dei territori di predicazione.</li>
<li>Mandare al proclamatore il link della piantina del territorio assegnato.</li>
<li>Monitorare la copertura territoriale per anno teocratico.</li> <li>Monitorare la copertura territoriale per anno teocratico.</li>
<li>Gestire le campagne speciali di predicazione.</li> <li>Gestire le campagne speciali di predicazione.</li>
</ul> </ul>
@@ -35,24 +50,60 @@
<p>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).</p> <p>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).</p>
</section> </section>
<section>
<h2 class="text-lg font-semibold text-gray-900 mb-2">Chi accede ai dati</h2>
<p>L'accesso è riservato agli utenti dell'applicazione, ciascuno secondo il proprio ruolo:</p>
<ul class="list-disc list-inside space-y-1">
@foreach($ruoli as $ruolo)
<li><strong>{{ ucfirst($ruolo['nome']) }}</strong>: {{ $ruolo['permessi'] ? implode('; ', $ruolo['permessi']) : 'nessun permesso' }}.</li>
@endforeach
</ul>
<p class="mt-2">Il proclamatore non ha un account: riceve solo il link alla piantina del territorio che gli è assegnato.</p>
</section>
<section> <section>
<h2 class="text-lg font-semibold text-gray-900 mb-2">Misure di sicurezza</h2> <h2 class="text-lg font-semibold text-gray-900 mb-2">Misure di sicurezza</h2>
<ul class="list-disc list-inside space-y-1"> <ul class="list-disc list-inside space-y-1">
<li><strong>Crittografia</strong>: i dati personali (nome/cognome) sono crittografati con AES-256-CBC prima della memorizzazione.</li> <li><strong>Cifratura</strong>: nome, cognome, email e telefono dei proclamatori sono cifrati con {{ $cifratura }} prima di essere salvati; la chiave non sta nel database.</li>
<li><strong>Controllo accessi</strong>: sistema RBAC con tre ruoli (Admin, Servitore di Territorio, Proclamatore Semplificato).</li> <li><strong>Accesso</strong>: password personale, conferma dell'email, blocco temporaneo dopo troppi tentativi; la sessione scade dopo {{ $durataSessione }} minuti di inattività.</li>
<li><strong>Audit trail</strong>: tutte le operazioni sono registrate e conservate per {{ $auditRetention }} giorni.</li> <li><strong>Permessi</strong>: ogni ruolo vede e modifica solo ciò che gli serve (vedi sopra).</li>
<li><strong>Comunicazioni sicure</strong>: l'accesso avviene tramite HTTPS.</li> <li><strong>Registro delle attività</strong>: le operazioni sono registrate e conservate per {{ $auditRetention }} giorni.</li>
<li><strong>Hosting locale</strong>: i dati risiedono sull'infrastruttura della congregazione, non su servizi cloud di terze parti.</li> <li><strong>Comunicazioni</strong>: l'applicazione risponde all'indirizzo <code>{{ $indirizzo }}</code>@if($https), su connessione cifrata (HTTPS)@else. <span class="text-red-700 font-medium">Attenzione: l'indirizzo non usa HTTPS, le comunicazioni non sono cifrate.</span>@endif.</li>
<li><strong>Hosting</strong>: i dati risiedono sul server gestito dagli amministratori dell'applicazione, non su servizi cloud di terze parti.</li>
</ul>
</section>
<section>
<h2 class="text-lg font-semibold text-gray-900 mb-2">Link alla piantina</h2>
<ul class="list-disc list-inside space-y-1">
<li>Il link è personale e funziona solo finché il territorio è assegnato, al massimo per {{ $scadenzaLink }} giorni dall'assegnazione.</li>
<li>Mostra la piantina del territorio, senza dati personali; il PDF {{ $downloadPdf ? 'si può anche scaricare' : 'si può solo consultare, non scaricare' }}.</li>
</ul>
</section>
<section>
<h2 class="text-lg font-semibold text-gray-900 mb-2">Servizi esterni</h2>
<ul class="list-disc list-inside space-y-1">
<li><strong>WhatsApp</strong>: se si sceglie questo canale, il messaggio con il nome del proclamatore e il link parte dal WhatsApp di chi assegna il territorio, non dall'applicazione.</li>
<li><strong>Email</strong>:
@if($postaAttiva)
il link viene inviato dal server di posta <code>{{ $hostPosta ?? 'configurato' }}</code>, che riceve l'indirizzo e il nome del proclamatore.
@else
nessun server di posta configurato: l'applicazione non invia email.
@endif
</li>
<li><strong>Mappe</strong>: le piantine usano le mappe di OpenStreetMap; per disegnarle si scaricano tessere dai server di OpenStreetMap, senza inviare dati personali.</li>
</ul> </ul>
</section> </section>
<section> <section>
<h2 class="text-lg font-semibold text-gray-900 mb-2">Conservazione dei dati</h2> <h2 class="text-lg font-semibold text-gray-900 mb-2">Conservazione dei dati</h2>
<ul class="list-disc list-inside space-y-1"> <ul class="list-disc list-inside space-y-1">
<li>I dati dei proclamatori inattivi possono essere anonimizzati su richiesta.</li> <li>I dati di un proclamatore possono essere anonimizzati su richiesta: nome, cognome, email e telefono vengono cancellati in modo irreversibile.</li>
<li>I territori eliminati sono conservati nel cestino (soft-delete) prima dell'eliminazione definitiva.</li> <li>Territori e proclamatori eliminati restano nel cestino finché non vengono eliminati definitivamente.</li>
<li>I log di audit sono conservati per <strong>{{ $auditRetention }} giorni</strong>, configurabili nelle impostazioni.</li> <li>Il registro delle attività è conservato per <strong>{{ $auditRetention }} giorni</strong> (modificabile nelle impostazioni), poi cancellato automaticamente.</li>
<li>Lo storico assegnazioni è conservato per la durata necessaria alla gestione territoriale.</li> <li>Lo storico delle assegnazioni è conservato per la durata necessaria alla gestione territoriale.</li>
<li>I backup e gli export XML scaricati dagli amministratori contengono i dati dei proclamatori <strong>in chiaro</strong> (nome, cognome, email, telefono): vanno custoditi in un luogo protetto e cancellati quando non servono più.</li>
</ul> </ul>
</section> </section>
@@ -65,7 +116,7 @@
<li><strong>Cancellazione</strong>: richiedere l'anonimizzazione o la rimozione dei propri dati.</li> <li><strong>Cancellazione</strong>: richiedere l'anonimizzazione o la rimozione dei propri dati.</li>
<li><strong>Portabilità</strong>: richiedere l'esportazione dei propri dati.</li> <li><strong>Portabilità</strong>: richiedere l'esportazione dei propri dati.</li>
</ul> </ul>
<p class="mt-2">Le richieste possono essere inoltrate al Servitore di Territorio o all'Amministratore dell'applicazione.</p> <p class="mt-2">Le richieste vanno rivolte agli amministratori dell'applicazione della congregazione {{ $congregazione }}.</p>
</section> </section>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,111 @@
<?php
namespace Tests\Feature;
use App\Models\Proclamatore;
use App\Models\Setting;
use App\Models\User;
use Database\Seeders\RolesAndPermissionsSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/**
* L'informativa privacy legge i suoi valori dalle impostazioni e dai dati,
* al momento: nessun numero scritto a mano.
*/
class PrivacyTest extends TestCase
{
use RefreshDatabase;
private User $admin;
protected function setUp(): void
{
parent::setUp();
$this->withoutVite();
$this->seed(RolesAndPermissionsSeeder::class);
$this->admin = User::create(['name' => 'Admin', 'email' => 'admin@example.test', 'password' => 'password-di-prova']);
$this->admin->assignRole('amministratore');
}
public function test_i_valori_arrivano_dalle_impostazioni(): void
{
Setting::firstOrCreate([], ['congregazione_nome' => 'x'])->update([
'congregazione_nome' => 'Congregazione Zisa',
'audit_retention_days' => 400,
'giorni_per_smarrito' => 95,
'pdf_viewer_show_download' => false,
]);
$this->actingAs($this->admin)->get(route('privacy'))
->assertOk()
->assertSee('Congregazione Zisa')
->assertSee('conservato per')
->assertSee('400 giorni')
->assertSee('al massimo per 95 giorni')
->assertSee('si può solo consultare, non scaricare');
}
public function test_cambiare_un_impostazione_cambia_l_informativa(): void
{
$impostazioni = Setting::firstOrCreate([], ['congregazione_nome' => 'Prova']);
$impostazioni->update(['pdf_viewer_show_download' => true, 'giorni_per_smarrito' => 60]);
$this->actingAs($this->admin)->get(route('privacy'))
->assertSee('al massimo per 60 giorni')
->assertSee('si può anche scaricare');
}
public function test_i_ruoli_sono_quelli_veri_con_i_loro_permessi(): void
{
Setting::firstOrCreate([], ['congregazione_nome' => 'Prova']);
$this->actingAs($this->admin)->get(route('privacy'))
->assertSee('Amministratore')
->assertSee('Assistente')
->assertSee('Operatore')
->assertSee('assegnazione dei territori')
->assertDontSee('Servitore di Territorio')
->assertDontSee('Proclamatore Semplificato');
}
public function test_conta_i_dati_conservati_senza_mostrarli(): void
{
Setting::firstOrCreate([], ['congregazione_nome' => 'Prova']);
Proclamatore::create(['nome' => 'Mario', 'cognome' => 'Rossi', 'attivo' => true, 'email' => 'mario@example.test', 'telefono' => '3331234567']);
Proclamatore::create(['nome' => 'Anna', 'cognome' => 'Verdi', 'attivo' => false]);
$this->actingAs($this->admin)->get(route('privacy'))
->assertSee('2 proclamatori, di cui 1 attivi; 1 con email e 1 con numero di telefono')
->assertDontSee('Rossi')
->assertDontSee('mario@example.test');
}
public function test_senza_posta_dice_che_non_si_inviano_email(): void
{
Setting::firstOrCreate([], ['congregazione_nome' => 'Prova']);
config(['mail.default' => 'log']);
$this->actingAs($this->admin)->get(route('privacy'))
->assertSee('nessun server di posta configurato');
}
public function test_con_la_posta_dell_app_ne_mostra_il_server(): void
{
Setting::firstOrCreate([], ['congregazione_nome' => 'Prova'])
->update(['mail_host' => 'smtp.example.test', 'mail_port' => 587]);
$this->actingAs($this->admin)->get(route('privacy'))
->assertSee('smtp.example.test');
}
public function test_e_riservata_all_amministratore(): void
{
Setting::firstOrCreate([], ['congregazione_nome' => 'Prova']);
$operatore = User::create(['name' => 'Op', 'email' => 'op@example.test', 'password' => 'password-di-prova']);
$operatore->assignRole('operatore');
$this->actingAs($operatore)->get(route('privacy'))->assertForbidden();
}
}