Files
maria_uslugi/public/includes/securite.php
T

203 lines
5.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare (strict_types = 1);
// Interdit l’accès direct au fichier (pas de HTML ici)
if (isset($_SERVER['SCRIPT_FILENAME']) && realpath(__FILE__) === realpath($_SERVER['SCRIPT_FILENAME'])) {
http_response_code(403);
exit('Forbidden');
}
/** Est-ce que l'utilisateur est connecté ? */
function is_logged_in(): bool
{
return isset($_SESSION['compte_id'], $_SESSION['reference_id'])
&& $_SESSION['compte_id'] !== ''
&& $_SESSION['reference_id'] !== '';
}
/** Timeout inactivité (2h) */
function enforce_session_timeout(int $maxIdleSeconds = 7200): void
{
$now = time();
$last = (int) ($_SESSION['last_activity'] ?? 0);
if ($last > 0 && ($now - $last) > $maxIdleSeconds) {
logout_and_redirect_to_login($_SERVER['REQUEST_URI'] ?? '/');
}
$_SESSION['last_activity'] = $now;
}
/** Anti open-redirect : n’autorise que les chemins internes commençant par '/' */
function next_safe(?string $u): ?string
{
if (! $u) {
return null;
}
if (! preg_match('#^/[^\\r\\n]*$#', $u)) {
return null;
}
if (strpos($u, '//') === 0) {
return null;
}
return $u;
}
function redirect_forbidden(?string $requestUri = null): void
{
$u = $requestUri ?: ($_SERVER['REQUEST_URI'] ?? '/');
$u = next_safe($u) ?? '/';
header('Location: /403.php?next=' . rawurlencode($u), true, 303);
exit;
}
function redirect_to_login_with_next(?string $requestUri = null): void
{
$u = $requestUri ?: ($_SERVER['REQUEST_URI'] ?? '/');
$u = next_safe($u) ?? '/';
$_SESSION['redirect_to'] = $u;
header('Location: /connexion.php?next=' . rawurlencode($u), true, 303);
exit;
}
function logout_and_redirect_to_login(): void
{
$_SESSION = [];
if (session_status() === PHP_SESSION_ACTIVE) {
$params = session_get_cookie_params();
setcookie(session_name(), '', [
'expires' => time() - 42000,
'path' => $params['path'] ?? '/',
'domain' => $params['domain'] ?? '',
'secure' => (bool) ($params['secure'] ?? false),
'httponly' => (bool) ($params['httponly'] ?? true),
'samesite' => $params['samesite'] ?? 'Lax',
]);
session_destroy();
}
header('Location: /index.php', true, 303);
exit;
}
/**
* 🔒 Re-check rôle depuis DB (optionnel) + hydrate $_SESSION['role'] si absent.
* Retourne le rôle (string) ou '' si introuvable.
*/
function refresh_role_from_db(PDO $pdo): string
{
$compteId = (int) ($_SESSION['compte_id'] ?? 0);
if ($compteId <= 0) {
return '';
}
$stmt = $pdo->prepare("SELECT role FROM compte WHERE id=? LIMIT 1");
$stmt->execute([$compteId]);
$role = (string) ($stmt->fetchColumn() ?: '');
if ($role !== '') {
$_SESSION['role'] = $role;
}
return strtolower($role);
}
/**
* ✅ À appeler en haut des pages protégées.
* - $allowedRoles = null => tous rôles
* - options:
* - 'pdo' => PDO (pour re-check DB si demandé)
* - 'recheck_db_role' => true/false
* - 'admin_strict_reference' => int|null (ex: 88888888)
*/
function require_login_or_redirect(?array $allowedRoles = null, array $options = []): void
{
if (! is_logged_in()) {
redirect_to_login_with_next($_SERVER['REQUEST_URI'] ?? '/');
}
enforce_session_timeout(7200);
$pdo = $options['pdo'] ?? ($GLOBALS['pdo'] ?? null);
$recheck = (bool) ($options['recheck_db_role'] ?? false);
// Rôle: session d'abord, DB si demandé (ou si role manquant)
$userRole = strtolower((string) ($_SESSION['role'] ?? ''));
if (($userRole === '' || $recheck) && ($pdo instanceof PDO)) {
$userRole = refresh_role_from_db($pdo);
}
if ($allowedRoles) {
$allowed = array_map(static fn($r) => strtolower((string) $r), $allowedRoles);
if (! in_array($userRole, $allowed, true)) {
redirect_forbidden($_SERVER['REQUEST_URI'] ?? '/');
}
// 🔥 Renfort admin optionnel : role + reference_id
if (in_array('admin', $allowed, true)) {
$strictRef = $options['admin_strict_reference'] ?? null;
if ($strictRef !== null) {
$refId = (int) ($_SESSION['reference_id'] ?? 0);
if ($refId !== (int) $strictRef) {
redirect_forbidden($_SERVER['REQUEST_URI'] ?? '/');
}
}
}
}
}
/* =========================
Admin helpers (CSRF + logs)
========================= */
/** Token CSRF session */
function ensure_csrf_token(): string
{
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return (string) $_SESSION['csrf_token'];
}
/** Exige POST + CSRF (supporte aussi header X-CSRF-Token si tu veux fetch/AJAX) */
function require_post_csrf(): void
{
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
http_response_code(405);
exit('Méthode non autorisée');
}
$token = $_POST['csrf_token'] ?? '';
if ($token === '' && ! empty($_SERVER['HTTP_X_CSRF_TOKEN'])) {
$token = (string) $_SERVER['HTTP_X_CSRF_TOKEN'];
}
if ($token === '' || ! hash_equals((string) ($_SESSION['csrf_token'] ?? ''), (string) $token)) {
http_response_code(419);
exit('CSRF invalide');
}
}
/** Log admin minimal */
function log_admin_action(PDO $pdo, int $userId, string $action, string $details = ''): void
{
// limite simple pour éviter d'exploser la table
if (strlen($details) > 2000) {
$details = substr($details, 0, 2000);
}
$pdo->prepare(
"INSERT INTO admin_actions_log (user_id, action, details, ip) VALUES (?,?,?,?)"
)->execute([
$userId,
$action,
$details,
$_SERVER['REMOTE_ADDR'] ?? '',
]);
}