Files
varlog/public/oidc/callback.php
T

194 lines
6.1 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);
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 dauthentification.';
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 dauthentification.';
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 dauthentification.';
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 dinscription (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;