-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[Security] Add clock dependency to OidcTokenHandler #50477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,6 @@ | |
|
||
use Jose\Component\Core\Algorithm; | ||
use Jose\Component\Core\JWK; | ||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SignatureAlgorithmFactory; | ||
use Symfony\Component\Config\Definition\Builder\NodeBuilder; | ||
use Symfony\Component\DependencyInjection\ChildDefinition; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
|
@@ -28,26 +27,28 @@ class OidcTokenHandlerFactory implements TokenHandlerFactoryInterface | |
{ | ||
public function create(ContainerBuilder $container, string $id, array|string $config): void | ||
{ | ||
$tokenHandlerDefinition = $container->setDefinition($id, new ChildDefinition('security.access_token_handler.oidc')); | ||
$tokenHandlerDefinition->replaceArgument(3, $config['claim']); | ||
$tokenHandlerDefinition->replaceArgument(4, $config['audience']); | ||
$tokenHandlerDefinition = $container->setDefinition($id, (new ChildDefinition('security.access_token_handler.oidc')) | ||
->replaceArgument(4, $config['claim']) | ||
->replaceArgument(5, $config['audience']) | ||
); | ||
|
||
// Create the signature algorithm and the JWK | ||
if (!ContainerBuilder::willBeAvailable('web-token/jwt-core', Algorithm::class, ['symfony/security-bundle'])) { | ||
$container->register('security.access_token_handler.oidc.signature', 'stdClass') | ||
$container->register('security.access_token_handler.oidc.signature', Algorithm::class) | ||
->addError('You cannot use the "oidc" token handler since "web-token/jwt-core" is not installed. Try running "web-token/jwt-core".'); | ||
$container->register('security.access_token_handler.oidc.jwk', 'stdClass') | ||
$container->register('security.access_token_handler.oidc.jwk', JWK::class) | ||
->addError('You cannot use the "oidc" token handler since "web-token/jwt-core" is not installed. Try running "web-token/jwt-core".'); | ||
} | ||
|
||
if (\in_array($config['algorithm'], ['ES256', 'ES384', 'ES512'], true)) { | ||
$tokenHandlerDefinition->replaceArgument(0, new Reference('security.access_token_handler.oidc.signature.'.$config['algorithm'])); | ||
} else { | ||
$container->register('security.access_token_handler.oidc.signature', Algorithm::class) | ||
->setFactory([SignatureAlgorithmFactory::class, 'create']) | ||
->setArguments([$config['signature']['algorithm']]); | ||
$container->register('security.access_token_handler.oidc.jwk', JWK::class) | ||
->setFactory([JWK::class, 'createFromJson']) | ||
->setArguments([$config['signature']['key']]); | ||
$tokenHandlerDefinition->replaceArgument(0, (new ChildDefinition('security.access_token_handler.oidc.signature'))) | ||
->replaceArgument(0, $config['algorithm']); | ||
} | ||
$tokenHandlerDefinition->replaceArgument(0, new Reference('security.access_token_handler.oidc.signature')); | ||
$tokenHandlerDefinition->replaceArgument(1, new Reference('security.access_token_handler.oidc.jwk')); | ||
|
||
$tokenHandlerDefinition->replaceArgument(1, (new ChildDefinition('security.access_token_handler.oidc.jwk')) | ||
->replaceArgument(0, $config['key']) | ||
); | ||
} | ||
|
||
public function getKey(): string | ||
|
@@ -69,18 +70,13 @@ public function addConfiguration(NodeBuilder $node): void | |
->info('Audience set in the token, for validation purpose.') | ||
->defaultNull() | ||
->end() | ||
->arrayNode('signature') | ||
->scalarNode('algorithm') | ||
->info('Algorithm used to sign the token.') | ||
->isRequired() | ||
->end() | ||
->scalarNode('key') | ||
->info('JSON-encoded JWK used to sign the token (must contain a "kty" key).') | ||
->isRequired() | ||
->children() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removing one unneeded nesting level in the config /cc @vincentchalamon for the doc There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
->scalarNode('algorithm') | ||
->info('Algorithm used to sign the token.') | ||
->isRequired() | ||
->end() | ||
->scalarNode('key') | ||
->info('JSON-encoded JWK used to sign the token (must contain a "kty" key).') | ||
->isRequired() | ||
->end() | ||
->end() | ||
->end() | ||
->end() | ||
->end() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,8 +20,9 @@ | |
use Jose\Component\Signature\JWSVerifier; | ||
use Jose\Component\Signature\Serializer\CompactSerializer; | ||
use Jose\Component\Signature\Serializer\JWSSerializerManager; | ||
use Psr\Clock\ClockInterface; | ||
use Psr\Log\LoggerInterface; | ||
use Symfony\Component\Clock\NativeClock; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NativeClock should never be used directly since 6.3 |
||
use Symfony\Component\Clock\Clock; | ||
use Symfony\Component\Security\Core\Exception\BadCredentialsException; | ||
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; | ||
use Symfony\Component\Security\Http\AccessToken\Oidc\Exception\InvalidSignatureException; | ||
|
@@ -41,6 +42,7 @@ public function __construct( | |
private Algorithm $signatureAlgorithm, | ||
private JWK $jwk, | ||
private ?LoggerInterface $logger = null, | ||
private ClockInterface $clock = new Clock(), | ||
private string $claim = 'sub', | ||
private ?string $audience = null | ||
) { | ||
|
@@ -74,11 +76,10 @@ public function getUserBadgeFrom(string $accessToken): UserBadge | |
$headerCheckerManager->check($jws, 0); | ||
|
||
// Verify the claims | ||
$clock = class_exists(NativeClock::class) ? new NativeClock() : null; | ||
$checkers = [ | ||
new Checker\IssuedAtChecker(0, false, $clock), | ||
new Checker\NotBeforeChecker(0, false, $clock), | ||
new Checker\ExpirationTimeChecker(0, false, $clock), | ||
new Checker\IssuedAtChecker(0, false, $this->clock), | ||
new Checker\NotBeforeChecker(0, false, $this->clock), | ||
new Checker\ExpirationTimeChecker(0, false, $this->clock), | ||
]; | ||
if ($this->audience) { | ||
$checkers[] = new Checker\AudienceChecker($this->audience); | ||
|
@@ -93,7 +94,7 @@ public function getUserBadgeFrom(string $accessToken): UserBadge | |
|
||
// UserLoader argument can be overridden by a UserProvider on AccessTokenAuthenticator::authenticate | ||
return new UserBadge($claims[$this->claim], fn () => $this->createUser($claims), $claims); | ||
} catch (\Throwable $e) { | ||
} catch (\Exception $e) { | ||
$this->logger?->error('An error while decoding and validating the token.', [ | ||
'error' => $e->getMessage(), | ||
'trace' => $e->getTraceAsString(), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,7 +49,7 @@ public function getUserBadgeFrom(string $accessToken): UserBadge | |
|
||
// UserLoader argument can be overridden by a UserProvider on AccessTokenAuthenticator::authenticate | ||
return new UserBadge($claims[$this->claim], fn () => $this->createUser($claims), $claims); | ||
} catch (\Throwable $e) { | ||
} catch (\Exception $e) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should never catch |
||
$this->logger?->error('An error occurred on OIDC server.', [ | ||
'error' => $e->getMessage(), | ||
'trace' => $e->getTraceAsString(), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes the wiring. Before this change, all firewalls would share a single jwk+algo definition, since varying key/algo were registered under the same id