Files
termanager2/app/Providers/AppServiceProvider.php
francesco 0502fad996 Correggi bug di sicurezza e integrita' dei dati
I due piu' gravi potevano causare perdita di dati silenziosa.

AppServiceProvider chiamava key:generate --force a ogni richiesta quando
APP_KEY risultava vuota. Poiche' nome e cognome dei proclamatori sono
cifrati con quella chiave, ogni riga scritta sotto una chiave poi
sostituita diventava irrecuperabile. La chiave e' ora risolta una sola
volta all'avvio del container e il provider solleva un'eccezione se manca,
invece di rigenerarla.

importTerritoryPdfFolder() caricava i PDF, costruiva l'elenco dei file e
poi lo scartava senza mai accodare il job: nessun errore, nessun territorio
aggiornato, e i file restavano su disco a crescita illimitata perche' la
pulizia viveva solo nel finally del job mai eseguito.

Sicurezza:
- i permessi non venivano riapplicati alle richieste /livewire/update, per
  cui un permesso revocato restava attivo fino al reload della pagina
- host header injection: nginx passava l'Host del client come
  X-Forwarded-Host e Laravel si fidava di tutti i proxy, abbastanza per far
  generare a route() link verso domini arbitrari, inclusi i PDF pubblici
  che contengono un codice di accesso valido
- lo schema forwarded e' ora deciso dal server e non dall'header del client
- campagna_id in Rientra e' #[Locked]: era derivato lato server ma
  manipolabile dal client, permettendo di conteggiare un rientro su una
  campagna arbitraria
- sortField del registro passava non validato a orderBy(), con 500 su
  colonna inesistente
- SESSION_SECURE_COOKIE ora vale true fuori da local
- limiti su dimensione e numero di voci negli import ZIP

Integrita' e prestazioni:
- il default di assignment_link_ttl_hours era rimasto 24 dopo la
  conversione da ore a mesi: i link PDF pubblici duravano due anni invece
  di uno mese
- il seeder usava syncPermissions(), che a un secondo giro azzerava i
  permessi personalizzati dei ruoli; ora e' additivo
- pdftoppm senza timeout poteva bloccare il worker indefinitamente
- nomi file deterministici nell'import: "1 A" e "1-A" scrivevano sullo
  stesso PDF, sovrascrivendosi
- Setting::flushCache() dopo le cancellazioni massive, che non emettono
  eventi e lasciavano la cache puntata a una riga inesistente
- riga settings creata dal seeder, per evitare due singleton concorrenti
- rimosse due migrazioni ridondanti che rompevano migrate:rollback
- indici su assigned_at, (attivo, deleted_at) e deleted_at
- risolti gli N+1 negli elenchi territori e proclamatori

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 16:49:42 +02:00

76 lines
3.0 KiB
PHP

<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\URL;
use Livewire\Livewire;
use RuntimeException;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
//
}
public function boot(): void
{
// Pin generated URLs to APP_URL.
//
// Behind a trusted proxy, Symfony derives the port from
// X-Forwarded-Port and falls back to the scheme's default when that
// header is absent — which silently drops a non-standard port from
// every generated link. Anchoring the root to APP_URL makes the
// public address explicit instead of reconstructed per request, and
// keeps PDF/e-mail links correct regardless of proxy behaviour.
$appUrl = (string) config('app.url');
if ($appUrl !== '') {
URL::forceRootUrl($appUrl);
if (str_starts_with($appUrl, 'https://')) {
URL::forceScheme('https');
}
}
// NOTE: APP_KEY is deliberately NOT auto-generated here.
//
// This provider boots on every request. Generating a key at runtime
// meant that if the key ever failed to persist, each request could
// mint a *different* one — and since proclamatori names are encrypted
// at column level with APP_KEY, every row written under a superseded
// key becomes permanently unreadable.
//
// The key is now resolved once at container start (docker/php/
// entrypoint.sh) and persisted to the storage volume. Fail loudly
// instead of silently rotating.
// Only enforced when serving HTTP. Build-time tooling (package:discover,
// config:cache) legitimately runs before the key exists, and failing
// there would break the image build rather than surface a real problem.
if (empty(config('app.key')) && ! $this->app->runningInConsole()) {
throw new RuntimeException(
'APP_KEY is not set. Set it in the environment before starting the application. '
.'Generate one with: php artisan key:generate --show'
);
}
// Livewire only re-applies a fixed allow-list of middleware to
// /livewire/update requests. Spatie's permission middleware is not on
// that list, so a component whose route is guarded by
// `permission:territori.manage` would keep accepting mutating actions
// from a user whose permission was revoked after page load, until they
// reload. Registering them as persistent closes that window.
Livewire::addPersistentMiddleware([
\Spatie\Permission\Middleware\PermissionMiddleware::class,
\Spatie\Permission\Middleware\RoleMiddleware::class,
\Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class,
]);
Gate::before(function ($user, $ability) {
return $user->hasRole('amministratore') ? true : null;
});
}
}