Skip to content

Commit

Permalink
[OidcClient] add a method to fetch introspection URL
Browse files Browse the repository at this point in the history
  • Loading branch information
David-Dadon authored and bobvandevijver committed Sep 10, 2024
1 parent 5440783 commit 3ab815a
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 1 deletion.
125 changes: 125 additions & 0 deletions src/Model/OidcIntrospectionData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

namespace Drenso\OidcBundle\Model;

use stdClass;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessor;

class OidcIntrospectionData
{
private static ?PropertyAccessor $accessor = null;
private readonly stdClass $introspectionData;

public function __construct(array $introspectionData)
{
// Cast the array data to a stdClass for easy access
$this->introspectionData = (object)$introspectionData;
}

public function isActive(): bool
{
return $this->getIntrospectionDataBoolean('active');
}

/** Get the OIDC scope claim */
public function getScope(): string
{
return $this->getIntrospectionDataString('scope');
}

/** Get the OIDC client_id claim */
public function getClientId(): string
{
return $this->getIntrospectionDataString('client_id');
}

/** Get the OIDC username claim */
public function getUsername(): string
{
return $this->getIntrospectionDataString('username');
}

/** Get the OIDC token_type claim */
public function getTokenType(): string
{
return $this->getIntrospectionDataString('token_type');
}

/** Get the OIDC exp claim */
public function getExp(): ?int
{
return $this->getIntrospectionDataInteger('exp');
}

/** Get the OIDC iat claim */
public function getIat(): ?int
{
return $this->getIntrospectionDataInteger('iat');
}

/** Get the OIDC nbf claim */
public function getNbf(): ?int
{
return $this->getIntrospectionDataInteger('nbf');
}

/** Get the OIDC sub claim */
public function getSub(): string
{
return $this->getIntrospectionDataString('sub');
}

/** Get the OIDC aud claim */
public function getAud(): string|array
{
return $this->getIntrospectionDataStringOrArray('aud');
}

/** Get the OIDC iss claim */
public function getIss(): string
{
return $this->getIntrospectionDataString('iss');
}

/** Get the OIDC jti claim */
public function getJti(): string
{
return $this->getIntrospectionDataString('jti');
}

/** Get a boolean property from the introspection data */
public function getIntrospectionDataBoolean(string $key): bool
{
return $this->getIntrospectionData($key) ?: false;
}

/** Get a string property from the introspection data */
public function getIntrospectionDataString(string $key): string
{
return $this->getIntrospectionData($key) ?: '';
}

/** Get a string property from the introspection data */
public function getIntrospectionDataStringOrArray(string $key): string|array
{
return $this->getIntrospectionData($key) ?: '';
}

/** Get a integer property from the introspection data */
public function getIntrospectionDataInteger(string $key): ?int
{
return $this->getIntrospectionData($key) ?: null;
}

public function getIntrospectionData(string $propertyPath): mixed
{
self::$accessor ??= PropertyAccess::createPropertyAccessorBuilder()
->disableExceptionOnInvalidIndex()
->disableExceptionOnInvalidPropertyPath()
->getPropertyAccessor();

// Cast the introspection data to a stdClass
return self::$accessor->getValue($this->introspectionData, $propertyPath);
}
}
64 changes: 63 additions & 1 deletion src/OidcClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

namespace Drenso\OidcBundle;

use Drenso\OidcBundle\Enum\OidcTokenType;
use Drenso\OidcBundle\Exception\OidcCodeChallengeMethodNotSupportedException;
use Drenso\OidcBundle\Exception\OidcConfigurationException;
use Drenso\OidcBundle\Exception\OidcConfigurationResolveException;
use Drenso\OidcBundle\Exception\OidcException;
use Drenso\OidcBundle\Model\OidcIntrospectionData;
use Drenso\OidcBundle\Model\OidcTokens;
use Drenso\OidcBundle\Model\OidcUserData;
use Drenso\OidcBundle\Model\UnvalidatedOidcTokens;
Expand Down Expand Up @@ -238,6 +240,39 @@ public function retrieveUserInfo(OidcTokens $tokens): OidcUserData
return new OidcUserData($data);
}

