Initial commit
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
<?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>
|
||||
Reference in New Issue
Block a user