diff --git a/src/Authenticator/BearerUserProvider.php b/src/Authenticator/BearerUserProvider.php index c0656f1aae2c7ee24cc76b365e86cea840483419..e209d48b8b230ad105e0adf38dfc42e8fd8777e8 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 fd961f8d209a0fca76d66b23fc0f90f661569a3d..040ba1975b578156747423116986eb18421385ac 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 8f1602fd541d1c843fb5c62e8fd92f2d14755e43..805793f5d61b7da97333455c1e886f575d7d284a 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 23093681aaa16334e3eb6240b6b547902f96032e..56f69b7dd06711c54f741796102fcc4a4b222e80 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; }