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>
59 lines
2.6 KiB
PHP
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?)#');
|
|
});
|