Files
varlog/public/oidc/me.php
T
Cedric Abonnel 700329f156 Initial commit
2026-05-08 12:55:46 +02:00

180 lines
6.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
// projet : mug.a5l.fr
// fichier : pages/oidc/me.php
// version : 20251005
declare(strict_types=1);
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';
function maskToken(?string $t): string {
if (!$t) return '';
$len = strlen($t);
if ($len <= 12) return str_repeat('•', $len);
return substr($t, 0, 6) . str_repeat('•', max(0, $len - 12)) . substr($t, -6);
}
function b64url_decode_str(string $s): string|false {
$s = strtr($s, '-_', '+/');
$pad = strlen($s) % 4;
if ($pad) $s .= str_repeat('=', 4 - $pad);
return base64_decode($s, true);
}
function decode_jwt(string $jwt): array {
if (substr_count($jwt, '.') !== 2) return [];
[, $payload, ] = explode('.', $jwt, 3);
$json = b64url_decode_str($payload);
if ($json === false) return [];
$arr = json_decode($json, true);
return is_array($arr) ? $arr : [];
}
$env = static function(string $k, ?string $d = null): ?string {
if (array_key_exists($k, $_ENV) && $_ENV[$k] !== '') return (string)$_ENV[$k];
$v = getenv($k);
if ($v !== false && $v !== '') return (string)$v;
return $d;
};
$debugEnabled = ($env('DEBUG_OIDC') === 'true') || (isset($_GET['debug']) && $_GET['debug'] === '1');
$oidc = $_SESSION['oidc'] ?? [];
$claims = $_SESSION['oidc_userinfo'] ?? [];
$issuer = (string)($oidc['issuer'] ?? '');
$sub = (string)($oidc['sub'] ?? '');
$idToken = (string)($oidc['id_token'] ?? '');
$accTok = (string)($oidc['access_token'] ?? '');
$expAt = (int) ($oidc['expires_at'] ?? 0);
$now = time();
$left = $expAt ? max(0, $expAt - $now) : null;
// Fallback 1 : si pas de claims userinfo, essayer de les lire dans l'id_token
if (!$claims && $idToken) {
$claims = decode_jwt($idToken);
}
// Fallback 2 (debug) : tenter un appel live au UserInfo si access_token présent
if ($debugEnabled && $claims === [] && $accTok && $issuer) {
$userinfoEndpoint = rtrim($issuer, '/') . '/protocol/openid-connect/userinfo';
$ch = curl_init($userinfoEndpoint);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $accTok],
CURLOPT_TIMEOUT => 6,
]);
$resp = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($resp !== false && $code === 200) {
$tmp = json_decode((string)$resp, true);
if (is_array($tmp)) $claims = $tmp;
}
}
// Extraire rôles groupés (Keycloak)
$roles = [];
if (isset($claims['realm_access']['roles']) && is_array($claims['realm_access']['roles'])) {
$roles = array_merge($roles, $claims['realm_access']['roles']);
}
if (isset($claims['resource_access']) && is_array($claims['resource_access'])) {
foreach ($claims['resource_access'] as $clientId => $data) {
if (!empty($data['roles']) && is_array($data['roles'])) {
foreach ($data['roles'] as $r) {
$roles[] = $clientId . ':' . $r;
}
}
}
}
$roles = array_values(array_unique($roles));
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>OIDC • Profil</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/assets/bootstrap/bootstrap.min.css" rel="stylesheet">
<style>
.kv { display:grid; grid-template-columns: 220px 1fr; gap:.5rem 1rem; }
.kv dt { font-weight: 600; color: #555; }
pre { background: #f8f9fa; padding: .75rem; border-radius: .5rem; overflow:auto; }
</style>
</head>
<body class="container py-4">
<h1 class="mb-4">Profil A5L</h1>
<?php if (!$oidc): ?>
<div class="alert alert-warning">Aucune session A5L. Connecte-toi via A5L d'abord.</div>
<?php else: ?>
<div class="card mb-4">
<div class="card-header">Session / Jetons</div>
<div class="card-body">
<dl class="kv">
<dt>Issuer</dt><dd><?= htmlspecialchars($issuer) ?></dd>
<dt>Subject (sub)</dt><dd><?= htmlspecialchars($sub) ?></dd>
<dt>ID Token</dt><dd><code><?= htmlspecialchars(maskToken($idToken)) ?></code></dd>
<dt>Access Token</dt><dd><code><?= htmlspecialchars(maskToken($accTok)) ?></code></dd>
<dt>Expire à</dt><dd><?= $expAt ? date('Y-m-d H:i:s', $expAt) : '—' ?></dd>
<dt>Temps restant</dt><dd><?= $left !== null ? ($left . ' s') : '—' ?></dd>
</dl>
<?php if ($debugEnabled): ?>
<details class="mt-3">
<summary>Voir jetons non masqués (danger)</summary>
<div class="mt-2">
<div><strong>ID Token</strong></div>
<pre><?= htmlspecialchars($idToken) ?></pre>
<div><strong>Access Token</strong></div>
<pre><?= htmlspecialchars($accTok) ?></pre>
</div>
</details>
<?php endif; ?>
</div>
</div>
<div class="card mb-4">
<div class="card-header">Claims</div>
<div class="card-body">
<dl class="kv">
<dt>Email</dt><dd><?= htmlspecialchars((string)($claims['email'] ?? '')) ?></dd>
<dt>Preferred username</dt><dd><?= htmlspecialchars((string)($claims['preferred_username'] ?? '')) ?></dd>
<dt>Given name</dt><dd><?= htmlspecialchars((string)($claims['given_name'] ?? '')) ?></dd>
<dt>Family name</dt><dd><?= htmlspecialchars((string)($claims['family_name'] ?? '')) ?></dd>
<dt>Name</dt><dd><?= htmlspecialchars((string)($claims['name'] ?? '')) ?></dd>
<dt>Locale</dt><dd><?= htmlspecialchars((string)($claims['locale'] ?? '')) ?></dd>
<dt>Rôles</dt>
<dd>
<?php if ($roles): ?>
<ul class="mb-0">
<?php foreach ($roles as $r): ?>
<li><?= htmlspecialchars((string)$r) ?></li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<?php endif; ?>
</dd>
</dl>
<?php if ($debugEnabled): ?>
<h6 class="mt-3">Claims (JSON complet)</h6>
<pre><?= htmlspecialchars(json_encode($claims, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)) ?></pre>
<?php endif; ?>
<?php if (!$claims): ?>
<div class="alert alert-info mt-3">
Aucun claim reçu. Vérifie que ton <code>callback</code> remplit bien <code>$_SESSION['oidc_userinfo']</code> ou que l<code>ID Token</code> contient les champs.
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<a class="btn btn-secondary" href="<?= htmlspecialchars(url('')) ?>">Retour</a>
</body>
</html>