Revisione blocchi 2 e 3: file protetti, ripristino atomico, import sicuri

Blocco 2
- Esiti delle azioni Livewire come avvisi (trait Avvisa) invece di flash
  che comparivano alla pagina dopo.
- PDF, miniature e versioni dei territori non più serviti da nginx su
  /storage: TerritorioFileController con login, permessi e cache privata.
- Ripristino tutto o niente: verifica dell'archivio, una transazione con
  import, piantine, blocco account e scambio dei file annullabile.
- Backup formato 2 con piantine, storico PDF e area KML.
- Posto unico preso in un passo (Cache::lock) e rispettato anche da
  import XML e PDF.
- Import PDF da cartella: abbinamento corretto (1-A, 12 B, ambigui),
  caricamento a lotti, file non PDF ignorati, PDF sostituito nello storico.
- L'import XML elimina i file dei territori che non usa più; comando
  territori:file-orfani per quelli accumulati prima.

Blocco 3
- Password attuale richiesta al cambio volontario; AuthenticateSession
  chiude le sessioni aperte altrove.
- Niente nomi dei proclamatori in chiaro nel registro attività.
- Export XML senza remember_token; chi riceve l'hash dal file deve
  cambiare password.
- Campagne: ultimo giorno incluso, percentuale sui territori distinti.
- Coda: retry_after 960 s, stop_grace_period 16m, Redis volatile-lru,
  impostazioni rilette a ogni job.
- Azzeramento che cancella anche piantine, area KML e anteprime.
- Pulsanti profilo e menu che non si nascondevano; icona del sito.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 15:01:41 +02:00
parent 056d95cfcb
commit 426a95211d
78 changed files with 3971 additions and 429 deletions

View File

@@ -0,0 +1,123 @@
<?php
namespace Tests\Feature;
use App\Models\Setting;
use App\Models\User;
use Database\Seeders\RolesAndPermissionsSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;
/**
* Cambio password: la password attuale si chiede a chi cambia di sua
* iniziativa, e cambiare la password chiude le sessioni aperte altrove.
*/
class CambioPasswordTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->withoutVite();
$this->seed(RolesAndPermissionsSeeder::class);
Setting::firstOrCreate([], ['congregazione_nome' => 'Congregazione di prova']);
}
private function utente(bool $obbligato = false): User
{
$utente = User::create([
'name' => 'Mario Rossi',
'email' => 'mario@example.test',
'password' => 'vecchia-password',
]);
$utente->forceFill(['password_change_required' => $obbligato])->save();
$utente->assignRole('operatore');
return $utente;
}
public function test_chi_cambia_di_sua_iniziativa_deve_dare_la_password_attuale(): void
{
$utente = $this->utente();
$this->actingAs($utente)
->post(route('password.change.store'), [
'password' => 'nuova-password-1',
'password_confirmation' => 'nuova-password-1',
])
->assertSessionHasErrors('current_password');
$this->actingAs($utente)
->post(route('password.change.store'), [
'current_password' => 'sbagliata',
'password' => 'nuova-password-1',
'password_confirmation' => 'nuova-password-1',
])
->assertSessionHasErrors('current_password');
$this->assertTrue(Hash::check('vecchia-password', $utente->fresh()->password));
$this->actingAs($utente)
->post(route('password.change.store'), [
'current_password' => 'vecchia-password',
'password' => 'nuova-password-1',
'password_confirmation' => 'nuova-password-1',
])
->assertRedirect(route('dashboard'));
$this->assertTrue(Hash::check('nuova-password-1', $utente->fresh()->password));
}
public function test_dopo_un_ripristino_la_password_attuale_non_si_chiede(): void
{
$utente = $this->utente(obbligato: true);
$this->actingAs($utente)
->get(route('password.change'))
->assertOk()
->assertDontSee('name="current_password"', false);
$this->actingAs($utente)
->post(route('password.change.store'), [
'password' => 'nuova-password-1',
'password_confirmation' => 'nuova-password-1',
])
->assertRedirect(route('dashboard'));
$this->assertFalse((bool) $utente->fresh()->password_change_required);
}
public function test_una_sessione_aperta_altrove_si_chiude_quando_cambia_la_password(): void
{
$utente = $this->utente();
$hashVecchio = $utente->password;
// La sessione "altrove" ha registrato l'impronta della password di allora.
$utente->update(['password' => 'cambiata-da-un-altra-parte']);
$this->actingAs($utente)
->withSession(['password_hash_web' => $hashVecchio])
->get(route('dashboard'))
->assertRedirect(route('login'));
$this->assertGuest();
}
public function test_la_sessione_che_cambia_la_password_resta_aperta(): void
{
$utente = $this->utente();
$this->actingAs($utente)
->withSession(['password_hash_web' => $utente->password])
->post(route('password.change.store'), [
'current_password' => 'vecchia-password',
'password' => 'nuova-password-1',
'password_confirmation' => 'nuova-password-1',
])
->assertRedirect(route('dashboard'));
$this->get(route('dashboard'))->assertOk();
}
}