From 7fdff54c18fd6b07604a1f5815635515a4f38e43 Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Wed, 29 Mar 2023 15:37:14 +0200 Subject: [PATCH] Use cacheable OpenID Configuration (#5) --- config/oidc.php | 16 ++++++++-- src/OpenIDConnectClient.php | 32 ++++++++++++++++++- src/OpenIDConnectServiceProvider.php | 7 ++-- .../Feature/OpenIDConfigurationLoaderTest.php | 23 +++++++++++++ 4 files changed, 71 insertions(+), 7 deletions(-) diff --git a/config/oidc.php b/config/oidc.php index aa39bd9..d7c0dfd 100644 --- a/config/oidc.php +++ b/config/oidc.php @@ -35,9 +35,19 @@ 'code_challenge_method' => env('OIDC_CODE_CHALLENGE_METHOD', 'S256'), /** - * TTL of the OpenID configuration cache in seconds. - */ - 'configuration_cache_ttl' => env('OIDC_CONFIGURATION_CACHE_TTL', 60 * 60 * 24), + * Configuration Cache + */ + 'configuration_cache' => [ + /** + * The cache store to use. + */ + 'store' => env('OIDC_CONFIGURATION_CACHE_DRIVER', 'file'), + + /** + * The cache TTL in seconds. + */ + 'ttl' => env('OIDC_CONFIGURATION_CACHE_TTL', 60 * 60 * 24), + ], /** * Route configuration diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2399d4e..f5ab59a 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -5,24 +5,29 @@ namespace MinVWS\OpenIDConnectLaravel; use Illuminate\Support\Facades\Session; +use Illuminate\Support\Str; use Jumbojett\OpenIDConnectClient as BaseOpenIDConnectClient; use Jumbojett\OpenIDConnectClientException; +use MinVWS\OpenIDConnectLaravel\OpenIDConfiguration\OpenIDConfiguration; use MinVWS\OpenIDConnectLaravel\Services\JWE\JweDecryptInterface; class OpenIDConnectClient extends BaseOpenIDConnectClient { protected ?JweDecryptInterface $jweDecrypter; + protected ?OpenIDConfiguration $openIDConfiguration; public function __construct( ?string $providerUrl = null, ?string $clientId = null, ?string $clientSecret = null, ?string $issuer = null, - ?JweDecryptInterface $jweDecrypter = null + ?JweDecryptInterface $jweDecrypter = null, + ?OpenIDConfiguration $openIDConfiguration = null, ) { parent::__construct($providerUrl, $clientId, $clientSecret, $issuer); $this->jweDecrypter = $jweDecrypter; + $this->openIDConfiguration = $openIDConfiguration; } protected function startSession(): void @@ -78,4 +83,29 @@ protected function handleJweResponse($jwe): string } return $this->jweDecrypter->decrypt($jwe); } + + /** + * Use cached OpenID configuration if available. + * + * @param string $param + * @param string $default optional + * @throws OpenIDConnectClientException + * @return string|string[]|bool + * @psalm-suppress ImplementedReturnTypeMismatch + */ + protected function getWellKnownConfigValue($param, $default = null): string|array|bool + { + if ($this->openIDConfiguration === null) { + return parent::getWellKnownConfigValue($param, $default); + } + + $config = $this->openIDConfiguration; + $param = Str::camel($param); + + if (!property_exists($config, $param)) { + return parent::getWellKnownConfigValue($param, $default); + } + + return $config->{$param}; + } } diff --git a/src/OpenIDConnectServiceProvider.php b/src/OpenIDConnectServiceProvider.php index 54cb174..e196582 100644 --- a/src/OpenIDConnectServiceProvider.php +++ b/src/OpenIDConnectServiceProvider.php @@ -64,8 +64,8 @@ protected function registerConfigurationLoader(): void $this->app->singleton(OpenIDConfigurationLoader::class, function (Application $app) { return new OpenIDConfigurationLoader( $app['config']->get('oidc.issuer'), - $app['config']->get('oidc.cache_ttl'), - $app['config']->get('oidc.cache_store'), + $app['cache']->store($app['config']->get('oidc.configuration_cache.store')), + $app['config']->get('oidc.configuration_cache.ttl'), ); }); } @@ -75,7 +75,8 @@ protected function registerClient(): void $this->app->singleton(OpenIDConnectClient::class, function (Application $app) { $oidc = new OpenIDConnectClient( providerUrl: $app['config']->get('oidc.issuer'), - jweDecrypter: $app->make(JweDecryptInterface::class) + jweDecrypter: $app->make(JweDecryptInterface::class), + openIDConfiguration: $app->make(OpenIDConfigurationLoader::class)->getConfiguration(), ); $oidc->setClientID($app['config']->get('oidc.client_id')); if (!empty($app['config']->get('oidc.client_secret'))) { diff --git a/tests/Feature/OpenIDConfigurationLoaderTest.php b/tests/Feature/OpenIDConfigurationLoaderTest.php index f1b828e..7bf330f 100644 --- a/tests/Feature/OpenIDConfigurationLoaderTest.php +++ b/tests/Feature/OpenIDConfigurationLoaderTest.php @@ -200,6 +200,29 @@ public function testLoaderReturnsEmptyConfigurationOnEmptyJsonResponse(): void $this->assertEmpty($configuration->tokenEndpoint); } + public function testConfigurationIsLoadedMultipleTimesWhenCacheStoreIsNull(): void + { + $this->fakeSuccessfulResponse(); + + $loader = new OpenIDConfigurationLoader( + issuer: 'https://provider.rdobeheer.nl', + cacheStore: Cache::store('null'), + ); + + // Load 2 times + $loader->getConfiguration(); + $configuration = $loader->getConfiguration(); + + Http::assertSentCount(2); + + $this->assertSame("3.0", $configuration->version); + $this->assertSame("https://provider.rdobeheer.nl", $configuration->issuer); + $this->assertSame("https://provider.rdobeheer.nl/authorize", $configuration->authorizationEndpoint); + $this->assertSame("https://provider.rdobeheer.nl/jwks", $configuration->jwksUri); + $this->assertSame("https://provider.rdobeheer.nl/token", $configuration->tokenEndpoint); + $this->assertSame("https://provider.rdobeheer.nl/userinfo", $configuration->userinfoEndpoint); + } + protected function fakeSuccessfulResponse(): void { Http::fake([