Skip to content

Commit 6bd07ac

Browse files
Merge branch '7.4' into 8.0
* 7.4: [TypeInfo] Simple array should be array type Handle signals on text input [TwigBridge] Fix form constraint [Runtime] Reuse the already created Request object when the app needs it as argument returns a kernel [Config] Fix array shape generation for backed enums [Config] Define `TreeBuilder` default generic type Update validators.el.xlf Fix MoneyType: add missing step attribute when html5=true [JsonStreamer] fix invalid json output for list of self [Console] Preserve `--help` option when a command is not found [FrameworkBundle] Fix using `FailedMessages*Command` with `SigningSerializer` [Lock] Fix unserializing already serialized Key payloads [HttpClient] CachingHttpClient must run after UriTemplate and Scoping Only register PhpConfigReferenceDumpPass in dev env with debug flag enabled [Messenger] Fix PHP 8.5 deprecation for pgsqlGetNotify() in PostgreSQL transport chore: PHP CS Fixer - do not use deprecated sets in config verify spanish translations with state needs-review-translation [Security] Fix OIDC discovery when using multiple HttpClient instances [DependencyInjection] Allow manual bindings on parameters with #[Target]
2 parents 51b887d + 46a4432 commit 6bd07ac

File tree

2 files changed

+100
-13
lines changed

2 files changed

+100
-13
lines changed

