From 412945641ec86cfa4c9d227c4547c9a33e9043cd Mon Sep 17 00:00:00 2001 From: Christoph Reiter <reiter.christoph@gmail.com> Date: Wed, 11 May 2022 12:48:31 +0200 Subject: [PATCH] Add a health check for remote token validation Using the client that has access to the introspection endpoint create a dummy access tokenand introspect it. This makes sure that the client is valid and that introspection works. --- src/Authenticator/BearerUserProvider.php | 7 +++- src/OIDC/OIDProvider.php | 43 ++++++++++++++++++++++++ src/OIDC/OIDProviderConfig.php | 5 +++ src/Service/HealthCheck.php | 16 +++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/Authenticator/BearerUserProvider.php b/src/Authenticator/BearerUserProvider.php index c0656f1..e209d48 100644 --- a/src/Authenticator/BearerUserProvider.php +++ b/src/Authenticator/BearerUserProvider.php @@ -38,10 +38,15 @@ class BearerUserProvider implements BearerUserProviderInterface, LoggerAwareInte return $config['local_validation_leeway']; } + public function usesRemoteValidation(): bool + { + return $this->config['remote_validation']; + } + public function loadUserByToken(string $accessToken): UserInterface { $config = $this->config; - if (!$config['remote_validation']) { + if (!$this->usesRemoteValidation()) { $leeway = $config['local_validation_leeway']; $validator = new LocalTokenValidator($this->oidProvider, $leeway); } else { diff --git a/src/OIDC/OIDProvider.php b/src/OIDC/OIDProvider.php index fd961f8..040ba19 100644 --- a/src/OIDC/OIDProvider.php +++ b/src/OIDC/OIDProvider.php @@ -138,6 +138,49 @@ class OIDProvider implements LoggerAwareInterface return $jwks; } + /** + * This creates a token using the introspection client. Mainly for testing + * introspection during health checks. + */ + public function createToken(): string + { + $providerConfig = $this->getProviderConfig(); + $tokenEndpoint = $providerConfig->getTokenEndpoint(); + if ($tokenEndpoint === null) { + throw new OIDError('No token endpoint'); + } + + $authId = $this->config['remote_validation_id'] ?? ''; + $authSecret = $this->config['remote_validation_secret'] ?? ''; + if ($authId === '' || $authSecret === '') { + throw new OIDError('remote_validation_id/secret not set'); + } + + $client = $this->getClient(); + + try { + // keep in mind that even if we are doing this request with a different client id the data returned will be + // from the client id of token $token (that's important for mapped attributes) + $response = $client->request('POST', $tokenEndpoint, [ + 'auth' => [$authId, $authSecret], + 'form_params' => [ + 'grant_type' => 'client_credentials', + ], + ]); + } catch (GuzzleException $e) { + throw new OIDError('Creating a token failed: '.$e->getMessage()); + } + + $data = (string) $response->getBody(); + try { + $decoded = json_decode($data, true, 512, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new OIDError('Token: invalid json: '.$e->getMessage()); + } + + return $decoded['access_token']; + } + /** * Introspect the token via the provider. Note that you have to check the result to see if the * token is valid/active. diff --git a/src/OIDC/OIDProviderConfig.php b/src/OIDC/OIDProviderConfig.php index 8f1602f..805793f 100644 --- a/src/OIDC/OIDProviderConfig.php +++ b/src/OIDC/OIDProviderConfig.php @@ -42,6 +42,11 @@ class OIDProviderConfig return $this->config['jwks_uri']; } + public function getTokenEndpoint(): ?string + { + return $this->config['token_endpoint'] ?? null; + } + public function getIntrospectionEndpoint(): ?string { return $this->config['introspection_endpoint'] ?? null; diff --git a/src/Service/HealthCheck.php b/src/Service/HealthCheck.php index 2309368..56f69b7 100644 --- a/src/Service/HealthCheck.php +++ b/src/Service/HealthCheck.php @@ -51,6 +51,21 @@ class HealthCheck implements CheckInterface $this->oidcProvider->getJWKs(); } + public function checkRemoteValidation() + { + if (!$this->userProvider->usesRemoteValidation()) { + // Not configured, so don't test + return; + } + + // Create a dummy token, and introspect it + $accessToken = $this->oidcProvider->createToken(); + $token = $this->oidcProvider->introspectToken($accessToken); + if ($token['active'] !== true) { + throw new \RuntimeException('invalid token'); + } + } + public function checkTimeSync() { $providerTime = $this->oidcProvider->getProviderDateTime(); @@ -68,6 +83,7 @@ class HealthCheck implements CheckInterface $results[] = $this->checkMethod('Check if the OIDC config can be fetched', [$this, 'checkConfig']); $results[] = $this->checkMethod('Check if the OIDC public key can be fetched', [$this, 'checkPublicKey']); $results[] = $this->checkMethod('Check if the OIDC server time is in sync', [$this, 'checkTimeSync']); + $results[] = $this->checkMethod('Check if remote validation works (if enabled)', [$this, 'checkRemoteValidation']); return $results; } -- GitLab