This commit is contained in:
mrKamoo
2026-09-30 16:27:32 +02:00
commit 310724708f
147 changed files with 21327 additions and 0 deletions
@@ -0,0 +1,77 @@
<?php
namespace App\Services\Auth;
use App\Models\Tenant;
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use RuntimeException;
use Throwable;
/**
* Valide les jetons d'accès Entra envoyés par l'add-in (obtenus par NAA).
* Contrôles : signature (clés publiques Entra), expiration, audience, émetteur,
* tenant autorisé et actif, scope attendu.
*/
class EntraTokenValidator
{
/** @return array<string, mixed> claims du jeton */
public function validate(string $jwt): array
{
JWT::$leeway = (int) config('signature.addin.leeway_seconds', 120);
try {
$claims = (array) JWT::decode($jwt, $this->keys());
} catch (Throwable $e) {
// Rotation des clés Entra : un seul nouvel essai avec des clés fraîches.
Cache::forget('entra-jwks');
try {
$claims = (array) JWT::decode($jwt, $this->keys());
} catch (Throwable) {
throw new RuntimeException('Jeton invalide : '.$e->getMessage());
}
}
$tid = $claims['tid'] ?? null;
if (! $tid || ! Tenant::where('entra_tenant_id', $tid)->where('active', true)->exists()) {
throw new RuntimeException('Tenant non autorisé');
}
$issuers = ["https://login.microsoftonline.com/{$tid}/v2.0", "https://sts.windows.net/{$tid}/"];
if (! in_array($claims['iss'] ?? null, $issuers, true)) {
throw new RuntimeException('Émetteur inattendu');
}
$audiences = (array) config('signature.addin.audiences');
if (! $audiences || ! in_array($claims['aud'] ?? null, $audiences, true)) {
throw new RuntimeException('Audience inattendue : reçue « '.($claims['aud'] ?? 'aucune').' », attendue(s) « '.implode(', ', $audiences).' »');
}
$scopes = explode(' ', (string) ($claims['scp'] ?? ''));
if (! in_array(config('signature.addin.required_scope'), $scopes, true)) {
throw new RuntimeException('Scope manquant : reçu « '.($claims['scp'] ?? 'aucun').' »');
}
if (empty($claims['oid'])) {
throw new RuntimeException('Identifiant utilisateur absent');
}
return $claims;
}
private function keys(): array
{
$jwks = Cache::remember('entra-jwks', config('signature.addin.jwks_cache_seconds', 86400), function () {
$res = Http::timeout(10)->get('https://login.microsoftonline.com/common/discovery/v2.0/keys');
if (! $res->successful()) {
throw new RuntimeException('Clés Entra indisponibles');
}
return $res->json();
});
return JWK::parseKeySet($jwks, 'RS256');
}
}