Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Classes/Authentication/OpenIdConnectEntryPoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Flownative\OpenIdConnect\Client\ConfigurationException;
use Flownative\OpenIdConnect\Client\CookieSettings;
use Flownative\OpenIdConnect\Client\OAuthClient;
use Flownative\OpenIdConnect\Client\OpenIdConnectClient;
use Flownative\OpenIdConnect\Client\OpenIdConnectClientFactory;
use Flownative\OpenIdConnect\Client\ServiceException;
use GuzzleHttp\Psr7\Query;
Expand Down Expand Up @@ -106,6 +107,10 @@ private function validateOptions(): void
if (!is_array($authorizationParameters) || array_filter($authorizationParameters, static fn (mixed $value, mixed $name): bool => !is_string($name) || $name === '' || !is_string($value), ARRAY_FILTER_USE_BOTH) !== []) {
throw new ConfigurationException('OpenID Connect: "authorizationParameters" option must map parameter names to strings for OpenIdConnectEntryPoint', 1791540884);
}
$reservedParameterNames = array_intersect(array_keys($authorizationParameters), OpenIdConnectClient::RESERVED_AUTHORIZATION_PARAMETER_NAMES);
if ($reservedParameterNames !== []) {
throw new ConfigurationException(sprintf('OpenID Connect: "authorizationParameters" option must not contain "%s", because the client sets these parameters itself, for OpenIdConnectEntryPoint', implode('", "', $reservedParameterNames)), 1791799612);
}
$loginFailedRedirectUri = $this->options['loginFailedRedirectUri'] ?? null;
if ($loginFailedRedirectUri !== null && !self::isRedirectTarget($loginFailedRedirectUri)) {
throw new ConfigurationException('OpenID Connect: "loginFailedRedirectUri" option must be a path starting with "/" or an absolute http(s) URI for OpenIdConnectEntryPoint', 1791540885);
Expand Down
14 changes: 9 additions & 5 deletions Classes/OpenIdConnectClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@

final class OpenIdConnectClient
{
/**
* Parameters of the authorization request which this client or the OAuth client set themselves
*/
public const array RESERVED_AUTHORIZATION_PARAMETER_NAMES = ['nonce', 'client_id', 'client_secret', 'redirect_uri', 'response_type', 'response_mode', 'scope', 'state', 'code_challenge', 'code_challenge_method', 'request', 'request_uri'];

private const array DEFAULT_OPTIONS = [
'issuer' => '',
'clientId' => '',
Expand Down Expand Up @@ -262,6 +267,7 @@ public function getAccessToken(string $serviceName, string $clientId, string $cl
* @param string $scope The authorization scope. Must be identifiers separated by space. "openid" will automatically be requested
* @param bool $requestRefreshToken If "offline_access" should be requested, so that an expired identity token can be refreshed
* @param array<string, string> $authorizationParameters Further parameters of the authorization request, like "prompt", "login_hint" or "ui_locales" (OpenID Connect Core 1.0, section 3.1.2.1), or ones of the identity provider like "screen_hint". Those which the client sets itself are rejected
* @throws InvalidArgumentException if the authorization parameters contain a reserved name or a value which is not a string
* @throws OAuthClientException
*/
public function startAuthorization(UriInterface $returnToUri, string $scope, Nonce $nonce, bool $requestRefreshToken = true, array $authorizationParameters = []): UriInterface
Expand Down Expand Up @@ -526,13 +532,11 @@ private function httpClient(): ClientInterface
return $this->httpClient ??= $this->httpClientFactory->create();
}

/**
* The nonce belongs to the login and is set here; the OAuth client rejects the other parameters it sets itself, like the state
*/
private static function validateAuthorizationParameters(array $authorizationParameters): void
{
if (array_key_exists('nonce', $authorizationParameters)) {
throw new InvalidArgumentException('OpenID Connect Client: The authorization parameters must not contain "nonce", because the client sets it itself.', 1791540882);
$reservedParameterNames = array_intersect(array_keys($authorizationParameters), self::RESERVED_AUTHORIZATION_PARAMETER_NAMES);
if ($reservedParameterNames !== []) {
throw new InvalidArgumentException(sprintf('OpenID Connect Client: The authorization parameters must not contain "%s", because the client sets them itself.', implode('", "', $reservedParameterNames)), 1791540882);
}
foreach ($authorizationParameters as $name => $value) {
if (!is_string($name) || $name === '' || !is_string($value)) {
Expand Down
1 change: 1 addition & 0 deletions Tests/Unit/OpenIdConnectClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public static function invalidAuthorizationParameters(): array
{
return [
'nonce, which belongs to the login' => [['nonce' => 'chosen-by-the-caller'], 1791540882],
'state, which the OAuth client sets' => [['state' => 'chosen-by-the-caller'], 1791540882],
'parameter without a name' => [['login'], 1791540883],
'parameter which is not a string' => [['max_age' => 300], 1791540883],
];
Expand Down
22 changes: 22 additions & 0 deletions Tests/Unit/OpenIdConnectEntryPointTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,28 @@ public function startAuthenticationRejectsInvalidOptions(array $options, int $ex
$entryPoint->startAuthentication(new ServerRequest('GET', 'https://www.example.com/'), new Response());
}

public static function reservedAuthorizationParameters(): array
{
return [
'nonce, which the client sets' => [['nonce' => 'chosen-in-the-configuration']],
'state, which the OAuth client sets' => [['state' => 'chosen-in-the-configuration']],
'redirect_uri, which the OAuth client sets' => [['prompt' => 'login', 'redirect_uri' => 'https://evil.example.com/']],
];
}

#[Test]
#[DataProvider('reservedAuthorizationParameters')]
public function startAuthenticationRejectsReservedAuthorizationParametersAsConfigurationError(array $authorizationParameters): void
{
$oAuthClient = $this->createMock(OAuthClient::class);
$oAuthClient->expects($this->never())->method('startAuthorization');
$entryPoint = $this->createEntryPoint($oAuthClient, ['serviceName' => 'test', 'authorizationParameters' => $authorizationParameters]);

$this->expectException(ConfigurationException::class);
$this->expectExceptionCode(1791799612);
$entryPoint->startAuthentication(new ServerRequest('GET', 'https://www.example.com/secure'), new Response());
}

#[Test]
public function startAuthenticationRedirectsToAuthorizationEndpoint(): void
{
Expand Down
Loading