AccessToken/Oidc/OidcTokenHandler.php

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -135,24 +135,31 @@ public function getUserBadgeFrom(string $accessToken): UserBadge
135135
public function computeDiscoveryKeys(ItemInterface $item): array
136136
{
137137
$clients = $this->discoveryClients;
138+
if (!$clients) {
139+
throw new \LogicException('No OIDC discovery client configured.');
140+
}
138141
$logger = $this->logger;
139-
140142
try {
143+
$keys = [];
144+
$minTtl = null;
141145
$configResponses = [];
146+
$jwkSetResponses = [];
147+
142148
foreach ($clients as $client) {
143-
$configResponses[] = $client->request('GET', '.well-known/openid-configuration', [
144-
'user_data' => $client,
145-
]);
149+
$configResponses[] = [$client, $client->request('GET', '.well-known/openid-configuration')];
146150
}
147151

148-
$jwkSetResponses = [];
149-
foreach ($client->stream($configResponses) as $response => $chunk) {
150-
if ($chunk->isLast()) {
151-
$jwkSetResponses[] = $response->getInfo('user_data')->request('GET', $response->toArray()['jwks_uri']);
152+
foreach ($configResponses as [$client, $response]) {
153+
$config = $response->toArray();
154+
155+
$jwksUri = $config['jwks_uri'] ?? null;
156+
if (!\is_string($jwksUri) || '' === $jwksUri) {
157+
throw new \RuntimeException('The "jwks_uri" is missing from the OIDC discovery document.');
152158
}
159+
160+
$jwkSetResponses[] = $client->request('GET', $jwksUri);
153161
}
154-
$keys = [];
155-
$minTtl = null;
162+
156163
foreach ($jwkSetResponses as $response) {
157164
$headers = $response->getHeaders();
158165
if (preg_match('/max-age=(\d+)/', $headers['cache-control'][0] ?? '', $m)) {
@@ -167,7 +174,7 @@ public function computeDiscoveryKeys(ItemInterface $item): array
167174
}
168175

169176
foreach ($response->toArray()['keys'] as $key) {
170-
if ('sig' === $key['use']) {
177+
if ('sig' === ($key['use'] ?? null)) {
171178
$keys[] = $key;
172179
}
173180
}
@@ -184,6 +191,7 @@ public function computeDiscoveryKeys(ItemInterface $item): array
184191
'error' => $e->getMessage(),
185192
'trace' => $e->getTraceAsString(),
186193
]);
194+
187195
throw new BadCredentialsException('Invalid credentials.', $e->getCode(), $e);
188196
}
189197
}

Tests/AccessToken/Oidc/OidcTokenHandlerTest.php

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use Symfony\Component\Security\Core\User\OidcUser;
2929
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcTokenHandler;
3030
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
31+
use Symfony\Contracts\Cache\ItemInterface;
3132

3233
#[RequiresPhpExtension('openssl')]
3334
class OidcTokenHandlerTest extends TestCase
@@ -316,7 +317,8 @@ public function testDiscoveryCachesJwksAccordingToCacheControl()
316317
{
317318
$time = time();
318319
$claims = [
319-
'iat' => $time, 'nbf' => $time,
320+
'iat' => $time,
321+
'nbf' => $time,
320322
'exp' => $time + 3600,
321323
'iss' => 'https://www.example.com',
322324
'aud' => self::AUDIENCE,
@@ -355,7 +357,8 @@ public function testDiscoveryCachesJwksAccordingToExpires()
355357
{
356358
$time = time();
357359
$claims = [
358-
'iat' => $time, 'nbf' => $time,
360+
'iat' => $time,
361+
'nbf' => $time,
359362
'exp' => $time + 3600,
360363
'iss' => 'https://www.example.com',
361364
'aud' => self::AUDIENCE,
@@ -390,4 +393,80 @@ public function testDiscoveryCachesJwksAccordingToExpires()
390393
$this->assertSame('user-expires', $handler->getUserBadgeFrom($token)->getUserIdentifier());
391394
$this->assertSame(2, $requestCount);
392395
}
396+
397+
public function testComputeDiscoveryKeysReturnsEmptyWhenNoClients()
398+
{
399+
$cache = new ArrayAdapter();
400+
$handler = new OidcTokenHandler(
401+
new AlgorithmManager([new ES256()]),
402+
null,
403+
self::AUDIENCE,
404+
['https://www.example.com']
405+
);
406+
407+
$handler->enableDiscovery($cache, [], 'oidc_empty_clients');
408+
409+
$item = $this->createMock(ItemInterface::class);
410+
$item->expects($this->never())->method('expiresAfter');
411+
412+
$this->expectException(\LogicException::class);
413+
$this->expectExceptionMessage('No OIDC discovery client configured.');
414+
$handler->computeDiscoveryKeys($item);
415+
}
416+
417+
public function testDiscoveryThrowsWhenJwksUriIsMissing()
418+
{
419+
$time = time();
420+
$claims = [
421+
'iat' => $time,
422+
'nbf' => $time,
423+
'exp' => $time + 3600,
424+
'iss' => 'https://www.example.com',
425+
'aud' => self::AUDIENCE,
426+
'sub' => 'user-missing-jwks-uri',
427+
];
428+
$token = self::buildJWS(json_encode($claims));
429+
430+
$httpClient = new MockHttpClient([
431+
new JsonMockResponse(['issuer' => 'https://www.example.com']),
432+
]);
433+
434+
$cache = new ArrayAdapter();
435+
$handler = new OidcTokenHandler(
436+
new AlgorithmManager([new ES256()]),
437+
null,
438+
self::AUDIENCE,
439+
['https://www.example.com']
440+
);
441+
$handler->enableDiscovery($cache, $httpClient, 'oidc_missing_jwks_uri');
442+
443+
$this->expectException(BadCredentialsException::class);
444+
$handler->getUserBadgeFrom($token);
445+
}
446+
447+
public function testDiscoveryIgnoresNonSignatureKeys()
448+
{
449+
$httpClient = new MockHttpClient([
450+
new JsonMockResponse(['jwks_uri' => 'https://www.example.com/jwks.json']),
451+
new JsonMockResponse([
452+
'keys' => [
453+
array_merge(self::getJWK()->all(), ['use' => 'enc']),
454+
array_merge(self::getSecondJWK()->all(), []),
455+
],
456+
]),
457+
]);
458+
459+
$cache = new ArrayAdapter();
460+
$handler = new OidcTokenHandler(
461+
new AlgorithmManager([new ES256()]),
462+
null,
463+
self::AUDIENCE,
464+
['https://www.example.com']
465+
);
466+
$handler->enableDiscovery($cache, $httpClient, 'oidc_non_sig_keys');
467+
468+
$item = $this->createMock(ItemInterface::class);
469+
$item->expects($this->never())->method('expiresAfter');
470+
$this->assertSame([], $handler->computeDiscoveryKeys($item));
471+
}
393472
}

0 commit comments

Comments
 (0)