diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 79156c9e..faf787a3 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -259,6 +259,12 @@ jobs: - name: Run RP backchannel run: | ./conformance-suite/scripts/run-test-plan.py "oidcc-backchannel-rp-initiated-logout-certification-test-plan[response_type=code][client_registration=static_client]" ./main/conformance-tests/conformance-back-channel-logout-ci.json + - name: Run form_post basic tests + run: | + ./conformance-suite/scripts/run-test-plan.py "oidcc-formpost-basic-certification-test-plan[server_metadata=discovery][client_registration=static_client]" ./main/conformance-tests/conformance-basic-ci.json + - name: Run form_post implicit tests + run: | + ./conformance-suite/scripts/run-test-plan.py "oidcc-formpost-implicit-certification-test-plan[server_metadata=discovery][client_registration=static_client]" ./main/conformance-tests/conformance-implicit-ci.json - name: Stop SSP working-directory: ./main run: | diff --git a/public/assets/js/src/formpost.js b/public/assets/js/src/formpost.js new file mode 100644 index 00000000..88950b52 --- /dev/null +++ b/public/assets/js/src/formpost.js @@ -0,0 +1 @@ +document.forms[0].submit(); \ No newline at end of file diff --git a/routing/services/services.yml b/routing/services/services.yml index 5b8f7c6c..ffcad634 100644 --- a/routing/services/services.yml +++ b/routing/services/services.yml @@ -67,6 +67,12 @@ services: SimpleSAML\Module\oidc\Server\ResponseTypes\TokenResponse: factory: ['@SimpleSAML\Module\oidc\Factories\TokenResponseFactory', 'build'] + SimpleSAML\Module\oidc\Server\ResponseModes\: + resource: '../../src/Server/ResponseModes/*' + + SimpleSAML\Configuration: + factory: ['SimpleSAML\Configuration', 'getInstance'] + oidc.key.private: class: League\OAuth2\Server\CryptKey factory: ['@SimpleSAML\Module\oidc\Factories\CryptKeyFactory', 'buildPrivateKey'] diff --git a/src/Controllers/Admin/ClientController.php b/src/Controllers/Admin/ClientController.php index 7f7ff7ef..db1db0f4 100644 --- a/src/Controllers/Admin/ClientController.php +++ b/src/Controllers/Admin/ClientController.php @@ -239,6 +239,7 @@ public function edit(Request $request): Response $clientData = $originalClient->toArray(); $clientData['allowed_origin'] = $clientAllowedOrigins; + $clientData['response_modes_allowed'] = $originalClient->getAllowedResponseModes(); // Handle extra metadata @@ -358,6 +359,9 @@ protected function buildClientEntityFromFormData( ClaimsEnum::IdTokenSignedResponseAlg->value => $idTokenSignedResponseAlg, ]; + $allowedResponseModes = is_array($data['response_modes_allowed']) ? $data['response_modes_allowed'] : []; + $extraMetadata['allowed_response_modes'] = $allowedResponseModes; + return $this->clientEntityFactory->fromData( $identifier, $secret, diff --git a/src/Entities/ClientEntity.php b/src/Entities/ClientEntity.php index a0ffb92b..aae1958a 100644 --- a/src/Entities/ClientEntity.php +++ b/src/Entities/ClientEntity.php @@ -388,4 +388,21 @@ public function getIdTokenSignedResponseAlg(): ?string return $idTokenSignedResponseAlg; } + + public function getAllowedResponseModes(): array + { + if (!is_array($this->extraMetadata)) { + // Default to allowing all response modes + return ['query', 'fragment', 'form_post']; + } + + $allowedResponseModes = $this->extraMetadata['allowed_response_modes'] ?? null; + + if (!is_array($allowedResponseModes)) { + // Default to allowing all response modes + return ['query', 'fragment', 'form_post']; + } + + return $allowedResponseModes; + } } diff --git a/src/Entities/Interfaces/ClientEntityInterface.php b/src/Entities/Interfaces/ClientEntityInterface.php index dea9ff66..47bc6f15 100644 --- a/src/Entities/Interfaces/ClientEntityInterface.php +++ b/src/Entities/Interfaces/ClientEntityInterface.php @@ -82,4 +82,5 @@ public function isGeneric(): bool; public function getExtraMetadata(): array; public function getIdTokenSignedResponseAlg(): ?string; + public function getAllowedResponseModes(): array; } diff --git a/src/Factories/Grant/ImplicitGrantFactory.php b/src/Factories/Grant/ImplicitGrantFactory.php index a22791d8..47d5ec6b 100644 --- a/src/Factories/Grant/ImplicitGrantFactory.php +++ b/src/Factories/Grant/ImplicitGrantFactory.php @@ -43,7 +43,6 @@ public function build(): ImplicitGrant $this->accessTokenRepository, $this->requestRulesManager, $this->requestParamsResolver, - '#', $this->accessTokenEntityFactory, ); } diff --git a/src/Factories/RequestRulesManagerFactory.php b/src/Factories/RequestRulesManagerFactory.php index 9b77e7c0..85fbf083 100644 --- a/src/Factories/RequestRulesManagerFactory.php +++ b/src/Factories/RequestRulesManagerFactory.php @@ -31,11 +31,15 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequestObjectRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequiredNonceRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequiredOpenIdScopeRule; +use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ResponseModeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ResponseTypeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ScopeOfflineAccessRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ScopeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\UiLocalesRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\FormPostResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\FragmentResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; use SimpleSAML\Module\oidc\Services\AuthenticationService; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\AuthenticatedOAuth2ClientResolver; @@ -72,6 +76,9 @@ public function __construct( private readonly AuthenticatedOAuth2ClientResolver $authenticatedOAuth2ClientResolver, private readonly ?FederationCache $federationCache = null, private readonly ?ProtocolCache $protocolCache = null, + private readonly QueryResponseMode $queryResponseMode, + private readonly FragmentResponseMode $fragmentResponseMode, + private readonly FormPostResponseMode $formPostResponseMode, ) { } @@ -107,6 +114,13 @@ private function getDefaultRules(): array ), new ClientRedirectUriRule($this->requestParamsResolver, $this->helpers, $this->moduleConfig), new RequestObjectRule($this->requestParamsResolver, $this->helpers, $this->jwksResolver), + new ResponseModeRule( + $this->requestParamsResolver, + $this->helpers, + $this->queryResponseMode, + $this->fragmentResponseMode, + $this->formPostResponseMode, + ), new PromptRule( $this->requestParamsResolver, $this->helpers, diff --git a/src/Forms/ClientForm.php b/src/Forms/ClientForm.php index 694a206e..a912c885 100644 --- a/src/Forms/ClientForm.php +++ b/src/Forms/ClientForm.php @@ -284,6 +284,12 @@ public function getValues(string|object|bool|null $returnType = null, ?array $co $values[ClaimsEnum::IdTokenSignedResponseAlg->value] = empty($idTokenSignedResponseAlg) ? null : $idTokenSignedResponseAlg; + $responseModesAllowed = is_array($values['response_modes_allowed']) ? $values['response_modes_allowed'] : []; + $values['response_modes_allowed'] = array_intersect( + $responseModesAllowed, + array_keys($this->getAllowedResponseModesValues()), + ); + return $values; } @@ -336,6 +342,9 @@ public function setDefaults(object|array $data, bool $erase = false): static $data['auth_source'] = null; } + $data['response_modes_allowed'] = is_array($data['response_modes_allowed']) ? + $data['response_modes_allowed'] : []; + parent::setDefaults($data, $erase); return $this; @@ -354,6 +363,7 @@ protected function buildForm(): void $this->onValidate[] = $this->validateBackChannelLogoutUri(...); $this->onValidate[] = $this->validateEntityIdentifier(...); $this->onValidate[] = $this->validateClientRegistrationTypes(...); + $this->onValidate[] = $this->validateResponseModes(...); $this->onValidate[] = $this->validateFederationJwks(...); $this->onValidate[] = $this->validateProtocolJwks(...); $this->onValidate[] = $this->validateJwksUri(...); @@ -423,6 +433,46 @@ protected function buildForm(): void ->setHtmlAttribute('class', 'full-width') ->setItems(['RS256'], false) ->setPrompt(Translate::noop('-')); + + $this->addMultiSelect( + 'response_modes_allowed', + Translate::noop('Allowed Response Modes'), + $this->getAllowedResponseModesValues(), + 3, + )->setHtmlAttribute('class', 'full-width') + ->setRequired(Translate::noop('At least one response mode is required.')); + } + + /** + * Validate provided response modes + * + * @throws \Exception + */ + public function validateResponseModes(Form $form): void + { + $values = $form->getValues(self::TYPE_ARRAY); + /** @var string[]|null $responseModes */ + $responseModes = $values['response_modes_allowed'] ?? null; + if (is_array($responseModes)) { + $allowed = array_keys($this->getAllowedResponseModesValues()); + foreach ($responseModes as $mode) { + if (!in_array($mode, $allowed, true)) { + $this->addError("Invalid value: $mode"); + } + } + } + } + + /** + * @return string[] map of value => label + */ + protected function getAllowedResponseModesValues(): array + { + return [ + 'query' => 'query', + 'fragment' => 'fragment', + 'form_post' => 'form_post', + ]; } /** diff --git a/src/Server/AuthorizationServer.php b/src/Server/AuthorizationServer.php index f0cc0585..56e1d7c4 100644 --- a/src/Server/AuthorizationServer.php +++ b/src/Server/AuthorizationServer.php @@ -22,9 +22,11 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\IdTokenHintRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\PostLogoutRedirectUriRule; +use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ResponseModeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\UiLocalesRule; use SimpleSAML\Module\oidc\Server\RequestTypes\LogoutRequest; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -85,13 +87,14 @@ public function validateAuthorizationRequest(ServerRequestInterface $request): O StateRule::class, ClientRule::class, ClientRedirectUriRule::class, + ResponseModeRule::class, ]; try { $resultBag = $this->requestRulesManager->check( $request, $rulesToExecute, - false, + new QueryResponseMode(), [HttpMethodsEnum::GET, HttpMethodsEnum::POST], ); } catch (OidcServerException $exception) { @@ -114,6 +117,8 @@ public function validateAuthorizationRequest(ServerRequestInterface $request): O $state = $resultBag->getOrFail(StateRule::class)->getValue(); /** @var string $redirectUri */ $redirectUri = $resultBag->getOrFail(ClientRedirectUriRule::class)->getValue(); + /** @var \SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface $responseMode */ + $responseMode = $resultBag->getOrFail(ResponseModeRule::class)->getValue(); foreach ($this->enabledGrantTypes as $grantType) { $this->loggerService?->debug( @@ -157,7 +162,7 @@ public function validateAuthorizationRequest(ServerRequestInterface $request): O 'request.', ['requestQueryParams' => $request->getQueryParams()], ); - throw OidcServerException::unsupportedResponseType($redirectUri, $state); + throw OidcServerException::unsupportedResponseType($redirectUri, $state, $responseMode); } /** @@ -177,7 +182,7 @@ public function validateLogoutRequest(ServerRequestInterface $request): LogoutRe $resultBag = $this->requestRulesManager->check( $request, $rulesToExecute, - false, + new QueryResponseMode(), [HttpMethodsEnum::GET, HttpMethodsEnum::POST], ); } catch (OidcServerException $exception) { diff --git a/src/Server/Exceptions/OidcServerException.php b/src/Server/Exceptions/OidcServerException.php index 5a9be60d..96edca56 100644 --- a/src/Server/Exceptions/OidcServerException.php +++ b/src/Server/Exceptions/OidcServerException.php @@ -6,10 +6,12 @@ use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseInterface; +use SimpleSAML\Module\oidc\Server\ResponseModes\FragmentResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\OpenID\Codebooks\ErrorsEnum; use Throwable; -use function http_build_query; use function json_encode; class OidcServerException extends OAuthServerException @@ -37,9 +39,9 @@ class OidcServerException extends OAuthServerException protected ?string $redirectUri; /** - * @var bool + * @var null|ResponseModeInterface */ - protected bool $useFragmentInHttpResponses = false; + protected ?ResponseModeInterface $responseMode = null; /** * Throw a new exception. @@ -62,12 +64,14 @@ public function __construct( ?string $redirectUri = null, ?Throwable $previous = null, ?string $state = null, + ?ResponseModeInterface $responseMode = new QueryResponseMode(), ) { parent::__construct($message, $code, $errorType, $httpStatusCode, $hint, $redirectUri, $previous); $this->httpStatusCode = $httpStatusCode; $this->errorType = $errorType; $this->redirectUri = $redirectUri; + $this->responseMode = $responseMode; if ($hint !== null) { $message .= ' (' . $hint . ')'; @@ -90,19 +94,19 @@ public function __construct( * * @param string|null $redirectUri * @param string|null $state - * @param bool $useFragment Use URI fragment to return error parameters - * @return self + * @param \SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface|null $responseMode + * @return OidcServerException */ public static function unsupportedResponseType( ?string $redirectUri = null, ?string $state = null, - bool $useFragment = false, + ?ResponseModeInterface $responseMode = null, ): OidcServerException { $errorMessage = 'The response type is not supported by the authorization server.'; $hint = 'Check that all required parameters have been provided'; $e = new self($errorMessage, 2, 'unsupported_response_type', 400, $hint, $redirectUri, null, $state); - $e->useFragmentInHttpResponses($useFragment); + $e->responseMode = $responseMode; return $e; } @@ -112,19 +116,36 @@ public static function unsupportedResponseType( * @param string $scope The bad scope * @param string|null $redirectUri An HTTP URI to redirect the user back to * @param string|null $state - * @param bool $useFragment Use URI fragment to return error parameters - * @return static + * @param \SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface|null $responseMode + * @return OidcServerException + * @psalm-suppress LessSpecificImplementedReturnType */ public static function invalidScope( $scope, $redirectUri = null, ?string $state = null, - bool $useFragment = false, + ?ResponseModeInterface $responseMode = null, ): OidcServerException { - // OAuthServerException correctly implements this error, however, it misses state parameter. - $e = parent::invalidScope($scope, $redirectUri); - $e->setState($state); - $e->useFragmentInHttpResponses($useFragment); + if (empty($scope)) { + $hint = 'Specify a scope in the request or set a default scope'; + } else { + $hint = sprintf( + 'Check the `%s` scope', + htmlspecialchars($scope, ENT_QUOTES, 'UTF-8', false), + ); + } + + $e = new self( + 'The requested scope is invalid, unknown, or malformed', + 5, + 'invalid_scope', + 400, + $hint, + $redirectUri, + null, + $state, + $responseMode, + ); return $e; } @@ -137,8 +158,9 @@ public static function invalidScope( * @param \Throwable|null $previous * @param string|null $redirectUri * @param string|null $state - * @param bool $useFragment Use URI fragment to return error parameters - * @return static + * @param \SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface|null $responseMode + * @return OidcServerException + * @psalm-suppress LessSpecificImplementedReturnType */ public static function invalidRequest( $parameter, @@ -146,13 +168,12 @@ public static function invalidRequest( ?Throwable $previous = null, ?string $redirectUri = null, ?string $state = null, - bool $useFragment = false, + ?ResponseModeInterface $responseMode = null, ): OidcServerException { - $e = parent::invalidRequest($parameter, $hint, $previous); - // OAuthServerException misses the ability to set redirectUri for invalid requests, as well as state. - $e->setRedirectUri($redirectUri); - $e->setState($state); - $e->useFragmentInHttpResponses($useFragment); + $errorMessage = 'The request is missing a required parameter, includes an invalid parameter value, ' . + 'includes a parameter more than once, or is otherwise malformed.'; + $hint = ($hint === null) ? \sprintf('Check the `%s` parameter', $parameter) : $hint; + $e = new self($errorMessage, 9, 'invalid_request', 400, $hint, $redirectUri, $previous, $state, $responseMode); return $e; } @@ -162,19 +183,28 @@ public static function invalidRequest( * @param string|null $redirectUri * @param \Throwable|null $previous * @param string|null $state - * @param bool $useFragment Use URI fragment to return error parameters - * @return static + * @param \SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface|null $responseMode + * @return OidcServerException + * @psalm-suppress LessSpecificImplementedReturnType */ public static function accessDenied( $hint = null, $redirectUri = null, ?Throwable $previous = null, ?string $state = null, - bool $useFragment = false, + ?ResponseModeInterface $responseMode = null, ): OidcServerException { - $e = parent::accessDenied($hint, $redirectUri, $previous); - $e->setState($state); - $e->useFragmentInHttpResponses($useFragment); + $e = new self( + 'The resource owner or authorization server denied the request.', + 9, + 'access_denied', + 401, + $hint, + $redirectUri, + $previous, + $state, + $responseMode, + ); return $e; } @@ -186,21 +216,20 @@ public static function accessDenied( * @param string|null $redirectUri * @param \Throwable|null $previous * @param string|null $state - * @param bool $useFragment Use URI fragment to return error parameters + * @param \SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface|null $responseMode * - * @return self + * @return OidcServerException */ public static function loginRequired( ?string $hint = null, ?string $redirectUri = null, ?Throwable $previous = null, ?string $state = null, - bool $useFragment = false, + ?ResponseModeInterface $responseMode = null, ): OidcServerException { $errorMessage = "End-User is not already authenticated."; - $e = new self($errorMessage, 6, 'login_required', 400, $hint, $redirectUri, $previous, $state); - $e->useFragmentInHttpResponses($useFragment); + $e = new self($errorMessage, 6, 'login_required', 400, $hint, $redirectUri, $previous, $state, $responseMode); return $e; } @@ -212,21 +241,30 @@ public static function loginRequired( * @param string|null $redirectUri * @param \Throwable|null $previous * @param string|null $state - * @param bool $useFragment Use URI fragment to return error parameters + * @param \SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface|null $responseMode * - * @return self + * @return OidcServerException */ public static function requestNotSupported( ?string $hint = null, ?string $redirectUri = null, ?Throwable $previous = null, ?string $state = null, - bool $useFragment = false, + ?ResponseModeInterface $responseMode = null, ): OidcServerException { $errorMessage = "Request object not supported."; - $e = new self($errorMessage, 7, 'request_not_supported', 400, $hint, $redirectUri, $previous, $state); - $e->useFragmentInHttpResponses($useFragment); + $e = new self( + $errorMessage, + 7, + 'request_not_supported', + 400, + $hint, + $redirectUri, + $previous, + $state, + $responseMode, + ); return $e; } @@ -237,7 +275,7 @@ public static function requestNotSupported( * @param string|null $hint * @param \Throwable|null $previous * - * @return self + * @return OidcServerException * @psalm-suppress LessSpecificImplementedReturnType */ public static function invalidRefreshToken($hint = null, ?Throwable $previous = null): OidcServerException @@ -250,7 +288,7 @@ public static function invalidTrustChain( ?string $redirectUri = null, ?Throwable $previous = null, ?string $state = null, - bool $useFragment = false, + ?ResponseModeInterface $responseMode = null, ): OidcServerException { $errorMessage = 'Trust chain validation failed.'; @@ -263,8 +301,8 @@ public static function invalidTrustChain( $redirectUri, $previous, $state, + $responseMode, ); - $e->useFragmentInHttpResponses($useFragment); return $e; } @@ -361,8 +399,7 @@ public function setState(?string $state = null): void * Generate an HTTP response. * * @param \Psr\Http\Message\ResponseInterface $response - * @param bool $useFragment True if errors should be in the URI fragment instead of query string. Note - * that this can also be set using useFragmentInHttpResponses(). + * @param bool $useFragment * @param int $jsonOptions options passed to json_encode * * @return \Psr\Http\Message\ResponseInterface @@ -377,16 +414,13 @@ public function generateHttpResponse( $payload = $this->getPayload(); - if ($this->redirectUri !== null) { - $paramSeparator = '?'; - - if ($this->useFragmentInHttpResponses || $useFragment) { - $paramSeparator = '#'; - } - - $this->redirectUri .= (!str_contains($this->redirectUri, $paramSeparator)) ? $paramSeparator : '&'; + if ($this->responseMode === null) { + // Fallback to useFragment if responseMode is not set + $this->responseMode = $useFragment ? new FragmentResponseMode() : new QueryResponseMode(); + } - return $response->withStatus(302)->withHeader('Location', $this->redirectUri . http_build_query($payload)); + if ($this->redirectUri !== null) { + return $this->responseMode->buildResponse($this->redirectUri, $payload)->generateHttpResponse($response); } foreach ($headers as $header => $content) { @@ -399,14 +433,4 @@ public function generateHttpResponse( return $response->withStatus($this->getHttpStatusCode()); } - - /** - * Use URI fragment to return parameters in HTTP redirection error responses - * - * @param bool $useFragment True if fragment should be used, false otherwise - */ - public function useFragmentInHttpResponses(bool $useFragment): void - { - $this->useFragmentInHttpResponses = $useFragment; - } } diff --git a/src/Server/Grants/AuthCodeGrant.php b/src/Server/Grants/AuthCodeGrant.php index dbbbce0b..d6fc76b8 100644 --- a/src/Server/Grants/AuthCodeGrant.php +++ b/src/Server/Grants/AuthCodeGrant.php @@ -17,7 +17,7 @@ use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface as OAuth2AuthCodeRepositoryInterface; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequest as OAuth2AuthorizationRequest; -use League\OAuth2\Server\ResponseTypes\RedirectResponse; +use League\OAuth2\Server\ResponseTypes\AbstractResponseType; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; use Psr\Http\Message\ServerRequestInterface; @@ -59,10 +59,12 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequestedClaimsRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequestObjectRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequiredOpenIdScopeRule; +use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ResponseModeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ScopeOfflineAccessRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ScopeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule; use SimpleSAML\Module\oidc\Server\RequestTypes\AuthorizationRequest; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; use SimpleSAML\Module\oidc\Server\ResponseTypes\Interfaces\AcrResponseTypeInterface; use SimpleSAML\Module\oidc\Server\ResponseTypes\Interfaces\AuthTimeResponseTypeInterface; use SimpleSAML\Module\oidc\Server\ResponseTypes\Interfaces\NonceResponseTypeInterface; @@ -255,7 +257,7 @@ public function completeAuthorizationRequest( */ public function completeOidcAuthorizationRequest( AuthorizationRequest $authorizationRequest, - ): RedirectResponse { + ): AbstractResponseType { $user = $authorizationRequest->getUser(); if ($user instanceof UserEntity === false) { throw new LogicException('An instance of UserEntity should be set on the ' . @@ -272,6 +274,7 @@ public function completeOidcAuthorizationRequest( $finalRedirectUri, null, $authorizationRequest->getState(), + $authorizationRequest->getResponseMode(), ); } @@ -304,15 +307,13 @@ public function completeOidcAuthorizationRequest( $jsonPayload = json_encode($payload, JSON_THROW_ON_ERROR); - $response = new RedirectResponse(); - $response->setRedirectUri( - $this->makeRedirectUri( - $finalRedirectUri, - [ - 'code' => $this->encrypt($jsonPayload), - 'state' => $authorizationRequest->getState(), - ], - ), + $responseMode = $authorizationRequest->getResponseMode() ?? new QueryResponseMode(); + $response = $responseMode->buildResponse( + $finalRedirectUri, + [ + 'code' => $this->encrypt($jsonPayload), + 'state' => $authorizationRequest->getState(), + ], ); return $response; @@ -503,7 +504,9 @@ public function respondToAccessTokenRequest( $resultBag = $this->requestRulesManager->check( $request, $rulesToExecute, - false, + // TODO: Response mode is not relevant for token request, as there is + // no redirection, but we need to provide something to execute rules. + new QueryResponseMode(), $this->allowedTokenHttpMethods, ); @@ -770,6 +773,8 @@ public function validateAuthorizationRequestWithRequestRules( $state = $resultBag->getOrFail(StateRule::class)->getValue(); /** @var \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface $client */ $client = $resultBag->getOrFail(ClientRule::class)->getValue(); + /** @var \SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface $responseMode */ + $responseMode = $resultBag->getOrFail(ResponseModeRule::class)->getValue(); $this->loggerService->debug('AuthCodeGrant: Resolved data:', [ 'redirectUri' => $redirectUri, @@ -784,7 +789,7 @@ public function validateAuthorizationRequestWithRequestRules( $resultBag = $this->requestRulesManager->check( $request, $rulesToExecute, - false, + $responseMode, $this->allowedAuthorizationHttpMethods, ); @@ -902,6 +907,14 @@ public function validateAuthorizationRequestWithRequestRules( ); $authorizationRequest->setAuthorizationDetails($authorizationDetails); + /** @var \SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface $responseMode */ + $responseMode = $resultBag->getOrFail(ResponseModeRule::class)->getValue(); + $this->loggerService->debug( + 'AuthCodeGrant: Response mode: ', + ['responseMode' => $responseMode], + ); + $authorizationRequest->setResponseMode($responseMode); + // TODO This is a band-aid fix for having credential claims in the userinfo endpoint when // only VCI authorizationDetails are supplied. This requires configuring a matching OIDC scope // that has all the credential type claims as well. diff --git a/src/Server/Grants/ImplicitGrant.php b/src/Server/Grants/ImplicitGrant.php index 2677ca98..75fcf1c8 100644 --- a/src/Server/Grants/ImplicitGrant.php +++ b/src/Server/Grants/ImplicitGrant.php @@ -7,7 +7,6 @@ use DateInterval; use League\OAuth2\Server\Grant\ImplicitGrant as OAuth2ImplicitGrant; use League\OAuth2\Server\RequestTypes\AuthorizationRequest as OAuth2AuthorizationRequest; -use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; use Psr\Http\Message\ServerRequestInterface; @@ -32,10 +31,12 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequestObjectRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequiredNonceRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequiredOpenIdScopeRule; +use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ResponseModeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ResponseTypeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ScopeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule; use SimpleSAML\Module\oidc\Server\RequestTypes\AuthorizationRequest; +use SimpleSAML\Module\oidc\Server\ResponseModes\FragmentResponseMode; use SimpleSAML\Module\oidc\Services\IdTokenBuilder; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -62,10 +63,9 @@ public function __construct( AccessTokenRepositoryInterface $accessTokenRepository, protected RequestRulesManager $requestRulesManager, protected RequestParamsResolver $requestParamsResolver, - protected string $queryDelimiter, AccessTokenEntityFactory $accessTokenEntityFactory, ) { - parent::__construct($accessTokenTTL, $queryDelimiter); + parent::__construct($accessTokenTTL); $this->accessTokenRepository = $accessTokenRepository; $this->accessTokenEntityFactory = $accessTokenEntityFactory; @@ -143,6 +143,8 @@ public function validateAuthorizationRequestWithRequestRules( $state = $resultBag->getOrFail(StateRule::class)->getValue(); /** @var \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface $client */ $client = $resultBag->getOrFail(ClientRule::class)->getValue(); + /** @var \SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface $responseMode */ + $responseMode = $resultBag->getOrFail(ResponseModeRule::class)->getValue(); // Some rules need certain things available in order to work properly... $this->requestRulesManager->setData('default_scope', $this->defaultScope); @@ -151,7 +153,7 @@ public function validateAuthorizationRequestWithRequestRules( $resultBag = $this->requestRulesManager->check( $request, $rulesToExecute, - $this->shouldUseFragment(), + $responseMode, $this->allowedAuthorizationHttpMethods, ); @@ -195,6 +197,10 @@ public function validateAuthorizationRequestWithRequestRules( $acrValues = $resultBag->getOrFail(AcrValuesRule::class)->getValue(); $authorizationRequest->setRequestedAcrValues($acrValues); + /** @var \SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface $responseMode */ + $responseMode = $resultBag->getOrFail(ResponseModeRule::class)->getValue(); + $authorizationRequest->setResponseMode($responseMode); + return $authorizationRequest; } @@ -220,7 +226,7 @@ private function completeOidcAuthorizationRequest(AuthorizationRequest $authoriz $redirectUrl, null, $authorizationRequest->getState(), - $this->shouldUseFragment(), + $authorizationRequest->getResponseMode(), ); } @@ -274,14 +280,10 @@ private function completeOidcAuthorizationRequest(AuthorizationRequest $authoriz $responseParams['id_token'] = $idToken->getToken(); - $response = new RedirectResponse(); - - $response->setRedirectUri( - $this->makeRedirectUri( - $redirectUrl, - $responseParams, - $this->queryDelimiter, - ), + $responseMode = $authorizationRequest->getResponseMode() ?? new FragmentResponseMode(); + $response = $responseMode->buildResponse( + $redirectUrl, + $responseParams, ); return $response; @@ -301,14 +303,4 @@ private function getRedirectUrl(AuthorizationRequest $authorizationRequest): str return $redirectUris; } - - /** - * Check if fragment should be used for params transportation in HTTP responses - * - * @return bool - */ - protected function shouldUseFragment(): bool - { - return $this->queryDelimiter === '#'; - } } diff --git a/src/Server/Grants/PreAuthCodeGrant.php b/src/Server/Grants/PreAuthCodeGrant.php index 5c7e0a24..4b13ef5c 100644 --- a/src/Server/Grants/PreAuthCodeGrant.php +++ b/src/Server/Grants/PreAuthCodeGrant.php @@ -22,6 +22,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\AuthorizationDetailsRule; use SimpleSAML\Module\oidc\Server\RequestTypes\AuthorizationRequest; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; use SimpleSAML\OpenID\Codebooks\GrantTypesEnum; use SimpleSAML\OpenID\Codebooks\ParamsEnum; @@ -174,7 +175,9 @@ public function respondToAccessTokenRequest( $resultBag = $this->requestRulesManager->check( $request, [AuthorizationDetailsRule::class], - false, + // TODO: Response mode is not relevant for token request, as there is + // no redirection, but we need to provide something to execute rules. + new QueryResponseMode(), $this->allowedTokenHttpMethods, ); diff --git a/src/Server/RequestRules/Interfaces/RequestRuleInterface.php b/src/Server/RequestRules/Interfaces/RequestRuleInterface.php index 6f719a31..65a39950 100644 --- a/src/Server/RequestRules/Interfaces/RequestRuleInterface.php +++ b/src/Server/RequestRules/Interfaces/RequestRuleInterface.php @@ -5,6 +5,8 @@ namespace SimpleSAML\Module\oidc\Server\RequestRules\Interfaces; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -18,13 +20,15 @@ public function getKey(): string; /** * Check specific rule. + * * @param \SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface $currentResultBag * ResultBag with all results of the checks performed to current check * @param array $data Data which will be available during check. - * @param bool $useFragmentInHttpErrorResponses Indicate that in case of HTTP error responses, params should be - * returned in URI fragment instead of query. + * @param ResponseModeInterface $responseMode Response mode to use for error responses * @param HttpMethodsEnum[] $allowedServerRequestMethods Indicate allowed HTTP methods used for request + * * @return \SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface|null Result of the specific check + * * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException If check fails */ public function checkRule( @@ -32,7 +36,7 @@ public function checkRule( ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface; } diff --git a/src/Server/RequestRules/RequestRulesManager.php b/src/Server/RequestRules/RequestRulesManager.php index c08c1a67..9adfad34 100644 --- a/src/Server/RequestRules/RequestRulesManager.php +++ b/src/Server/RequestRules/RequestRulesManager.php @@ -9,6 +9,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\RequestRuleInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -45,15 +47,16 @@ public function add(RequestRuleInterface $rule): void /** * @param class-string[] $ruleKeysToExecute - * @param bool $useFragmentInHttpErrorResponses Indicate that in case of HTTP error responses, params should be - * returned in URI fragment instead of query. + * @param ResponseModeInterface $responseMode Response mode which will be + * used in rules execution, as some rules might need to adjust their + * behaviour based on response mode used in request. * @param HttpMethodsEnum[] $allowedServerRequestMethods Indicate allowed HTTP methods used for request * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function check( ServerRequestInterface $request, array $ruleKeysToExecute, - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ResultBagInterface { foreach ($ruleKeysToExecute as $ruleKey) { @@ -66,7 +69,7 @@ public function check( $this->resultBag, $this->loggerService, $this->data, - $useFragmentInHttpErrorResponses, + $responseMode, $allowedServerRequestMethods, ); @@ -97,7 +100,7 @@ public function predefineResultBag(ResultBagInterface $resultBag): void /** * Set data which will be available in each check, using key value pair */ - public function setData(string $key, mixed $value): void + public function setData(string $key, string $value): void { $this->data[$key] = $value; } diff --git a/src/Server/RequestRules/Rules/AcrValuesRule.php b/src/Server/RequestRules/Rules/AcrValuesRule.php index 7d02cf1f..d971303b 100644 --- a/src/Server/RequestRules/Rules/AcrValuesRule.php +++ b/src/Server/RequestRules/Rules/AcrValuesRule.php @@ -8,6 +8,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; use SimpleSAML\OpenID\Codebooks\ParamsEnum; @@ -16,13 +18,16 @@ class AcrValuesRule extends AbstractRule { /** * @inheritDoc + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('AcrValuesRule::checkRule'); diff --git a/src/Server/RequestRules/Rules/AddClaimsToIdTokenRule.php b/src/Server/RequestRules/Rules/AddClaimsToIdTokenRule.php index a6aae64f..e0ca70f1 100644 --- a/src/Server/RequestRules/Rules/AddClaimsToIdTokenRule.php +++ b/src/Server/RequestRules/Rules/AddClaimsToIdTokenRule.php @@ -8,6 +8,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -15,14 +17,17 @@ class AddClaimsToIdTokenRule extends AbstractRule { /** * @inheritDoc + * * @throws \Throwable + * + * @param ResponseModeInterface $responseMode */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { /** @var string $responseType */ diff --git a/src/Server/RequestRules/Rules/AuthorizationDetailsRule.php b/src/Server/RequestRules/Rules/AuthorizationDetailsRule.php index 4b4ff89f..4af36369 100644 --- a/src/Server/RequestRules/Rules/AuthorizationDetailsRule.php +++ b/src/Server/RequestRules/Rules/AuthorizationDetailsRule.php @@ -11,6 +11,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -28,13 +30,16 @@ public function __construct( /** * @inheritDoc + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('AuthorizationDetailsRule::checkRule.'); diff --git a/src/Server/RequestRules/Rules/ClientAuthenticationRule.php b/src/Server/RequestRules/Rules/ClientAuthenticationRule.php index 7a062bd6..f6e7a92f 100644 --- a/src/Server/RequestRules/Rules/ClientAuthenticationRule.php +++ b/src/Server/RequestRules/Rules/ClientAuthenticationRule.php @@ -10,6 +10,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\AuthenticatedOAuth2ClientResolver; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -28,13 +30,15 @@ public function __construct( /** * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException * @throws \Throwable + * + * @param ResponseModeInterface $responseMode */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { diff --git a/src/Server/RequestRules/Rules/ClientIdRule.php b/src/Server/RequestRules/Rules/ClientIdRule.php index b329c179..0a2e80bc 100644 --- a/src/Server/RequestRules/Rules/ClientIdRule.php +++ b/src/Server/RequestRules/Rules/ClientIdRule.php @@ -9,6 +9,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; use SimpleSAML\OpenID\Codebooks\ParamsEnum; @@ -20,6 +22,7 @@ class ClientIdRule extends AbstractRule { /** * @inheritDoc + * * @throws \JsonException * @throws \League\OAuth2\Server\Exception\OAuthServerException * @throws \Psr\SimpleCache\InvalidArgumentException @@ -32,13 +35,16 @@ class ClientIdRule extends AbstractRule * @throws \SimpleSAML\OpenID\Exceptions\RequestObjectException * @throws \SimpleSAML\OpenID\Exceptions\TrustChainException * @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('ClientIdRule::checkRule'); diff --git a/src/Server/RequestRules/Rules/ClientRedirectUriRule.php b/src/Server/RequestRules/Rules/ClientRedirectUriRule.php index 3c00c763..d4aa11f1 100644 --- a/src/Server/RequestRules/Rules/ClientRedirectUriRule.php +++ b/src/Server/RequestRules/Rules/ClientRedirectUriRule.php @@ -13,6 +13,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -30,14 +32,18 @@ public function __construct( /** * @inheritDoc + * * @throws \Throwable + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('RedirectUriRule::checkRule'); diff --git a/src/Server/RequestRules/Rules/ClientRule.php b/src/Server/RequestRules/Rules/ClientRule.php index ac6a1160..242c60d0 100644 --- a/src/Server/RequestRules/Rules/ClientRule.php +++ b/src/Server/RequestRules/Rules/ClientRule.php @@ -17,6 +17,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\FederationCache; use SimpleSAML\Module\oidc\Utils\FederationParticipationValidator; @@ -53,6 +55,7 @@ public function __construct( /** * @inheritDoc + * * @throws \JsonException * @throws \League\OAuth2\Server\Exception\OAuthServerException * @throws \Psr\SimpleCache\InvalidArgumentException @@ -65,13 +68,16 @@ public function __construct( * @throws \SimpleSAML\OpenID\Exceptions\RequestObjectException * @throws \SimpleSAML\OpenID\Exceptions\TrustChainException * @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('ClientRule::checkRule.'); diff --git a/src/Server/RequestRules/Rules/CodeChallengeMethodRule.php b/src/Server/RequestRules/Rules/CodeChallengeMethodRule.php index 33d5f70a..f0e46c42 100644 --- a/src/Server/RequestRules/Rules/CodeChallengeMethodRule.php +++ b/src/Server/RequestRules/Rules/CodeChallengeMethodRule.php @@ -11,6 +11,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -29,13 +31,16 @@ public function __construct( /** * @throws \Throwable * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('CodeChallengeMethodRule::checkRule'); @@ -62,7 +67,7 @@ public function checkRule( null, $redirectUri, $state, - $useFragmentInHttpErrorResponses, + $responseMode, ); } diff --git a/src/Server/RequestRules/Rules/CodeChallengeRule.php b/src/Server/RequestRules/Rules/CodeChallengeRule.php index feb37160..b55d79d6 100644 --- a/src/Server/RequestRules/Rules/CodeChallengeRule.php +++ b/src/Server/RequestRules/Rules/CodeChallengeRule.php @@ -9,6 +9,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; use SimpleSAML\OpenID\Codebooks\ParamsEnum; @@ -17,14 +19,18 @@ class CodeChallengeRule extends AbstractRule { /** * @inheritDoc + * * @throws \Throwable + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('CodeChallengeRule::checkRule'); @@ -50,7 +56,7 @@ public function checkRule( null, $redirectUri, $state, - $useFragmentInHttpErrorResponses, + $responseMode, ); } @@ -66,7 +72,7 @@ public function checkRule( null, $redirectUri, $state, - $useFragmentInHttpErrorResponses, + $responseMode, ); } diff --git a/src/Server/RequestRules/Rules/CodeVerifierRule.php b/src/Server/RequestRules/Rules/CodeVerifierRule.php index 8b3767eb..d4bcc04a 100644 --- a/src/Server/RequestRules/Rules/CodeVerifierRule.php +++ b/src/Server/RequestRules/Rules/CodeVerifierRule.php @@ -9,6 +9,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; use SimpleSAML\OpenID\Codebooks\ParamsEnum; @@ -17,13 +19,16 @@ class CodeVerifierRule extends AbstractRule { /** * @inheritDoc + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { /** @var \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface $client */ diff --git a/src/Server/RequestRules/Rules/IdTokenHintRule.php b/src/Server/RequestRules/Rules/IdTokenHintRule.php index 8feccbf2..dc481b22 100644 --- a/src/Server/RequestRules/Rules/IdTokenHintRule.php +++ b/src/Server/RequestRules/Rules/IdTokenHintRule.php @@ -11,6 +11,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -32,14 +34,18 @@ public function __construct( /** * @inheritDoc + * * @throws \Throwable + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { /** @var string|null $state */ diff --git a/src/Server/RequestRules/Rules/IssuerStateRule.php b/src/Server/RequestRules/Rules/IssuerStateRule.php index 7ba9bf2d..962e94e9 100644 --- a/src/Server/RequestRules/Rules/IssuerStateRule.php +++ b/src/Server/RequestRules/Rules/IssuerStateRule.php @@ -8,6 +8,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; use SimpleSAML\OpenID\Codebooks\ParamsEnum; @@ -16,13 +18,16 @@ class IssuerStateRule extends AbstractRule { /** * @inheritDoc + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $issuerState = $this->requestParamsResolver->getAsStringBasedOnAllowedMethods( diff --git a/src/Server/RequestRules/Rules/MaxAgeRule.php b/src/Server/RequestRules/Rules/MaxAgeRule.php index e5731a7f..1b02c085 100644 --- a/src/Server/RequestRules/Rules/MaxAgeRule.php +++ b/src/Server/RequestRules/Rules/MaxAgeRule.php @@ -12,6 +12,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\AuthenticationService; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -37,13 +39,16 @@ public function __construct( * @throws \SimpleSAML\Error\NotFound * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException * @throws \Throwable + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('MaxAgeRule::checkRule'); @@ -80,7 +85,7 @@ public function checkRule( null, $redirectUri, $state, - $useFragmentInHttpErrorResponses, + $responseMode, ); } diff --git a/src/Server/RequestRules/Rules/PostLogoutRedirectUriRule.php b/src/Server/RequestRules/Rules/PostLogoutRedirectUriRule.php index 258d8186..d0d4e8df 100644 --- a/src/Server/RequestRules/Rules/PostLogoutRedirectUriRule.php +++ b/src/Server/RequestRules/Rules/PostLogoutRedirectUriRule.php @@ -11,6 +11,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -28,14 +30,18 @@ public function __construct( /** * @inheritDoc + * * @throws \Throwable + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { /** @var string|null $state */ diff --git a/src/Server/RequestRules/Rules/PromptRule.php b/src/Server/RequestRules/Rules/PromptRule.php index 8a994f45..9b708ab3 100644 --- a/src/Server/RequestRules/Rules/PromptRule.php +++ b/src/Server/RequestRules/Rules/PromptRule.php @@ -12,6 +12,8 @@ use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\AuthenticationService; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -38,13 +40,16 @@ public function __construct( * @throws \SimpleSAML\Error\NotFound * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException * @throws \Throwable + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('PromptRule::checkRule'); @@ -78,7 +83,7 @@ public function checkRule( $redirectUri, null, $state, - $useFragmentInHttpErrorResponses, + $responseMode, ); } diff --git a/src/Server/RequestRules/Rules/RequestObjectRule.php b/src/Server/RequestRules/Rules/RequestObjectRule.php index 81c05812..e4eab6e3 100644 --- a/src/Server/RequestRules/Rules/RequestObjectRule.php +++ b/src/Server/RequestRules/Rules/RequestObjectRule.php @@ -10,6 +10,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\JwksResolver; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -29,13 +31,16 @@ public function __construct( /** * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException * @throws \Throwable + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('RequestObjectRule::checkRule'); @@ -80,7 +85,7 @@ public function checkRule( $redirectUri, null, $stateValue, - $useFragmentInHttpErrorResponses, + $responseMode, ); try { @@ -91,7 +96,7 @@ public function checkRule( $redirectUri, null, $stateValue, - $useFragmentInHttpErrorResponses, + $responseMode, ); } diff --git a/src/Server/RequestRules/Rules/RequestedClaimsRule.php b/src/Server/RequestRules/Rules/RequestedClaimsRule.php index 3a7d60b3..18388d64 100644 --- a/src/Server/RequestRules/Rules/RequestedClaimsRule.php +++ b/src/Server/RequestRules/Rules/RequestedClaimsRule.php @@ -9,6 +9,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -28,13 +30,16 @@ public function __construct( /** * @throws \Throwable + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('RequestedClaimsRule::checkRule'); diff --git a/src/Server/RequestRules/Rules/RequiredNonceRule.php b/src/Server/RequestRules/Rules/RequiredNonceRule.php index 16034d17..1839ca9c 100644 --- a/src/Server/RequestRules/Rules/RequiredNonceRule.php +++ b/src/Server/RequestRules/Rules/RequiredNonceRule.php @@ -9,6 +9,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; use SimpleSAML\OpenID\Codebooks\ParamsEnum; @@ -17,14 +19,18 @@ class RequiredNonceRule extends AbstractRule { /** * @inheritDoc + * * @throws \Throwable + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { /** @var string $redirectUri */ @@ -45,7 +51,7 @@ public function checkRule( null, $redirectUri, $state, - $useFragmentInHttpErrorResponses, + $responseMode, ); } diff --git a/src/Server/RequestRules/Rules/RequiredOpenIdScopeRule.php b/src/Server/RequestRules/Rules/RequiredOpenIdScopeRule.php index 5fa0dc86..53b62375 100644 --- a/src/Server/RequestRules/Rules/RequiredOpenIdScopeRule.php +++ b/src/Server/RequestRules/Rules/RequiredOpenIdScopeRule.php @@ -9,6 +9,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -16,14 +18,18 @@ class RequiredOpenIdScopeRule extends AbstractRule { /** * @inheritDoc + * * @throws \Throwable + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('RequiredOpenIdScopeRule::checkRule.'); @@ -53,7 +59,7 @@ public function checkRule( null, $redirectUri, $state, - $useFragmentInHttpErrorResponses, + $responseMode, ); } } catch (\Throwable $e) { diff --git a/src/Server/RequestRules/Rules/ResponseModeRule.php b/src/Server/RequestRules/Rules/ResponseModeRule.php new file mode 100644 index 00000000..34c7a485 --- /dev/null +++ b/src/Server/RequestRules/Rules/ResponseModeRule.php @@ -0,0 +1,116 @@ +requestParamsResolver->getAllBasedOnAllowedMethods( + $request, + $allowedServerRequestMethods, + ); + + // response_mode requires client_id to be present + if ( + !isset($requestParams[ParamsEnum::ClientId->value]) + ) { + throw OidcServerException::invalidRequest('Missing client_id'); + } + + $reponseModeValue = isset($requestParams[ParamsEnum::ResponseMode->value]) ? + (string)$requestParams[ParamsEnum::ResponseMode->value] : null; + $loggerService->debug('ResponseModeRule: response_mode requestParams value: ' . ($reponseModeValue ?? 'null')); + + + // if response_mode is not set, we set the default + // default to 'code' if not set. Error will be thrown by ResponseTypeRule. + $responseType = isset($requestParams[ParamsEnum::ResponseType->value]) ? + (string)$requestParams[ParamsEnum::ResponseType->value] : 'code'; + if (!$reponseModeValue) { + $reponseModeValue = str_contains($responseType, 'token') ? 'fragment' : 'query'; + } + + // Verify if response_mode is one of 'query', 'fragment', 'form_post' + if ( + !in_array( + $reponseModeValue, + ['query', 'fragment', 'form_post'], + true, + ) + ) { + throw OidcServerException::invalidRequest('Invalid response_mode'); + } + + // Validate whether response_mode is allowed by client configuration + /** @var \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface $client */ + $client = $currentResultBag->getOrFail(ClientRule::class)->getValue(); + $currentResultBag->getOrFail(ClientRedirectUriRule::class)->getValue(); + $currentResultBag->getOrFail(StateRule::class)->getValue(); + + $allowedResponseModes = $client->getAllowedResponseModes(); + if (!in_array($reponseModeValue, $allowedResponseModes, true)) { + throw OidcServerException::invalidRequest( + 'response_mode', + 'response_mode "' . $reponseModeValue . '" is not allowed for this client', + ); + } + + // Resolve ResponseModeStrategy + switch ($reponseModeValue) { + case 'query': + $responseMode = $this->queryResponseMode; + break; + case 'fragment': + $responseMode = $this->fragmentResponseMode; + break; + case 'form_post': + $responseMode = $this->formPostResponseMode; + break; + default: + throw OidcServerException::invalidRequest('Unsupported response_mode. How did we get here?'); + } + + return new Result($this->getKey(), $responseMode); + } +} diff --git a/src/Server/RequestRules/Rules/ResponseTypeRule.php b/src/Server/RequestRules/Rules/ResponseTypeRule.php index 30acb5ad..1b71a93d 100644 --- a/src/Server/RequestRules/Rules/ResponseTypeRule.php +++ b/src/Server/RequestRules/Rules/ResponseTypeRule.php @@ -9,6 +9,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; use SimpleSAML\OpenID\Codebooks\ParamsEnum; @@ -17,13 +19,16 @@ class ResponseTypeRule extends AbstractRule { /** * @inheritDoc + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $requestParams = $this->requestParamsResolver->getAllBasedOnAllowedMethods( diff --git a/src/Server/RequestRules/Rules/ScopeOfflineAccessRule.php b/src/Server/RequestRules/Rules/ScopeOfflineAccessRule.php index ee4188b2..fd41cdfe 100644 --- a/src/Server/RequestRules/Rules/ScopeOfflineAccessRule.php +++ b/src/Server/RequestRules/Rules/ScopeOfflineAccessRule.php @@ -9,6 +9,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -16,14 +18,17 @@ class ScopeOfflineAccessRule extends AbstractRule { /** * @inheritDoc + * * @throws \Throwable + * + * @param ResponseModeInterface $responseMode */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('ScopeOfflineAccessRule::checkRule'); @@ -50,7 +55,7 @@ public function checkRule( null, $redirectUri, $state, - $useFragmentInHttpErrorResponses, + $responseMode, ); } diff --git a/src/Server/RequestRules/Rules/ScopeRule.php b/src/Server/RequestRules/Rules/ScopeRule.php index bc6b753c..2587aaee 100644 --- a/src/Server/RequestRules/Rules/ScopeRule.php +++ b/src/Server/RequestRules/Rules/ScopeRule.php @@ -12,6 +12,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -29,14 +31,18 @@ public function __construct( /** * @inheritDoc + * * @throws \Throwable + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('ScopeRule::checkRule.'); @@ -69,7 +75,7 @@ public function checkRule( if ($scope instanceof ScopeEntityInterface === false) { $loggerService->error('ScopeRule: Invalid scope: ' . $scopeItem); - throw OidcServerException::invalidScope($scopeItem, $redirectUri, $state); + throw OidcServerException::invalidScope($scopeItem, $redirectUri, $state, $responseMode); } $loggerService->debug('ScopeRule: Valid scope: ' . $scopeItem); $validScopes[] = $scope; diff --git a/src/Server/RequestRules/Rules/StateRule.php b/src/Server/RequestRules/Rules/StateRule.php index d60d31d7..8f337281 100644 --- a/src/Server/RequestRules/Rules/StateRule.php +++ b/src/Server/RequestRules/Rules/StateRule.php @@ -8,6 +8,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; use SimpleSAML\OpenID\Codebooks\ParamsEnum; @@ -16,13 +18,16 @@ class StateRule extends AbstractRule { /** * @inheritDoc + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { $loggerService->debug('StateRule::checkRule'); diff --git a/src/Server/RequestRules/Rules/UiLocalesRule.php b/src/Server/RequestRules/Rules/UiLocalesRule.php index 3bbbd3c3..4561da7b 100644 --- a/src/Server/RequestRules/Rules/UiLocalesRule.php +++ b/src/Server/RequestRules/Rules/UiLocalesRule.php @@ -8,6 +8,8 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; use SimpleSAML\OpenID\Codebooks\ParamsEnum; @@ -16,13 +18,16 @@ class UiLocalesRule extends AbstractRule { /** * @inheritDoc + * + * @param ResponseModeInterface $responseMode + * @param HttpMethodsEnum[] $allowedServerRequestMethods */ public function checkRule( ServerRequestInterface $request, ResultBagInterface $currentResultBag, LoggerService $loggerService, array $data = [], - bool $useFragmentInHttpErrorResponses = false, + ResponseModeInterface $responseMode = new QueryResponseMode(), array $allowedServerRequestMethods = [HttpMethodsEnum::GET], ): ?ResultInterface { return new Result($this->getKey(), $this->requestParamsResolver->getBasedOnAllowedMethods( diff --git a/src/Server/RequestTypes/AuthorizationRequest.php b/src/Server/RequestTypes/AuthorizationRequest.php index 1278e9f9..bc969f10 100644 --- a/src/Server/RequestTypes/AuthorizationRequest.php +++ b/src/Server/RequestTypes/AuthorizationRequest.php @@ -6,6 +6,7 @@ use League\OAuth2\Server\RequestTypes\AuthorizationRequest as OAuth2AuthorizationRequest; use SimpleSAML\Module\oidc\Codebooks\FlowTypeEnum; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; class AuthorizationRequest extends OAuth2AuthorizationRequest { @@ -70,6 +71,8 @@ class AuthorizationRequest extends OAuth2AuthorizationRequest */ protected ?string $issuerState = null; + private ?ResponseModeInterface $responseMode = null; + public static function fromOAuth2AuthorizationRequest( OAuth2AuthorizationRequest $oAuth2authorizationRequest, ): AuthorizationRequest { @@ -162,6 +165,16 @@ public function getResponseType(): ?string return $this->responseType; } + public function setResponseMode(ResponseModeInterface $responseMode): void + { + $this->responseMode = $responseMode; + } + + public function getResponseMode(): ?ResponseModeInterface + { + return $this->responseMode; + } + /** * Check if access token should be issued in authorization response (implicit flow, hybrid flow...). * @return bool diff --git a/src/Server/ResponseModes/FormPostResponseMode.php b/src/Server/ResponseModes/FormPostResponseMode.php new file mode 100644 index 00000000..48da84fd --- /dev/null +++ b/src/Server/ResponseModes/FormPostResponseMode.php @@ -0,0 +1,35 @@ +simpleSAMLConfiguration = $simpleSAMLConfiguration; + } + + public function buildResponse(string $redirectUri, array $params): AbstractResponseType + { + $template = new Template($this->simpleSAMLConfiguration, 'oidc:formpost.twig'); + $template->data = [ + 'redirectUri' => $redirectUri, + 'params' => $params, + ]; + $html = $template->getContents(); // renders to a string + + $response = new HtmlResponse(); + $response->setHtml($html); + return $response; + } +} diff --git a/src/Server/ResponseModes/FragmentResponseMode.php b/src/Server/ResponseModes/FragmentResponseMode.php new file mode 100644 index 00000000..b8e40652 --- /dev/null +++ b/src/Server/ResponseModes/FragmentResponseMode.php @@ -0,0 +1,20 @@ +setRedirectUri($redirectUri . $separator . http_build_query($params)); + + return $response; + } +} diff --git a/src/Server/ResponseModes/QueryResponseMode.php b/src/Server/ResponseModes/QueryResponseMode.php new file mode 100644 index 00000000..2a379447 --- /dev/null +++ b/src/Server/ResponseModes/QueryResponseMode.php @@ -0,0 +1,20 @@ +setRedirectUri($redirectUri . $separator . http_build_query($params)); + + return $response; + } +} diff --git a/src/Server/ResponseModes/ResponseModeInterface.php b/src/Server/ResponseModes/ResponseModeInterface.php new file mode 100644 index 00000000..529e4824 --- /dev/null +++ b/src/Server/ResponseModes/ResponseModeInterface.php @@ -0,0 +1,12 @@ +html = $html; + } + + /** + * @param ResponseInterface $response + * + * @return ResponseInterface + */ + public function generateHttpResponse(ResponseInterface $response) + { + $response->getBody()->write($this->html); + + return $response->withStatus(200)->withHeader('Content-Type', 'text/html'); + } +} diff --git a/src/Services/Container.php b/src/Services/Container.php index d03173b5..566f094c 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -92,12 +92,16 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequestObjectRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequiredNonceRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequiredOpenIdScopeRule; +use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ResponseModeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ResponseTypeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ScopeOfflineAccessRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ScopeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\UiLocalesRule; use SimpleSAML\Module\oidc\Server\ResourceServer; +use SimpleSAML\Module\oidc\Server\ResponseModes\FormPostResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\FragmentResponseMode; +use SimpleSAML\Module\oidc\Server\ResponseModes\QueryResponseMode; use SimpleSAML\Module\oidc\Server\ResponseTypes\TokenResponse; use SimpleSAML\Module\oidc\Server\TokenIssuers\RefreshTokenIssuer; use SimpleSAML\Module\oidc\Server\Validators\BearerTokenValidator; @@ -424,6 +428,11 @@ public function __construct() ); $this->services[AuthenticatedOAuth2ClientResolver::class] = $authenticatedOAuth2ClientResolver; + $queryResponseMode = new QueryResponseMode(); + $fragmentResponseMode = new FragmentResponseMode(); + // FormPostResponseMode renders a template, so it requires the configuration. + $formPostResponseMode = new FormPostResponseMode($simpleSAMLConfiguration); + $requestRules = [ new StateRule($requestParamsResolver, $helpers), new ClientRule( @@ -440,6 +449,13 @@ public function __construct() ), new ClientRedirectUriRule($requestParamsResolver, $helpers, $moduleConfig), new RequestObjectRule($requestParamsResolver, $helpers, $jwksResolver), + new ResponseModeRule( + $requestParamsResolver, + $helpers, + $queryResponseMode, + $fragmentResponseMode, + $formPostResponseMode, + ), new PromptRule($requestParamsResolver, $helpers, $authSimpleFactory, $authenticationService, $sspBridge), new MaxAgeRule($requestParamsResolver, $helpers, $authSimpleFactory, $authenticationService, $sspBridge), new ScopeRule($requestParamsResolver, $helpers, $scopeRepository), diff --git a/src/Services/OpMetadataService.php b/src/Services/OpMetadataService.php index 91537d7f..5b3eaa48 100644 --- a/src/Services/OpMetadataService.php +++ b/src/Services/OpMetadataService.php @@ -100,6 +100,8 @@ private function initMetadata(): void $this->metadata[ClaimsEnum::ClaimsSupported->value] = $claimsSupported; } + $this->metadata[ClaimsEnum::ResponseModesSupported->value] = ['query', 'fragment', 'form_post']; + // https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-oauth-20-authorization-serv // OPTIONAL // pre-authorized_grant_anonymous_access_supported // TODO mivanci Make configurable diff --git a/src/Utils/Routes.php b/src/Utils/Routes.php index ab37629d..68791e1e 100644 --- a/src/Utils/Routes.php +++ b/src/Utils/Routes.php @@ -53,7 +53,7 @@ public function newResponse( } public function newJsonResponse( - mixed $data = null, + array|null $data = null, int $status = 200, array $headers = [], bool $json = false, diff --git a/templates/clients/includes/form.twig b/templates/clients/includes/form.twig index 51b1a869..1926a7b0 100644 --- a/templates/clients/includes/form.twig +++ b/templates/clients/includes/form.twig @@ -150,6 +150,15 @@ {{ form.id_token_signed_response_alg.getError }} {% endif %} + + {{ form.response_modes_allowed.control | raw }} + + {% trans %}Allowed response modes for this Client, a selection of 'query', 'fragment' and 'form_post'. Set to 'form_post' only to protect against browser-swapping attacks.{% endtrans %} + + {% if form.response_modes_allowed.hasErrors %} + {{ form.response_modes_allowed.getError }} + {% endif %} +

