179 lines
6.0 KiB
PHP
179 lines
6.0 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
use App\Infrastructure\Database;
|
||
|
||
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
|
||
|
||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||
require_once dirname(__DIR__, 2) . '/app/bootstrap.php';
|
||
require_once dirname(__DIR__, 2) . '/config/config.php';
|
||
|
||
if (!function_exists('env')) {
|
||
function env(string $key, ?string $default = null): ?string {
|
||
if (array_key_exists($key, $_ENV) && $_ENV[$key] !== '') return (string)$_ENV[$key];
|
||
$v = getenv($key);
|
||
if ($v !== false && $v !== '') return (string)$v;
|
||
return $default;
|
||
}
|
||
}
|
||
$debug = (env('APP_DEBUG', '0') === '1');
|
||
|
||
// --- OIDC config ---
|
||
$OIDC_ISSUER = rtrim((string)env('OIDC_ISSUER', ''), '/');
|
||
$OIDC_CLIENT_ID = (string)env('OIDC_CLIENT_ID', '');
|
||
$OIDC_CLIENT_SECRET = (string)env('OIDC_CLIENT_SECRET', '');
|
||
$OIDC_REDIRECT_URI = (string)(env('OIDC_REDIRECT_URI') ?: url('oidc/callback'));
|
||
|
||
if (!$OIDC_ISSUER || !$OIDC_CLIENT_ID || !$OIDC_REDIRECT_URI) {
|
||
http_response_code(500);
|
||
echo $debug ? 'OIDC config manquante.' : 'Erreur.';
|
||
exit;
|
||
}
|
||
|
||
$tokenEndpoint = $OIDC_ISSUER . '/protocol/openid-connect/token';
|
||
$userInfoEndpoint = $OIDC_ISSUER . '/protocol/openid-connect/userinfo';
|
||
|
||
// --- Checks retour ---
|
||
if (!isset($_GET['state'], $_SESSION['oidc_state']) || !hash_equals((string)$_SESSION['oidc_state'], (string)$_GET['state'])) {
|
||
http_response_code(400);
|
||
echo $debug ? 'State invalide.' : 'Requête invalide.';
|
||
exit;
|
||
}
|
||
unset($_SESSION['oidc_state']);
|
||
|
||
if (empty($_GET['code'])) {
|
||
http_response_code(400);
|
||
echo $debug ? 'Code manquant.' : 'Requête invalide.';
|
||
exit;
|
||
}
|
||
$code = (string)$_GET['code'];
|
||
|
||
$codeVerifier = $_SESSION['oidc_code_verifier'] ?? null;
|
||
unset($_SESSION['oidc_code_verifier']); // anti-replay
|
||
$expectedNonce = $_SESSION['oidc_nonce'] ?? null;
|
||
unset($_SESSION['oidc_nonce']); // anti-replay
|
||
|
||
if (!$codeVerifier) {
|
||
http_response_code(400);
|
||
echo $debug ? 'PKCE code_verifier manquant.' : 'Requête invalide.';
|
||
exit;
|
||
}
|
||
|
||
// --- Échange code -> tokens ---
|
||
$post = [
|
||
'grant_type' => 'authorization_code',
|
||
'code' => $code,
|
||
'redirect_uri' => $OIDC_REDIRECT_URI,
|
||
'client_id' => $OIDC_CLIENT_ID,
|
||
'code_verifier' => $codeVerifier,
|
||
];
|
||
if ($OIDC_CLIENT_SECRET !== '') $post['client_secret'] = $OIDC_CLIENT_SECRET;
|
||
|
||
$ch = curl_init($tokenEndpoint);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_POST => true,
|
||
CURLOPT_POSTFIELDS => http_build_query($post, '', '&', PHP_QUERY_RFC3986),
|
||
CURLOPT_TIMEOUT => 15,
|
||
]);
|
||
$tokenResponse = curl_exec($ch);
|
||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
$err = curl_error($ch);
|
||
curl_close($ch);
|
||
|
||
if ($tokenResponse === false || $httpCode !== 200) {
|
||
http_response_code(500);
|
||
echo $debug ? 'Échec échange token: ' . htmlspecialchars($err ?: (string)$tokenResponse) : 'Erreur d’authentification.';
|
||
exit;
|
||
}
|
||
|
||
$tokens = json_decode((string)$tokenResponse, true) ?: [];
|
||
$accessToken = $tokens['access_token'] ?? null;
|
||
$idToken = $tokens['id_token'] ?? null;
|
||
|
||
if (!$accessToken) {
|
||
http_response_code(500);
|
||
echo $debug ? 'Access token manquant.' : 'Erreur d’authentification.';
|
||
exit;
|
||
}
|
||
|
||
// --- UserInfo ---
|
||
$ch = curl_init($userInfoEndpoint);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $accessToken],
|
||
CURLOPT_TIMEOUT => 10,
|
||
]);
|
||
$userInfoResponse = curl_exec($ch);
|
||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
if ($userInfoResponse === false || $httpCode !== 200) {
|
||
http_response_code(500);
|
||
echo $debug ? 'Échec UserInfo: ' . htmlspecialchars((string)$userInfoResponse) : 'Erreur d’authentification.';
|
||
exit;
|
||
}
|
||
|
||
$claims = json_decode((string)$userInfoResponse, true) ?: [];
|
||
|
||
// --- Récup info utiles ---
|
||
$email = $claims['email'] ?? null;
|
||
$username = $claims['preferred_username'] ?? ($email ?: null);
|
||
$firstname = $claims['given_name'] ?? null;
|
||
$lastname = $claims['family_name'] ?? null;
|
||
|
||
if (!$email && $idToken && substr_count($idToken, '.') === 2) {
|
||
[, $p, ] = explode('.', $idToken, 3);
|
||
$payloadJson = base64_decode(strtr($p, '-_', '+/'), true);
|
||
$payload = $payloadJson ? json_decode($payloadJson, true) : null;
|
||
if (is_array($payload) && !empty($payload['email'])) $email = $payload['email'];
|
||
}
|
||
|
||
if (!$email) {
|
||
http_response_code(400);
|
||
echo $debug ? 'Email non fourni par IdP.' : 'Impossible de récupérer votre email.';
|
||
exit;
|
||
}
|
||
|
||
// --- Si l'utilisateur existe déjà -> connecter et redirect ---
|
||
$flow = $_SESSION['oidc_flow'] ?? 'login';
|
||
|
||
// Vérifie existence en base
|
||
/** @var \PDO $pdo */
|
||
$pdo = Database::get();
|
||
$stmt = $pdo->prepare('SELECT id FROM users WHERE email = :email LIMIT 1');
|
||
$stmt->execute([':email' => $email]);
|
||
$existingId = $stmt->fetchColumn();
|
||
|
||
// Si flow=login ET utilisateur existe → connexion directe
|
||
if ($flow === 'login' && $existingId) {
|
||
$_SESSION['user_id'] = (int)$existingId;
|
||
$_SESSION['user_email'] = $email;
|
||
$_SESSION['oidc'] = [
|
||
'issuer' => $OIDC_ISSUER,
|
||
'sub' => $claims['sub'] ?? null,
|
||
'access_token' => $accessToken,
|
||
'id_token' => $idToken,
|
||
'expires_at' => time() + (int)($tokens['expires_in'] ?? 3600),
|
||
];
|
||
$target = $_SESSION['oidc_return_to'] ?? '/';
|
||
unset($_SESSION['oidc_return_to'], $_SESSION['oidc_flow']);
|
||
if (!is_string($target) || $target === '' || $target[0] !== '/') $target = '/';
|
||
header('Location: ' . $target, true, 303);
|
||
exit;
|
||
}
|
||
|
||
// Sinon : go formulaire d’inscription (pré-rempli)
|
||
$_SESSION['pending_oidc'] = [
|
||
'issuer' => $OIDC_ISSUER,
|
||
'sub' => $claims['sub'] ?? null,
|
||
'email' => $email,
|
||
'username' => $claims['preferred_username'] ?? ($email ?: null),
|
||
'firstname' => $claims['given_name'] ?? null,
|
||
'lastname' => $claims['family_name'] ?? null,
|
||
];
|
||
unset($_SESSION['oidc_flow']);
|
||
|
||
header('Location: ' . url('register/from-oidc'), true, 303);
|
||
exit; |