Skip to content
Snippets Groups Projects
Commit 41294564 authored by Reiter, Christoph's avatar Reiter, Christoph :snake:
Browse files

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.
parent ad0cd513
No related branches found
No related tags found
No related merge requests found
Pipeline #112317 passed
...@@ -38,10 +38,15 @@ class BearerUserProvider implements BearerUserProviderInterface, LoggerAwareInte ...@@ -38,10 +38,15 @@ class BearerUserProvider implements BearerUserProviderInterface, LoggerAwareInte
return $config['local_validation_leeway']; return $config['local_validation_leeway'];
} }
public function usesRemoteValidation(): bool
{
return $this->config['remote_validation'];
}
public function loadUserByToken(string $accessToken): UserInterface public function loadUserByToken(string $accessToken): UserInterface
{ {
$config = $this->config; $config = $this->config;
if (!$config['remote_validation']) { if (!$this->usesRemoteValidation()) {
$leeway = $config['local_validation_leeway']; $leeway = $config['local_validation_leeway'];
$validator = new LocalTokenValidator($this->oidProvider, $leeway); $validator = new LocalTokenValidator($this->oidProvider, $leeway);
} else { } else {
......
...@@ -138,6 +138,49 @@ class OIDProvider implements LoggerAwareInterface ...@@ -138,6 +138,49 @@ class OIDProvider implements LoggerAwareInterface
return $jwks; 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 * Introspect the token via the provider. Note that you have to check the result to see if the
* token is valid/active. * token is valid/active.
......
...@@ -42,6 +42,11 @@ class OIDProviderConfig ...@@ -42,6 +42,11 @@ class OIDProviderConfig
return $this->config['jwks_uri']; return $this->config['jwks_uri'];
} }
public function getTokenEndpoint(): ?string
{
return $this->config['token_endpoint'] ?? null;
}
public function getIntrospectionEndpoint(): ?string public function getIntrospectionEndpoint(): ?string
{ {
return $this->config['introspection_endpoint'] ?? null; return $this->config['introspection_endpoint'] ?? null;
......
...@@ -51,6 +51,21 @@ class HealthCheck implements CheckInterface ...@@ -51,6 +51,21 @@ class HealthCheck implements CheckInterface
$this->oidcProvider->getJWKs(); $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() public function checkTimeSync()
{ {
$providerTime = $this->oidcProvider->getProviderDateTime(); $providerTime = $this->oidcProvider->getProviderDateTime();
...@@ -68,6 +83,7 @@ class HealthCheck implements CheckInterface ...@@ -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 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 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 the OIDC server time is in sync', [$this, 'checkTimeSync']);
$results[] = $this->checkMethod('Check if remote validation works (if enabled)', [$this, 'checkRemoteValidation']);
return $results; return $results;
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment