Paillasse/portail/tests/Feature/PolitiqueSecuriteContenuTest.php
Adam Belkacemi e4a6aaebb1 ajoute les pages légales, la purge RGPD et sert polices et scripts depuis le portail
Pages /legal et /confidentialite réécrites d'après le système réel, purge
des classes archivées depuis 90 jours (prouvée par Pest), archivage côté
prof. Polices, xterm.js et CodeMirror ne sont plus chargés depuis Google
ni jsDelivr. Pages d'aperçu signalées, sans vrais établissements ni faux
chiffres. Journaux Laravel gardés 14 jours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:07:25 +02:00

59 lines
2.6 KiB
PHP

<?php
use App\Models\Eleve;
function politique($reponse): array
{
return collect(explode(';', $reponse->headers->get('Content-Security-Policy')))
->mapWithKeys(function (string $d) {
$morceaux = preg_split('/\s+/', trim($d), 2);
return [$morceaux[0] => $morceaux[1] ?? ''];
})->all();
}
test("les pages de l'application ont une CSP stricte : pas de script en ligne sans le nonce", function () {
$eleve = Eleve::factory()->create();
$jeton = $eleve->demarreNouvelleSession();
$reponse = $this->withSession(['jeton_session_eleve' => $jeton])->actingAs($eleve, 'eleve')->get(route('eleve'));
$csp = politique($reponse);
expect($csp['script-src'])->toMatch("/^'self' 'nonce-[A-Za-z0-9+\\/=]+'$/")
->and($csp['script-src'])->not->toContain('unsafe-inline')
->and($csp['object-src'])->toBe("'none'")
->and($csp['frame-ancestors'])->toBe("'none'");
});
test('la page de connexion est stricte aussi, la vitrine tolère les scripts en ligne', function () {
expect(politique($this->get(route('login')))['script-src'])->not->toContain('unsafe-inline')
->and(politique($this->get(route('accueil')))['script-src'])->toContain("'unsafe-inline'");
});
test("chaque script de la page d'inscription porte le nonce de l'en-tête", function () {
$reponse = $this->get(route('register'));
preg_match("/'nonce-([^']+)'/", $reponse->headers->get('Content-Security-Policy'), $m);
preg_match_all('/<script\b[^>]*>/', $reponse->getContent(), $scripts);
expect($scripts[0])->not->toBeEmpty();
foreach ($scripts[0] as $balise) {
expect($balise)->toContain("nonce=\"{$m[1]}\"");
}
});
test('le rapport de violation est accepté sans jeton CSRF', function () {
$this->call('POST', route('csp.rapport'), [], [], [], ['CONTENT_TYPE' => 'application/csp-report'],
json_encode(['csp-report' => ['document-uri' => 'https://syllabuslab.fr/eleve?x=1', 'violated-directive' => 'script-src', 'blocked-uri' => 'inline']]))
->assertNoContent();
});
test("les pages de l'application ne chargent aucune ressource externe", function () {
$eleve = Eleve::factory()->create();
$jeton = $eleve->demarreNouvelleSession();
foreach ([route('login'), route('register')] as $url) {
expect($this->get($url)->getContent())->not->toMatch('#(src|href)="https?://(?!localhost)#', $url);
}
$page = $this->withSession(['jeton_session_eleve' => $jeton])->actingAs($eleve, 'eleve')->get(route('eleve'))->getContent();
expect($page)->not->toMatch('#(src|href)="https?://(?!localhost)[^"]*\.(js|css|woff2?)#');
});