{{ 'OpenID Federation Related Properties'|trans }}

diff --git a/templates/clients/show.twig b/templates/clients/show.twig index 307de574..47d549c9 100644 --- a/templates/clients/show.twig +++ b/templates/clients/show.twig @@ -213,6 +213,18 @@ {{ client.idTokenSignedResponseAlg|default('N/A'|trans) }} + + + {{ 'Allowed response modes'|trans }} + + + + + {{ 'Owner'|trans }} diff --git a/templates/formpost.twig b/templates/formpost.twig new file mode 100644 index 00000000..95e9604f --- /dev/null +++ b/templates/formpost.twig @@ -0,0 +1,18 @@ + + + + + Submitting... + + +
+ {% for name, value in params %} + + {% endfor %} + +
+ + + \ No newline at end of file diff --git a/tests/unit/src/Controllers/Admin/ClientControllerTest.php b/tests/unit/src/Controllers/Admin/ClientControllerTest.php index 02134ac8..2e77e018 100644 --- a/tests/unit/src/Controllers/Admin/ClientControllerTest.php +++ b/tests/unit/src/Controllers/Admin/ClientControllerTest.php @@ -82,6 +82,7 @@ class ClientControllerTest extends TestCase ], 'jwks_uri' => 'https://example.com/jwks', 'signed_jwks_uri' => 'https://example.com/signed-jwks', + 'response_modes_allowed' => ['query', 'fragment', 'form_post'], ]; protected function setUp(): void diff --git a/tests/unit/src/Forms/ClientFormTest.php b/tests/unit/src/Forms/ClientFormTest.php index 425c3d2e..7638da68 100644 --- a/tests/unit/src/Forms/ClientFormTest.php +++ b/tests/unit/src/Forms/ClientFormTest.php @@ -81,6 +81,7 @@ public function setUp(): void ), 'expires_at' => null, 'allowed_origin' => [], + 'response_modes_allowed' => ['query', 'fragment', 'form_post',], ]; } diff --git a/tests/unit/src/Server/Grants/ImplicitGrantTest.php b/tests/unit/src/Server/Grants/ImplicitGrantTest.php index d28dabda..5f487746 100644 --- a/tests/unit/src/Server/Grants/ImplicitGrantTest.php +++ b/tests/unit/src/Server/Grants/ImplicitGrantTest.php @@ -31,7 +31,6 @@ class ImplicitGrantTest extends TestCase protected MockObject $accessTokenRepositoryMock; protected MockObject $requestRulesManagerMock; protected MockObject $requestParamsResolverMock; - protected string $queryDelimiter; protected MockObject $accessTokenEntityFactoryMock; protected MockObject $scopeRepositoryMock; protected MockObject $serverRequestMock; @@ -48,7 +47,6 @@ protected function setUp(): void $this->accessTokenRepositoryMock = $this->createMock(AccessTokenRepository::class); $this->requestRulesManagerMock = $this->createMock(RequestRulesManager::class); $this->requestParamsResolverMock = $this->createMock(RequestParamsResolver::class); - $this->queryDelimiter = '#'; $this->accessTokenEntityFactoryMock = $this->createMock(AccessTokenEntityFactory::class); $this->scopeRepositoryMock = $this->createMock(ScopeRepositoryInterface::class); @@ -66,7 +64,6 @@ protected function sut( ?AccessTokenRepositoryInterface $accessTokenRepository = null, ?RequestRulesManager $requestRulesManager = null, ?RequestParamsResolver $requestParamsResolver = null, - ?string $queryDelimiter = null, ?AccessTokenEntityFactory $accessTokenEntityFactory = null, ?ScopeRepositoryInterface $scopeRepository = null, ): ImplicitGrant { @@ -75,7 +72,6 @@ protected function sut( $accessTokenRepository ??= $this->accessTokenRepositoryMock; $requestRulesManager ??= $this->requestRulesManagerMock; $requestParamsResolver ??= $this->requestParamsResolverMock; - $queryDelimiter ??= $this->queryDelimiter; $accessTokenEntityFactory ??= $this->accessTokenEntityFactoryMock; $scopeRepository ??= $this->scopeRepositoryMock; @@ -86,7 +82,6 @@ protected function sut( $accessTokenRepository, $requestRulesManager, $requestParamsResolver, - $queryDelimiter, $accessTokenEntityFactory, ); diff --git a/tests/unit/src/Server/RequestRules/RequestRulesManagerTest.php b/tests/unit/src/Server/RequestRules/RequestRulesManagerTest.php index c70118db..2efabc97 100644 --- a/tests/unit/src/Server/RequestRules/RequestRulesManagerTest.php +++ b/tests/unit/src/Server/RequestRules/RequestRulesManagerTest.php @@ -12,6 +12,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\RequestRulesManager; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; /** @@ -122,6 +123,7 @@ public function testSetData(RequestRulesManager $requestRulesManager): void $this->isInstanceOf(ResultBagInterface::class), $this->isInstanceOf(LoggerService::class), $this->arrayHasKey($this->key), + $this->isInstanceOf(ResponseModeInterface::class), ); $requestRulesManager->add($ruleMock); diff --git a/tests/unit/src/Server/RequestRules/Rules/AcrValuesRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/AcrValuesRuleTest.php index 302eaa30..8a147402 100644 --- a/tests/unit/src/Server/RequestRules/Rules/AcrValuesRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/AcrValuesRuleTest.php @@ -12,6 +12,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\AcrValuesRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -25,6 +26,7 @@ class AcrValuesRuleTest extends TestCase protected Stub $resultStub; protected Stub $loggerServiceStub; protected Stub $requestParamsResolverStub; + protected Stub $responseModeStub; protected Helpers $helpers; /** @@ -37,6 +39,7 @@ protected function setUp(): void $this->resultStub = $this->createStub(ResultInterface::class); $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); $this->helpers = new Helpers(); } @@ -62,6 +65,8 @@ public function testNoAcrIsSetIfAcrValuesNotRequested(): void $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, + [], + $this->responseModeStub, ) ?? new Result(AcrValuesRule::class, null); $this->assertNull($result->getValue()); } @@ -79,6 +84,8 @@ public function testPopulatesAcrValuesFromClaimsParameter(): void $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, + [], + $this->responseModeStub, ) ?? new Result(AcrValuesRule::class, null); $this->assertSame(['1', '0'], $result->getValue()['values']); @@ -96,6 +103,8 @@ public function testPopulatesAcrValuesFromAcrValuesRequestParameter(): void $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, + [], + $this->responseModeStub, ) ?? new Result(AcrValuesRule::class, null); $this->assertSame(['1', '0'], $result->getValue()['values']); diff --git a/tests/unit/src/Server/RequestRules/Rules/AddClaimsToIdTokenRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/AddClaimsToIdTokenRuleTest.php index fb54da45..121dc7e2 100644 --- a/tests/unit/src/Server/RequestRules/Rules/AddClaimsToIdTokenRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/AddClaimsToIdTokenRuleTest.php @@ -13,6 +13,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\ResultBag; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\AddClaimsToIdTokenRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ResponseTypeRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -48,6 +49,7 @@ class AddClaimsToIdTokenRuleTest extends TestCase private ResultBag $resultBag; private Stub $loggerServiceStub; + private Stub $responseModeStub; /** * @throws \Exception @@ -60,6 +62,7 @@ protected function setUp(): void $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); $this->helpers = new Helpers(); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -83,7 +86,13 @@ public function testAddClaimsToIdTokenRuleTest($responseType) { $this->resultBag->add(new Result(ResponseTypeRule::class, $responseType)); - $result = $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ) ?? new Result(AddClaimsToIdTokenRule::class, null); $this->assertTrue($result->getValue()); } @@ -103,7 +112,13 @@ public function testDoNotAddClaimsToIdTokenRuleTest($responseType) { $this->resultBag->add(new Result(ResponseTypeRule::class, $responseType)); - $result = $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ) ?? new Result(AddClaimsToIdTokenRule::class, null); $this->assertFalse($result->getValue()); @@ -128,6 +143,12 @@ public static function invalidResponseTypeProvider(): array public function testAddClaimsToIdTokenRuleThrowsWithNoResponseTypeParamTest() { $this->expectException(LogicException::class); - $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); } } diff --git a/tests/unit/src/Server/RequestRules/Rules/ClientRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/ClientRuleTest.php index abf0eb9e..191c982a 100644 --- a/tests/unit/src/Server/RequestRules/Rules/ClientRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/ClientRuleTest.php @@ -16,6 +16,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\FederationCache; use SimpleSAML\Module\oidc\Utils\FederationParticipationValidator; @@ -41,6 +42,7 @@ class ClientRuleTest extends TestCase protected Stub $helpersStub; protected Stub $jwksResolverStub; protected Stub $federationParticipationValidatorStub; + protected Stub $responseModeStub; /** * @throws \Exception @@ -60,6 +62,7 @@ protected function setUp(): void $this->helpersStub = $this->createStub(Helpers::class); $this->jwksResolverStub = $this->createStub(JwksResolver::class); $this->federationParticipationValidatorStub = $this->createStub(FederationParticipationValidator::class); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut(): ClientRule @@ -91,6 +94,8 @@ public function testCheckRuleEmptyClientIdThrows(): void $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, + [], + $this->responseModeStub, ); } @@ -103,6 +108,8 @@ public function testCheckRuleInvalidClientThrows(): void $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, + [], + $this->responseModeStub, ); } @@ -119,6 +126,8 @@ public function testCheckRuleForValidClientId(): void $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, + [], + $this->responseModeStub, ); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertInstanceOf(ClientEntityInterface::class, $result->getValue()); diff --git a/tests/unit/src/Server/RequestRules/Rules/CodeChallengeMethodRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/CodeChallengeMethodRuleTest.php index f01343ca..e3c66093 100644 --- a/tests/unit/src/Server/RequestRules/Rules/CodeChallengeMethodRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/CodeChallengeMethodRuleTest.php @@ -19,6 +19,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientRedirectUriRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\CodeChallengeMethodRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -36,6 +37,7 @@ class CodeChallengeMethodRuleTest extends TestCase protected Stub $requestParamsResolverStub; protected MockObject $codeChallengeVerifiersRepositoryMock; protected Helpers $helpers; + protected Stub $responseModeStub; /** * @throws \Exception */ @@ -49,6 +51,7 @@ protected function setUp(): void $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); $this->codeChallengeVerifiersRepositoryMock = $this->createMock(CodeChallengeVerifiersRepository::class); $this->helpers = new Helpers(); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -75,7 +78,7 @@ public function testCheckRuleRedirectUriDependency(): void { $resultBag = new ResultBag(); $this->expectException(LogicException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, [], $this->responseModeStub); } /** @@ -87,7 +90,7 @@ public function testCheckRuleStateDependency(): void $resultBag = new ResultBag(); $resultBag->add($this->redirectUriResult); $this->expectException(LogicException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, [], $this->responseModeStub); } /** @@ -100,7 +103,7 @@ public function testCheckRuleWithInvalidCodeChallengeMethodThrows(): void $this->codeChallengeVerifiersRepositoryMock->expects($this->once())->method('has') ->with('invalid')->willReturn(false); $this->expectException(OidcServerException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, [], $this->responseModeStub); } /** @@ -113,7 +116,13 @@ public function testCheckRuleForValidCodeChallengeMethod(): void $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn('plain'); $this->codeChallengeVerifiersRepositoryMock->expects($this->once())->method('has') ->with('plain')->willReturn(true); - $result = $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $result = $this->sut()->checkRule( + $this->requestStub, + $resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertSame('plain', $result->getValue()); diff --git a/tests/unit/src/Server/RequestRules/Rules/CodeChallengeRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/CodeChallengeRuleTest.php index 1755ea6f..6e84765d 100644 --- a/tests/unit/src/Server/RequestRules/Rules/CodeChallengeRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/CodeChallengeRuleTest.php @@ -19,6 +19,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\CodeChallengeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -39,6 +40,7 @@ class CodeChallengeRuleTest extends TestCase protected Stub $clientStub; protected Result $clientIdResult; protected Helpers $helpers; + protected Stub $responseModeStub; /** * @throws \Exception @@ -54,6 +56,7 @@ protected function setUp(): void $this->clientStub = $this->createStub(ClientEntityInterface::class); $this->clientIdResult = new Result(ClientRule::class, $this->clientStub); $this->helpers = new Helpers(); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -77,7 +80,7 @@ public function testCheckRuleRedirectUriDependency(): void { $resultBag = new ResultBag(); $this->expectException(LogicException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, [], $this->responseModeStub); } /** @@ -89,7 +92,7 @@ public function testCheckRuleStateDependency(): void $resultBag = new ResultBag(); $resultBag->add($this->redirectUriResult); $this->expectException(LogicException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, [], $this->responseModeStub); } /** @@ -100,7 +103,13 @@ public function testCheckRuleNoCodeReturnsNullForConfidentialClients(): void $this->clientStub->method('isConfidential')->willReturn(true); $resultBag = $this->prepareValidResultBag(); $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn(null); - $result = $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $result = $this->sut()->checkRule( + $this->requestStub, + $resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertNull($result->getValue()); } @@ -113,7 +122,7 @@ public function testCheckRuleInvalidCodeChallengeThrows(): void $resultBag = $this->prepareValidResultBag(); $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn('too-short'); $this->expectException(OidcServerException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, [], $this->responseModeStub); } /** @@ -125,7 +134,13 @@ public function testCheckRuleForValidCodeChallenge(): void $resultBag = $this->prepareValidResultBag(); $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn($this->codeChallenge); - $result = $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $result = $this->sut()->checkRule( + $this->requestStub, + $resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertSame($this->codeChallenge, $result->getValue()); diff --git a/tests/unit/src/Server/RequestRules/Rules/IdTokenHintRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/IdTokenHintRuleTest.php index d8137a8e..65de0270 100644 --- a/tests/unit/src/Server/RequestRules/Rules/IdTokenHintRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/IdTokenHintRuleTest.php @@ -14,6 +14,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\IdTokenHintRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Core; @@ -46,6 +47,7 @@ class IdTokenHintRuleTest extends TestCase protected MockObject $coreMock; protected MockObject $idTokenFactoryMock; protected MockObject $idTokenMock; + protected Stub $responseModeStub; /** * @throws \ReflectionException @@ -70,6 +72,7 @@ protected function setUp(): void $this->idTokenFactoryMock = $this->createMock(IdTokenFactory::class); $this->idTokenMock = $this->createMock(IdToken::class); $this->coreMock->method('idTokenFactory')->willReturn($this->idTokenFactoryMock); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -110,6 +113,8 @@ public function testCheckRuleIsNullWhenParamNotSet(): void $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, + [], + $this->responseModeStub, ) ?? new Result(IdTokenHintRule::class); $this->assertNull($result->getValue()); @@ -122,7 +127,13 @@ public function testCheckRuleThrowsForMalformedIdToken(): void { $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn('malformed'); $this->expectException(Throwable::class); - $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); } /** @@ -139,7 +150,13 @@ public function testCheckRuleThrowsForIdTokenWithInvalidSignature(): void ->with('invalid-it-token') ->willReturn($this->idTokenMock); $this->expectException(Throwable::class); - $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); } /** @@ -157,7 +174,13 @@ public function testCheckRuleThrowsForIdTokenWithInvalidIssuer(): void $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods') ->willReturn('id-token'); $this->expectException(Throwable::class); - $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); } /** @@ -172,7 +195,13 @@ public function testCheckRulePassesForValidIdToken(): void $this->idTokenMock->method('getIssuer')->willReturn(self::$issuer); $this->idTokenFactoryMock->method('fromToken') ->willReturn($this->idTokenMock); - $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ) ?? new Result(IdTokenHintRule::class); $this->assertInstanceOf(IdToken::class, $result->getValue()); diff --git a/tests/unit/src/Server/RequestRules/Rules/PostLogoutRedirectUriRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/PostLogoutRedirectUriRuleTest.php index 7bf7d279..8b5c225a 100644 --- a/tests/unit/src/Server/RequestRules/Rules/PostLogoutRedirectUriRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/PostLogoutRedirectUriRuleTest.php @@ -21,6 +21,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\IdTokenHintRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\PostLogoutRedirectUriRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Core\IdToken; @@ -50,6 +51,7 @@ class PostLogoutRedirectUriRuleTest extends TestCase protected Stub $requestParamsResolverStub; protected Helpers $helpers; protected MockObject $idTokenMock; + protected Stub $responseModeStub; public static function setUpBeforeClass(): void { @@ -83,6 +85,7 @@ protected function setUp(): void $this->helpers = new Helpers(); $this->idTokenMock = $this->createMock(IdToken::class); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -107,7 +110,13 @@ protected function sut( */ public function testCheckRuleReturnsNullIfNoParamSet(): void { - $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ) ?? (new Result(PostLogoutRedirectUriRule::class)); $this->assertNull($result->getValue()); @@ -123,7 +132,13 @@ public function testCheckRuleThrowsWhenIdTokenHintNotAvailable(): void $this->expectException(Throwable::class); - $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ) ?? (new Result(PostLogoutRedirectUriRule::class)); } @@ -148,7 +163,13 @@ public function testCheckRuleThrowsWhenAudClaimNotValid(): void $this->expectException(Throwable::class); - $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ) ?? (new Result(PostLogoutRedirectUriRule::class)); } @@ -176,7 +197,13 @@ public function testCheckRuleThrowsWhenClientNotFound(): void $this->expectException(Throwable::class); - $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ) ?? (new Result(PostLogoutRedirectUriRule::class)); } @@ -204,7 +231,13 @@ public function testCheckRuleThrowsWhenPostLogoutRegisteredUriNotRegistered(): v $this->expectException(Throwable::class); - $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ) ?? (new Result(PostLogoutRedirectUriRule::class)); } @@ -231,7 +264,13 @@ public function testCheckRuleReturnsForRegisteredPostLogoutRedirectUri(): void new Result(IdTokenHintRule::class, $this->idTokenMock), ); - $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ) ?? (new Result(PostLogoutRedirectUriRule::class)); $this->assertEquals(self::$postLogoutRedirectUri, $result->getValue()); diff --git a/tests/unit/src/Server/RequestRules/Rules/RedirectUriRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/RedirectUriRuleTest.php index 33a31f4e..68dbd391 100644 --- a/tests/unit/src/Server/RequestRules/Rules/RedirectUriRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/RedirectUriRuleTest.php @@ -17,6 +17,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\ResultBag; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientRedirectUriRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -34,6 +35,7 @@ class RedirectUriRuleTest extends TestCase protected Stub $requestParamsResolverStub; protected Helpers $helpers; protected Stub $moduleConfigStub; + protected Stub $responseModeStub; /** @@ -48,6 +50,7 @@ protected function setUp(): void $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); $this->helpers = new Helpers(); $this->moduleConfigStub = $this->createStub(ModuleConfig::class); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -73,7 +76,13 @@ protected function sut( public function testCheckRuleClientIdDependency(): void { $this->expectException(LogicException::class); - $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); } /** @@ -84,7 +93,13 @@ public function testCheckRuleWithInvalidClientDependancy(): void { $this->resultBag->add(new Result(ClientRule::class, 'invalid')); $this->expectException(LogicException::class); - $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); } /** @@ -95,7 +110,7 @@ public function testCheckRuleRedirectUriNotSetThrows(): void $resultBag = $this->prepareValidResultBag(); $this->expectException(OidcServerException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, [], $this->responseModeStub); } /** @@ -107,7 +122,7 @@ public function testCheckRuleDifferentClientRedirectUriThrows(): void $resultBag = $this->prepareValidResultBag(); $this->expectException(OidcServerException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, [], $this->responseModeStub); } /** @@ -121,7 +136,13 @@ public function testCheckRuleDifferentClientRedirectUriArrayThrows(): void $this->resultBag->add(new Result(ClientRule::class, $this->clientStub)); $this->expectException(OidcServerException::class); - $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); } /** @@ -134,7 +155,13 @@ public function testCheckRuleWithValidRedirectUri(): void $resultBag = $this->prepareValidResultBag(); - $result = $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $result = $this->sut()->checkRule( + $this->requestStub, + $resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertSame($this->redirectUri, $result->getValue()); diff --git a/tests/unit/src/Server/RequestRules/Rules/RequestObjectRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/RequestObjectRuleTest.php index 45861adb..56f0679a 100644 --- a/tests/unit/src/Server/RequestRules/Rules/RequestObjectRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/RequestObjectRuleTest.php @@ -17,6 +17,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientRedirectUriRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequestObjectRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\JwksResolver; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -33,6 +34,7 @@ class RequestObjectRuleTest extends TestCase protected Stub $loggerServiceStub; protected MockObject $jwksResolverMock; protected Helpers $helpers; + protected Stub $responseModeStub; protected function setUp(): void { @@ -49,6 +51,7 @@ protected function setUp(): void $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->jwksResolverMock = $this->createMock(JwksResolver::class); $this->helpers = new Helpers(); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -74,7 +77,13 @@ public function testCanCreateInstance(): void public function testRequestParamCanBeAbsent(): void { - $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); $this->assertNull($result); } @@ -85,7 +94,13 @@ public function testUnprotectedRequestParamCanBeUsed(): void $this->requestParamsResolverMock->expects($this->once())->method('parseRequestObjectToken') ->with('token')->willReturn($this->requestObjectMock); - $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); $this->assertInstanceOf(Result::class, $result); $this->assertIsArray($result->getValue()); $this->assertNotEmpty($result->getValue()); @@ -100,7 +115,13 @@ public function testMissingClientJwksThrows(): void $this->clientStub->expects($this->once())->method('getJwks')->willReturn(null); $this->expectException(OidcServerException::class); - $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); } public function testThrowsForInvalidRequestObject(): void @@ -116,7 +137,13 @@ public function testThrowsForInvalidRequestObject(): void ->willReturn(['jwks']); $this->expectException(OidcServerException::class); - $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); } public function testReturnsValidRequestObject(): void @@ -132,7 +159,13 @@ public function testReturnsValidRequestObject(): void ->with($this->clientStub) ->willReturn(['jwks']); - $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); $this->assertInstanceOf(Result::class, $result); $this->assertIsArray($result->getValue()); diff --git a/tests/unit/src/Server/RequestRules/Rules/RequestedClaimsRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/RequestedClaimsRuleTest.php index a17f677d..33caca10 100644 --- a/tests/unit/src/Server/RequestRules/Rules/RequestedClaimsRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/RequestedClaimsRuleTest.php @@ -15,6 +15,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\ResultBag; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequestedClaimsRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -33,6 +34,7 @@ class RequestedClaimsRuleTest extends TestCase protected Stub $requestParamsResolverStub; protected Stub $claimSetEntityFactoryStub; protected Helpers $helpers; + protected Stub $responseModeStub; /** @@ -57,6 +59,7 @@ protected function setUp(): void }); $this->helpers = new Helpers(); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -80,7 +83,13 @@ protected function sut( */ public function testNoRequestedClaims(): void { - $result = $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); $this->assertNull($result); } @@ -113,7 +122,13 @@ public function testWithClaims(): void $this->requestParamsResolverStub->method('getBasedOnAllowedMethods')->willReturn(json_encode($requestedClaims)); - $result = $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); $this->assertNotNull($result); $this->assertEquals($expectedClaims, $result->getValue()); } @@ -132,7 +147,13 @@ public function testOnlyWithNonStandardClaimRequest(): void $requestedClaims = $expectedClaims; $this->requestParamsResolverStub->method('getBasedOnAllowedMethods')->willReturn(json_encode($requestedClaims)); - $result = $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); $this->assertNotNull($result); $this->assertEquals($expectedClaims, $result->getValue()); } diff --git a/tests/unit/src/Server/RequestRules/Rules/RequiredNonceRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/RequiredNonceRuleTest.php index 6bfbd34e..cb15a571 100644 --- a/tests/unit/src/Server/RequestRules/Rules/RequiredNonceRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/RequiredNonceRuleTest.php @@ -15,6 +15,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientRedirectUriRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequiredNonceRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -37,6 +38,7 @@ class RequiredNonceRuleTest extends TestCase protected Stub $loggerServiceStub; protected Stub $requestParamsResolverStub; + protected Stub $responseModeStub; /** * @throws \Exception @@ -54,6 +56,7 @@ protected function setUp(): void $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); $this->helpers = new Helpers(); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -77,7 +80,7 @@ public function testCheckRuleRedirectUriDependency(): void { $resultBag = new ResultBag(); $this->expectException(LogicException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, [], $this->responseModeStub); } /** @@ -89,7 +92,7 @@ public function testCheckRuleStateDependency(): void $resultBag = new ResultBag(); $resultBag->add($this->redirectUriResult); $this->expectException(LogicException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, [], $this->responseModeStub); } /** @@ -101,7 +104,13 @@ public function testCheckRulePassesWhenNonceIsPresent() $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods') ->willReturn($this->requestQueryParams['nonce']); - $result = $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ) ?? new Result(RequiredNonceRule::class, null); $this->assertEquals($this->requestQueryParams['nonce'], $result->getValue()); @@ -114,6 +123,12 @@ public function testCheckRuleThrowsWhenNonceIsNotPresent() { $this->expectException(OidcServerException::class); - $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); } } diff --git a/tests/unit/src/Server/RequestRules/Rules/RequiredOpenIdScopeRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/RequiredOpenIdScopeRuleTest.php index 05668b79..84de98eb 100644 --- a/tests/unit/src/Server/RequestRules/Rules/RequiredOpenIdScopeRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/RequiredOpenIdScopeRuleTest.php @@ -17,6 +17,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequiredOpenIdScopeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ScopeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -36,6 +37,7 @@ class RequiredOpenIdScopeRuleTest extends TestCase protected Stub $loggerServiceStub; protected Stub $requestParamsResolverStub; protected Helpers $helpers; + protected Stub $responseModeStub; /** * @throws \Exception @@ -53,6 +55,7 @@ protected function setUp(): void $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); $this->helpers = new Helpers(); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -76,7 +79,7 @@ public function testCheckRuleRedirectUriDependency(): void { $resultBag = new ResultBag(); $this->expectException(LogicException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, [], $this->responseModeStub); } /** @@ -88,7 +91,7 @@ public function testCheckRuleStateDependency(): void $resultBag = new ResultBag(); $resultBag->add($this->redirectUriResult); $this->expectException(LogicException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, [], $this->responseModeStub); } /** @@ -102,7 +105,13 @@ public function testCheckRulePassesWhenOpenIdScopeIsPresent() $resultBag->add($this->stateResult); $resultBag->add($this->scopeResult); - $result = $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule( + $this->requestStub, + $resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ) ?? new Result(RequiredOpenIdScopeRule::class, null); $this->assertTrue($result->getValue()); @@ -123,6 +132,6 @@ public function testCheckRuleThrowsWhenOpenIdScopeIsNotPresent() $this->expectException(OidcServerException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, [], $this->responseModeStub); } } diff --git a/tests/unit/src/Server/RequestRules/Rules/ResponseModeRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/ResponseModeRuleTest.php new file mode 100644 index 00000000..4fcef301 --- /dev/null +++ b/tests/unit/src/Server/RequestRules/Rules/ResponseModeRuleTest.php @@ -0,0 +1,332 @@ + 'client123', + 'response_type' => 'code', + 'response_mode' => 'query', + ]; + + protected function setUp(): void + { + $this->requestStub = $this->createStub(ServerRequestInterface::class); + $this->loggerServiceStub = $this->createStub(LoggerService::class); + $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); + $this->helpers = new Helpers(); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); + + $this->clientStub = $this->createStub(ClientEntityInterface::class); + $this->clientStub->method('getAllowedResponseModes')->willReturn(['query', 'fragment', 'form_post']); + + $this->queryResponseModeStub = $this->createStub(QueryResponseMode::class); + $this->fragmentResponseModeStub = $this->createStub(FragmentResponseMode::class); + $this->formPostResponseModeStub = $this->createStub(FormPostResponseMode::class); + + $this->resultBag = new ResultBag(); + $this->resultBag->add(new Result(ClientRule::class, $this->clientStub)); + $this->resultBag->add(new Result(ClientRedirectUriRule::class, 'https://example.org/callback')); + $this->resultBag->add(new Result(StateRule::class, 'state123')); + } + + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ?QueryResponseMode $queryResponseMode = null, + ?FragmentResponseMode $fragmentResponseMode = null, + ?FormPostResponseMode $formPostResponseMode = null, + ): ResponseModeRule { + return new ResponseModeRule( + $requestParamsResolver ?? $this->requestParamsResolverStub, + $helpers ?? $this->helpers, + $queryResponseMode ?? $this->queryResponseModeStub, + $fragmentResponseMode ?? $this->fragmentResponseModeStub, + $formPostResponseMode ?? $this->formPostResponseModeStub, + ); + } + + public function testThrowsWhenClientIdMissing(): void + { + $params = $this->requestParams; + unset($params['client_id']); + $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($params); + + $this->expectException(OidcServerException::class); + $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); + } + + public function testReturnsQueryResponseModeWhenExplicitlyRequested(): void + { + $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($this->requestParams); + + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); + + $this->assertNotNull($result); + $this->assertSame($this->queryResponseModeStub, $result->getValue()); + } + + public function testReturnsFragmentResponseModeWhenExplicitlyRequested(): void + { + $params = $this->requestParams; + $params['response_mode'] = 'fragment'; + $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($params); + + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); + + $this->assertNotNull($result); + $this->assertSame($this->fragmentResponseModeStub, $result->getValue()); + } + + public function testReturnsFormPostResponseModeWhenExplicitlyRequested(): void + { + $params = $this->requestParams; + $params['response_mode'] = 'form_post'; + $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($params); + + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); + + $this->assertNotNull($result); + $this->assertSame($this->formPostResponseModeStub, $result->getValue()); + } + + public function testDefaultsToQueryWhenResponseModeNotSetAndResponseTypeIsCode(): void + { + $params = $this->requestParams; + unset($params['response_mode']); + $params['response_type'] = 'code'; + $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($params); + + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); + + $this->assertNotNull($result); + $this->assertSame($this->queryResponseModeStub, $result->getValue()); + } + + /** + * @dataProvider tokenResponseTypeProvider + */ + public function testDefaultsToFragmentWhenResponseModeNotSetAndResponseTypeContainsToken( + string $responseType, + ): void { + $params = $this->requestParams; + unset($params['response_mode']); + $params['response_type'] = $responseType; + $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($params); + + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); + + $this->assertNotNull($result); + $this->assertSame($this->fragmentResponseModeStub, $result->getValue()); + } + + public static function tokenResponseTypeProvider(): array + { + return [ + 'token' => ['token'], + 'id_token token' => ['id_token token'], + 'code token' => ['code token'], + 'code id_token token' => ['code id_token token'], + ]; + } + + public function testDefaultsToQueryWhenResponseModeAndResponseTypeNotSet(): void + { + $params = ['client_id' => 'client123']; + $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($params); + + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); + + $this->assertNotNull($result); + $this->assertSame($this->queryResponseModeStub, $result->getValue()); + } + + public function testThrowsOnInvalidResponseMode(): void + { + $params = $this->requestParams; + $params['response_mode'] = 'invalid'; + $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($params); + + $this->expectException(OidcServerException::class); + $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); + } + + public function testThrowsWhenResponseModeNotAllowedByClient(): void + { + $this->clientStub = $this->createStub(ClientEntityInterface::class); + $this->clientStub->method('getAllowedResponseModes')->willReturn(['query']); + + $this->resultBag = new ResultBag(); + $this->resultBag->add(new Result(ClientRule::class, $this->clientStub)); + $this->resultBag->add(new Result(ClientRedirectUriRule::class, 'https://example.org/callback')); + $this->resultBag->add(new Result(StateRule::class, 'state123')); + + $params = $this->requestParams; + $params['response_mode'] = 'fragment'; + $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($params); + + $this->expectException(OidcServerException::class); + $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); + } + + public function testThrowsWhenClientRuleResultMissing(): void + { + $resultBag = new ResultBag(); + + $params = $this->requestParams; + $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($params); + + $this->expectException(LogicException::class); + $this->sut()->checkRule( + $this->requestStub, + $resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); + } + + public function testThrowsWhenRedirectUriResultMissing(): void + { + $resultBag = new ResultBag(); + $resultBag->add(new Result(ClientRule::class, $this->clientStub)); + + $params = $this->requestParams; + $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($params); + + $this->expectException(LogicException::class); + $this->sut()->checkRule( + $this->requestStub, + $resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); + } + + public function testThrowsWhenStateResultMissing(): void + { + $resultBag = new ResultBag(); + $resultBag->add(new Result(ClientRule::class, $this->clientStub)); + $resultBag->add(new Result(ClientRedirectUriRule::class, 'https://example.org/callback')); + + $params = $this->requestParams; + $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($params); + + $this->expectException(LogicException::class); + $this->sut()->checkRule( + $this->requestStub, + $resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); + } + + public function testResultKeyMatchesRuleClass(): void + { + $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($this->requestParams); + + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); + + $this->assertNotNull($result); + $this->assertSame(ResponseModeRule::class, $result->getKey()); + } +} diff --git a/tests/unit/src/Server/RequestRules/Rules/ResponseTypeRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/ResponseTypeRuleTest.php index ecd2d1e6..5b1c5c91 100644 --- a/tests/unit/src/Server/RequestRules/Rules/ResponseTypeRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/ResponseTypeRuleTest.php @@ -12,6 +12,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Result; use SimpleSAML\Module\oidc\Server\RequestRules\ResultBag; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ResponseTypeRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -49,6 +50,7 @@ class ResponseTypeRuleTest extends TestCase private ResultBag $resultBag; protected Stub $loggerServiceStub; + protected Stub $responseModeStub; /** * @throws \Exception @@ -61,6 +63,7 @@ protected function setUp(): void $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); $this->helpers = new Helpers(); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -84,7 +87,13 @@ public function testResponseTypeRuleTest($responseType) { $this->requestParams['response_type'] = $responseType; $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($this->requestParams); - $result = $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ) ?? new Result(ResponseTypeRule::class, null); $this->assertSame($responseType, $result->getValue()); } @@ -103,6 +112,12 @@ public function testResponseTypeRuleThrowsWithNoResponseTypeParamTest() unset($params['response_type']); $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($params); $this->expectException(OidcServerException::class); - $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $this->sut()->checkRule( + $this->requestStub, + $this->resultBag, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ); } } diff --git a/tests/unit/src/Server/RequestRules/Rules/ScopeOfflineAccessRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/ScopeOfflineAccessRuleTest.php index e9bddd38..a640af98 100644 --- a/tests/unit/src/Server/RequestRules/Rules/ScopeOfflineAccessRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/ScopeOfflineAccessRuleTest.php @@ -17,6 +17,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ScopeOfflineAccessRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -39,6 +40,7 @@ class ScopeOfflineAccessRuleTest extends TestCase protected Stub $openIdConfigurationStub; protected Stub $requestParamsResolverStub; protected Helpers $helpers; + protected Stub $responseModeStub; /** * @throws \Exception @@ -69,6 +71,7 @@ protected function setUp(): void $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); $this->helpers = new Helpers(); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -115,7 +118,13 @@ public function testReturnsFalseWhenOfflineAccessScopeNotPresent(): void $this->moduleConfigStub->method('config') ->willReturn($this->openIdConfigurationStub); - $result = $this->sut()->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); + $result = $this->sut()->checkRule( + $this->serverRequestStub, + $this->resultBagMock, + $this->loggerServiceMock, + [], + $this->responseModeStub, + ); $this->assertNotNull($result); $this->assertFalse($result->getValue()); @@ -146,7 +155,13 @@ public function testThrowsWhenClientDoesntHaveOfflineAccessScopeRegistered(): vo $this->expectException(OidcServerException::class); - $this->sut()->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); + $this->sut()->checkRule( + $this->serverRequestStub, + $this->resultBagMock, + $this->loggerServiceMock, + [], + $this->responseModeStub, + ); } /** @@ -173,7 +188,13 @@ public function testReturnsTrueWhenClientDoesHaveOfflineAccessScopeRegistered(): $this->moduleConfigStub->method('config') ->willReturn($this->openIdConfigurationStub); - $result = $this->sut()->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); + $result = $this->sut()->checkRule( + $this->serverRequestStub, + $this->resultBagMock, + $this->loggerServiceMock, + [], + $this->responseModeStub, + ); $this->assertNotNull($result); $this->assertTrue($result->getValue()); diff --git a/tests/unit/src/Server/RequestRules/Rules/ScopeRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/ScopeRuleTest.php index 1916686e..fa144662 100644 --- a/tests/unit/src/Server/RequestRules/Rules/ScopeRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/ScopeRuleTest.php @@ -20,6 +20,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientRedirectUriRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ScopeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -47,6 +48,7 @@ class ScopeRuleTest extends TestCase protected Stub $requestParamsResolverStub; protected Stub $helpersStub; protected Stub $strHelperMock; + protected Stub $responseModeStub; /** * @throws \Exception @@ -67,6 +69,7 @@ protected function setUp(): void $this->helpersStub = $this->createStub(Helpers::class); $this->strHelperMock = $this->createMock(Helpers\Str::class); $this->helpersStub->method('str')->willReturn($this->strHelperMock); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -98,7 +101,13 @@ public function testCheckRuleRedirectUriDependency(): void { $resultBag = new ResultBag(); $this->expectException(LogicException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, $this->data); + $this->sut()->checkRule( + $this->requestStub, + $resultBag, + $this->loggerServiceStub, + $this->data, + $this->responseModeStub, + ); } /** @@ -110,7 +119,13 @@ public function testCheckRuleStateDependency(): void $resultBag = new ResultBag(); $resultBag->add($this->redirectUriResult); $this->expectException(LogicException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, $this->data); + $this->sut()->checkRule( + $this->requestStub, + $resultBag, + $this->loggerServiceStub, + $this->data, + $this->responseModeStub, + ); } /** @@ -134,7 +149,13 @@ public function testValidScopes(): void ), ); - $result = $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, $this->data); + $result = $this->sut()->checkRule( + $this->requestStub, + $resultBag, + $this->loggerServiceStub, + $this->data, + $this->responseModeStub, + ); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertIsArray($result->getValue()); $this->assertSame($this->scopeEntities['openid'], $result->getValue()[0]); @@ -161,7 +182,13 @@ public function testInvalidScopeThrows(): void ); $this->expectException(OidcServerException::class); - $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, $this->data); + $this->sut()->checkRule( + $this->requestStub, + $resultBag, + $this->loggerServiceStub, + $this->data, + $this->responseModeStub, + ); } protected function prepareValidResultBag(): ResultBag diff --git a/tests/unit/src/Server/RequestRules/Rules/StateRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/StateRuleTest.php index aa38de1b..336ca8bb 100644 --- a/tests/unit/src/Server/RequestRules/Rules/StateRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/StateRuleTest.php @@ -11,6 +11,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\ResultBag; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -23,6 +24,7 @@ class StateRuleTest extends TestCase protected Stub $loggerServiceStub; protected Stub $requestParamsResolverStub; protected Helpers $helpers; + protected Stub $responseModeStub; /** * @throws \Exception @@ -32,6 +34,7 @@ public function setUp(): void $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); $this->helpers = new Helpers(); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -65,7 +68,13 @@ public function testCheckRuleHasValue(): void $resultBag = new ResultBag(); $data = []; - $result = $this->sut()->checkRule($request, $resultBag, $this->loggerServiceStub, $data); + $result = $this->sut()->checkRule( + $request, + $resultBag, + $this->loggerServiceStub, + $data, + $this->responseModeStub, + ); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertSame($value, $result->getValue()); @@ -85,6 +94,8 @@ public function testCheckRulePostMethod(): void $request, $resultBag, $this->loggerServiceStub, + [], + $this->responseModeStub, ); $this->assertInstanceOf(ResultInterface::class, $result); diff --git a/tests/unit/src/Server/RequestRules/Rules/UiLocalesRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/UiLocalesRuleTest.php index a4eefd8c..749525c3 100644 --- a/tests/unit/src/Server/RequestRules/Rules/UiLocalesRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/UiLocalesRuleTest.php @@ -11,6 +11,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\UiLocalesRule; +use SimpleSAML\Module\oidc\Server\ResponseModes\ResponseModeInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -24,6 +25,7 @@ class UiLocalesRuleTest extends TestCase protected Stub $loggerServiceStub; protected Stub $requestParamsResolverStub; protected Helpers $helpers; + protected Stub $responseModeStub; /** * @throws \Exception @@ -37,6 +39,7 @@ protected function setUp(): void $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); $this->helpers = new Helpers(); + $this->responseModeStub = $this->createStub(ResponseModeInterface::class); } protected function sut( @@ -59,7 +62,13 @@ public function testCheckRuleReturnsResultWhenParamSet() { $this->requestParamsResolverStub->method('getBasedOnAllowedMethods')->willReturn('en'); - $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ) ?? new Result(UiLocalesRule::class); $this->assertEquals('en', $result->getValue()); @@ -72,7 +81,13 @@ public function testCheckRuleReturnsNullWhenParamNotSet() { $this->requestStub->method('getQueryParams')->willReturn([]); - $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule( + $this->requestStub, + $this->resultBagStub, + $this->loggerServiceStub, + [], + $this->responseModeStub, + ) ?? new Result(UiLocalesRule::class); $this->assertNull($result->getValue()); diff --git a/tests/unit/src/Server/ResponseModes/FormPostResponseModeTest.php b/tests/unit/src/Server/ResponseModes/FormPostResponseModeTest.php new file mode 100644 index 00000000..605a9e34 --- /dev/null +++ b/tests/unit/src/Server/ResponseModes/FormPostResponseModeTest.php @@ -0,0 +1,45 @@ + 'simplesaml/', + 'module.enable' => ['oidc' => true], + ], '', 'simplesaml'); + + $this->sut = new FormPostResponseMode($config); + } + + public function testBuildResponseReturnsHtmlWithFormPost(): void + { + $result = $this->sut->buildResponse( + 'https://example.org/callback', + ['code' => 'abc123', 'state' => 'xyz'], + ); + + $this->assertInstanceOf(HtmlResponse::class, $result); + + $body = (string) $result->generateHttpResponse(new Response())->getBody(); + $this->assertStringContainsString('https://example.org/callback', $body); + $this->assertStringContainsString('abc123', $body); + $this->assertStringContainsString('xyz', $body); + $this->assertMatchesRegularExpression('/method=["\']post["\']/i', $body); + } +} diff --git a/tests/unit/src/Services/OpMetadataServiceTest.php b/tests/unit/src/Services/OpMetadataServiceTest.php index f354b630..9247da12 100644 --- a/tests/unit/src/Services/OpMetadataServiceTest.php +++ b/tests/unit/src/Services/OpMetadataServiceTest.php @@ -142,6 +142,7 @@ public function testItReturnsExpectedMetadata(): void 'acr_values_supported' => ['1'], 'backchannel_logout_supported' => true, 'backchannel_logout_session_supported' => true, + 'response_modes_supported' => ['query', 'fragment', 'form_post'], ], $this->sut()->getMetadata(), );