public function introspect(OidcTokens $tokens, ?OidcTokenType $tokenType = null): OidcIntrospectionData
{
$headers = [];
if (in_array('client_secret_basic', $this->getIntrospectionEndpointAuthMethodsSupported())) {
$headers = [$this->generateBasicAuthorization()];
}

$params = match ($tokenType) {
OidcTokenType::ACCESS => [
'token' => $tokens->getAccessToken(),
'token_type_hint' => 'access_token',
],
OidcTokenType::REFRESH => [
'token' => $tokens->getRefreshToken(),
'token_type_hint' => 'refresh_token',
],
default => throw new InvalidArgumentException('Only access and refresh tokens can be introspected'),
};

$jsonData = $this->urlFetcher->fetchUrl($this->getIntrospectionEndpoint(), $params, $headers);
$jsonData = mb_convert_encoding($jsonData, 'UTF-8');

// Read the data
$data = json_decode($jsonData, true);

// Check data due
if (!is_array($data)) {
throw new OidcException('Error from the introspection endpoint.');
}

return new OidcIntrospectionData($data);
}

/**
* @throws OidcConfigurationException
* @throws OidcConfigurationResolveException
Expand Down Expand Up @@ -321,6 +356,28 @@ protected function getUserinfoEndpoint(): string
return $this->getConfigurationValue('userinfo_endpoint');
}

/**
* @throws OidcConfigurationException
* @throws OidcConfigurationResolveException
*/
protected function getIntrospectionEndpointAuthMethodsSupported(): array
{
try {
return $this->getConfigurationValue('introspection_endpoint_auth_methods_supported');
} catch (OidcConfigurationException) {
return $this->getTokenEndpointAuthMethods();
}
}

/**
* @throws OidcConfigurationException
* @throws OidcConfigurationResolveException
*/
protected function getIntrospectionEndpoint(): string
{
return $this->getConfigurationValue('introspection_endpoint');
}

/** Generate a nonce to verify the response */
private function generateNonce(): string
{
Expand Down Expand Up @@ -433,7 +490,7 @@ private function requestTokens(
// Use basic auth if offered
$headers = [];
if (in_array('client_secret_basic', $this->getTokenEndpointAuthMethods())) {
$headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientId) . ':' . urlencode($this->clientSecret))];
$headers = [$this->generateBasicAuthorization()];
unset($params['client_id']);
unset($params['client_secret']);
}
Expand Down Expand Up @@ -536,4 +593,9 @@ private function retrieveWellKnownConfiguration(): array

return $this->wellKnownParser?->parseWellKnown($config) ?? $config;
}

private function generateBasicAuthorization(): string
{
return 'Authorization: Basic ' . base64_encode(urlencode($this->clientId) . ':' . urlencode($this->clientSecret));
}
}
9 changes: 9 additions & 0 deletions src/OidcClientInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

namespace Drenso\OidcBundle;

use Drenso\OidcBundle\Enum\OidcTokenType;
use Drenso\OidcBundle\Exception\OidcCodeChallengeMethodNotSupportedException;
use Drenso\OidcBundle\Exception\OidcConfigurationException;
use Drenso\OidcBundle\Exception\OidcConfigurationResolveException;
use Drenso\OidcBundle\Exception\OidcException;
use Drenso\OidcBundle\Model\OidcIntrospectionData;
use Drenso\OidcBundle\Model\OidcTokens;
use Drenso\OidcBundle\Model\OidcUserData;
use Symfony\Component\HttpFoundation\RedirectResponse;
Expand Down Expand Up @@ -82,4 +84,11 @@ public function generateEndSessionEndpointRedirect(
* @throws OidcException
*/
public function retrieveUserInfo(OidcTokens $tokens): OidcUserData;

/**
* Introspect the supplied token.
*
* @throws OidcException
*/
public function introspect(OidcTokens $tokens, ?OidcTokenType $tokenType = null): OidcIntrospectionData;
}

0 comments on commit 3ab815a

Please sign in to comment.