V10.1 stable avec correction securité,bug sur csrf dans index

This commit is contained in:
Oleg Barykin committed 2026-05-27 13:54:59 +02:00
1 parent e9d0010451
commit 21f34c64c9
11 files changed
+1136 -445

No files matched your search

+94 -62
View File
@@ -1,49 +1,51 @@
<?php
// admin/tools/connecter_en_admin.php
require_once __DIR__ . '/../../public/includes/init_admin.php';
// admin/tools/connecter_en_admin.php
require_once __DIR__ . '/../../public/includes/init_admin.php';
const ADMIN_PRACTICIEN_ID = 88888888;
$me = (int)($_SESSION['reference_id'] ?? 0);
if ($me !== ADMIN_PRACTICIEN_ID) {
const ADMIN_PRACTICIEN_ID = 88888888;
$me = (int) ($_SESSION['reference_id'] ?? 0);
if ($me !== ADMIN_PRACTICIEN_ID) {
http_response_code(403);
echo '<div class="container py-5"><div class="alert alert-danger">'.htmlspecialchars(($translations['access_denied'] ?? 'Доступ запрещён.'), ENT_QUOTES, 'UTF-8').'</div></div>';
echo '<div class="container py-5"><div class="alert alert-danger">' . htmlspecialchars(($translations['access_denied'] ?? 'Доступ запрещён.'), ENT_QUOTES, 'UTF-8') . '</div></div>';
exit;
}
// reauth (10 min)
$mustReauth=function(int $ttl=600){$t=(int)($_SESSION['admin_reauth_at']??0);return ($t===0||(time()-$t)>$ttl);};
if ($mustReauth()){
$qs=http_build_query(['page'=>'reauth','redir'=>($_SERVER['REQUEST_URI']??'index.php?page=maintenance')]);
header('Location:?'.$qs);
}
// reauth (10 min)
$mustReauth = function (int $ttl = 600) {$t = (int) ($_SESSION['admin_reauth_at'] ?? 0);return ($t === 0 || (time() - $t) > $ttl);};
if ($mustReauth()) {
$qs = http_build_query(['page' => 'reauth', 'redir' => ($_SERVER['REQUEST_URI'] ?? 'index.php?page=maintenance')]);
header('Location:?' . $qs);
exit;
}
}
// CSRF
if (!isset($_SESSION['csrf_token'])) {
// CSRF
if (! isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$CSRF = $_SESSION['csrf_token'];
}
$CSRF = $_SESSION['csrf_token'];
function e($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
function e($s)
{return htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8');}
// Helpers DB : test existence de la table de log
function tableExists(PDO $pdo, string $table): bool {
// Helpers DB : test existence de la table de log
function tableExists(PDO $pdo, string $table): bool
{
$sql = "SELECT 1 FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? LIMIT 1";
$st = $pdo->prepare($sql);
$st->execute([$table]);
return (bool)$st->fetchColumn();
}
return (bool) $st->fetchColumn();
}
// ------------ Traitement POST ------------
$flash = null;
// ------------ Traitement POST ------------
$flash = null;
if (($_POST['do'] ?? '') === 'impersonate') {
if (($_POST['do'] ?? '') === 'impersonate') {
try {
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf'] ?? '')) {
if (! hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf'] ?? '')) {
throw new Exception('CSRF');
}
$email = trim((string)($_POST['email'] ?? ''));
$email = trim((string) ($_POST['email'] ?? ''));
if ($email === '') {
throw new Exception('NO_EMAIL');
}
@@ -57,37 +59,37 @@ if (($_POST['do'] ?? '') === 'impersonate') {
$st->execute([$email]);
$user = $st->fetch(PDO::FETCH_ASSOC);
if (!$user) {
if (! $user) {
throw new Exception('NOT_FOUND');
}
$role = (string)($user['role'] ?? '');
$role = (string) ($user['role'] ?? '');
// On autorise seulement certains rôles à être "impersonnés"
$allowedRoles = ['praticien', 'client'];
if (!in_array($role, $allowedRoles, true)) {
if (! in_array($role, $allowedRoles, true)) {
throw new Exception('ROLE_FORBIDDEN');
}
// Sauvegarder l’admin actuel si ce n’est pas déjà fait
if (empty($_SESSION['admin_impersonate']) || !is_array($_SESSION['admin_impersonate'])) {
if (empty($_SESSION['admin_impersonate']) || ! is_array($_SESSION['admin_impersonate'])) {
$_SESSION['admin_impersonate'] = [
'admin_user_id' => $_SESSION['user_id'] ?? null,
'admin_compte_id' => $_SESSION['compte_id'] ?? null,
'admin_reference_id' => $_SESSION['reference_id'] ?? null,
'admin_role' => $_SESSION['role'] ?? null,
'admin_role' => $_SESSION['role'] ?? null,
'started_at' => time(),
];
}
// Log de l’action si la table existe
if (tableExists($pdo, 'admin_impersonation_log')) {
$ip = $_SERVER['REMOTE_ADDR'] ?? null;
$ip = $_SERVER['REMOTE_ADDR'] ?? null;
$ua = $_SERVER['HTTP_USER_AGENT'] ?? null;
$adminUid = (int)($_SESSION['user_id'] ?? 0);
$adminRef = (int)($_SESSION['reference_id'] ?? 0);
$targetUid = (int)($user['id'] ?? 0);
$targetRef = (int)($user['reference_id'] ?? 0);
$emailLog = (string)($user['email'] ?? '');
$adminUid = (int) ($_SESSION['compte_id'] ?? 0);
$adminRef = (int) ($_SESSION['reference_id'] ?? 0);
$targetUid = (int) ($user['id'] ?? 0);
$targetRef = (int) ($user['reference_id'] ?? 0);
$emailLog = (string) ($user['email'] ?? '');
$logSql = "INSERT INTO admin_impersonation_log
(admin_user_id, admin_reference_id, target_user_id, target_reference_id, target_role, email, ip, user_agent)
@@ -105,15 +107,34 @@ if (($_POST['do'] ?? '') === 'impersonate') {
}
// Basculer la session sur l’utilisateur cible
$_SESSION['user_id'] = (int)$user['id'];
$_SESSION['role'] = $role;
$_SESSION['reference_id'] = (int)($user['reference_id'] ?? 0);
session_regenerate_id(true);
unset(
$_SESSION['client_id'],
$_SESSION['praticien_id'],
$_SESSION['secretaire_id'],
$_SESSION['admin_id'],
$_SESSION['compte_id'],
$_SESSION['reference_id'],
$_SESSION['role']
);
$_SESSION['compte_id'] = (int) $user['id'];
$_SESSION['role'] = $role;
$_SESSION['reference_id'] = (int) ($user['reference_id'] ?? 0);
$_SESSION['last_activity'] = time();
if ($role === 'praticien') {
$_SESSION['praticien_id'] = (int) $user['reference_id'];
} elseif ($role === 'client') {
$_SESSION['client_id'] = (int) $user['reference_id'];
}
// Redirection selon le rôle
if ($role === 'praticien') {
header('Location: /public/espace_praticien/index.php');
header('Location: /public/praticien/agenda.php');
} elseif ($role === 'client') {
header('Location: /public/espace_client/index.php');
header('Location: /public/client/mes_rendez_vous.php');
} else {
// fallback
header('Location: /');
@@ -124,56 +145,67 @@ if (($_POST['do'] ?? '') === 'impersonate') {
$code = $e->getMessage();
$msg = ($translations['admin_impersonate_err_default'] ?? 'Ошибка при подключении.');
if ($code === 'CSRF') $msg = ($translations['admin_impersonate_err_csrf'] ?? 'Ошибка безопасности. Обновите страницу.');
if ($code === 'NO_EMAIL') $msg = ($translations['admin_impersonate_err_no_email'] ?? 'Укажите e-mail практикующего или клиента.');
if ($code === 'NOT_FOUND') $msg = ($translations['admin_impersonate_err_not_found'] ?? 'Пользователь с таким e-mail не найден.');
if ($code === 'ROLE_FORBIDDEN') $msg = ($translations['admin_impersonate_err_role_forbidden'] ?? 'Невозможно подключиться под этим типом пользователя.');
if ($code === 'CSRF') {
$msg = ($translations['admin_impersonate_err_csrf'] ?? 'Ошибка безопасности. Обновите страницу.');
}
$flash = ['type'=>'danger','msg'=>$msg];
if ($code === 'NO_EMAIL') {
$msg = ($translations['admin_impersonate_err_no_email'] ?? 'Укажите e-mail практикующего или клиента.');
}
if ($code === 'NOT_FOUND') {
$msg = ($translations['admin_impersonate_err_not_found'] ?? 'Пользователь с таким e-mail не найден.');
}
if ($code === 'ROLE_FORBIDDEN') {
$msg = ($translations['admin_impersonate_err_role_forbidden'] ?? 'Невозможно подключиться под этим типом пользователя.');
}
$flash = ['type' => 'danger', 'msg' => $msg];
}
}
}
// Indicateur si on est déjà en mode impersonation (admin_impersonate présent)
$isImpersonating = !empty($_SESSION['admin_impersonate']) && is_array($_SESSION['admin_impersonate']);
// Indicateur si on est déjà en mode impersonation (admin_impersonate présent)
$isImpersonating = ! empty($_SESSION['admin_impersonate']) && is_array($_SESSION['admin_impersonate']);
?>
<div class="shadow p-4 rounded bg-light">
<h2 class="mb-3"><?= e($translations['admin_impersonate_title'] ?? 'Подключиться как пользователь') ?></h2>
<h2 class="mb-3"><?php echo e($translations['admin_impersonate_title'] ?? 'Подключиться как пользователь') ?></h2>
<?php if ($isImpersonating): ?>
<div class="alert alert-warning">
<?= e($translations['admin_impersonate_warn_active'] ?? 'Внимание: сейчас вы авторизованы как другой пользователь.') ?>
<?php echo e($translations['admin_impersonate_warn_active'] ?? 'Внимание: сейчас вы авторизованы как другой пользователь.') ?>
<a href="/admin/index.php?page=retour_admin" class="btn btn-sm btn-outline-danger ms-2" style="border-radius:999px;">
<?= e($translations['admin_impersonate_btn_back_admin'] ?? 'Вернуться в режим администратора') ?>
<?php echo e($translations['admin_impersonate_btn_back_admin'] ?? 'Вернуться в режим администратора') ?>
</a>
</div>
<?php endif; ?>
<?php if ($flash): ?>
<div class="alert alert-<?= e($flash['type'] ?? 'info') ?>"><?= e($flash['msg'] ?? '') ?></div>
<div class="alert alert-<?php echo e($flash['type'] ?? 'info') ?>"><?php echo e($flash['msg'] ?? '') ?></div>
<?php endif; ?>
<form method="post" class="mb-4">
<input type="hidden" name="csrf" value="<?= e($CSRF) ?>">
<input type="hidden" name="csrf" value="<?php echo e($CSRF) ?>">
<input type="hidden" name="do" value="impersonate">
<div class="mb-3">
<label class="form-label"><?= e($translations['admin_impersonate_label_email'] ?? 'E-mail практикующего или клиента') ?></label>
<label class="form-label"><?php echo e($translations['admin_impersonate_label_email'] ?? 'E-mail практикующего или клиента') ?></label>
<input type="email" name="email" class="form-control" required
placeholder="<?= e($translations['admin_impersonate_ph_email'] ?? 'example@domaine.fr') ?>">
placeholder="<?php echo e($translations['admin_impersonate_ph_email'] ?? 'example@domaine.fr') ?>">
<div class="form-text">
<?= e($translations['admin_impersonate_help'] ?? 'Администратор будет авторизован под этим пользователем без ввода пароля.') ?>
<?php echo e($translations['admin_impersonate_help'] ?? 'Администратор будет авторизован под этим пользователем без ввода пароля.') ?>
</div>
</div>
<button type="submit" class="btn btn-danger" style="border-radius:999px;">
<?= e($translations['admin_impersonate_btn_submit'] ?? 'Подключиться под этим пользователем') ?>
<?php echo e($translations['admin_impersonate_btn_submit'] ?? 'Подключиться под этим пользователем') ?>
</button>
</form>
<hr>
<p class="text-muted mb-0">
<?= e($translations['admin_impersonate_footer'] ?? 'Все действия должны соответствовать политике конфиденциальности. Доступ к этой функции есть только у супер-администратора.') ?>
<?php echo e($translations['admin_impersonate_footer'] ?? 'Все действия должны соответствовать политике конфиденциальности. Доступ к этой функции есть только у супер-администратора.') ?>
</p>
</div>
+20 -8
View File
@@ -1,26 +1,38 @@
<?php
// admin/tools/retour_admin.php
declare(strict_types=1);
declare (strict_types = 1);
if (session_status() !== PHP_SESSION_ACTIVE) {
@session_start();
}
// On ne met pas require_login_or_redirect ici, car on peut être sous un compte client/praticien
// On se contente de vérifier qu'on a bien une info d’admin d’origine.
if (!empty($_SESSION['admin_impersonate']) && is_array($_SESSION['admin_impersonate'])) {
// On ne met pas require_login_or_redirect ici,
// car on peut être sous un compte client/praticien.
// On vérifie seulement qu'on a bien l'admin d'origine.
if (! empty($_SESSION['admin_impersonate']) && is_array($_SESSION['admin_impersonate'])) {
$orig = $_SESSION['admin_impersonate'];
if (isset($orig['admin_user_id'])) {
$_SESSION['user_id'] = $orig['admin_user_id'];
session_regenerate_id(true);
unset(
$_SESSION['client_id'],
$_SESSION['praticien_id'],
$_SESSION['secretaire_id'],
$_SESSION['admin_id'],
);
if (isset($orig['admin_compte_id'])) {
$_SESSION['compte_id'] = (int) $orig['admin_compte_id'];
}
if (isset($orig['admin_reference_id'])) {
$_SESSION['reference_id'] = $orig['admin_reference_id'];
$_SESSION['reference_id'] = (int) $orig['admin_reference_id'];
}
if (isset($orig['admin_role'])) {
$_SESSION['role'] = $orig['admin_role'];
$_SESSION['role'] = (string) $orig['admin_role'];
}
$_SESSION['last_activity'] = time();
unset($_SESSION['admin_impersonate']);
}