Reprise dans le répertoire de production du travail mené dans le worktree ux-service-enfance : - tableau de bord Service Enfance (dashboard.php) et navigation regroupée ; - guide d'utilisation (faq.php) accessible depuis toute la navigation ; - éditeur de semaine type mis en avant sur l'écran de saisie ; - droit à l'enveloppe de formation par agent, en remplacement de la case « formation déjà prévue dans la répartition annuelle » ; - correctif de « [object Object] » sur les cartes de quota du PTA ; - motif de travail par défaut à la saisie, heures travaillées complètes dans les PDF. Le docker-compose.yml de production est conservé : celui du worktree décrit une pile de test (conteneurs et volume suffixés « test », ports 9080/9081), qui aurait fait redémarrer l'application sur un volume MariaDB vide. Les migrations database/migration_v7_9*.sql sont obligatoires ; le service db-migrate les applique au déploiement. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
90 lines
2.7 KiB
PHP
90 lines
2.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Core;
|
|
|
|
/**
|
|
* En-têtes de sécurité des réponses HTML.
|
|
*
|
|
* La CSP est nominative : aucun script inline n'est autorisé sans le nonce
|
|
* généré à chaque requête. PTA ne contient aucun gestionnaire d'événement
|
|
* inline (onclick=, onchange=…) ni style inline, la politique peut donc rester
|
|
* stricte sans dégrader l'interface.
|
|
*/
|
|
final class Security
|
|
{
|
|
private static ?string $nonce = null;
|
|
|
|
public static function nonce(): string
|
|
{
|
|
if (self::$nonce === null) {
|
|
self::$nonce = base64_encode(random_bytes(16));
|
|
}
|
|
|
|
return self::$nonce;
|
|
}
|
|
|
|
public static function sendHtmlHeaders(): void
|
|
{
|
|
if (headers_sent()) {
|
|
return;
|
|
}
|
|
|
|
$nonce = self::nonce();
|
|
$policy = [
|
|
"default-src 'self'",
|
|
"base-uri 'self'",
|
|
"object-src 'none'",
|
|
"frame-ancestors 'none'",
|
|
"form-action 'self'",
|
|
"img-src 'self' data:",
|
|
"style-src 'self'",
|
|
"font-src 'self'",
|
|
// Aucune requête sortante depuis le navigateur : les appels à
|
|
// data.education.gouv.fr sont faits côté serveur.
|
|
"connect-src 'self'",
|
|
sprintf("script-src 'self' 'nonce-%s'", $nonce),
|
|
];
|
|
|
|
header('Content-Security-Policy: ' . implode('; ', $policy));
|
|
header('X-Content-Type-Options: nosniff');
|
|
header('X-Frame-Options: DENY');
|
|
header('Referrer-Policy: no-referrer');
|
|
header('Permissions-Policy: geolocation=(), camera=(), microphone=(), payment=()');
|
|
header('Cross-Origin-Opener-Policy: same-origin');
|
|
|
|
if (Session::isHttps()) {
|
|
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Vrai uniquement si le mode debug a été activé explicitement.
|
|
* Par défaut, aucun détail technique ne sort du serveur.
|
|
*/
|
|
public static function debugEnabled(): bool
|
|
{
|
|
return in_array(strtolower((string) (getenv('PTA_DEBUG') ?: '0')), ['1', 'true', 'on', 'yes'], true);
|
|
}
|
|
|
|
/**
|
|
* Journalise l'exception côté serveur et renvoie le message à afficher.
|
|
* Le détail technique n'est rendu que si PTA_DEBUG est explicitement actif.
|
|
*/
|
|
public static function reportThrowable(\Throwable $exception, string $publicMessage): string
|
|
{
|
|
error_log(sprintf(
|
|
'[PTA] %s: %s in %s:%d',
|
|
get_class($exception),
|
|
$exception->getMessage(),
|
|
$exception->getFile(),
|
|
$exception->getLine()
|
|
));
|
|
|
|
return self::debugEnabled()
|
|
? $publicMessage . ' Détail : ' . $exception->getMessage()
|
|
: $publicMessage;
|
|
}
|
|
}
|