From d92c803753d9779e92af35c46d68caecf8591e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Sun, 3 Dec 2023 12:26:36 +0100 Subject: [PATCH 01/70] Mark TODO for v6 --- README.md | 2 +- UPGRADE.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b122b509..bee55e56 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Currently supported flows are: [![Build Status](https://github.com/simplesamlphp/simplesamlphp-module-oidc/actions/workflows/test.yaml/badge.svg)](https://github.com/simplesamlphp/simplesamlphp-module-oidc/actions/workflows/test.yaml) [![Coverage Status](https://codecov.io/gh/simplesamlphp/simplesamlphp-module-oidc/branch/master/graph/badge.svg)](https://app.codecov.io/gh/simplesamlphp/simplesamlphp-module-oidc) -[![SimpleSAMLphp](https://img.shields.io/badge/simplesamlphp-1.19-brightgreen)](https://simplesamlphp.org/) +[![SimpleSAMLphp](https://img.shields.io/badge/simplesamlphp-2.1-brightgreen)](https://simplesamlphp.org/) ![Main screen capture](docs/oidc.png) diff --git a/UPGRADE.md b/UPGRADE.md index 13b10812..df1746f7 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,3 +1,33 @@ +# Version 5 to 6 + +## New features +- TODO move away from SSP database as store; move to custom store interface +- TODO key rollover +- TODO token introspection +- TODO implement store for different entities?: i.e. client data can use RDB like mysql, whilst short term data + like tokens can utilize faster stores like memcache, redis... +- TODO move to SimpleSAMLphp ProcessingChain + +## Major impact changes +- TODO move away from SSP database as store; move to custom store interface + +## Medium impact changes +- TODO move to SSP (symfony) routing + - TODO handle CORS + +## Low impact changes + +Below are some internal changes that should not have impact for the OIDC OP implementors. However, if you are using +this module as a library or extending from it, you will probably encounter breaking changes, since a lot of code +has been refactored: + +- TODO upgrade to v5 of lcobucci/jwt https://github.com/lcobucci/jwt +- TODO move checkers to templates (generics) for proper static type handling +- TODO move to SSP (symfony) container +- TODO remove dependency on laminas/laminas-diactoros +- TODO remove dependency on laminas/laminas-httphandlerrunner +- TODO create a bridge towards SSP utility classes, so they can be easily mocked + # Version 4 to 5 ## Major impact changes From 3ce0aba0fd0f1546495ca7fc08621b42e717085d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Tue, 14 May 2024 11:38:28 +0200 Subject: [PATCH 02/70] Add note about upgrading to v9 of oauth2-server --- UPGRADE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADE.md b/UPGRADE.md index df1746f7..15a4fc80 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -21,6 +21,7 @@ Below are some internal changes that should not have impact for the OIDC OP impl this module as a library or extending from it, you will probably encounter breaking changes, since a lot of code has been refactored: +- TODO upgrade to v9 of oauth2-server https://github.com/thephpleague/oauth2-server/releases/tag/9.0.0 - TODO upgrade to v5 of lcobucci/jwt https://github.com/lcobucci/jwt - TODO move checkers to templates (generics) for proper static type handling - TODO move to SSP (symfony) container From 1bf6adfbb8eb0fa080c857d3cc080713ab048d27 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Mon, 20 May 2024 16:19:55 +0200 Subject: [PATCH 03/70] Upgrade lcobucci/jwt to version 5 (#220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Require lcobucci/jwt v5 * Accommodate for new immutable behavior of Builder * Update expected error message for RP initiated logout conformance test * Update upgrade.md --------- Co-authored-by: Marko Ivančić --- UPGRADE.md | 2 +- composer.json | 2 +- .../conformance-rp-initiated-logout-ci.json | 2 +- src/Services/IdTokenBuilder.php | 28 +++++++++---------- src/Services/LogoutTokenBuilder.php | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 15a4fc80..cb907c3d 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -22,7 +22,7 @@ this module as a library or extending from it, you will probably encounter break has been refactored: - TODO upgrade to v9 of oauth2-server https://github.com/thephpleague/oauth2-server/releases/tag/9.0.0 -- TODO upgrade to v5 of lcobucci/jwt https://github.com/lcobucci/jwt +- upgraded to v5 of lcobucci/jwt https://github.com/lcobucci/jwt - TODO move checkers to templates (generics) for proper static type handling - TODO move to SSP (symfony) container - TODO remove dependency on laminas/laminas-diactoros diff --git a/composer.json b/composer.json index 39f8b63b..fa5b9c1d 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "guzzlehttp/guzzle": "^7.0", "laminas/laminas-diactoros": "^2.25.2", "laminas/laminas-httphandlerrunner": "^2", - "lcobucci/jwt": "^4.1", + "lcobucci/jwt": "^5.3", "league/oauth2-server": "^8.5.3", "nette/forms": "^3", "psr/container": "^2.0", diff --git a/conformance-tests/conformance-rp-initiated-logout-ci.json b/conformance-tests/conformance-rp-initiated-logout-ci.json index d33b7a1d..3c600c80 100644 --- a/conformance-tests/conformance-rp-initiated-logout-ci.json +++ b/conformance-tests/conformance-rp-initiated-logout-ci.json @@ -556,7 +556,7 @@ "xpath", "//*", 10, - "Token signer mismatch", + "The JWT string is missing the Signature part", "update-image-placeholder" ] ] diff --git a/src/Services/IdTokenBuilder.php b/src/Services/IdTokenBuilder.php index ad03b38c..d9449dcb 100644 --- a/src/Services/IdTokenBuilder.php +++ b/src/Services/IdTokenBuilder.php @@ -48,15 +48,15 @@ public function build( $builder = $this->getBuilder($accessToken, $userEntity); if (null !== $nonce) { - $builder->withClaim('nonce', $nonce); + $builder = $builder->withClaim('nonce', $nonce); } if (null !== $authTime) { - $builder->withClaim('auth_time', $authTime); + $builder = $builder->withClaim('auth_time', $authTime); } if ($addAccessTokenHash) { - $builder->withClaim( + $builder = $builder->withClaim( 'at_hash', $this->generateAccessTokenHash( $accessToken, @@ -66,11 +66,11 @@ public function build( } if (null !== $acr) { - $builder->withClaim('acr', $acr); + $builder = $builder->withClaim('acr', $acr); } if (null !== $sessionId) { - $builder->withClaim('sid', $sessionId); + $builder = $builder->withClaim('sid', $sessionId); } // Need a claim factory here to reduce the number of claims by provided scope. @@ -92,36 +92,36 @@ public function build( if (is_array($claimValue)) { /** @psalm-suppress MixedAssignment */ foreach ($claimValue as $aud) { - $builder->permittedFor((string)$aud); + $builder = $builder->permittedFor((string)$aud); } } else { - $builder->permittedFor((string)$claimValue); + $builder = $builder->permittedFor((string)$claimValue); } break; case RegisteredClaims::EXPIRATION_TIME: /** @noinspection PhpUnnecessaryStringCastInspection */ - $builder->expiresAt(new DateTimeImmutable('@' . (string)$claimValue)); + $builder = $builder->expiresAt(new DateTimeImmutable('@' . (string)$claimValue)); break; case RegisteredClaims::ID: - $builder->identifiedBy((string)$claimValue); + $builder = $builder->identifiedBy((string)$claimValue); break; case RegisteredClaims::ISSUED_AT: /** @noinspection PhpUnnecessaryStringCastInspection */ - $builder->issuedAt(new DateTimeImmutable('@' . (string)$claimValue)); + $builder = $builder->issuedAt(new DateTimeImmutable('@' . (string)$claimValue)); break; case RegisteredClaims::ISSUER: - $builder->issuedBy((string)$claimValue); + $builder = $builder->issuedBy((string)$claimValue); break; case RegisteredClaims::NOT_BEFORE: /** @noinspection PhpUnnecessaryStringCastInspection */ - $builder->canOnlyBeUsedAfter(new DateTimeImmutable('@' . (string)$claimValue)); + $builder = $builder->canOnlyBeUsedAfter(new DateTimeImmutable('@' . (string)$claimValue)); break; case RegisteredClaims::SUBJECT: - $builder->relatedTo((string)$claimValue); + $builder = $builder->relatedTo((string)$claimValue); break; default: if ($addClaimsFromScopes || array_key_exists($claimName, $additionalClaims)) { - $builder->withClaim($claimName, $claimValue); + $builder = $builder->withClaim($claimName, $claimValue); } } } diff --git a/src/Services/LogoutTokenBuilder.php b/src/Services/LogoutTokenBuilder.php index 23c9746a..4e06f846 100644 --- a/src/Services/LogoutTokenBuilder.php +++ b/src/Services/LogoutTokenBuilder.php @@ -30,7 +30,7 @@ public function forRelyingPartyAssociation(RelyingPartyAssociationInterface $rel ; if ($relyingPartyAssociation->getSessionId() !== null) { - $logoutTokenBuilder->withClaim('sid', $relyingPartyAssociation->getSessionId()); + $logoutTokenBuilder = $logoutTokenBuilder->withClaim('sid', $relyingPartyAssociation->getSessionId()); } return $this->jsonWebTokenBuilderService->getSignedJwtTokenFromBuilder($logoutTokenBuilder)->toString(); From 89f5941d3995e960584af6ffdacfafa6de2f8e37 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Mon, 20 May 2024 17:03:06 +0200 Subject: [PATCH 04/70] Fix deprecations (#221) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix psalm errors * Use {} instead of * Use v3 everywhere for actions/cache * Add PHP v8.3 to tests * Enclose key in quotes to suppress unexpected dash warning --------- Co-authored-by: Marko Ivančić --- .github/workflows/test.yaml | 12 ++++++------ src/Entities/AccessTokenEntity.php | 2 +- src/Server/Validators/BearerTokenValidator.php | 2 +- src/Services/DatabaseMigration.php | 18 +++++++++--------- src/Services/IdTokenBuilder.php | 2 ++ src/Services/JsonWebTokenBuilderService.php | 3 +++ src/Services/LogoutTokenBuilder.php | 1 + src/Utils/Checker/Rules/IdTokenHintRule.php | 10 ++++++++++ 8 files changed, 33 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c1c02764..7d6ac93a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ["8.1", "8.2"] + php-versions: ["8.1", "8.2", "8.3"] steps: - name: Setup PHP, with composer and extensions @@ -45,7 +45,7 @@ jobs: uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + key: "${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}" restore-keys: ${{ runner.os }}-composer- - name: Validate composer.json and composer.lock @@ -93,10 +93,10 @@ jobs: run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + key: "${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}" restore-keys: ${{ runner.os }}-composer- - name: Install Composer dependencies @@ -137,7 +137,7 @@ jobs: uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + key: "${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}" restore-keys: ${{ runner.os }}-composer- - name: Install Composer dependencies @@ -169,7 +169,7 @@ jobs: uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + key: "${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}" restore-keys: ${{ runner.os }}-composer- - name: Install Composer dependencies diff --git a/src/Entities/AccessTokenEntity.php b/src/Entities/AccessTokenEntity.php index 0a1c4d86..88aaf0a2 100644 --- a/src/Entities/AccessTokenEntity.php +++ b/src/Entities/AccessTokenEntity.php @@ -201,7 +201,7 @@ public function toString(): ?string protected function convertToJWT(): Token { $jwtBuilderService = new JsonWebTokenBuilderService(); - + /** @psalm-suppress ArgumentTypeCoercion */ $jwtBuilder = $jwtBuilderService->getDefaultJwtTokenBuilder() ->permittedFor($this->getClient()->getIdentifier()) ->identifiedBy((string)$this->getIdentifier()) diff --git a/src/Server/Validators/BearerTokenValidator.php b/src/Server/Validators/BearerTokenValidator.php index ede61f87..e6d267c4 100644 --- a/src/Server/Validators/BearerTokenValidator.php +++ b/src/Server/Validators/BearerTokenValidator.php @@ -115,7 +115,7 @@ public function validateAuthorization(ServerRequestInterface $request): ServerRe $jwt = $parsedBody['access_token']; } - if (!is_string($jwt)) { + if (!is_string($jwt) || empty($jwt)) { throw OidcServerException::accessDenied('Missing Authorization header or access_token request body param.'); } diff --git a/src/Services/DatabaseMigration.php b/src/Services/DatabaseMigration.php index f3d5da2a..b0ce1681 100644 --- a/src/Services/DatabaseMigration.php +++ b/src/Services/DatabaseMigration.php @@ -218,7 +218,7 @@ private function version20180425203400(): void { $clientTableName = $this->database->applyPrefix(ClientRepository::TABLE_NAME); $this->database->write(<<< EOT - ALTER TABLE ${clientTableName} + ALTER TABLE {$clientTableName} ADD is_enabled BOOLEAN NOT NULL DEFAULT true EOT ); @@ -228,7 +228,7 @@ private function version20200517071100(): void { $clientTableName = $this->database->applyPrefix(ClientRepository::TABLE_NAME); $this->database->write(<<< EOT - ALTER TABLE ${clientTableName} + ALTER TABLE {$clientTableName} ADD is_confidential BOOLEAN NOT NULL DEFAULT false EOT ); @@ -238,7 +238,7 @@ private function version20200901163000(): void { $clientTableName = $this->database->applyPrefix(AuthCodeRepository::TABLE_NAME); $this->database->write(<<< EOT - ALTER TABLE ${clientTableName} + ALTER TABLE {$clientTableName} ADD nonce TEXT NULL EOT ); @@ -248,7 +248,7 @@ private function version20210902113500(): void { $clientTableName = $this->database->applyPrefix(ClientRepository::TABLE_NAME); $this->database->write(<<< EOT - ALTER TABLE ${clientTableName} + ALTER TABLE {$clientTableName} ADD owner VARCHAR(191) NULL EOT ); @@ -261,14 +261,14 @@ protected function version20210714113000(): void { $tableName = $this->database->applyPrefix(AccessTokenRepository::TABLE_NAME); $this->database->write(<<< EOT - ALTER TABLE ${tableName} + ALTER TABLE {$tableName} ADD auth_code_id VARCHAR(191) NULL EOT ); $tableName = $this->database->applyPrefix(RefreshTokenRepository::TABLE_NAME); $this->database->write(<<< EOT - ALTER TABLE ${tableName} + ALTER TABLE {$tableName} ADD auth_code_id VARCHAR(191) NULL EOT ); @@ -281,7 +281,7 @@ protected function version20210823141300(): void { $tableName = $this->database->applyPrefix(AccessTokenRepository::TABLE_NAME); $this->database->write(<<< EOT - ALTER TABLE ${tableName} + ALTER TABLE {$tableName} ADD requested_claims TEXT NULL EOT ); @@ -316,7 +316,7 @@ protected function version20210908143500(): void { $clientTableName = $this->database->applyPrefix(ClientRepository::TABLE_NAME); $this->database->write(<<< EOT - ALTER TABLE ${clientTableName} + ALTER TABLE {$clientTableName} ADD post_logout_redirect_uri TEXT NULL EOT ); @@ -329,7 +329,7 @@ protected function version20210916153400(): void { $clientTableName = $this->database->applyPrefix(ClientRepository::TABLE_NAME); $this->database->write(<<< EOT - ALTER TABLE ${clientTableName} + ALTER TABLE {$clientTableName} ADD backchannel_logout_uri TEXT NULL EOT ); diff --git a/src/Services/IdTokenBuilder.php b/src/Services/IdTokenBuilder.php index d9449dcb..eb8c9f39 100644 --- a/src/Services/IdTokenBuilder.php +++ b/src/Services/IdTokenBuilder.php @@ -29,6 +29,7 @@ public function __construct( /** * @throws Exception + * @psalm-suppress ArgumentTypeCoercion */ public function build( UserEntityInterface $userEntity, @@ -136,6 +137,7 @@ protected function getBuilder( AccessTokenEntityInterface $accessToken, UserEntityInterface $userEntity ): Builder { + /** @psalm-suppress ArgumentTypeCoercion */ return $this->jsonWebTokenBuilderService ->getDefaultJwtTokenBuilder() ->permittedFor($accessToken->getClient()->getIdentifier()) diff --git a/src/Services/JsonWebTokenBuilderService.php b/src/Services/JsonWebTokenBuilderService.php index fff9e447..42b1de79 100644 --- a/src/Services/JsonWebTokenBuilderService.php +++ b/src/Services/JsonWebTokenBuilderService.php @@ -25,6 +25,8 @@ class JsonWebTokenBuilderService /** * @throws ReflectionException * @throws Exception + * + * @psalm-suppress ArgumentTypeCoercion */ public function __construct( protected ModuleConfig $moduleConfig = new ModuleConfig() @@ -44,6 +46,7 @@ public function __construct( */ public function getDefaultJwtTokenBuilder(): Builder { + /** @psalm-suppress ArgumentTypeCoercion */ // Ignore microseconds when handling dates. return $this->jwtConfig->builder(ChainedFormatter::withUnixTimestampDates()) ->issuedBy($this->moduleConfig->getSimpleSAMLSelfURLHost()) diff --git a/src/Services/LogoutTokenBuilder.php b/src/Services/LogoutTokenBuilder.php index 4e06f846..81ab2ff6 100644 --- a/src/Services/LogoutTokenBuilder.php +++ b/src/Services/LogoutTokenBuilder.php @@ -18,6 +18,7 @@ public function __construct( /** * @throws OAuthServerException|Exception + * @psalm-suppress ArgumentTypeCoercion */ public function forRelyingPartyAssociation(RelyingPartyAssociationInterface $relyingPartyAssociation): string { diff --git a/src/Utils/Checker/Rules/IdTokenHintRule.php b/src/Utils/Checker/Rules/IdTokenHintRule.php index e0f65ab2..db215338 100644 --- a/src/Utils/Checker/Rules/IdTokenHintRule.php +++ b/src/Utils/Checker/Rules/IdTokenHintRule.php @@ -62,6 +62,16 @@ public function checkRule( InMemory::plainText($publicKey->getKeyContents()) ); + if (empty($idTokenHintParam)) { + throw OidcServerException::invalidRequest( + 'id_token_hint', + 'Received empty id_token_hint', + null, + null, + $state + ); + } + try { /** @var UnencryptedToken $idTokenHint */ $idTokenHint = $jwtConfig->parser()->parse($idTokenHintParam); From 3c3e89b136afad5ee63f07b944446b132f0a5148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Thu, 23 May 2024 17:54:35 +0200 Subject: [PATCH 05/70] Fix phpcs --- src/Utils/Checker/Rules/IdTokenHintRule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utils/Checker/Rules/IdTokenHintRule.php b/src/Utils/Checker/Rules/IdTokenHintRule.php index a8d7da82..19397fb1 100644 --- a/src/Utils/Checker/Rules/IdTokenHintRule.php +++ b/src/Utils/Checker/Rules/IdTokenHintRule.php @@ -68,7 +68,7 @@ public function checkRule( 'Received empty id_token_hint', null, null, - $state + $state, ); } From 0dcd26193c922fa0bba876ce0dc794bef8c144d1 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Fri, 24 May 2024 13:51:14 +0200 Subject: [PATCH 06/70] Initial federation entity configuration endpoint implementation (#222) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Start with EntityStatementController * Refactor JWT and JWKS builders to support OpenID Federation PKI configuration * Cover ModuleConfig with unit tests * Stop using NotFound exception bc it is imposibble to mock * Start with SimpleSAMLphp bridge classes * Start using enums for common options, keys... * Bump PHP version requirement to v8.2 * Start testing on SSP v2.2 * Initial federation entity configuration endpoint implementation --------- Co-authored-by: Marko Ivančić --- .github/workflows/test.yaml | 12 +- README.md | 3 +- UPGRADE.md | 18 + composer.json | 4 +- config-templates/module_oidc.php | 57 +- docker/Dockerfile | 4 +- routing/routes/routes.yml | 8 +- src/Bridges/SspBridge.php | 28 + src/Bridges/SspBridge/Module.php | 13 + src/Bridges/SspBridge/Utils.php | 24 + src/Codebooks/ClaimNamesEnum.php | 23 + .../ClaimValues/PublicKeyUseEnum.php | 11 + src/Codebooks/ClaimValues/TypeEnum.php | 10 + src/Codebooks/EntityTypeEnum.php | 11 + src/Codebooks/ScopesEnum.php | 15 + .../Federation/EntityStatementController.php | 102 ++ src/Controller/JwksController.php | 2 +- ...AuthenticatedGetClientFromRequestTrait.php | 2 +- src/Entities/AccessTokenEntity.php | 5 +- src/Factories/CryptKeyFactory.php | 6 +- src/Forms/ClientForm.php | 1 - src/ModuleConfig.php | 231 ++- src/Services/IdTokenBuilder.php | 6 +- src/Services/JsonWebKeySetService.php | 52 +- src/Services/JsonWebTokenBuilderService.php | 128 +- src/Services/LogoutTokenBuilder.php | 4 +- src/Services/OpMetadataService.php | 16 +- src/Utils/Checker/Rules/IdTokenHintRule.php | 6 +- src/Utils/TimestampGenerator.php | 9 + src/Utils/UniqueIdentifierGenerator.php | 5 +- tests/config/config.php | 1306 +++++++++++++++++ tests/config/module_oidc.php | 19 + .../Controller/Client/EditControllerTest.php | 2 +- .../Client/ResetSecretControllerTest.php | 10 +- .../Controller/Client/ShowControllerTest.php | 9 +- tests/src/Controller/JwksControllerTest.php | 2 +- tests/src/Entities/AccessTokenEntityTest.php | 7 - tests/src/ModuleConfigTest.php | 290 +++- .../ResponseTypes/IdTokenResponseTest.php | 10 +- .../src/Services/JsonWebKeySetServiceTest.php | 4 +- .../JsonWebTokenBuilderServiceTest.php | 28 +- tests/src/Services/LogoutTokenBuilderTest.php | 20 +- tests/src/Services/OpMetadataServiceTest.php | 9 +- .../Checker/Rules/IdTokenHintRuleTest.php | 10 +- 44 files changed, 2329 insertions(+), 213 deletions(-) create mode 100644 src/Bridges/SspBridge.php create mode 100644 src/Bridges/SspBridge/Module.php create mode 100644 src/Bridges/SspBridge/Utils.php create mode 100644 src/Codebooks/ClaimNamesEnum.php create mode 100644 src/Codebooks/ClaimValues/PublicKeyUseEnum.php create mode 100644 src/Codebooks/ClaimValues/TypeEnum.php create mode 100644 src/Codebooks/EntityTypeEnum.php create mode 100644 src/Codebooks/ScopesEnum.php create mode 100644 src/Controller/Federation/EntityStatementController.php create mode 100644 tests/config/config.php diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e0675576..aefe3726 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ["8.1", "8.2", "8.3"] + php-versions: ["8.2", "8.3"] steps: - name: Setup PHP, with composer and extensions @@ -55,7 +55,7 @@ jobs: run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Decide whether to run code coverage or not - if: ${{ matrix.php-versions != '8.1' }} + if: ${{ matrix.php-versions != '8.2' }} run: | echo "NO_COVERAGE=--no-coverage" >> $GITHUB_ENV @@ -65,7 +65,7 @@ jobs: ./vendor/bin/phpunit $NO_COVERAGE - name: Save coverage data - if: ${{ matrix.php-versions == '8.1' }} + if: ${{ matrix.php-versions == '8.2' }} uses: actions/upload-artifact@v4 with: name: build-data @@ -78,7 +78,7 @@ jobs: - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php with: - php-version: "8.1" + php-version: "8.2" extensions: mbstring, xml tools: composer:v2 coverage: none @@ -119,7 +119,7 @@ jobs: - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php with: - php-version: "8.1" + php-version: "8.2" extensions: mbstring, xml tools: composer:v2 coverage: none @@ -152,7 +152,7 @@ jobs: - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php with: - php-version: "8.1" + php-version: "8.2" tools: composer:v2 extensions: mbstring, xml diff --git a/README.md b/README.md index 0faad19b..88abcee7 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ Currently supported flows are: | OIDC module | SimpleSAMLphp | PHP | Note | |:------------|:--------------|:------:|-----------------------------| -| v5.\* | v2.1.\* | \>=8.1 | Recommended | +| v6.\* | v2.2.\* | \>=8.2 | Recommended | +| v5.\* | v2.1.\* | \>=8.1 | | | v4.\* | v2.0.\* | \>=8.0 | | | v3.\* | v2.0.\* | \>=7.4 | Abandoned from August 2023. | | v2.\* | v1.19.\* | \>=7.4 | | diff --git a/UPGRADE.md b/UPGRADE.md index cb907c3d..6c605d29 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -7,8 +7,26 @@ - TODO implement store for different entities?: i.e. client data can use RDB like mysql, whilst short term data like tokens can utilize faster stores like memcache, redis... - TODO move to SimpleSAMLphp ProcessingChain +- TODO OpenID Federation capabilities + - [ ] Expose OP configuration entity statement (statement about itself) + +## New configuration options +- (optional) Issuer - you can now override the issuer (OP identifier). If not set, it falls back to current scheme, host +and optionally a port (as in all previous module versions). +- (optional) OpenID Federation related options (needed if federation capabilities are to be used): + - PKI keys - federation keys used for example to sign federation entity statements + - signer algorithm + - entity statement duration + - authority hints + - organization name + - contacts + - logo URI + - policy URI + - homepage URI ## Major impact changes +- PHP version requirement was bumped to v8.2 +- SimpleSAMLphp version requirement was bumped to v2.2 - TODO move away from SSP database as store; move to custom store interface ## Medium impact changes diff --git a/composer.json b/composer.json index fa5b9c1d..788debc0 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-curl": "*", "ext-json": "*", "ext-openssl": "*", @@ -39,7 +39,7 @@ "friendsofphp/php-cs-fixer": "^3", "phpunit/phpunit": "^10", "rector/rector": "^0.18.3", - "simplesamlphp/simplesamlphp": "2.1.*", + "simplesamlphp/simplesamlphp": "2.2.*", "simplesamlphp/simplesamlphp-test-framework": "^1.5", "squizlabs/php_codesniffer": "^3", "vimeo/psalm": "^5" diff --git a/config-templates/module_oidc.php b/config-templates/module_oidc.php index 5b052025..e7d66624 100644 --- a/config-templates/module_oidc.php +++ b/config-templates/module_oidc.php @@ -21,11 +21,21 @@ */ $config = [ /** - * PKI (public / private key) related options. + * (optional) Issuer (OP) identifier which will be used as an issuer (iss) claim in tokens. If not set, it will + * fall back to current HTTP scheme, host and port number if no standard port is used. + * Description of issuer from OIDC Core specification: "Verifiable Identifier for an Issuer. An Issuer Identifier + * is a case-sensitive URL using the https scheme that contains scheme, host, and optionally, port number and + * path components and no query or fragment components." */ - // The private key passphrase (optional). + //ModuleConfig::OPTION_ISSUER => 'https://op.example.org', + + /** + * PKI (public / private key) settings related to OIDC protocol. These keys will be used, for example, to + * sign ID Token JWT. + */ + // (optional) The private key passphrase. //ModuleConfig::OPTION_PKI_PRIVATE_KEY_PASSPHRASE => 'secret', - // The certificate and private key filenames for ID token signature handling, with given defaults. + // The certificate and private key filenames, with given defaults. ModuleConfig::OPTION_PKI_PRIVATE_KEY_FILENAME => ModuleConfig::DEFAULT_PKI_PRIVATE_KEY_FILENAME, ModuleConfig::OPTION_PKI_CERTIFICATE_FILENAME => ModuleConfig::DEFAULT_PKI_CERTIFICATE_FILENAME, @@ -263,4 +273,45 @@ // Pagination options. ModuleConfig::OPTION_ADMIN_UI_PAGINATION_ITEMS_PER_PAGE => 20, + + /** + * (optional) OpenID Federation related options. If these are not set, OpenID Federation capabilities will be + * disabled. + */ + + /** + * PKI settings related to OpenID Federation. These keys will be used, for example, to sign federation + * entity statements. Note that these keys SHOULD NOT be the same as the ones used in OIDC protocol itself. + */ + // The federation private key passphrase (optional). + //ModuleConfig::OPTION_PKI_FEDERATION_PRIVATE_KEY_PASSPHRASE => 'secret', + // The federation certificate and private key filenames, with given defaults. + //ModuleConfig::OPTION_PKI_FEDERATION_PRIVATE_KEY_FILENAME => + // ModuleConfig::DEFAULT_PKI_FEDERATION_PRIVATE_KEY_FILENAME, + //ModuleConfig::OPTION_PKI_FEDERATION_CERTIFICATE_FILENAME => + // ModuleConfig::DEFAULT_PKI_FEDERATION_CERTIFICATE_FILENAME, + + // Federation token signer, with given default. + //ModuleConfig::OPTION_FEDERATION_TOKEN_SIGNER => \Lcobucci\JWT\Signer\Rsa\Sha256::class, + + // Federation entity statement duration which determines the Expiration Time (exp) claim set in entity + // statement JWTs. If not set, default of 1 day will be used. For duration format info, check + // https://www.php.net/manual/en/dateinterval.construct.php + //ModuleConfig::OPTION_FEDERATION_ENTITY_STATEMENT_DURATION => 'P1D', // 1 day + + // Federation authority hints. An array of strings representing the Entity Identifiers of Intermediate Entities + // or Trust Anchors. Required if this entity has a Superior entity above it. + ModuleConfig::OPTION_FEDERATION_AUTHORITY_HINTS => [ + //'https://edugain.org/federation', + ], + + // Common federation entity parameters: + // https://openid.net/specs/openid-federation-1_0.html#name-common-metadata-parameters + ModuleConfig::OPTION_ORGANIZATION_NAME => null, + ModuleConfig::OPTION_CONTACTS => [ + // 'John Doe jdoe@example.org', + ], + ModuleConfig::OPTION_LOGO_URI => null, + ModuleConfig::OPTION_POLICY_URI => null, + ModuleConfig::OPTION_HOMEPAGE_URI => null, ]; diff --git a/docker/Dockerfile b/docker/Dockerfile index 18946657..43369845 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ -#FROM cirrusid/simplesamlphp:v2.0.0 -FROM cicnavi/simplesamlphp:dev-simplesamlphp-2.1 +FROM cirrusid/simplesamlphp:v2.2.2 +#FROM cicnavi/simplesamlphp:dev-simplesamlphp-2.1 RUN apt-get update && apt-get install -y sqlite3 # Prepopulate the DB with items needed for testing diff --git a/routing/routes/routes.yml b/routing/routes/routes.yml index 0abd5cb5..ecd5a6f8 100644 --- a/routing/routes/routes.yml +++ b/routing/routes/routes.yml @@ -6,4 +6,10 @@ openid-configuration: path: openid-configuration - controller: SimpleSAML\Module\oidc\Controller\ConfigurationDiscoveryController \ No newline at end of file + controller: SimpleSAML\Module\oidc\Controller\ConfigurationDiscoveryController + +# Federation related routes +# https://openid.net/specs/openid-federation-1_0.html#name-federation-entity-configura +openid-federation-entity-statement-configuration: + path: openid-federation + controller: SimpleSAML\Module\oidc\Controller\Federation\EntityStatementController::configuration diff --git a/src/Bridges/SspBridge.php b/src/Bridges/SspBridge.php new file mode 100644 index 00000000..1d0a6173 --- /dev/null +++ b/src/Bridges/SspBridge.php @@ -0,0 +1,28 @@ +jsonWebTokenBuilderService->getFederationJwtBuilder() + ->withHeader(ClaimNamesEnum::Type->value, TypeEnum::EntityStatementJwt->value) + ->relatedTo($this->moduleConfig->getIssuer()) // This is entity configuration (statement about itself). + ->expiresAt( + (TimestampGenerator::utcImmutable())->add($this->moduleConfig->getFederationEntityStatementDuration()), + )->withClaim( + ClaimNamesEnum::JsonWebKeySet->value, + ['keys' => array_values($this->jsonWebKeySetService->federationKeys()),], + ) + ->withClaim( + ClaimNamesEnum::Metadata->value, + [ + EntityTypeEnum::FederationEntity->value => [ + // Common https://openid.net/specs/openid-federation-1_0.html#name-common-metadata-parameters + ...(array_filter( + [ + ClaimNamesEnum::OrganizationName->value => $this->moduleConfig->getOrganizationName(), + ClaimNamesEnum::Contacts->value => $this->moduleConfig->getContacts(), + ClaimNamesEnum::LogoUri->value => $this->moduleConfig->getLogoUri(), + ClaimNamesEnum::PolicyUri->value => $this->moduleConfig->getPolicyUri(), + ClaimNamesEnum::HomepageUri->value => $this->moduleConfig->getHomepageUri(), + ], + )), + // TODO mivanci Add when ready. Use ClaimsEnum for keys. + // https://openid.net/specs/openid-federation-1_0.html#name-federation-entity + //'federation_fetch_endpoint', + //'federation_list_endpoint', + //'federation_resolve_endpoint', + //'federation_trust_mark_status_endpoint', + //'federation_trust_mark_list_endpoint', + //'federation_trust_mark_endpoint', + //'federation_historical_keys_endpoint', + // Common https://openid.net/specs/openid-federation-1_0.html#name-common-metadata-parameters + //'signed_jwks_uri', + //'jwks_uri', + //'jwks', + ], + // TODO mivanci expand OP metadata with federation related claims. + EntityTypeEnum::OpenIdProvider->value => $this->opMetadataService->getMetadata(), + ], + ); + + if ( + is_array($authorityHints = $this->moduleConfig->getFederationAuthorityHints()) && + (!empty($authorityHints)) + ) { + $builder = $builder->withClaim(ClaimNamesEnum::AuthorityHints->value, $authorityHints); + } + + // TODO mivanci Add remaining claims when ready. + // * crit + // * trust_marks + // * trust_mark_issuers + // * source_endpoint + + // Note: claims which should only be present in Trust Anchors + // * trust_mark_owners + + + // Note: claims which must not be present in entity configuration: + // * metadata_policy + // * constraints + // * metadata_policy_crit + + $jws = $this->jsonWebTokenBuilderService->getSignedFederationJwt($builder); + return new Response($jws->toString()); + } +} diff --git a/src/Controller/JwksController.php b/src/Controller/JwksController.php index ed951baa..c3a44a14 100644 --- a/src/Controller/JwksController.php +++ b/src/Controller/JwksController.php @@ -28,7 +28,7 @@ public function __construct(private readonly JsonWebKeySetService $jsonWebKeySet public function __invoke(): JsonResponse { return new JsonResponse([ - 'keys' => array_values($this->jsonWebKeySetService->keys()), + 'keys' => array_values($this->jsonWebKeySetService->protocolKeys()), ]); } } diff --git a/src/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php b/src/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php index 9ca2dacf..792c6e22 100644 --- a/src/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php +++ b/src/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php @@ -49,7 +49,7 @@ protected function getClientFromRequest(ServerRequestInterface $request): Client } $client = $this->clientRepository->findById($clientId, $authedUser); if (!$client) { - throw new NotFound('Client not found.'); + throw OidcServerException::invalidClient($request); } return $client; diff --git a/src/Entities/AccessTokenEntity.php b/src/Entities/AccessTokenEntity.php index f8b39d0e..fb8755ea 100644 --- a/src/Entities/AccessTokenEntity.php +++ b/src/Entities/AccessTokenEntity.php @@ -117,6 +117,7 @@ public static function fromState(array $state): self $accessToken->identifier = $state['id']; $accessToken->scopes = $scopes; + // TODO mivanci move to new 'utcImmutable' method in TimestampGenerator. $accessToken->expiryDateTime = DateTimeImmutable::createFromMutable( TimestampGenerator::utc($state['expires_at']), ); @@ -202,7 +203,7 @@ protected function convertToJWT(): Token { $jwtBuilderService = new JsonWebTokenBuilderService(); /** @psalm-suppress ArgumentTypeCoercion */ - $jwtBuilder = $jwtBuilderService->getDefaultJwtTokenBuilder() + $jwtBuilder = $jwtBuilderService->getProtocolJwtBuilder() ->permittedFor($this->getClient()->getIdentifier()) ->identifiedBy((string)$this->getIdentifier()) ->issuedAt(new DateTimeImmutable()) @@ -211,6 +212,6 @@ protected function convertToJWT(): Token ->relatedTo((string) $this->getUserIdentifier()) ->withClaim('scopes', $this->getScopes()); - return $jwtBuilderService->getSignedJwtTokenFromBuilder($jwtBuilder); + return $jwtBuilderService->getSignedProtocolJwt($jwtBuilder); } } diff --git a/src/Factories/CryptKeyFactory.php b/src/Factories/CryptKeyFactory.php index 6fd38f71..a7cc02ea 100644 --- a/src/Factories/CryptKeyFactory.php +++ b/src/Factories/CryptKeyFactory.php @@ -20,8 +20,8 @@ public function __construct( public function buildPrivateKey(): CryptKey { return new CryptKey( - $this->moduleConfig->getPrivateKeyPath(), - $this->moduleConfig->getPrivateKeyPassPhrase(), + $this->moduleConfig->getProtocolPrivateKeyPath(), + $this->moduleConfig->getProtocolPrivateKeyPassPhrase(), ); } @@ -30,6 +30,6 @@ public function buildPrivateKey(): CryptKey */ public function buildPublicKey(): CryptKey { - return new CryptKey($this->moduleConfig->getCertPath()); + return new CryptKey($this->moduleConfig->getProtocolCertPath()); } } diff --git a/src/Forms/ClientForm.php b/src/Forms/ClientForm.php index 4c2462e9..b01a4e57 100644 --- a/src/Forms/ClientForm.php +++ b/src/Forms/ClientForm.php @@ -17,7 +17,6 @@ namespace SimpleSAML\Module\oidc\Forms; use Exception; -use Nette\Forms\Container; use Nette\Forms\Form; use SimpleSAML\Auth\Source; use SimpleSAML\Module\oidc\ModuleConfig; diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php index 85ccd475..e6d1e44b 100644 --- a/src/ModuleConfig.php +++ b/src/ModuleConfig.php @@ -16,19 +16,23 @@ namespace SimpleSAML\Module\oidc; +use DateInterval; use Exception; use Lcobucci\JWT\Signer; use Lcobucci\JWT\Signer\Rsa\Sha256; use ReflectionClass; use ReflectionException; +use SimpleSAML\Module\oidc\Bridges\SspBridge; use SimpleSAML\Configuration; use SimpleSAML\Error\ConfigurationError; -use SimpleSAML\Module; -use SimpleSAML\Utils\Config; -use SimpleSAML\Utils\HTTP; +use SimpleSAML\Module\oidc\Codebooks\ScopesEnum; +use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; class ModuleConfig { + final public const MODULE_NAME = 'oidc'; + protected const KEY_DESCRIPTION = 'description'; + /** * Default file name for module configuration. Can be overridden in constructor, for example, for testing purposes. */ @@ -54,26 +58,39 @@ class ModuleConfig final public const OPTION_CRON_TAG = 'cron_tag'; final public const OPTION_ADMIN_UI_PERMISSIONS = 'permissions'; final public const OPTION_ADMIN_UI_PAGINATION_ITEMS_PER_PAGE = 'items_per_page'; - - protected static array $standardClaims = [ - // TODO mivanci Move registered scopes to enum? - 'openid' => [ - 'description' => 'openid', + final public const OPTION_FEDERATION_TOKEN_SIGNER = 'federation_token_signer'; + final public const OPTION_PKI_FEDERATION_PRIVATE_KEY_PASSPHRASE = 'federation_private_key_passphrase'; + final public const OPTION_PKI_FEDERATION_PRIVATE_KEY_FILENAME = 'federation_private_key_filename'; + final public const DEFAULT_PKI_FEDERATION_PRIVATE_KEY_FILENAME = 'oidc_module_federation.key'; + final public const OPTION_PKI_FEDERATION_CERTIFICATE_FILENAME = 'federation_certificate_filename'; + final public const DEFAULT_PKI_FEDERATION_CERTIFICATE_FILENAME = 'oidc_module_federation.crt'; + final public const OPTION_ISSUER = 'issuer'; + final public const OPTION_FEDERATION_ENTITY_STATEMENT_DURATION = 'federation_entity_statement_duration'; + final public const OPTION_FEDERATION_AUTHORITY_HINTS = 'federation_authority_hints'; + final public const OPTION_ORGANIZATION_NAME = 'organization_name'; + final public const OPTION_CONTACTS = 'contacts'; + final public const OPTION_LOGO_URI = 'logo_uri'; + final public const OPTION_POLICY_URI = 'policy_uri'; + final public const OPTION_HOMEPAGE_URI = 'homepage_uri'; + + protected static array $standardScopes = [ + ScopesEnum::OpenId->value => [ + self::KEY_DESCRIPTION => 'openid', ], - 'offline_access' => [ - 'description' => 'offline_access', + ScopesEnum::OfflineAccess->value => [ + self::KEY_DESCRIPTION => 'offline_access', ], - 'profile' => [ - 'description' => 'profile', + ScopesEnum::Profile->value => [ + self::KEY_DESCRIPTION => 'profile', ], - 'email' => [ - 'description' => 'email', + ScopesEnum::Email->value => [ + self::KEY_DESCRIPTION => 'email', ], - 'address' => [ - 'description' => 'address', + ScopesEnum::Address->value => [ + self::KEY_DESCRIPTION => 'address', ], - 'phone' => [ - 'description' => 'phone', + ScopesEnum::Phone->value => [ + self::KEY_DESCRIPTION => 'phone', ], ]; @@ -92,18 +109,20 @@ class ModuleConfig public function __construct( string $fileName = self::DEFAULT_FILE_NAME, // Primarily used for easy (unit) testing overrides. array $overrides = [], // Primarily used for easy (unit) testing overrides. + Configuration $sspConfig = null, + private readonly SspBridge $sspBridge = new SspBridge(), ) { $this->moduleConfig = Configuration::loadFromArray( array_merge(Configuration::getConfig($fileName)->toArray(), $overrides), ); - $this->sspConfig = Configuration::getInstance(); + $this->sspConfig = $sspConfig ?? Configuration::getInstance(); $this->validate(); } /** - * @throws Exception + * Get SimpleSAMLphp Configuration (config.php) instance. */ public function sspConfig(): Configuration { @@ -111,23 +130,31 @@ public function sspConfig(): Configuration } /** - * @throws Exception + * Get module config Configuration instance. */ public function config(): Configuration { return $this->moduleConfig; } - public function getSimpleSAMLSelfURLHost(): string + /** + * @throws OidcServerException + * @return non-empty-string + */ + public function getIssuer(): string { - // TODO mivanci Create bridge to SSP utility classes - return (new HTTP())->getSelfURLHost(); + $issuer = $this->config()->getOptionalString(self::OPTION_ISSUER, null) ?? + $this->sspBridge->utils()->http()->getSelfURLHost(); + + if (empty($issuer)) { + throw OidcServerException::serverError('Issuer can not be empty.'); + } + return $issuer; } - public function getOpenIdConnectModuleURL(string $path = null): string + public function getModuleUrl(string $path = null): string { - // TODO mivanci Create bridge to SSP utility classes - $base = Module::getModuleURL('oidc'); + $base = $this->sspBridge->module()->getModuleURL(self::MODULE_NAME); if ($path) { $base .= "/$path"; @@ -141,7 +168,7 @@ public function getOpenIdConnectModuleURL(string $path = null): string */ public function getOpenIDScopes(): array { - return array_merge(self::$standardClaims, $this->getOpenIDPrivateScopes()); + return array_merge(self::$standardScopes, $this->getOpenIDPrivateScopes()); } /** @@ -167,7 +194,7 @@ private function validate(): void * @throws ConfigurationError */ function (array $scope, string $name): void { - if (in_array($name, array_keys(self::$standardClaims), true)) { + if (in_array($name, array_keys(self::$standardScopes), true)) { throw new ConfigurationError( 'Can not overwrite protected scope: ' . $name, self::DEFAULT_FILE_NAME, @@ -226,10 +253,12 @@ function (array $scope, string $name): void { } /** + * Get signer for OIDC protocol. + * * @throws ReflectionException * @throws Exception */ - public function getSigner(): Signer + public function getProtocolSigner(): Signer { /** @psalm-var class-string $signerClassname */ $signerClassname = $this->config()->getOptionalString( @@ -237,56 +266,59 @@ public function getSigner(): Signer Sha256::class, ); - $class = new ReflectionClass($signerClassname); + return $this->instantiateSigner($signerClassname); + } + + /** + * @param class-string $className + * @throws \SimpleSAML\Error\ConfigurationError + * @throws ReflectionException + */ + protected function instantiateSigner(string $className): Signer + { + $class = new ReflectionClass($className); $signer = $class->newInstance(); if (!$signer instanceof Signer) { - return new Sha256(); + throw new ConfigurationError(sprintf('Unsupported signer class provided (%s).', $className)); } return $signer; } /** - * Return the path to the public certificate + * Get the path to the public certificate used in OIDC protocol. * @return string The file system path * @throws Exception */ - public function getCertPath(): string + public function getProtocolCertPath(): string { $certName = $this->config()->getOptionalString( self::OPTION_PKI_CERTIFICATE_FILENAME, self::DEFAULT_PKI_CERTIFICATE_FILENAME, ); - return (new Config())->getCertPath($certName); + return $this->sspBridge->utils()->config()->getCertPath($certName); } /** - * Get the path to the private key + * Get the path to the private key used in OIDC protocol. * @throws Exception */ - public function getPrivateKeyPath(): string + public function getProtocolPrivateKeyPath(): string { $keyName = $this->config()->getOptionalString( self::OPTION_PKI_PRIVATE_KEY_FILENAME, self::DEFAULT_PKI_PRIVATE_KEY_FILENAME, ); - // TODO mivanci move to bridge classes to SSP utils - return (new Config())->getCertPath($keyName); - } - - public function getEncryptionKey(): string - { - // TODO mivanci move to bridge classes to SSP utils - return (new Config())->getSecretSalt(); + return $this->sspBridge->utils()->config()->getCertPath($keyName); } /** - * Get the path to the private key + * Get the OIDC protocol private key passphrase. * @return ?string * @throws Exception */ - public function getPrivateKeyPassPhrase(): ?string + public function getProtocolPrivateKeyPassPhrase(): ?string { return $this->config()->getOptionalString(self::OPTION_PKI_PRIVATE_KEY_PASSPHRASE, null); } @@ -348,4 +380,109 @@ public function getUserIdentifierAttribute(): string { return $this->config()->getString(ModuleConfig::OPTION_AUTH_USER_IDENTIFIER_ATTRIBUTE); } + + /** + * @throws \ReflectionException + * @throws \SimpleSAML\Error\ConfigurationError + */ + public function getFederationSigner(): ?Signer + { + /** @psalm-var ?class-string $signerClassname */ + $signerClassname = $this->config()->getOptionalString(self::OPTION_FEDERATION_TOKEN_SIGNER, null); + + return is_null($signerClassname) ? null : $this->instantiateSigner($signerClassname); + } + + public function getFederationPrivateKeyPath(): ?string + { + $keyName = $this->config()->getOptionalString( + self::OPTION_PKI_FEDERATION_PRIVATE_KEY_FILENAME, + null, + ); + + return is_null($keyName) ? null : $this->sspBridge->utils()->config()->getCertPath($keyName); + } + + public function getFederationPrivateKeyPassPhrase(): ?string + { + return $this->config()->getOptionalString(self::OPTION_PKI_FEDERATION_PRIVATE_KEY_PASSPHRASE, null); + } + + /** + * Return the path to the federation public certificate + * @return ?string The file system path or null if not set. + * @throws Exception + */ + public function getFederationCertPath(): ?string + { + $certName = $this->config()->getOptionalString( + self::OPTION_PKI_FEDERATION_CERTIFICATE_FILENAME, + null, + ); + + return is_null($certName) ? null : $this->sspBridge->utils()->config()->getCertPath($certName); + } + + /** + * @throws Exception + */ + public function getFederationEntityStatementDuration(): DateInterval + { + return new DateInterval( + $this->config()->getOptionalString( + self::OPTION_FEDERATION_ENTITY_STATEMENT_DURATION, + null, + ) ?? 'P1D', + ); + } + + public function getFederationAuthorityHints(): ?array + { + $authorityHints = $this->config()->getOptionalArray( + self::OPTION_FEDERATION_AUTHORITY_HINTS, + null, + ); + + return empty($authorityHints) ? null : $authorityHints; + } + + public function getOrganizationName(): ?string + { + return $this->config()->getOptionalString( + self::OPTION_ORGANIZATION_NAME, + null, + ); + } + + public function getContacts(): ?array + { + return $this->config()->getOptionalArray( + self::OPTION_CONTACTS, + null, + ); + } + + public function getLogoUri(): ?string + { + return $this->config()->getOptionalString( + self::OPTION_LOGO_URI, + null, + ); + } + + public function getPolicyUri(): ?string + { + return $this->config()->getOptionalString( + self::OPTION_POLICY_URI, + null, + ); + } + + public function getHomepageUri(): ?string + { + return $this->config()->getOptionalString( + self::OPTION_HOMEPAGE_URI, + null, + ); + } } diff --git a/src/Services/IdTokenBuilder.php b/src/Services/IdTokenBuilder.php index a5ef2412..3c26a64c 100644 --- a/src/Services/IdTokenBuilder.php +++ b/src/Services/IdTokenBuilder.php @@ -61,7 +61,7 @@ public function build( 'at_hash', $this->generateAccessTokenHash( $accessToken, - $this->jsonWebTokenBuilderService->getSigner()->algorithmId(), + $this->jsonWebTokenBuilderService->getProtocolSigner()->algorithmId(), ), ); } @@ -127,7 +127,7 @@ public function build( } } - return $this->jsonWebTokenBuilderService->getSignedJwtTokenFromBuilder($builder); + return $this->jsonWebTokenBuilderService->getSignedProtocolJwt($builder); } /** @@ -139,7 +139,7 @@ protected function getBuilder( ): Builder { /** @psalm-suppress ArgumentTypeCoercion */ return $this->jsonWebTokenBuilderService - ->getDefaultJwtTokenBuilder() + ->getProtocolJwtBuilder() ->permittedFor($accessToken->getClient()->getIdentifier()) ->identifiedBy($accessToken->getIdentifier()) ->canOnlyBeUsedAfter(new DateTimeImmutable('now')) diff --git a/src/Services/JsonWebKeySetService.php b/src/Services/JsonWebKeySetService.php index 74d24158..0a485399 100644 --- a/src/Services/JsonWebKeySetService.php +++ b/src/Services/JsonWebKeySetService.php @@ -19,12 +19,18 @@ use Jose\Component\Core\JWKSet; use Jose\Component\KeyManagement\JWKFactory; use SimpleSAML\Error\Exception; +use SimpleSAML\Module\oidc\Codebooks\ClaimNamesEnum; +use SimpleSAML\Module\oidc\Codebooks\ClaimValues\PublicKeyUseEnum; use SimpleSAML\Module\oidc\ModuleConfig; +use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Utils\FingerprintGenerator; class JsonWebKeySetService { - private readonly JWKSet $jwkSet; + /** @var JWKSet JWKS for OIDC protocol. */ + private readonly JWKSet $protocolJwkSet; + /** @var JWKSet|null JWKS for OpenID Federation. */ + private ?JWKSet $federationJwkSet = null; /** * @throws Exception @@ -32,27 +38,51 @@ class JsonWebKeySetService */ public function __construct(ModuleConfig $moduleConfig) { - $publicKeyPath = $moduleConfig->getCertPath(); + $publicKeyPath = $moduleConfig->getProtocolCertPath(); if (!file_exists($publicKeyPath)) { - throw new Exception("OpenId Connect certification file does not exists: $publicKeyPath."); + throw new Exception("OIDC protocol public key file does not exists: $publicKeyPath."); } - $kid = FingerprintGenerator::forFile($publicKeyPath); - $jwk = JWKFactory::createFromKeyFile($publicKeyPath, null, [ - 'kid' => $kid, - 'use' => 'sig', - 'alg' => 'RS256', + ClaimNamesEnum::KeyId->value => FingerprintGenerator::forFile($publicKeyPath), + ClaimNamesEnum::PublicKeyUse->value => PublicKeyUseEnum::Signature->value, + ClaimNamesEnum::Algorithm->value => $moduleConfig->getProtocolSigner()->algorithmId(), ]); - $this->jwkSet = new JWKSet([$jwk]); + $this->protocolJwkSet = new JWKSet([$jwk]); + + if ( + ($federationPublicKeyPath = $moduleConfig->getFederationCertPath()) && + file_exists($federationPublicKeyPath) && + ($federationSigner = $moduleConfig->getFederationSigner()) + ) { + $federationJwk = JWKFactory::createFromKeyFile($federationPublicKeyPath, null, [ + ClaimNamesEnum::KeyId->value => FingerprintGenerator::forFile($federationPublicKeyPath), + ClaimNamesEnum::PublicKeyUse->value => PublicKeyUseEnum::Signature->value, + ClaimNamesEnum::Algorithm->value => $federationSigner->algorithmId(), + ]); + + $this->federationJwkSet = new JWKSet([$federationJwk]); + } } /** * @return JWK[] */ - public function keys(): array + public function protocolKeys(): array { - return $this->jwkSet->all(); + return $this->protocolJwkSet->all(); + } + + /** + * @throws OidcServerException + */ + public function federationKeys(): array + { + if (is_null($this->federationJwkSet)) { + throw OidcServerException::serverError('OpenID Federation public key not set.'); + } + + return $this->federationJwkSet->all(); } } diff --git a/src/Services/JsonWebTokenBuilderService.php b/src/Services/JsonWebTokenBuilderService.php index 373c4d8c..4230fb63 100644 --- a/src/Services/JsonWebTokenBuilderService.php +++ b/src/Services/JsonWebTokenBuilderService.php @@ -12,15 +12,24 @@ use Lcobucci\JWT\Signer; use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\UnencryptedToken; -use League\OAuth2\Server\Exception\OAuthServerException; use ReflectionException; +use SimpleSAML\Module\oidc\Codebooks\ClaimNamesEnum; use SimpleSAML\Module\oidc\ModuleConfig; +use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Utils\FingerprintGenerator; use SimpleSAML\Module\oidc\Utils\UniqueIdentifierGenerator; class JsonWebTokenBuilderService { - protected Configuration $jwtConfig; + /** + * @var Configuration Token configuration related to OIDC protocol. + */ + protected Configuration $protocolJwtConfig; + + /** + * @var ?Configuration Token configuration related to OpenID Federation. + */ + protected ?Configuration $federationJwtConfig = null; /** * @throws ReflectionException @@ -31,48 +40,129 @@ class JsonWebTokenBuilderService public function __construct( protected ModuleConfig $moduleConfig = new ModuleConfig(), ) { - $this->jwtConfig = Configuration::forAsymmetricSigner( - $this->moduleConfig->getSigner(), + $this->protocolJwtConfig = Configuration::forAsymmetricSigner( + $this->moduleConfig->getProtocolSigner(), InMemory::file( - $this->moduleConfig->getPrivateKeyPath(), - $this->moduleConfig->getPrivateKeyPassPhrase() ?? '', + $this->moduleConfig->getProtocolPrivateKeyPath(), + $this->moduleConfig->getProtocolPrivateKeyPassPhrase() ?? '', ), InMemory::plainText('empty', 'empty'), ); + + // According to OpenID Federation specification, we need to use different signing keys for federation related + // functions. Since we won't force OP implementor to enable federation support, this part is optional. + if ( + ($federationSigner = $this->moduleConfig->getFederationSigner()) && + ($federationPrivateKeyPath = $this->moduleConfig->getFederationPrivateKeyPath()) && + file_exists($federationPrivateKeyPath) + ) { + $this->federationJwtConfig = Configuration::forAsymmetricSigner( + $federationSigner, + InMemory::file( + $federationPrivateKeyPath, + $this->moduleConfig->getFederationPrivateKeyPassPhrase() ?? '', + ), + InMemory::plainText('empty', 'empty'), + ); + } + } + + /** + * Get JWT Builder which uses OIDC protocol related signing configuration. + * + * @throws OidcServerException + */ + public function getProtocolJwtBuilder(): Builder + { + return $this->getDefaultJwtBuilder($this->protocolJwtConfig); + } + + /** + * Get JWT Builder which uses OpenID Federation related signing configuration. + * + * @throws OidcServerException + */ + public function getFederationJwtBuilder(): Builder + { + if (is_null($this->federationJwtConfig)) { + throw OidcServerException::serverError('Federation JWT PKI configuration is not set.'); + } + + return $this->getDefaultJwtBuilder($this->federationJwtConfig); } /** - * @throws OAuthServerException + * Get default JWT Builder by using the provided configuration, with predefined claims like iss, iat, jti. + * + * @throws OidcServerException */ - public function getDefaultJwtTokenBuilder(): Builder + public function getDefaultJwtBuilder(Configuration $configuration): Builder { /** @psalm-suppress ArgumentTypeCoercion */ // Ignore microseconds when handling dates. - return $this->jwtConfig->builder(ChainedFormatter::withUnixTimestampDates()) - ->issuedBy($this->moduleConfig->getSimpleSAMLSelfURLHost()) + return $configuration->builder(ChainedFormatter::withUnixTimestampDates()) + ->issuedBy($this->moduleConfig->getIssuer()) ->issuedAt(new DateTimeImmutable('now')) ->identifiedBy(UniqueIdentifierGenerator::hitMe()); } /** + * Get signed JWT using the OIDC protocol JWT signing configuration. + * * @throws Exception */ - public function getSignedJwtTokenFromBuilder(Builder $builder): UnencryptedToken + public function getSignedProtocolJwt(Builder $builder): UnencryptedToken { - $kid = FingerprintGenerator::forFile($this->moduleConfig->getCertPath()); + $headers = [ + ClaimNamesEnum::KeyId->value => FingerprintGenerator::forFile($this->moduleConfig->getProtocolCertPath()), + ]; - return $builder->withHeader('kid', $kid) - ->getToken( - $this->jwtConfig->signer(), - $this->jwtConfig->signingKey(), - ); + return $this->getSignedJwt($builder, $this->protocolJwtConfig, $headers); + } + + /** + * Get signed JWT using the OpenID Federation JWT signing configuration. + * + * @throws OidcServerException + */ + public function getSignedFederationJwt(Builder $builder): UnencryptedToken + { + if (is_null($federationCertPath = $this->moduleConfig->getFederationCertPath())) { + throw OidcServerException::serverError('Federation certificate path not set.'); + } + + $headers = [ + ClaimNamesEnum::KeyId->value => FingerprintGenerator::forFile($federationCertPath), + ]; + + return $this->getSignedJwt($builder, $this->protocolJwtConfig, $headers); + } + + /** + * Get signed JWT for provided builder and JWT signing configuration, and optionally with any additional headers to + * include. + */ + public function getSignedJwt( + Builder $builder, + Configuration $jwtConfig, + array $headers = [], + ): UnencryptedToken { + /** + * @var non-empty-string $headerKey + * @psalm-suppress MixedAssignment + */ + foreach ($headers as $headerKey => $headerValue) { + $builder = $builder->withHeader($headerKey, $headerValue); + } + + return $builder->getToken($jwtConfig->signer(), $jwtConfig->signingKey()); } /** * @throws ReflectionException */ - public function getSigner(): Signer + public function getProtocolSigner(): Signer { - return $this->moduleConfig->getSigner(); + return $this->moduleConfig->getProtocolSigner(); } } diff --git a/src/Services/LogoutTokenBuilder.php b/src/Services/LogoutTokenBuilder.php index c0d9a3c1..fc761f0b 100644 --- a/src/Services/LogoutTokenBuilder.php +++ b/src/Services/LogoutTokenBuilder.php @@ -23,7 +23,7 @@ public function __construct( public function forRelyingPartyAssociation(RelyingPartyAssociationInterface $relyingPartyAssociation): string { $logoutTokenBuilder = $this->jsonWebTokenBuilderService - ->getDefaultJwtTokenBuilder() + ->getProtocolJwtBuilder() ->withHeader('typ', 'logout+jwt') ->permittedFor($relyingPartyAssociation->getClientId()) ->relatedTo($relyingPartyAssociation->getUserId()) @@ -34,6 +34,6 @@ public function forRelyingPartyAssociation(RelyingPartyAssociationInterface $rel $logoutTokenBuilder = $logoutTokenBuilder->withClaim('sid', $relyingPartyAssociation->getSessionId()); } - return $this->jsonWebTokenBuilderService->getSignedJwtTokenFromBuilder($logoutTokenBuilder)->toString(); + return $this->jsonWebTokenBuilderService->getSignedProtocolJwt($logoutTokenBuilder)->toString(); } } diff --git a/src/Services/OpMetadataService.php b/src/Services/OpMetadataService.php index 7756e000..ea408a0e 100644 --- a/src/Services/OpMetadataService.php +++ b/src/Services/OpMetadataService.php @@ -33,17 +33,19 @@ public function __construct( private function initMetadata(): void { $this->metadata = []; - $this->metadata['issuer'] = $this->moduleConfig->getSimpleSAMLSelfURLHost(); + $this->metadata['issuer'] = $this->moduleConfig->getIssuer(); $this->metadata['authorization_endpoint'] = - $this->moduleConfig->getOpenIdConnectModuleURL('authorize.php'); - $this->metadata['token_endpoint'] = $this->moduleConfig->getOpenIdConnectModuleURL('token.php'); - $this->metadata['userinfo_endpoint'] = $this->moduleConfig->getOpenIdConnectModuleURL('userinfo.php'); - $this->metadata['end_session_endpoint'] = $this->moduleConfig->getOpenIdConnectModuleURL('logout.php'); - $this->metadata['jwks_uri'] = $this->moduleConfig->getOpenIdConnectModuleURL('jwks.php'); + $this->moduleConfig->getModuleUrl('authorize.php'); + $this->metadata['token_endpoint'] = $this->moduleConfig->getModuleUrl('token.php'); + $this->metadata['userinfo_endpoint'] = $this->moduleConfig->getModuleUrl('userinfo.php'); + $this->metadata['end_session_endpoint'] = $this->moduleConfig->getModuleUrl('logout.php'); + $this->metadata['jwks_uri'] = $this->moduleConfig->getModuleUrl('jwks.php'); $this->metadata['scopes_supported'] = array_keys($this->moduleConfig->getOpenIDScopes()); $this->metadata['response_types_supported'] = ['code', 'token', 'id_token', 'id_token token']; $this->metadata['subject_types_supported'] = ['public']; - $this->metadata['id_token_signing_alg_values_supported'] = ['RS256']; + $this->metadata['id_token_signing_alg_values_supported'] = [ + $this->moduleConfig->getProtocolSigner()->algorithmId(), + ]; $this->metadata['code_challenge_methods_supported'] = ['plain', 'S256']; $this->metadata['token_endpoint_auth_methods_supported'] = ['client_secret_post', 'client_secret_basic']; $this->metadata['request_parameter_supported'] = false; diff --git a/src/Utils/Checker/Rules/IdTokenHintRule.php b/src/Utils/Checker/Rules/IdTokenHintRule.php index 19397fb1..b4069b88 100644 --- a/src/Utils/Checker/Rules/IdTokenHintRule.php +++ b/src/Utils/Checker/Rules/IdTokenHintRule.php @@ -57,7 +57,7 @@ public function checkRule( $publicKey = $this->cryptKeyFactory->buildPublicKey(); /** @psalm-suppress ArgumentTypeCoercion */ $jwtConfig = Configuration::forAsymmetricSigner( - $this->moduleConfig->getSigner(), + $this->moduleConfig->getProtocolSigner(), InMemory::plainText($privateKey->getKeyContents(), $privateKey->getPassPhrase() ?? ''), InMemory::plainText($publicKey->getKeyContents()), ); @@ -79,12 +79,12 @@ public function checkRule( /** @psalm-suppress ArgumentTypeCoercion */ $jwtConfig->validator()->assert( $idTokenHint, - new IssuedBy($this->moduleConfig->getSimpleSAMLSelfURLHost()), + new IssuedBy($this->moduleConfig->getIssuer()), // Note: although logout spec does not mention it, validating signature seems like an important check // to make. However, checking the signature in a key roll-over scenario will fail for ID tokens // signed with previous key... new SignedWith( - $this->moduleConfig->getSigner(), + $this->moduleConfig->getProtocolSigner(), InMemory::plainText($publicKey->getKeyContents()), ), ); diff --git a/src/Utils/TimestampGenerator.php b/src/Utils/TimestampGenerator.php index 236ac816..ced00daf 100644 --- a/src/Utils/TimestampGenerator.php +++ b/src/Utils/TimestampGenerator.php @@ -16,6 +16,7 @@ namespace SimpleSAML\Module\oidc\Utils; use DateTime; +use DateTimeImmutable; use DateTimeZone; use Exception; @@ -28,4 +29,12 @@ public static function utc(string $time = 'now'): DateTime { return new DateTime($time, new DateTimeZone('UTC')); } + + /** + * @throws Exception + */ + public static function utcImmutable(string $time = 'now'): DateTimeImmutable + { + return DateTimeImmutable::createFromMutable(self::utc($time)); + } } diff --git a/src/Utils/UniqueIdentifierGenerator.php b/src/Utils/UniqueIdentifierGenerator.php index 42ff5a8b..ed548817 100644 --- a/src/Utils/UniqueIdentifierGenerator.php +++ b/src/Utils/UniqueIdentifierGenerator.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Module\oidc\Utils; -use League\OAuth2\Server\Exception\OAuthServerException; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use Throwable; @@ -13,12 +12,12 @@ class UniqueIdentifierGenerator /** * Generate a new unique identifier. * - * @throws OAuthServerException + * @throws OidcServerException */ public static function hitMe(int $length = 40): string { if ($length < 1) { - throw OidcServerException::serverError('Random string lenght can not be less than 1'); + throw OidcServerException::serverError('Random string length can not be less than 1'); } try { diff --git a/tests/config/config.php b/tests/config/config.php new file mode 100644 index 00000000..0a500bdc --- /dev/null +++ b/tests/config/config.php @@ -0,0 +1,1306 @@ + 'simplesaml/', + + /* + * The 'application' configuration array groups a set configuration options + * relative to an application protected by SimpleSAMLphp. + */ + 'application' => [ + /* + * The 'baseURL' configuration option allows you to specify a protocol, + * host and optionally a port that serves as the canonical base for all + * your application's URLs. This is useful when the environment + * observed in the server differs from the one observed by end users, + * for example, when using a load balancer to offload TLS. + * + * Note that this configuration option does not allow setting a path as + * part of the URL. If your setup involves URL rewriting or any other + * tricks that would result in SimpleSAMLphp observing a URL for your + * application's scripts different than the canonical one, you will + * need to compute the right URLs yourself and pass them dynamically + * to SimpleSAMLphp's API. + */ + //'baseURL' => 'https://example.com', + ], + + /* + * The following settings are *filesystem paths* which define where + * SimpleSAMLphp can find or write the following things: + * - 'cachedir': Where SimpleSAMLphp can write its cache. + * - 'loggingdir': Where to write logs. MUST be set to NULL when using a logging + * handler other than `file`. + * - 'datadir': Storage of general data. + * - 'tempdir': Saving temporary files. SimpleSAMLphp will attempt to create + * this directory if it doesn't exist. DEPRECATED - replaced by cachedir. + * When specified as a relative path, this is relative to the SimpleSAMLphp + * root directory. + */ + 'cachedir' => '/var/cache/simplesamlphp', + //'loggingdir' => '/var/log/', + //'datadir' => '/var/data/', + //'tempdir' => '/tmp/simplesamlphp', + + /* + * Certificate and key material can be loaded from different possible + * locations. Currently two locations are supported, the local filesystem + * and the database via pdo using the global database configuration. Locations + * are specified by a URL-link prefix before the file name/path or database + * identifier. + */ + + /* To load a certificate or key from the filesystem, it should be specified + * as 'file://' where is either a relative filename or a fully + * qualified path to a file containing the certificate or key in PEM + * format, such as 'cert.pem' or '/path/to/cert.pem'. If the path is + * relative, it will be searched for in the directory defined by the + * '' parameter below. When 'certdir' is specified as a relative + * path, it will be interpreted as relative to the SimpleSAMLphp root + * directory. Note that locations with no prefix included will be treated + * as file locations. + */ + 'certdir' => 'cert/', + + /* To load a certificate or key from the database, it should be specified + * as 'pdo://' where is the identifier in the database table that + * should be matched. While the certificate and key tables are expected to + * be in the simplesaml database, they are not created or managed by + * simplesaml. The following parameters control how the pdo location + * attempts to retrieve certificates and keys from the database: + * + * - 'cert.pdo.table': name of table where certificates are stored + * - 'cert.pdo.keytable': name of table where keys are stored + * - 'cert.pdo.apply_prefix': whether or not to prepend the database.prefix + * parameter to the table names; if you are using + * database.prefix to separate multiple SSP instances + * in the same database but want to share certificate/key + * data between them, set this to false + * - 'cert.pdo.id_column': name of column to use as identifier + * - 'cert.pdo.data_column': name of column where PEM data is stored + * + * Basically, the query executed will be: + * + * SELECT cert.pdo.data_column FROM cert.pdo.table WHERE cert.pdo.id_column = :id + * + * Defaults are shown below, to change them, uncomment the line and update as + * needed + */ + //'cert.pdo.table' => 'certificates', + //'cert.pdo.keytable' => 'private_keys', + //'cert.pdo.apply_prefix' => true, + //'cert.pdo.id_column' => 'id', + //'cert.pdo.data_column' => 'data', + + /* + * Some information about the technical persons running this installation. + * The email address will be used as the recipient address for error reports, and + * also as the technical contact in generated metadata. + */ + 'technicalcontact_name' => 'Administrator', + 'technicalcontact_email' => 'na@example.org', + + /* + * (Optional) The method by which email is delivered. Defaults to mail which utilizes the + * PHP mail() function. + * + * Valid options are: mail, sendmail and smtp. + */ + //'mail.transport.method' => 'smtp', + + /* + * Set the transport options for the transport method specified. The valid settings are relative to the + * selected transport method. + */ + /* + 'mail.transport.options' => [ + 'host' => 'mail.example.org', // required + 'port' => 25, // optional + 'username' => 'user@example.org', // optional: if set, enables smtp authentication + 'password' => 'password', // optional: if set, enables smtp authentication + 'security' => 'tls', // optional: defaults to no smtp security + 'smtpOptions' => [], // optional: passed to stream_context_create when connecting via SMTP + ], + + // sendmail mail transport options + /* + 'mail.transport.options' => [ + 'path' => '/usr/sbin/sendmail' // optional: defaults to php.ini path + ], + */ + + /* + * The envelope from address for outgoing emails. + * This should be in a domain that has your application's IP addresses in its SPF record + * to prevent it from being rejected by mail filters. + */ + //'sendmail_from' => 'no-reply@example.org', + + /* + * The timezone of the server. This option should be set to the timezone you want + * SimpleSAMLphp to report the time in. The default is to guess the timezone based + * on your system timezone. + * + * See this page for a list of valid timezones: http://php.net/manual/en/timezones.php + */ + 'timezone' => null, + + + + /********************************** + | SECURITY CONFIGURATION OPTIONS | + **********************************/ + + /* + * This is a secret salt used by SimpleSAMLphp when it needs to generate a secure hash + * of a value. It must be changed from its default value to a secret value. The value of + * 'secretsalt' can be any valid string of any length. + * + * A possible way to generate a random salt is by running the following command from a unix shell: + * LC_ALL=C tr -c -d '0123456789abcdefghijklmnopqrstuvwxyz' /dev/null;echo + */ + 'secretsalt' => 'abc123', + + /* + * This password must be kept secret, and modified from the default value 123. + * This password will give access to the installation page of SimpleSAMLphp with + * metadata listing and diagnostics pages. + * You can also put a hash here; run "bin/pwgen.php" to generate one. + */ + 'auth.adminpassword' => 'admin', + + /* + * Set this option to true if you want to require administrator password to access the metadata. + */ + 'admin.protectmetadata' => false, + + /* + * Set this option to false if you don't want SimpleSAMLphp to check for new stable releases when + * visiting the configuration tab in the web interface. + */ + 'admin.checkforupdates' => true, + + /* + * Array of domains that are allowed when generating links or redirects + * to URLs. SimpleSAMLphp will use this option to determine whether to + * to consider a given URL valid or not, but you should always validate + * URLs obtained from the input on your own (i.e. ReturnTo or RelayState + * parameters obtained from the $_REQUEST array). + * + * SimpleSAMLphp will automatically add your own domain (either by checking + * it dynamically, or by using the domain defined in the 'baseurlpath' + * directive, the latter having precedence) to the list of trusted domains, + * in case this option is NOT set to NULL. In that case, you are explicitly + * telling SimpleSAMLphp to verify URLs. + * + * Set to an empty array to disallow ALL redirects or links pointing to + * an external URL other than your own domain. This is the default behaviour. + * + * Set to NULL to disable checking of URLs. DO NOT DO THIS UNLESS YOU KNOW + * WHAT YOU ARE DOING! + * + * Example: + * 'trusted.url.domains' => ['sp.example.com', 'app.example.com'], + */ + 'trusted.url.domains' => [], + + /* + * Enable regular expression matching of trusted.url.domains. + * + * Set to true to treat the values in trusted.url.domains as regular + * expressions. Set to false to do exact string matching. + * + * If enabled, the start and end delimiters ('^' and '$') will be added to + * all regular expressions in trusted.url.domains. + */ + 'trusted.url.regex' => false, + + /* + * Enable secure POST from HTTPS to HTTP. + * + * If you have some SP's on HTTP and IdP is normally on HTTPS, this option + * enables secure POSTing to HTTP endpoint without warning from browser. + * + * For this to work, module.php/core/postredirect.php must be accessible + * also via HTTP on IdP, e.g. if your IdP is on + * https://idp.example.org/ssp/, then + * http://idp.example.org/ssp/module.php/core/postredirect.php must be accessible. + */ + 'enable.http_post' => false, + + /* + * Set the allowed clock skew between encrypting/decrypting assertions + * + * If you have a server that is constantly out of sync, this option + * allows you to adjust the allowed clock-skew. + * + * Allowed range: 180 - 300 + * Defaults to 180. + */ + 'assertion.allowed_clock_skew' => 180, + + /* + * Set custom security headers. The defaults can be found in \SimpleSAML\Configuration::DEFAULT_SECURITY_HEADERS + * + * NOTE: When a header is already set on the response we will NOT overrule it and leave it untouched. + * + * Whenever you change any of these headers, make sure to validate your config by running your + * hostname through a security-test like https://en.internet.nl + 'headers.security' => [ + // phpcs:ignore + 'Content-Security-Policy' => "default-src 'none'; frame-ancestors 'self'; object-src 'none'; script-src 'self'; style-src 'self'; font-src 'self'; connect-src 'self'; img-src 'self' data:; base-uri 'none'", + 'X-Frame-Options' => 'SAMEORIGIN', + 'X-Content-Type-Options' => 'nosniff', + 'Referrer-Policy' => 'origin-when-cross-origin', + ], + */ + + + /************************ + | ERRORS AND DEBUGGING | + ************************/ + + /* + * The 'debug' option allows you to control how SimpleSAMLphp behaves in certain + * situations where further action may be taken + * + * It can be left unset, in which case, debugging is switched off for all actions. + * If set, it MUST be an array containing the actions that you want to enable, or + * alternatively a hashed array where the keys are the actions and their + * corresponding values are booleans enabling or disabling each particular action. + * + * SimpleSAMLphp provides some pre-defined actions, though modules could add new + * actions here. Refer to the documentation of every module to learn if they + * allow you to set any more debugging actions. + * + * The pre-defined actions are: + * + * - 'saml': this action controls the logging of SAML messages exchanged with other + * entities. When enabled ('saml' is present in this option, or set to true), all + * SAML messages will be logged, including plaintext versions of encrypted + * messages. + * + * - 'backtraces': this action controls the logging of error backtraces so you + * can debug any possible errors happening in SimpleSAMLphp. + * + * - 'validatexml': this action allows you to validate SAML documents against all + * the relevant XML schemas. SAML 1.1 messages or SAML metadata parsed with + * the XML to SimpleSAMLphp metadata converter or the metaedit module will + * validate the SAML documents if this option is enabled. + * + * If you want to disable debugging completely, unset this option or set it to an + * empty array. + */ + 'debug' => [ + 'saml' => false, + 'backtraces' => true, + 'validatexml' => false, + ], + + /* + * When 'showerrors' is enabled, all error messages and stack traces will be output + * to the browser. + * + * When 'errorreporting' is enabled, a form will be presented for the user to report + * the error to 'technicalcontact_email'. + */ + 'showerrors' => true, + 'errorreporting' => true, + + /* + * Custom error show function called from SimpleSAML\Error\Error::show. + * See docs/simplesamlphp-errorhandling.md for function code example. + * + * Example: + * 'errors.show_function' => ['SimpleSAML\Module\example\Error', 'show'], + */ + + + /************************** + | LOGGING AND STATISTICS | + **************************/ + + /* + * Define the minimum log level to log. Available levels: + * - SimpleSAML\Logger::ERR No statistics, only errors + * - SimpleSAML\Logger::WARNING No statistics, only warnings/errors + * - SimpleSAML\Logger::NOTICE Statistics and errors + * - SimpleSAML\Logger::INFO Verbose logs + * - SimpleSAML\Logger::DEBUG Full debug logs - not recommended for production + * + * Choose logging handler. + * + * Options: [syslog,file,errorlog,stderr] + * + * If you set the handler to 'file', the directory specified in loggingdir above + * must exist and be writable for SimpleSAMLphp. If set to something else, set + * loggingdir above to 'null'. + */ + 'logging.level' => SimpleSAML\Logger::NOTICE, + 'logging.handler' => 'syslog', + + /* + * Specify the format of the logs. Its use varies depending on the log handler used (for instance, you cannot + * control here how dates are displayed when using the syslog or errorlog handlers), but in general the options + * are: + * + * - %date{}: the date and time, with its format specified inside the brackets. See the PHP documentation + * of the date() function for more information on the format. If the brackets are omitted, the standard + * format is applied. This can be useful if you just want to control the placement of the date, but don't care + * about the format. + * + * - %process: the name of the SimpleSAMLphp process. Remember you can configure this in the 'logging.processname' + * option below. + * + * - %level: the log level (name or number depending on the handler used). + * + * - %stat: if the log entry is intended for statistical purposes, it will print the string 'STAT ' (bear in mind + * the trailing space). + * + * - %trackid: the track ID, an identifier that allows you to track a single session. + * + * - %srcip: the IP address of the client. If you are behind a proxy, make sure to modify the + * $_SERVER['REMOTE_ADDR'] variable on your code accordingly to the X-Forwarded-For header. + * + * - %msg: the message to be logged. + * + */ + //'logging.format' => '%date{M j H:i:s} %process %level %stat[%trackid] %msg', + + /* + * Choose which facility should be used when logging with syslog. + * + * These can be used for filtering the syslog output from SimpleSAMLphp into its + * own file by configuring the syslog daemon. + * + * See the documentation for openlog (http://php.net/manual/en/function.openlog.php) for available + * facilities. Note that only LOG_USER is valid on windows. + * + * The default is to use LOG_LOCAL5 if available, and fall back to LOG_USER if not. + */ + 'logging.facility' => defined('LOG_LOCAL5') ? constant('LOG_LOCAL5') : LOG_USER, + + /* + * The process name that should be used when logging to syslog. + * The value is also written out by the other logging handlers. + */ + 'logging.processname' => 'simplesamlphp', + + /* + * Logging: file - Logfilename in the loggingdir from above. + */ + 'logging.logfile' => 'simplesamlphp.log', + + /* + * This is an array of outputs. Each output has at least a 'class' option, which + * selects the output. + */ + 'statistics.out' => [ + // Log statistics to the normal log. + /* + [ + 'class' => 'core:Log', + 'level' => 'notice', + ], + */ + // Log statistics to files in a directory. One file per day. + /* + [ + 'class' => 'core:File', + 'directory' => '/var/log/stats', + ], + */ + ], + + + + /*********************** + | PROXY CONFIGURATION | + ***********************/ + + /* + * Proxy to use for retrieving URLs. + * + * Example: + * 'proxy' => 'tcp://proxy.example.com:5100' + */ + 'proxy' => null, + + /* + * Username/password authentication to proxy (Proxy-Authorization: Basic) + * Example: + * 'proxy.auth' = 'myuser:password' + */ + //'proxy.auth' => 'myuser:password', + + + + /************************** + | DATABASE CONFIGURATION | + **************************/ + + /* + * This database configuration is optional. If you are not using + * core functionality or modules that require a database, you can + * skip this configuration. + */ + + /* + * Database connection string. + * Ensure that you have the required PDO database driver installed + * for your connection string. + */ + 'database.dsn' => 'mysql:host=localhost;dbname=saml', + + /* + * SQL database credentials + */ + 'database.username' => 'simplesamlphp', + 'database.password' => 'secret', + 'database.options' => [], + + /* + * (Optional) Table prefix + */ + 'database.prefix' => '', + + /* + * (Optional) Driver options + */ + 'database.driver_options' => [], + + /* + * True or false if you would like a persistent database connection + */ + 'database.persistent' => false, + + /* + * Database secondary configuration is optional as well. If you are only + * running a single database server, leave this blank. If you have + * a primary/secondary configuration, you can define as many secondary servers + * as you want here. Secondaries will be picked at random to be queried from. + * + * Configuration options in the secondary array are exactly the same as the + * options for the primary (shown above) with the exception of the table + * prefix and driver options. + */ + 'database.secondaries' => [ + /* + [ + 'dsn' => 'mysql:host=mysecondary;dbname=saml', + 'username' => 'simplesamlphp', + 'password' => 'secret', + 'persistent' => false, + ], + */ + ], + + + + /************* + | PROTOCOLS | + *************/ + + /* + * Which functionality in SimpleSAMLphp do you want to enable. Normally you would enable only + * one of the functionalities below, but in some cases you could run multiple functionalities. + * In example when you are setting up a federation bridge. + */ + 'enable.saml20-idp' => false, + 'enable.adfs-idp' => false, + + + + /*********** + | MODULES | + ***********/ + + /* + * Configuration for enabling/disabling modules. By default the 'core', 'admin' and 'saml' modules are enabled. + * + * Example: + * + * 'module.enable' => [ + * 'exampleauth' => true, // Setting to TRUE enables. + * 'consent' => false, // Setting to FALSE disables. + * 'core' => null, // Unset or NULL uses default. + * ], + */ + + 'module.enable' => [ + 'exampleauth' => false, + 'core' => true, + 'admin' => true, + 'saml' => true, + ], + + + /************************* + | SESSION CONFIGURATION | + *************************/ + + /* + * This value is the duration of the session in seconds. Make sure that the time duration of + * cookies both at the SP and the IdP exceeds this duration. + */ + 'session.duration' => 8 * (60 * 60), // 8 hours. + + /* + * Sets the duration, in seconds, data should be stored in the datastore. As the data store is used for + * login and logout requests, this option will control the maximum time these operations can take. + * The default is 4 hours (4*60*60) seconds, which should be more than enough for these operations. + */ + 'session.datastore.timeout' => (4 * 60 * 60), // 4 hours + + /* + * Sets the duration, in seconds, auth state should be stored. + */ + 'session.state.timeout' => (60 * 60), // 1 hour + + /* + * Option to override the default settings for the session cookie name + */ + 'session.cookie.name' => 'SimpleSAMLSessionID', + + /* + * Expiration time for the session cookie, in seconds. + * + * Defaults to 0, which means that the cookie expires when the browser is closed. + * + * Example: + * 'session.cookie.lifetime' => 30*60, + */ + 'session.cookie.lifetime' => 0, + + /* + * Limit the path of the cookies. + * + * Can be used to limit the path of the cookies to a specific subdirectory. + * + * Example: + * 'session.cookie.path' => '/simplesaml/', + */ + 'session.cookie.path' => '/', + + /* + * Cookie domain. + * + * Can be used to make the session cookie available to several domains. + * + * Example: + * 'session.cookie.domain' => '.example.org', + */ + 'session.cookie.domain' => '', + + /* + * Set the secure flag in the cookie. + * + * Set this to TRUE if the user only accesses your service + * through https. If the user can access the service through + * both http and https, this must be set to FALSE. + */ + 'session.cookie.secure' => true, + + /* + * Set the SameSite attribute in the cookie. + * + * You can set this to the strings 'None', 'Lax', or 'Strict' to support + * the RFC6265bis SameSite cookie attribute. If set to null, no SameSite + * attribute will be sent. + * + * A value of "None" is required to properly support cross-domain POST + * requests which are used by different SAML bindings. Because some older + * browsers do not support this value, the canSetSameSiteNone function + * can be called to only set it for compatible browsers. + * + * You must also set the 'session.cookie.secure' value above to true. + * + * Example: + * 'session.cookie.samesite' => 'None', + */ + 'session.cookie.samesite' => $httpUtils->canSetSameSiteNone() ? 'None' : null, + + /* + * Options to override the default settings for php sessions. + */ + 'session.phpsession.cookiename' => 'SimpleSAML', + 'session.phpsession.savepath' => null, + 'session.phpsession.httponly' => true, + + /* + * Option to override the default settings for the auth token cookie + */ + 'session.authtoken.cookiename' => 'SimpleSAMLAuthToken', + + /* + * Options for remember me feature for IdP sessions. Remember me feature + * has to be also implemented in authentication source used. + * + * Option 'session.cookie.lifetime' should be set to zero (0), i.e. cookie + * expires on browser session if remember me is not checked. + * + * Session duration ('session.duration' option) should be set according to + * 'session.rememberme.lifetime' option. + * + * It's advised to use remember me feature with session checking function + * defined with 'session.check_function' option. + */ + 'session.rememberme.enable' => false, + 'session.rememberme.checked' => false, + 'session.rememberme.lifetime' => (14 * 86400), + + /* + * Custom function for session checking called on session init and loading. + * See docs/simplesamlphp-advancedfeatures.md for function code example. + * + * Example: + * 'session.check_function' => ['\SimpleSAML\Module\example\Util', 'checkSession'], + */ + + + + /************************** + | MEMCACHE CONFIGURATION | + **************************/ + + /* + * Configuration for the 'memcache' session store. This allows you to store + * multiple redundant copies of sessions on different memcache servers. + * + * 'memcache_store.servers' is an array of server groups. Every data + * item will be mirrored in every server group. + * + * Each server group is an array of servers. The data items will be + * load-balanced between all servers in each server group. + * + * Each server is an array of parameters for the server. The following + * options are available: + * - 'hostname': This is the hostname or ip address where the + * memcache server runs. This is the only required option. + * - 'port': This is the port number of the memcache server. If this + * option isn't set, then we will use the 'memcache.default_port' + * ini setting. This is 11211 by default. + * + * When using the "memcache" extension, the following options are also + * supported: + * - 'weight': This sets the weight of this server in this server + * group. http://php.net/manual/en/function.Memcache-addServer.php + * contains more information about the weight option. + * - 'timeout': The timeout for this server. By default, the timeout + * is 3 seconds. + * + * Example of redundant configuration with load balancing: + * This configuration makes it possible to lose both servers in the + * a-group or both servers in the b-group without losing any sessions. + * Note that sessions will be lost if one server is lost from both the + * a-group and the b-group. + * + * 'memcache_store.servers' => [ + * [ + * ['hostname' => 'mc_a1'], + * ['hostname' => 'mc_a2'], + * ], + * [ + * ['hostname' => 'mc_b1'], + * ['hostname' => 'mc_b2'], + * ], + * ], + * + * Example of simple configuration with only one memcache server, + * running on the same computer as the web server: + * Note that all sessions will be lost if the memcache server crashes. + * + * 'memcache_store.servers' => [ + * [ + * ['hostname' => 'localhost'], + * ], + * ], + * + * Additionally, when using the "memcached" extension, unique keys must + * be provided for each group of servers if persistent connections are + * desired. Each server group can also have an "options" indexed array + * with the options desired for the given group: + * + * 'memcache_store.servers' => [ + * 'memcache_group_1' => [ + * 'options' => [ + * \Memcached::OPT_BINARY_PROTOCOL => true, + * \Memcached::OPT_NO_BLOCK => true, + * \Memcached::OPT_TCP_NODELAY => true, + * \Memcached::OPT_LIBKETAMA_COMPATIBLE => true, + * ], + * ['hostname' => '127.0.0.1', 'port' => 11211], + * ['hostname' => '127.0.0.2', 'port' => 11211], + * ], + * + * 'memcache_group_2' => [ + * 'options' => [ + * \Memcached::OPT_BINARY_PROTOCOL => true, + * \Memcached::OPT_NO_BLOCK => true, + * \Memcached::OPT_TCP_NODELAY => true, + * \Memcached::OPT_LIBKETAMA_COMPATIBLE => true, + * ], + * ['hostname' => '127.0.0.3', 'port' => 11211], + * ['hostname' => '127.0.0.4', 'port' => 11211], + * ], + * ], + * + */ + 'memcache_store.servers' => [ + [ + ['hostname' => 'localhost'], + ], + ], + + /* + * This value allows you to set a prefix for memcache-keys. The default + * for this value is 'simpleSAMLphp', which is fine in most cases. + * + * When running multiple instances of SSP on the same host, and more + * than one instance is using memcache, you probably want to assign + * a unique value per instance to this setting to avoid data collision. + */ + 'memcache_store.prefix' => '', + + /* + * This value is the duration data should be stored in memcache. Data + * will be dropped from the memcache servers when this time expires. + * The time will be reset every time the data is written to the + * memcache servers. + * + * This value should always be larger than the 'session.duration' + * option. Not doing this may result in the session being deleted from + * the memcache servers while it is still in use. + * + * Set this value to 0 if you don't want data to expire. + * + * Note: The oldest data will always be deleted if the memcache server + * runs out of storage space. + */ + 'memcache_store.expires' => 36 * (60 * 60), // 36 hours. + + + + /************************************* + | LANGUAGE AND INTERNATIONALIZATION | + *************************************/ + + /* + * Languages available, RTL languages, and what language is the default. + */ + 'language.available' => [ + 'en', 'no', 'nn', 'se', 'da', 'de', 'sv', 'fi', 'es', 'ca', 'fr', 'it', 'nl', 'lb', + 'cs', 'sk', 'sl', 'lt', 'hr', 'hu', 'pl', 'pt', 'pt-br', 'tr', 'ja', 'zh', 'zh-tw', + 'ru', 'et', 'he', 'id', 'sr', 'lv', 'ro', 'eu', 'el', 'af', 'zu', 'xh', 'st', + ], + 'language.rtl' => ['ar', 'dv', 'fa', 'ur', 'he'], + 'language.default' => 'en', + + /* + * Options to override the default settings for the language parameter + */ + 'language.parameter.name' => 'language', + 'language.parameter.setcookie' => true, + + /* + * Options to override the default settings for the language cookie + */ + 'language.cookie.name' => 'language', + 'language.cookie.domain' => '', + 'language.cookie.path' => '/', + 'language.cookie.secure' => true, + 'language.cookie.httponly' => false, + 'language.cookie.lifetime' => (60 * 60 * 24 * 900), + 'language.cookie.samesite' => $httpUtils->canSetSameSiteNone() ? 'None' : null, + + /** + * Custom getLanguage function called from SimpleSAML\Locale\Language::getLanguage(). + * Function should return language code of one of the available languages or NULL. + * See SimpleSAML\Locale\Language::getLanguage() source code for more info. + * + * This option can be used to implement a custom function for determining + * the default language for the user. + * + * Example: + * 'language.get_language_function' => ['\SimpleSAML\Module\example\Template', 'getLanguage'], + */ + + /************** + | APPEARANCE | + **************/ + + /* + * Which theme directory should be used? + */ + 'theme.use' => 'default', + + /* + * Set this option to the text you would like to appear at the header of each page. Set to false if you don't want + * any text to appear in the header. + */ + //'theme.header' => 'SimpleSAMLphp', + + /** + * A template controller, if any. + * + * Used to intercept certain parts of the template handling, while keeping away unwanted/unexpected hooks. Set + * the 'theme.controller' configuration option to a class that implements the + * \SimpleSAML\XHTML\TemplateControllerInterface interface to use it. + */ + //'theme.controller' => '', + + /* + * Templating options + * + * By default, twig templates are not cached. To turn on template caching: + * Set 'template.cache' to an absolute path pointing to a directory that + * SimpleSAMLphp has read and write permissions to. + */ + //'template.cache' => '', + + /* + * Set the 'template.auto_reload' to true if you would like SimpleSAMLphp to + * recompile the templates (when using the template cache) if the templates + * change. If you don't want to check the source templates for every request, + * set it to false. + */ + 'template.auto_reload' => false, + + /* + * Set this option to true to indicate that your installation of SimpleSAMLphp + * is running in a production environment. This will affect the way resources + * are used, offering an optimized version when running in production, and an + * easy-to-debug one when not. Set it to false when you are testing or + * developing the software, in which case a banner will be displayed to remind + * users that they're dealing with a non-production instance. + * + * Defaults to true. + */ + 'production' => true, + + /* + * SimpleSAMLphp modules can host static resources which are served through PHP. + * The serving of the resources can be configured through these settings. + */ + 'assets' => [ + /* + * These settings adjust the caching headers that are sent + * when serving static resources. + */ + 'caching' => [ + /* + * Amount of seconds before the resource should be fetched again + */ + 'max_age' => 86400, + /* + * Calculate a checksum of every file and send it to the browser + * This allows the browser to avoid downloading assets again in situations + * where the Last-Modified header cannot be trusted, + * for example in cluster setups + * + * Defaults false + */ + 'etag' => false, + ], + ], + + /** + * Set to a full URL if you want to redirect users that land on SimpleSAMLphp's + * front page to somewhere more useful. If left unset, a basic welcome message + * is shown. + */ + //'frontpage.redirect' => 'https://example.com/', + + /********************* + | DISCOVERY SERVICE | + *********************/ + + /* + * Whether the discovery service should allow the user to save his choice of IdP. + */ + 'idpdisco.enableremember' => true, + 'idpdisco.rememberchecked' => true, + + /* + * The disco service only accepts entities it knows. + */ + 'idpdisco.validate' => true, + + 'idpdisco.extDiscoveryStorage' => null, + + /* + * IdP Discovery service look configuration. + * Whether to display a list of idp or to display a dropdown box. For many IdP' a dropdown box + * gives the best use experience. + * + * When using dropdown box a cookie is used to highlight the previously chosen IdP in the dropdown. + * This makes it easier for the user to choose the IdP + * + * Options: [links,dropdown] + */ + 'idpdisco.layout' => 'dropdown', + + + + /************************************* + | AUTHENTICATION PROCESSING FILTERS | + *************************************/ + + /* + * Authentication processing filters that will be executed for all IdPs + */ + 'authproc.idp' => [ + /* Enable the authproc filter below to add URN prefixes to all attributes + 10 => [ + 'class' => 'core:AttributeMap', 'addurnprefix' + ], + */ + /* Enable the authproc filter below to automatically generated eduPersonTargetedID. + 20 => 'core:TargetedID', + */ + + // Adopts language from attribute to use in UI + 30 => 'core:LanguageAdaptor', + + 45 => [ + 'class' => 'core:StatisticsWithAttribute', + 'attributename' => 'realm', + 'type' => 'saml20-idp-SSO', + ], + + /* When called without parameters, it will fallback to filter attributes 'the old way' + * by checking the 'attributes' parameter in metadata on IdP hosted and SP remote. + */ + 50 => 'core:AttributeLimit', + + /* + * Search attribute "distinguishedName" for pattern and replaces if found + */ + /* + 60 => [ + 'class' => 'core:AttributeAlter', + 'pattern' => '/OU=studerende/', + 'replacement' => 'Student', + 'subject' => 'distinguishedName', + '%replace', + ], + */ + + /* + * Consent module is enabled (with no permanent storage, using cookies). + */ + /* + 90 => [ + 'class' => 'consent:Consent', + 'store' => 'consent:Cookie', + 'focus' => 'yes', + 'checked' => true + ], + */ + // If language is set in Consent module it will be added as an attribute. + 99 => 'core:LanguageAdaptor', + ], + + /* + * Authentication processing filters that will be executed for all SPs + */ + 'authproc.sp' => [ + /* + 10 => [ + 'class' => 'core:AttributeMap', 'removeurnprefix' + ], + */ + + /* + * Generate the 'group' attribute populated from other variables, including eduPersonAffiliation. + 60 => [ + 'class' => 'core:GenerateGroups', 'eduPersonAffiliation' + ], + */ + /* + * All users will be members of 'users' and 'members' + */ + /* + 61 => [ + 'class' => 'core:AttributeAdd', 'groups' => ['users', 'members'] + ], + */ + + // Adopts language from attribute to use in UI + 90 => 'core:LanguageAdaptor', + ], + + + + /************************** + | METADATA CONFIGURATION | + **************************/ + + /* + * This option allows you to specify a directory for your metadata outside of the standard metadata directory + * included in the standard distribution of the software. + */ + 'metadatadir' => 'metadata', + + /* + * This option configures the metadata sources. The metadata sources is given as an array with + * different metadata sources. When searching for metadata, SimpleSAMLphp will search through + * the array from start to end. + * + * Each element in the array is an associative array which configures the metadata source. + * The type of the metadata source is given by the 'type' element. For each type we have + * different configuration options. + * + * Flat file metadata handler: + * - 'type': This is always 'flatfile'. + * - 'directory': The directory we will load the metadata files from. The default value for + * this option is the value of the 'metadatadir' configuration option, or + * 'metadata/' if that option is unset. + * + * XML metadata handler: + * This metadata handler parses an XML file with either an EntityDescriptor element or an + * EntitiesDescriptor element. The XML file may be stored locally, or (for debugging) on a remote + * web server. + * The XML metadata handler defines the following options: + * - 'type': This is always 'xml'. + * - 'file': Path to the XML file with the metadata. + * - 'url': The URL to fetch metadata from. THIS IS ONLY FOR DEBUGGING - THERE IS NO CACHING OF THE RESPONSE. + * + * MDQ metadata handler: + * This metadata handler looks up for the metadata of an entity at the given MDQ server. + * The MDQ metadata handler defines the following options: + * - 'type': This is always 'mdq'. + * - 'server': Base URL of the MDQ server. Mandatory. + * - 'validateCertificate': The certificates file that may be used to sign the metadata. You don't need this + * option if you don't want to validate the signature on the metadata. Optional. + * - 'cachedir': Directory where metadata can be cached. Optional. + * - 'cachelength': Maximum time metadata can be cached, in seconds. Defaults to 24 + * hours (86400 seconds). Optional. + * + * PDO metadata handler: + * This metadata handler looks up metadata of an entity stored in a database. + * + * Note: If you are using the PDO metadata handler, you must configure the database + * options in this configuration file. + * + * The PDO metadata handler defines the following options: + * - 'type': This is always 'pdo'. + * + * Examples: + * + * This example defines two flatfile sources. One is the default metadata directory, the other + * is a metadata directory with auto-generated metadata files. + * + * 'metadata.sources' => [ + * ['type' => 'flatfile'], + * ['type' => 'flatfile', 'directory' => 'metadata-generated'], + * ], + * + * This example defines a flatfile source and an XML source. + * 'metadata.sources' => [ + * ['type' => 'flatfile'], + * ['type' => 'xml', 'file' => 'idp.example.org-idpMeta.xml'], + * ], + * + * This example defines an mdq source. + * 'metadata.sources' => [ + * [ + * 'type' => 'mdq', + * 'server' => 'http://mdq.server.com:8080', + * 'validateCertificate' => [ + * '/var/simplesamlphp/cert/metadata-key.new.crt', + * '/var/simplesamlphp/cert/metadata-key.old.crt' + * ], + * 'cachedir' => '/var/simplesamlphp/mdq-cache', + * 'cachelength' => 86400 + * ] + * ], + * + * This example defines an pdo source. + * 'metadata.sources' => [ + * ['type' => 'pdo'] + * ], + * + * Default: + * 'metadata.sources' => [ + * ['type' => 'flatfile'] + * ], + */ + 'metadata.sources' => [ + ['type' => 'flatfile'], + ], + + /* + * Should signing of generated metadata be enabled by default. + * + * Metadata signing can also be enabled for a individual SP or IdP by setting the + * same option in the metadata for the SP or IdP. + */ + 'metadata.sign.enable' => false, + + /* + * The default key & certificate which should be used to sign generated metadata. These + * are files stored in the cert dir. + * These values can be overridden by the options with the same names in the SP or + * IdP metadata. + * + * If these aren't specified here or in the metadata for the SP or IdP, then + * the 'certificate' and 'privatekey' option in the metadata will be used. + * if those aren't set, signing of metadata will fail. + */ + 'metadata.sign.privatekey' => null, + 'metadata.sign.privatekey_pass' => null, + 'metadata.sign.certificate' => null, + 'metadata.sign.algorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', + + + /**************************** + | DATA STORE CONFIGURATION | + ****************************/ + + /* + * Configure the data store for SimpleSAMLphp. + * + * - 'phpsession': Limited datastore, which uses the PHP session. + * - 'memcache': Key-value datastore, based on memcache. + * - 'sql': SQL datastore, using PDO. + * - 'redis': Key-value datastore, based on redis. + * + * The default datastore is 'phpsession'. + */ + 'store.type' => 'phpsession', + + /* + * The DSN the sql datastore should connect to. + * + * See http://www.php.net/manual/en/pdo.drivers.php for the various + * syntaxes. + */ + 'store.sql.dsn' => 'sqlite:/path/to/sqlitedatabase.sq3', + + /* + * The username and password to use when connecting to the database. + */ + 'store.sql.username' => null, + 'store.sql.password' => null, + + /* + * The prefix we should use on our tables. + */ + 'store.sql.prefix' => 'SimpleSAMLphp', + + /* + * The driver-options we should pass to the PDO-constructor. + */ + 'store.sql.options' => [], + + /* + * The hostname and port of the Redis datastore instance. + */ + 'store.redis.host' => 'localhost', + 'store.redis.port' => 6379, + + /* + * The credentials to use when connecting to Redis. + * + * If your Redis server is using the legacy password protection (config + * directive "requirepass" in redis.conf) then you should only provide + * a password. + * + * If your Redis server is using ACL's (which are recommended as of + * Redis 6+) then you should provide both a username and a password. + * See https://redis.io/docs/manual/security/acl/ + */ + 'store.redis.username' => '', + 'store.redis.password' => '', + + /* + * Communicate with Redis over a secure connection instead of plain TCP. + * + * This setting affects both single host connections as + * well as Sentinel mode. + */ + 'store.redis.tls' => false, + + /* + * Verify the Redis server certificate. + */ + 'store.redis.insecure' => false, + + /* + * Files related to secure communication with Redis. + * + * Files are searched in the 'certdir' when using relative paths. + */ + 'store.redis.ca_certificate' => null, + 'store.redis.certificate' => null, + 'store.redis.privatekey' => null, + + /* + * The prefix we should use on our Redis datastore. + */ + 'store.redis.prefix' => 'SimpleSAMLphp', + + /* + * The master group to use for Redis Sentinel. + */ + 'store.redis.mastergroup' => 'mymaster', + + /* + * The Redis Sentinel hosts. + * Example: + * 'store.redis.sentinels' => [ + * 'tcp://[yoursentinel1]:[port]', + * 'tcp://[yoursentinel2]:[port]', + * 'tcp://[yoursentinel3]:[port] + * ], + * + * Use 'tls' instead of 'tcp' in order to make use of the additional + * TLS settings. + */ + 'store.redis.sentinels' => [], + + /********************* + | IdP/SP PROXY MODE | + *********************/ + + /* + * If the IdP in front of SimpleSAMLphp in IdP/SP proxy mode sends + * AuthnContextClassRef, decide whether the AuthnContextClassRef will be + * processed by the IdP/SP proxy or if it will be passed to the SP behind + * the IdP/SP proxy. + */ + 'proxymode.passAuthnContextClassRef' => false, +]; diff --git a/tests/config/module_oidc.php b/tests/config/module_oidc.php index d7c01fcc..31c5eb16 100644 --- a/tests/config/module_oidc.php +++ b/tests/config/module_oidc.php @@ -17,6 +17,8 @@ use SimpleSAML\Module\oidc\ModuleConfig; $config = [ + ModuleConfig::OPTION_ISSUER => 'http://test.issuer', + ModuleConfig::OPTION_TOKEN_AUTHORIZATION_CODE_TTL => 'PT10M', ModuleConfig::OPTION_TOKEN_REFRESH_TOKEN_TTL => 'P1M', ModuleConfig::OPTION_TOKEN_ACCESS_TOKEN_TTL => 'PT1H', @@ -41,4 +43,21 @@ ], ModuleConfig::OPTION_AUTH_FORCED_ACR_VALUE_FOR_COOKIE_AUTHENTICATION => null, + + ModuleConfig::OPTION_FEDERATION_TOKEN_SIGNER => Sha256::class, + ModuleConfig::OPTION_PKI_FEDERATION_PRIVATE_KEY_FILENAME => + ModuleConfig::DEFAULT_PKI_FEDERATION_PRIVATE_KEY_FILENAME, + ModuleConfig::OPTION_PKI_FEDERATION_PRIVATE_KEY_PASSPHRASE => 'abc123', + ModuleConfig::OPTION_PKI_FEDERATION_CERTIFICATE_FILENAME => + ModuleConfig::DEFAULT_PKI_FEDERATION_CERTIFICATE_FILENAME, + ModuleConfig::OPTION_FEDERATION_AUTHORITY_HINTS => [ + 'abc123', + ], + ModuleConfig::OPTION_ORGANIZATION_NAME => 'Foo corp', + ModuleConfig::OPTION_CONTACTS => [ + 'John Doe jdoe@example.org', + ], + ModuleConfig::OPTION_LOGO_URI => 'https://example.org/logo', + ModuleConfig::OPTION_POLICY_URI => 'https://example.org/policy', + ModuleConfig::OPTION_HOMEPAGE_URI => 'https://example.org', ]; diff --git a/tests/src/Controller/Client/EditControllerTest.php b/tests/src/Controller/Client/EditControllerTest.php index 7b475d69..e7178d86 100644 --- a/tests/src/Controller/Client/EditControllerTest.php +++ b/tests/src/Controller/Client/EditControllerTest.php @@ -62,7 +62,7 @@ protected function setUp(): void $this->templateStub = $this->createStub(Template::class); $this->clientFormMock = $this->createMock(ClientForm::class); - $this->moduleConfigMock->method('getOpenIdConnectModuleURL')->willReturn('url'); + $this->moduleConfigMock->method('getModuleUrl')->willReturn('url'); $this->uriStub->method('getPath')->willReturn('/'); $this->serverRequestMock->method('getUri')->willReturn($this->uriStub); $this->serverRequestMock->method('withQueryParams')->willReturn($this->serverRequestMock); diff --git a/tests/src/Controller/Client/ResetSecretControllerTest.php b/tests/src/Controller/Client/ResetSecretControllerTest.php index 0e64b8ba..5583e7d9 100644 --- a/tests/src/Controller/Client/ResetSecretControllerTest.php +++ b/tests/src/Controller/Client/ResetSecretControllerTest.php @@ -14,6 +14,7 @@ use SimpleSAML\Module\oidc\Controller\Client\ResetSecretController; use SimpleSAML\Module\oidc\Entities\ClientEntity; use SimpleSAML\Module\oidc\Repositories\ClientRepository; +use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\AuthContextService; use SimpleSAML\Module\oidc\Services\SessionMessagesService; @@ -43,13 +44,6 @@ protected function setUp(): void $this->clientEntityMock = $this->createMock(ClientEntity::class); } - public static function setUpBeforeClass(): void - { - // To make lib/SimpleSAML/Utils/HTTP::getSelfURL() work... - global $_SERVER; - $_SERVER['REQUEST_URI'] = ''; - } - protected function prepareStubbedInstance(): \SimpleSAML\Module\oidc\Controller\Client\ResetSecretController { return new ResetSecretController( @@ -91,7 +85,7 @@ public function testItThrowsClientNotFoundExceptionInResetSecretAction(): void ->with('clientid') ->willReturn(null); - $this->expectException(NotFound::class); + $this->expectException(OidcServerException::class); $this->prepareStubbedInstance()->__invoke($this->serverRequestMock); } diff --git a/tests/src/Controller/Client/ShowControllerTest.php b/tests/src/Controller/Client/ShowControllerTest.php index 2cee7d75..4b4b14c1 100644 --- a/tests/src/Controller/Client/ShowControllerTest.php +++ b/tests/src/Controller/Client/ShowControllerTest.php @@ -50,13 +50,6 @@ protected function setUp(): void $this->templateMock = $this->createMock(Template::class); } - public static function setUpBeforeClass(): void - { - // To make lib/SimpleSAML/Utils/HTTP::getSelfURL() work... - global $_SERVER; - $_SERVER['REQUEST_URI'] = ''; - } - protected function getStubbedInstance(): ShowController { return new ShowController( @@ -144,7 +137,7 @@ public function testItThrowsClientNotFoundException(): void ->with('clientid') ->willReturn(null); - $this->expectException(NotFound::class); + $this->expectException(OidcServerException::class); $this->getStubbedInstance()->__invoke($this->serverRequestMock); } } diff --git a/tests/src/Controller/JwksControllerTest.php b/tests/src/Controller/JwksControllerTest.php index 41943d9c..66d09549 100644 --- a/tests/src/Controller/JwksControllerTest.php +++ b/tests/src/Controller/JwksControllerTest.php @@ -49,7 +49,7 @@ public function testItReturnsJsonKeys(): void ], ]; - $this->jsonWebKeySetServiceMock->expects($this->once())->method('keys')->willReturn($keys); + $this->jsonWebKeySetServiceMock->expects($this->once())->method('protocolKeys')->willReturn($keys); $this->assertSame( ['keys' => $keys], diff --git a/tests/src/Entities/AccessTokenEntityTest.php b/tests/src/Entities/AccessTokenEntityTest.php index 5d2d6897..8877b0c3 100644 --- a/tests/src/Entities/AccessTokenEntityTest.php +++ b/tests/src/Entities/AccessTokenEntityTest.php @@ -47,13 +47,6 @@ class AccessTokenEntityTest extends TestCase */ protected ScopeEntity $scopeEntityProfile; - public static function setUpBeforeClass(): void - { - // To make lib/SimpleSAML/Utils/HTTP::getSelfURL() work... - global $_SERVER; - $_SERVER['REQUEST_URI'] = ''; - } - /** * @throws Exception * @throws JsonException diff --git a/tests/src/ModuleConfigTest.php b/tests/src/ModuleConfigTest.php index 28893b0c..a0472120 100644 --- a/tests/src/ModuleConfigTest.php +++ b/tests/src/ModuleConfigTest.php @@ -5,53 +5,279 @@ namespace SimpleSAML\Test\Module\oidc; use Exception; +use Lcobucci\JWT\Signer; +use Lcobucci\JWT\Signer\Rsa\Sha256; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; +use SimpleSAML\Error\ConfigurationError; +use SimpleSAML\Module\oidc\Bridges\SspBridge; use SimpleSAML\Module\oidc\ModuleConfig; +use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; +use SimpleSAML\Utils\Config; +use SimpleSAML\Utils\HTTP; -/** - * @covers \SimpleSAML\Module\oidc\ModuleConfig - */ +#[CoversClass(ModuleConfig::class)] class ModuleConfigTest extends TestCase { + protected string $fileName; + protected array $overrides; + protected MockObject $sspConfigMock; + + protected array $moduleConfig = [ + ModuleConfig::OPTION_ISSUER => 'http://test.issuer', + + ModuleConfig::OPTION_TOKEN_AUTHORIZATION_CODE_TTL => 'PT10M', + ModuleConfig::OPTION_TOKEN_REFRESH_TOKEN_TTL => 'P1M', + ModuleConfig::OPTION_TOKEN_ACCESS_TOKEN_TTL => 'PT1H', + + ModuleConfig::OPTION_CRON_TAG => 'hourly', + + ModuleConfig::OPTION_TOKEN_SIGNER => Sha256::class, + + ModuleConfig::OPTION_AUTH_SOURCE => 'default-sp', + + ModuleConfig::OPTION_AUTH_USER_IDENTIFIER_ATTRIBUTE => 'uid', + + ModuleConfig::OPTION_AUTH_CUSTOM_SCOPES => [ + ], + ModuleConfig::OPTION_AUTH_SAML_TO_OIDC_TRANSLATE_TABLE => [ + ], + + ModuleConfig::OPTION_AUTH_ACR_VALUES_SUPPORTED => [ + ], + + ModuleConfig::OPTION_AUTH_SOURCES_TO_ACR_VALUES_MAP => [ + ], + + ModuleConfig::OPTION_AUTH_FORCED_ACR_VALUE_FOR_COOKIE_AUTHENTICATION => null, + + ModuleConfig::OPTION_FEDERATION_TOKEN_SIGNER => Sha256::class, + ModuleConfig::OPTION_PKI_FEDERATION_PRIVATE_KEY_FILENAME => + ModuleConfig::DEFAULT_PKI_FEDERATION_PRIVATE_KEY_FILENAME, + ModuleConfig::OPTION_PKI_FEDERATION_PRIVATE_KEY_PASSPHRASE => 'abc123', + ModuleConfig::OPTION_PKI_FEDERATION_CERTIFICATE_FILENAME => + ModuleConfig::DEFAULT_PKI_FEDERATION_CERTIFICATE_FILENAME, + ModuleConfig::OPTION_FEDERATION_AUTHORITY_HINTS => [ + 'abc123', + ], + ]; + private MockObject $sspBridgeMock; + private MockObject $sspBridgeUtilsMock; + private MockObject $sspBridgeUtilsHttpMock; + private MockObject $sspBridgeModuleMock; + private MockObject $sspBridgeUtilsConfigMock; + + protected function setUp(): void + { + $this->fileName = ModuleConfig::DEFAULT_FILE_NAME; + $this->sspConfigMock = $this->createMock(Configuration::class); + $this->overrides = []; + + $this->sspBridgeMock = $this->createMock(SspBridge::class); + + $this->sspBridgeUtilsMock = $this->createMock(SspBridge\Utils::class); + + $this->sspBridgeUtilsConfigMock = $this->createMock(Config::class); + $this->sspBridgeUtilsConfigMock->method('getCertPath') + ->willReturnCallback(fn(string $filename): string => '/path/to/cert' . $filename); + $this->sspBridgeUtilsHttpMock = $this->createMock(HTTP::class); + + $this->sspBridgeModuleMock = $this->createMock(SspBridge\Module::class); + $this->sspBridgeModuleMock->method('getModuleUrl') + ->willReturn('http://sample.test/' . ModuleConfig::MODULE_NAME); + + $this->sspBridgeMock->method('utils')->willReturn($this->sspBridgeUtilsMock); + $this->sspBridgeMock->method('module')->willReturn($this->sspBridgeModuleMock); + + $this->sspBridgeUtilsMock->method('http')->willReturn($this->sspBridgeUtilsHttpMock); + $this->sspBridgeUtilsMock->method('config')->willReturn($this->sspBridgeUtilsConfigMock); + } + + protected function mock(): ModuleConfig + { + return new ModuleConfig($this->fileName, $this->overrides, $this->sspConfigMock, $this->sspBridgeMock); + } + /** * @throws Exception */ public function testSigningKeyNameCanBeCustomized(): void { - $certDir = '/tmp/cert/'; - Configuration::clearInternalState(); - Configuration::setPreLoadedConfig( - Configuration::loadFromArray( - [ - 'certdir' => $certDir, - ], - ), - ); - Configuration::setPreLoadedConfig( - Configuration::loadFromArray([]), - ModuleConfig::DEFAULT_FILE_NAME, - ); // Test default cert and pem - $moduleConfig = new ModuleConfig(); - $this->assertEquals($certDir . ModuleConfig::DEFAULT_PKI_CERTIFICATE_FILENAME, $moduleConfig->getCertPath()); - $this->assertEquals( - $certDir . ModuleConfig::DEFAULT_PKI_PRIVATE_KEY_FILENAME, - $moduleConfig->getPrivateKeyPath(), + $this->assertStringContainsString( + ModuleConfig::DEFAULT_PKI_CERTIFICATE_FILENAME, + $this->mock()->getProtocolCertPath(), + ); + $this->assertStringContainsString( + ModuleConfig::DEFAULT_PKI_PRIVATE_KEY_FILENAME, + $this->mock()->getProtocolPrivateKeyPath(), ); // Set customized - Configuration::setPreLoadedConfig( - Configuration::loadFromArray( - [ - ModuleConfig::OPTION_PKI_PRIVATE_KEY_FILENAME => 'myPrivateKey.key', - ModuleConfig::OPTION_PKI_CERTIFICATE_FILENAME => 'myCertificate.crt', - ], - ), - ModuleConfig::DEFAULT_FILE_NAME, + $this->overrides[ModuleConfig::OPTION_PKI_PRIVATE_KEY_FILENAME] = 'myPrivateKey.key'; + $this->overrides[ModuleConfig::OPTION_PKI_CERTIFICATE_FILENAME] = 'myCertificate.crt'; + $this->assertStringContainsString('myCertificate.crt', $this->mock()->getProtocolCertPath()); + $this->assertStringContainsString('myPrivateKey.key', $this->mock()->getProtocolPrivateKeyPath()); + } + + public function testCanGetSspConfig(): void + { + $this->assertInstanceOf(Configuration::class, $this->mock()->sspConfig()); + } + + public function testCanGetModuleUrl(): void + { + $this->assertStringContainsString(ModuleConfig::MODULE_NAME, $this->mock()->getModuleUrl('test')); + } + + public function testCanGetOpenIdScopes(): void + { + $this->assertNotEmpty($this->mock()->getOpenIDScopes()); + } + + public function testCanGetProtocolSigner(): void + { + $this->assertInstanceOf(Signer::class, $this->mock()->getProtocolSigner()); + } + + public function testCanGetProtocolPrivateKeyPassphrase(): void + { + $this->overrides[ModuleConfig::OPTION_PKI_PRIVATE_KEY_PASSPHRASE] = 'test'; + $this->assertNotEmpty($this->mock()->getProtocolPrivateKeyPassPhrase()); + } + + public function testCanGetAuthProcFilters(): void + { + $this->assertIsArray($this->mock()->getAuthProcFilters()); + } + + public function testCanGetIssuer(): void + { + $this->assertNotEmpty($this->mock()->getIssuer()); + } + + public function testGetsCurrentHostIfIssuerNotSetInConfig(): void + { + $this->sspBridgeUtilsHttpMock->expects($this->once())->method('getSelfURLHost') + ->willReturn('sample'); + $this->overrides[ModuleConfig::OPTION_ISSUER] = null; + $this->mock()->getIssuer(); + } + + public function testThrowsOnEmptyIssuer(): void + { + $this->overrides[ModuleConfig::OPTION_ISSUER] = ''; + $this->expectException(OidcServerException::class); + + $this->mock()->getIssuer(); + } + + public function testCanGetForcedAcrValueForCookieAuthentication(): void + { + $this->overrides[ModuleConfig::OPTION_AUTH_FORCED_ACR_VALUE_FOR_COOKIE_AUTHENTICATION] = '1a'; + $this->overrides[ModuleConfig::OPTION_AUTH_ACR_VALUES_SUPPORTED] = ['1a']; + $this->assertEquals('1a', $this->mock()->getForcedAcrValueForCookieAuthentication()); + } + + public function testCanGetUserIdentifierAttribute(): void + { + $this->overrides[ModuleConfig::OPTION_AUTH_USER_IDENTIFIER_ATTRIBUTE] = 'sample'; + $this->assertEquals('sample', $this->mock()->getUserIdentifierAttribute()); + } + + public function testCanGetCommonFederationOptions(): void + { + $this->assertInstanceOf(Signer::class, $this->mock()->getFederationSigner()); + $this->assertStringContainsString( + ModuleConfig::DEFAULT_PKI_FEDERATION_PRIVATE_KEY_FILENAME, + $this->mock()->getFederationPrivateKeyPath(), + ); + $this->assertNotEmpty($this->mock()->getFederationPrivateKeyPassPhrase()); + $this->assertStringContainsString( + ModuleConfig::DEFAULT_PKI_FEDERATION_CERTIFICATE_FILENAME, + $this->mock()->getFederationCertPath(), ); - $moduleConfig = new ModuleConfig(); - $this->assertEquals($certDir . 'myCertificate.crt', $moduleConfig->getCertPath()); - $this->assertEquals($certDir . 'myPrivateKey.key', $moduleConfig->getPrivateKeyPath()); + $this->assertNotEmpty($this->mock()->getFederationEntityStatementDuration()); + $this->assertNotEmpty($this->mock()->getFederationAuthorityHints()); + $this->assertNotEmpty($this->mock()->getOrganizationName()); + $this->assertNotEmpty($this->mock()->getContacts()); + $this->assertNotEmpty($this->mock()->getLogoUri()); + $this->assertNotEmpty($this->mock()->getPolicyUri()); + $this->assertNotEmpty($this->mock()->getHomepageUri()); + } + + public function testThrowsIfTryingToOverrideProtectedScopes(): void + { + $this->overrides[ModuleConfig::OPTION_AUTH_CUSTOM_SCOPES] = [ + 'openid' => [ + 'description' => 'openid', + ], + ]; + + $this->expectException(ConfigurationError::class); + $this->mock(); + } + + public function testThrowsIfCustomScopeDoesNotHaveDescription(): void + { + $this->overrides[ModuleConfig::OPTION_AUTH_CUSTOM_SCOPES] = [ + 'custom' => [], + ]; + + $this->expectException(ConfigurationError::class); + $this->mock(); + } + + public function testThrowsIfAcrIsNotString(): void + { + $this->overrides[ModuleConfig::OPTION_AUTH_ACR_VALUES_SUPPORTED] = [123]; + + $this->expectException(ConfigurationError::class); + $this->mock(); + } + + public function testThrowsIfAuthSourceNotString(): void + { + $this->overrides[ModuleConfig::OPTION_AUTH_SOURCES_TO_ACR_VALUES_MAP] = [123 => []]; + $this->expectException(ConfigurationError::class); + $this->mock(); + } + + public function testThrowsIfAuthSourceToAcrMapAcrNotArray(): void + { + $this->overrides[ModuleConfig::OPTION_AUTH_SOURCES_TO_ACR_VALUES_MAP] = ['abc' => 123]; + $this->expectException(ConfigurationError::class); + $this->mock(); + } + + public function testThrowsIfAuthSourceToAcrMapAcrNotString(): void + { + $this->overrides[ModuleConfig::OPTION_AUTH_SOURCES_TO_ACR_VALUES_MAP] = ['abc' => [123]]; + $this->expectException(ConfigurationError::class); + $this->mock(); + } + + public function testThrowsIfAuthSourceToAcrMapAcrNotAllowed(): void + { + $this->overrides[ModuleConfig::OPTION_AUTH_SOURCES_TO_ACR_VALUES_MAP] = ['abc' => ['acr']]; + $this->expectException(ConfigurationError::class); + $this->mock(); + } + + public function testThrowsIForcedAcrValueForCookieAuthenticationNotAllowed(): void + { + $this->overrides[ModuleConfig::OPTION_AUTH_ACR_VALUES_SUPPORTED] = ['abc']; + $this->overrides[ModuleConfig::OPTION_AUTH_FORCED_ACR_VALUE_FOR_COOKIE_AUTHENTICATION] = 'cba'; + $this->expectException(ConfigurationError::class); + $this->mock(); + } + + public function testThrowsIfInvalidSignerProvided(): void + { + $this->overrides[ModuleConfig::OPTION_TOKEN_SIGNER] = \stdClass::class; + $this->expectException(ConfigurationError::class); + $this->mock()->getProtocolSigner(); } } diff --git a/tests/src/Server/ResponseTypes/IdTokenResponseTest.php b/tests/src/Server/ResponseTypes/IdTokenResponseTest.php index 44597285..63d306d3 100644 --- a/tests/src/Server/ResponseTypes/IdTokenResponseTest.php +++ b/tests/src/Server/ResponseTypes/IdTokenResponseTest.php @@ -94,15 +94,15 @@ protected function setUp(): void ->willReturn($this->userEntity); $this->moduleConfigMock = $this->createMock(ModuleConfig::class); - $this->moduleConfigMock->method('getSigner')->willReturn(new Sha256()); - $this->moduleConfigMock->method('getSimpleSAMLSelfURLHost')->willReturn(self::ISSUER); - $this->moduleConfigMock->method('getCertPath') + $this->moduleConfigMock->method('getProtocolSigner')->willReturn(new Sha256()); + $this->moduleConfigMock->method('getIssuer')->willReturn(self::ISSUER); + $this->moduleConfigMock->method('getProtocolCertPath') ->willReturn($this->certFolder . '/oidc_module.crt'); - $this->moduleConfigMock->method('getPrivateKeyPath') + $this->moduleConfigMock->method('getProtocolPrivateKeyPath') ->willReturn($this->certFolder . '/oidc_module.key'); $this->moduleConfigMock ->expects($this->atLeast(1)) - ->method('getPrivateKeyPassPhrase'); + ->method('getProtocolPrivateKeyPassPhrase'); $this->sspConfigurationMock = $this->createMock(Configuration::class); $this->moduleConfigMock->method('config') ->willReturn($this->sspConfigurationMock); diff --git a/tests/src/Services/JsonWebKeySetServiceTest.php b/tests/src/Services/JsonWebKeySetServiceTest.php index d99a8164..9d86fccc 100644 --- a/tests/src/Services/JsonWebKeySetServiceTest.php +++ b/tests/src/Services/JsonWebKeySetServiceTest.php @@ -86,7 +86,7 @@ public function testKeys() $jsonWebKeySetService = new JsonWebKeySetService(new ModuleConfig()); - $this->assertEquals($JWKSet->all(), $jsonWebKeySetService->keys()); + $this->assertEquals($JWKSet->all(), $jsonWebKeySetService->protocolKeys()); } /** @@ -95,7 +95,7 @@ public function testKeys() public function testCertificationFileNotFound(): void { $this->expectException(Exception::class); - $this->expectExceptionMessageMatches('/OpenId Connect certification file does not exists/'); + $this->expectExceptionMessageMatches('/OIDC protocol public key file does not exists/'); $config = [ 'certdir' => __DIR__, diff --git a/tests/src/Services/JsonWebTokenBuilderServiceTest.php b/tests/src/Services/JsonWebTokenBuilderServiceTest.php index f2a5d366..587374e5 100644 --- a/tests/src/Services/JsonWebTokenBuilderServiceTest.php +++ b/tests/src/Services/JsonWebTokenBuilderServiceTest.php @@ -48,10 +48,10 @@ public static function setUpBeforeClass(): void public function setUp(): void { $this->moduleConfigStub = $this->createStub(ModuleConfig::class); - $this->moduleConfigStub->method('getSigner')->willReturn(self::$signerSha256); - $this->moduleConfigStub->method('getPrivateKeyPath')->willReturn(self::$privateKeyPath); - $this->moduleConfigStub->method('getCertPath')->willReturn(self::$publicKeyPath); - $this->moduleConfigStub->method('getSimpleSAMLSelfURLHost')->willReturn(self::$selfUrlHost); + $this->moduleConfigStub->method('getProtocolSigner')->willReturn(self::$signerSha256); + $this->moduleConfigStub->method('getProtocolPrivateKeyPath')->willReturn(self::$privateKeyPath); + $this->moduleConfigStub->method('getProtocolCertPath')->willReturn(self::$publicKeyPath); + $this->moduleConfigStub->method('getIssuer')->willReturn(self::$selfUrlHost); } /** @@ -64,7 +64,7 @@ public function testCanCreateBuilderInstance(): void $this->assertInstanceOf( Builder::class, - $builderService->getDefaultJwtTokenBuilder(), + $builderService->getProtocolJwtBuilder(), ); } @@ -76,9 +76,9 @@ public function testCanCreateBuilderInstance(): void public function testCanGenerateSignedJwtToken(): void { $builderService = new JsonWebTokenBuilderService($this->moduleConfigStub); - $tokenBuilder = $builderService->getDefaultJwtTokenBuilder(); + $tokenBuilder = $builderService->getProtocolJwtBuilder(); - $unencryptedToken = $builderService->getSignedJwtTokenFromBuilder($tokenBuilder); + $unencryptedToken = $builderService->getSignedProtocolJwt($tokenBuilder); $this->assertInstanceOf(UnencryptedToken::class, $unencryptedToken); $this->assertSame(self::$selfUrlHost, $unencryptedToken->claims()->get('iss')); @@ -87,12 +87,12 @@ public function testCanGenerateSignedJwtToken(): void $token = $unencryptedToken->toString(); $jwtConfig = Configuration::forAsymmetricSigner( - $this->moduleConfigStub->getSigner(), + $this->moduleConfigStub->getProtocolSigner(), InMemory::file( - $this->moduleConfigStub->getPrivateKeyPath(), - $this->moduleConfigStub->getPrivateKeyPassPhrase() ?? '', + $this->moduleConfigStub->getProtocolPrivateKeyPath(), + $this->moduleConfigStub->getProtocolPrivateKeyPassPhrase() ?? '', ), - InMemory::file($this->moduleConfigStub->getCertPath()), + InMemory::file($this->moduleConfigStub->getProtocolCertPath()), ); $parsedToken = $jwtConfig->parser()->parse($token); @@ -102,8 +102,8 @@ public function testCanGenerateSignedJwtToken(): void $parsedToken, new IssuedBy(self::$selfUrlHost), new SignedWith( - $this->moduleConfigStub->getSigner(), - InMemory::file($this->moduleConfigStub->getCertPath()), + $this->moduleConfigStub->getProtocolSigner(), + InMemory::file($this->moduleConfigStub->getProtocolCertPath()), ), ), ); @@ -116,7 +116,7 @@ public function testCanReturnCurrentSigner(): void { $this->assertSame( self::$signerSha256, - (new JsonWebTokenBuilderService($this->moduleConfigStub))->getSigner(), + (new JsonWebTokenBuilderService($this->moduleConfigStub))->getProtocolSigner(), ); } } diff --git a/tests/src/Services/LogoutTokenBuilderTest.php b/tests/src/Services/LogoutTokenBuilderTest.php index 51c1d468..65dc51ef 100644 --- a/tests/src/Services/LogoutTokenBuilderTest.php +++ b/tests/src/Services/LogoutTokenBuilderTest.php @@ -62,10 +62,10 @@ public static function setUpBeforeClass(): void public function setUp(): void { $this->moduleConfigStub = $this->createStub(ModuleConfig::class); - $this->moduleConfigStub->method('getSigner')->willReturn(self::$signerSha256); - $this->moduleConfigStub->method('getPrivateKeyPath')->willReturn(self::$privateKeyPath); - $this->moduleConfigStub->method('getCertPath')->willReturn(self::$publicKeyPath); - $this->moduleConfigStub->method('getSimpleSAMLSelfURLHost')->willReturn(self::$selfUrlHost); + $this->moduleConfigStub->method('getProtocolSigner')->willReturn(self::$signerSha256); + $this->moduleConfigStub->method('getProtocolPrivateKeyPath')->willReturn(self::$privateKeyPath); + $this->moduleConfigStub->method('getProtocolCertPath')->willReturn(self::$publicKeyPath); + $this->moduleConfigStub->method('getIssuer')->willReturn(self::$selfUrlHost); $this->relyingPartyAssociationStub = $this->createStub(RelyingPartyAssociationInterface::class); $this->relyingPartyAssociationStub->method('getClientId')->willReturn(self::$clientId); @@ -90,12 +90,12 @@ public function testCanGenerateSignedTokenForRelyingPartyAssociation(): void // Check token validity $jwtConfig = Configuration::forAsymmetricSigner( - $this->moduleConfigStub->getSigner(), + $this->moduleConfigStub->getProtocolSigner(), InMemory::file( - $this->moduleConfigStub->getPrivateKeyPath(), - $this->moduleConfigStub->getPrivateKeyPassPhrase() ?? '', + $this->moduleConfigStub->getProtocolPrivateKeyPath(), + $this->moduleConfigStub->getProtocolPrivateKeyPassPhrase() ?? '', ), - InMemory::file($this->moduleConfigStub->getCertPath()), + InMemory::file($this->moduleConfigStub->getProtocolCertPath()), ); $parsedToken = $jwtConfig->parser()->parse($token); @@ -107,8 +107,8 @@ public function testCanGenerateSignedTokenForRelyingPartyAssociation(): void new PermittedFor(self::$clientId), new RelatedTo(self::$userId), new SignedWith( - $this->moduleConfigStub->getSigner(), - InMemory::file($this->moduleConfigStub->getCertPath()), + $this->moduleConfigStub->getProtocolSigner(), + InMemory::file($this->moduleConfigStub->getProtocolCertPath()), ), ), ); diff --git a/tests/src/Services/OpMetadataServiceTest.php b/tests/src/Services/OpMetadataServiceTest.php index c94cf06c..af74b3c7 100644 --- a/tests/src/Services/OpMetadataServiceTest.php +++ b/tests/src/Services/OpMetadataServiceTest.php @@ -4,6 +4,7 @@ namespace SimpleSAML\Test\Module\oidc\Services; +use Lcobucci\JWT\Signer\Rsa; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use SimpleSAML\Module\oidc\ModuleConfig; @@ -26,9 +27,9 @@ public function setUp(): void $this->moduleConfigMock->expects($this->once())->method('getOpenIDScopes') ->willReturn(['openid' => 'openid']); - $this->moduleConfigMock->expects($this->once())->method('getSimpleSAMLSelfURLHost') + $this->moduleConfigMock->expects($this->once())->method('getIssuer') ->willReturn('http://localhost'); - $this->moduleConfigMock->method('getOpenIdConnectModuleURL') + $this->moduleConfigMock->method('getModuleUrl') ->willReturnCallback(function ($path) { $paths = [ 'authorize.php' => 'http://localhost/authorize.php', @@ -41,6 +42,10 @@ public function setUp(): void return $paths[$path] ?? null; }); $this->moduleConfigMock->method('getAcrValuesSupported')->willReturn(['1']); + + $signer = $this->createMock(Rsa::class); + $signer->method('algorithmId')->willReturn('RS256'); + $this->moduleConfigMock->method('getProtocolSigner')->willReturn($signer); } /** diff --git a/tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php b/tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php index d0fc1289..45180c6a 100644 --- a/tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php +++ b/tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php @@ -64,15 +64,15 @@ protected function setUp(): void $this->resultBagStub = $this->createStub(ResultBagInterface::class); $this->moduleConfigStub = $this->createStub(ModuleConfig::class); - $this->moduleConfigStub->method('getSigner')->willReturn(new Sha256()); - $this->moduleConfigStub->method('getSimpleSAMLSelfURLHost')->willReturn(self::$issuer); + $this->moduleConfigStub->method('getProtocolSigner')->willReturn(new Sha256()); + $this->moduleConfigStub->method('getIssuer')->willReturn(self::$issuer); $this->cryptKeyFactoryStub = $this->createStub(CryptKeyFactory::class); $this->cryptKeyFactoryStub->method('buildPrivateKey')->willReturn(self::$privateKey); $this->cryptKeyFactoryStub->method('buildPublicKey')->willReturn(self::$publicKey); $this->jwtConfig = Configuration::forAsymmetricSigner( - $this->moduleConfigStub->getSigner(), + $this->moduleConfigStub->getProtocolSigner(), InMemory::plainText(self::$privateKey->getKeyContents()), InMemory::plainText(self::$publicKey->getKeyContents()), ); @@ -145,7 +145,7 @@ public function testCheckRuleThrowsForIdTokenWithInvalidIssuer(): void $this->requestStub->method('getMethod')->willReturn('GET'); $invalidIssuerJwt = $this->jwtConfig->builder()->issuedBy('invalid')->getToken( - $this->moduleConfigStub->getSigner(), + $this->moduleConfigStub->getProtocolSigner(), InMemory::plainText(self::$privateKey->getKeyContents()), )->toString(); @@ -165,7 +165,7 @@ public function testCheckRulePassesForValidIdToken(): void $this->requestStub->method('getMethod')->willReturn('GET'); $idToken = $this->jwtConfig->builder()->issuedBy(self::$issuer)->getToken( - $this->moduleConfigStub->getSigner(), + $this->moduleConfigStub->getProtocolSigner(), InMemory::plainText(self::$privateKey->getKeyContents()), )->toString(); From 0ffb51f095327da1ca8dec2db280db316af51770 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos <3846025+ioigoume@users.noreply.github.com> Date: Fri, 24 May 2024 20:06:43 +0300 Subject: [PATCH 07/70] ISSUE_218_Support_single_page_app_browser_clients_using_authorization_code_and_pkce (#219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support single page app browser clients using authorization code and pkce * phpunit: mock all dependencies. Make the csrfprotection the injected dependency instead of the session. * Use mock for ModuleConfig * psalm changes * Add CsrfProtection as a service --------- Co-authored-by: Patrick Radtke Co-authored-by: Marko Ivančić --- routing/services/services.yml | 1 + src/Controller/AccessTokenController.php | 19 +++- src/Controller/Traits/RequestTrait.php | 53 ++++++++++ src/Controller/UserInfoController.php | 30 +----- src/Factories/FormFactory.php | 6 +- src/Forms/ClientForm.php | 9 +- src/Forms/Controls/CsrfProtection.php | 6 +- src/Services/Container.php | 10 +- .../Controller/AccessTokenControllerTest.php | 47 +++++++-- .../Controller/Client/EditControllerTest.php | 7 ++ .../Controller/Traits/RequestTraitTest.php | 91 +++++++++++++++++ .../src/Controller/UserInfoControllerTest.php | 51 ++-------- tests/src/Forms/ClientFormTest.php | 99 ++++++++++++++++++- 13 files changed, 332 insertions(+), 97 deletions(-) create mode 100644 src/Controller/Traits/RequestTrait.php create mode 100644 tests/src/Controller/Traits/RequestTraitTest.php diff --git a/routing/services/services.yml b/routing/services/services.yml index c9daf55d..5eeca754 100644 --- a/routing/services/services.yml +++ b/routing/services/services.yml @@ -27,6 +27,7 @@ services: resource: '../../src/Stores/*' SimpleSAML\Module\oidc\ModuleConfig: ~ + SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection: ~ oidc.key.private: class: League\OAuth2\Server\CryptKey diff --git a/src/Controller/AccessTokenController.php b/src/Controller/AccessTokenController.php index 3988e80b..cc83c404 100644 --- a/src/Controller/AccessTokenController.php +++ b/src/Controller/AccessTokenController.php @@ -15,16 +15,22 @@ */ namespace SimpleSAML\Module\oidc\Controller; +use Laminas\Diactoros\Response; +use Laminas\Diactoros\ServerRequest; use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseInterface; +use SimpleSAML\Module\oidc\Controller\Traits\RequestTrait; +use SimpleSAML\Module\oidc\Repositories\AllowedOriginRepository; use SimpleSAML\Module\oidc\Server\AuthorizationServer; -use Laminas\Diactoros\Response; -use Laminas\Diactoros\ServerRequest; class AccessTokenController { - public function __construct(private readonly AuthorizationServer $authorizationServer) - { + use RequestTrait; + + public function __construct( + private readonly AuthorizationServer $authorizationServer, + private readonly AllowedOriginRepository $allowedOriginRepository, + ) { } /** @@ -32,6 +38,11 @@ public function __construct(private readonly AuthorizationServer $authorizationS */ public function __invoke(ServerRequest $request): ResponseInterface { + // Check if this is actually a CORS preflight request... + if (strtoupper($request->getMethod()) === 'OPTIONS') { + return $this->handleCors($request); + } + return $this->authorizationServer->respondToAccessTokenRequest($request, new Response()); } } diff --git a/src/Controller/Traits/RequestTrait.php b/src/Controller/Traits/RequestTrait.php new file mode 100644 index 00000000..5c12ae4b --- /dev/null +++ b/src/Controller/Traits/RequestTrait.php @@ -0,0 +1,53 @@ +getHeaderLine('Origin'); + + if (empty($origin)) { + throw OidcServerException::requestNotSupported('CORS error: no Origin header present'); + } + + if (! $this->allowedOriginRepository->has($origin)) { + throw OidcServerException::accessDenied(sprintf('CORS error: origin %s is not allowed', $origin)); + } + + $headers = [ + 'Access-Control-Allow-Origin' => $origin, + 'Access-Control-Allow-Methods' => 'GET, POST, OPTIONS', + // Support AJAX requests for JS clients + // e.g. https://github.com/swagger-api/swagger-ui/commit/937c8f6208f3adf713b10a349a82a1b129bd0ffd + 'Access-Control-Allow-Headers' => 'Authorization, X-Requested-With', + 'Access-Control-Allow-Credentials' => 'true', + ]; + + return new Response('php://memory', 204, $headers); + } +} diff --git a/src/Controller/UserInfoController.php b/src/Controller/UserInfoController.php index e1f50517..c9de92ca 100644 --- a/src/Controller/UserInfoController.php +++ b/src/Controller/UserInfoController.php @@ -22,6 +22,7 @@ use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\ResourceServer; use SimpleSAML\Error\UserNotFound; +use SimpleSAML\Module\oidc\Controller\Traits\RequestTrait; use SimpleSAML\Module\oidc\Entities\AccessTokenEntity; use SimpleSAML\Module\oidc\Entities\UserEntity; use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; @@ -32,6 +33,8 @@ class UserInfoController { + use RequestTrait; + public function __construct( private readonly ResourceServer $resourceServer, private readonly AccessTokenRepository $accessTokenRepository, @@ -91,31 +94,4 @@ private function getUser(AccessTokenEntity $accessToken): UserEntity return $user; } - - /** - * Handle CORS 'preflight' requests by checking if 'origin' is registered as allowed to make HTTP CORS requests, - * typically initiated in browser by JavaScript clients. - * @throws OidcServerException - */ - protected function handleCors(ServerRequest $request): Response - { - $origin = $request->getHeaderLine('Origin'); - - if (empty($origin)) { - throw OidcServerException::requestNotSupported('CORS error: no Origin header present'); - } - - if (! $this->allowedOriginRepository->has($origin)) { - throw OidcServerException::accessDenied(sprintf('CORS error: origin %s is not allowed', $origin)); - } - - $headers = [ - 'Access-Control-Allow-Origin' => $origin, - 'Access-Control-Allow-Methods' => 'GET, POST, OPTIONS', - 'Access-Control-Allow-Headers' => 'Authorization', - 'Access-Control-Allow-Credentials' => 'true', - ]; - - return new Response('php://memory', 204, $headers); - } } diff --git a/src/Factories/FormFactory.php b/src/Factories/FormFactory.php index 9e3fc04f..caaf84dc 100644 --- a/src/Factories/FormFactory.php +++ b/src/Factories/FormFactory.php @@ -18,10 +18,12 @@ use Nette\Forms\Form; use SimpleSAML\Error\Exception; use SimpleSAML\Module\oidc\ModuleConfig; +use SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection; +use SimpleSAML\Session; class FormFactory { - public function __construct(private readonly ModuleConfig $moduleConfig) + public function __construct(private readonly ModuleConfig $moduleConfig, protected CsrfProtection $csrfProtection) { } @@ -39,6 +41,6 @@ public function build(string $classname): mixed } /** @psalm-suppress UnsafeInstantiation */ - return new $classname($this->moduleConfig); + return new $classname($this->moduleConfig, $this->csrfProtection); } } diff --git a/src/Forms/ClientForm.php b/src/Forms/ClientForm.php index b01a4e57..82c6c0fa 100644 --- a/src/Forms/ClientForm.php +++ b/src/Forms/ClientForm.php @@ -19,8 +19,8 @@ use Exception; use Nette\Forms\Form; use SimpleSAML\Auth\Source; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection; +use SimpleSAML\Module\oidc\ModuleConfig; use Traversable; /** @@ -41,7 +41,8 @@ class ClientForm extends Form * No reserved chars allowed, meaning no userinfo, path, query or fragment components. May end with port number. */ final public const REGEX_ALLOWED_ORIGIN_URL = - "/^http(s?):\/\/[^\s\/!$&'()+,;=.?#@*:]+\.[^\s\/!$&'()+,;=.?#@*]+\.?(\.[^\s\/!$&'()+,;=?#@*:]+)*(:\d{1,5})?$/i"; + "/^http(s?):\/\/([^\s\/!$&'()+,;=.?#@*:]+\.)" + . "?[^\s\/!$&'()+,;=.?#@*:]+(\.[^\s\/!$&'()+,;=.?#@*:]+)*\.?(:\d{1,5})?$/i"; /** * URI which must contain https or http scheme, can contain path and query, and can't contain fragment. @@ -51,7 +52,7 @@ class ClientForm extends Form /** * @throws Exception */ - public function __construct(private readonly ModuleConfig $moduleConfig) + public function __construct(private readonly ModuleConfig $moduleConfig, protected CsrfProtection $csrfProtection) { parent::__construct(); @@ -208,7 +209,7 @@ protected function buildForm(): void $this->onValidate[] = $this->validateBackChannelLogoutUri(...); $this->setMethod('POST'); - $this->addComponent(new CsrfProtection('{oidc:client:csrf_error}'), Form::ProtectorId); + $this->addComponent($this->csrfProtection, Form::ProtectorId); $this->addText('name', '{oidc:client:name}') ->setMaxLength(255) diff --git a/src/Forms/Controls/CsrfProtection.php b/src/Forms/Controls/CsrfProtection.php index 1935b4a9..a6decde5 100644 --- a/src/Forms/Controls/CsrfProtection.php +++ b/src/Forms/Controls/CsrfProtection.php @@ -27,13 +27,11 @@ class CsrfProtection extends BaseCsrfProtection { final public const PROTECTION = [\SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection::class, 'validateCsrf']; - protected Session $sspSession; - /** @noinspection PhpMissingParentConstructorInspection */ /** * @throws Exception */ - public function __construct(string|Stringable|null $errorMessage) + public function __construct(string|Stringable|null $errorMessage, protected Session $sspSession) { // Instead of calling CsrfProtection parent class constructor, go to it's parent (HiddenField), and call // its constructor. This is to avoid setting a Nette session in CsrfProtection parent, and use the SSP one. @@ -52,8 +50,6 @@ public function __construct(string|Stringable|null $errorMessage) $this->setOmitted() ->setRequired() ->addRule(self::PROTECTION, $errorMessage); - - $this->sspSession = Session::getSessionFromRequest(); } /** diff --git a/src/Services/Container.php b/src/Services/Container.php index 88ef2622..de00f166 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -36,6 +36,7 @@ use SimpleSAML\Module\oidc\Factories\IdTokenResponseFactory; use SimpleSAML\Module\oidc\Factories\ResourceServerFactory; use SimpleSAML\Module\oidc\Factories\TemplateFactory; +use SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; use SimpleSAML\Module\oidc\Repositories\AllowedOriginRepository; @@ -131,15 +132,16 @@ public function __construct() $authContextService = new AuthContextService($moduleConfig, $authSimpleFactory); $this->services[AuthContextService::class] = $authContextService; - $formFactory = new FormFactory($moduleConfig); + $session = Session::getSessionFromRequest(); + $this->services[Session::class] = $session; + + $csrfProtection = new CsrfProtection('{oidc:client:csrf_error}', $session); + $formFactory = new FormFactory($moduleConfig, $csrfProtection); $this->services[FormFactory::class] = $formFactory; $jsonWebKeySetService = new JsonWebKeySetService($moduleConfig); $this->services[JsonWebKeySetService::class] = $jsonWebKeySetService; - $session = Session::getSessionFromRequest(); - $this->services[Session::class] = $session; - $sessionService = new SessionService($session); $this->services[SessionService::class] = $sessionService; diff --git a/tests/src/Controller/AccessTokenControllerTest.php b/tests/src/Controller/AccessTokenControllerTest.php index f207c300..62004f6d 100644 --- a/tests/src/Controller/AccessTokenControllerTest.php +++ b/tests/src/Controller/AccessTokenControllerTest.php @@ -4,13 +4,16 @@ namespace SimpleSAML\Test\Module\oidc\Controller; +use Laminas\Diactoros\Response; +use Laminas\Diactoros\ServerRequest; use League\OAuth2\Server\Exception\OAuthServerException; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; -use Laminas\Diactoros\Response; -use Laminas\Diactoros\ServerRequest; -use SimpleSAML\Module\oidc\Controller\AccessTokenController; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\oidc\Controller\AccessTokenController; +use SimpleSAML\Module\oidc\Controller\Traits\RequestTrait; +use SimpleSAML\Module\oidc\Controller\UserInfoController; +use SimpleSAML\Module\oidc\Repositories\AllowedOriginRepository; use SimpleSAML\Module\oidc\Server\AuthorizationServer; /** @@ -19,6 +22,7 @@ class AccessTokenControllerTest extends TestCase { protected MockObject $authorizationServerMock; + protected MockObject $allowedOriginRepository; protected MockObject $serverRequestMock; protected MockObject $responseMock; @@ -28,7 +32,7 @@ class AccessTokenControllerTest extends TestCase protected function setUp(): void { $this->authorizationServerMock = $this->createMock(AuthorizationServer::class); - + $this->allowedOriginRepository = $this->createMock(AllowedOriginRepository::class); $this->serverRequestMock = $this->createMock(ServerRequest::class); $this->responseMock = $this->createMock(Response::class); } @@ -37,7 +41,10 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( AccessTokenController::class, - new AccessTokenController($this->authorizationServerMock), + new AccessTokenController( + $this->authorizationServerMock, + $this->allowedOriginRepository, + ), ); } @@ -54,7 +61,35 @@ public function testItRespondsToAccessTokenRequest(): void $this->assertSame( $this->responseMock, - (new AccessTokenController($this->authorizationServerMock))->__invoke($this->serverRequestMock), + (new AccessTokenController( + $this->authorizationServerMock, + $this->allowedOriginRepository, + ))->__invoke($this->serverRequestMock), ); } + + public function testItHandlesCorsRequest(): void + { + $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('OPTIONS'); + $userInfoControllerMock = $this->getMockBuilder(UserInfoController::class) + ->disableOriginalConstructor() + ->onlyMethods(['handleCors']) + ->getMock(); + $userInfoControllerMock->expects($this->once())->method('handleCors'); + + $userInfoControllerMock->__invoke($this->serverRequestMock); + } + + /** + * @return AccessTokenController + */ + protected function prepareMockedInstance(): AccessTokenController + { + return new AccessTokenController($this->authorizationServerMock, $this->allowedOriginRepository); + } + + public function testItUsesRequestTrait(): void + { + $this->assertContains(RequestTrait::class, class_uses(AccessTokenController::class)); + } } diff --git a/tests/src/Controller/Client/EditControllerTest.php b/tests/src/Controller/Client/EditControllerTest.php index e7178d86..b721c46a 100644 --- a/tests/src/Controller/Client/EditControllerTest.php +++ b/tests/src/Controller/Client/EditControllerTest.php @@ -68,6 +68,13 @@ protected function setUp(): void $this->serverRequestMock->method('withQueryParams')->willReturn($this->serverRequestMock); } + public static function setUpBeforeClass(): void + { + // To make lib/SimpleSAML/Utils/HTTP::getSelfURL() work... + global $_SERVER; + $_SERVER['REQUEST_URI'] = ''; + } + protected function getStubbedInstance(): EditController { return new EditController( diff --git a/tests/src/Controller/Traits/RequestTraitTest.php b/tests/src/Controller/Traits/RequestTraitTest.php new file mode 100644 index 00000000..e46b00a3 --- /dev/null +++ b/tests/src/Controller/Traits/RequestTraitTest.php @@ -0,0 +1,91 @@ +allowedOriginRepositoryMock = $this->createMock(AllowedOriginRepository::class); + $this->prepareMockedInstance = new class ($this->allowedOriginRepositoryMock) { + use RequestTrait; + + public function __construct( + public AllowedOriginRepository $allowedOriginRepository, + ) { + } + + public function handleCorsWrapper(ServerRequest $request): Response + { + return $this->handleCors($request); + } + }; + + $this->serverRequestMock = $this->createMock(ServerRequest::class); + } + + /** + * @throws OidcServerException + */ + public function testItThrowsIfOriginHeaderNotAvailable(): void + { + $this->serverRequestMock->expects($this->once())->method('getHeaderLine')->willReturn(''); + + $this->expectException(OidcServerException::class); + $this->prepareMockedInstance->handleCorsWrapper($this->serverRequestMock); + } + + /** + * @throws OidcServerException + */ + public function testItThrowsIfOriginHeaderNotAllowed(): void + { + $origin = 'https://example.org'; + + $this->serverRequestMock->expects($this->once())->method('getHeaderLine')->willReturn($origin); + $this->prepareMockedInstance->allowedOriginRepository->expects($this->once())->method('has')->willReturn(false); + + $this->expectException(OidcServerException::class); + $this->prepareMockedInstance->handleCorsWrapper($this->serverRequestMock); + } + + public function testItHandlesCorsRequest(): void + { + $origin = 'https://example.org'; + + $this->serverRequestMock->expects($this->once())->method('getHeaderLine')->willReturn($origin); + $this->prepareMockedInstance->allowedOriginRepository->expects($this->once())->method('has')->willReturn(true); + + $response = $this->prepareMockedInstance->handleCorsWrapper($this->serverRequestMock); + $this->assertEquals(204, $response->getStatusCode()); + $this->assertSame( + $response->getHeaders(), + [ + 'Access-Control-Allow-Origin' => [$origin], + 'Access-Control-Allow-Methods' => ['GET, POST, OPTIONS'], + 'Access-Control-Allow-Headers' => ['Authorization, X-Requested-With'], + 'Access-Control-Allow-Credentials' => ['true'], + ], + ); + } +} diff --git a/tests/src/Controller/UserInfoControllerTest.php b/tests/src/Controller/UserInfoControllerTest.php index 4a9022ff..fb10e4a3 100644 --- a/tests/src/Controller/UserInfoControllerTest.php +++ b/tests/src/Controller/UserInfoControllerTest.php @@ -12,6 +12,7 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Error\UserNotFound; +use SimpleSAML\Module\oidc\Controller\Traits\RequestTrait; use SimpleSAML\Module\oidc\Controller\UserInfoController; use SimpleSAML\Module\oidc\Entities\AccessTokenEntity; use SimpleSAML\Module\oidc\Entities\UserEntity; @@ -216,54 +217,20 @@ public function testItThrowsIfUserNotFound(): void $this->prepareMockedInstance()->__invoke($this->serverRequestMock); } - /** - * @throws UserNotFound - * @throws OidcServerException - * @throws OAuthServerException - */ public function testItHandlesCorsRequest(): void { - $origin = 'https://example.org'; - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('OPTIONS'); - $this->serverRequestMock->expects($this->once())->method('getHeaderLine')->willReturn($origin); - $this->allowedOriginRepositoryMock->expects($this->once())->method('has')->willReturn(true); - - $this->assertSame( - $this->prepareMockedInstance()->__invoke($this->serverRequestMock)->getHeaders(), - [ - 'Access-Control-Allow-Origin' => [$origin], - 'Access-Control-Allow-Methods' => ['GET, POST, OPTIONS'], - 'Access-Control-Allow-Headers' => ['Authorization'], - 'Access-Control-Allow-Credentials' => ['true'], - ], - ); - } - - /** - * @throws UserNotFound - * @throws OAuthServerException - */ - public function testItThrowsIfCorsOriginNotAllowed(): void - { - $origin = 'https://example.org'; $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('OPTIONS'); - $this->serverRequestMock->expects($this->once())->method('getHeaderLine')->willReturn($origin); - $this->allowedOriginRepositoryMock->expects($this->once())->method('has')->willReturn(false); + $userInfoControllerMock = $this->getMockBuilder(UserInfoController::class) + ->disableOriginalConstructor() + ->onlyMethods(['handleCors']) + ->getMock(); + $userInfoControllerMock->expects($this->once())->method('handleCors'); - $this->expectException(OidcServerException::class); - $this->prepareMockedInstance()->__invoke($this->serverRequestMock); + $userInfoControllerMock->__invoke($this->serverRequestMock); } - /** - * @throws UserNotFound - * @throws OAuthServerException - */ - public function testItThrowsIfOriginHeaderNotAvailable(): void + public function testItUsesRequestTrait(): void { - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('OPTIONS'); - $this->serverRequestMock->expects($this->once())->method('getHeaderLine')->willReturn(''); - - $this->expectException(OidcServerException::class); - $this->prepareMockedInstance()->__invoke($this->serverRequestMock); + $this->assertContains(RequestTrait::class, class_uses(UserInfoController::class)); } } diff --git a/tests/src/Forms/ClientFormTest.php b/tests/src/Forms/ClientFormTest.php index 244ca520..12033bb6 100644 --- a/tests/src/Forms/ClientFormTest.php +++ b/tests/src/Forms/ClientFormTest.php @@ -4,16 +4,109 @@ namespace SimpleSAML\Test\Module\oidc\Forms; -use SimpleSAML\Module\oidc\Forms\ClientForm; +use Laminas\Diactoros\ServerRequest; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\MockObject\Exception; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use SimpleSAML\Configuration; +use SimpleSAML\Module\oidc\Forms\ClientForm; +use SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection; +use SimpleSAML\Module\oidc\ModuleConfig; /** * @covers \SimpleSAML\Module\oidc\Forms\ClientForm */ class ClientFormTest extends TestCase { - public function testIncomplete(): never + /** @var MockObject */ + protected MockObject $csrfProtection; + + /** @var MockObject */ + protected MockObject $moduleConfig; + + /** @var MockObject */ + protected MockObject $serverRequestMock; + + /** + * @throws Exception + */ + public function setUp(): void + { + parent::setUp(); + Configuration::clearInternalState(); + $this->csrfProtection = $this->createMock(CsrfProtection::class); + $this->moduleConfig = $this->createMock(ModuleConfig::class); + $this->serverRequestMock = $this->createMock(ServerRequest::class); + } + + public static function setUpBeforeClass(): void + { + // To make lib/SimpleSAML/Utils/HTTP::getSelfURL() work... + global $_SERVER; + $_SERVER['REQUEST_URI'] = '/'; + } + + public static function validateOriginProvider(): array + { + return [ + ['example.com', false], + ['https://example.com.', true], + ['http://example.com.', true], + ['http://foo.', true], + ['http://foo', true], + ['https://user:pass@example.com', false], + ['http://example.com', true], + ['https://example.com:2020', true], + ['https://localhost:2020', true], + ['http://localhost:2020', true], + ['http://localhost', true], + ['https://example.com/path', false], + ['https://example.com:8080/path', false], + ['http://*.example.com', false], + ['http://*.example.com.', false], + ['https://foo.example.com:80', true], + ['http://*.example', false], + ['http://foo.*.test.com', false], + ['http://*', false], + ['http://*.com', false], + ['https://test........', false], + ['https://developer.mozilla.org:80', true], + ['http://attacker.bar/test.php', false], + ['https://cors-test.codehappy.dev', true], + ['http://80.345.28.123', true], + ['https://127.0.0.1:8080', true], + ['https://127.0.0.1:8080/path', false], + ['https://user:pass@127.0.0.1:8080/path', false], + ]; + } + + + /** + * @param string $url + * @param bool $isValid + * + * @return void + * @throws \Exception + */ + #[DataProvider('validateOriginProvider')] + #[TestDox('Allowed Origin URL: $url is expected to be $isValid')] + public function testValidateOrigin(string $url, bool $isValid): void + { + $clientForm = $this->prepareMockedInstance(); + $clientForm->setValues(['allowed_origin' => $url]); + $clientForm->validateAllowedOrigin($clientForm); + + $this->assertEquals(!$isValid, $clientForm->hasErrors(), $url); + } + + /** + * @return ClientForm + * @throws \Exception + */ + protected function prepareMockedInstance(): ClientForm { - $this->markTestIncomplete(); + return new ClientForm($this->moduleConfig, $this->csrfProtection); } } From b0e19bd06872521bb60bf9963de45c8fe39153a9 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Mon, 27 May 2024 21:12:55 +0200 Subject: [PATCH 08/70] Remove legacy configuration item from SSP <2.0 --- src/Factories/TemplateFactory.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Factories/TemplateFactory.php b/src/Factories/TemplateFactory.php index 3eda36f1..7f756d47 100644 --- a/src/Factories/TemplateFactory.php +++ b/src/Factories/TemplateFactory.php @@ -25,11 +25,7 @@ class TemplateFactory public function __construct(Configuration $configuration) { - $config = $configuration->toArray(); - // TODO mivanci check if this is really necessary anymore - $config['usenewui'] = true; - - $this->configuration = new Configuration($config, 'oidc'); + $this->configuration = new Configuration($configuration->toArray(), 'oidc'); } /** From 7c001c5cbc2efd2c7db68533d1e9abfd507e5b86 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Mon, 27 May 2024 22:31:52 +0200 Subject: [PATCH 09/70] Fix @covers tag --- tests/src/Controller/Traits/RequestTraitTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/Controller/Traits/RequestTraitTest.php b/tests/src/Controller/Traits/RequestTraitTest.php index e46b00a3..399fc340 100644 --- a/tests/src/Controller/Traits/RequestTraitTest.php +++ b/tests/src/Controller/Traits/RequestTraitTest.php @@ -14,7 +14,7 @@ use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; /** - * @covers \SimpleSAML\Module\oidc\Controller\Traits\RequestTraitTest + * @covers \SimpleSAML\Module\oidc\Controller\Traits\RequestTrait */ class RequestTraitTest extends TestCase { From 909c0a1583b2347c5cd7bca9e80bf5f9e1c2b91a Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 29 May 2024 13:51:21 +0200 Subject: [PATCH 10/70] Use FQCN in DocBlocks (#223) * Replace use-statements with fqdn phpdocs (src) * Replace use-statements with fqdn phpdocs (tests) * Remove duplicate sniffs - already part of SSP test-framework --- hooks/hook_cron.php | 17 ++- hooks/hook_federationpage.php | 5 +- hooks/hook_frontpage.php | 2 + phpcs.xml | 8 +- src/Bridges/SspBridge/Module.php | 4 +- src/Controller/AccessTokenController.php | 3 +- src/Controller/AuthorizationController.php | 22 ++-- src/Controller/Client/CreateController.php | 6 +- src/Controller/Client/DeleteController.php | 18 +-- src/Controller/Client/EditController.php | 8 +- src/Controller/Client/IndexController.php | 3 +- .../Client/ResetSecretController.php | 14 +-- src/Controller/Client/ShowController.php | 11 +- .../Federation/EntityStatementController.php | 5 +- src/Controller/InstallerController.php | 9 +- src/Controller/JwksController.php | 2 +- src/Controller/LogoutController.php | 18 ++- ...AuthenticatedGetClientFromRequestTrait.php | 13 ++- .../Traits/GetClientFromRequestTrait.php | 14 +-- src/Controller/Traits/RequestTrait.php | 3 +- src/Controller/UserInfoController.php | 18 ++- src/Entities/AccessTokenEntity.php | 23 ++-- src/Entities/AuthCodeEntity.php | 11 +- src/Entities/ClientEntity.php | 13 +-- src/Entities/RefreshTokenEntity.php | 5 +- src/Entities/UserEntity.php | 10 +- src/Factories/AuthSimpleFactory.php | 12 +- src/Factories/AuthorizationServerFactory.php | 9 +- .../ClaimTranslatorExtractorFactory.php | 5 +- src/Factories/FormFactory.php | 6 +- src/Factories/Grant/AuthCodeGrantFactory.php | 3 +- src/Factories/ResourceServerFactory.php | 1 + src/Factories/TemplateFactory.php | 4 +- src/Forms/ClientForm.php | 13 +-- src/Forms/Controls/CsrfProtection.php | 8 +- src/ModuleConfig.php | 44 ++++---- .../AbstractDatabaseRepository.php | 3 +- src/Repositories/AccessTokenRepository.php | 21 ++-- src/Repositories/AuthCodeRepository.php | 19 ++-- src/Repositories/ClientRepository.php | 30 ++--- .../CodeChallengeVerifiersRepository.php | 7 +- .../AccessTokenRepositoryInterface.php | 7 +- src/Repositories/RefreshTokenRepository.php | 14 +-- src/Repositories/ScopeRepository.php | 3 +- src/Repositories/UserRepository.php | 9 +- src/Server/AuthorizationServer.php | 21 ++-- src/Server/Exceptions/OidcServerException.php | 18 +-- src/Server/Grants/AuthCodeGrant.php | 86 +++++++-------- src/Server/Grants/ImplicitGrant.php | 29 ++--- src/Server/Grants/OAuth2ImplicitGrant.php | 77 ++++--------- src/Server/Grants/RefreshTokenGrant.php | 58 +++------- .../Grants/Traits/IssueAccessTokenTrait.php | 13 +-- .../BackChannelLogoutHandler.php | 16 +-- src/Server/ResponseTypes/IdTokenResponse.php | 15 +-- .../Validators/BearerTokenValidator.php | 34 +++--- src/Services/AuthContextService.php | 5 +- src/Services/AuthProcService.php | 8 +- src/Services/AuthenticationService.php | 15 ++- src/Services/DatabaseLegacyOAuth2Import.php | 10 +- src/Services/IdTokenBuilder.php | 6 +- src/Services/JsonWebKeySetService.php | 11 +- src/Services/JsonWebTokenBuilderService.php | 20 ++-- src/Services/LogoutTokenBuilder.php | 5 +- src/Services/OpMetadataService.php | 5 +- src/Services/RoutingService.php | 56 +++++----- src/Services/SessionMessagesService.php | 3 +- src/Services/SessionService.php | 17 ++- src/Stores/Session/LogoutTicketStoreDb.php | 9 +- .../Interfaces/RequestRuleInterface.php | 10 +- .../Checker/Interfaces/ResultBagInterface.php | 6 +- src/Utils/Checker/RequestRulesManager.php | 13 +-- src/Utils/Checker/ResultBag.php | 10 +- src/Utils/Checker/Rules/AcrValuesRule.php | 2 +- .../Checker/Rules/AddClaimsToIdTokenRule.php | 3 +- .../Checker/Rules/CodeChallengeMethodRule.php | 5 +- src/Utils/Checker/Rules/CodeChallengeRule.php | 3 +- src/Utils/Checker/Rules/IdTokenHintRule.php | 7 +- src/Utils/Checker/Rules/MaxAgeRule.php | 17 ++- .../Rules/PostLogoutRedirectUriRule.php | 6 +- src/Utils/Checker/Rules/PromptRule.php | 19 ++-- src/Utils/Checker/Rules/RedirectUriRule.php | 3 +- .../Checker/Rules/RequestParameterRule.php | 5 +- .../Checker/Rules/RequestedClaimsRule.php | 6 +- src/Utils/Checker/Rules/RequiredNonceRule.php | 3 +- .../Checker/Rules/RequiredOpenIdScopeRule.php | 6 +- .../Checker/Rules/ScopeOfflineAccessRule.php | 9 +- src/Utils/Checker/Rules/ScopeRule.php | 5 +- src/Utils/ClaimTranslatorExtractor.php | 6 +- src/Utils/FingerprintGenerator.php | 12 +- src/Utils/ScopeHelper.php | 4 +- src/Utils/TimestampGenerator.php | 5 +- src/Utils/UniqueIdentifierGenerator.php | 2 +- .../Controller/AccessTokenControllerTest.php | 8 +- .../AuthorizationControllerTest.php | 104 +++++++++--------- .../Client/CreateControllerTest.php | 7 +- .../Client/DeleteControllerTest.php | 49 +++++++-- .../Controller/Client/EditControllerTest.php | 19 ++-- .../Controller/Client/IndexControllerTest.php | 3 +- .../Client/ResetSecretControllerTest.php | 30 +++-- .../Controller/Client/ShowControllerTest.php | 22 ++-- .../ConfigurationDiscoveryControllerTest.php | 5 +- .../Controller/InstallerControllerTest.php | 7 +- tests/src/Controller/JwksControllerTest.php | 7 +- tests/src/Controller/LogoutControllerTest.php | 36 +++--- .../Controller/Traits/RequestTraitTest.php | 5 +- .../src/Controller/UserInfoControllerTest.php | 21 ++-- tests/src/Entities/AccessTokenEntityTest.php | 27 ++--- tests/src/Entities/AuthCodeEntityTest.php | 27 ++--- tests/src/Entities/ClientEntityTest.php | 28 +++-- tests/src/Entities/RefreshTokenEntityTest.php | 12 +- tests/src/Entities/ScopeEntityTest.php | 2 +- tests/src/Entities/UserEntityTest.php | 16 ++- tests/src/Factories/AuthSimpleFactoryTest.php | 1 - .../ClaimTranslatorExtractorFactoryTest.php | 5 +- tests/src/Forms/ClientFormTest.php | 11 +- tests/src/ModuleConfigTest.php | 6 +- .../AccessTokenRepositoryTest.php | 34 +++--- .../AllowedOriginRepositoryTest.php | 3 +- .../Repositories/AuthCodeRepositoryTest.php | 26 ++--- .../src/Repositories/ClientRepositoryTest.php | 66 ++++++----- .../RefreshTokenRepositoryTest.php | 39 +++---- .../src/Repositories/ScopeRepositoryTest.php | 9 +- tests/src/Repositories/UserRepositoryTest.php | 18 ++- .../RelyingPartyAssociationTest.php | 2 +- tests/src/Server/AuthorizationServerTest.php | 1 - tests/src/Server/Grants/AuthCodeGrantTest.php | 5 +- tests/src/Server/Grants/ImplicitGrantTest.php | 1 - .../Server/Grants/OAuth2ImplicitGrantTest.php | 1 - .../BackChannelLogoutHandlerTest.php | 12 +- .../RequestTypes/AuthorizationRequestTest.php | 1 - .../Server/RequestTypes/LogoutRequestTest.php | 5 +- .../ResponseTypes/IdTokenResponseTest.php | 16 ++- .../Validators/BearerTokenValidatorTest.php | 22 ++-- tests/src/Services/AuthContextServiceTest.php | 6 +- tests/src/Services/AuthProcServiceTest.php | 7 +- .../Services/AuthenticationServiceTest.php | 29 +++-- tests/src/Services/IdTokenBuilderTest.php | 1 - .../src/Services/JsonWebKeySetServiceTest.php | 2 +- .../JsonWebTokenBuilderServiceTest.php | 18 ++- tests/src/Services/LogoutTokenBuilderTest.php | 12 +- tests/src/Services/OpMetadataServiceTest.php | 5 +- .../Services/SessionMessagesServiceTest.php | 5 +- tests/src/Services/SessionServiceTest.php | 1 - .../Session/LogoutTicketStoreBuilderTest.php | 2 +- .../Session/LogoutTicketStoreDbTest.php | 7 +- .../Utils/Checker/RequestRulesManagerTest.php | 16 ++- tests/src/Utils/Checker/ResultTest.php | 1 - .../Utils/Checker/Rules/AcrValuesRuleTest.php | 12 +- .../Rules/AddClaimsToIdTokenRuleTest.php | 15 +-- .../Utils/Checker/Rules/ClientIdRuleTest.php | 7 +- .../Rules/CodeChallengeMethodRuleTest.php | 18 ++- .../Checker/Rules/CodeChallengeRuleTest.php | 20 ++-- .../Checker/Rules/IdTokenHintRuleTest.php | 27 ++--- .../Rules/PostLogoutRedirectUriRuleTest.php | 22 ++-- .../Checker/Rules/RedirectUriRuleTest.php | 22 ++-- .../Checker/Rules/RequestedClaimsRuleTest.php | 10 +- .../Checker/Rules/RequiredNonceRuleTest.php | 18 ++- .../Rules/RequiredOpenIdScopeRuleTest.php | 18 ++- .../Checker/Rules/ResponseTypeRuleTest.php | 7 +- .../Rules/ScopeOfflineAccessRuleTest.php | 20 ++-- .../src/Utils/Checker/Rules/ScopeRuleTest.php | 18 ++- .../src/Utils/Checker/Rules/StateRuleTest.php | 20 ++-- .../Utils/Checker/Rules/UiLocalesRuleTest.php | 10 +- .../Utils/ClaimTranslatorExtractorTest.php | 13 +-- tests/src/Utils/ScopeHelperTest.php | 5 +- .../Utils/UniqueIdentifierGeneratorTest.php | 5 +- 166 files changed, 1025 insertions(+), 1280 deletions(-) diff --git a/hooks/hook_cron.php b/hooks/hook_cron.php index 1ea969ae..cb57e66d 100644 --- a/hooks/hook_cron.php +++ b/hooks/hook_cron.php @@ -14,8 +14,6 @@ * file that was distributed with this source code. */ -use Psr\Container\ContainerExceptionInterface; -use Psr\Container\NotFoundExceptionInterface; use SimpleSAML\Logger; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; @@ -25,11 +23,10 @@ use SimpleSAML\Module\oidc\Services\Container; /** - * @param array $croninfo - * @throws OidcServerException - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - * @throws Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + * @throws \Exception */ function oidc_hook_cron(array &$croninfo): void { @@ -55,15 +52,15 @@ function oidc_hook_cron(array &$croninfo): void $container = new Container(); try { - /** @var AccessTokenRepository $accessTokenRepository */ + /** @var \SimpleSAML\Module\oidc\Repositories\AccessTokenRepository $accessTokenRepository */ $accessTokenRepository = $container->get(AccessTokenRepository::class); $accessTokenRepository->removeExpired(); - /** @var AuthCodeRepository $authTokenRepository */ + /** @var \SimpleSAML\Module\oidc\Repositories\AuthCodeRepository $authTokenRepository */ $authTokenRepository = $container->get(AuthCodeRepository::class); $authTokenRepository->removeExpired(); - /** @var RefreshTokenRepository $refreshTokenRepository */ + /** @var \SimpleSAML\Module\oidc\Repositories\RefreshTokenRepository $refreshTokenRepository */ $refreshTokenRepository = $container->get(RefreshTokenRepository::class); $refreshTokenRepository->removeExpired(); diff --git a/hooks/hook_federationpage.php b/hooks/hook_federationpage.php index dca1cca7..6c0ae6d0 100644 --- a/hooks/hook_federationpage.php +++ b/hooks/hook_federationpage.php @@ -14,11 +14,14 @@ * file that was distributed with this source code. */ +use SimpleSAML\Locale\Translate; use SimpleSAML\Module; use SimpleSAML\Module\oidc\Services\DatabaseMigration; use SimpleSAML\XHTML\Template; -use SimpleSAML\Locale\Translate; +/** + * @param \SimpleSAML\XHTML\Template $template + */ function oidc_hook_federationpage(Template $template): void { $href = Module::getModuleURL('oidc/admin-clients/index.php'); diff --git a/hooks/hook_frontpage.php b/hooks/hook_frontpage.php index 7b642389..9bedae85 100644 --- a/hooks/hook_frontpage.php +++ b/hooks/hook_frontpage.php @@ -17,6 +17,8 @@ use SimpleSAML\Module; use SimpleSAML\Module\oidc\Services\DatabaseMigration; +/** + */ function oidc_hook_frontpage(array &$links): void { if (!is_array($links['federation'])) { diff --git a/phpcs.xml b/phpcs.xml index 6c188982..591c70da 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -22,13 +22,7 @@ - - - - - - - + diff --git a/src/Bridges/SspBridge/Module.php b/src/Bridges/SspBridge/Module.php index b9e9916b..b423fac0 100644 --- a/src/Bridges/SspBridge/Module.php +++ b/src/Bridges/SspBridge/Module.php @@ -4,10 +4,12 @@ namespace SimpleSAML\Module\oidc\Bridges\SspBridge; +use SimpleSAML\Module as SspModule; + class Module { public function getModuleUrl(string $resource, array $parameters = []): string { - return \SimpleSAML\Module::getModuleURL($resource, $parameters); + return SspModule::getModuleURL($resource, $parameters); } } diff --git a/src/Controller/AccessTokenController.php b/src/Controller/AccessTokenController.php index cc83c404..2e9fbe5c 100644 --- a/src/Controller/AccessTokenController.php +++ b/src/Controller/AccessTokenController.php @@ -17,7 +17,6 @@ use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; -use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseInterface; use SimpleSAML\Module\oidc\Controller\Traits\RequestTrait; use SimpleSAML\Module\oidc\Repositories\AllowedOriginRepository; @@ -34,7 +33,7 @@ public function __construct( } /** - * @throws OAuthServerException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function __invoke(ServerRequest $request): ResponseInterface { diff --git a/src/Controller/AuthorizationController.php b/src/Controller/AuthorizationController.php index f6592b6a..20988720 100644 --- a/src/Controller/AuthorizationController.php +++ b/src/Controller/AuthorizationController.php @@ -16,19 +16,15 @@ namespace SimpleSAML\Module\oidc\Controller; -use Exception; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; -use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseInterface; -use SimpleSAML\Error; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Server\AuthorizationServer; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestTypes\AuthorizationRequest; use SimpleSAML\Module\oidc\Services\AuthenticationService; use SimpleSAML\Module\oidc\Services\LoggerService; -use Throwable; class AuthorizationController { @@ -41,12 +37,13 @@ public function __construct( } /** - * @throws Error\AuthSource - * @throws Error\BadRequest - * @throws Error\NotFound - * @throws Error\Exception - * @throws OAuthServerException - * @throws Exception|Throwable + * @throws \Exception + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Error\Exception + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \Throwable */ public function __invoke(ServerRequest $request): ResponseInterface { @@ -71,7 +68,8 @@ public function __invoke(ServerRequest $request): ResponseInterface /** * Validate authorization request after the authn has been performed. For example, check if the * ACR claim has been requested and that authn performed satisfies it. - * @throws Exception + * + * @throws \Exception */ protected function validatePostAuthnAuthorizationRequest(AuthorizationRequest $authorizationRequest): void { @@ -79,7 +77,7 @@ protected function validatePostAuthnAuthorizationRequest(AuthorizationRequest $a } /** - * @throws Exception + * @throws \Exception */ protected function validateAcr(AuthorizationRequest $authorizationRequest): void { diff --git a/src/Controller/Client/CreateController.php b/src/Controller/Client/CreateController.php index 852227a3..5e7ae30b 100644 --- a/src/Controller/Client/CreateController.php +++ b/src/Controller/Client/CreateController.php @@ -17,7 +17,6 @@ namespace SimpleSAML\Module\oidc\Controller\Client; use Laminas\Diactoros\Response\RedirectResponse; -use SimpleSAML\Error\Exception; use SimpleSAML\Module\oidc\Entities\ClientEntity; use SimpleSAML\Module\oidc\Factories\FormFactory; use SimpleSAML\Module\oidc\Factories\TemplateFactory; @@ -44,8 +43,9 @@ public function __construct( } /** - * @return RedirectResponse|Template - * @throws Exception + * @return \Laminas\Diactoros\Response\RedirectResponse|\SimpleSAML\XHTML\Template + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException * @throws \Exception */ public function __invoke(): Template|RedirectResponse diff --git a/src/Controller/Client/DeleteController.php b/src/Controller/Client/DeleteController.php index b656af73..bf8bb575 100644 --- a/src/Controller/Client/DeleteController.php +++ b/src/Controller/Client/DeleteController.php @@ -16,17 +16,12 @@ namespace SimpleSAML\Module\oidc\Controller\Client; -use JsonException; use Laminas\Diactoros\Response\RedirectResponse; use Laminas\Diactoros\ServerRequest; -use SimpleSAML\Error\BadRequest; -use SimpleSAML\Error\ConfigurationError; -use SimpleSAML\Error\Exception; -use SimpleSAML\Error\NotFound; +use SimpleSAML\Error; use SimpleSAML\Module\oidc\Controller\Traits\AuthenticatedGetClientFromRequestTrait; use SimpleSAML\Module\oidc\Factories\TemplateFactory; use SimpleSAML\Module\oidc\Repositories\ClientRepository; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\AuthContextService; use SimpleSAML\Module\oidc\Services\SessionMessagesService; use SimpleSAML\Utils\HTTP; @@ -47,8 +42,13 @@ public function __construct( } /** - * @throws ConfigurationError|BadRequest|NotFound|Exception|OidcServerException|JsonException * @throws \Exception + * @throws \JsonException + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\ConfigurationError + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function __invoke(ServerRequest $request): Template|RedirectResponse { @@ -58,11 +58,11 @@ public function __invoke(ServerRequest $request): Template|RedirectResponse $authedUser = $this->authContextService->isSspAdmin() ? null : $this->authContextService->getAuthUserId(); if ('POST' === mb_strtoupper($request->getMethod())) { if (!$clientSecret) { - throw new BadRequest('Client secret is missing.'); + throw new Error\BadRequest('Client secret is missing.'); } if ($clientSecret !== $client->getSecret()) { - throw new BadRequest('Client secret is invalid.'); + throw new Error\BadRequest('Client secret is invalid.'); } $this->clientRepository->delete($client, $authedUser); diff --git a/src/Controller/Client/EditController.php b/src/Controller/Client/EditController.php index da10c1b5..3602659c 100644 --- a/src/Controller/Client/EditController.php +++ b/src/Controller/Client/EditController.php @@ -18,9 +18,6 @@ use Laminas\Diactoros\Response\RedirectResponse; use Laminas\Diactoros\ServerRequest; -use SimpleSAML\Error\BadRequest; -use SimpleSAML\Error\Exception; -use SimpleSAML\Error\NotFound; use SimpleSAML\Module\oidc\Controller\Traits\AuthenticatedGetClientFromRequestTrait; use SimpleSAML\Module\oidc\Entities\ClientEntity; use SimpleSAML\Module\oidc\Factories\FormFactory; @@ -51,7 +48,10 @@ public function __construct( } /** - * @throws BadRequest|Exception|NotFound|\Exception + * @throws \Exception + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound */ public function __invoke(ServerRequest $request): Template|RedirectResponse { diff --git a/src/Controller/Client/IndexController.php b/src/Controller/Client/IndexController.php index 8bed980a..c912ab29 100644 --- a/src/Controller/Client/IndexController.php +++ b/src/Controller/Client/IndexController.php @@ -17,7 +17,6 @@ namespace SimpleSAML\Module\oidc\Controller\Client; use Laminas\Diactoros\ServerRequest; -use SimpleSAML\Error\Exception; use SimpleSAML\Module\oidc\Factories\TemplateFactory; use SimpleSAML\Module\oidc\Repositories\ClientRepository; use SimpleSAML\Module\oidc\Services\AuthContextService; @@ -33,8 +32,8 @@ public function __construct( } /** - * @throws Exception * @throws \Exception + * @throws \SimpleSAML\Error\Exception */ public function __invoke(ServerRequest $request): Template { diff --git a/src/Controller/Client/ResetSecretController.php b/src/Controller/Client/ResetSecretController.php index 3c6c5a9b..803d3702 100644 --- a/src/Controller/Client/ResetSecretController.php +++ b/src/Controller/Client/ResetSecretController.php @@ -18,9 +18,7 @@ use Laminas\Diactoros\Response\RedirectResponse; use Laminas\Diactoros\ServerRequest; -use SimpleSAML\Error\BadRequest; -use SimpleSAML\Error\Exception; -use SimpleSAML\Error\NotFound; +use SimpleSAML\Error; use SimpleSAML\Module\oidc\Controller\Traits\AuthenticatedGetClientFromRequestTrait; use SimpleSAML\Module\oidc\Repositories\ClientRepository; use SimpleSAML\Module\oidc\Services\AuthContextService; @@ -42,10 +40,10 @@ public function __construct( } /** - * @throws BadRequest - * @throws NotFound - * @throws Exception * @throws \Exception + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\NotFound */ public function __invoke(ServerRequest $request): RedirectResponse { @@ -55,11 +53,11 @@ public function __invoke(ServerRequest $request): RedirectResponse if ('POST' === mb_strtoupper($request->getMethod())) { if (!$clientSecret) { - throw new BadRequest('Client secret is missing.'); + throw new Error\BadRequest('Client secret is missing.'); } if ($clientSecret !== $client->getSecret()) { - throw new BadRequest('Client secret is invalid.'); + throw new Error\BadRequest('Client secret is invalid.'); } $client->restoreSecret((new Random())->generateID()); diff --git a/src/Controller/Client/ShowController.php b/src/Controller/Client/ShowController.php index 3a6b743b..fc85c2fe 100644 --- a/src/Controller/Client/ShowController.php +++ b/src/Controller/Client/ShowController.php @@ -15,16 +15,11 @@ */ namespace SimpleSAML\Module\oidc\Controller\Client; -use JsonException; use Laminas\Diactoros\ServerRequest; -use SimpleSAML\Error\BadRequest; -use SimpleSAML\Error\Exception; -use SimpleSAML\Error\NotFound; use SimpleSAML\Module\oidc\Controller\Traits\AuthenticatedGetClientFromRequestTrait; use SimpleSAML\Module\oidc\Factories\TemplateFactory; use SimpleSAML\Module\oidc\Repositories\AllowedOriginRepository; use SimpleSAML\Module\oidc\Repositories\ClientRepository; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\AuthContextService; use SimpleSAML\XHTML\Template; @@ -43,7 +38,11 @@ public function __construct( } /** - * @throws BadRequest|Exception|NotFound|OidcServerException|JsonException + * @throws \JsonException + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function __invoke(ServerRequest $request): Template { diff --git a/src/Controller/Federation/EntityStatementController.php b/src/Controller/Federation/EntityStatementController.php index 634dda33..1d96d00b 100644 --- a/src/Controller/Federation/EntityStatementController.php +++ b/src/Controller/Federation/EntityStatementController.php @@ -8,7 +8,6 @@ use SimpleSAML\Module\oidc\Codebooks\ClaimValues\TypeEnum; use SimpleSAML\Module\oidc\Codebooks\EntityTypeEnum; use SimpleSAML\Module\oidc\ModuleConfig; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\JsonWebKeySetService; use SimpleSAML\Module\oidc\Services\JsonWebTokenBuilderService; use SimpleSAML\Module\oidc\Services\OpMetadataService; @@ -27,8 +26,8 @@ public function __construct( /** * Return the JWS with the OP configuration statement. - * @return Response - * @throws OidcServerException + * @return \Symfony\Component\HttpFoundation\Response + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function configuration(): Response { diff --git a/src/Controller/InstallerController.php b/src/Controller/InstallerController.php index 5ae32b20..e74bb269 100644 --- a/src/Controller/InstallerController.php +++ b/src/Controller/InstallerController.php @@ -15,16 +15,15 @@ */ namespace SimpleSAML\Module\oidc\Controller; -use Exception; -use SimpleSAML\XHTML\Template; +use Laminas\Diactoros\Response\RedirectResponse; +use Laminas\Diactoros\ServerRequest; use SimpleSAML\Module; use SimpleSAML\Module\oidc\Factories\TemplateFactory; use SimpleSAML\Module\oidc\Services\DatabaseLegacyOAuth2Import; use SimpleSAML\Module\oidc\Services\DatabaseMigration; use SimpleSAML\Module\oidc\Services\SessionMessagesService; use SimpleSAML\Utils\HTTP; -use Laminas\Diactoros\Response\RedirectResponse; -use Laminas\Diactoros\ServerRequest; +use SimpleSAML\XHTML\Template; use function in_array; @@ -39,7 +38,7 @@ public function __construct( } /** - * @throws Exception + * @throws \Exception */ public function __invoke(ServerRequest $request): Template|RedirectResponse { diff --git a/src/Controller/JwksController.php b/src/Controller/JwksController.php index c3a44a14..6a2bc93f 100644 --- a/src/Controller/JwksController.php +++ b/src/Controller/JwksController.php @@ -16,8 +16,8 @@ namespace SimpleSAML\Module\oidc\Controller; -use SimpleSAML\Module\oidc\Services\JsonWebKeySetService; use Laminas\Diactoros\Response\JsonResponse; +use SimpleSAML\Module\oidc\Services\JsonWebKeySetService; class JwksController { diff --git a/src/Controller/LogoutController.php b/src/Controller/LogoutController.php index 87040110..0502ec2c 100644 --- a/src/Controller/LogoutController.php +++ b/src/Controller/LogoutController.php @@ -4,13 +4,9 @@ namespace SimpleSAML\Module\oidc\Controller; -use Exception; use Laminas\Diactoros\ServerRequest; -use SimpleSAML\Error\BadRequest; -use SimpleSAML\Error\ConfigurationError; use SimpleSAML\Module\oidc\Factories\TemplateFactory; use SimpleSAML\Module\oidc\Server\AuthorizationServer; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\LogoutHandlers\BackChannelLogoutHandler; use SimpleSAML\Module\oidc\Server\RequestTypes\LogoutRequest; use SimpleSAML\Module\oidc\Services\LoggerService; @@ -33,9 +29,9 @@ public function __construct( } /** - * @throws BadRequest - * @throws OidcServerException - * @throws Throwable + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Throwable */ public function __invoke(ServerRequest $request): Response { @@ -93,7 +89,7 @@ public function __invoke(ServerRequest $request): Response } $currentSessionValidAuthorities = $this->sessionService->getCurrentSession()->getAuthorities(); - if (! empty($currentSessionValidAuthorities)) { + if (!empty($currentSessionValidAuthorities)) { $wasLogoutActionCalled = true; // Initiate logout for every valid auth source for the current session. foreach ($this->sessionService->getCurrentSession()->getAuthorities() as $authSourceId) { @@ -110,7 +106,7 @@ public function __invoke(ServerRequest $request): Response /** * Logout handler function registered using Session::registerLogoutHandler() during authn. - * @throws Exception + * @throws \Exception */ public static function logoutHandler(): void { @@ -119,7 +115,7 @@ public static function logoutHandler(): void // Only run this handler if logout was initiated using OIDC protocol. This is important since this // logout handler will (currently) also be called in re-authentication cases. // https://groups.google.com/g/simplesamlphp/c/-uhiVE8TaF4 - if (! SessionService::getIsOidcInitiatedLogoutForSession($session)) { + if (!SessionService::getIsOidcInitiatedLogoutForSession($session)) { return; } @@ -169,7 +165,7 @@ public static function logoutHandler(): void } /** - * @throws ConfigurationError + * @throws \SimpleSAML\Error\ConfigurationError */ protected function resolveResponse(LogoutRequest $logoutRequest, bool $wasLogoutActionCalled): Response { diff --git a/src/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php b/src/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php index 792c6e22..fd4e627e 100644 --- a/src/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php +++ b/src/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php @@ -16,11 +16,8 @@ namespace SimpleSAML\Module\oidc\Controller\Traits; -use JsonException; -use SimpleSAML\Error\Exception; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Error\BadRequest; -use SimpleSAML\Error\NotFound; +use SimpleSAML\Error; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\Repositories\ClientRepository; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; @@ -33,7 +30,11 @@ trait AuthenticatedGetClientFromRequestTrait private AuthContextService $authContextService; /** - * @throws BadRequest|NotFound|Exception|OidcServerException|JsonException + * @throws \JsonException + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ protected function getClientFromRequest(ServerRequestInterface $request): ClientEntityInterface { @@ -41,7 +42,7 @@ protected function getClientFromRequest(ServerRequestInterface $request): Client $clientId = empty($params['client_id']) ? null : (string)$params['client_id']; if (!is_string($clientId)) { - throw new BadRequest('Client id is missing.'); + throw new Error\BadRequest('Client id is missing.'); } $authedUser = null; if (!$this->authContextService->isSspAdmin()) { diff --git a/src/Controller/Traits/GetClientFromRequestTrait.php b/src/Controller/Traits/GetClientFromRequestTrait.php index f375cc36..01070d53 100644 --- a/src/Controller/Traits/GetClientFromRequestTrait.php +++ b/src/Controller/Traits/GetClientFromRequestTrait.php @@ -16,20 +16,20 @@ namespace SimpleSAML\Module\oidc\Controller\Traits; -use JsonException; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Error\BadRequest; -use SimpleSAML\Error\NotFound; +use SimpleSAML\Error; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\Repositories\ClientRepository; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; trait GetClientFromRequestTrait { protected ClientRepository $clientRepository; /** - * @throws BadRequest|NotFound|OidcServerException|JsonException + * @throws \JsonException + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ protected function getClientFromRequest(ServerRequestInterface $request): ClientEntityInterface { @@ -37,12 +37,12 @@ protected function getClientFromRequest(ServerRequestInterface $request): Client $clientId = empty($params['client_id']) ? null : (string)$params['client_id']; if (!is_string($clientId)) { - throw new BadRequest('Client id is missing.'); + throw new Error\BadRequest('Client id is missing.'); } $client = $this->clientRepository->findById($clientId); if (!$client) { - throw new NotFound('Client not found.'); + throw new Error\NotFound('Client not found.'); } return $client; diff --git a/src/Controller/Traits/RequestTrait.php b/src/Controller/Traits/RequestTrait.php index 5c12ae4b..aae26eef 100644 --- a/src/Controller/Traits/RequestTrait.php +++ b/src/Controller/Traits/RequestTrait.php @@ -25,7 +25,8 @@ trait RequestTrait /** * Handle CORS 'preflight' requests by checking if 'origin' is registered as allowed to make HTTP CORS requests, * typically initiated in browser by JavaScript clients. - * @throws OidcServerException + * + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ protected function handleCors(ServerRequest $request): Response { diff --git a/src/Controller/UserInfoController.php b/src/Controller/UserInfoController.php index c9de92ca..1d273fca 100644 --- a/src/Controller/UserInfoController.php +++ b/src/Controller/UserInfoController.php @@ -19,16 +19,14 @@ use Laminas\Diactoros\Response; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequest; -use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\ResourceServer; -use SimpleSAML\Error\UserNotFound; +use SimpleSAML\Error; use SimpleSAML\Module\oidc\Controller\Traits\RequestTrait; use SimpleSAML\Module\oidc\Entities\AccessTokenEntity; use SimpleSAML\Module\oidc\Entities\UserEntity; use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; use SimpleSAML\Module\oidc\Repositories\AllowedOriginRepository; use SimpleSAML\Module\oidc\Repositories\UserRepository; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; class UserInfoController @@ -45,9 +43,9 @@ public function __construct( } /** - * @throws UserNotFound - * @throws OidcServerException - * @throws OAuthServerException + * @throws \SimpleSAML\Error\UserNotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function __invoke(ServerRequest $request): Response { @@ -65,7 +63,7 @@ public function __invoke(ServerRequest $request): Response $accessToken = $this->accessTokenRepository->findById($tokenId); if (!$accessToken instanceof AccessTokenEntity) { - throw new UserNotFound('Access token not found'); + throw new Error\UserNotFound('Access token not found'); } $user = $this->getUser($accessToken); @@ -81,15 +79,15 @@ public function __invoke(ServerRequest $request): Response } /** - * @throws OidcServerException - * @throws UserNotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \SimpleSAML\Error\UserNotFound */ private function getUser(AccessTokenEntity $accessToken): UserEntity { $userIdentifier = (string) $accessToken->getUserIdentifier(); $user = $this->userRepository->getUserEntityByIdentifier($userIdentifier); if (!$user instanceof UserEntity) { - throw new UserNotFound("User $userIdentifier not found"); + throw new Error\UserNotFound("User $userIdentifier not found"); } return $user; diff --git a/src/Entities/AccessTokenEntity.php b/src/Entities/AccessTokenEntity.php index fb8755ea..cf86ed60 100644 --- a/src/Entities/AccessTokenEntity.php +++ b/src/Entities/AccessTokenEntity.php @@ -16,17 +16,12 @@ namespace SimpleSAML\Module\oidc\Entities; -use Exception; -use JsonException; -use Stringable; use DateTimeImmutable; use Lcobucci\JWT\Token; use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface; -use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Entities\Traits\AccessTokenTrait; use League\OAuth2\Server\Entities\Traits\EntityTrait; use League\OAuth2\Server\Entities\Traits\TokenEntityTrait; -use League\OAuth2\Server\Exception\OAuthServerException; use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\EntityStringRepresentationInterface; @@ -35,6 +30,7 @@ use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\JsonWebTokenBuilderService; use SimpleSAML\Module\oidc\Utils\TimestampGenerator; +use Stringable; /** * @psalm-suppress PropertyNotSetInConstructor @@ -69,7 +65,7 @@ private function __construct() /** * Create new Access Token from data. * - * @param ScopeEntityInterface[] $scopes + * @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes */ public static function fromData( OAuth2ClientEntityInterface $clientEntity, @@ -92,7 +88,9 @@ public static function fromData( } /** - * @throws OidcServerException|JsonException|Exception + * @throws \Exception + * @throws \JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public static function fromState(array $state): self { @@ -155,8 +153,7 @@ public function setRequestedClaims(array $requestedClaims): void /** * {@inheritdoc} - * @throws JsonException - * @throws JsonException + * @throws \JsonException */ public function getState(): array { @@ -175,7 +172,7 @@ public function getState(): array /** * Generate string representation, save it in a field, and return it. * @return string - * @throws OAuthServerException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function __toString(): string { @@ -195,9 +192,9 @@ public function toString(): ?string * Implemented instead of original AccessTokenTrait::convertToJWT() method in order to remove microseconds from * timestamps and to add claims like iss, etc., by using our own JWT builder service. * - * @return Token - * @throws OAuthServerException - * @throws Exception + * @return \Lcobucci\JWT\Token + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \Exception */ protected function convertToJWT(): Token { diff --git a/src/Entities/AuthCodeEntity.php b/src/Entities/AuthCodeEntity.php index 9cca532a..210651f4 100644 --- a/src/Entities/AuthCodeEntity.php +++ b/src/Entities/AuthCodeEntity.php @@ -16,17 +16,15 @@ namespace SimpleSAML\Module\oidc\Entities; use DateTimeImmutable; -use Exception; -use JsonException; use League\OAuth2\Server\Entities\Traits\EntityTrait; use League\OAuth2\Server\Entities\Traits\TokenEntityTrait; +use SimpleSAML\Module\oidc\Entities\Interfaces\AuthCodeEntityInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\MementoInterface; use SimpleSAML\Module\oidc\Entities\Traits\OidcAuthCodeTrait; use SimpleSAML\Module\oidc\Entities\Traits\RevokeTokenTrait; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Utils\TimestampGenerator; -use SimpleSAML\Module\oidc\Entities\Interfaces\AuthCodeEntityInterface; class AuthCodeEntity implements AuthCodeEntityInterface, MementoInterface { @@ -36,8 +34,9 @@ class AuthCodeEntity implements AuthCodeEntityInterface, MementoInterface use RevokeTokenTrait; /** - * @throws OidcServerException|JsonException - * @throws Exception + * @throws \Exception + * @throws \JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public static function fromState(array $state): self { @@ -81,7 +80,7 @@ public static function fromState(array $state): self } /** - * @throws JsonException + * @throws \JsonException */ public function getState(): array { diff --git a/src/Entities/ClientEntity.php b/src/Entities/ClientEntity.php index 3838a9af..81bf55e5 100644 --- a/src/Entities/ClientEntity.php +++ b/src/Entities/ClientEntity.php @@ -16,10 +16,9 @@ namespace SimpleSAML\Module\oidc\Entities; -use JsonException; -use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use League\OAuth2\Server\Entities\Traits\ClientTrait; use League\OAuth2\Server\Entities\Traits\EntityTrait; +use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; /** @@ -72,7 +71,7 @@ private function __construct() * @param string|null $owner * @param string[] $postLogoutRedirectUri * @param string|null $backChannelLogoutUri - * @return ClientEntityInterface + * @return \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface */ public static function fromData( string $id, @@ -107,8 +106,8 @@ public static function fromData( } /** - * @throws JsonException - * @throws OidcServerException + * @throws \JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public static function fromState(array $state): self { @@ -161,9 +160,7 @@ public static function fromState(array $state): self /** * {@inheritdoc} - * @throws JsonException - * @throws JsonException - * @throws JsonException + * @throws \JsonException */ public function getState(): array { diff --git a/src/Entities/RefreshTokenEntity.php b/src/Entities/RefreshTokenEntity.php index ca111f2e..d75ddf32 100644 --- a/src/Entities/RefreshTokenEntity.php +++ b/src/Entities/RefreshTokenEntity.php @@ -17,7 +17,6 @@ namespace SimpleSAML\Module\oidc\Entities; use DateTimeImmutable; -use Exception; use League\OAuth2\Server\Entities\Traits\EntityTrait; use League\OAuth2\Server\Entities\Traits\RefreshTokenTrait; use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface; @@ -35,8 +34,8 @@ class RefreshTokenEntity implements RefreshTokenEntityInterface use AssociateWithAuthCodeTrait; /** - * @throws OidcServerException - * @throws Exception + * @throws \Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public static function fromState(array $state): RefreshTokenEntityInterface { diff --git a/src/Entities/UserEntity.php b/src/Entities/UserEntity.php index c5ce1135..74233aa0 100644 --- a/src/Entities/UserEntity.php +++ b/src/Entities/UserEntity.php @@ -17,7 +17,6 @@ namespace SimpleSAML\Module\oidc\Entities; use DateTime; -use Exception; use League\OAuth2\Server\Entities\UserEntityInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\ClaimSetInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\MementoInterface; @@ -54,7 +53,7 @@ private function __construct() } /** - * @throws Exception + * @throws \Exception */ public static function fromData(string $identifier, array $claims = []): self { @@ -69,9 +68,8 @@ public static function fromData(string $identifier, array $claims = []): self } /** - * @throws OidcServerException - * @throws Exception - * @throws Exception + * @throws \Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public static function fromState(array $state): self { @@ -123,7 +121,7 @@ public function getClaims(): array } /** - * @throws Exception + * @throws \Exception */ public function setClaims(array $claims): self { diff --git a/src/Factories/AuthSimpleFactory.php b/src/Factories/AuthSimpleFactory.php index 5b339e19..70c91d9f 100644 --- a/src/Factories/AuthSimpleFactory.php +++ b/src/Factories/AuthSimpleFactory.php @@ -13,13 +13,13 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + namespace SimpleSAML\Module\oidc\Factories; -use Exception; use SimpleSAML\Auth\Simple; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Controller\Traits\GetClientFromRequestTrait; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\ClientRepository; class AuthSimpleFactory @@ -35,7 +35,7 @@ public function __construct( /** * @codeCoverageIgnore - * @throws Exception + * @throws \Exception */ public function build(ClientEntityInterface $clientEntity): Simple { @@ -46,7 +46,7 @@ public function build(ClientEntityInterface $clientEntity): Simple /** * @return Simple The default authsource - * @throws Exception + * @throws \Exception */ public function getDefaultAuthSource(): Simple { @@ -56,7 +56,7 @@ public function getDefaultAuthSource(): Simple /** * Get auth source defined on the client. If not set on the client, get the default auth source defined in config. * - * @throws Exception + * @throws \Exception */ public function resolveAuthSourceId(ClientEntityInterface $client): string { @@ -64,7 +64,7 @@ public function resolveAuthSourceId(ClientEntityInterface $client): string } /** - * @throws Exception + * @throws \Exception */ public function getDefaultAuthSourceId(): string { diff --git a/src/Factories/AuthorizationServerFactory.php b/src/Factories/AuthorizationServerFactory.php index 4f4762e2..c3e6c03f 100644 --- a/src/Factories/AuthorizationServerFactory.php +++ b/src/Factories/AuthorizationServerFactory.php @@ -13,18 +13,19 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + namespace SimpleSAML\Module\oidc\Factories; use DateInterval; -use SimpleSAML\Module\oidc\Server\AuthorizationServer; use League\OAuth2\Server\CryptKey; -use SimpleSAML\Module\oidc\Server\Grants\AuthCodeGrant; -use SimpleSAML\Module\oidc\Server\Grants\ImplicitGrant; -use SimpleSAML\Module\oidc\Server\Grants\OAuth2ImplicitGrant; use League\OAuth2\Server\Grant\RefreshTokenGrant; use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; use SimpleSAML\Module\oidc\Repositories\ClientRepository; use SimpleSAML\Module\oidc\Repositories\ScopeRepository; +use SimpleSAML\Module\oidc\Server\AuthorizationServer; +use SimpleSAML\Module\oidc\Server\Grants\AuthCodeGrant; +use SimpleSAML\Module\oidc\Server\Grants\ImplicitGrant; +use SimpleSAML\Module\oidc\Server\Grants\OAuth2ImplicitGrant; use SimpleSAML\Module\oidc\Server\ResponseTypes\IdTokenResponse; use SimpleSAML\Module\oidc\Utils\Checker\RequestRulesManager; diff --git a/src/Factories/ClaimTranslatorExtractorFactory.php b/src/Factories/ClaimTranslatorExtractorFactory.php index 762bf29f..b137eb66 100644 --- a/src/Factories/ClaimTranslatorExtractorFactory.php +++ b/src/Factories/ClaimTranslatorExtractorFactory.php @@ -16,9 +16,8 @@ namespace SimpleSAML\Module\oidc\Factories; -use Exception; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Entities\ClaimSetEntity; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; class ClaimTranslatorExtractorFactory @@ -32,7 +31,7 @@ public function __construct(private readonly ModuleConfig $moduleConfig) } /** - * @throws Exception + * @throws \Exception */ public function build(): ClaimTranslatorExtractor { diff --git a/src/Factories/FormFactory.php b/src/Factories/FormFactory.php index caaf84dc..ebd4713b 100644 --- a/src/Factories/FormFactory.php +++ b/src/Factories/FormFactory.php @@ -13,13 +13,13 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + namespace SimpleSAML\Module\oidc\Factories; use Nette\Forms\Form; use SimpleSAML\Error\Exception; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection; -use SimpleSAML\Session; +use SimpleSAML\Module\oidc\ModuleConfig; class FormFactory { @@ -30,7 +30,7 @@ public function __construct(private readonly ModuleConfig $moduleConfig, protect /** * @param class-string $classname Form classname * - * @throws \Exception + * @throws \SimpleSAML\Error\Exception * * @return mixed */ diff --git a/src/Factories/Grant/AuthCodeGrantFactory.php b/src/Factories/Grant/AuthCodeGrantFactory.php index ccb0d087..ffc6e9e1 100644 --- a/src/Factories/Grant/AuthCodeGrantFactory.php +++ b/src/Factories/Grant/AuthCodeGrantFactory.php @@ -17,7 +17,6 @@ namespace SimpleSAML\Module\oidc\Factories\Grant; use DateInterval; -use Exception; use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; use SimpleSAML\Module\oidc\Repositories\AuthCodeRepository; use SimpleSAML\Module\oidc\Repositories\RefreshTokenRepository; @@ -37,7 +36,7 @@ public function __construct( } /** - * @throws Exception + * @throws \Exception */ public function build(): AuthCodeGrant { diff --git a/src/Factories/ResourceServerFactory.php b/src/Factories/ResourceServerFactory.php index 9798b570..12245c9d 100644 --- a/src/Factories/ResourceServerFactory.php +++ b/src/Factories/ResourceServerFactory.php @@ -13,6 +13,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + namespace SimpleSAML\Module\oidc\Factories; use League\OAuth2\Server\AuthorizationValidators\AuthorizationValidatorInterface; diff --git a/src/Factories/TemplateFactory.php b/src/Factories/TemplateFactory.php index 7f756d47..0fdc5bc3 100644 --- a/src/Factories/TemplateFactory.php +++ b/src/Factories/TemplateFactory.php @@ -13,10 +13,10 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + namespace SimpleSAML\Module\oidc\Factories; use SimpleSAML\Configuration; -use SimpleSAML\Error\ConfigurationError; use SimpleSAML\XHTML\Template; class TemplateFactory @@ -29,7 +29,7 @@ public function __construct(Configuration $configuration) } /** - * @throws ConfigurationError + * @throws \SimpleSAML\Error\ConfigurationError */ public function render(string $templateName, array $data = []): Template { diff --git a/src/Forms/ClientForm.php b/src/Forms/ClientForm.php index 82c6c0fa..60e448d6 100644 --- a/src/Forms/ClientForm.php +++ b/src/Forms/ClientForm.php @@ -16,7 +16,6 @@ namespace SimpleSAML\Module\oidc\Forms; -use Exception; use Nette\Forms\Form; use SimpleSAML\Auth\Source; use SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection; @@ -50,7 +49,7 @@ class ClientForm extends Form final public const REGEX_HTTP_URI = '/^http(s?):\/\/[^\s\/$.?#][^\s#]*$/i'; /** - * @throws Exception + * @throws \Exception */ public function __construct(private readonly ModuleConfig $moduleConfig, protected CsrfProtection $csrfProtection) { @@ -159,11 +158,11 @@ public function getValues(string|object|bool|null $returnType = null, ?array $co } /** - * @throws Exception + * @throws \Exception */ public function setDefaults(object|array $data, bool $erase = false): static { - if (! is_array($data)) { + if (!is_array($data)) { if ($data instanceof Traversable) { $data = iterator_to_array($data); } else { @@ -176,7 +175,7 @@ public function setDefaults(object|array $data, bool $erase = false): static $data['redirect_uri'] = implode("\n", $redirectUris); // Allowed origins are only available for public clients (not for confidential clients). - if (! $data['is_confidential'] && isset($data['allowed_origin'])) { + if (!$data['is_confidential'] && isset($data['allowed_origin'])) { /** @var string[] $allowedOrigins */ $allowedOrigins = is_array($data['allowed_origin']) ? $data['allowed_origin'] : []; $data['allowed_origin'] = implode("\n", $allowedOrigins); @@ -197,7 +196,7 @@ public function setDefaults(object|array $data, bool $erase = false): static } /** - * @throws Exception + * @throws \Exception */ protected function buildForm(): void { @@ -244,7 +243,7 @@ protected function buildForm(): void } /** - * @throws Exception + * @throws \Exception */ protected function getScopes(): array { diff --git a/src/Forms/Controls/CsrfProtection.php b/src/Forms/Controls/CsrfProtection.php index a6decde5..0b93b51c 100644 --- a/src/Forms/Controls/CsrfProtection.php +++ b/src/Forms/Controls/CsrfProtection.php @@ -16,7 +16,6 @@ namespace SimpleSAML\Module\oidc\Forms\Controls; -use Exception; use Nette\Forms\Controls\CsrfProtection as BaseCsrfProtection; use Nette\InvalidStateException; use Nette\Utils\Random; @@ -29,7 +28,8 @@ class CsrfProtection extends BaseCsrfProtection /** @noinspection PhpMissingParentConstructorInspection */ /** - * @throws Exception + * @throws \Exception + * @throws \Nette\InvalidStateException */ public function __construct(string|Stringable|null $errorMessage, protected Session $sspSession) { @@ -37,7 +37,7 @@ public function __construct(string|Stringable|null $errorMessage, protected Sess // its constructor. This is to avoid setting a Nette session in CsrfProtection parent, and use the SSP one. $hiddentFieldParent = get_parent_class(get_parent_class($this)); - if (! is_string($hiddentFieldParent)) { + if (!is_string($hiddentFieldParent)) { throw new InvalidStateException('CsrfProtection initialization error'); } @@ -53,7 +53,7 @@ public function __construct(string|Stringable|null $errorMessage, protected Sess } /** - * @throws Exception + * @throws \Exception */ public function getToken(): string { diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php index e6d1e44b..28a2c5b1 100644 --- a/src/ModuleConfig.php +++ b/src/ModuleConfig.php @@ -17,14 +17,12 @@ namespace SimpleSAML\Module\oidc; use DateInterval; -use Exception; use Lcobucci\JWT\Signer; use Lcobucci\JWT\Signer\Rsa\Sha256; use ReflectionClass; -use ReflectionException; -use SimpleSAML\Module\oidc\Bridges\SspBridge; use SimpleSAML\Configuration; use SimpleSAML\Error\ConfigurationError; +use SimpleSAML\Module\oidc\Bridges\SspBridge; use SimpleSAML\Module\oidc\Codebooks\ScopesEnum; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; @@ -104,7 +102,7 @@ class ModuleConfig private readonly Configuration $sspConfig; /** - * @throws Exception + * @throws \Exception */ public function __construct( string $fileName = self::DEFAULT_FILE_NAME, // Primarily used for easy (unit) testing overrides. @@ -138,7 +136,7 @@ public function config(): Configuration } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException * @return non-empty-string */ public function getIssuer(): string @@ -164,7 +162,7 @@ public function getModuleUrl(string $path = null): string } /** - * @throws Exception + * @throws \Exception */ public function getOpenIDScopes(): array { @@ -172,7 +170,7 @@ public function getOpenIDScopes(): array } /** - * @throws Exception + * @throws \Exception */ public function getOpenIDPrivateScopes(): array { @@ -181,9 +179,9 @@ public function getOpenIDPrivateScopes(): array /** * @return void - * @throws Exception + * @throws \Exception * - * @throws ConfigurationError + * @throws \SimpleSAML\Error\ConfigurationError */ private function validate(): void { @@ -191,7 +189,7 @@ private function validate(): void array_walk( $privateScopes, /** - * @throws ConfigurationError + * @throws \SimpleSAML\Error\ConfigurationError */ function (array $scope, string $name): void { if (in_array($name, array_keys(self::$standardScopes), true)) { @@ -255,8 +253,8 @@ function (array $scope, string $name): void { /** * Get signer for OIDC protocol. * - * @throws ReflectionException - * @throws Exception + * @throws \ReflectionException + * @throws \Exception */ public function getProtocolSigner(): Signer { @@ -272,7 +270,7 @@ public function getProtocolSigner(): Signer /** * @param class-string $className * @throws \SimpleSAML\Error\ConfigurationError - * @throws ReflectionException + * @throws \ReflectionException */ protected function instantiateSigner(string $className): Signer { @@ -289,7 +287,7 @@ protected function instantiateSigner(string $className): Signer /** * Get the path to the public certificate used in OIDC protocol. * @return string The file system path - * @throws Exception + * @throws \Exception */ public function getProtocolCertPath(): string { @@ -302,7 +300,7 @@ public function getProtocolCertPath(): string /** * Get the path to the private key used in OIDC protocol. - * @throws Exception + * @throws \Exception */ public function getProtocolPrivateKeyPath(): string { @@ -316,7 +314,7 @@ public function getProtocolPrivateKeyPath(): string /** * Get the OIDC protocol private key passphrase. * @return ?string - * @throws Exception + * @throws \Exception */ public function getProtocolPrivateKeyPassPhrase(): ?string { @@ -327,7 +325,7 @@ public function getProtocolPrivateKeyPassPhrase(): ?string * Get autproc filters defined in the OIDC configuration. * * @return array - * @throws Exception + * @throws \Exception */ public function getAuthProcFilters(): array { @@ -338,7 +336,7 @@ public function getAuthProcFilters(): array * Get supported Authentication Context Class References (ACRs). * * @return array - * @throws Exception + * @throws \Exception */ public function getAcrValuesSupported(): array { @@ -349,7 +347,7 @@ public function getAcrValuesSupported(): array * Get a map of auth sources and their supported ACRs * * @return array - * @throws Exception + * @throws \Exception */ public function getAuthSourcesToAcrValuesMap(): array { @@ -358,7 +356,7 @@ public function getAuthSourcesToAcrValuesMap(): array /** * @return null|string - * @throws Exception + * @throws \Exception */ public function getForcedAcrValueForCookieAuthentication(): ?string { @@ -374,7 +372,7 @@ public function getForcedAcrValueForCookieAuthentication(): ?string } /** - * @throws Exception + * @throws \Exception */ public function getUserIdentifierAttribute(): string { @@ -411,7 +409,7 @@ public function getFederationPrivateKeyPassPhrase(): ?string /** * Return the path to the federation public certificate * @return ?string The file system path or null if not set. - * @throws Exception + * @throws \Exception */ public function getFederationCertPath(): ?string { @@ -424,7 +422,7 @@ public function getFederationCertPath(): ?string } /** - * @throws Exception + * @throws \Exception */ public function getFederationEntityStatementDuration(): DateInterval { diff --git a/src/Repositories/AbstractDatabaseRepository.php b/src/Repositories/AbstractDatabaseRepository.php index 1c62ad82..aff63ba6 100644 --- a/src/Repositories/AbstractDatabaseRepository.php +++ b/src/Repositories/AbstractDatabaseRepository.php @@ -15,7 +15,6 @@ */ namespace SimpleSAML\Module\oidc\Repositories; -use Exception; use SimpleSAML\Configuration; use SimpleSAML\Database; use SimpleSAML\Module\oidc\ModuleConfig; @@ -28,7 +27,7 @@ abstract class AbstractDatabaseRepository /** * ClientRepository constructor. - * @throws Exception + * @throws \Exception */ public function __construct(protected ModuleConfig $moduleConfig) { diff --git a/src/Repositories/AccessTokenRepository.php b/src/Repositories/AccessTokenRepository.php index 7070d585..d21057d9 100644 --- a/src/Repositories/AccessTokenRepository.php +++ b/src/Repositories/AccessTokenRepository.php @@ -16,8 +16,6 @@ namespace SimpleSAML\Module\oidc\Repositories; -use Exception; -use JsonException; use League\OAuth2\Server\Entities\AccessTokenEntityInterface as OAuth2AccessTokenEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface; use RuntimeException; @@ -26,7 +24,6 @@ use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface; use SimpleSAML\Module\oidc\Repositories\Interfaces\AccessTokenRepositoryInterface; use SimpleSAML\Module\oidc\Repositories\Traits\RevokeTokenByAuthCodeIdTrait; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Utils\TimestampGenerator; class AccessTokenRepository extends AbstractDatabaseRepository implements AccessTokenRepositoryInterface @@ -61,8 +58,8 @@ public function getNewToken( /** * {@inheritdoc} - * @throws Error - * @throws JsonException + * @throws \JsonException + * @throws \SimpleSAML\Error\Error */ public function persistNewAccessToken(OAuth2AccessTokenEntityInterface $accessTokenEntity): void { @@ -84,8 +81,8 @@ public function persistNewAccessToken(OAuth2AccessTokenEntityInterface $accessTo /** * Find Access Token by id. - * @throws Exception - * @throws OidcServerException + * @throws \Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function findById(string $tokenId): ?AccessTokenEntity { @@ -110,8 +107,8 @@ public function findById(string $tokenId): ?AccessTokenEntity /** * {@inheritdoc} - * @throws JsonException - * @throws OidcServerException + * @throws \JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function revokeAccessToken($tokenId): void { @@ -127,7 +124,7 @@ public function revokeAccessToken($tokenId): void /** * {@inheritdoc} - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function isAccessTokenRevoked($tokenId): bool { @@ -142,7 +139,7 @@ public function isAccessTokenRevoked($tokenId): bool /** * Removes expired access tokens. - * @throws Exception + * @throws \Exception */ public function removeExpired(): void { @@ -163,7 +160,7 @@ public function removeExpired(): void } /** - * @throws JsonException + * @throws \JsonException */ private function update(AccessTokenEntity $accessTokenEntity): void { diff --git a/src/Repositories/AuthCodeRepository.php b/src/Repositories/AuthCodeRepository.php index 7e286db6..2f95d179 100644 --- a/src/Repositories/AuthCodeRepository.php +++ b/src/Repositories/AuthCodeRepository.php @@ -16,8 +16,6 @@ namespace SimpleSAML\Module\oidc\Repositories; -use Exception; -use JsonException; use League\OAuth2\Server\Entities\AuthCodeEntityInterface as OAuth2AuthCodeEntityInterface; use RuntimeException; use SimpleSAML\Error\Error; @@ -36,7 +34,7 @@ public function getTableName(): string } /** - * @return AuthCodeEntityInterface + * @return \SimpleSAML\Module\oidc\Entities\Interfaces\AuthCodeEntityInterface */ public function getNewAuthCode(): AuthCodeEntityInterface { @@ -45,7 +43,8 @@ public function getNewAuthCode(): AuthCodeEntityInterface /** * {@inheritdoc} - * @throws Error|JsonException + * @throws \JsonException + * @throws \SimpleSAML\Error\Error */ public function persistNewAuthCode(OAuth2AuthCodeEntityInterface $authCodeEntity): void { @@ -67,7 +66,7 @@ public function persistNewAuthCode(OAuth2AuthCodeEntityInterface $authCodeEntity /** * Find Auth Code by id. - * @throws Exception + * @throws \Exception */ public function findById(string $codeId): ?AuthCodeEntityInterface { @@ -92,8 +91,8 @@ public function findById(string $codeId): ?AuthCodeEntityInterface /** * {@inheritdoc} - * @throws JsonException - * @throws Exception + * @throws \Exception + * @throws \JsonException */ public function revokeAuthCode($codeId): void { @@ -109,7 +108,7 @@ public function revokeAuthCode($codeId): void /** * {@inheritdoc} - * @throws Exception + * @throws \Exception */ public function isAuthCodeRevoked($codeId): bool { @@ -124,7 +123,7 @@ public function isAuthCodeRevoked($codeId): bool /** * Removes expired auth codes. - * @throws Exception + * @throws \Exception */ public function removeExpired(): void { @@ -137,7 +136,7 @@ public function removeExpired(): void } /** - * @throws JsonException + * @throws \JsonException */ private function update(AuthCodeEntity $authCodeEntity): void { diff --git a/src/Repositories/ClientRepository.php b/src/Repositories/ClientRepository.php index d76ae5ef..838b1a18 100644 --- a/src/Repositories/ClientRepository.php +++ b/src/Repositories/ClientRepository.php @@ -15,14 +15,11 @@ */ namespace SimpleSAML\Module\oidc\Repositories; -use Exception; -use JsonException; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use SimpleSAML\Module\oidc\Entities\ClientEntity; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\ModuleConfig; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; class ClientRepository extends AbstractDatabaseRepository implements ClientRepositoryInterface { @@ -35,8 +32,8 @@ public function getTableName(): string /** * {@inheritdoc} - * @throws OAuthServerException - * @throws JsonException + * @throws \JsonException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function getClientEntity($clientIdentifier) { @@ -55,8 +52,8 @@ public function getClientEntity($clientIdentifier) /** * @inheritDoc - * @throws OAuthServerException - * @throws JsonException + * @throws \JsonException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function validateClient($clientIdentifier, $clientSecret, $grantType): bool { @@ -74,8 +71,8 @@ public function validateClient($clientIdentifier, $clientSecret, $grantType): bo } /** - * @throws OidcServerException - * @throws JsonException + * @throws \JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function findById(string $clientIdentifier, ?string $owner = null): ?ClientEntityInterface { @@ -120,8 +117,9 @@ private function addOwnerWhereClause(string $query, array $params, ?string $owne } /** - * @return ClientEntityInterface[] - * @throws OidcServerException|JsonException + * @return \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface[] + * @throws \JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function findAll(?string $owner = null): array { @@ -150,8 +148,12 @@ public function findAll(?string $owner = null): array } /** - * @return array{numPages: int, currentPage: int, items: ClientEntityInterface[]} - * @throws Exception + * @return array{ + * numPages: int, + * currentPage: int, + * items: \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface[] + * } + * @throws \Exception */ public function findPaginated(int $page = 1, string $query = '', ?string $owner = null): array { @@ -301,7 +303,7 @@ private function count(string $query, ?string $owner): int } /** - * @throws Exception + * @throws \Exception */ private function getItemsPerPage(): int { diff --git a/src/Repositories/CodeChallengeVerifiersRepository.php b/src/Repositories/CodeChallengeVerifiersRepository.php index 2f65be3d..ac0c700e 100644 --- a/src/Repositories/CodeChallengeVerifiersRepository.php +++ b/src/Repositories/CodeChallengeVerifiersRepository.php @@ -14,7 +14,7 @@ class CodeChallengeVerifiersRepository { /** - * @var CodeChallengeVerifierInterface[] + * @var \League\OAuth2\Server\CodeChallengeVerifiers\CodeChallengeVerifierInterface[] */ protected array $codeChallengeVerifiers = []; @@ -30,7 +30,7 @@ public function __construct() } /** - * @return CodeChallengeVerifierInterface[] + * @return \League\OAuth2\Server\CodeChallengeVerifiers\CodeChallengeVerifierInterface[] */ public function getAll(): array { @@ -38,7 +38,8 @@ public function getAll(): array } /** - * @return CodeChallengeVerifierInterface|null Verifier for the method or null if not supported. + * @return \League\OAuth2\Server\CodeChallengeVerifiers\CodeChallengeVerifierInterface|null + * Verifier for the method or null if not supported. */ public function get(string $method): ?CodeChallengeVerifierInterface { diff --git a/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php b/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php index d51f145e..dae29026 100644 --- a/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php +++ b/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php @@ -5,7 +5,6 @@ namespace SimpleSAML\Module\oidc\Repositories\Interfaces; use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface; -use League\OAuth2\Server\Entities\ScopeEntityInterface as OAuth2ScopeEntityInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface as OAuth2AccessTokenRepositoryInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface; @@ -19,12 +18,12 @@ public function revokeByAuthCodeId(string $authCodeId): void; /** * Create a new access token * - * @param OAuth2ClientEntityInterface $clientEntity - * @param OAuth2ScopeEntityInterface[] $scopes + * @param \League\OAuth2\Server\Entities\ClientEntityInterface $clientEntity + * @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes * @param mixed $userIdentifier * @param string|null $authCodeId * @param array|null $requestedClaims Any requested claims - * @return AccessTokenEntityInterface + * @return \SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface */ public function getNewToken( OAuth2ClientEntityInterface $clientEntity, diff --git a/src/Repositories/RefreshTokenRepository.php b/src/Repositories/RefreshTokenRepository.php index 3a7adde4..8cbe30c9 100644 --- a/src/Repositories/RefreshTokenRepository.php +++ b/src/Repositories/RefreshTokenRepository.php @@ -16,7 +16,6 @@ namespace SimpleSAML\Module\oidc\Repositories; -use Exception; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface as OAuth2RefreshTokenEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use RuntimeException; @@ -24,7 +23,6 @@ use SimpleSAML\Module\oidc\Entities\RefreshTokenEntity; use SimpleSAML\Module\oidc\Repositories\Interfaces\RefreshTokenRepositoryInterface; use SimpleSAML\Module\oidc\Repositories\Traits\RevokeTokenByAuthCodeIdTrait; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Utils\TimestampGenerator; class RefreshTokenRepository extends AbstractDatabaseRepository implements RefreshTokenRepositoryInterface @@ -51,7 +49,7 @@ public function getNewRefreshToken(): RefreshTokenEntityInterface /** * {@inheritdoc} - * @throws OAuthServerException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function persistNewRefreshToken(OAuth2RefreshTokenEntityInterface $refreshTokenEntity): void { @@ -73,8 +71,8 @@ public function persistNewRefreshToken(OAuth2RefreshTokenEntityInterface $refres /** * Find Refresh Token by id. - * @throws OidcServerException - * @throws Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function findById(string $tokenId): ?RefreshTokenEntityInterface { @@ -99,7 +97,7 @@ public function findById(string $tokenId): ?RefreshTokenEntityInterface /** * {@inheritdoc} - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function revokeRefreshToken($tokenId): void { @@ -115,7 +113,7 @@ public function revokeRefreshToken($tokenId): void /** * {@inheritdoc} - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function isRefreshTokenRevoked($tokenId): bool { @@ -130,7 +128,7 @@ public function isRefreshTokenRevoked($tokenId): bool /** * Removes expired refresh tokens. - * @throws Exception + * @throws \Exception */ public function removeExpired(): void { diff --git a/src/Repositories/ScopeRepository.php b/src/Repositories/ScopeRepository.php index 8c71f5a3..5bb57aab 100644 --- a/src/Repositories/ScopeRepository.php +++ b/src/Repositories/ScopeRepository.php @@ -15,7 +15,6 @@ */ namespace SimpleSAML\Module\oidc\Repositories; -use Exception; use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; @@ -34,7 +33,7 @@ public function getTableName(): ?string /** * {@inheritdoc} - * @throws Exception + * @throws \Exception */ public function getScopeEntityByIdentifier($identifier): ScopeEntity|ScopeEntityInterface|null { diff --git a/src/Repositories/UserRepository.php b/src/Repositories/UserRepository.php index f90b68b6..3bd27077 100644 --- a/src/Repositories/UserRepository.php +++ b/src/Repositories/UserRepository.php @@ -20,9 +20,8 @@ use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; -use SimpleSAML\Module\oidc\Repositories\Interfaces\IdentityProviderInterface; use SimpleSAML\Module\oidc\Entities\UserEntity; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; +use SimpleSAML\Module\oidc\Repositories\Interfaces\IdentityProviderInterface; class UserRepository extends AbstractDatabaseRepository implements UserRepositoryInterface, IdentityProviderInterface { @@ -36,8 +35,8 @@ public function getTableName(): string /** * @param string $identifier * - * @return UserEntity|null - * @throws OidcServerException + * @return \SimpleSAML\Module\oidc\Entities\UserEntity|null + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function getUserEntityByIdentifier(string $identifier): ?UserEntity { @@ -63,7 +62,7 @@ public function getUserEntityByIdentifier(string $identifier): ?UserEntity /** * {@inheritdoc} - * @throws Exception + * @throws \Exception */ public function getUserEntityByUserCredentials( $username, diff --git a/src/Server/AuthorizationServer.php b/src/Server/AuthorizationServer.php index 448ee6c2..cfda6ba7 100644 --- a/src/Server/AuthorizationServer.php +++ b/src/Server/AuthorizationServer.php @@ -5,20 +5,19 @@ namespace SimpleSAML\Module\oidc\Server; use Defuse\Crypto\Key; -use Lcobucci\JWT\UnencryptedToken; use League\OAuth2\Server\AuthorizationServer as OAuth2AuthorizationServer; use League\OAuth2\Server\CryptKey; -use LogicException; -use SimpleSAML\Error\BadRequest; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\RequestTypes\AuthorizationRequest as OAuth2AuthorizationRequest; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; +use LogicException; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Error\BadRequest; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; -use SimpleSAML\Module\oidc\Server\RequestTypes\LogoutRequest; use SimpleSAML\Module\oidc\Server\Grants\Interfaces\AuthorizationValidatableWithCheckerResultBagInterface; +use SimpleSAML\Module\oidc\Server\RequestTypes\LogoutRequest; use SimpleSAML\Module\oidc\Utils\Checker\RequestRulesManager; use SimpleSAML\Module\oidc\Utils\Checker\Rules\ClientIdRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\IdTokenHintRule; @@ -26,7 +25,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\Rules\RedirectUriRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\StateRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\UiLocalesRule; -use Throwable; class AuthorizationServer extends OAuth2AuthorizationServer { @@ -34,8 +32,9 @@ class AuthorizationServer extends OAuth2AuthorizationServer protected ClientRepositoryInterface $clientRepository; protected RequestRulesManager $requestRulesManager; + /** - * @var CryptKey + * @var \League\OAuth2\Server\CryptKey * @psalm-suppress PropertyNotSetInConstructor */ protected $publicKey; @@ -71,7 +70,9 @@ public function __construct( /** * @inheritDoc - * @throws BadRequest|Throwable + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Throwable */ public function validateAuthorizationRequest(ServerRequestInterface $request): OAuth2AuthorizationRequest { @@ -109,8 +110,8 @@ public function validateAuthorizationRequest(ServerRequestInterface $request): O } /** - * @throws Throwable - * @throws BadRequest + * @throws \Throwable + * @throws \SimpleSAML\Error\BadRequest */ public function validateLogoutRequest(ServerRequestInterface $request): LogoutRequest { @@ -128,7 +129,7 @@ public function validateLogoutRequest(ServerRequestInterface $request): LogoutRe throw new BadRequest($reason); } - /** @var UnencryptedToken|null $idTokenHint */ + /** @var \Lcobucci\JWT\UnencryptedToken|null $idTokenHint */ $idTokenHint = $resultBag->getOrFail(IdTokenHintRule::class)->getValue(); /** @var string|null $postLogoutRedirectUri */ $postLogoutRedirectUri = $resultBag->getOrFail(PostLogoutRedirectUriRule::class)->getValue(); diff --git a/src/Server/Exceptions/OidcServerException.php b/src/Server/Exceptions/OidcServerException.php index 90f5e53b..cc37db01 100644 --- a/src/Server/Exceptions/OidcServerException.php +++ b/src/Server/Exceptions/OidcServerException.php @@ -49,7 +49,7 @@ class OidcServerException extends OAuthServerException * @param int $httpStatusCode HTTP status code to send (default = 400) * @param null|string $hint A helper hint * @param null|string $redirectUri An HTTP URI to redirect the user back to - * @param Throwable|null $previous Previous exception + * @param \Throwable|null $previous Previous exception * @param string|null $state */ public function __construct( @@ -133,7 +133,7 @@ public static function invalidScope( * * @param string $parameter * @param string|null $hint - * @param Throwable|null $previous + * @param \Throwable|null $previous * @param string|null $redirectUri * @param string|null $state * @param bool $useFragment Use URI fragment to return error parameters @@ -159,7 +159,7 @@ public static function invalidRequest( /** * @param string|null $hint * @param string|null $redirectUri - * @param Throwable|null $previous + * @param \Throwable|null $previous * @param string|null $state * @param bool $useFragment Use URI fragment to return error parameters * @return static @@ -183,7 +183,7 @@ public static function accessDenied( * * @param string|null $hint * @param string|null $redirectUri - * @param Throwable|null $previous + * @param \Throwable|null $previous * @param string|null $state * @param bool $useFragment Use URI fragment to return error parameters * @@ -209,7 +209,7 @@ public static function loginRequired( * * @param string|null $hint * @param string|null $redirectUri - * @param Throwable|null $previous + * @param \Throwable|null $previous * @param string|null $state * @param bool $useFragment Use URI fragment to return error parameters * @@ -234,7 +234,7 @@ public static function requestNotSupported( * Invalid refresh token. * * @param string|null $hint - * @param Throwable|null $previous + * @param \Throwable|null $previous * * @return self * @psalm-suppress LessSpecificImplementedReturnType @@ -313,12 +313,12 @@ public function setState(string $state = null): void /** * Generate an HTTP response. * - * @param ResponseInterface $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(). + * that this can also be set using useFragmentInHttpResponses(). * @param int $jsonOptions options passed to json_encode * - * @return ResponseInterface + * @return \Psr\Http\Message\ResponseInterface */ public function generateHttpResponse( ResponseInterface $response, diff --git a/src/Server/Grants/AuthCodeGrant.php b/src/Server/Grants/AuthCodeGrant.php index b76791f9..7d3b4761 100644 --- a/src/Server/Grants/AuthCodeGrant.php +++ b/src/Server/Grants/AuthCodeGrant.php @@ -4,14 +4,10 @@ namespace SimpleSAML\Module\oidc\Server\Grants; -use Exception; use DateInterval; use DateTimeImmutable; -use JsonException; -use League\OAuth2\Server\CodeChallengeVerifiers\CodeChallengeVerifierInterface; use League\OAuth2\Server\CodeChallengeVerifiers\PlainVerifier; use League\OAuth2\Server\CodeChallengeVerifiers\S256Verifier; -use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Entities\AccessTokenEntityInterface as OAuth2AccessTokenEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; @@ -19,9 +15,6 @@ use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; use League\OAuth2\Server\Grant\AuthCodeGrant as OAuth2AuthCodeGrant; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface as OAuth2AuthCodeRepositoryInterface; -use League\OAuth2\Server\Repositories\ClientRepositoryInterface; -use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; -use League\OAuth2\Server\Repositories\UserRepositoryInterface; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequest as OAuth2AuthorizationRequest; use League\OAuth2\Server\ResponseTypes\RedirectResponse; @@ -29,7 +22,6 @@ use LogicException; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\AuthCodeEntityInterface; -use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\RefreshTokenEntityInterface; use SimpleSAML\Module\oidc\Entities\UserEntity; use SimpleSAML\Module\oidc\Repositories\Interfaces\AccessTokenRepositoryInterface; @@ -61,7 +53,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\Rules\ScopeRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\StateRule; use SimpleSAML\Module\oidc\Utils\ScopeHelper; -use Throwable; class AuthCodeGrant extends OAuth2AuthCodeGrant implements // phpcs:ignore @@ -75,22 +66,23 @@ class AuthCodeGrant extends OAuth2AuthCodeGrant implements protected DateInterval $authCodeTTL; - /** - * @var CodeChallengeVerifierInterface[] - */ + /** @var \League\OAuth2\Server\CodeChallengeVerifiers\CodeChallengeVerifierInterface[] */ protected array $codeChallengeVerifiers = []; /** + * @var \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface * @psalm-suppress PropertyNotSetInConstructor */ protected $authCodeRepository; /** + * @var \League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface * @psalm-suppress PropertyNotSetInConstructor */ protected $accessTokenRepository; /** + * @var \League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface * @psalm-suppress PropertyNotSetInConstructor */ protected $refreshTokenRepository; @@ -100,7 +92,7 @@ class AuthCodeGrant extends OAuth2AuthCodeGrant implements /** * @var bool * @psalm-suppress PropertyNotSetInConstructor - */ + */ protected $revokeRefreshTokens; /** @@ -110,25 +102,25 @@ class AuthCodeGrant extends OAuth2AuthCodeGrant implements protected $defaultScope; /** - * @var UserRepositoryInterface + * @var \League\OAuth2\Server\Repositories\UserRepositoryInterface * @psalm-suppress PropertyNotSetInConstructor */ protected $userRepository; /** - * @var ScopeRepositoryInterface + * @var \League\OAuth2\Server\Repositories\ScopeRepositoryInterface * @psalm-suppress PropertyNotSetInConstructor */ protected $scopeRepository; /** - * @var ClientRepositoryInterface + * @var \League\OAuth2\Server\Repositories\ClientRepositoryInterface * @psalm-suppress PropertyNotSetInConstructor */ protected $clientRepository; /** - * @var CryptKey + * @var \League\OAuth2\Server\CryptKey * @psalm-suppress PropertyNotSetInConstructor */ protected $privateKey; @@ -145,7 +137,7 @@ class AuthCodeGrant extends OAuth2AuthCodeGrant implements * acr?: null|string, * session_id?: null|string * } - * @throws Exception + * @throws \Exception */ public function __construct( OAuth2AuthCodeRepositoryInterface $authCodeRepository, @@ -191,8 +183,8 @@ public function isOidcCandidate( /** * @inheritDoc - * @throws OAuthServerException - * @throws JsonException + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \JsonException */ public function completeAuthorizationRequest( OAuth2AuthorizationRequest $authorizationRequest, @@ -207,9 +199,9 @@ public function completeAuthorizationRequest( /** * This is reimplementation of OAuth2 completeAuthorizationRequest method with addition of nonce handling. * - * @throws OAuthServerException - * @throws UniqueTokenIdentifierConstraintViolationException - * @throws JsonException + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException + * @throws \JsonException */ public function completeOidcAuthorizationRequest( AuthorizationRequest $authorizationRequest, @@ -276,9 +268,9 @@ public function completeOidcAuthorizationRequest( } /** - * @param ScopeEntityInterface[] $scopes - * @throws OAuthServerException - * @throws UniqueTokenIdentifierConstraintViolationException + * @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException */ protected function issueOidcAuthCode( DateInterval $authCodeTTL, @@ -290,13 +282,13 @@ protected function issueOidcAuthCode( ): AuthCodeEntityInterface { $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; - if (! is_a($this->authCodeRepository, AuthCodeRepositoryInterface::class)) { + if (!is_a($this->authCodeRepository, AuthCodeRepositoryInterface::class)) { throw OidcServerException::serverError('Unexpected auth code repository entity type.'); } $authCode = $this->authCodeRepository->getNewAuthCode(); - if (! is_a($authCode, AuthCodeEntityInterface::class)) { + if (!is_a($authCode, AuthCodeEntityInterface::class)) { throw OidcServerException::serverError('Unexpected auth code entity type.'); } @@ -331,7 +323,7 @@ protected function issueOidcAuthCode( /** * Get the client redirect URI if not set in the request. * - * @param OAuth2AuthorizationRequest $authorizationRequest + * @param \League\OAuth2\Server\RequestTypes\AuthorizationRequest $authorizationRequest * * @return string */ @@ -349,17 +341,15 @@ protected function getClientRedirectUri(OAuth2AuthorizationRequest $authorizatio /** * Reimplementation respondToAccessTokenRequest because of nonce feature. * - * @param ServerRequestInterface $request - * @param ResponseTypeInterface $responseType - * @param DateInterval $accessTokenTTL + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface $responseType + * @param \DateInterval $accessTokenTTL * - * @return ResponseTypeInterface + * @return \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface * * TODO refactor to request checkers - * @throws OAuthServerException - * @throws JsonException - * @throws JsonException - * @throws JsonException + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \JsonException * */ public function respondToAccessTokenRequest( @@ -523,10 +513,10 @@ public function respondToAccessTokenRequest( * Reimplementation because of private parent access * * @param object $authCodePayload - * @param OAuth2ClientEntityInterface $client - * @param ServerRequestInterface $request - * @throws OAuthServerException - * @throws OidcServerException + * @param \League\OAuth2\Server\Entities\ClientEntityInterface $client + * @param \Psr\Http\Message\ServerRequestInterface $request + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ protected function validateAuthorizationCode( object $authCodePayload, @@ -585,7 +575,7 @@ protected function validateAuthorizationCode( /** * @inheritDoc - * @throws Throwable + * @throws \Throwable */ public function validateAuthorizationRequestWithCheckerResultBag( ServerRequestInterface $request, @@ -608,7 +598,7 @@ public function validateAuthorizationRequestWithCheckerResultBag( $redirectUri = $resultBag->getOrFail(RedirectUriRule::class)->getValue(); /** @var string|null $state */ $state = $resultBag->getOrFail(StateRule::class)->getValue(); - /** @var ClientEntityInterface $client */ + /** @var \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface $client */ $client = $resultBag->getOrFail(ClientIdRule::class)->getValue(); // Some rules have to have certain things available in order to work properly... @@ -623,7 +613,7 @@ public function validateAuthorizationRequestWithCheckerResultBag( $resultBag = $this->requestRulesManager->check($request, $rulesToExecute); - /** @var ScopeEntityInterface[] $scopes */ + /** @var \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes */ $scopes = $resultBag->getOrFail(ScopeRule::class)->getValue(); $oAuth2AuthorizationRequest = new OAuth2AuthorizationRequest(); @@ -681,11 +671,11 @@ public function validateAuthorizationRequestWithCheckerResultBag( } /** - * @param OAuth2AccessTokenEntityInterface $accessToken + * @param \League\OAuth2\Server\Entities\AccessTokenEntityInterface $accessToken * @param string|null $authCodeId - * @return RefreshTokenEntityInterface|null - * @throws OAuthServerException - * @throws UniqueTokenIdentifierConstraintViolationException + * @return \SimpleSAML\Module\oidc\Entities\Interfaces\RefreshTokenEntityInterface|null + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException */ protected function issueRefreshToken( OAuth2AccessTokenEntityInterface $accessToken, diff --git a/src/Server/Grants/ImplicitGrant.php b/src/Server/Grants/ImplicitGrant.php index e773a7e6..8c6baffc 100644 --- a/src/Server/Grants/ImplicitGrant.php +++ b/src/Server/Grants/ImplicitGrant.php @@ -5,10 +5,6 @@ namespace SimpleSAML\Module\oidc\Server\Grants; use DateInterval; -use Exception; -use League\OAuth2\Server\CryptKey; -use League\OAuth2\Server\Exception\OAuthServerException; -use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; use League\OAuth2\Server\RequestTypes\AuthorizationRequest as OAuth2AuthorizationRequest; use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; @@ -34,15 +30,14 @@ use SimpleSAML\Module\oidc\Utils\Checker\Rules\RequiredNonceRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\RequiredOpenIdScopeRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\ResponseTypeRule; -use Throwable; class ImplicitGrant extends OAuth2ImplicitGrant { use IssueAccessTokenTrait; /** - * @var CryptKey * @psalm-suppress PropertyNotSetInConstructor + * @var \League\OAuth2\Server\CryptKey */ protected $privateKey; @@ -79,11 +74,11 @@ public function canRespondToAuthorizationRequest(ServerRequestInterface $request /** * {@inheritdoc} - * @param OAuth2AuthorizationRequest $authorizationRequest - * @return ResponseTypeInterface - * @throws OidcServerException - * @throws OAuthServerException - * @throws UniqueTokenIdentifierConstraintViolationException + * @param \League\OAuth2\Server\RequestTypes\AuthorizationRequest $authorizationRequest + * @return \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function completeAuthorizationRequest( OAuth2AuthorizationRequest $authorizationRequest, @@ -96,8 +91,8 @@ public function completeAuthorizationRequest( } /** - * @throws Throwable - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Throwable */ public function validateAuthorizationRequestWithCheckerResultBag( ServerRequestInterface $request, @@ -156,10 +151,10 @@ public function validateAuthorizationRequestWithCheckerResultBag( } /** - * @throws UniqueTokenIdentifierConstraintViolationException - * @throws OAuthServerException - * @throws OidcServerException - * @throws Exception + * @throws \Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ private function completeOidcAuthorizationRequest(AuthorizationRequest $authorizationRequest): ResponseTypeInterface { diff --git a/src/Server/Grants/OAuth2ImplicitGrant.php b/src/Server/Grants/OAuth2ImplicitGrant.php index 5e040ade..d7aeed8f 100644 --- a/src/Server/Grants/OAuth2ImplicitGrant.php +++ b/src/Server/Grants/OAuth2ImplicitGrant.php @@ -5,20 +5,10 @@ namespace SimpleSAML\Module\oidc\Server\Grants; use DateInterval; -use League\OAuth2\Server\CryptKey; -use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Grant\ImplicitGrant; -use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; -use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; -use League\OAuth2\Server\Repositories\ClientRepositoryInterface; -use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; -use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; -use League\OAuth2\Server\Repositories\UserRepositoryInterface; use League\OAuth2\Server\RequestTypes\AuthorizationRequest as OAuth2AuthorizationRequest; use LogicException; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\Grants\Interfaces\AuthorizationValidatableWithCheckerResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\RequestRulesManager; @@ -26,7 +16,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\Rules\RedirectUriRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\ScopeRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\StateRule; -use Throwable; class OAuth2ImplicitGrant extends ImplicitGrant implements AuthorizationValidatableWithCheckerResultBagInterface { @@ -35,57 +24,39 @@ class OAuth2ImplicitGrant extends ImplicitGrant implements AuthorizationValidata protected string $queryDelimiter; protected RequestRulesManager $requestRulesManager; - /** - * @var bool - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $revokeRefreshTokens; - /** - * @var string - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $defaultScope; - /** - * @var CryptKey - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $privateKey; - /** - * @var DateInterval - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $refreshTokenTTL; - /** - * @var UserRepositoryInterface - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $userRepository; - /** - * @var RefreshTokenRepositoryInterface - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $refreshTokenRepository; - /** - * @var AuthCodeRepositoryInterface - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $authCodeRepository; + /** - * @var ScopeRepositoryInterface * @psalm-suppress PropertyNotSetInConstructor + * @var \League\OAuth2\Server\Repositories\ScopeRepositoryInterface */ protected $scopeRepository; - /** - * @var AccessTokenRepositoryInterface - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $accessTokenRepository; - /** - * @var ClientRepositoryInterface - * @psalm-suppress PropertyNotSetInConstructor - */ - protected $clientRepository; + /** @psalm-suppress PropertyNotSetInConstructor */ + protected $clientRepository; /** @@ -108,8 +79,8 @@ public function __construct( } /** - * @throws Throwable - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Throwable */ public function validateAuthorizationRequestWithCheckerResultBag( ServerRequestInterface $request, @@ -126,7 +97,7 @@ public function validateAuthorizationRequestWithCheckerResultBag( $redirectUri = $resultBag->getOrFail(RedirectUriRule::class)->getValue(); /** @var string|null $state */ $state = $resultBag->getOrFail(StateRule::class)->getValue(); - /** @var ClientEntityInterface $client */ + /** @var \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface $client */ $client = $resultBag->getOrFail(ClientIdRule::class)->getValue(); // Some rules have to have certain things available in order to work properly... @@ -135,7 +106,7 @@ public function validateAuthorizationRequestWithCheckerResultBag( $resultBag = $this->requestRulesManager->check($request, $rulesToExecute); - /** @var ScopeEntityInterface[] $scopes */ + /** @var \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes */ $scopes = $resultBag->getOrFail(ScopeRule::class)->getValue(); $oAuth2AuthorizationRequest = new OAuth2AuthorizationRequest(); diff --git a/src/Server/Grants/RefreshTokenGrant.php b/src/Server/Grants/RefreshTokenGrant.php index 3f2dc717..13d947ca 100644 --- a/src/Server/Grants/RefreshTokenGrant.php +++ b/src/Server/Grants/RefreshTokenGrant.php @@ -5,14 +5,7 @@ namespace SimpleSAML\Module\oidc\Server\Grants; use Exception; -use JsonException; -use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Grant\RefreshTokenGrant as OAuth2RefreshTokenGrant; -use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; -use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; -use League\OAuth2\Server\Repositories\ClientRepositoryInterface; -use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; -use League\OAuth2\Server\Repositories\UserRepositoryInterface; use League\OAuth2\Server\RequestEvent; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; @@ -23,50 +16,33 @@ class RefreshTokenGrant extends OAuth2RefreshTokenGrant { - /** - * @var bool - * @psalm-suppress PropertyNotSetInConstructor - */ + /** @psalm-suppress PropertyNotSetInConstructor */ protected $revokeRefreshTokens; - /** - * @var string - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $defaultScope; - /** - * @var CryptKey - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $privateKey; - /** - * @var UserRepositoryInterface - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $userRepository; - /** - * @var AuthCodeRepositoryInterface - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $authCodeRepository; - /** - * @var ScopeRepositoryInterface - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $scopeRepository; - /** - * @var AccessTokenRepositoryInterface - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $accessTokenRepository; - /** - * @var ClientRepositoryInterface - * @psalm-suppress PropertyNotSetInConstructor - */ + + /** @psalm-suppress PropertyNotSetInConstructor */ protected $clientRepository; /** - * @throws OidcServerException - * @throws JsonException + * @throws \JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ protected function validateOldRefreshToken(ServerRequestInterface $request, $clientId): array { diff --git a/src/Server/Grants/Traits/IssueAccessTokenTrait.php b/src/Server/Grants/Traits/IssueAccessTokenTrait.php index 9abfaaf5..63eda383 100644 --- a/src/Server/Grants/Traits/IssueAccessTokenTrait.php +++ b/src/Server/Grants/Traits/IssueAccessTokenTrait.php @@ -6,10 +6,7 @@ use DateInterval; use DateTimeImmutable; -use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Entities\ClientEntityInterface; -use League\OAuth2\Server\Entities\ScopeEntityInterface; -use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; use League\OAuth2\Server\Grant\AbstractGrant; use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface; @@ -30,7 +27,7 @@ trait IssueAccessTokenTrait protected $accessTokenRepository; /** - * @var CryptKey + * @var \League\OAuth2\Server\CryptKey */ protected $privateKey; @@ -38,10 +35,10 @@ trait IssueAccessTokenTrait * Issue an access token. * * @param string|null $userIdentifier - * @param ScopeEntityInterface[] $scopes + * @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes * @param array|null $requestedClaims Any requested claims - * @throws OAuthServerException - * @throws UniqueTokenIdentifierConstraintViolationException + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException */ protected function issueAccessToken( DateInterval $accessTokenTTL, @@ -89,7 +86,7 @@ protected function issueAccessToken( * Generate a new unique identifier. * * @param int $length - * @throws OAuthServerException + * @throws \League\OAuth2\Server\Exception\OAuthServerException * * @return string */ diff --git a/src/Server/LogoutHandlers/BackChannelLogoutHandler.php b/src/Server/LogoutHandlers/BackChannelLogoutHandler.php index d119740b..a0987572 100644 --- a/src/Server/LogoutHandlers/BackChannelLogoutHandler.php +++ b/src/Server/LogoutHandlers/BackChannelLogoutHandler.php @@ -11,8 +11,6 @@ use GuzzleHttp\Pool; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; -use League\OAuth2\Server\Exception\OAuthServerException; -use SimpleSAML\Module\oidc\Server\Associations\Interfaces\RelyingPartyAssociationInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Services\LogoutTokenBuilder; use Throwable; @@ -26,9 +24,10 @@ public function __construct( } /** - * @param array $relyingPartyAssociations - * @param HandlerStack|null $handlerStack For easier testing - * @throws OAuthServerException + * @param \SimpleSAML\Module\oidc\Server\Associations\Interfaces\RelyingPartyAssociationInterface[] + * $relyingPartyAssociations + * @param \GuzzleHttp\HandlerStack|null $handlerStack For easier testing + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function handle(array $relyingPartyAssociations, HandlerStack $handlerStack = null): void { @@ -60,9 +59,10 @@ public function handle(array $relyingPartyAssociations, HandlerStack $handlerSta } /** - * @param array $relyingPartyAssociations - * @return Generator - * @throws OAuthServerException + * @param \SimpleSAML\Module\oidc\Server\Associations\Interfaces\RelyingPartyAssociationInterface[] + * $relyingPartyAssociations + * @return \Generator + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ protected function logoutRequestsGenerator(array $relyingPartyAssociations): Generator { diff --git a/src/Server/ResponseTypes/IdTokenResponse.php b/src/Server/ResponseTypes/IdTokenResponse.php index 022e703b..7c212e92 100644 --- a/src/Server/ResponseTypes/IdTokenResponse.php +++ b/src/Server/ResponseTypes/IdTokenResponse.php @@ -16,15 +16,12 @@ namespace SimpleSAML\Module\oidc\Server\ResponseTypes; -use Exception; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; -use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; -use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\ResponseTypes\BearerTokenResponse; -use SimpleSAML\Module\oidc\Repositories\Interfaces\IdentityProviderInterface; use RuntimeException; use SimpleSAML\Module\oidc\Entities\AccessTokenEntity; +use SimpleSAML\Module\oidc\Repositories\Interfaces\IdentityProviderInterface; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\ResponseTypes\Interfaces\AcrResponseTypeInterface; use SimpleSAML\Module\oidc\Server\ResponseTypes\Interfaces\AuthTimeResponseTypeInterface; @@ -59,13 +56,13 @@ class IdTokenResponse extends BearerTokenResponse implements protected ?string $sessionId = null; /** - * @var AccessTokenEntityInterface + * @var \League\OAuth2\Server\Entities\AccessTokenEntityInterface * @psalm-suppress PropertyNotSetInConstructor */ protected $accessToken; /** - * @var RefreshTokenEntityInterface + * @var \League\OAuth2\Server\Entities\RefreshTokenEntityInterface * @psalm-suppress PropertyNotSetInConstructor */ protected $refreshToken; @@ -79,9 +76,9 @@ public function __construct( } /** - * @param AccessTokenEntityInterface $accessToken + * @param \League\OAuth2\Server\Entities\AccessTokenEntityInterface $accessToken * @return array - * @throws Exception + * @throws \Exception */ protected function getExtraParams(AccessTokenEntityInterface $accessToken): array { @@ -122,7 +119,7 @@ protected function getExtraParams(AccessTokenEntityInterface $accessToken): arra } /** - * @param ScopeEntityInterface[] $scopes + * @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes * * @return bool */ diff --git a/src/Server/Validators/BearerTokenValidator.php b/src/Server/Validators/BearerTokenValidator.php index bf28acc8..905c0eeb 100644 --- a/src/Server/Validators/BearerTokenValidator.php +++ b/src/Server/Validators/BearerTokenValidator.php @@ -6,12 +6,10 @@ use DateInterval; use DateTimeZone; -use Exception; use Lcobucci\Clock\SystemClock; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Rsa\Sha256; -use Lcobucci\JWT\Token\Plain; use Lcobucci\JWT\Validation\Constraint\SignedWith; use Lcobucci\JWT\Validation\Constraint\StrictValidAt; use Lcobucci\JWT\Validation\RequiredConstraintsViolated; @@ -30,26 +28,20 @@ class BearerTokenValidator extends OAuth2BearerTokenValidator { - /** - * @var Configuration - */ + /** @var \Lcobucci\JWT\Configuration */ protected Configuration $jwtConfiguration; - /** - * @var OAuth2AccessTokenRepositoryInterface - */ + /** @var \League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface */ protected OAuth2AccessTokenRepositoryInterface $accessTokenRepository; - /** - * @var CryptKey - */ + /** @var \League\OAuth2\Server\CryptKey */ protected $publicKey; /** - * @param AccessTokenRepositoryInterface $accessTokenRepository - * @param CryptKey $publicKey - * @param DateInterval|null $jwtValidAtDateLeeway - * @throws Exception + * @param \League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface $accessTokenRepository + * @param \League\OAuth2\Server\CryptKey $publicKey + * @param \DateInterval|null $jwtValidAtDateLeeway + * @throws \Exception */ public function __construct( AccessTokenRepositoryInterface $accessTokenRepository, @@ -64,8 +56,8 @@ public function __construct( /** * Set the public key * - * @param CryptKey $key - * @throws Exception + * @param \League\OAuth2\Server\CryptKey $key + * @throws \Exception */ public function setPublicKey(CryptKey $key): void { @@ -76,7 +68,7 @@ public function setPublicKey(CryptKey $key): void /** * Initialise the JWT configuration. - * @throws Exception + * @throws \Exception */ protected function initJwtConfiguration(): void { @@ -97,7 +89,7 @@ protected function initJwtConfiguration(): void /** * {@inheritdoc} - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function validateAuthorization(ServerRequestInterface $request): ServerRequestInterface { @@ -121,7 +113,7 @@ public function validateAuthorization(ServerRequestInterface $request): ServerRe try { // Attempt to parse the JWT - /** @var Plain $token */ + /** @var \Lcobucci\JWT\Token\Plain $token */ $token = $this->jwtConfiguration->parser()->parse($jwt); } catch (\Lcobucci\JWT\Exception $exception) { throw OidcServerException::accessDenied($exception->getMessage(), null, $exception); @@ -160,7 +152,7 @@ public function validateAuthorization(ServerRequestInterface $request): ServerRe * @param mixed $aud * * @return array|string - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ protected function convertSingleRecordAudToString(mixed $aud): array|string { diff --git a/src/Services/AuthContextService.php b/src/Services/AuthContextService.php index cf3a75db..7099addd 100644 --- a/src/Services/AuthContextService.php +++ b/src/Services/AuthContextService.php @@ -6,9 +6,8 @@ use RuntimeException; use SimpleSAML\Auth\Simple; -use SimpleSAML\Error\Exception; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Utils\Attributes; use SimpleSAML\Utils\Auth; @@ -39,7 +38,7 @@ public function isSspAdmin(): bool } /** - * @throws Exception + * @throws \SimpleSAML\Error\Exception * @throws \Exception */ public function getAuthUserId(): string diff --git a/src/Services/AuthProcService.php b/src/Services/AuthProcService.php index b3cd8e17..e00d7536 100644 --- a/src/Services/AuthProcService.php +++ b/src/Services/AuthProcService.php @@ -19,7 +19,7 @@ class AuthProcService /** * AuthProcService constructor. * - * @throws Exception + * @throws \Exception * @see \SimpleSAML\Auth\ProcessingChain for original implementation */ public function __construct( @@ -30,7 +30,7 @@ public function __construct( /** * Load filters defined in configuration. - * @throws Exception + * @throws \Exception */ private function loadFilters(): void { @@ -43,8 +43,8 @@ private function loadFilters(): void * @see \SimpleSAML\Auth\ProcessingChain::parseFilterList for original implementation * * @param array $filterSrc Array with filter configuration. - * @return array Array of ProcessingFilter objects. - * @throws Exception + * @return \SimpleSAML\Auth\ProcessingFilter[] Array of ProcessingFilter objects. + * @throws \Exception */ private function parseFilterList(array $filterSrc): array { diff --git a/src/Services/AuthenticationService.php b/src/Services/AuthenticationService.php index 5e579117..1669092a 100644 --- a/src/Services/AuthenticationService.php +++ b/src/Services/AuthenticationService.php @@ -16,7 +16,6 @@ namespace SimpleSAML\Module\oidc\Services; -use Exception; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Auth\Simple; use SimpleSAML\Auth\State; @@ -43,7 +42,7 @@ class AuthenticationService private string $userIdAttr; /** - * @throws Exception + * @throws \Exception */ public function __construct( private readonly UserRepository $userRepository, @@ -60,11 +59,11 @@ public function __construct( } /** - * @throws Error\Exception - * @throws Error\AuthSource - * @throws Error\BadRequest - * @throws Error\NotFound - * @throws Exception + * @throws \Exception + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound */ public function getAuthenticateUser( ServerRequestInterface $request, @@ -175,7 +174,7 @@ public function getSessionId(): ?string /** * Store Relying Party Association to the current session. - * @throws Exception + * @throws \Exception */ protected function addRelyingPartyAssociation(ClientEntityInterface $oidcClient, UserEntity $user): void { diff --git a/src/Services/DatabaseLegacyOAuth2Import.php b/src/Services/DatabaseLegacyOAuth2Import.php index 3501f590..fe9e58c9 100644 --- a/src/Services/DatabaseLegacyOAuth2Import.php +++ b/src/Services/DatabaseLegacyOAuth2Import.php @@ -16,10 +16,9 @@ namespace SimpleSAML\Module\oidc\Services; -use JsonException; use SimpleSAML\Module\oidc\Entities\ClientEntity; use SimpleSAML\Module\oidc\Repositories\ClientRepository; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; +use SimpleSAML\Modules\OAuth2\Repositories\ClientRepository as OAuth2ClientRepository; /** * Class DatabaseLegacyOAuth2Import. @@ -32,15 +31,16 @@ public function __construct(private readonly ClientRepository $clientRepository) /** * @psalm-suppress UndefinedClass, MixedAssignment, MixedArrayAccess, MixedArgument - * @throws OidcServerException|JsonException + * @throws \JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function import(): void { - if (!class_exists('\SimpleSAML\Modules\OAuth2\Repositories\ClientRepository')) { + if (!class_exists(ClientRepository::class)) { return; } - $oauth2ClientRepository = new \SimpleSAML\Modules\OAuth2\Repositories\ClientRepository(); + $oauth2ClientRepository = new OAuth2ClientRepository(); $clients = $oauth2ClientRepository->findAll(); foreach ($clients as $client) { diff --git a/src/Services/IdTokenBuilder.php b/src/Services/IdTokenBuilder.php index 3c26a64c..ac5f7c7c 100644 --- a/src/Services/IdTokenBuilder.php +++ b/src/Services/IdTokenBuilder.php @@ -6,13 +6,11 @@ use Base64Url\Base64Url; use DateTimeImmutable; -use Exception; use Lcobucci\JWT\Builder; use Lcobucci\JWT\Token\RegisteredClaims; use Lcobucci\JWT\UnencryptedToken; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; -use League\OAuth2\Server\Exception\OAuthServerException; use RuntimeException; use SimpleSAML\Module\oidc\Entities\AccessTokenEntity; use SimpleSAML\Module\oidc\Entities\Interfaces\ClaimSetInterface; @@ -28,7 +26,7 @@ public function __construct( } /** - * @throws Exception + * @throws \Exception * @psalm-suppress ArgumentTypeCoercion */ public function build( @@ -131,7 +129,7 @@ public function build( } /** - * @throws OAuthServerException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ protected function getBuilder( AccessTokenEntityInterface $accessToken, diff --git a/src/Services/JsonWebKeySetService.php b/src/Services/JsonWebKeySetService.php index 0a485399..5c13793e 100644 --- a/src/Services/JsonWebKeySetService.php +++ b/src/Services/JsonWebKeySetService.php @@ -15,10 +15,9 @@ */ namespace SimpleSAML\Module\oidc\Services; -use Jose\Component\Core\JWK; use Jose\Component\Core\JWKSet; use Jose\Component\KeyManagement\JWKFactory; -use SimpleSAML\Error\Exception; +use SimpleSAML\Error; use SimpleSAML\Module\oidc\Codebooks\ClaimNamesEnum; use SimpleSAML\Module\oidc\Codebooks\ClaimValues\PublicKeyUseEnum; use SimpleSAML\Module\oidc\ModuleConfig; @@ -33,14 +32,14 @@ class JsonWebKeySetService private ?JWKSet $federationJwkSet = null; /** - * @throws Exception + * @throws \SimpleSAML\Error\Exception * @throws \Exception */ public function __construct(ModuleConfig $moduleConfig) { $publicKeyPath = $moduleConfig->getProtocolCertPath(); if (!file_exists($publicKeyPath)) { - throw new Exception("OIDC protocol public key file does not exists: $publicKeyPath."); + throw new Error\Exception("OIDC protocol public key file does not exists: $publicKeyPath."); } $jwk = JWKFactory::createFromKeyFile($publicKeyPath, null, [ @@ -67,7 +66,7 @@ public function __construct(ModuleConfig $moduleConfig) } /** - * @return JWK[] + * @return \Jose\Component\Core\JWK[] */ public function protocolKeys(): array { @@ -75,7 +74,7 @@ public function protocolKeys(): array } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function federationKeys(): array { diff --git a/src/Services/JsonWebTokenBuilderService.php b/src/Services/JsonWebTokenBuilderService.php index 4230fb63..38d1b6d3 100644 --- a/src/Services/JsonWebTokenBuilderService.php +++ b/src/Services/JsonWebTokenBuilderService.php @@ -5,14 +5,12 @@ namespace SimpleSAML\Module\oidc\Services; use DateTimeImmutable; -use Exception; use Lcobucci\JWT\Builder; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Encoding\ChainedFormatter; use Lcobucci\JWT\Signer; use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\UnencryptedToken; -use ReflectionException; use SimpleSAML\Module\oidc\Codebooks\ClaimNamesEnum; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; @@ -22,18 +20,18 @@ class JsonWebTokenBuilderService { /** - * @var Configuration Token configuration related to OIDC protocol. + * @var \Lcobucci\JWT\Configuration Token configuration related to OIDC protocol. */ protected Configuration $protocolJwtConfig; /** - * @var ?Configuration Token configuration related to OpenID Federation. + * @var \Lcobucci\JWT\Configuration|null Token configuration related to OpenID Federation. */ protected ?Configuration $federationJwtConfig = null; /** - * @throws ReflectionException - * @throws Exception + * @throws \ReflectionException + * @throws \Exception * * @psalm-suppress ArgumentTypeCoercion */ @@ -70,7 +68,7 @@ public function __construct( /** * Get JWT Builder which uses OIDC protocol related signing configuration. * - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function getProtocolJwtBuilder(): Builder { @@ -80,7 +78,7 @@ public function getProtocolJwtBuilder(): Builder /** * Get JWT Builder which uses OpenID Federation related signing configuration. * - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function getFederationJwtBuilder(): Builder { @@ -109,7 +107,7 @@ public function getDefaultJwtBuilder(Configuration $configuration): Builder /** * Get signed JWT using the OIDC protocol JWT signing configuration. * - * @throws Exception + * @throws \Exception */ public function getSignedProtocolJwt(Builder $builder): UnencryptedToken { @@ -123,7 +121,7 @@ public function getSignedProtocolJwt(Builder $builder): UnencryptedToken /** * Get signed JWT using the OpenID Federation JWT signing configuration. * - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function getSignedFederationJwt(Builder $builder): UnencryptedToken { @@ -159,7 +157,7 @@ public function getSignedJwt( } /** - * @throws ReflectionException + * @throws \ReflectionException */ public function getProtocolSigner(): Signer { diff --git a/src/Services/LogoutTokenBuilder.php b/src/Services/LogoutTokenBuilder.php index fc761f0b..d561dfc5 100644 --- a/src/Services/LogoutTokenBuilder.php +++ b/src/Services/LogoutTokenBuilder.php @@ -4,8 +4,6 @@ namespace SimpleSAML\Module\oidc\Services; -use Exception; -use League\OAuth2\Server\Exception\OAuthServerException; use SimpleSAML\Module\oidc\Server\Associations\Interfaces\RelyingPartyAssociationInterface; use stdClass; @@ -17,7 +15,8 @@ public function __construct( } /** - * @throws OAuthServerException|Exception + * @throws \Exception + * @throws \League\OAuth2\Server\Exception\OAuthServerException * @psalm-suppress ArgumentTypeCoercion */ public function forRelyingPartyAssociation(RelyingPartyAssociationInterface $relyingPartyAssociation): string diff --git a/src/Services/OpMetadataService.php b/src/Services/OpMetadataService.php index ea408a0e..04cfcd28 100644 --- a/src/Services/OpMetadataService.php +++ b/src/Services/OpMetadataService.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Module\oidc\Services; -use Exception; use SimpleSAML\Module\oidc\ModuleConfig; /** @@ -18,7 +17,7 @@ class OpMetadataService private array $metadata; /** - * @throws Exception + * @throws \Exception */ public function __construct( private readonly ModuleConfig $moduleConfig, @@ -28,7 +27,7 @@ public function __construct( /** * Initialize metadata array. - * @throws Exception + * @throws \Exception */ private function initMetadata(): void { diff --git a/src/Services/RoutingService.php b/src/Services/RoutingService.php index b3e73412..eeb83f3c 100644 --- a/src/Services/RoutingService.php +++ b/src/Services/RoutingService.php @@ -21,28 +21,24 @@ use Laminas\Diactoros\ServerRequestFactory; use Laminas\HttpHandlerRunner\Emitter\SapiEmitter; use League\OAuth2\Server\Exception\OAuthServerException; -use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; -use Psr\Container\NotFoundExceptionInterface; use Psr\Http\Message\ResponseInterface; use ReflectionClass; -use ReflectionException; use RuntimeException; -use SimpleSAML\Error\BadRequest; -use SimpleSAML\Error\Error; -use SimpleSAML\Error\Exception; +use SimpleSAML\Error; use SimpleSAML\Utils\Auth; use SimpleSAML\XHTML\Template; +use Symfony\Component\HttpFoundation\Response as SymfonyResponse; use Throwable; class RoutingService { /** - * @throws BadRequest - * @throws ContainerExceptionInterface - * @throws Exception - * @throws NotFoundExceptionInterface - * @throws ReflectionException + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + * @throws \ReflectionException + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\Exception */ public static function call( string $controllerClassname, @@ -60,12 +56,12 @@ public static function call( } /** - * @throws BadRequest - * @throws ContainerExceptionInterface - * @throws Exception - * @throws NotFoundExceptionInterface - * @throws ReflectionException * @throws \Exception + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + * @throws \ReflectionException + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\Exception */ public static function callWithPermission(string $controllerClassname, string $permission): void { @@ -77,12 +73,12 @@ public static function callWithPermission(string $controllerClassname, string $p } /** - * @throws BadRequest - * @throws Exception - * @throws ReflectionException - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface * @throws \Exception + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + * @throws \ReflectionException + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\Exception * @psalm-suppress MixedMethodCall, MixedAssignment */ private static function callController(ContainerInterface $container, string $controllerClassname): void @@ -93,7 +89,7 @@ private static function callController(ContainerInterface $container, string $co $response = $controller($serverRequest); # TODO sspv2 return Symfony\Component\HttpFoundation\Response (Template instance) in SSP v2 - if ($response instanceof \Symfony\Component\HttpFoundation\Response) { + if ($response instanceof SymfonyResponse) { if ($response instanceof Template) { $response->data['messages'] = $container->get(SessionMessagesService::class)->getMessages(); } @@ -122,20 +118,20 @@ private static function callController(ContainerInterface $container, string $co return; } - throw new Exception('Response type not supported: ' . $response::class); + throw new Error\Exception('Response type not supported: ' . $response::class); } /** - * @throws BadRequest - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - * @throws ReflectionException + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + * @throws \ReflectionException + * @throws \SimpleSAML\Error\BadRequest * @psalm-suppress MixedAssignment */ protected static function getController(string $controllerClassname, ContainerInterface $container): object { if (!class_exists($controllerClassname)) { - throw new BadRequest("Controller does not exist: $controllerClassname"); + throw new Error\BadRequest("Controller does not exist: $controllerClassname"); } $controllerReflectionClass = new ReflectionClass($controllerClassname); @@ -167,7 +163,7 @@ protected static function getController(string $controllerClassname, ContainerIn protected static function enableJsonExceptionResponse(): void { set_exception_handler(function (Throwable $t) { - if ($t instanceof Error) { + if ($t instanceof Error\Error) { // Showing SSP Error will also use SSP logger to log it. $t->show(); return; @@ -183,7 +179,7 @@ protected static function enableJsonExceptionResponse(): void } // Log exception using SSP Exception logging feature. - (Exception::fromException($t))->logError(); + (Error\Exception::fromException($t))->logError(); $emitter = new SapiEmitter(); $emitter->emit($response); diff --git a/src/Services/SessionMessagesService.php b/src/Services/SessionMessagesService.php index 799825a8..2e6a0267 100644 --- a/src/Services/SessionMessagesService.php +++ b/src/Services/SessionMessagesService.php @@ -16,7 +16,6 @@ namespace SimpleSAML\Module\oidc\Services; -use Exception; use SimpleSAML\Session; class SessionMessagesService @@ -26,7 +25,7 @@ public function __construct(private readonly Session $session) } /** - * @throws Exception + * @throws \Exception */ public function addMessage(string $value): void { diff --git a/src/Services/SessionService.php b/src/Services/SessionService.php index 00204021..aaeccd3c 100644 --- a/src/Services/SessionService.php +++ b/src/Services/SessionService.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Module\oidc\Services; -use Exception; use SimpleSAML\Module\oidc\Server\Associations\Interfaces\RelyingPartyAssociationInterface; use SimpleSAML\Session; @@ -36,7 +35,7 @@ public function getSessionById(string $id): ?Session } /** - * @throws Exception + * @throws \Exception */ public function setIsCookieBasedAuthn(bool $isCookieBasedAuthn): void { @@ -64,7 +63,7 @@ public function getIsCookieBasedAuthn(): ?bool } /** - * @throws Exception + * @throws \Exception */ public function addRelyingPartyAssociation(RelyingPartyAssociationInterface $association): void { @@ -94,7 +93,7 @@ public function getRelyingPartyAssociations(): array } /** - * @return array + * @return \SimpleSAML\Module\oidc\Server\Associations\Interfaces\RelyingPartyAssociationInterface[] */ public static function getRelyingPartyAssociationsForSession(Session $session): array { @@ -112,7 +111,7 @@ public static function getRelyingPartyAssociationsForSession(Session $session): } /** - * @throws Exception + * @throws \Exception */ public function clearRelyingPartyAssociations(): void { @@ -120,7 +119,7 @@ public function clearRelyingPartyAssociations(): void } /** - * @throws Exception + * @throws \Exception */ public static function clearRelyingPartyAssociationsForSession(Session $session): void { @@ -133,7 +132,7 @@ public static function clearRelyingPartyAssociationsForSession(Session $session) } /** - * @throws Exception + * @throws \Exception */ public function setIsAuthnPerformedInPreviousRequest(bool $isAuthnPerformedInPreviousRequest): void { @@ -154,7 +153,7 @@ public function getIsAuthnPerformedInPreviousRequest(): bool } /** - * @throws Exception + * @throws \Exception */ public function registerLogoutHandler(string $authSourceId, string $className, string $functionName): void { @@ -163,7 +162,7 @@ public function registerLogoutHandler(string $authSourceId, string $className, s /** * Set indication if logout was initiated using OIDC protocol. - * @throws Exception + * @throws \Exception */ public function setIsOidcInitiatedLogout(bool $isOidcInitiatedLogout): void { diff --git a/src/Stores/Session/LogoutTicketStoreDb.php b/src/Stores/Session/LogoutTicketStoreDb.php index f0b39a2d..5c3887f5 100644 --- a/src/Stores/Session/LogoutTicketStoreDb.php +++ b/src/Stores/Session/LogoutTicketStoreDb.php @@ -5,7 +5,6 @@ namespace SimpleSAML\Module\oidc\Stores\Session; use DateInterval; -use Exception; use PDO; use SimpleSAML\Database; use SimpleSAML\Module\oidc\Utils\TimestampGenerator; @@ -41,7 +40,7 @@ public function add(string $sid): void } /** - * @throws Exception + * @throws \Exception */ public function delete(string $sid): void { @@ -55,7 +54,7 @@ public function delete(string $sid): void /** * @inheritDoc - * @throws Exception + * @throws \Exception */ public function deleteMultiple(array $sids): void { @@ -82,7 +81,7 @@ public function deleteMultiple(array $sids): void } /** - * @throws Exception + * @throws \Exception */ public function getAll(): array { @@ -91,7 +90,7 @@ public function getAll(): array } /** - * @throws Exception + * @throws \Exception */ protected function deleteExpired(): void { diff --git a/src/Utils/Checker/Interfaces/RequestRuleInterface.php b/src/Utils/Checker/Interfaces/RequestRuleInterface.php index fb33abfc..5903ce22 100644 --- a/src/Utils/Checker/Interfaces/RequestRuleInterface.php +++ b/src/Utils/Checker/Interfaces/RequestRuleInterface.php @@ -5,7 +5,6 @@ namespace SimpleSAML\Module\oidc\Utils\Checker\Interfaces; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\LoggerService; interface RequestRuleInterface @@ -18,13 +17,14 @@ public function getKey(): string; /** * Check specific rule. - * @param ResultBagInterface $currentResultBag ResultBag with all results of the checks performed to current check + * @param \SimpleSAML\Module\oidc\Utils\Checker\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. + * returned in URI fragment instead of query. * @param string[] $allowedServerRequestMethods Indicate allowed HTTP methods used for request - * @return ResultInterface|null Result of the specific check - * @throws OidcServerException If check fails + * @return \SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface|null Result of the specific check + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException If check fails */ public function checkRule( ServerRequestInterface $request, diff --git a/src/Utils/Checker/Interfaces/ResultBagInterface.php b/src/Utils/Checker/Interfaces/ResultBagInterface.php index 122ebe60..c63cadcf 100644 --- a/src/Utils/Checker/Interfaces/ResultBagInterface.php +++ b/src/Utils/Checker/Interfaces/ResultBagInterface.php @@ -4,8 +4,6 @@ namespace SimpleSAML\Module\oidc\Utils\Checker\Interfaces; -use Throwable; - interface ResultBagInterface { /** @@ -20,13 +18,13 @@ public function get(string $key): ?ResultInterface; /** * Get specific result or fail if it doesn't exits. - * @throws Throwable If result with specific key is not present. + * @throws \Throwable If result with specific key is not present. */ public function getOrFail(string $key): ResultInterface; /** * Get all results. - * @return ResultInterface[] + * @return \SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface[] */ public function getAll(): array; diff --git a/src/Utils/Checker/RequestRulesManager.php b/src/Utils/Checker/RequestRulesManager.php index c57ca15a..6e78cc17 100644 --- a/src/Utils/Checker/RequestRulesManager.php +++ b/src/Utils/Checker/RequestRulesManager.php @@ -6,7 +6,6 @@ use LogicException; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\RequestRuleInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; @@ -16,14 +15,10 @@ class RequestRulesManager { - /** - * @var RequestRuleInterface[] $rules - */ + /** @var \SimpleSAML\Module\oidc\Utils\Checker\Interfaces\RequestRuleInterface[] $rules */ private array $rules = []; - /** - * @var ResultBagInterface $resultBag - */ + /** @var \SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface $resultBag */ protected ResultBagInterface $resultBag; /** @var array $data Which will be available during each check */ @@ -31,7 +26,7 @@ class RequestRulesManager /** * RequestRulesManager constructor. - * @param RequestRuleInterface[] $rules + * @param \SimpleSAML\Module\oidc\Utils\Checker\Interfaces\RequestRuleInterface[] $rules */ public function __construct(array $rules = [], protected LoggerService $loggerService = new LoggerService()) { @@ -52,7 +47,7 @@ public function add(RequestRuleInterface $rule): void * @param bool $useFragmentInHttpErrorResponses Indicate that in case of HTTP error responses, params should be * returned in URI fragment instead of query. * @param string[] $allowedServerRequestMethods Indicate allowed HTTP methods used for request - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function check( ServerRequestInterface $request, diff --git a/src/Utils/Checker/ResultBag.php b/src/Utils/Checker/ResultBag.php index 6d506cf9..8fd453fa 100644 --- a/src/Utils/Checker/ResultBag.php +++ b/src/Utils/Checker/ResultBag.php @@ -13,12 +13,12 @@ class ResultBag implements ResultBagInterface { /** - * @var ResultInterface[] $results + * @var \SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface[] $results */ protected array $results = []; /** - * @param ResultInterface $result + * @param \SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface $result */ public function add(ResultInterface $result): void { @@ -27,7 +27,7 @@ public function add(ResultInterface $result): void /** * @param string $key - * @return ResultInterface|null + * @return \SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface|null */ public function get(string $key): ?ResultInterface { @@ -36,7 +36,7 @@ public function get(string $key): ?ResultInterface /** * @param string $key - * @return ResultInterface + * @return \SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface */ public function getOrFail(string $key): ResultInterface { @@ -50,7 +50,7 @@ public function getOrFail(string $key): ResultInterface } /** - * @return ResultInterface[] + * @return \SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface[] */ public function getAll(): array { diff --git a/src/Utils/Checker/Rules/AcrValuesRule.php b/src/Utils/Checker/Rules/AcrValuesRule.php index b70823f6..7cf7c7b1 100644 --- a/src/Utils/Checker/Rules/AcrValuesRule.php +++ b/src/Utils/Checker/Rules/AcrValuesRule.php @@ -29,7 +29,7 @@ public function checkRule( ]; // Check if RequestedClaims rule contains acr - /** @var Result $requestedClaimsResult */ + /** @var \SimpleSAML\Module\oidc\Utils\Checker\Result $requestedClaimsResult */ if (($requestedClaimsResult = $currentResultBag->get(RequestedClaimsRule::class)) !== null) { // Format: https://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests /** diff --git a/src/Utils/Checker/Rules/AddClaimsToIdTokenRule.php b/src/Utils/Checker/Rules/AddClaimsToIdTokenRule.php index c6e01cf5..b7d7baab 100644 --- a/src/Utils/Checker/Rules/AddClaimsToIdTokenRule.php +++ b/src/Utils/Checker/Rules/AddClaimsToIdTokenRule.php @@ -9,13 +9,12 @@ use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; -use Throwable; class AddClaimsToIdTokenRule extends AbstractRule { /** * @inheritDoc - * @throws Throwable + * @throws \Throwable */ public function checkRule( ServerRequestInterface $request, diff --git a/src/Utils/Checker/Rules/CodeChallengeMethodRule.php b/src/Utils/Checker/Rules/CodeChallengeMethodRule.php index 04528d6c..c4122bad 100644 --- a/src/Utils/Checker/Rules/CodeChallengeMethodRule.php +++ b/src/Utils/Checker/Rules/CodeChallengeMethodRule.php @@ -11,7 +11,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; -use Throwable; class CodeChallengeMethodRule extends AbstractRule { @@ -20,8 +19,8 @@ public function __construct(protected CodeChallengeVerifiersRepository $codeChal } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function checkRule( ServerRequestInterface $request, diff --git a/src/Utils/Checker/Rules/CodeChallengeRule.php b/src/Utils/Checker/Rules/CodeChallengeRule.php index 1c6bb8e2..69ad37fb 100644 --- a/src/Utils/Checker/Rules/CodeChallengeRule.php +++ b/src/Utils/Checker/Rules/CodeChallengeRule.php @@ -10,13 +10,12 @@ use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; -use Throwable; class CodeChallengeRule extends AbstractRule { /** * @inheritDoc - * @throws Throwable + * @throws \Throwable */ public function checkRule( ServerRequestInterface $request, diff --git a/src/Utils/Checker/Rules/IdTokenHintRule.php b/src/Utils/Checker/Rules/IdTokenHintRule.php index b4069b88..a928c0c8 100644 --- a/src/Utils/Checker/Rules/IdTokenHintRule.php +++ b/src/Utils/Checker/Rules/IdTokenHintRule.php @@ -6,12 +6,11 @@ use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Key\InMemory; -use Lcobucci\JWT\UnencryptedToken; use Lcobucci\JWT\Validation\Constraint\IssuedBy; use Lcobucci\JWT\Validation\Constraint\SignedWith; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Factories\CryptKeyFactory; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; @@ -29,7 +28,7 @@ public function __construct( /** * @inheritDoc - * @throws Throwable + * @throws \Throwable */ public function checkRule( ServerRequestInterface $request, @@ -73,7 +72,7 @@ public function checkRule( } try { - /** @var UnencryptedToken $idTokenHint */ + /** @var \Lcobucci\JWT\UnencryptedToken $idTokenHint */ $idTokenHint = $jwtConfig->parser()->parse($idTokenHintParam); /** @psalm-suppress ArgumentTypeCoercion */ diff --git a/src/Utils/Checker/Rules/MaxAgeRule.php b/src/Utils/Checker/Rules/MaxAgeRule.php index 8461fb91..32751f9a 100644 --- a/src/Utils/Checker/Rules/MaxAgeRule.php +++ b/src/Utils/Checker/Rules/MaxAgeRule.php @@ -5,7 +5,6 @@ namespace SimpleSAML\Module\oidc\Utils\Checker\Rules; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\AuthenticationService; @@ -14,8 +13,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; use SimpleSAML\Utils\HTTP; -use SimpleSAML\Error; -use Throwable; class MaxAgeRule extends AbstractRule { @@ -26,12 +23,12 @@ public function __construct( } /** - * @throws Error\AuthSource - * @throws Throwable - * @throws Error\BadRequest - * @throws OidcServerException - * @throws Error\NotFound - * @throws Error\Exception + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Throwable */ public function checkRule( ServerRequestInterface $request, @@ -43,7 +40,7 @@ public function checkRule( ): ?ResultInterface { $queryParams = $request->getQueryParams(); - /** @var ClientEntityInterface $client */ + /** @var \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface $client */ $client = $currentResultBag->getOrFail(ClientIdRule::class)->getValue(); $authSimple = $this->authSimpleFactory->build($client); diff --git a/src/Utils/Checker/Rules/PostLogoutRedirectUriRule.php b/src/Utils/Checker/Rules/PostLogoutRedirectUriRule.php index 0f55d7c7..85c73ac2 100644 --- a/src/Utils/Checker/Rules/PostLogoutRedirectUriRule.php +++ b/src/Utils/Checker/Rules/PostLogoutRedirectUriRule.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Module\oidc\Utils\Checker\Rules; -use Lcobucci\JWT\UnencryptedToken; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Repositories\ClientRepository; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; @@ -12,7 +11,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; -use Throwable; class PostLogoutRedirectUriRule extends AbstractRule { @@ -22,7 +20,7 @@ public function __construct(protected ClientRepository $clientRepository) /** * @inheritDoc - * @throws Throwable + * @throws \Throwable */ public function checkRule( ServerRequestInterface $request, @@ -35,7 +33,7 @@ public function checkRule( /** @var string|null $state */ $state = $currentResultBag->getOrFail(StateRule::class)->getValue(); - /** @var UnencryptedToken|null $idTokenHint */ + /** @var \Lcobucci\JWT\UnencryptedToken|null $idTokenHint */ $idTokenHint = $currentResultBag->getOrFail(IdTokenHintRule::class)->getValue(); $postLogoutRedirectUri = $this->getParamFromRequestBasedOnAllowedMethods( diff --git a/src/Utils/Checker/Rules/PromptRule.php b/src/Utils/Checker/Rules/PromptRule.php index fae74633..93e232fa 100644 --- a/src/Utils/Checker/Rules/PromptRule.php +++ b/src/Utils/Checker/Rules/PromptRule.php @@ -6,7 +6,6 @@ use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\AuthenticationService; @@ -14,8 +13,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Utils\HTTP; -use SimpleSAML\Error; -use Throwable; class PromptRule extends AbstractRule { @@ -26,13 +23,13 @@ public function __construct( } /** - * @throws Error\AuthSource - * @throws Error\BadRequest - * @throws Error\Exception - * @throws OAuthServerException - * @throws Throwable - * @throws OidcServerException - * @throws Error\NotFound + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Throwable */ public function checkRule( ServerRequestInterface $request, @@ -42,7 +39,7 @@ public function checkRule( bool $useFragmentInHttpErrorResponses = false, array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { - /** @var ClientEntityInterface $client */ + /** @var \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface $client */ $client = $currentResultBag->getOrFail(ClientIdRule::class)->getValue(); $authSimple = $this->authSimpleFactory->build($client); diff --git a/src/Utils/Checker/Rules/RedirectUriRule.php b/src/Utils/Checker/Rules/RedirectUriRule.php index 2d1ba31d..8cc2ab98 100644 --- a/src/Utils/Checker/Rules/RedirectUriRule.php +++ b/src/Utils/Checker/Rules/RedirectUriRule.php @@ -12,13 +12,12 @@ use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; -use Throwable; class RedirectUriRule extends AbstractRule { /** * @inheritDoc - * @throws Throwable + * @throws \Throwable */ public function checkRule( ServerRequestInterface $request, diff --git a/src/Utils/Checker/Rules/RequestParameterRule.php b/src/Utils/Checker/Rules/RequestParameterRule.php index 4bd25160..89d98f14 100644 --- a/src/Utils/Checker/Rules/RequestParameterRule.php +++ b/src/Utils/Checker/Rules/RequestParameterRule.php @@ -9,13 +9,12 @@ use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; -use Throwable; class RequestParameterRule extends AbstractRule { /** - * @throws Throwable - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Throwable */ public function checkRule( ServerRequestInterface $request, diff --git a/src/Utils/Checker/Rules/RequestedClaimsRule.php b/src/Utils/Checker/Rules/RequestedClaimsRule.php index 59dded79..c635fe2f 100644 --- a/src/Utils/Checker/Rules/RequestedClaimsRule.php +++ b/src/Utils/Checker/Rules/RequestedClaimsRule.php @@ -5,13 +5,11 @@ namespace SimpleSAML\Module\oidc\Utils\Checker\Rules; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; -use Throwable; class RequestedClaimsRule extends AbstractRule { @@ -21,7 +19,7 @@ public function __construct(private readonly ClaimTranslatorExtractor $claimExtr /** - * @throws Throwable + * @throws \Throwable */ public function checkRule( ServerRequestInterface $request, @@ -41,7 +39,7 @@ public function checkRule( if (is_null($claims)) { return null; } - /** @var ClientEntityInterface $client */ + /** @var \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface $client */ $client = $currentResultBag->getOrFail(ClientIdRule::class)->getValue(); $authorizedClaims = []; diff --git a/src/Utils/Checker/Rules/RequiredNonceRule.php b/src/Utils/Checker/Rules/RequiredNonceRule.php index 70b0d1d6..8108ca8b 100644 --- a/src/Utils/Checker/Rules/RequiredNonceRule.php +++ b/src/Utils/Checker/Rules/RequiredNonceRule.php @@ -10,13 +10,12 @@ use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; -use Throwable; class RequiredNonceRule extends AbstractRule { /** * @inheritDoc - * @throws Throwable + * @throws \Throwable */ public function checkRule( ServerRequestInterface $request, diff --git a/src/Utils/Checker/Rules/RequiredOpenIdScopeRule.php b/src/Utils/Checker/Rules/RequiredOpenIdScopeRule.php index 2bde9ada..bdd527ec 100644 --- a/src/Utils/Checker/Rules/RequiredOpenIdScopeRule.php +++ b/src/Utils/Checker/Rules/RequiredOpenIdScopeRule.php @@ -4,20 +4,18 @@ namespace SimpleSAML\Module\oidc\Utils\Checker\Rules; -use League\OAuth2\Server\Entities\ScopeEntityInterface; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; -use Throwable; class RequiredOpenIdScopeRule extends AbstractRule { /** * @inheritDoc - * @throws Throwable + * @throws \Throwable */ public function checkRule( ServerRequestInterface $request, @@ -31,7 +29,7 @@ public function checkRule( $redirectUri = $currentResultBag->getOrFail(RedirectUriRule::class)->getValue(); /** @var string|null $state */ $state = $currentResultBag->getOrFail(StateRule::class)->getValue(); - /** @var ScopeEntityInterface[] $validScopes */ + /** @var \League\OAuth2\Server\Entities\ScopeEntityInterface[] $validScopes */ $validScopes = $currentResultBag->getOrFail(ScopeRule::class)->getValue(); $isOpenIdScopePresent = (bool) array_filter( diff --git a/src/Utils/Checker/Rules/ScopeOfflineAccessRule.php b/src/Utils/Checker/Rules/ScopeOfflineAccessRule.php index 8ca17c33..c2c61542 100644 --- a/src/Utils/Checker/Rules/ScopeOfflineAccessRule.php +++ b/src/Utils/Checker/Rules/ScopeOfflineAccessRule.php @@ -4,22 +4,19 @@ namespace SimpleSAML\Module\oidc\Utils\Checker\Rules; -use League\OAuth2\Server\Entities\ScopeEntityInterface; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; use SimpleSAML\Module\oidc\Utils\ScopeHelper; -use Throwable; class ScopeOfflineAccessRule extends AbstractRule { /** * @inheritDoc - * @throws Throwable + * @throws \Throwable */ public function checkRule( ServerRequestInterface $request, @@ -33,9 +30,9 @@ public function checkRule( $redirectUri = $currentResultBag->getOrFail(RedirectUriRule::class)->getValue(); /** @var string|null $state */ $state = $currentResultBag->getOrFail(StateRule::class)->getValue(); - /** @var ClientEntityInterface $client */ + /** @var \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface $client */ $client = $currentResultBag->getOrFail(ClientIdRule::class)->getValue(); - /** @var ScopeEntityInterface[] $validScopes */ + /** @var \League\OAuth2\Server\Entities\ScopeEntityInterface[] $validScopes */ $validScopes = $currentResultBag->getOrFail(ScopeRule::class)->getValue(); // Check if offline_access scope is used. If not, we don't have to check anything else. diff --git a/src/Utils/Checker/Rules/ScopeRule.php b/src/Utils/Checker/Rules/ScopeRule.php index 42321499..1c47ba04 100644 --- a/src/Utils/Checker/Rules/ScopeRule.php +++ b/src/Utils/Checker/Rules/ScopeRule.php @@ -12,7 +12,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; -use Throwable; class ScopeRule extends AbstractRule { @@ -22,7 +21,7 @@ public function __construct(protected ScopeRepositoryInterface $scopeRepository) /** * @inheritDoc - * @throws Throwable + * @throws \Throwable */ public function checkRule( ServerRequestInterface $request, @@ -65,7 +64,7 @@ public function checkRule( * Converts a scopes query string to an array to easily iterate for validation. * * @return string[] - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ protected function convertScopesQueryStringToArray(string $scopes, string $scopeDelimiterString): array { diff --git a/src/Utils/ClaimTranslatorExtractor.php b/src/Utils/ClaimTranslatorExtractor.php index 3ff32978..0a9c31df 100644 --- a/src/Utils/ClaimTranslatorExtractor.php +++ b/src/Utils/ClaimTranslatorExtractor.php @@ -129,7 +129,7 @@ class ClaimTranslatorExtractor * ClaimTranslatorExtractor constructor. * * @param ClaimSetEntity[] $claimSets - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function __construct( string $userIdAttr, @@ -191,7 +191,7 @@ public function __construct( } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function addClaimSet(ClaimSetEntityInterface $claimSet): self { @@ -284,7 +284,7 @@ private function convertType(string $type, mixed $attributes): mixed } /** - * @param array $scopes + * @param array $scopes */ public function extract(array $scopes, array $claims): array { diff --git a/src/Utils/FingerprintGenerator.php b/src/Utils/FingerprintGenerator.php index a69093ac..a7cdc965 100644 --- a/src/Utils/FingerprintGenerator.php +++ b/src/Utils/FingerprintGenerator.php @@ -22,8 +22,9 @@ public static function forFile(string $path, string $algo = 'md5'): string $fingerprint = hash_file($algo, $path); if (false === (bool) $fingerprint) { - throw new InvalidArgumentException('Could not create a fingerprint for provided file using' . - ' provided algorithm.'); + throw new InvalidArgumentException( + 'Could not create a fingerprint for provided file using provided algorithm.', + ); } return $fingerprint; @@ -36,15 +37,16 @@ public static function forFile(string $path, string $algo = 'md5'): string * @param string $algo One of the supported algorithms (see hash_algos() function) * @return string * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ public static function forString(string $content, string $algo = 'md5'): string { $fingerprint = hash($algo, $content); if (false === (bool) $fingerprint) { - throw new InvalidArgumentException('Could not create a fingerprint for provided content using' . - ' provided algorithm.'); + throw new InvalidArgumentException( + 'Could not create a fingerprint for provided content using provided algorithm.', + ); } return $fingerprint; diff --git a/src/Utils/ScopeHelper.php b/src/Utils/ScopeHelper.php index 5a74dee6..339b6ffb 100644 --- a/src/Utils/ScopeHelper.php +++ b/src/Utils/ScopeHelper.php @@ -10,8 +10,8 @@ class ScopeHelper { /** - * @param ScopeEntityInterface[] $scopes - * @throws OidcServerException + * @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public static function scopeExists(array $scopes, string $scopeIdentifier): bool { diff --git a/src/Utils/TimestampGenerator.php b/src/Utils/TimestampGenerator.php index ced00daf..d855af4d 100644 --- a/src/Utils/TimestampGenerator.php +++ b/src/Utils/TimestampGenerator.php @@ -18,12 +18,11 @@ use DateTime; use DateTimeImmutable; use DateTimeZone; -use Exception; class TimestampGenerator { /** - * @throws Exception + * @throws \Exception */ public static function utc(string $time = 'now'): DateTime { @@ -31,7 +30,7 @@ public static function utc(string $time = 'now'): DateTime } /** - * @throws Exception + * @throws \Exception */ public static function utcImmutable(string $time = 'now'): DateTimeImmutable { diff --git a/src/Utils/UniqueIdentifierGenerator.php b/src/Utils/UniqueIdentifierGenerator.php index ed548817..5c82c49a 100644 --- a/src/Utils/UniqueIdentifierGenerator.php +++ b/src/Utils/UniqueIdentifierGenerator.php @@ -12,7 +12,7 @@ class UniqueIdentifierGenerator /** * Generate a new unique identifier. * - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public static function hitMe(int $length = 40): string { diff --git a/tests/src/Controller/AccessTokenControllerTest.php b/tests/src/Controller/AccessTokenControllerTest.php index 62004f6d..b8e6c83d 100644 --- a/tests/src/Controller/AccessTokenControllerTest.php +++ b/tests/src/Controller/AccessTokenControllerTest.php @@ -6,8 +6,6 @@ use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; -use League\OAuth2\Server\Exception\OAuthServerException; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Controller\AccessTokenController; @@ -27,7 +25,7 @@ class AccessTokenControllerTest extends TestCase protected MockObject $responseMock; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -49,7 +47,7 @@ public function testItIsInitializable(): void } /** - * @throws OAuthServerException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function testItRespondsToAccessTokenRequest(): void { @@ -81,7 +79,7 @@ public function testItHandlesCorsRequest(): void } /** - * @return AccessTokenController + * @return \SimpleSAML\Module\oidc\Controller\AccessTokenController */ protected function prepareMockedInstance(): AccessTokenController { diff --git a/tests/src/Controller/AuthorizationControllerTest.php b/tests/src/Controller/AuthorizationControllerTest.php index e007b042..dbd68393 100644 --- a/tests/src/Controller/AuthorizationControllerTest.php +++ b/tests/src/Controller/AuthorizationControllerTest.php @@ -5,22 +5,18 @@ namespace SimpleSAML\Test\Module\oidc\Controller; use Laminas\Diactoros\ServerRequest; -use League\OAuth2\Server\Exception\OAuthServerException; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; -use SimpleSAML\Error; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Controller\AuthorizationController; use SimpleSAML\Module\oidc\Entities\UserEntity; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Server\AuthorizationServer; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestTypes\AuthorizationRequest; use SimpleSAML\Module\oidc\Services\AuthenticationService; use SimpleSAML\Module\oidc\Services\LoggerService; -use Throwable; /** * @covers \SimpleSAML\Module\oidc\Controller\AuthorizationController @@ -43,7 +39,7 @@ class AuthorizationControllerTest extends TestCase protected static array $sampleRequestedAcrs = ['values' => ['1', '0'], 'essential' => false]; /** - * @throws Exception + * @throws \Exception */ public function setUp(): void { @@ -59,12 +55,12 @@ public function setUp(): void } /** - * @throws Error\AuthSource - * @throws Error\BadRequest - * @throws Error\NotFound - * @throws Error\Exception - * @throws OAuthServerException - * @throws Throwable + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \Throwable */ public function testReturnsResponseWhenInvoked(): void { @@ -88,12 +84,12 @@ public function testReturnsResponseWhenInvoked(): void } /** - * @throws Error\AuthSource - * @throws Error\BadRequest - * @throws Error\NotFound - * @throws Error\Exception - * @throws OAuthServerException - * @throws Throwable + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Error\Exception + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \Throwable */ public function testValidateAcrThrowsIfAuthSourceIdNotSetInAuthorizationRequest(): void { @@ -116,12 +112,12 @@ public function testValidateAcrThrowsIfAuthSourceIdNotSetInAuthorizationRequest( } /** - * @throws Error\AuthSource - * @throws Error\BadRequest - * @throws Error\NotFound - * @throws Error\Exception - * @throws OAuthServerException - * @throws Throwable + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Error\Exception + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \Throwable */ public function testValidateAcrThrowsIfCookieBasedAuthnNotSetInAuthorizationRequest(): void { @@ -146,12 +142,12 @@ public function testValidateAcrThrowsIfCookieBasedAuthnNotSetInAuthorizationRequ } /** - * @throws Error\AuthSource - * @throws Error\BadRequest - * @throws Error\NotFound - * @throws Error\Exception - * @throws OAuthServerException - * @throws Throwable + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Error\Exception + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \Throwable */ public function testValidateAcrSetsForcedAcrForCookieAuthentication(): void { @@ -185,12 +181,12 @@ public function testValidateAcrSetsForcedAcrForCookieAuthentication(): void } /** - * @throws Error\AuthSource - * @throws Error\BadRequest - * @throws Error\NotFound - * @throws Error\Exception - * @throws OAuthServerException - * @throws Throwable + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Error\Exception + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \Throwable */ public function testValidateAcrThrowsIfNoMatchedAcrForEssentialAcrs(): void { @@ -224,12 +220,12 @@ public function testValidateAcrThrowsIfNoMatchedAcrForEssentialAcrs(): void } /** - * @throws Error\AuthSource - * @throws Error\BadRequest - * @throws Error\NotFound - * @throws Error\Exception - * @throws OAuthServerException - * @throws Throwable + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Error\Exception + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \Throwable */ public function testValidateAcrSetsFirstMatchedAcr(): void { @@ -262,12 +258,12 @@ public function testValidateAcrSetsFirstMatchedAcr(): void } /** - * @throws Error\AuthSource - * @throws Error\BadRequest - * @throws Error\NotFound - * @throws Error\Exception - * @throws OAuthServerException - * @throws Throwable + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Error\Exception + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \Throwable */ public function testValidateAcrSetsCurrentSessionAcrIfNoMatchedAcr(): void { @@ -301,12 +297,12 @@ public function testValidateAcrSetsCurrentSessionAcrIfNoMatchedAcr(): void } /** - * @throws Error\AuthSource - * @throws Error\BadRequest - * @throws Error\NotFound - * @throws Error\Exception - * @throws OAuthServerException - * @throws Throwable + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Error\Exception + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \Throwable */ public function testValidateAcrLogsWarningIfNoAcrsConfigured(): void { diff --git a/tests/src/Controller/Client/CreateControllerTest.php b/tests/src/Controller/Client/CreateControllerTest.php index 90043f64..64942c40 100644 --- a/tests/src/Controller/Client/CreateControllerTest.php +++ b/tests/src/Controller/Client/CreateControllerTest.php @@ -9,7 +9,6 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; -use SimpleSAML\Error\Exception; use SimpleSAML\Module\oidc\Controller\Client\CreateController; use SimpleSAML\Module\oidc\Entities\ClientEntity; use SimpleSAML\Module\oidc\Factories\FormFactory; @@ -72,7 +71,7 @@ protected function getStubbedInstance(): CreateController } /** - * @throws Exception + * @throws \Exception */ public function testCanShowNewClientForm(): void { @@ -107,7 +106,7 @@ public function testCanShowNewClientForm(): void } /** - * @throws Exception + * @throws \Exception */ public function testCanCreateNewClientFromFormData(): void { @@ -161,7 +160,7 @@ public function testCanCreateNewClientFromFormData(): void } /** - * @throws Exception + * @throws \Exception */ public function testCanSetOwnerInNewClient(): void { diff --git a/tests/src/Controller/Client/DeleteControllerTest.php b/tests/src/Controller/Client/DeleteControllerTest.php index 35f21a50..cae5cc8e 100644 --- a/tests/src/Controller/Client/DeleteControllerTest.php +++ b/tests/src/Controller/Client/DeleteControllerTest.php @@ -4,22 +4,17 @@ namespace SimpleSAML\Test\Module\oidc\Controller\Client; -use JsonException; use Laminas\Diactoros\Response\RedirectResponse; use Laminas\Diactoros\ServerRequest; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\UriInterface; use SimpleSAML\Error\BadRequest; -use SimpleSAML\Error\ConfigurationError; -use SimpleSAML\Error\NotFound; use SimpleSAML\Module\oidc\Controller\Client\DeleteController; use SimpleSAML\Module\oidc\Entities\ClientEntity; use SimpleSAML\Module\oidc\Factories\TemplateFactory; use SimpleSAML\Module\oidc\Repositories\ClientRepository; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\AuthContextService; use SimpleSAML\Module\oidc\Services\SessionMessagesService; use SimpleSAML\XHTML\Template; @@ -39,7 +34,7 @@ class DeleteControllerTest extends TestCase protected Stub $templateStub; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -71,7 +66,12 @@ public function testCanInstantiate(): void } /** - * @throws ConfigurationError|BadRequest|NotFound|\SimpleSAML\Error\Exception|OidcServerException|JsonException + * @throws \JsonException + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\ConfigurationError + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testItAsksConfirmationBeforeDeletingClient(): void { @@ -91,7 +91,12 @@ public function testItAsksConfirmationBeforeDeletingClient(): void } /** - * @throws ConfigurationError|BadRequest|NotFound|\SimpleSAML\Error\Exception|OidcServerException|JsonException + * @throws \JsonException + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\ConfigurationError + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testThrowsIfIdNotFoundInDeleteAction(): void { @@ -103,7 +108,12 @@ public function testThrowsIfIdNotFoundInDeleteAction(): void } /** - * @throws ConfigurationError|BadRequest|NotFound|\SimpleSAML\Error\Exception|OidcServerException|JsonException + * @throws \JsonException + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\ConfigurationError + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testThrowsIfSecretNotFoundInDeleteAction(): void { @@ -120,7 +130,12 @@ public function testThrowsIfSecretNotFoundInDeleteAction(): void } /** - * @throws ConfigurationError|BadRequest|NotFound|\SimpleSAML\Error\Exception|OidcServerException|JsonException + * @throws \JsonException + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\ConfigurationError + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testThrowsIfSecretIsInvalidInDeleteAction(): void { @@ -139,7 +154,12 @@ public function testThrowsIfSecretIsInvalidInDeleteAction(): void } /** - * @throws ConfigurationError|BadRequest|NotFound|\SimpleSAML\Error\Exception|OidcServerException|JsonException + * @throws \JsonException + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\ConfigurationError + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testItDeletesClient(): void { @@ -163,7 +183,12 @@ public function testItDeletesClient(): void } /** - * @throws ConfigurationError|BadRequest|NotFound|\SimpleSAML\Error\Exception|OidcServerException|JsonException + * @throws \JsonException + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\ConfigurationError + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testItDeletesClientWithOwner(): void { diff --git a/tests/src/Controller/Client/EditControllerTest.php b/tests/src/Controller/Client/EditControllerTest.php index b721c46a..2db0d63b 100644 --- a/tests/src/Controller/Client/EditControllerTest.php +++ b/tests/src/Controller/Client/EditControllerTest.php @@ -12,7 +12,6 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\UriInterface; use SimpleSAML\Error\BadRequest; -use SimpleSAML\Error\NotFound; use SimpleSAML\Module\oidc\Controller\Client\EditController; use SimpleSAML\Module\oidc\Entities\ClientEntity; use SimpleSAML\Module\oidc\Factories\FormFactory; @@ -96,9 +95,9 @@ public function testItIsInitializable(): void } /** - * @throws BadRequest + * @throws \SimpleSAML\Error\BadRequest * @throws \SimpleSAML\Error\Exception - * @throws NotFound + * @throws \SimpleSAML\Error\NotFound */ public function testItShowsEditClientForm(): void { @@ -147,9 +146,9 @@ public function testItShowsEditClientForm(): void } /** - * @throws BadRequest + * @throws \SimpleSAML\Error\BadRequest * @throws \SimpleSAML\Error\Exception - * @throws NotFound + * @throws \SimpleSAML\Error\NotFound */ public function testItUpdatesClientFromEditClientFormData(): void { @@ -233,9 +232,9 @@ public function testItUpdatesClientFromEditClientFormData(): void } /** - * @throws BadRequest + * @throws \SimpleSAML\Error\BadRequest * @throws \SimpleSAML\Error\Exception - * @throws NotFound + * @throws \SimpleSAML\Error\NotFound */ public function testItSendsOwnerArgToRepoOnUpdate(): void { @@ -322,7 +321,7 @@ public function testItSendsOwnerArgToRepoOnUpdate(): void /** * @throws \SimpleSAML\Error\Exception - * @throws NotFound + * @throws \SimpleSAML\Error\NotFound */ public function testThrowsIdNotFoundExceptionInEditAction(): void { @@ -334,9 +333,9 @@ public function testThrowsIdNotFoundExceptionInEditAction(): void } /** - * @throws BadRequest + * @throws \SimpleSAML\Error\BadRequest * @throws \SimpleSAML\Error\Exception - * @throws NotFound + * @throws \SimpleSAML\Error\NotFound */ public function testThrowsClientNotFoundExceptionInEditAction(): void { diff --git a/tests/src/Controller/Client/IndexControllerTest.php b/tests/src/Controller/Client/IndexControllerTest.php index 320f7f14..0fbbb4bb 100644 --- a/tests/src/Controller/Client/IndexControllerTest.php +++ b/tests/src/Controller/Client/IndexControllerTest.php @@ -5,7 +5,6 @@ namespace SimpleSAML\Test\Module\oidc\Controller\Client; use Laminas\Diactoros\ServerRequest; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; @@ -29,7 +28,7 @@ class IndexControllerTest extends TestCase protected Stub $templateStub; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { diff --git a/tests/src/Controller/Client/ResetSecretControllerTest.php b/tests/src/Controller/Client/ResetSecretControllerTest.php index 5583e7d9..b5372d23 100644 --- a/tests/src/Controller/Client/ResetSecretControllerTest.php +++ b/tests/src/Controller/Client/ResetSecretControllerTest.php @@ -9,8 +9,6 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SimpleSAML\Error\BadRequest; -use SimpleSAML\Error\Exception; -use SimpleSAML\Error\NotFound; use SimpleSAML\Module\oidc\Controller\Client\ResetSecretController; use SimpleSAML\Module\oidc\Entities\ClientEntity; use SimpleSAML\Module\oidc\Repositories\ClientRepository; @@ -62,8 +60,8 @@ public function testCanInstantiate(): void } /** - * @throws Exception - * @throws NotFound + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound */ public function testItThrowsIdNotFoundExceptionInResetSecretAction(): void { @@ -73,8 +71,8 @@ public function testItThrowsIdNotFoundExceptionInResetSecretAction(): void } /** - * @throws BadRequest - * @throws Exception + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\Exception */ public function testItThrowsClientNotFoundExceptionInResetSecretAction(): void { @@ -90,8 +88,8 @@ public function testItThrowsClientNotFoundExceptionInResetSecretAction(): void } /** - * @throws Exception - * @throws NotFound + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound */ public function testThrowsSecretNotFoundExceptionInResetSecretAction(): void { @@ -111,8 +109,8 @@ public function testThrowsSecretNotFoundExceptionInResetSecretAction(): void } /** - * @throws Exception - * @throws NotFound + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound */ public function testThrowsSecretInvalidExceptionInResetSecretAction(): void { @@ -138,9 +136,9 @@ public function testThrowsSecretInvalidExceptionInResetSecretAction(): void } /** - * @throws BadRequest - * @throws Exception - * @throws NotFound + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound */ public function testItResetSecretsClient(): void { @@ -177,9 +175,9 @@ public function testItResetSecretsClient(): void } /** - * @throws BadRequest - * @throws Exception - * @throws NotFound + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\NotFound */ public function testItSendBackToShowClientIfNotPostMethodInResetAction(): void { diff --git a/tests/src/Controller/Client/ShowControllerTest.php b/tests/src/Controller/Client/ShowControllerTest.php index 4b4b14c1..15ac5a34 100644 --- a/tests/src/Controller/Client/ShowControllerTest.php +++ b/tests/src/Controller/Client/ShowControllerTest.php @@ -4,13 +4,10 @@ namespace SimpleSAML\Test\Module\oidc\Controller\Client; -use JsonException; use Laminas\Diactoros\ServerRequest; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SimpleSAML\Error\BadRequest; -use SimpleSAML\Error\NotFound; use SimpleSAML\Module\oidc\Controller\Client\ShowController; use SimpleSAML\Module\oidc\Entities\ClientEntity; use SimpleSAML\Module\oidc\Factories\TemplateFactory; @@ -36,7 +33,7 @@ class ShowControllerTest extends TestCase protected MockObject $templateMock; /** - * @throws Exception + * @throws \SimpleSAML\Error\Exception */ protected function setUp(): void { @@ -69,10 +66,11 @@ public function testItIsInitializable(): void } /** - * @throws BadRequest + * @throws \SimpleSAML\Error\BadRequest * @throws \SimpleSAML\Error\Exception - * @throws NotFound - * @throws OidcServerException|JsonException + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testItShowsClientDescription(): void { @@ -109,8 +107,9 @@ public function testItShowsClientDescription(): void /** * @throws \SimpleSAML\Error\Exception - * @throws NotFound - * @throws OidcServerException|JsonException + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testItThrowsIdNotFoundException(): void { @@ -121,9 +120,10 @@ public function testItThrowsIdNotFoundException(): void } /** - * @throws BadRequest + * @throws \SimpleSAML\Error\BadRequest * @throws \SimpleSAML\Error\Exception - * @throws OidcServerException|JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testItThrowsClientNotFoundException(): void { diff --git a/tests/src/Controller/ConfigurationDiscoveryControllerTest.php b/tests/src/Controller/ConfigurationDiscoveryControllerTest.php index 63f14776..7d099451 100644 --- a/tests/src/Controller/ConfigurationDiscoveryControllerTest.php +++ b/tests/src/Controller/ConfigurationDiscoveryControllerTest.php @@ -5,10 +5,9 @@ namespace SimpleSAML\Test\Module\oidc\Controller; use Laminas\Diactoros\ServerRequest; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; -use SimpleSAML\Module\oidc\Controller\ConfigurationDiscoveryController; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\oidc\Controller\ConfigurationDiscoveryController; use SimpleSAML\Module\oidc\Services\OpMetadataService; /** @@ -34,7 +33,7 @@ class ConfigurationDiscoveryControllerTest extends TestCase protected MockObject $serverRequestMock; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { diff --git a/tests/src/Controller/InstallerControllerTest.php b/tests/src/Controller/InstallerControllerTest.php index e3dcb3d5..1e7af7aa 100644 --- a/tests/src/Controller/InstallerControllerTest.php +++ b/tests/src/Controller/InstallerControllerTest.php @@ -4,12 +4,11 @@ namespace SimpleSAML\Test\Module\oidc\Controller; -use PHPUnit\Framework\MockObject\Exception; -use PHPUnit\Framework\MockObject\MockObject; use Laminas\Diactoros\Response\RedirectResponse; use Laminas\Diactoros\ServerRequest; -use SimpleSAML\Module\oidc\Controller\InstallerController; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\oidc\Controller\InstallerController; use SimpleSAML\Module\oidc\Factories\TemplateFactory; use SimpleSAML\Module\oidc\Services\DatabaseLegacyOAuth2Import; use SimpleSAML\Module\oidc\Services\DatabaseMigration; @@ -29,7 +28,7 @@ class InstallerControllerTest extends TestCase protected MockObject $templateMock; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { diff --git a/tests/src/Controller/JwksControllerTest.php b/tests/src/Controller/JwksControllerTest.php index 66d09549..87150280 100644 --- a/tests/src/Controller/JwksControllerTest.php +++ b/tests/src/Controller/JwksControllerTest.php @@ -4,11 +4,10 @@ namespace SimpleSAML\Test\Module\oidc\Controller; -use PHPUnit\Framework\MockObject\Exception; -use PHPUnit\Framework\MockObject\MockObject; use Laminas\Diactoros\ServerRequest; -use SimpleSAML\Module\oidc\Controller\JwksController; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\oidc\Controller\JwksController; use SimpleSAML\Module\oidc\Services\JsonWebKeySetService; /** @@ -20,7 +19,7 @@ class JwksControllerTest extends TestCase protected MockObject $serverRequestMock; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { diff --git a/tests/src/Controller/LogoutControllerTest.php b/tests/src/Controller/LogoutControllerTest.php index 3c2658f8..62a1f8fb 100644 --- a/tests/src/Controller/LogoutControllerTest.php +++ b/tests/src/Controller/LogoutControllerTest.php @@ -15,7 +15,6 @@ use SimpleSAML\Module\oidc\Controller\LogoutController; use SimpleSAML\Module\oidc\Factories\TemplateFactory; use SimpleSAML\Module\oidc\Server\AuthorizationServer; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestTypes\LogoutRequest; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Services\SessionService; @@ -24,7 +23,6 @@ use SimpleSAML\Session; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; -use Throwable; /** * @covers \SimpleSAML\Module\oidc\Controller\LogoutController @@ -81,8 +79,8 @@ public function testConstruct(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testInvokeThrowsForInvalidLogoutRequest(): void { @@ -103,9 +101,9 @@ public function testInvokeThrowsForInvalidLogoutRequest(): void } /** - * @throws Throwable - * @throws BadRequest - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCallLogoutForSessionIdInIdTokenHint(): void { @@ -134,9 +132,9 @@ public function testCallLogoutForSessionIdInIdTokenHint(): void } /** - * @throws Throwable - * @throws BadRequest - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testLogsIfSessionFromIdTokenHintNotFound(): void { @@ -164,9 +162,9 @@ public function testLogsIfSessionFromIdTokenHintNotFound(): void } /** - * @throws Throwable - * @throws BadRequest - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testLogoutCalledOnCurrentSession(): void { @@ -188,9 +186,9 @@ public function testLogoutCalledOnCurrentSession(): void } /** - * @throws Throwable - * @throws BadRequest - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testReturnsRedirectResponseIfPostLogoutRedirectUriIsSet(): void { @@ -212,9 +210,9 @@ public function testReturnsRedirectResponseIfPostLogoutRedirectUriIsSet(): void } /** - * @throws Throwable - * @throws BadRequest - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testReturnsResponse(): void { diff --git a/tests/src/Controller/Traits/RequestTraitTest.php b/tests/src/Controller/Traits/RequestTraitTest.php index 399fc340..df99572f 100644 --- a/tests/src/Controller/Traits/RequestTraitTest.php +++ b/tests/src/Controller/Traits/RequestTraitTest.php @@ -6,7 +6,6 @@ use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; -use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Controller\Traits\RequestTrait; @@ -45,7 +44,7 @@ public function handleCorsWrapper(ServerRequest $request): Response } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testItThrowsIfOriginHeaderNotAvailable(): void { @@ -56,7 +55,7 @@ public function testItThrowsIfOriginHeaderNotAvailable(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testItThrowsIfOriginHeaderNotAllowed(): void { diff --git a/tests/src/Controller/UserInfoControllerTest.php b/tests/src/Controller/UserInfoControllerTest.php index fb10e4a3..c74cd084 100644 --- a/tests/src/Controller/UserInfoControllerTest.php +++ b/tests/src/Controller/UserInfoControllerTest.php @@ -4,11 +4,9 @@ namespace SimpleSAML\Test\Module\oidc\Controller; -use League\OAuth2\Server\Exception\OAuthServerException; -use PHPUnit\Framework\MockObject\Exception; -use PHPUnit\Framework\MockObject\MockObject; use Laminas\Diactoros\ServerRequest; use League\OAuth2\Server\ResourceServer; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Error\UserNotFound; @@ -19,7 +17,6 @@ use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; use SimpleSAML\Module\oidc\Repositories\AllowedOriginRepository; use SimpleSAML\Module\oidc\Repositories\UserRepository; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; /** @@ -38,7 +35,7 @@ class UserInfoControllerTest extends TestCase protected MockObject $userEntityMock; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -74,9 +71,9 @@ public function testItIsInitializable(): void } /** - * @throws UserNotFound - * @throws OidcServerException - * @throws OAuthServerException + * @throws \SimpleSAML\Error\UserNotFound + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function testItReturnsUserClaims(): void { @@ -139,8 +136,8 @@ public function testItReturnsUserClaims(): void } /** - * @throws OidcServerException - * @throws OAuthServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function testItThrowsIfAccessTokenNotFound(): void { @@ -174,8 +171,8 @@ public function testItThrowsIfAccessTokenNotFound(): void } /** - * @throws OidcServerException - * @throws OAuthServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function testItThrowsIfUserNotFound(): void { diff --git a/tests/src/Entities/AccessTokenEntityTest.php b/tests/src/Entities/AccessTokenEntityTest.php index 8877b0c3..3b06bd27 100644 --- a/tests/src/Entities/AccessTokenEntityTest.php +++ b/tests/src/Entities/AccessTokenEntityTest.php @@ -4,14 +4,11 @@ namespace SimpleSAML\Test\Module\oidc\Entities; -use JsonException; -use PHPUnit\Framework\MockObject\Exception; +use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; use SimpleSAML\Module\oidc\Entities\AccessTokenEntity; use SimpleSAML\Module\oidc\Entities\ClientEntity; -use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Entities\ScopeEntity; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; /** * @covers \SimpleSAML\Module\oidc\Entities\AccessTokenEntity @@ -33,23 +30,23 @@ class AccessTokenEntityTest extends TestCase /** - * @var ClientEntity + * @var \SimpleSAML\Module\oidc\Entities\ClientEntity */ protected ClientEntity $clientEntityStub; /** - * @var ScopeEntity + * @var \SimpleSAML\Module\oidc\Entities\ScopeEntity */ protected ScopeEntity $scopeEntityOpenId; /** - * @var ScopeEntity + * @var \SimpleSAML\Module\oidc\Entities\ScopeEntity */ protected ScopeEntity $scopeEntityProfile; /** - * @throws Exception - * @throws JsonException + * @throws \Exception + * @throws \JsonException */ protected function setUp(): void { @@ -87,8 +84,8 @@ protected function setUp(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testCanCreateInstanceFromState(): void { @@ -110,8 +107,8 @@ public function testCanCreateInstanceFromData(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testHasProperState(): void { @@ -125,8 +122,8 @@ public function testHasProperState(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testHasImmutableStringRepresentation(): void { diff --git a/tests/src/Entities/AuthCodeEntityTest.php b/tests/src/Entities/AuthCodeEntityTest.php index 2acbaddb..adb3a3d3 100644 --- a/tests/src/Entities/AuthCodeEntityTest.php +++ b/tests/src/Entities/AuthCodeEntityTest.php @@ -4,13 +4,10 @@ namespace SimpleSAML\Test\Module\oidc\Entities; -use JsonException; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; -use SimpleSAML\Module\oidc\Entities\AuthCodeEntity; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\oidc\Entities\AuthCodeEntity; use SimpleSAML\Module\oidc\Entities\ClientEntity; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; /** * @covers \SimpleSAML\Module\oidc\Entities\AuthCodeEntity @@ -21,7 +18,7 @@ class AuthCodeEntityTest extends TestCase protected array $state; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -40,8 +37,8 @@ protected function setUp(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ protected function prepareMockedInstance(array $state = null): AuthCodeEntity { @@ -50,8 +47,8 @@ protected function prepareMockedInstance(array $state = null): AuthCodeEntity } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testItIsInitializable(): void { @@ -62,8 +59,8 @@ public function testItIsInitializable(): void } /** - * @throws JsonException - * @throws OidcServerException + * @throws \JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCanGetState(): void { @@ -83,8 +80,8 @@ public function testCanGetState(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testCanSetNonce(): void { @@ -95,8 +92,8 @@ public function testCanSetNonce(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testCanBeRevoked(): void { diff --git a/tests/src/Entities/ClientEntityTest.php b/tests/src/Entities/ClientEntityTest.php index 920fa9d2..0c7938b4 100644 --- a/tests/src/Entities/ClientEntityTest.php +++ b/tests/src/Entities/ClientEntityTest.php @@ -4,10 +4,8 @@ namespace SimpleSAML\Test\Module\oidc\Entities; -use JsonException; -use SimpleSAML\Module\oidc\Entities\ClientEntity; use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; +use SimpleSAML\Module\oidc\Entities\ClientEntity; /** * @covers \SimpleSAML\Module\oidc\Entities\ClientEntity @@ -34,8 +32,8 @@ protected function setUp(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function prepareMockedInstance(array $state = null): ClientEntity { @@ -44,8 +42,8 @@ public function prepareMockedInstance(array $state = null): ClientEntity } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testItIsInitializable(): void { @@ -61,8 +59,8 @@ public function testItIsInitializable(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testCanGetProperties(): void { @@ -88,8 +86,8 @@ public function testCanGetProperties(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testCanChangeSecret(): void { @@ -100,8 +98,8 @@ public function testCanChangeSecret(): void } /** - * @throws JsonException - * @throws OidcServerException + * @throws \JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCanGetState(): void { @@ -125,8 +123,8 @@ public function testCanGetState(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testCanExportAsArray(): void { diff --git a/tests/src/Entities/RefreshTokenEntityTest.php b/tests/src/Entities/RefreshTokenEntityTest.php index 409a6c61..5b585062 100644 --- a/tests/src/Entities/RefreshTokenEntityTest.php +++ b/tests/src/Entities/RefreshTokenEntityTest.php @@ -4,13 +4,11 @@ namespace SimpleSAML\Test\Module\oidc\Entities; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Entities\AccessTokenEntity; use SimpleSAML\Module\oidc\Entities\Interfaces\RefreshTokenEntityInterface; use SimpleSAML\Module\oidc\Entities\RefreshTokenEntity; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; /** * @covers \SimpleSAML\Module\oidc\Entities\RefreshTokenEntity @@ -21,7 +19,7 @@ class RefreshTokenEntityTest extends TestCase protected array $state; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -38,7 +36,7 @@ protected function setUp(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ protected function prepareMockedInstance(array $state = null): RefreshTokenEntityInterface { @@ -47,7 +45,7 @@ protected function prepareMockedInstance(array $state = null): RefreshTokenEntit } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testItIsInitializable(): void { @@ -58,7 +56,7 @@ public function testItIsInitializable(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCanGetState(): void { diff --git a/tests/src/Entities/ScopeEntityTest.php b/tests/src/Entities/ScopeEntityTest.php index 4aa2e66c..61910cfe 100644 --- a/tests/src/Entities/ScopeEntityTest.php +++ b/tests/src/Entities/ScopeEntityTest.php @@ -4,8 +4,8 @@ namespace SimpleSAML\Test\Module\oidc\Entities; -use SimpleSAML\Module\oidc\Entities\ScopeEntity; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\oidc\Entities\ScopeEntity; class ScopeEntityTest extends TestCase { diff --git a/tests/src/Entities/UserEntityTest.php b/tests/src/Entities/UserEntityTest.php index 9c32252c..cc96ab6c 100644 --- a/tests/src/Entities/UserEntityTest.php +++ b/tests/src/Entities/UserEntityTest.php @@ -4,10 +4,8 @@ namespace SimpleSAML\Test\Module\oidc\Entities; -use Exception; -use SimpleSAML\Module\oidc\Entities\UserEntity; use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; +use SimpleSAML\Module\oidc\Entities\UserEntity; /** * @covers \SimpleSAML\Module\oidc\Entities\UserEntity @@ -27,7 +25,7 @@ protected function setUp(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ protected function prepareMockedInstance(array $state = null): UserEntity { @@ -36,8 +34,8 @@ protected function prepareMockedInstance(array $state = null): UserEntity } /** - * @throws OidcServerException - * @throws Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function testItIsInitializable(): void { @@ -53,8 +51,8 @@ public function testItIsInitializable(): void } /** - * @throws OidcServerException - * @throws Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function testCanGetProperties(): void { @@ -69,7 +67,7 @@ public function testCanGetProperties(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCanGetState(): void { diff --git a/tests/src/Factories/AuthSimpleFactoryTest.php b/tests/src/Factories/AuthSimpleFactoryTest.php index 2463ee09..dd84f8e7 100644 --- a/tests/src/Factories/AuthSimpleFactoryTest.php +++ b/tests/src/Factories/AuthSimpleFactoryTest.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Test\Module\oidc\Factories; -use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory; use PHPUnit\Framework\TestCase; /** diff --git a/tests/src/Factories/ClaimTranslatorExtractorFactoryTest.php b/tests/src/Factories/ClaimTranslatorExtractorFactoryTest.php index 076ef7eb..d639983f 100644 --- a/tests/src/Factories/ClaimTranslatorExtractorFactoryTest.php +++ b/tests/src/Factories/ClaimTranslatorExtractorFactoryTest.php @@ -4,12 +4,11 @@ namespace SimpleSAML\Test\Module\oidc\Factories; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Factories\ClaimTranslatorExtractorFactory; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; /** @@ -20,7 +19,7 @@ class ClaimTranslatorExtractorFactoryTest extends TestCase protected MockObject $moduleConfigMock; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { diff --git a/tests/src/Forms/ClientFormTest.php b/tests/src/Forms/ClientFormTest.php index 12033bb6..9f915c7c 100644 --- a/tests/src/Forms/ClientFormTest.php +++ b/tests/src/Forms/ClientFormTest.php @@ -7,7 +7,6 @@ use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\TestDox; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; @@ -20,17 +19,17 @@ */ class ClientFormTest extends TestCase { - /** @var MockObject */ + /** @var \PHPUnit\Framework\MockObject\MockObject */ protected MockObject $csrfProtection; - /** @var MockObject */ + /** @var \PHPUnit\Framework\MockObject\MockObject */ protected MockObject $moduleConfig; - /** @var MockObject */ + /** @var \PHPUnit\Framework\MockObject\MockObject */ protected MockObject $serverRequestMock; /** - * @throws Exception + * @throws \Exception */ public function setUp(): void { @@ -102,7 +101,7 @@ public function testValidateOrigin(string $url, bool $isValid): void } /** - * @return ClientForm + * @return \SimpleSAML\Module\oidc\Forms\ClientForm * @throws \Exception */ protected function prepareMockedInstance(): ClientForm diff --git a/tests/src/ModuleConfigTest.php b/tests/src/ModuleConfigTest.php index a0472120..1be5ff2c 100644 --- a/tests/src/ModuleConfigTest.php +++ b/tests/src/ModuleConfigTest.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Test\Module\oidc; -use Exception; use Lcobucci\JWT\Signer; use Lcobucci\JWT\Signer\Rsa\Sha256; use PHPUnit\Framework\Attributes\CoversClass; @@ -17,6 +16,7 @@ use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Utils\Config; use SimpleSAML\Utils\HTTP; +use stdClass; #[CoversClass(ModuleConfig::class)] class ModuleConfigTest extends TestCase @@ -101,7 +101,7 @@ protected function mock(): ModuleConfig } /** - * @throws Exception + * @throws \Exception */ public function testSigningKeyNameCanBeCustomized(): void { @@ -276,7 +276,7 @@ public function testThrowsIForcedAcrValueForCookieAuthenticationNotAllowed(): vo public function testThrowsIfInvalidSignerProvided(): void { - $this->overrides[ModuleConfig::OPTION_TOKEN_SIGNER] = \stdClass::class; + $this->overrides[ModuleConfig::OPTION_TOKEN_SIGNER] = stdClass::class; $this->expectException(ConfigurationError::class); $this->mock()->getProtocolSigner(); } diff --git a/tests/src/Repositories/AccessTokenRepositoryTest.php b/tests/src/Repositories/AccessTokenRepositoryTest.php index 50f0d87f..28493117 100644 --- a/tests/src/Repositories/AccessTokenRepositoryTest.php +++ b/tests/src/Repositories/AccessTokenRepositoryTest.php @@ -17,18 +17,14 @@ use DateTimeImmutable; use Exception; -use JsonException; -use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; -use SimpleSAML\Error\Error; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Entities\ScopeEntity; use SimpleSAML\Module\oidc\Entities\UserEntity; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; use SimpleSAML\Module\oidc\Repositories\ClientRepository; use SimpleSAML\Module\oidc\Repositories\UserRepository; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\DatabaseMigration; use SimpleSAML\Module\oidc\Utils\TimestampGenerator; @@ -44,7 +40,7 @@ class AccessTokenRepositoryTest extends TestCase protected static AccessTokenRepository $repository; /** - * @throws Exception + * @throws \Exception */ public static function setUpBeforeClass(): void { @@ -76,11 +72,11 @@ public function testGetTableName(): void } /** - * @throws UniqueTokenIdentifierConstraintViolationException - * @throws Error - * @throws OidcServerException - * @throws JsonException - * @throws Exception + * @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException + * @throws \SimpleSAML\Error\Error + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException + * @throws \Exception */ public function testAddAndFound(): void { @@ -106,7 +102,7 @@ public function testAddAndFound(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testAddAndNotFound(): void { @@ -116,8 +112,8 @@ public function testAddAndNotFound(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testRevokeToken(): void { @@ -128,8 +124,8 @@ public function testRevokeToken(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testErrorRevokeInvalidToken(): void { @@ -139,7 +135,7 @@ public function testErrorRevokeInvalidToken(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testErrorCheckIsRevokedInvalidToken(): void { @@ -149,8 +145,8 @@ public function testErrorCheckIsRevokedInvalidToken(): void } /** - * @throws OidcServerException - * @throws Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function testRemoveExpired(): void { diff --git a/tests/src/Repositories/AllowedOriginRepositoryTest.php b/tests/src/Repositories/AllowedOriginRepositoryTest.php index 2fedf975..f4e443d3 100644 --- a/tests/src/Repositories/AllowedOriginRepositoryTest.php +++ b/tests/src/Repositories/AllowedOriginRepositoryTest.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Test\Module\oidc\Repositories; -use Exception; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; use SimpleSAML\Module\oidc\ModuleConfig; @@ -29,7 +28,7 @@ class AllowedOriginRepositoryTest extends TestCase private static ModuleConfig $moduleConfig; /** - * @throws Exception + * @throws \Exception */ public static function setUpBeforeClass(): void { diff --git a/tests/src/Repositories/AuthCodeRepositoryTest.php b/tests/src/Repositories/AuthCodeRepositoryTest.php index 5efa3e51..5ce3f035 100644 --- a/tests/src/Repositories/AuthCodeRepositoryTest.php +++ b/tests/src/Repositories/AuthCodeRepositoryTest.php @@ -17,14 +17,11 @@ use DateTimeImmutable; use Exception; -use JsonException; -use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; -use SimpleSAML\Error\Error; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Entities\ScopeEntity; use SimpleSAML\Module\oidc\Entities\UserEntity; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\AuthCodeRepository; use SimpleSAML\Module\oidc\Repositories\ClientRepository; use SimpleSAML\Module\oidc\Repositories\UserRepository; @@ -44,7 +41,7 @@ class AuthCodeRepositoryTest extends TestCase protected static AuthCodeRepository $repository; /** - * @throws Exception + * @throws \Exception */ public static function setUpBeforeClass(): void { @@ -76,11 +73,10 @@ public function testGetTableName(): void } /** - * @throws UniqueTokenIdentifierConstraintViolationException - * @throws Error - * @throws JsonException - * @throws Exception - * @throws Exception + * @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException + * @throws \SimpleSAML\Error\Error + * @throws \JsonException + * @throws \Exception */ public function testAddAndFound(): void { @@ -107,7 +103,7 @@ public function testAddAndFound(): void } /** - * @throws Exception + * @throws \Exception */ public function testAddAndNotFound(): void { @@ -117,8 +113,8 @@ public function testAddAndNotFound(): void } /** - * @throws JsonException - * @throws Exception + * @throws \JsonException + * @throws \Exception */ public function testRevokeCode(): void { @@ -129,7 +125,7 @@ public function testRevokeCode(): void } /** - * @throws JsonException + * @throws \JsonException */ public function testErrorRevokeInvalidAuthCode(): void { @@ -146,7 +142,7 @@ public function testErrorCheckIsRevokedInvalidAuthCode(): void } /** - * @throws Exception + * @throws \Exception */ public function testRemoveExpired(): void { diff --git a/tests/src/Repositories/ClientRepositoryTest.php b/tests/src/Repositories/ClientRepositoryTest.php index 2ab94c0d..95159abb 100644 --- a/tests/src/Repositories/ClientRepositoryTest.php +++ b/tests/src/Repositories/ClientRepositoryTest.php @@ -15,16 +15,13 @@ */ namespace SimpleSAML\Test\Module\oidc\Repositories; -use Exception; -use JsonException; use League\OAuth2\Server\Exception\OAuthServerException; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Entities\ClientEntity; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\ClientRepository; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\DatabaseMigration; /** @@ -35,7 +32,7 @@ class ClientRepositoryTest extends TestCase protected static ClientRepository $repository; /** - * @throws Exception + * @throws \Exception */ public static function setUpBeforeClass(): void { @@ -57,8 +54,8 @@ public static function setUpBeforeClass(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function tearDown(): void { @@ -75,8 +72,8 @@ public function testGetTableName(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testAddAndFound(): void { @@ -88,8 +85,8 @@ public function testAddAndFound(): void } /** - * @throws OAuthServerException - * @throws JsonException + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \JsonException */ public function testGetClientEntity(): void { @@ -101,7 +98,7 @@ public function testGetClientEntity(): void } /** - * @throws JsonException + * @throws \JsonException */ public function testGetDisabledClientEntity(): void { @@ -114,8 +111,8 @@ public function testGetDisabledClientEntity(): void } /** - * @throws OAuthServerException - * @throws JsonException + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \JsonException */ public function testNotFoundClient(): void { @@ -125,8 +122,8 @@ public function testNotFoundClient(): void } /** - * @throws OAuthServerException - * @throws JsonException + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \JsonException */ public function testValidateConfidentialClient(): void { @@ -138,8 +135,8 @@ public function testValidateConfidentialClient(): void } /** - * @throws OAuthServerException - * @throws JsonException + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \JsonException */ public function testValidatePublicClient(): void { @@ -151,8 +148,8 @@ public function testValidatePublicClient(): void } /** - * @throws OAuthServerException - * @throws JsonException + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \JsonException */ public function testNotValidateConfidentialClientWithWrongSecret() { @@ -164,8 +161,8 @@ public function testNotValidateConfidentialClientWithWrongSecret() } /** - * @throws OAuthServerException - * @throws JsonException + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \JsonException */ public function testNotValidateWhenClientDoesNotExists() { @@ -174,8 +171,8 @@ public function testNotValidateWhenClientDoesNotExists() } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testFindAll(): void { @@ -188,7 +185,7 @@ public function testFindAll(): void } /** - * @throws Exception + * @throws \Exception */ public function testFindPaginated(): void { @@ -207,7 +204,7 @@ public function testFindPaginated(): void } /** - * @throws Exception + * @throws \Exception */ public function testFindPageInRange(): void { @@ -222,7 +219,7 @@ public function testFindPageInRange(): void } /** - * @throws Exception + * @throws \Exception */ public function testFindPaginationWithEmptyList() { @@ -233,8 +230,8 @@ public function testFindPaginationWithEmptyList() } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testUpdate(): void { @@ -260,8 +257,8 @@ public function testUpdate(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testDelete(): void { @@ -276,10 +273,9 @@ public function testDelete(): void } /** - * @throws JsonException - * @throws OidcServerException - * @throws Exception - * @throws Exception + * @throws \JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function testCrudWithOwner(): void { diff --git a/tests/src/Repositories/RefreshTokenRepositoryTest.php b/tests/src/Repositories/RefreshTokenRepositoryTest.php index 08ae8eca..8141f2d4 100644 --- a/tests/src/Repositories/RefreshTokenRepositoryTest.php +++ b/tests/src/Repositories/RefreshTokenRepositoryTest.php @@ -16,22 +16,16 @@ namespace SimpleSAML\Test\Module\oidc\Repositories; use DateTimeImmutable; -use Exception; -use JsonException; -use League\OAuth2\Server\Exception\OAuthServerException; -use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; -use RuntimeException; use PHPUnit\Framework\TestCase; +use RuntimeException; use SimpleSAML\Configuration; -use SimpleSAML\Error\Error; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Entities\AccessTokenEntity; use SimpleSAML\Module\oidc\Entities\UserEntity; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; use SimpleSAML\Module\oidc\Repositories\ClientRepository; use SimpleSAML\Module\oidc\Repositories\RefreshTokenRepository; use SimpleSAML\Module\oidc\Repositories\UserRepository; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\DatabaseMigration; use SimpleSAML\Module\oidc\Utils\TimestampGenerator; @@ -48,10 +42,10 @@ class RefreshTokenRepositoryTest extends TestCase protected static RefreshTokenRepository $repository; /** - * @throws UniqueTokenIdentifierConstraintViolationException - * @throws Error - * @throws JsonException - * @throws Exception + * @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException + * @throws \SimpleSAML\Error\Error + * @throws \JsonException + * @throws \Exception */ public static function setUpBeforeClass(): void { @@ -88,11 +82,10 @@ public function testGetTableName(): void } /** - * @throws UniqueTokenIdentifierConstraintViolationException - * @throws OidcServerException - * @throws OAuthServerException - * @throws Exception - * @throws Exception + * @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \Exception */ public function testAddAndFound(): void { @@ -112,7 +105,7 @@ public function testAddAndFound(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testAddAndNotFound(): void { @@ -122,7 +115,7 @@ public function testAddAndNotFound(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testRevokeToken(): void { @@ -133,7 +126,7 @@ public function testRevokeToken(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testErrorRevokeInvalidToken(): void { @@ -143,7 +136,7 @@ public function testErrorRevokeInvalidToken(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testErrorCheckIsRevokedInvalidToken(): void { @@ -153,8 +146,8 @@ public function testErrorCheckIsRevokedInvalidToken(): void } /** - * @throws OidcServerException - * @throws Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function testRemoveExpired(): void { diff --git a/tests/src/Repositories/ScopeRepositoryTest.php b/tests/src/Repositories/ScopeRepositoryTest.php index b558dcf7..1fc57628 100644 --- a/tests/src/Repositories/ScopeRepositoryTest.php +++ b/tests/src/Repositories/ScopeRepositoryTest.php @@ -15,11 +15,10 @@ */ namespace SimpleSAML\Test\Module\oidc\Repositories; -use Exception; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Entities\ScopeEntity; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\ScopeRepository; use SimpleSAML\Module\oidc\Services\DatabaseMigration; @@ -45,7 +44,7 @@ public static function setUpBeforeClass(): void } /** - * @throws Exception + * @throws \Exception */ public function testGetScopeEntityByIdentifier(): void { @@ -62,7 +61,7 @@ public function testGetScopeEntityByIdentifier(): void } /** - * @throws Exception + * @throws \Exception */ public function testGetUnknownScope(): void { @@ -72,7 +71,7 @@ public function testGetUnknownScope(): void } /** - * @throws Exception + * @throws \Exception */ public function testFinalizeScopes(): void { diff --git a/tests/src/Repositories/UserRepositoryTest.php b/tests/src/Repositories/UserRepositoryTest.php index 53c590ea..3cd53da4 100644 --- a/tests/src/Repositories/UserRepositoryTest.php +++ b/tests/src/Repositories/UserRepositoryTest.php @@ -15,13 +15,11 @@ */ namespace SimpleSAML\Test\Module\oidc\Repositories; -use Exception; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Entities\UserEntity; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\UserRepository; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\DatabaseMigration; /** @@ -32,7 +30,7 @@ class UserRepositoryTest extends TestCase protected static UserRepository $repository; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -59,8 +57,8 @@ public function testGetTableName(): void } /** - * @throws OidcServerException - * @throws Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function testAddAndFound(): void { @@ -72,7 +70,7 @@ public function testAddAndFound(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testNotFound(): void { @@ -82,8 +80,8 @@ public function testNotFound(): void } /** - * @throws OidcServerException - * @throws Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function testUpdate(): void { @@ -96,7 +94,7 @@ public function testUpdate(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testDelete(): void { diff --git a/tests/src/Server/Associations/RelyingPartyAssociationTest.php b/tests/src/Server/Associations/RelyingPartyAssociationTest.php index 56e4f2a4..8cae14e9 100644 --- a/tests/src/Server/Associations/RelyingPartyAssociationTest.php +++ b/tests/src/Server/Associations/RelyingPartyAssociationTest.php @@ -4,8 +4,8 @@ namespace SimpleSAML\Test\Module\oidc\Server\Associations; -use SimpleSAML\Module\oidc\Server\Associations\RelyingPartyAssociation; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\oidc\Server\Associations\RelyingPartyAssociation; /** * @covers \SimpleSAML\Module\oidc\Server\Associations\RelyingPartyAssociation diff --git a/tests/src/Server/AuthorizationServerTest.php b/tests/src/Server/AuthorizationServerTest.php index 2351cff4..61a4b897 100644 --- a/tests/src/Server/AuthorizationServerTest.php +++ b/tests/src/Server/AuthorizationServerTest.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Test\Module\oidc\Server; -use SimpleSAML\Module\oidc\Server\AuthorizationServer; use PHPUnit\Framework\TestCase; /** diff --git a/tests/src/Server/Grants/AuthCodeGrantTest.php b/tests/src/Server/Grants/AuthCodeGrantTest.php index 3e1c4df4..09bf7f59 100644 --- a/tests/src/Server/Grants/AuthCodeGrantTest.php +++ b/tests/src/Server/Grants/AuthCodeGrantTest.php @@ -4,9 +4,8 @@ namespace SimpleSAML\Test\Module\oidc\Server\Grants; -use PHPUnit\Framework\MockObject\Exception; -use PHPUnit\Framework\MockObject\Stub; use DateInterval; +use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\Interfaces\AccessTokenRepositoryInterface; @@ -28,7 +27,7 @@ class AuthCodeGrantTest extends TestCase protected Stub $moduleConfigStub; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { diff --git a/tests/src/Server/Grants/ImplicitGrantTest.php b/tests/src/Server/Grants/ImplicitGrantTest.php index ccd35463..846347f1 100644 --- a/tests/src/Server/Grants/ImplicitGrantTest.php +++ b/tests/src/Server/Grants/ImplicitGrantTest.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Test\Module\oidc\Server\Grants; -use SimpleSAML\Module\oidc\Server\Grants\ImplicitGrant; use PHPUnit\Framework\TestCase; /** diff --git a/tests/src/Server/Grants/OAuth2ImplicitGrantTest.php b/tests/src/Server/Grants/OAuth2ImplicitGrantTest.php index 4c6df02e..0730764f 100644 --- a/tests/src/Server/Grants/OAuth2ImplicitGrantTest.php +++ b/tests/src/Server/Grants/OAuth2ImplicitGrantTest.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Test\Module\oidc\Server\Grants; -use SimpleSAML\Module\oidc\Server\Grants\OAuth2ImplicitGrant; use PHPUnit\Framework\TestCase; /** diff --git a/tests/src/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php b/tests/src/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php index 6580bc88..d9645e41 100644 --- a/tests/src/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php +++ b/tests/src/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php @@ -4,15 +4,13 @@ namespace SimpleSAML\Test\Module\oidc\Server\LogoutHandlers; -use League\OAuth2\Server\Exception\OAuthServerException; -use PHPUnit\Framework\MockObject\Exception; -use PHPUnit\Framework\MockObject\MockObject; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; use GuzzleHttp\Psr7\Response; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Server\Associations\RelyingPartyAssociation; use SimpleSAML\Module\oidc\Server\LogoutHandlers\BackChannelLogoutHandler; -use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Services\LogoutTokenBuilder; @@ -33,7 +31,7 @@ class BackChannelLogoutHandlerTest extends TestCase private array $sampleRelyingPartyAssociation = []; /** - * @throws Exception + * @throws \Exception */ public function setUp(): void { @@ -44,7 +42,7 @@ public function setUp(): void } /** - * @throws OAuthServerException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function testLogsErrorForInvalidUri(): void { @@ -59,7 +57,7 @@ public function testLogsErrorForInvalidUri(): void } /** - * @throws OAuthServerException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function testLogsNoticeForSuccessfulResponse(): void { diff --git a/tests/src/Server/RequestTypes/AuthorizationRequestTest.php b/tests/src/Server/RequestTypes/AuthorizationRequestTest.php index 2ec0e99b..7f5fe6fb 100644 --- a/tests/src/Server/RequestTypes/AuthorizationRequestTest.php +++ b/tests/src/Server/RequestTypes/AuthorizationRequestTest.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Test\Module\oidc\Server\RequestTypes; -use SimpleSAML\Module\oidc\Server\RequestTypes\AuthorizationRequest; use PHPUnit\Framework\TestCase; /** diff --git a/tests/src/Server/RequestTypes/LogoutRequestTest.php b/tests/src/Server/RequestTypes/LogoutRequestTest.php index 4ee65bc6..d2448c03 100644 --- a/tests/src/Server/RequestTypes/LogoutRequestTest.php +++ b/tests/src/Server/RequestTypes/LogoutRequestTest.php @@ -5,10 +5,9 @@ namespace SimpleSAML\Test\Module\oidc\Server\RequestTypes; use Lcobucci\JWT\UnencryptedToken; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; -use SimpleSAML\Module\oidc\Server\RequestTypes\LogoutRequest; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\oidc\Server\RequestTypes\LogoutRequest; /** * @covers \SimpleSAML\Module\oidc\Server\RequestTypes\LogoutRequest @@ -22,7 +21,7 @@ class LogoutRequestTest extends TestCase protected static string $uiLocales = 'en'; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { diff --git a/tests/src/Server/ResponseTypes/IdTokenResponseTest.php b/tests/src/Server/ResponseTypes/IdTokenResponseTest.php index 63d306d3..5440c5ea 100644 --- a/tests/src/Server/ResponseTypes/IdTokenResponseTest.php +++ b/tests/src/Server/ResponseTypes/IdTokenResponseTest.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Test\Module\oidc\Server\ResponseTypes; -use PHPUnit\Framework\MockObject\MockObject; use DateTimeImmutable; use Exception; use Laminas\Diactoros\Response; @@ -13,7 +12,6 @@ use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Token\Parser; -use Lcobucci\JWT\Token\Plain; use Lcobucci\JWT\Validation\Constraint\IdentifiedBy; use Lcobucci\JWT\Validation\Constraint\IssuedBy; use Lcobucci\JWT\Validation\Constraint\PermittedFor; @@ -22,14 +20,14 @@ use Lcobucci\JWT\Validation\Constraint\StrictValidAt; use Lcobucci\JWT\Validation\Validator; use League\OAuth2\Server\CryptKey; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use ReflectionException; use SimpleSAML\Configuration; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Entities\AccessTokenEntity; use SimpleSAML\Module\oidc\Entities\ClientEntity; use SimpleSAML\Module\oidc\Entities\ScopeEntity; use SimpleSAML\Module\oidc\Entities\UserEntity; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\Interfaces\IdentityProviderInterface; use SimpleSAML\Module\oidc\Server\ResponseTypes\IdTokenResponse; use SimpleSAML\Module\oidc\Services\IdTokenBuilder; @@ -61,8 +59,8 @@ class IdTokenResponseTest extends TestCase /** * @throws \PHPUnit\Framework\MockObject\Exception - * @throws ReflectionException - * @throws Exception + * @throws \ReflectionException + * @throws \Exception */ protected function setUp(): void { @@ -140,7 +138,7 @@ public function testItIsInitializable(): void } /** - * @throws Exception + * @throws \Exception */ public function testItCanGenerateResponse(): void { @@ -156,7 +154,7 @@ public function testItCanGenerateResponse(): void } /** - * @throws Exception + * @throws \Exception */ public function testItCanGenerateResponseWithIndividualRequestedClaims(): void { @@ -205,7 +203,7 @@ public function testNoExtraParamsForNonOidcRequest(): void } /** - * @throws Exception + * @throws \Exception */ protected function shouldHaveValidIdToken(string $body, $expectedClaims = []): bool { diff --git a/tests/src/Server/Validators/BearerTokenValidatorTest.php b/tests/src/Server/Validators/BearerTokenValidatorTest.php index a9202272..6d949a25 100644 --- a/tests/src/Server/Validators/BearerTokenValidatorTest.php +++ b/tests/src/Server/Validators/BearerTokenValidatorTest.php @@ -4,12 +4,10 @@ namespace SimpleSAML\Test\Module\oidc\Server\Validators; -use JsonException; use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\StreamFactory; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface as OAuth2AccessTokenRepositoryInterface; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Configuration; @@ -44,7 +42,6 @@ class BearerTokenValidatorTest extends TestCase protected ServerRequestInterface $serverRequest; /** - * @throws Exception * @throws \Exception */ public function setUp(): void @@ -55,8 +52,8 @@ public function setUp(): void } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public static function setUpBeforeClass(): void { @@ -137,7 +134,7 @@ public function testValidatorThrowsForNonExistentAccessToken() } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testValidatesForAuthorizationHeader() { @@ -152,7 +149,7 @@ public function testValidatesForAuthorizationHeader() } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testValidatesForPostBodyParam() { @@ -183,8 +180,8 @@ public function testThrowsForUnparsableAccessToken() } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testThrowsForExpiredAccessToken() { @@ -204,7 +201,8 @@ public function testThrowsForExpiredAccessToken() } /** - * @throws OidcServerException|\Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function testThrowsForRevokedAccessToken() { @@ -223,8 +221,8 @@ public function testThrowsForRevokedAccessToken() } /** - * @throws OidcServerException - * @throws JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException */ public function testThrowsForEmptyAccessTokenJti() { diff --git a/tests/src/Services/AuthContextServiceTest.php b/tests/src/Services/AuthContextServiceTest.php index b851b2bb..bc1e0f63 100644 --- a/tests/src/Services/AuthContextServiceTest.php +++ b/tests/src/Services/AuthContextServiceTest.php @@ -5,13 +5,13 @@ namespace SimpleSAML\Test\Module\oidc\Services; use PHPUnit\Framework\MockObject\MockObject; -use RuntimeException; use PHPUnit\Framework\TestCase; +use RuntimeException; use SimpleSAML\Auth\Simple; use SimpleSAML\Configuration; use SimpleSAML\Error\Exception; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Services\AuthContextService; /** @@ -71,7 +71,7 @@ public function testItIsInitializable(): void } /** - * @throws Exception + * @throws \Exception */ public function testItReturnsUsername(): void { diff --git a/tests/src/Services/AuthProcServiceTest.php b/tests/src/Services/AuthProcServiceTest.php index 00e39aee..cc4c6a33 100644 --- a/tests/src/Services/AuthProcServiceTest.php +++ b/tests/src/Services/AuthProcServiceTest.php @@ -4,11 +4,10 @@ namespace SimpleSAML\Test\Module\oidc\Services; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; -use SimpleSAML\Module\oidc\ModuleConfig; -use SimpleSAML\Module\core\Auth\Process\AttributeAdd; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\core\Auth\Process\AttributeAdd; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Services\AuthProcService; /** @@ -19,7 +18,7 @@ class AuthProcServiceTest extends TestCase protected MockObject $moduleConfigMock; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { diff --git a/tests/src/Services/AuthenticationServiceTest.php b/tests/src/Services/AuthenticationServiceTest.php index 4839d65c..9662be89 100644 --- a/tests/src/Services/AuthenticationServiceTest.php +++ b/tests/src/Services/AuthenticationServiceTest.php @@ -4,20 +4,17 @@ namespace SimpleSAML\Test\Module\oidc\Services; -use PHPUnit\Framework\MockObject\MockObject; use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\Uri; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SimpleSAML\Auth\Simple; use SimpleSAML\Auth\Source; -use SimpleSAML\Error\AuthSource; -use SimpleSAML\Error\BadRequest; use SimpleSAML\Error\Exception; -use SimpleSAML\Error\NotFound; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Entities\ClientEntity; use SimpleSAML\Module\oidc\Entities\UserEntity; use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\ClientRepository; use SimpleSAML\Module\oidc\Repositories\UserRepository; use SimpleSAML\Module\oidc\Services\AuthenticationService; @@ -133,10 +130,10 @@ public function testItIsInitializable(): void } /** - * @throws AuthSource - * @throws BadRequest - * @throws NotFound - * @throws Exception + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Error\Exception */ public function testItCreatesNewUser(): void { @@ -175,10 +172,10 @@ public function testItCreatesNewUser(): void } /** - * @throws AuthSource - * @throws BadRequest - * @throws NotFound - * @throws Exception + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\NotFound + * @throws \SimpleSAML\Error\Exception */ public function testItReturnsAnUser(): void { @@ -219,9 +216,9 @@ public function testItReturnsAnUser(): void } /** - * @throws AuthSource - * @throws BadRequest - * @throws NotFound + * @throws \SimpleSAML\Error\AuthSource + * @throws \SimpleSAML\Error\BadRequest + * @throws \SimpleSAML\Error\NotFound */ public function testItThrowsIfClaimsNotExist(): void { diff --git a/tests/src/Services/IdTokenBuilderTest.php b/tests/src/Services/IdTokenBuilderTest.php index 38e5ac28..56e78c7a 100644 --- a/tests/src/Services/IdTokenBuilderTest.php +++ b/tests/src/Services/IdTokenBuilderTest.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Test\Module\oidc\Services; -use SimpleSAML\Module\oidc\Services\IdTokenBuilder; use PHPUnit\Framework\TestCase; /** diff --git a/tests/src/Services/JsonWebKeySetServiceTest.php b/tests/src/Services/JsonWebKeySetServiceTest.php index 9d86fccc..c86fd267 100644 --- a/tests/src/Services/JsonWebKeySetServiceTest.php +++ b/tests/src/Services/JsonWebKeySetServiceTest.php @@ -33,7 +33,7 @@ class JsonWebKeySetServiceTest extends TestCase /** * @return void - * @throws Exception + * @throws \Exception */ public static function setUpBeforeClass(): void { diff --git a/tests/src/Services/JsonWebTokenBuilderServiceTest.php b/tests/src/Services/JsonWebTokenBuilderServiceTest.php index 587374e5..954eb072 100644 --- a/tests/src/Services/JsonWebTokenBuilderServiceTest.php +++ b/tests/src/Services/JsonWebTokenBuilderServiceTest.php @@ -4,8 +4,6 @@ namespace SimpleSAML\Test\Module\oidc\Services; -use PHPUnit\Framework\MockObject\Stub; -use Exception; use Lcobucci\JWT\Builder; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Key\InMemory; @@ -13,9 +11,8 @@ use Lcobucci\JWT\UnencryptedToken; use Lcobucci\JWT\Validation\Constraint\IssuedBy; use Lcobucci\JWT\Validation\Constraint\SignedWith; -use League\OAuth2\Server\Exception\OAuthServerException; +use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; -use ReflectionException; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Services\JsonWebTokenBuilderService; @@ -29,6 +26,7 @@ class JsonWebTokenBuilderServiceTest extends TestCase private static string $publicKeyPath; private static Sha256 $signerSha256; private static string $selfUrlHost = 'https://example.org'; + /** * @var mixed */ @@ -55,8 +53,8 @@ public function setUp(): void } /** - * @throws ReflectionException - * @throws OAuthServerException + * @throws \ReflectionException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function testCanCreateBuilderInstance(): void { @@ -69,9 +67,9 @@ public function testCanCreateBuilderInstance(): void } /** - * @throws ReflectionException - * @throws OAuthServerException - * @throws Exception + * @throws \ReflectionException + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \Exception */ public function testCanGenerateSignedJwtToken(): void { @@ -110,7 +108,7 @@ public function testCanGenerateSignedJwtToken(): void } /** - * @throws ReflectionException + * @throws \ReflectionException */ public function testCanReturnCurrentSigner(): void { diff --git a/tests/src/Services/LogoutTokenBuilderTest.php b/tests/src/Services/LogoutTokenBuilderTest.php index 65dc51ef..9d30defa 100644 --- a/tests/src/Services/LogoutTokenBuilderTest.php +++ b/tests/src/Services/LogoutTokenBuilderTest.php @@ -4,8 +4,6 @@ namespace SimpleSAML\Test\Module\oidc\Services; -use PHPUnit\Framework\MockObject\Stub; -use Exception; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Rsa\Sha256; @@ -13,8 +11,8 @@ use Lcobucci\JWT\Validation\Constraint\PermittedFor; use Lcobucci\JWT\Validation\Constraint\RelatedTo; use Lcobucci\JWT\Validation\Constraint\SignedWith; +use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; -use ReflectionException; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Server\Associations\Interfaces\RelyingPartyAssociationInterface; use SimpleSAML\Module\oidc\Services\JsonWebTokenBuilderService; @@ -36,10 +34,12 @@ class LogoutTokenBuilderTest extends TestCase private static string $sessionId = 'session123'; private static string $backChannelLogoutUri = 'https//some-host.org/logout'; private static string $logoutTokenType = 'logout+jwt'; + /** * @var mixed */ private Stub $moduleConfigStub; + /** * @var mixed */ @@ -55,7 +55,7 @@ public static function setUpBeforeClass(): void } /** - * @throws ReflectionException + * @throws \ReflectionException * @throws \PHPUnit\Framework\MockObject\Exception * @throws \PHPUnit\Framework\MockObject\Exception */ @@ -79,8 +79,8 @@ public function setUp(): void } /** - * @throws ReflectionException - * @throws Exception + * @throws \ReflectionException + * @throws \Exception */ public function testCanGenerateSignedTokenForRelyingPartyAssociation(): void { diff --git a/tests/src/Services/OpMetadataServiceTest.php b/tests/src/Services/OpMetadataServiceTest.php index af74b3c7..07102881 100644 --- a/tests/src/Services/OpMetadataServiceTest.php +++ b/tests/src/Services/OpMetadataServiceTest.php @@ -5,10 +5,9 @@ namespace SimpleSAML\Test\Module\oidc\Services; use Lcobucci\JWT\Signer\Rsa; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; -use SimpleSAML\Module\oidc\ModuleConfig; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Services\OpMetadataService; /** @@ -19,7 +18,7 @@ class OpMetadataServiceTest extends TestCase protected MockObject $moduleConfigMock; /** - * @throws Exception + * @throws \Exception */ public function setUp(): void { diff --git a/tests/src/Services/SessionMessagesServiceTest.php b/tests/src/Services/SessionMessagesServiceTest.php index b8113bb1..852eb47a 100644 --- a/tests/src/Services/SessionMessagesServiceTest.php +++ b/tests/src/Services/SessionMessagesServiceTest.php @@ -4,10 +4,9 @@ namespace SimpleSAML\Test\Module\oidc\Services; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; -use SimpleSAML\Module\oidc\Services\SessionMessagesService; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\oidc\Services\SessionMessagesService; use SimpleSAML\Session; /** @@ -18,7 +17,7 @@ class SessionMessagesServiceTest extends TestCase protected MockObject $sessionMock; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { diff --git a/tests/src/Services/SessionServiceTest.php b/tests/src/Services/SessionServiceTest.php index b56e6510..eab38aee 100644 --- a/tests/src/Services/SessionServiceTest.php +++ b/tests/src/Services/SessionServiceTest.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Test\Module\oidc\Services; -use SimpleSAML\Module\oidc\Services\SessionService; use PHPUnit\Framework\TestCase; /** diff --git a/tests/src/Stores/Session/LogoutTicketStoreBuilderTest.php b/tests/src/Stores/Session/LogoutTicketStoreBuilderTest.php index 32e71202..e56bc6d1 100644 --- a/tests/src/Stores/Session/LogoutTicketStoreBuilderTest.php +++ b/tests/src/Stores/Session/LogoutTicketStoreBuilderTest.php @@ -4,9 +4,9 @@ namespace SimpleSAML\Test\Module\oidc\Stores\Session; -use SimpleSAML\Module\oidc\Stores\Session\LogoutTicketStoreBuilder; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; +use SimpleSAML\Module\oidc\Stores\Session\LogoutTicketStoreBuilder; use SimpleSAML\Module\oidc\Stores\Session\LogoutTicketStoreInterface; /** diff --git a/tests/src/Stores/Session/LogoutTicketStoreDbTest.php b/tests/src/Stores/Session/LogoutTicketStoreDbTest.php index ddfc9537..94e38bdb 100644 --- a/tests/src/Stores/Session/LogoutTicketStoreDbTest.php +++ b/tests/src/Stores/Session/LogoutTicketStoreDbTest.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Test\Module\oidc\Stores\Session; -use Exception; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; use SimpleSAML\Module\oidc\Services\DatabaseMigration; @@ -31,7 +30,7 @@ public static function setUpBeforeClass(): void } /** - * @throws Exception + * @throws \Exception */ public function testCanAddAndDeleteTickets(): void { @@ -48,7 +47,7 @@ public function testCanAddAndDeleteTickets(): void } /** - * @throws Exception + * @throws \Exception */ public function testCanDeleteMultipleTickets(): void { @@ -77,7 +76,7 @@ public function testCanDeleteMultipleTickets(): void } /** - * @throws Exception + * @throws \Exception */ public function testCanDeleteExpiredTickets(): void { diff --git a/tests/src/Utils/Checker/RequestRulesManagerTest.php b/tests/src/Utils/Checker/RequestRulesManagerTest.php index 374bc6f2..b45b7bbc 100644 --- a/tests/src/Utils/Checker/RequestRulesManagerTest.php +++ b/tests/src/Utils/Checker/RequestRulesManagerTest.php @@ -5,11 +5,9 @@ namespace SimpleSAML\Test\Module\oidc\Utils\Checker; use LogicException; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\RequestRuleInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; @@ -30,7 +28,7 @@ class RequestRulesManagerTest extends TestCase /** - * @throws Exception + * @throws \Exception */ public function setUp(): void { @@ -55,7 +53,7 @@ public function testConstructWithoutRules(): RequestRulesManager } /** - * @throws Exception + * @throws \Exception */ public function testConstructWithRules(): void { @@ -66,7 +64,7 @@ public function testConstructWithRules(): void /** * @depends testConstructWithoutRules * - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testAddAndCheck(RequestRulesManager $requestRulesManager): void { @@ -81,7 +79,7 @@ public function testAddAndCheck(RequestRulesManager $requestRulesManager): void /** * @depends testConstructWithoutRules * - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckWithNonExistingRuleKeyThrows(RequestRulesManager $requestRulesManager): void { @@ -92,7 +90,7 @@ public function testCheckWithNonExistingRuleKeyThrows(RequestRulesManager $reque /** * @depends testConstructWithoutRules * - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testPredefineResult(RequestRulesManager $requestRulesManager): void { @@ -106,8 +104,8 @@ public function testPredefineResult(RequestRulesManager $requestRulesManager): v /** * @depends testConstructWithoutRules * - * @throws OidcServerException - * @throws Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function testSetData(RequestRulesManager $requestRulesManager): void { diff --git a/tests/src/Utils/Checker/ResultTest.php b/tests/src/Utils/Checker/ResultTest.php index 80c67cb3..29daad64 100644 --- a/tests/src/Utils/Checker/ResultTest.php +++ b/tests/src/Utils/Checker/ResultTest.php @@ -31,7 +31,6 @@ public function testConstructWithoutValue(): void /** * @depends testConstruct - * */ public function testGetKey(Result $result): void { diff --git a/tests/src/Utils/Checker/Rules/AcrValuesRuleTest.php b/tests/src/Utils/Checker/Rules/AcrValuesRuleTest.php index 2a05ec07..0e769b45 100644 --- a/tests/src/Utils/Checker/Rules/AcrValuesRuleTest.php +++ b/tests/src/Utils/Checker/Rules/AcrValuesRuleTest.php @@ -4,16 +4,14 @@ namespace SimpleSAML\Test\Module\oidc\Utils\Checker\Rules; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; use SimpleSAML\Module\oidc\Utils\Checker\Rules\AcrValuesRule; -use PHPUnit\Framework\TestCase; /** * @covers \SimpleSAML\Module\oidc\Utils\Checker\Rules\AcrValuesRule @@ -26,7 +24,7 @@ class AcrValuesRuleTest extends TestCase protected Stub $loggerServiceStub; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -37,7 +35,7 @@ protected function setUp(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testNoAcrIsSetIfAcrValuesNotRequested(): void { @@ -50,7 +48,7 @@ public function testNoAcrIsSetIfAcrValuesNotRequested(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testPopulatesAcrValuesFromClaimsParameter(): void { @@ -69,7 +67,7 @@ public function testPopulatesAcrValuesFromClaimsParameter(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testPopulatesAcrValuesFromAcrValuesRequestParameter(): void { diff --git a/tests/src/Utils/Checker/Rules/AddClaimsToIdTokenRuleTest.php b/tests/src/Utils/Checker/Rules/AddClaimsToIdTokenRuleTest.php index 54abab16..606fb56b 100644 --- a/tests/src/Utils/Checker/Rules/AddClaimsToIdTokenRuleTest.php +++ b/tests/src/Utils/Checker/Rules/AddClaimsToIdTokenRuleTest.php @@ -4,18 +4,15 @@ namespace SimpleSAML\Test\Module\oidc\Utils\Checker\Rules; -use PHPUnit\Framework\MockObject\Exception; -use PHPUnit\Framework\MockObject\Stub; use LogicException; +use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Checker\Result; use SimpleSAML\Module\oidc\Utils\Checker\ResultBag; use SimpleSAML\Module\oidc\Utils\Checker\Rules\AddClaimsToIdTokenRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\ResponseTypeRule; -use Throwable; /** * @covers \SimpleSAML\Module\oidc\Utils\Checker\Rules\AddClaimsToIdTokenRule @@ -49,7 +46,7 @@ class AddClaimsToIdTokenRuleTest extends TestCase private Stub $loggerServiceStub; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -61,7 +58,7 @@ protected function setUp(): void /** * @dataProvider validResponseTypeProvider - * @throws Throwable + * @throws \Throwable */ public function testAddClaimsToIdTokenRuleTest($responseType) { @@ -82,7 +79,7 @@ public static function validResponseTypeProvider(): array /** * @dataProvider invalidResponseTypeProvider - * @throws Throwable + * @throws \Throwable */ public function testDoNotAddClaimsToIdTokenRuleTest($responseType) { @@ -108,8 +105,8 @@ public static function invalidResponseTypeProvider(): array } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testAddClaimsToIdTokenRuleThrowsWithNoResponseTypeParamTest() { diff --git a/tests/src/Utils/Checker/Rules/ClientIdRuleTest.php b/tests/src/Utils/Checker/Rules/ClientIdRuleTest.php index 9cc47332..6235ffea 100644 --- a/tests/src/Utils/Checker/Rules/ClientIdRuleTest.php +++ b/tests/src/Utils/Checker/Rules/ClientIdRuleTest.php @@ -5,7 +5,6 @@ namespace SimpleSAML\Test\Module\oidc\Utils\Checker\Rules; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; @@ -27,7 +26,7 @@ class ClientIdRuleTest extends TestCase protected Stub $loggerServiceStub; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -66,8 +65,8 @@ public function testCheckRuleInvalidClientThrows(): void } /** - * @throws OidcServerException - * @throws Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function testCheckRuleForValidClientId(): void { diff --git a/tests/src/Utils/Checker/Rules/CodeChallengeMethodRuleTest.php b/tests/src/Utils/Checker/Rules/CodeChallengeMethodRuleTest.php index c1059028..28372799 100644 --- a/tests/src/Utils/Checker/Rules/CodeChallengeMethodRuleTest.php +++ b/tests/src/Utils/Checker/Rules/CodeChallengeMethodRuleTest.php @@ -5,7 +5,6 @@ namespace SimpleSAML\Test\Module\oidc\Utils\Checker\Rules; use LogicException; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; @@ -19,7 +18,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\Rules\CodeChallengeMethodRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\RedirectUriRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\StateRule; -use Throwable; /** * @covers \SimpleSAML\Module\oidc\Utils\Checker\Rules\CodeChallengeMethodRule @@ -34,7 +32,7 @@ class CodeChallengeMethodRuleTest extends TestCase protected Stub $loggerServiceStub; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -47,8 +45,8 @@ protected function setUp(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleRedirectUriDependency(): void { @@ -58,8 +56,8 @@ public function testCheckRuleRedirectUriDependency(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleStateDependency(): void { @@ -70,7 +68,7 @@ public function testCheckRuleStateDependency(): void } /** - * @throws Throwable + * @throws \Throwable */ public function testCheckRuleWithInvalidCodeChallengeMethodThrows(): void { @@ -81,8 +79,8 @@ public function testCheckRuleWithInvalidCodeChallengeMethodThrows(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleForValidCodeChallengeMethod(): void { diff --git a/tests/src/Utils/Checker/Rules/CodeChallengeRuleTest.php b/tests/src/Utils/Checker/Rules/CodeChallengeRuleTest.php index 4ac0249a..29a2c3bf 100644 --- a/tests/src/Utils/Checker/Rules/CodeChallengeRuleTest.php +++ b/tests/src/Utils/Checker/Rules/CodeChallengeRuleTest.php @@ -5,7 +5,6 @@ namespace SimpleSAML\Test\Module\oidc\Utils\Checker\Rules; use LogicException; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; @@ -18,7 +17,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\Rules\CodeChallengeRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\RedirectUriRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\StateRule; -use Throwable; /** * @covers \SimpleSAML\Module\oidc\Utils\Checker\Rules\CodeChallengeRule @@ -35,7 +33,7 @@ class CodeChallengeRuleTest extends TestCase protected Stub $loggerServiceStub; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -48,8 +46,8 @@ protected function setUp(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleRedirectUriDependency(): void { @@ -59,8 +57,8 @@ public function testCheckRuleRedirectUriDependency(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleStateDependency(): void { @@ -71,7 +69,7 @@ public function testCheckRuleStateDependency(): void } /** - * @throws Throwable + * @throws \Throwable */ public function testCheckRuleNoCodeChallengeThrows(): void { @@ -82,7 +80,7 @@ public function testCheckRuleNoCodeChallengeThrows(): void } /** - * @throws Throwable + * @throws \Throwable */ public function testCheckRuleInvalidCodeChallengeThrows(): void { @@ -93,8 +91,8 @@ public function testCheckRuleInvalidCodeChallengeThrows(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleForValidCodeChallenge(): void { diff --git a/tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php b/tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php index 45180c6a..dc38dccd 100644 --- a/tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php +++ b/tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php @@ -9,14 +9,11 @@ use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\UnencryptedToken; use League\OAuth2\Server\CryptKey; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; -use ReflectionException; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Factories\CryptKeyFactory; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; @@ -54,8 +51,8 @@ public static function setUpBeforeClass(): void } /** - * @throws ReflectionException - * @throws Exception + * @throws \ReflectionException + * @throws \Exception */ protected function setUp(): void { @@ -89,8 +86,8 @@ public function testConstruct(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleIsNullWhenParamNotSet(): void { @@ -106,7 +103,7 @@ public function testCheckRuleIsNullWhenParamNotSet(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleThrowsForMalformedIdToken(): void { @@ -118,7 +115,7 @@ public function testCheckRuleThrowsForMalformedIdToken(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleThrowsForIdTokenWithInvalidSignature(): void { @@ -137,8 +134,8 @@ public function testCheckRuleThrowsForIdTokenWithInvalidSignature(): void } /** - * @throws ReflectionException - * @throws OidcServerException + * @throws \ReflectionException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleThrowsForIdTokenWithInvalidIssuer(): void { @@ -156,9 +153,9 @@ public function testCheckRuleThrowsForIdTokenWithInvalidIssuer(): void } /** - * @throws ReflectionException - * @throws Throwable - * @throws OidcServerException + * @throws \ReflectionException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRulePassesForValidIdToken(): void { diff --git a/tests/src/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php b/tests/src/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php index a0591f47..6c7a8c84 100644 --- a/tests/src/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php +++ b/tests/src/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php @@ -8,19 +8,17 @@ use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Rsa\Sha256; use League\OAuth2\Server\CryptKey; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\ClientRepository; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; use SimpleSAML\Module\oidc\Utils\Checker\Rules\IdTokenHintRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\PostLogoutRedirectUriRule; -use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Utils\Checker\Rules\StateRule; use Throwable; @@ -56,7 +54,7 @@ public static function setUpBeforeClass(): void } /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -76,8 +74,8 @@ protected function setUp(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleReturnsNullIfNoParamSet(): void { @@ -89,7 +87,7 @@ public function testCheckRuleReturnsNullIfNoParamSet(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleThrowsWhenIdTokenHintNotAvailable(): void { @@ -104,7 +102,7 @@ public function testCheckRuleThrowsWhenIdTokenHintNotAvailable(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleThrowsWhenAudClaimNotValid(): void { @@ -131,7 +129,7 @@ public function testCheckRuleThrowsWhenAudClaimNotValid(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleThrowsWhenClientNotFound(): void { @@ -161,7 +159,7 @@ public function testCheckRuleThrowsWhenClientNotFound(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleThrowsWhenPostLogoutRegisteredUriNotRegistered(): void { @@ -195,8 +193,8 @@ public function testCheckRuleThrowsWhenPostLogoutRegisteredUriNotRegistered(): v } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleReturnsForRegisteredPostLogoutRedirectUri(): void { diff --git a/tests/src/Utils/Checker/Rules/RedirectUriRuleTest.php b/tests/src/Utils/Checker/Rules/RedirectUriRuleTest.php index 34a94190..2e84fc53 100644 --- a/tests/src/Utils/Checker/Rules/RedirectUriRuleTest.php +++ b/tests/src/Utils/Checker/Rules/RedirectUriRuleTest.php @@ -5,7 +5,6 @@ namespace SimpleSAML\Test\Module\oidc\Utils\Checker\Rules; use LogicException; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; @@ -17,7 +16,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\ResultBag; use SimpleSAML\Module\oidc\Utils\Checker\Rules\ClientIdRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\RedirectUriRule; -use Throwable; /** * @covers \SimpleSAML\Module\oidc\Utils\Checker\Rules\RedirectUriRule @@ -33,7 +31,7 @@ class RedirectUriRuleTest extends TestCase /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -45,8 +43,8 @@ protected function setUp(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleClientIdDependancy(): void { @@ -55,8 +53,8 @@ public function testCheckRuleClientIdDependancy(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleWithInvalidClientDependancy(): void { @@ -66,7 +64,7 @@ public function testCheckRuleWithInvalidClientDependancy(): void } /** - * @throws Throwable + * @throws \Throwable */ public function testCheckRuleRedirectUriNotSetThrows(): void { @@ -78,7 +76,7 @@ public function testCheckRuleRedirectUriNotSetThrows(): void } /** - * @throws Throwable + * @throws \Throwable */ public function testCheckRuleDifferentClientRedirectUriThrows(): void { @@ -90,7 +88,7 @@ public function testCheckRuleDifferentClientRedirectUriThrows(): void } /** - * @throws Throwable + * @throws \Throwable */ public function testCheckRuleDifferentClientRedirectUriArrayThrows(): void { @@ -104,8 +102,8 @@ public function testCheckRuleDifferentClientRedirectUriArrayThrows(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleWithValidRedirectUri(): void { diff --git a/tests/src/Utils/Checker/Rules/RequestedClaimsRuleTest.php b/tests/src/Utils/Checker/Rules/RequestedClaimsRuleTest.php index 681c8072..c692706b 100644 --- a/tests/src/Utils/Checker/Rules/RequestedClaimsRuleTest.php +++ b/tests/src/Utils/Checker/Rules/RequestedClaimsRuleTest.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Test\Module\oidc\Utils\Checker\Rules; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; @@ -15,7 +14,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\Rules\ClientIdRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\RequestedClaimsRule; use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; -use Throwable; /** * @covers \SimpleSAML\Module\oidc\Utils\Checker\Rules\RequestedClaimsRule @@ -31,7 +29,7 @@ class RequestedClaimsRuleTest extends TestCase /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -44,7 +42,7 @@ protected function setUp(): void } /** - * @throws Throwable + * @throws \Throwable */ public function testNoRequestedClaims(): void { @@ -54,7 +52,7 @@ public function testNoRequestedClaims(): void } /** - * @throws Throwable + * @throws \Throwable */ public function testWithClaims(): void { @@ -92,7 +90,7 @@ public function testWithClaims(): void /** - * @throws Throwable + * @throws \Throwable */ public function testOnlyWithNonStandardClaimRequest(): void { diff --git a/tests/src/Utils/Checker/Rules/RequiredNonceRuleTest.php b/tests/src/Utils/Checker/Rules/RequiredNonceRuleTest.php index 8b117832..ac1a9b6d 100644 --- a/tests/src/Utils/Checker/Rules/RequiredNonceRuleTest.php +++ b/tests/src/Utils/Checker/Rules/RequiredNonceRuleTest.php @@ -5,7 +5,6 @@ namespace SimpleSAML\Test\Module\oidc\Utils\Checker\Rules; use LogicException; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; @@ -16,7 +15,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\Rules\RedirectUriRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\RequiredNonceRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\StateRule; -use Throwable; /** * @covers \SimpleSAML\Module\oidc\Utils\Checker\Rules\RequiredNonceRule @@ -37,7 +35,7 @@ class RequiredNonceRuleTest extends TestCase protected Stub $loggerServiceStub; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -53,8 +51,8 @@ protected function setUp(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleRedirectUriDependency(): void { @@ -65,8 +63,8 @@ public function testCheckRuleRedirectUriDependency(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleStateDependency(): void { @@ -78,8 +76,8 @@ public function testCheckRuleStateDependency(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRulePassesWhenNonceIsPresent() { @@ -94,7 +92,7 @@ public function testCheckRulePassesWhenNonceIsPresent() } /** - * @throws Throwable + * @throws \Throwable */ public function testCheckRuleThrowsWhenNonceIsNotPresent() { diff --git a/tests/src/Utils/Checker/Rules/RequiredOpenIdScopeRuleTest.php b/tests/src/Utils/Checker/Rules/RequiredOpenIdScopeRuleTest.php index 22a56b26..9374b89b 100644 --- a/tests/src/Utils/Checker/Rules/RequiredOpenIdScopeRuleTest.php +++ b/tests/src/Utils/Checker/Rules/RequiredOpenIdScopeRuleTest.php @@ -5,7 +5,6 @@ namespace SimpleSAML\Test\Module\oidc\Utils\Checker\Rules; use LogicException; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; @@ -18,7 +17,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\Rules\RequiredOpenIdScopeRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\ScopeRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\StateRule; -use Throwable; /** * @covers \SimpleSAML\Module\oidc\Utils\Checker\Rules\RequiredOpenIdScopeRule @@ -36,7 +34,7 @@ class RequiredOpenIdScopeRuleTest extends TestCase protected Stub $loggerServiceStub; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -52,8 +50,8 @@ protected function setUp(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleRedirectUriDependency(): void { @@ -64,8 +62,8 @@ public function testCheckRuleRedirectUriDependency(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleStateDependency(): void { @@ -77,8 +75,8 @@ public function testCheckRuleStateDependency(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRulePassesWhenOpenIdScopeIsPresent() { @@ -95,7 +93,7 @@ public function testCheckRulePassesWhenOpenIdScopeIsPresent() } /** - * @throws Throwable + * @throws \Throwable */ public function testCheckRuleThrowsWhenOpenIdScopeIsNotPresent() { diff --git a/tests/src/Utils/Checker/Rules/ResponseTypeRuleTest.php b/tests/src/Utils/Checker/Rules/ResponseTypeRuleTest.php index 9838ac13..f0180270 100644 --- a/tests/src/Utils/Checker/Rules/ResponseTypeRuleTest.php +++ b/tests/src/Utils/Checker/Rules/ResponseTypeRuleTest.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Test\Module\oidc\Utils\Checker\Rules; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; @@ -41,14 +40,14 @@ class ResponseTypeRuleTest extends TestCase ]; /** - * @var ResultBag + * @var \SimpleSAML\Module\oidc\Utils\Checker\ResultBag */ private ResultBag $resultBag; protected Stub $loggerServiceStub; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -60,7 +59,7 @@ protected function setUp(): void /** * @dataProvider validResponseTypeProvider - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testResponseTypeRuleTest($responseType) { diff --git a/tests/src/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php b/tests/src/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php index b4a55ebe..64a8a7f5 100644 --- a/tests/src/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php +++ b/tests/src/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php @@ -4,21 +4,19 @@ namespace SimpleSAML\Test\Module\oidc\Utils\Checker\Rules; -use PHPUnit\Framework\MockObject\Exception; -use PHPUnit\Framework\MockObject\Stub; -use PHPUnit\Framework\MockObject\MockObject; use League\OAuth2\Server\Entities\ScopeEntityInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Configuration; -use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; +use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Utils\Checker\Rules\ScopeOfflineAccessRule; -use Throwable; /** * @covers \SimpleSAML\Module\oidc\Utils\Checker\Rules\ScopeOfflineAccessRule @@ -39,7 +37,7 @@ class ScopeOfflineAccessRuleTest extends TestCase protected Stub $openIdConfigurationStub; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -75,8 +73,8 @@ public function testCanCreateInstance(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testReturnsFalseWhenOfflineAccessScopeNotPresent(): void { @@ -105,7 +103,7 @@ public function testReturnsFalseWhenOfflineAccessScopeNotPresent(): void } /** - * @throws Throwable + * @throws \Throwable */ public function testThrowsWhenClientDoesntHaveOfflineAccessScopeRegistered(): void { @@ -134,8 +132,8 @@ public function testThrowsWhenClientDoesntHaveOfflineAccessScopeRegistered(): vo } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testReturnsTrueWhenClientDoesHaveOfflineAccessScopeRegistered(): void { diff --git a/tests/src/Utils/Checker/Rules/ScopeRuleTest.php b/tests/src/Utils/Checker/Rules/ScopeRuleTest.php index 25d5c699..6f70e429 100644 --- a/tests/src/Utils/Checker/Rules/ScopeRuleTest.php +++ b/tests/src/Utils/Checker/Rules/ScopeRuleTest.php @@ -7,7 +7,6 @@ use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use LogicException; use PHPUnit\Framework\MockObject\Builder\InvocationStubber; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; @@ -21,7 +20,6 @@ use SimpleSAML\Module\oidc\Utils\Checker\Rules\RedirectUriRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\ScopeRule; use SimpleSAML\Module\oidc\Utils\Checker\Rules\StateRule; -use Throwable; /** * @covers \SimpleSAML\Module\oidc\Utils\Checker\Rules\ScopeRule @@ -46,7 +44,7 @@ class ScopeRuleTest extends TestCase protected Stub $loggerServiceStub; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -68,8 +66,8 @@ public function testConstruct(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleRedirectUriDependency(): void { @@ -80,8 +78,8 @@ public function testCheckRuleRedirectUriDependency(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleStateDependency(): void { @@ -93,8 +91,8 @@ public function testCheckRuleStateDependency(): void } /** - * @throws Throwable - * @throws OidcServerException + * @throws \Throwable + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testValidScopes(): void { @@ -118,7 +116,7 @@ public function testValidScopes(): void } /** - * @throws Throwable + * @throws \Throwable */ public function testInvalidScopeThrows(): void { diff --git a/tests/src/Utils/Checker/Rules/StateRuleTest.php b/tests/src/Utils/Checker/Rules/StateRuleTest.php index dff253b8..39c491f7 100644 --- a/tests/src/Utils/Checker/Rules/StateRuleTest.php +++ b/tests/src/Utils/Checker/Rules/StateRuleTest.php @@ -4,11 +4,9 @@ namespace SimpleSAML\Test\Module\oidc\Utils\Checker\Rules; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Utils\Checker\ResultBag; @@ -23,7 +21,7 @@ class StateRuleTest extends TestCase protected Stub $loggerServiceStub; /** - * @throws Exception + * @throws \Exception */ public function setUp(): void { @@ -37,8 +35,8 @@ public function testGetKey(): void } /** - * @throws OidcServerException - * @throws Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function testCheckRuleGetMethod(): void { @@ -59,8 +57,8 @@ public function testCheckRuleGetMethod(): void } /** - * @throws OidcServerException - * @throws Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function testCheckRulePostMethod(): void { @@ -81,8 +79,8 @@ public function testCheckRulePostMethod(): void } /** - * @throws OidcServerException - * @throws Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function testCheckRuleReturnsNullWhenMethodNotAllowed(): void { @@ -102,8 +100,8 @@ public function testCheckRuleReturnsNullWhenMethodNotAllowed(): void } /** - * @throws OidcServerException - * @throws Exception + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \Exception */ public function testCheckRuleReturnsNullWhenMethodNotSupported(): void { diff --git a/tests/src/Utils/Checker/Rules/UiLocalesRuleTest.php b/tests/src/Utils/Checker/Rules/UiLocalesRuleTest.php index 9a5fe1da..74a27f72 100644 --- a/tests/src/Utils/Checker/Rules/UiLocalesRuleTest.php +++ b/tests/src/Utils/Checker/Rules/UiLocalesRuleTest.php @@ -4,15 +4,13 @@ namespace SimpleSAML\Test\Module\oidc\Utils\Checker\Rules; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Checker\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Utils\Checker\Result; use SimpleSAML\Module\oidc\Utils\Checker\Rules\UiLocalesRule; -use PHPUnit\Framework\TestCase; /** * @covers \SimpleSAML\Module\oidc\Utils\Checker\Rules\UiLocalesRule @@ -24,7 +22,7 @@ class UiLocalesRuleTest extends TestCase protected Stub $loggerServiceStub; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -36,7 +34,7 @@ protected function setUp(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleReturnsResultWhenParamSet() { @@ -50,7 +48,7 @@ public function testCheckRuleReturnsResultWhenParamSet() } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCheckRuleReturnsNullWhenParamNotSet() { diff --git a/tests/src/Utils/ClaimTranslatorExtractorTest.php b/tests/src/Utils/ClaimTranslatorExtractorTest.php index 1a2e9ab9..576d9649 100644 --- a/tests/src/Utils/ClaimTranslatorExtractorTest.php +++ b/tests/src/Utils/ClaimTranslatorExtractorTest.php @@ -6,7 +6,6 @@ use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Entities\ClaimSetEntity; -use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; use SimpleSAML\Utils\Attributes; @@ -19,7 +18,7 @@ class ClaimTranslatorExtractorTest extends TestCase /** * Test various type conversions work, including types in subobjects - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testTypeConversion(): void { @@ -114,7 +113,7 @@ public function testTypeConversion(): void /** * Test that the default translator configuration sets address correctly. - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testDefaultTypeConversion(): void { @@ -140,7 +139,7 @@ public function testDefaultTypeConversion(): void /** * Test we can set the non-string standard claims - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testStandardClaimTypesCanBeSet(): void { @@ -194,7 +193,7 @@ public function testStandardClaimTypesCanBeSet(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testInvalidTypeConversion(): void { @@ -212,7 +211,7 @@ public function testInvalidTypeConversion(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testExtractRequestClaimsUserInfo(): void { @@ -228,7 +227,7 @@ public function testExtractRequestClaimsUserInfo(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testExtractRequestClaimsIdToken(): void { diff --git a/tests/src/Utils/ScopeHelperTest.php b/tests/src/Utils/ScopeHelperTest.php index 83990170..6ed08398 100644 --- a/tests/src/Utils/ScopeHelperTest.php +++ b/tests/src/Utils/ScopeHelperTest.php @@ -5,7 +5,6 @@ namespace SimpleSAML\Test\Module\oidc\Utils; use League\OAuth2\Server\Entities\ScopeEntityInterface; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; @@ -21,7 +20,7 @@ class ScopeHelperTest extends TestCase protected array $scopeEntitiesArray; /** - * @throws Exception + * @throws \Exception */ protected function setUp(): void { @@ -36,7 +35,7 @@ protected function setUp(): void } /** - * @throws OidcServerException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCanCheckScopeExistence(): void { diff --git a/tests/src/Utils/UniqueIdentifierGeneratorTest.php b/tests/src/Utils/UniqueIdentifierGeneratorTest.php index 17c78b98..36afe30d 100644 --- a/tests/src/Utils/UniqueIdentifierGeneratorTest.php +++ b/tests/src/Utils/UniqueIdentifierGeneratorTest.php @@ -4,9 +4,8 @@ namespace SimpleSAML\Test\Module\oidc\Utils; -use League\OAuth2\Server\Exception\OAuthServerException; -use SimpleSAML\Module\oidc\Utils\UniqueIdentifierGenerator; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\oidc\Utils\UniqueIdentifierGenerator; /** * @covers \SimpleSAML\Module\oidc\Utils\UniqueIdentifierGenerator @@ -14,7 +13,7 @@ class UniqueIdentifierGeneratorTest extends TestCase { /** - * @throws OAuthServerException + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ public function testDifferentIdentifiersCanBeGenerated(): void { From c05c4c62bcbb764a78ff7c3ea9f00e7c84251aa8 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos <3846025+ioigoume@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:33:57 +0300 Subject: [PATCH 11/70] Improve display of public clients (#225) Move client State and Type to radio buttons Hide reset secret for public client --- locales/en/LC_MESSAGES/oidc.po | 18 ++++++-- locales/fr/LC_MESSAGES/oidc.po | 31 ++++++++++++- templates/clients/_form.twig | 83 ++++++++++++++++++++++++++-------- templates/clients/show.twig | 19 +++++--- 4 files changed, 122 insertions(+), 29 deletions(-) diff --git a/locales/en/LC_MESSAGES/oidc.po b/locales/en/LC_MESSAGES/oidc.po index e3ebe70f..ec4a6a11 100644 --- a/locales/en/LC_MESSAGES/oidc.po +++ b/locales/en/LC_MESSAGES/oidc.po @@ -130,13 +130,16 @@ msgstr "Old oauth2 module clients has been imported." msgid "{oidc:client:is_enabled}" msgstr "Activated" +msgid "{oidc:client:deactivated}" +msgstr "Deactivated" + msgid "{oidc:title}" msgstr "OpenID Connect Client Registry" -msgid "{oidc:client:is_confidential}" -msgstr "Confidential client" +msgid "{oidc:client:confidential}" +msgstr "Confidential" -msgid "{oidc:client:is_confidential_help}" +msgid "{oidc:client:confidential_help}" msgstr "" "Choose if client is confidential or public. Confidential clients are capable of maintaining the confidentiality " "of their credentials (e.g., client implemented on a secure server with restricted access to the client credentials), " @@ -146,6 +149,15 @@ msgstr "" "any other means. " "" +msgid "{oidc:client:public}" +msgstr "Public" + +msgid "{oidc:client:type}" +msgstr "Type" + +msgid "{oidc:client:client}" +msgstr "Client" + msgid "{oidc:client:state}" msgstr "State" diff --git a/locales/fr/LC_MESSAGES/oidc.po b/locales/fr/LC_MESSAGES/oidc.po index fc578f7e..d171b146 100644 --- a/locales/fr/LC_MESSAGES/oidc.po +++ b/locales/fr/LC_MESSAGES/oidc.po @@ -92,6 +92,9 @@ msgstr "Confirmer la suppression du client. Cette action est irréversible." msgid "{oidc:delete}" msgstr "Supprimer" +msgid "{oidc:edit}" +msgstr "Modifier" + #, fuzzy msgid "{oidc:client:added}" msgstr "Ajout du client réussi." @@ -160,4 +163,30 @@ msgstr "Les clients du module désuet oauth2 ont été importés." #, fuzzy msgid "{oidc:client:is_enabled}" -msgstr "Activé." +msgstr "Activé" + +msgid "{oidc:client:deactivated}" +msgstr "Désactivé" + +msgid "{oidc:client:confidential}" +msgstr "Confidentiel" + +msgid "{oidc:client:client}" +msgstr "Client" + +msgid "{oidc:client:type}" +msgstr "Taper" + +msgid "{oidc:client:confidential_help}" +msgstr "" +"Choisissez si le client est confidentiel ou public. Les clients confidentiels sont capables de maintenir la " +"confidentialité de leurs informations d'identification (par exemple, client implémenté sur un serveur sécurisé avec " +"un accès restreint aux informations d'identification du client), ou capable de sécuriser l'authentification du client " +"par d'autres moyens. Les clients publics sont incapables de maintenir le confidentialité de leurs informations " +"d'identification (par exemple, les clients s'exécutant sur le périphérique utilisé par le propriétaire de la " +"ressource, tel qu'un application native installée ou application basée sur un navigateur Web), et incapable de " +"sécuriser l'authentification du client via tout autre moyen." +"" + +msgid "{oidc:client:public_client}" +msgstr "Client public" \ No newline at end of file diff --git a/templates/clients/_form.twig b/templates/clients/_form.twig index 2e817729..afcc3d15 100644 --- a/templates/clients/_form.twig +++ b/templates/clients/_form.twig @@ -29,26 +29,64 @@
-
- - - + +
+
+
+ + +
+
+
+
+ + +
+
-
- - - - {{ '{oidc:client:is_confidential_help}'|trans }} + +
+
+
+ + +
+
+
+
+ + +
+
+ {{ '{oidc:client:confidential_help}'|trans }}
@@ -85,12 +123,14 @@ {{ '{oidc:client:post_logout_redirect_uri_help}'|trans }}
-
+ {% if form['is_confidential'].value == true %} +
{{ form['allowed_origin'].control | raw }} {{ '{oidc:client:allowed_origin_help}'|trans }}
+ {% endif %}
@@ -111,11 +151,18 @@ +{% endblock postload %} + +{% block oidcPostload %}{% endblock %} diff --git a/templates/clients.twig b/templates/clients.twig new file mode 100644 index 00000000..2de33798 --- /dev/null +++ b/templates/clients.twig @@ -0,0 +1,117 @@ +{% set subPageTitle = 'Client Registry'|trans %} + +{% extends "@oidc/base.twig" %} + +{% block oidcContent %} + +
+
+
+
+ + Reset +
+
+
+ +
+ +
+ {% if clients is empty %} +

+ {{ 'No clients registered.'|trans }} +

+ {% else %} +
+ + + + + + + {% for client in clients %} + + + + + {% endfor %} + +
+ + {{ client.name }} +
+ {{ client.description }} +
+ + {{ 'Registration:'|trans }} {{ client.registrationType.description }} | + {{ 'Created at:'|trans }} {{ client.createdAt ? client.createdAt|date() : 'n/a' }} | + {{ 'Updated at:'|trans }} {{ client.updatedAt ? client.updatedAt|date() : 'n/a' }} | + {{ 'Expires at:'|trans }} {{ client.expiresAt ? client.expiresAt|date() : 'never' }} + +
+
+
+ + + + + + + + +
+
+
+ +
+
+
+ + + + {% for i in range(1, numPages) %} + + {{ i }} + + {% endfor %} + + + +
+ + + +
+
+ {% endif %} + +{% endblock oidcContent -%} diff --git a/templates/clients/_form.twig b/templates/clients/_form.twig deleted file mode 100644 index 919be7a5..00000000 --- a/templates/clients/_form.twig +++ /dev/null @@ -1,466 +0,0 @@ -{% extends "@oidc/oidc_base.twig" %} - -{% block content %} -

{{ pagetitle }}

- - - - {{ form.render('begin') }} - {% if form.hasErrors %} -
-
    - {% for error in form.getErrors %} -
  • {{ error | trans }}
  • - {% endfor %} -
-
- {% endif %} - -
- - - {{ form['name'].control | raw }} -
- -
- - - {{ form['description'].control | raw }} -
- -
- -
-
-
- - -
-
-
-
- - -
-
-
-
- -
- -
-
-
- - -
-
-
-
- - -
-
-
- {{ '{oidc:client:confidential_help}'|trans }} -
- -
- - - {{ form['redirect_uri'].control | raw }} - {{ '{oidc:client:redirect_uri_help}'|trans }} -
- -
- - - {{ form['auth_source'].control | raw }} - {{ '{oidc:client:auth_source_help}'|trans }} -
- -
- - - {{ form['scopes'].control | raw }} -
- -
- - - {{ form['backchannel_logout_uri'].control | raw }} - {{ '{oidc:client:backchannel_logout_uri_help}'|trans }} -
- -
- - - {{ form['post_logout_redirect_uri'].control | raw }} - {{ '{oidc:client:post_logout_redirect_uri_help}'|trans }} -
- - {% if form['is_confidential'].value == true %} -
- - - {{ form['allowed_origin'].control | raw }} - {{ '{oidc:client:allowed_origin_help}'|trans }} -
- {% endif %} - -
- - - {{ form['signed_jwks_uri'].control | raw }} - - {% trans %}URL to a JWS document containing protocol public keys in JWKS format (claim 'keys'). Example: - https://example.org/signed-jwks{% endtrans %} - -
- -
- - - {{ form['jwks_uri'].control | raw }} - - {% trans %}URL to a JWKS document containing protocol public keys. Will be used if Signed JWKS URI - is not set. Example: https://example.org/jwks{% endtrans %} - -
- -
- - - {{ form['jwks'].control | raw }} - - {% trans %}JSON object (string) representing JWKS document containing protocol public keys. Note that - this should be different from Federation JWKS. Will be used if JWKS URI is not set. Example: - {"keys":[{"kty": "RSA","n": "...","e": "AQAB","kid": "pro123","use": "sig","alg": "RS256"}]}{% endtrans %} - -
- -

OpenID Federation related properties

- -
- -
-
-
- - -
-
-
-
- - -
-
-
- - {% trans %}Choose if the client is allowed to participate in federation context or not.{% endtrans %} - -
- -
- - - {{ form['entity_identifier'].control | raw }} - - {% trans %}A globally unique URI that is bound to the entity. URI must have https or http scheme and - host / domain. It can contain path, but no query, or fragment component.{% endtrans %} - -
- -
- - - {{ form['client_registration_types'].control | raw }} - - {% trans %}One or more values from the list. If not selected, falls back to 'automatic'.{% endtrans %} - -
- -
- - - {{ form['federation_jwks'].control | raw }} - - {% trans %}JSON object (string) representing federation JWKS. This can be used, for example, in - entity statements. Note that this should be different from Protocol JWKS. Example: - {"keys":[{"kty": "RSA","n": "...","e": "AQAB","kid": "fed123","use": "sig","alg": "RS256"}]}{% endtrans %} - -
- -
- -
- {% block action %}{% endblock %} - - - {{ '{oidc:return}'|trans }} - -
- -
- {{ form.render('end') }} -{% endblock %} - -{% block postload %} - {{ parent() }} - -{% endblock %} diff --git a/templates/clients/add.twig b/templates/clients/add.twig new file mode 100644 index 00000000..9b9d4b4f --- /dev/null +++ b/templates/clients/add.twig @@ -0,0 +1,23 @@ +{% set subPageTitle = 'Add Client'|trans %} + +{% extends "@oidc/base.twig" %} + +{% block oidcContent %} + +
+
+ +
+ +
+ + {% include "@oidc/clients/includes/form.twig" %} + +{% endblock oidcContent -%} diff --git a/templates/clients/delete.twig b/templates/clients/delete.twig deleted file mode 100644 index 05622129..00000000 --- a/templates/clients/delete.twig +++ /dev/null @@ -1,42 +0,0 @@ -{% extends "@oidc/oidc_base.twig" %} - -{% set pagetitle = 'Delete OpenID Connect Client' | trans %} - -{% block pre_breadcrump %} - / - {{ 'OpenID Connect Client Registry'|trans }} -{% endblock %} - -{% block content %} -

{{ pagetitle }}

- - - -
-
- {{ '{oidc:client:delete}'|trans }} -
-

{{ '{oidc:client:confirm_delete}'|trans }}

-
-
- -
- - -
-
- - -
-
- - - {{ '{oidc:return}'|trans }} - -
-
-{% endblock %} diff --git a/templates/clients/edit.twig b/templates/clients/edit.twig index c3dadb70..5ec1ce29 100644 --- a/templates/clients/edit.twig +++ b/templates/clients/edit.twig @@ -1,15 +1,29 @@ -{% extends "@oidc/clients/_form.twig" %} +{% set subPageTitle = 'Edit Client '|trans ~ originalClient.getIdentifier %} -{% set pagetitle = 'Edit new OpenID Connect Client' | trans %} +{% extends "@oidc/base.twig" %} -{% block pre_breadcrump %} - / - {{ 'OpenID Connect Client Registry'|trans }} -{% endblock %} +{% block oidcContent %} -{% block action %} -
- - {{ '{oidc:save}'|trans }} + -{% endblock %} + + {% include "@oidc/clients/includes/form.twig" %} + +{% endblock oidcContent -%} + +{% block postload %} + {{ parent() }} + + +{% endblock %} \ No newline at end of file diff --git a/templates/clients/includes/form.twig b/templates/clients/includes/form.twig new file mode 100644 index 00000000..1eb8fc97 --- /dev/null +++ b/templates/clients/includes/form.twig @@ -0,0 +1,196 @@ + +{% if form.hasErrors %} +
+
    + {% for error in form.getErrors %} +
  • {{ error | trans }}
  • + {% endfor %} +
+
+{% endif %} + +
+ + {{ form['_token_'].control | raw }} + +
+ + {{ form.name.control | raw }} + {% if form.name.hasErrors %} + {{ form.name.getError }} + {% endif %} + + + {{ form.description.control | raw }} + {% if form.description.hasErrors %} + {{ form.description.getError }} + {% endif %} + + + + + + + + + + {% trans %}Choose if client is confidential or public. Confidential clients are capable of maintaining the confidentiality of their credentials (e.g., client implemented on a secure server with restricted access to the client credentials), or capable of secure client authentication using other means. Public clients are incapable of maintaining the confidentiality of their credentials (e.g., clients executing on the device used by the resource owner, such as an installed native application or a web browser-based application), and incapable of secure client authentication via any other means.{% endtrans %} + + + + {{ form.redirect_uri.control | raw }} + + {% trans %}Allowed redirect URIs to which the authorization response will be sent. Must be a valid URI, one per line. Example: https://example.org/foo?bar=1{% endtrans %} + + {% if form.redirect_uri.hasErrors %} + {{ form.redirect_uri.getError }} + {% endif %} + + + {{ form.auth_source.control | raw }} + + {% trans %}Authentication source for this particular client. If no authentication source is selected, the default one from configuration file will be used.{% endtrans %} + + {% if form.auth_source.hasErrors %} + {{ form.auth_source.getError }} + {% endif %} + + + {{ form.scopes.control | raw }} + {% if form.scopes.hasErrors %} + {{ form.scopes.getError }} + {% endif %} + + + {{ form.backchannel_logout_uri.control | raw }} + + {% trans %}Enter if client supports Back-Channel Logout specification. When logout is initiated at the OpenID Provider, it will send a Logout Token to this URI in order to notify the client about that event. Must be a valid URI. Example: https://example.org/foo?bar=1{% endtrans %} + + {% if form.backchannel_logout_uri.hasErrors %} + {{ form.backchannel_logout_uri.getError }} + {% endif %} + + + {{ form.post_logout_redirect_uri.control | raw }} + + {% trans %}Allowed redirect URIs to use after client initiated logout. Must be a valid URI, one per line. Example: https://example.org/foo?bar=1{% endtrans %} + + {% if form.post_logout_redirect_uri.hasErrors %} + {{ form.post_logout_redirect_uri.getError }} + {% endif %} + + + {{ form.allowed_origin.control | raw }} + + {% trans %}URLs as allowed origins for CORS requests, for public clients running in browser. Must have http:// or https:// scheme, and at least one 'domain.top-level-domain' pair, or more subdomains. Top-level-domain may end with '.'. No userinfo, path, query or fragment components allowed. May end with port number. One per line. Example: https://example.org{% endtrans %} + + {% if form.allowed_origin.hasErrors %} + {{ form.allowed_origin.getError }} + {% endif %} + + + {{ form.signed_jwks_uri.control | raw }} + + {% trans %}URL to a JWS document containing protocol public keys in JWKS format (claim 'keys'). Example: https://example.org/signed-jwks{% endtrans %} + + {% if form.signed_jwks_uri.hasErrors %} + {{ form.signed_jwks_uri.getError }} + {% endif %} + + + {{ form.jwks_uri.control | raw }} + + {% trans %}URL to a JWKS document containing protocol public keys. Will be used if Signed JWKS URI is not set. Example: https://example.org/jwks{% endtrans %} + + {% if form.jwks_uri.hasErrors %} + {{ form.jwks_uri.getError }} + {% endif %} + + + {{ form.jwks.control | raw }} + + {% trans %}JSON object (string) representing JWKS document containing protocol public keys. Note that this should be different from Federation JWKS. Will be used if JWKS URI is not set. Example: {"keys":[{"kty": "RSA","n": "...","e": "AQAB","kid": "pro123","use": "sig","alg": "RS256"}]}{% endtrans %} + + {% if form.jwks.hasErrors %} + {{ form.jwks.getError }} + {% endif %} + +
+

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

+ + + + + + {% trans %}Choose if the client is allowed to participate in federation context or not.{% endtrans %} + + + + {{ form.entity_identifier.control | raw }} + + {% trans %}A globally unique URI that is bound to the entity. URI must have https or http scheme and host / domain. It can contain path, but no query, or fragment component.{% endtrans %} + + {% if form.entity_identifier.hasErrors %} + {{ form.entity_identifier.getError }} + {% endif %} + + + {{ form.client_registration_types.control | raw }} + + {% trans %}One or more values from the list. If not selected, falls back to 'automatic'{% endtrans %} + + {% if form.client_registration_types.hasErrors %} + {{ form.client_registration_types.getError }} + {% endif %} + + + {{ form.federation_jwks.control | raw }} + + {% trans %}JSON object (string) representing federation JWKS. This can be used, for example, in entity statements. Note that this should be different from Protocol JWKS. Example: {"keys":[{"kty": "RSA","n": "...","e": "AQAB","kid": "fed123","use": "sig","alg": "RS256"}]}{% endtrans %} + + {% if form.federation_jwks.hasErrors %} + {{ form.federation_jwks.getError }} + {% endif %} + +
+ +
+
diff --git a/templates/clients/index.twig b/templates/clients/index.twig deleted file mode 100644 index 5b31d4be..00000000 --- a/templates/clients/index.twig +++ /dev/null @@ -1,104 +0,0 @@ -{% extends "@oidc/oidc_base.twig" %} - -{% set pagetitle = 'OpenID Connect Client Registry' | trans %} - -{% block content %} -

{{ pagetitle }}

- - - - - - - - - - - - - {% for client in clients %} - - - - - {% else %} - - - - {% endfor %} - - - - - - -
- - {{ '{oidc:client_list}'|trans }} - - - - {{ '{oidc:add_client}'|trans }} - -
-
- {% set state = client.enabled ? "green" : "red" %} - - {{ client.name }} -
- {{ client.description }} -
-
- {{ 'Registration:'|trans }} {{ client.registrationType.description }} | - {{ 'Created at:'|trans }} {{ client.createdAt ? client.createdAt|date() : 'n/a' }} | - {{ 'Updated at:'|trans }} {{ client.updatedAt ? client.updatedAt|date() : 'n/a' }} | - {{ 'Expires at:'|trans }} {{ client.expiresAt ? client.expiresAt|date() : 'never' }} -
-
-
- -
-
- {{ '{oidc:no_clients}'|trans }} -
-
- -
-{% endblock %} diff --git a/templates/clients/new.twig b/templates/clients/new.twig deleted file mode 100644 index 39e30279..00000000 --- a/templates/clients/new.twig +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "@oidc/clients/_form.twig" %} - -{% set pagetitle = 'Create new OpenID Connect Client' | trans %} - -{% block pre_breadcrump %} - / - {{ 'OpenID Connect Client Registry'|trans }} -{% endblock %} - -{% block action %} -
- - {{ '{oidc:create}'|trans }} -
-{% endblock %} diff --git a/templates/clients/show.twig b/templates/clients/show.twig index 3992a13f..525c8c1c 100644 --- a/templates/clients/show.twig +++ b/templates/clients/show.twig @@ -1,231 +1,274 @@ -{% extends "@oidc/oidc_base.twig" %} +{% set subPageTitle = 'Client '|trans ~ client.getIdentifier %} -{% set pagetitle = 'Show OpenID Connect Client' | trans %} +{% extends "@oidc/base.twig" %} -{% block pre_breadcrump %} - / - {{ 'OpenID Connect Client Registry'|trans }} -{% endblock %} +{% block oidcContent %} -{% block content %} -

{{ pagetitle }}

+
+
+ + + {{ client.enabled ? 'enabled'|trans : 'disabled'|trans }} + +
+
+
+
+ + + {{ 'Back'|trans }} + + + + {{ 'Edit'|trans }} + + + +
- -
+
+
+
+ +
{{ 'Registration:'|trans }} {{ client.registrationType.description }} | {{ 'Created at:'|trans }} {{ client.createdAt ? client.createdAt|date() : 'n/a' }} | {{ 'Updated at:'|trans }} {{ client.updatedAt ? client.updatedAt|date() : 'n/a' }} | {{ 'Expires at:'|trans }} {{ client.expiresAt ? client.expiresAt|date() : 'never' }}
- - - - - - - - - - - - - - - - - - - - - - - {% if client.isConfidential %} - - - - - {% endif %} - - - - - - - - - - - - - - - - - - - - - {% if client.isConfidential == false %} - - - - - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -{% endblock %} - -{% block postload %} - {{ parent() }} - - -{% endblock %} +
+

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

+
+
{{ '{oidc:client:name}'|trans }} - {{ client.name }} -
{{ '{oidc:client:description}'|trans }}{{ client.description }}
{{ '{oidc:client:state}'|trans }} - - {{ (client.isEnabled ? '{oidc:client:is_enabled}' : '{oidc:client:deactivated}')|trans }} - -
{{ '{oidc:client:type}'|trans }}{{ (client.isConfidential ? '{oidc:client:confidential}' : '{oidc:client:public}')|trans }}
{{ '{oidc:client:identifier}'|trans }}{{ client.identifier }} - -
{{ '{oidc:client:secret}'|trans }} - {{ client.secret }} - -
{{ '{oidc:client:auth_source}'|trans }}{{ client.authSourceId }}
{{ '{oidc:client:redirect_uri}'|trans }} -
    - {% for uri in client.redirectUri %} -
  • {{ uri }}
  • - {% endfor %} -
-
{{ '{oidc:client:scopes}'|trans }} -
    - {% for key, scope in client.scopes %} -
  • {{ scope }}
  • - {% endfor %} -
-
{{ '{oidc:client:backchannel_logout_uri}'|trans }}{{ client.backChannelLogoutUri }}
{{ '{oidc:client:post_logout_redirect_uri}'|trans }} -
    - {% for uri in client.postLogoutRedirectUri %} -
  • {{ uri }}
  • - {% endfor %} -
-
{{ '{oidc:client:allowed_origin}'|trans }} -
    - {% for allowedOrigin in allowedOrigins %} -
  • {{ allowedOrigin }}
  • - {% endfor %} -
-
{{ 'Signed JWKS URI'|trans }}{{ client.signedJwksUri }}
{{ 'JWKS URI'|trans }}{{ client.jwksUri }}
{{ 'JWKS'|trans }} - {% if client.jwks %} -
-
-
- {{ client.jwks|json_encode(constant('JSON_PRETTY_PRINT')) }} -
-
-
- {% endif %} -
{{ '{oidc:client:owner}'|trans }}{{ client.owner }}
OpenID Federation related properties
{{ 'Is federated'|trans }}{{ (client.isFederated ? 'Yes' : 'No')|trans }}
{{ 'Entity Identifier'|trans }}{{ client.entityIdentifier }}
{{ 'Client registration types'|trans }} -
    - {% for clientRegistrationType in client.clientRegistrationTypes %} -
  • {{ clientRegistrationType }}
  • - {% endfor %} -
-
{{ 'Federation JWKS'|trans }} - {% if client.federationJwks %} -
-
-
- {{ client.federationJwks|json_encode(constant('JSON_PRETTY_PRINT')) }} -
-
-
- {% endif %} -
-
- - {{ '{oidc:return}'|trans }} - - - {{ '{oidc:edit}'|trans }} - - {% if client.isConfidential %} -
- {{ '{oidc:client:reset_secret}'|trans }} -
- +
+
+ + + + + + + + + + + + + + + + + + + {% if client.isConfidential %} + + + + + {% endif %} + + + + + + + + + + + + + + + + + + + - -
+ {{ 'Name and description'|trans }} + + {{ client.name }}
+ {{ client.description }} +
+ {{ 'Type' }} + + {{ (client.isConfidential ? 'Confidential' : 'Public')|trans }} +
+ {{ 'Identifier'|trans }} + + {{ client.identifier }} +
+ {{ 'Secret'|trans }} + +
+ {{- client.secret -}} + + +
+
+ {{ 'Authentication Source'|trans }} + + {{ client.authSourceId|default('N/A'|trans) }} +
+ {{ 'Redirect URIs'|trans }} + +
    + {% for uri in client.redirectUri %} +
  • {{ uri }}
  • + {% endfor %} +
+
+ {{ 'Scopes'|trans }} + +
    + {% for key, scope in client.scopes %} +
  • {{ scope }}
  • + {% endfor %} +
+
+ {{ 'Back-channel Logout URI'|trans }} + + {{ client.backChannelLogoutUri|default('N/A') }} +
+ {{ 'Post-logout Redirect URIs'|trans }} + + {% if client.postLogoutRedirectUri is not empty %} +
    + {% for uri in client.postLogoutRedirectUri %} +
  • {{ uri }}
  • + {% endfor %} +
+ {% else %} + {{ 'N/A'|trans }} {% endif %} - - -
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+ {{ 'Is Federated'|trans }} + + {{ (client.isFederated ? 'Yes' : 'No')|trans }} +
+ {{ 'Entity Identifier'|trans }} + + {{ client.entityIdentifier|default('N/A'|trans) }} +
+ {{ 'Client Registration Types'|trans }} + +
    + {% for clientRegistrationType in client.clientRegistrationTypes %} +
  • {{ clientRegistrationType }}
  • + {% endfor %} +
+
+ {{ 'Federation JWKS'|trans }} + + {% if client.federationJwks %} + + {{- client.federationJwks|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}} + + {% else %} + {{ 'N/A'|trans }} + {% endif %} +
+
+{% endblock oidcContent -%} diff --git a/templates/config/federation.twig b/templates/config/federation.twig new file mode 100644 index 00000000..51a0d116 --- /dev/null +++ b/templates/config/federation.twig @@ -0,0 +1,119 @@ +{% set subPageTitle = 'Federation Settings'|trans %} + +{% extends "@oidc/base.twig" %} + +{% block oidcContent %} +

+ {{ 'Federation Enabled'|trans }}: + {{ moduleConfig.getFederationEnabled ? 'Yes'|trans : 'No'|trans }} +

+ +

{{ 'Entity'|trans }}

+

+ {{ 'Configuration URL'|trans }}: + {{ routes.urlFederationConfiguration }} +

+

+ {{ 'Issuer'|trans }}: {{ moduleConfig.getIssuer }} +
+ {{ 'Organization Name'|trans }}: {{ moduleConfig.getOrganizationName }} +
+ {{ 'Logo URI'|trans }}: + {{ moduleConfig.getLogoUri }} +
+ {{ 'Policy URI'|trans }}: + {{ moduleConfig.getPolicyUri }} +
+ {{ 'Homepage URI'|trans }}: + {{ moduleConfig.getHomepageUri }} +
+ {{ 'Contacts'|trans }}: + {% if moduleConfig.getContacts is not empty %} + {% for contact in moduleConfig.getContacts %} +
+ - {{ contact }} + {% endfor %} + {% else %} + {{ 'N/A'|trans }} + {% endif %} +

+

+ {{ 'Entity Statement Duration'|trans }}: + {{ moduleConfig.getFederationEntityStatementDuration|date("%mm %dd %hh %i' %s''") }} +

+ +

{{ 'PKI'|trans }}

+

+ {{ 'Private Key'|trans }}: {{ moduleConfig.getFederationPrivateKeyPath }} +
+ {{ 'Private Key Password Set'|trans }}: + {{ moduleConfig.getFederationPrivateKeyPassPhrase ? 'Yes'|trans : 'No'|trans }} +
+ {{ 'Public Key'|trans }}: {{ moduleConfig.getFederationCertPath }} +

+

+ {{ 'Signing Algorithm'|trans }}: {{ moduleConfig.getFederationSigner.algorithmId }} +

+ +

{{ 'Trust Anchors'|trans }}

+ {% if moduleConfig.getFederationTrustAnchors is not empty %} + {% for trustAnchorId, jwks in moduleConfig.getFederationTrustAnchors %} +

+ - {{ trustAnchorId }} +
+ {{ 'JWKS'|trans }}: + {% if jwks|default is not empty %} + + {{- jwks|json_encode(constant('JSON_PRETTY_PRINT')) -}} + + {% else %} + {{ 'N/A'|trans }} + {% endif %} +

+ {% endfor %} + {% else %} +

{{ 'N/A'|trans }}

+ {% endif %} + +

{{ 'Authority Hints'|trans }}

+

+ {% if moduleConfig.getFederationAuthorityHints|default is not empty %} + {% for authorityHint in moduleConfig.getFederationAuthorityHints %} + {% if not loop.first %} +
+ {% endif %} + - {{ authorityHint }} + {% endfor %} + {% else %} + {{ 'N/A'|trans }} + {% endif %} +

+ +

{{ 'Trust Marks'|trans }}

+ {% if trustMarks|default is not empty %} + {% for trustMark in trustMarks %} +

+ - {{ trustMark.getPayload.id }} + + {{- trustMark.getPayload|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}} + +

+ {% endfor %} + {% else %} +

{{ 'N/A'|trans }}

+ {% endif %} + +

{{ 'Cache'|trans }}

+

+ {{ 'Cache Adapter'|trans }}: + {{ moduleConfig.getFederationCacheAdapterClass|default('N/A'|trans) }} +
+ {{ 'Maximum Cache Duration For Fetched Artifacts'|trans }}: + {{ moduleConfig.getFederationCacheMaxDurationForFetched|date("%mm %dd %hh %i' %s''") }} +
+ {{ 'Cache Duration For Produced Artifacts'|trans }}: + {{ moduleConfig.getFederationEntityStatementCacheDurationForProduced|date("%mm %dd %hh %i' %s''") }} + +

+ +{% endblock oidcContent -%} diff --git a/templates/config/migrations.twig b/templates/config/migrations.twig new file mode 100644 index 00000000..007495b5 --- /dev/null +++ b/templates/config/migrations.twig @@ -0,0 +1,30 @@ +{% set subPageTitle = 'Database Migrations'|trans %} + +{% extends "@oidc/base.twig" %} + +{% block oidcContent %} + + {% if databaseMigration.isMigrated %} +

{{ 'All database migrations are implemented.'|trans }}

+ {% else %} +

+ + {% trans %}There are database migrations that have not been implemented. + Use the button below to run them now.{% endtrans %} +

+ +
+ + + +
+
+ {% endif %} + +
+ Before running the migrations, make sure that the database user has proper privileges to change the scheme + (for example, alter, create, drop, index). After running the migrations, it is a good practice to remove + those privileges. +
+ +{% endblock oidcContent -%} diff --git a/templates/config/protocol.twig b/templates/config/protocol.twig new file mode 100644 index 00000000..1c5c1a1c --- /dev/null +++ b/templates/config/protocol.twig @@ -0,0 +1,110 @@ +{% set subPageTitle = 'Protocol Settings'|trans %} + +{% extends "@oidc/base.twig" %} + +{% block oidcContent %} + +

{{ 'Entity'|trans }}

+

+ {{ 'Discovery URL'|trans }}: + {{ routes.urlConfiguration }} +

+

+ {{ 'Issuer'|trans }}: {{ moduleConfig.getIssuer }} +

+ +

{{ 'Tokens Time-To-Live (TTL)'|trans }}

+

+ {{ 'Authorization Code'|trans }}: + {{ moduleConfig.getAuthCodeDuration|date("%mm %dd %hh %i' %s''") }} +
+ {{ 'Access Token'|trans }}: + {{ moduleConfig.getAccessTokenDuration|date("%mm %dd %hh %i' %s''") }} +
+ {{ 'Refresh Token'|trans }}: + {{ moduleConfig.getRefreshTokenDuration|date("%mm %dd %hh %i' %s''") }} +

+ +

{{ 'PKI'|trans }}

+

+ {{ 'Private Key'|trans }}: {{ moduleConfig.getProtocolPrivateKeyPath }} +
+ {{ 'Private Key Password Set'|trans }}: + {{ moduleConfig.getProtocolPrivateKeyPassPhrase ? 'Yes'|trans : 'No'|trans }} +
+ {{ 'Public Key'|trans }}: {{ moduleConfig.getProtocolCertPath }} +

+

+ {{ 'Signing Algorithm'|trans }}: {{ moduleConfig.getProtocolSigner.algorithmId }} +

+ +

{{ 'Authentication'|trans }}

+

+ {{ 'Default Authentication Source'|trans }}: {{ moduleConfig.getDefaultAuthSourceId }} +
+ {{ 'User Identifier Attribute'|trans }}: {{ moduleConfig.getUserIdentifierAttribute }} +

+

+ {{ 'Authentication Processing Filters'|trans }}: + {% if moduleConfig.getAuthProcFilters is not empty %} + {% for authproc in moduleConfig.getAuthProcFilters %} +
+ - {{ authproc.class|default('[class-not-set]') }} + {% endfor %} + {% else %} + {{ 'N/A'|trans }} + {% endif %} +

+ +

{{ 'Authentication Context Class References (ACRs)'|trans }}

+

+ {{ 'Supported ACRs'|trans }}: + {% if moduleConfig.getAcrValuesSupported is not empty %} + {% for acr in moduleConfig.getAcrValuesSupported %} +
+ - {{ acr }} + {% endfor %} + {% else %} + {{ 'N/A'|trans }} + {% endif %} + +

+

+ {{ 'Authentication Sources to ACRs Map'|trans }}: + {% if moduleConfig.getAuthSourcesToAcrValuesMap is not empty %} + {% for authsource, acrs in moduleConfig.getAuthSourcesToAcrValuesMap %} +
+ - {{ authsource }}: + {% for acr in acrs %} + {{ acr }}{{ loop.last ? '' : ',' }} + {% endfor %} + {% endfor %} + {% else %} + {{ 'N/A'|trans }} + {% endif %} +

+

+ {{ 'Forced ACR For Cookie Authentication'|trans }}: + {{ moduleConfig.getForcedAcrValueForCookieAuthentication|default('N/A'|trans) }} +

+ + +

{{ 'Scopes'|trans }}

+

+ {% for scope, claims in moduleConfig.getScopes %} + {{ scope }}{{ loop.last ? '' : ', ' }} + {# TODO mivanci Add claims or extract scopes to sepparate page. #} + {% endfor %} +

+ +

{{ 'Cache'|trans }}

+

+ {{ 'Cache Adapter'|trans }}: + {{ moduleConfig.getProtocolCacheAdapterClass|default('N/A'|trans) }} +
+ {{ 'User Entity Cache Duration'|trans }}: + {{ moduleConfig.getProtocolUserEntityCacheDuration|date("%mm %dd %hh %i' %s''") }} +

+ + +{% endblock oidcContent -%} diff --git a/templates/includes/menu.twig b/templates/includes/menu.twig new file mode 100644 index 00000000..014fd0a0 --- /dev/null +++ b/templates/includes/menu.twig @@ -0,0 +1,15 @@ +{% if oidcMenu|default %} + + + +{% endif %} \ No newline at end of file diff --git a/templates/install.twig b/templates/install.twig deleted file mode 100644 index 4f1cc44b..00000000 --- a/templates/install.twig +++ /dev/null @@ -1,33 +0,0 @@ -{% extends "@oidc/oidc_base.twig" %} - -{% set pagetitle = 'Install OpenID Connect Module' | trans %} - -{% block content %} -

{{ pagetitle }}

- - - -
-
- {{ '{oidc:install}'|trans }} -
-

{{ '{oidc:install:description}'|trans }}

-
-
- {% if oauth2_enabled %} -
- - -
- {% endif %} - -
- - - {{ '{oidc:return}'|trans }} - -
-
-{% endblock %} diff --git a/templates/logout.twig b/templates/logout.twig index 024f634a..db712fb4 100644 --- a/templates/logout.twig +++ b/templates/logout.twig @@ -1,8 +1,8 @@ -{% extends "@oidc/oidc_base.twig" %} +{% extends "@oidc/base.twig" %} {% set pagetitle = 'Logout Info' | trans %} -{% block content %} +{% block oidcContent %}

{% if wasLogoutActionCalled %} {{ '{oidc:logout:page_title_success}'|trans }} @@ -11,12 +11,11 @@ {% endif %}

- -
-
+
+

{{ '{oidc:logout:info_title}'|trans }} -

+

{% if wasLogoutActionCalled %} {{ '{oidc:logout:info_message_success}'|trans }} diff --git a/templates/oidc_base.twig b/templates/oidc_base.twig deleted file mode 100644 index d38a9721..00000000 --- a/templates/oidc_base.twig +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - {{ pagetitle }} - - - - - - {% if isRTL %} - - {% endif %} - - {% block preload %}{% endblock %} - - - -

-
-
-
- {% if header == 'SimpleSAMLphp' %} - Simple{# -#} - SAML{# -#} - php - {% else %} - {{ header }} - {% endif %} -
-
-
- - - - {% if not hideLanguageBar %} - - {% endif %} - - - - {% block contentwrapper %} - - {% if messages is defined and messages is not empty %} -
- {% for message in messages %} -

{{ message|trans }}

- {% endfor %} -
- {% endif %} - - {% block content %}{% endblock %} - - {% endblock %} -
-
- {% block footer %}{% include "_footer.twig" %}{% endblock %} -
-
-
- -{% block postload %} - - - -{% endblock %} - diff --git a/tests/config/config.php b/tests/config/config.php index 0a500bdc..73a11e40 100644 --- a/tests/config/config.php +++ b/tests/config/config.php @@ -87,7 +87,7 @@ * qualified path to a file containing the certificate or key in PEM * format, such as 'cert.pem' or '/path/to/cert.pem'. If the path is * relative, it will be searched for in the directory defined by the - * '' parameter below. When 'certdir' is specified as a relative + * 'certdir' parameter below. When 'certdir' is specified as a relative * path, it will be interpreted as relative to the SimpleSAMLphp root * directory. Note that locations with no prefix included will be treated * as file locations. @@ -564,6 +564,7 @@ 'core' => true, 'admin' => true, 'saml' => true, + 'oidc' => true, ], @@ -630,6 +631,8 @@ * Set this to TRUE if the user only accesses your service * through https. If the user can access the service through * both http and https, this must be set to FALSE. + * + * If unset, SimpleSAMLphp will try to automatically determine the right value */ 'session.cookie.secure' => true, @@ -994,12 +997,6 @@ // Adopts language from attribute to use in UI 30 => 'core:LanguageAdaptor', - 45 => [ - 'class' => 'core:StatisticsWithAttribute', - 'attributename' => 'realm', - 'type' => 'saml20-idp-SSO', - ], - /* When called without parameters, it will fallback to filter attributes 'the old way' * by checking the 'attributes' parameter in metadata on IdP hosted and SP remote. */ diff --git a/tests/unit/src/Admin/AuthorizationTest.php b/tests/unit/src/Admin/AuthorizationTest.php new file mode 100644 index 00000000..ba2af7c3 --- /dev/null +++ b/tests/unit/src/Admin/AuthorizationTest.php @@ -0,0 +1,122 @@ +sspBridgeMock = $this->createMock(SspBridge::class); + $this->sspBridgeUtilsMock = $this->createMock(Utils::class); + $this->sspBridgeMock->method('utils')->willReturn($this->sspBridgeUtilsMock); + $this->sspBridgeUtilsAuthMock = $this->createMock(Auth::class); + $this->sspBridgeUtilsMock->method('auth')->willReturn($this->sspBridgeUtilsAuthMock); + + $this->authContextServiceMock = $this->createMock(AuthContextService::class); + } + + protected function sut( + ?SspBridge $sspBridge = null, + ?AuthContextService $authContextService = null, + ): Authorization { + $sspBridge ??= $this->sspBridgeMock; + $authContextService ??= $this->authContextServiceMock; + + return new Authorization($sspBridge, $authContextService); + } + + public function testCanCreateInstance(): void + { + $this->assertInstanceOf(Authorization::class, $this->sut()); + } + + public function testCanCheckIsAdmin(): void + { + $this->assertFalse($this->sut()->isAdmin()); + $this->sspBridgeUtilsAuthMock->method('isAdmin')->willReturn(true); + $this->assertTrue($this->sut()->isAdmin()); + } + + public function testCanRequireAdmin(): void + { + $this->expectException(AuthorizationException::class); + $this->expectExceptionMessage('admin'); + + $this->sspBridgeUtilsAuthMock->method('isAdmin')->willReturn(false); + + $this->sut()->requireAdmin(); + } + + public function testCanForceRequireAdmin(): void + { + $this->sspBridgeUtilsAuthMock->expects($this->once())->method('requireAdmin'); + $this->sspBridgeUtilsAuthMock->expects($this->once())->method('isAdmin')->willReturn(true); + + $this->sut()->requireAdmin(true); + } + + public function testThrowsOnForceRequireAdminError(): void + { + $this->sspBridgeUtilsAuthMock->expects($this->once())->method('requireAdmin') + ->willThrowException(new Exception('error')); + + $this->expectException(AuthorizationException::class); + $this->expectExceptionMessage('admin'); + + $this->sut()->requireAdmin(true); + } + + public function testRequireAdminOrUserWithPermissionReturnsIfAdmin(): void + { + $this->sspBridgeUtilsAuthMock->expects($this->once())->method('isAdmin')->willReturn(true); + $this->authContextServiceMock->expects($this->never())->method('requirePermission'); + + $this->sut()->requireAdminOrUserWithPermission('permission'); + } + + public function testRequireAdminOrUserWithPermissionReturnsIfUser(): void + { + $this->sspBridgeUtilsAuthMock->expects($this->once())->method('isAdmin')->willReturn(false); + $this->authContextServiceMock->expects($this->once())->method('requirePermission'); + + $this->sut()->requireAdminOrUserWithPermission('permission'); + } + + public function testRequireUserWithPermissionThrowsIfUserNotAuthorized(): void + { + $this->expectException(AuthorizationException::class); + $this->expectExceptionMessage('not authorized'); + + $this->sspBridgeUtilsAuthMock->expects($this->once())->method('isAdmin')->willReturn(false); + $this->authContextServiceMock->expects($this->once())->method('requirePermission') + ->willThrowException(new Exception('error')); + + $this->sut()->requireAdminOrUserWithPermission('permission'); + } + + public function testCanGetUserId(): void + { + $this->authContextServiceMock->expects($this->once())->method('getAuthUserId')->willReturn('id'); + + $this->assertSame('id', $this->sut()->getUserId()); + } +} diff --git a/tests/unit/src/Admin/Menu/ItemTest.php b/tests/unit/src/Admin/Menu/ItemTest.php new file mode 100644 index 00000000..941cae4f --- /dev/null +++ b/tests/unit/src/Admin/Menu/ItemTest.php @@ -0,0 +1,46 @@ +hrefPath = 'path'; + $this->label = 'label'; + $this->iconAssetPath = 'icon-path'; + } + + protected function sut( + ?string $hrefPath = null, + ?string $label = null, + ?string $iconAssetPath = null, + ): Item { + $hrefPath ??= $this->hrefPath; + $label ??= $this->label; + $iconAssetPath ??= $this->iconAssetPath; + + return new Item($hrefPath, $label, $iconAssetPath); + } + + public function testCanCreateInstance(): void + { + $sut = $this->sut(); + $this->assertInstanceOf(Item::class, $sut); + + $this->assertSame($sut->getHrefPath(), $this->hrefPath); + $this->assertSame($sut->getLabel(), $this->label); + $this->assertSame($sut->getIconAssetPath(), $this->iconAssetPath); + } +} diff --git a/tests/unit/src/Admin/MenuTest.php b/tests/unit/src/Admin/MenuTest.php new file mode 100644 index 00000000..be99a132 --- /dev/null +++ b/tests/unit/src/Admin/MenuTest.php @@ -0,0 +1,57 @@ +itemMock = $this->createMock(Item::class); + } + + protected function sut( + ?Item ...$items, + ): Menu { + return new Menu(...$items); + } + + public function testCanCreateInstance(): void + { + $this->assertInstanceOf(Menu::class, $this->sut()); + $this->assertInstanceOf(Menu::class, $this->sut($this->itemMock)); + } + + public function testCanAddGetItem(): void + { + $sut = $this->sut(); + $this->assertEmpty($sut->getItems()); + $sut->addItem($this->itemMock); + $this->assertCount(1, $sut->getItems()); + } + + public function testCanSetGetActiveHrefPath(): void + { + $sut = $this->sut(); + $this->assertNull($sut->getActiveHrefPath()); + $sut->setActiveHrefPath('oidc'); + $this->assertSame('oidc', $sut->getActiveHrefPath()); + } + + public function testCanBuildItem(): void + { + $this->assertInstanceOf(Item::class, $this->sut()->buildItem('oidc', 'OIDC')); + } +} diff --git a/tests/unit/src/Bridges/PsrHttpBridgeTest.php b/tests/unit/src/Bridges/PsrHttpBridgeTest.php new file mode 100644 index 00000000..c27cc750 --- /dev/null +++ b/tests/unit/src/Bridges/PsrHttpBridgeTest.php @@ -0,0 +1,74 @@ +httpFoundationFactoryMock = $this->createMock(HttpFoundationFactory::class); + $this->serverRequestFactoryMock = $this->createMock(ServerRequestFactoryInterface::class); + $this->responseFactoryMock = $this->createMock(ResponseFactoryInterface::class); + $this->streamFactoryMock = $this->createMock(StreamFactoryInterface::class); + $this->uploadedFileFactoryMock = $this->createMock(UploadedFileFactoryInterface::class); + } + + protected function sut( + ?HttpFoundationFactory $httpFoundationFactory = null, + ?ServerRequestFactoryInterface $serverRequestFactory = null, + ?ResponseFactoryInterface $responseFactory = null, + ?StreamFactoryInterface $streamFactory = null, + ?UploadedFileFactoryInterface $uploadedFileFactory = null, + ): PsrHttpBridge { + $httpFoundationFactory ??= $this->httpFoundationFactoryMock; + $serverRequestFactory ??= $this->serverRequestFactoryMock; + $responseFactory ??= $this->responseFactoryMock; + $streamFactory ??= $this->streamFactoryMock; + $uploadedFileFactory ??= $this->uploadedFileFactoryMock; + + return new PsrHttpBridge( + $httpFoundationFactory, + $serverRequestFactory, + $responseFactory, + $streamFactory, + $uploadedFileFactory, + ); + } + + public function testCanCreateInstance(): void + { + $this->assertInstanceOf(PsrHttpBridge::class, $this->sut()); + } + + public function testCanGetProperties(): void + { + $sut = $this->sut(); + + $this->assertInstanceOf(HttpFoundationFactory::class, $sut->getHttpFoundationFactory()); + $this->assertInstanceOf(ServerRequestFactoryInterface::class, $sut->getServerRequestFactory()); + $this->assertInstanceOf(ResponseFactoryInterface::class, $sut->getResponseFactory()); + $this->assertInstanceOf(StreamFactoryInterface::class, $sut->getStreamFactory()); + $this->assertInstanceOf(UploadedFileFactoryInterface::class, $sut->getUploadedFileFactory()); + $this->assertInstanceOf(PsrHttpFactory::class, $sut->getPsrHttpFactory()); + } +} diff --git a/tests/unit/src/Bridges/SspBridge/Module/AdminTest.php b/tests/unit/src/Bridges/SspBridge/Module/AdminTest.php new file mode 100644 index 00000000..8e8d3aac --- /dev/null +++ b/tests/unit/src/Bridges/SspBridge/Module/AdminTest.php @@ -0,0 +1,29 @@ +assertInstanceOf(Admin::class, $this->sut()); + } + + public function testCanBuildSspAdminMenu(): void + { + $this->assertInstanceOf(Menu::class, $this->sut()->buildSspAdminMenu()); + } +} diff --git a/tests/unit/src/Bridges/SspBridge/ModuleTest.php b/tests/unit/src/Bridges/SspBridge/ModuleTest.php new file mode 100644 index 00000000..08c831e4 --- /dev/null +++ b/tests/unit/src/Bridges/SspBridge/ModuleTest.php @@ -0,0 +1,42 @@ +assertInstanceOf(Module::class, $this->sut()); + } + + public function testCanBuildAdminInstance(): void + { + $this->assertInstanceOf(Module\Admin::class, $this->sut()->admin()); + } + + public function testCanGetModuleUrl(): void + { + $this->assertStringContainsString( + 'test', + $this->sut()->getModuleUrl('test'), + ); + } + + public function testCanCheckIsModuleEnabled(): void + { + $this->assertFalse($this->sut()->isModuleEnabled('invalid')); + $this->assertTrue($this->sut()->isModuleEnabled('core')); + } +} diff --git a/tests/unit/src/Bridges/SspBridge/UtilsTest.php b/tests/unit/src/Bridges/SspBridge/UtilsTest.php new file mode 100644 index 00000000..3fc4941d --- /dev/null +++ b/tests/unit/src/Bridges/SspBridge/UtilsTest.php @@ -0,0 +1,47 @@ +assertInstanceOf(Utils::class, $this->sut()); + } + + public function testCanBuildConfigInstance(): void + { + $this->assertInstanceOf(Config::class, $this->sut()->config()); + } + + public function testCanBuildHttpInstance(): void + { + $this->assertInstanceOf(HTTP::class, $this->sut()->http()); + } + + public function testCanBuildRandomInstance(): void + { + $this->assertInstanceOf(Random::class, $this->sut()->random()); + } + + public function testCanBuildAuthInstance(): void + { + $this->assertInstanceOf(Auth::class, $this->sut()->auth()); + } +} diff --git a/tests/unit/src/Bridges/SspBridgeTest.php b/tests/unit/src/Bridges/SspBridgeTest.php new file mode 100644 index 00000000..bd4da0aa --- /dev/null +++ b/tests/unit/src/Bridges/SspBridgeTest.php @@ -0,0 +1,33 @@ +assertInstanceOf(SspBridge::class, $this->sut()); + } + + public function testCanBuildUtilsInstance(): void + { + $this->assertInstanceOf(SspBridge\Utils::class, $this->sut()->utils()); + } + + public function testCanBuildModuleInstance(): void + { + $this->assertInstanceOf(SspBridge\Module::class, $this->sut()->module()); + } +} diff --git a/tests/unit/src/Codebooks/RegistrationTypeEnumTest.php b/tests/unit/src/Codebooks/RegistrationTypeEnumTest.php new file mode 100644 index 00000000..3a9a444e --- /dev/null +++ b/tests/unit/src/Codebooks/RegistrationTypeEnumTest.php @@ -0,0 +1,26 @@ +assertStringContainsString( + 'Manual', + RegistrationTypeEnum::Manual->description(), + ); + + $this->assertStringContainsString( + 'Automatic', + RegistrationTypeEnum::FederatedAutomatic->description(), + ); + } +} diff --git a/tests/unit/src/Controller/Client/CreateControllerTest.php b/tests/unit/src/Controller/Client/CreateControllerTest.php deleted file mode 100644 index b150144e..00000000 --- a/tests/unit/src/Controller/Client/CreateControllerTest.php +++ /dev/null @@ -1,253 +0,0 @@ -clientRepositoryMock = $this->createMock(ClientRepository::class); - $this->allowedOriginRepositoryMock = $this->createMock(AllowedOriginRepository::class); - $this->templateFactoryMock = $this->createMock(TemplateFactory::class); - $this->formFactoryMock = $this->createMock(FormFactory::class); - $this->sessionMessageServiceMock = $this->createMock(SessionMessagesService::class); - $this->authContextServiceMock = $this->createMock(AuthContextService::class); - - $this->clientFormMock = $this->createMock(ClientForm::class); - $this->serverRequestStub = $this->createStub(ServerRequest::class); - $this->templateStub = $this->createStub(Template::class); - - $this->helpersMock = $this->createMock(Helpers::class); - - $this->clientEntityFactoryMock = $this->createMock(ClientEntityFactory::class); - - $this->clientEntityMock = $this->createMock(ClientEntity::class); - } - - public function testCanInstantiate(): void - { - $controller = $this->mock(); - $this->assertInstanceOf(CreateController::class, $controller); - } - - protected function mock(): CreateController - { - return new CreateController( - $this->clientRepositoryMock, - $this->allowedOriginRepositoryMock, - $this->templateFactoryMock, - $this->formFactoryMock, - $this->sessionMessageServiceMock, - $this->authContextServiceMock, - $this->helpersMock, - $this->clientEntityFactoryMock, - ); - } - - /** - * @throws \Exception - */ - public function testCanShowNewClientForm(): void - { - $this->clientFormMock - ->expects($this->once()) - ->method('setAction') - ->with($this->anything()); - $this->clientFormMock - ->expects($this->once()) - ->method('isSuccess') - ->willReturn(false); - - $this->templateFactoryMock - ->expects($this->once()) - ->method('render') - ->with('oidc:clients/new.twig', [ - 'form' => $this->clientFormMock, - 'regexUri' => ClientForm::REGEX_URI, - 'regexAllowedOriginUrl' => ClientForm::REGEX_ALLOWED_ORIGIN_URL, - 'regexHttpUri' => ClientForm::REGEX_HTTP_URI, - 'regexHttpUriPath' => ClientForm::REGEX_HTTP_URI_PATH, - ]) - ->willReturn($this->templateStub); - - $this->formFactoryMock - ->expects($this->once()) - ->method('build') - ->with($this->equalTo(ClientForm::class)) - ->willReturn($this->clientFormMock); - - $controller = $this->mock(); - $this->assertSame($this->templateStub, $controller->__invoke()); - } - - /** - * @throws \Exception - */ - public function testCanCreateNewClientFromFormData(): void - { - $clientData = [ - 'name' => 'name', - 'description' => 'description', - 'auth_source' => 'auth_source', - 'redirect_uri' => ['http://localhost/redirect'], - 'scopes' => ['openid'], - 'is_enabled' => true, - 'is_confidential' => false, - 'allowed_origin' => [], - 'post_logout_redirect_uri' => [], - 'backchannel_logout_uri' => null, - 'entity_identifier' => null, - 'client_registration_types' => null, - 'federation_jwks' => null, - 'jwks' => null, - 'jwks_uri' => null, - 'signed_jwks_uri' => null, - 'is_federated' => false, - ]; - - $this->clientFormMock - ->expects($this->once()) - ->method('setAction') - ->with($this->anything()); - $this->clientFormMock - ->expects($this->once()) - ->method('isSuccess') - ->willReturn(true); - $this->clientFormMock - ->expects($this->once()) - ->method('getValues') - ->willReturn($clientData); - - $this->formFactoryMock - ->expects($this->once()) - ->method('build') - ->willReturn($this->clientFormMock); - - $this->clientEntityFactoryMock->expects($this->once()) - ->method('fromData') - ->willReturn($this->clientEntityMock); - - $this->clientRepositoryMock - ->expects($this->once()) - ->method('add') - ->with($this->isInstanceOf(ClientEntity::class)); - - $this->allowedOriginRepositoryMock - ->expects($this->once()) - ->method('set') - ->with($this->isType('string'), []); - $this->sessionMessageServiceMock - ->expects($this->once()) - ->method('addMessage') - ->with('{oidc:client:added}'); - - $this->assertInstanceOf(RedirectResponse::class, $this->mock()->__invoke()); - } - - /** - * @throws \Exception - */ - public function testCanSetOwnerInNewClient(): void - { - $this->authContextServiceMock->expects($this->once())->method('isSspAdmin')->willReturn(false); - $this->authContextServiceMock->expects($this->once()) - ->method('getAuthUserId')->willReturn('ownerUsername'); - - $this->clientFormMock - ->expects($this->once()) - ->method('setAction') - ->with($this->anything()); - $this->clientFormMock - ->expects($this->once()) - ->method('isSuccess') - ->willReturn(true); - $this->clientFormMock - ->expects($this->once()) - ->method('getValues') - ->willReturn( - [ - 'name' => 'name', - 'description' => 'description', - 'auth_source' => 'auth_source', - 'redirect_uri' => ['http://localhost/redirect'], - 'scopes' => ['openid'], - 'is_enabled' => true, - 'is_confidential' => false, - 'owner' => 'wrongOwner', - 'allowed_origin' => [], - 'post_logout_redirect_uri' => [], - 'backchannel_logout_uri' => null, - 'entity_identifier' => null, - 'client_registration_types' => null, - 'federation_jwks' => null, - 'jwks' => null, - 'jwks_uri' => null, - 'signed_jwks_uri' => null, - 'is_federated' => false, - ], - ); - - $this->formFactoryMock - ->expects($this->once()) - ->method('build') - ->willReturn($this->clientFormMock); - - $this->clientEntityMock->expects($this->once()) - ->method('getOwner') - ->willReturn('ownerUsername'); - - $this->clientEntityFactoryMock->expects($this->once()) - ->method('fromData') - ->willReturn($this->clientEntityMock); - - $this->clientRepositoryMock->expects($this->once())->method('add') - ->with($this->callback(fn($client) => is_callable([$client, 'getOwner']) && - $client->getOwner() == 'ownerUsername')); - - $this->sessionMessageServiceMock - ->expects($this->once()) - ->method('addMessage') - ->with('{oidc:client:added}'); - - $this->assertInstanceOf(RedirectResponse::class, $this->mock()->__invoke()); - } -} diff --git a/tests/unit/src/Controller/Client/DeleteControllerTest.php b/tests/unit/src/Controller/Client/DeleteControllerTest.php deleted file mode 100644 index 07bc0dda..00000000 --- a/tests/unit/src/Controller/Client/DeleteControllerTest.php +++ /dev/null @@ -1,215 +0,0 @@ -clientRepositoryMock = $this->createMock(ClientRepository::class); - $this->templateFactoryMock = $this->createMock(TemplateFactory::class); - $this->sessionMessageServiceMock = $this->createMock(SessionMessagesService::class); - $this->serverRequestMock = $this->createMock(ServerRequest::class); - $this->uriStub = $this->createStub(UriInterface::class); - $this->authContextServiceMock = $this->createMock(AuthContextService::class); - - $this->clientEntityMock = $this->createMock(ClientEntity::class); - $this->templateStub = $this->createStub(Template::class); - } - - protected function getStubbedInstance(): DeleteController - { - return new DeleteController( - $this->clientRepositoryMock, - $this->templateFactoryMock, - $this->sessionMessageServiceMock, - $this->authContextServiceMock, - ); - } - - public function testCanInstantiate(): void - { - $controller = $this->getStubbedInstance(); - $this->assertInstanceOf(DeleteController::class, $controller); - } - - /** - * @throws \JsonException - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\ConfigurationError - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException - */ - public function testItAsksConfirmationBeforeDeletingClient(): void - { - $this->serverRequestMock->expects($this->once())->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - $this->serverRequestMock->expects($this->once())->method('getParsedBody')->willReturn([]); - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('get'); - $this->clientRepositoryMock->expects($this->once())->method('findById')->with('clientid') - ->willReturn($this->clientEntityMock); - $this->templateFactoryMock->expects($this->once())->method('render') - ->with('oidc:clients/delete.twig', ['client' => $this->clientEntityMock]) - ->willReturn($this->templateStub); - - $controller = $this->getStubbedInstance(); - - $this->assertInstanceOf(Template::class, $controller->__invoke($this->serverRequestMock)); - } - - /** - * @throws \JsonException - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\ConfigurationError - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException - */ - public function testThrowsIfIdNotFoundInDeleteAction(): void - { - $this->serverRequestMock->expects($this->once())->method('getQueryParams')->willReturn([]); - - $this->expectException(BadRequest::class); - - ($this->getStubbedInstance())->__invoke($this->serverRequestMock); - } - - /** - * @throws \JsonException - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\ConfigurationError - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException - */ - public function testThrowsIfSecretNotFoundInDeleteAction(): void - { - $this->serverRequestMock->expects($this->once())->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - $this->serverRequestMock->expects($this->once())->method('getParsedBody')->willReturn([]); - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('post'); - $this->clientRepositoryMock->expects($this->once())->method('findById') - ->willReturn($this->clientEntityMock); - - $this->expectException(BadRequest::class); - - ($this->getStubbedInstance())->__invoke($this->serverRequestMock); - } - - /** - * @throws \JsonException - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\ConfigurationError - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException - */ - public function testThrowsIfSecretIsInvalidInDeleteAction(): void - { - $this->serverRequestMock->expects($this->once())->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - $this->serverRequestMock->expects($this->once())->method('getParsedBody') - ->willReturn(['secret' => 'invalidsecret']); - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('post'); - $this->clientEntityMock->expects($this->once())->method('getSecret')->willReturn('validsecret'); - $this->clientRepositoryMock->expects($this->once())->method('findById') - ->willReturn($this->clientEntityMock); - - $this->expectException(BadRequest::class); - - ($this->getStubbedInstance())->__invoke($this->serverRequestMock); - } - - /** - * @throws \JsonException - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\ConfigurationError - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException - */ - public function testItDeletesClient(): void - { - $this->serverRequestMock->expects($this->once())->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - $this->serverRequestMock->expects($this->once())->method('getParsedBody') - ->willReturn(['secret' => 'validsecret']); - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('post'); - $this->clientEntityMock->expects($this->once())->method('getSecret')->willReturn('validsecret'); - $this->clientRepositoryMock->expects($this->once())->method('findById') - ->willReturn($this->clientEntityMock); - $this->clientRepositoryMock->expects($this->once())->method('delete') - ->with($this->clientEntityMock, null); - $this->sessionMessageServiceMock->expects($this->once())->method('addMessage') - ->with('{oidc:client:removed}'); - - $this->assertInstanceOf( - RedirectResponse::class, - ($this->getStubbedInstance())->__invoke($this->serverRequestMock), - ); - } - - /** - * @throws \JsonException - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\ConfigurationError - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException - */ - public function testItDeletesClientWithOwner(): void - { - $this->authContextServiceMock->expects($this->exactly(2))->method('isSspAdmin')->willReturn(false); - $this->authContextServiceMock->expects($this->exactly(2))->method('getAuthUserId')->willReturn('theOwner'); - $this->serverRequestMock->expects($this->once())->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - $this->serverRequestMock->expects($this->once())->method('getParsedBody') - ->willReturn(['secret' => 'validsecret']); - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('post'); - $this->clientEntityMock->expects($this->once())->method('getSecret')->willReturn('validsecret'); - $this->clientRepositoryMock->expects($this->once())->method('findById') - ->willReturn($this->clientEntityMock); - $this->clientRepositoryMock->expects($this->once())->method('delete') - ->with($this->clientEntityMock, 'theOwner'); - $this->sessionMessageServiceMock->expects($this->once())->method('addMessage') - ->with('{oidc:client:removed}'); - - $this->assertInstanceOf( - RedirectResponse::class, - ($this->getStubbedInstance())->__invoke($this->serverRequestMock), - ); - } -} diff --git a/tests/unit/src/Controller/Client/EditControllerTest.php b/tests/unit/src/Controller/Client/EditControllerTest.php deleted file mode 100644 index f8fd47e3..00000000 --- a/tests/unit/src/Controller/Client/EditControllerTest.php +++ /dev/null @@ -1,403 +0,0 @@ -moduleConfigMock = $this->createMock(ModuleConfig::class); - $this->clientRepositoryMock = $this->createMock(ClientRepository::class); - $this->allowedOriginRepositoryMock = $this->createMock(AllowedOriginRepository::class); - $this->templateFactoryMock = $this->createMock(TemplateFactory::class); - $this->formFactoryMock = $this->createMock(FormFactory::class); - $this->sessionMessageServiceMock = $this->createMock(SessionMessagesService::class); - $this->authContextServiceMock = $this->createMock(AuthContextService::class); - $this->serverRequestMock = $this->createMock(ServerRequest::class); - $this->uriStub = $this->createStub(UriInterface::class); - - $this->clientEntityMock = $this->createMock(ClientEntity::class); - $this->clientEntityMock->method('getRegistrationType')->willReturn(RegistrationTypeEnum::Manual); - $this->templateStub = $this->createStub(Template::class); - $this->clientFormMock = $this->createMock(ClientForm::class); - - $this->moduleConfigMock->method('getModuleUrl')->willReturn('url'); - $this->uriStub->method('getPath')->willReturn('/'); - $this->serverRequestMock->method('getUri')->willReturn($this->uriStub); - $this->serverRequestMock->method('withQueryParams')->willReturn($this->serverRequestMock); - - $this->helpersMock = $this->createMock(Helpers::class); - $this->dateTimeHelperMock = $this->createMock(Helpers\DateTime::class); - $this->helpersMock->method('dateTime')->willReturn($this->dateTimeHelperMock); - - $this->updatedAtMock = $this->createMock(DateTimeImmutable::class); - $this->dateTimeHelperMock->method('getUtc')->willReturn($this->updatedAtMock); - - $this->clientEntityFactoryMock = $this->createMock(ClientEntityFactory::class); - } - - public static function setUpBeforeClass(): void - { - // To make lib/SimpleSAML/Utils/HTTP::getSelfURL() work... - global $_SERVER; - $_SERVER['REQUEST_URI'] = ''; - } - - protected function mock(): EditController - { - return new EditController( - $this->clientRepositoryMock, - $this->allowedOriginRepositoryMock, - $this->templateFactoryMock, - $this->formFactoryMock, - $this->sessionMessageServiceMock, - $this->authContextServiceMock, - $this->helpersMock, - $this->clientEntityFactoryMock, - ); - } - - public function testItIsInitializable(): void - { - $this->assertInstanceOf( - EditController::class, - $this->mock(), - ); - } - - /** - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - */ - public function testItShowsEditClientForm(): void - { - $this->authContextServiceMock->method('isSspAdmin')->willReturn(true); - - $data = [ - 'id' => 'clientid', - 'secret' => 'validsecret', - 'name' => 'name', - 'description' => 'description', - 'auth_source' => 'auth_source', - 'redirect_uri' => ['http://localhost/redirect'], - 'scopes' => ['openid'], - 'is_enabled' => true, - 'allowed_origin' => [], - 'post_logout_redirect_uri' => [], - 'backchannel_logout_uri' => null, - 'entity_identifier' => null, - 'client_registration_types' => null, - 'federation_jwks' => null, - 'jwks' => null, - 'jwks_uri' => null, - 'signed_jwks_uri' => null, - 'registration_type' => RegistrationTypeEnum::Manual, - 'updated_at' => null, - 'created_at' => null, - 'expires_at' => null, - 'is_federated' => false, - ]; - - $this->clientEntityMock->expects($this->atLeastOnce())->method('getIdentifier')->willReturn('clientid'); - $this->serverRequestMock->expects($this->once())->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - $this->clientEntityMock->expects($this->once())->method('toArray')->willReturn($data); - $this->clientRepositoryMock->expects($this->once())->method('findById') - ->willReturn($this->clientEntityMock); - $this->allowedOriginRepositoryMock->expects($this->once())->method('get')->with('clientid') - ->willReturn([]); - $this->clientFormMock->expects($this->once())->method('setAction'); - $this->clientFormMock->expects($this->once())->method('setDefaults')->with($data); - $this->clientFormMock->expects($this->once())->method('isSuccess')->willReturn(false); - $this->formFactoryMock->expects($this->once())->method('build')->willReturn($this->clientFormMock); - $this->templateFactoryMock->expects($this->once())->method('render')->with( - 'oidc:clients/edit.twig', - [ - 'form' => $this->clientFormMock, - 'regexUri' => ClientForm::REGEX_URI, - 'regexAllowedOriginUrl' => ClientForm::REGEX_ALLOWED_ORIGIN_URL, - 'regexHttpUri' => ClientForm::REGEX_HTTP_URI, - 'regexHttpUriPath' => ClientForm::REGEX_HTTP_URI_PATH, - ], - )->willReturn($this->templateStub); - - $this->assertSame( - ($this->mock())->__invoke($this->serverRequestMock), - $this->templateStub, - ); - } - - /** - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - */ - public function testItUpdatesClientFromEditClientFormData(): void - { - $this->authContextServiceMock->method('isSspAdmin')->willReturn(true); - - $data = [ - 'id' => 'clientid', - 'secret' => 'validsecret', - 'name' => 'name', - 'description' => 'description', - 'auth_source' => 'auth_source', - 'redirect_uri' => ['http://localhost/redirect'], - 'scopes' => ['openid'], - 'is_enabled' => true, - 'is_confidential' => false, - 'owner' => 'existingOwner', - 'allowed_origin' => [], - 'post_logout_redirect_uri' => [], - 'backchannel_logout_uri' => null, - 'entity_identifier' => null, - 'client_registration_types' => null, - 'federation_jwks' => null, - 'jwks' => null, - 'jwks_uri' => null, - 'signed_jwks_uri' => null, - 'registration_type' => RegistrationTypeEnum::Manual, - 'updated_at' => null, - 'created_at' => null, - 'expires_at' => null, - 'is_federated' => false, - ]; - - $this->serverRequestMock->expects($this->once())->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - - $this->clientEntityMock->expects($this->atLeastOnce())->method('getIdentifier')->willReturn('clientid'); - $this->clientEntityMock->expects($this->once())->method('getSecret')->willReturn('validsecret'); - $this->clientEntityMock->expects($this->once())->method('getOwner')->willReturn('existingOwner'); - $this->clientEntityMock->expects($this->once())->method('toArray')->willReturn($data); - - $this->clientRepositoryMock->expects($this->once())->method('findById') - ->willReturn($this->clientEntityMock); - - $this->allowedOriginRepositoryMock->expects($this->once())->method('get')->with('clientid') - ->willReturn([]); - - $this->clientFormMock->expects($this->once())->method('setAction'); - $this->clientFormMock->expects($this->once())->method('setDefaults')->with($data); - $this->clientFormMock->expects($this->once())->method('isSuccess')->willReturn(true); - $this->clientFormMock->expects($this->once())->method('getValues')->willReturn( - [ - 'name' => 'name', - 'description' => 'description', - 'auth_source' => 'auth_source', - 'redirect_uri' => ['http://localhost/redirect'], - 'scopes' => ['openid'], - 'is_enabled' => true, - 'is_confidential' => false, - 'owner' => 'existingOwner', - 'allowed_origin' => [], - 'post_logout_redirect_uri' => [], - 'backchannel_logout_uri' => null, - 'entity_identifier' => null, - 'client_registration_types' => null, - 'federation_jwks' => null, - 'jwks' => null, - 'jwks_uri' => null, - 'signed_jwks_uri' => null, - 'is_federated' => false, - ], - ); - - $this->formFactoryMock->expects($this->once())->method('build')->willReturn($this->clientFormMock); - - $this->clientEntityFactoryMock->expects($this->once())->method('fromData') - ->willReturn($this->clientEntityMock); - - $this->clientRepositoryMock->expects($this->once())->method('update')->with( - $this->clientEntityMock, - null, - ); - - $this->allowedOriginRepositoryMock->expects($this->once())->method('set')->with('clientid', []); - $this->sessionMessageServiceMock->expects($this->once())->method('addMessage') - ->with('{oidc:client:updated}'); - - $this->assertInstanceOf( - RedirectResponse::class, - ($this->mock())->__invoke($this->serverRequestMock), - ); - } - - /** - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - */ - public function testItSendsOwnerArgToRepoOnUpdate(): void - { - $this->authContextServiceMock->expects($this->atLeastOnce())->method('isSspAdmin')->willReturn(false); - $this->authContextServiceMock->expects($this->atLeastOnce())->method('getAuthUserId') - ->willReturn('authedUserId'); - $data = [ - 'id' => 'clientid', - 'secret' => 'validsecret', - 'name' => 'name', - 'description' => 'description', - 'auth_source' => 'auth_source', - 'redirect_uri' => ['http://localhost/redirect'], - 'scopes' => ['openid'], - 'is_enabled' => true, - 'is_confidential' => false, - 'owner' => 'existingOwner', - 'allowed_origin' => [], - 'post_logout_redirect_uri' => [], - 'backchannel_logout_uri' => null, - 'entity_identifier' => null, - 'client_registration_types' => null, - 'federation_jwks' => null, - 'jwks' => null, - 'jwks_uri' => null, - 'signed_jwks_uri' => null, - 'registration_type' => RegistrationTypeEnum::Manual, - 'updated_at' => null, - 'created_at' => null, - 'expires_at' => null, - 'is_federated' => false, - ]; - - $this->serverRequestMock->expects($this->once())->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - - $this->clientEntityMock->expects($this->atLeastOnce())->method('getIdentifier')->willReturn('clientid'); - $this->clientEntityMock->expects($this->once())->method('getSecret')->willReturn('validsecret'); - $this->clientEntityMock->expects($this->once())->method('getOwner')->willReturn('existingOwner'); - $this->clientEntityMock->expects($this->once())->method('toArray')->willReturn($data); - - $this->clientRepositoryMock->expects($this->once())->method('findById') - ->with('clientid', 'authedUserId')->willReturn($this->clientEntityMock); - - $this->allowedOriginRepositoryMock->expects($this->once())->method('get')->with('clientid') - ->willReturn([]); - - $this->clientFormMock->expects($this->once())->method('setAction'); - $this->clientFormMock->expects($this->once())->method('setDefaults')->with($data); - $this->clientFormMock->expects($this->once())->method('isSuccess')->willReturn(true); - $this->clientFormMock->expects($this->once())->method('getValues')->willReturn( - [ - 'name' => 'name', - 'description' => 'description', - 'auth_source' => 'auth_source', - 'redirect_uri' => ['http://localhost/redirect'], - 'scopes' => ['openid'], - 'is_enabled' => true, - 'is_confidential' => false, - 'owner' => 'existingOwner', - 'allowed_origin' => [], - 'post_logout_redirect_uri' => [], - 'backchannel_logout_uri' => null, - 'entity_identifier' => null, - 'client_registration_types' => null, - 'federation_jwks' => null, - 'jwks' => null, - 'jwks_uri' => null, - 'signed_jwks_uri' => null, - 'is_federated' => false, - ], - ); - - $this->formFactoryMock->expects($this->once())->method('build')->willReturn($this->clientFormMock); - - $this->clientEntityFactoryMock->expects($this->once())->method('fromData') - ->willReturn($this->clientEntityMock); - - $this->clientRepositoryMock->expects($this->once())->method('update')->with( - $this->clientEntityMock, - 'authedUserId', - ); - - $this->allowedOriginRepositoryMock->expects($this->once())->method('get')->with('clientid') - ->willReturn([]); - $this->allowedOriginRepositoryMock->expects($this->once())->method('set')->with('clientid', []); - $this->sessionMessageServiceMock->expects($this->once())->method('addMessage') - ->with('{oidc:client:updated}'); - - $this->assertInstanceOf( - RedirectResponse::class, - ($this->mock())->__invoke($this->serverRequestMock), - ); - } - - /** - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - */ - public function testThrowsIdNotFoundExceptionInEditAction(): void - { - $this->serverRequestMock->expects($this->once())->method('getQueryParams')->willReturn([]); - - $this->expectException(BadRequest::class); - - ($this->mock())->__invoke($this->serverRequestMock); - } - - /** - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - */ - public function testThrowsClientNotFoundExceptionInEditAction(): void - { - $this->serverRequestMock->expects($this->once())->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - $this->clientRepositoryMock->expects($this->once())->method('findById')->willReturn(null); - - $this->expectException(Exception::class); - - ($this->mock())->__invoke($this->serverRequestMock); - } -} diff --git a/tests/unit/src/Controller/Client/IndexControllerTest.php b/tests/unit/src/Controller/Client/IndexControllerTest.php deleted file mode 100644 index cfa54219..00000000 --- a/tests/unit/src/Controller/Client/IndexControllerTest.php +++ /dev/null @@ -1,90 +0,0 @@ -clientRepositoryMock = $this->createMock(ClientRepository::class); - $this->templateFactoryMock = $this->createMock(TemplateFactory::class); - $this->authContextServiceMock = $this->createMock(AuthContextService::class); - $this->serverRequestMock = $this->createMock(ServerRequest::class); - $this->uriStub = $this->createStub(UriInterface::class); - - $this->templateStub = $this->createStub(Template::class); - - $this->authContextServiceMock->method('isSspAdmin')->willReturn(true); - $this->uriStub->method('getPath')->willReturn('/'); - $this->serverRequestMock->method('getUri')->willReturn($this->uriStub); - $this->serverRequestMock->method('getQueryParams')->willReturn(['page' => 1]); - } - - protected function getStubbedInstance(): IndexController - { - return new IndexController( - $this->clientRepositoryMock, - $this->templateFactoryMock, - $this->authContextServiceMock, - ); - } - - public function testItIsInitializable(): void - { - $this->assertInstanceOf(IndexController::class, $this->getStubbedInstance()); - } - - /** - * @throws \SimpleSAML\Error\Exception - */ - public function testItShowsClientIndex(): void - { - $this->clientRepositoryMock->expects($this->once())->method('findPaginated') - ->with(1, '', null) - ->willReturn( - [ - 'items' => [], - 'numPages' => 1, - 'currentPage' => 1, - ], - ); - - $this->templateFactoryMock->expects($this->once())->method('render')->with( - 'oidc:clients/index.twig', - [ - 'clients' => [], - 'numPages' => 1, - 'currentPage' => 1, - 'query' => '', - ], - )->willReturn($this->templateStub); - - $this->assertSame($this->templateStub, ($this->getStubbedInstance())->__invoke($this->serverRequestMock)); - } -} diff --git a/tests/unit/src/Controller/Client/ResetSecretControllerTest.php b/tests/unit/src/Controller/Client/ResetSecretControllerTest.php deleted file mode 100644 index 8d59b742..00000000 --- a/tests/unit/src/Controller/Client/ResetSecretControllerTest.php +++ /dev/null @@ -1,207 +0,0 @@ -clientRepositoryMock = $this->createMock(ClientRepository::class); - $this->sessionMessagesServiceMock = $this->createMock(SessionMessagesService::class); - $this->authContextServiceMock = $this->createMock(AuthContextService::class); - - $this->serverRequestMock = $this->createMock(ServerRequest::class); - $this->clientEntityMock = $this->createMock(ClientEntity::class); - } - - protected function prepareStubbedInstance(): ResetSecretController - { - return new ResetSecretController( - $this->clientRepositoryMock, - $this->sessionMessagesServiceMock, - $this->authContextServiceMock, - ); - } - - public function testCanInstantiate(): void - { - $this->assertInstanceOf( - ResetSecretController::class, - $this->prepareStubbedInstance(), - ); - } - - /** - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - */ - public function testItThrowsIdNotFoundExceptionInResetSecretAction(): void - { - $this->serverRequestMock->method('getQueryParams')->willReturn([]); - $this->expectException(BadRequest::class); - $this->prepareStubbedInstance()->__invoke($this->serverRequestMock); - } - - /** - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\Exception - */ - public function testItThrowsClientNotFoundExceptionInResetSecretAction(): void - { - $this->serverRequestMock->method('getQueryParams')->willReturn(['client_id' => 'clientid']); - $this->clientRepositoryMock - ->expects($this->once()) - ->method('findById') - ->with('clientid') - ->willReturn(null); - - $this->expectException(OidcServerException::class); - $this->prepareStubbedInstance()->__invoke($this->serverRequestMock); - } - - /** - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - */ - public function testThrowsSecretNotFoundExceptionInResetSecretAction(): void - { - $this->serverRequestMock - ->expects($this->once()) - ->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('post'); - $this->clientRepositoryMock - ->expects($this->once()) - ->method('findById') - ->with('clientid') - ->willReturn($this->clientEntityMock); - - $this->expectException(BadRequest::class); - $this->prepareStubbedInstance()->__invoke($this->serverRequestMock); - } - - /** - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - */ - public function testThrowsSecretInvalidExceptionInResetSecretAction(): void - { - $this->serverRequestMock - ->expects($this->once()) - ->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - $this->serverRequestMock - ->expects($this->once()) - ->method('getParsedBody') - ->willReturn(['secret' => 'invalidsecret']); - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('post'); - - $this->clientEntityMock->method('getSecret')->willReturn('validsecret'); - $this->clientRepositoryMock - ->expects($this->once()) - ->method('findById') - ->with('clientid') - ->willReturn($this->clientEntityMock); - - $this->expectException(BadRequest::class); - $this->prepareStubbedInstance()->__invoke($this->serverRequestMock); - } - - /** - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - */ - public function testItResetSecretsClient(): void - { - $this->serverRequestMock - ->expects($this->once()) - ->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - $this->serverRequestMock - ->expects($this->once()) - ->method('getParsedBody') - ->willReturn(['secret' => 'validsecret']); - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('post'); - - $this->clientEntityMock->method('getIdentifier')->willReturn('clientid'); - $this->clientEntityMock->method('getSecret')->willReturn('validsecret'); - $this->clientEntityMock->expects($this->once())->method('restoreSecret'); - - $this->clientRepositoryMock - ->expects($this->once()) - ->method('findById') - ->with('clientid') - ->willReturn($this->clientEntityMock); - $this->clientRepositoryMock - ->expects($this->once()) - ->method('update') - ->with($this->clientEntityMock); - - $this->sessionMessagesServiceMock - ->expects($this->once()) - ->method('addMessage') - ->with('{oidc:client:secret_updated}'); - - $this->prepareStubbedInstance()->__invoke($this->serverRequestMock); - } - - /** - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - */ - public function testItSendBackToShowClientIfNotPostMethodInResetAction(): void - { - $this->serverRequestMock - ->expects($this->once()) - ->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - $this->serverRequestMock - ->expects($this->once()) - ->method('getParsedBody') - ->willReturn(['secret' => 'validsecret']); - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('get'); - - $this->clientEntityMock->method('getIdentifier')->willReturn('clientid'); - - $this->clientRepositoryMock - ->expects($this->once()) - ->method('findById') - ->with('clientid') - ->willReturn($this->clientEntityMock); - - $this->assertInstanceOf( - RedirectResponse::class, - $this->prepareStubbedInstance()->__invoke($this->serverRequestMock), - ); - } -} diff --git a/tests/unit/src/Controller/Client/ShowControllerTest.php b/tests/unit/src/Controller/Client/ShowControllerTest.php deleted file mode 100644 index 1487059d..00000000 --- a/tests/unit/src/Controller/Client/ShowControllerTest.php +++ /dev/null @@ -1,143 +0,0 @@ -clientRepositoryMock = $this->createMock(ClientRepository::class); - $this->allowedOriginRepositoryMock = $this->createMock(AllowedOriginRepository::class); - $this->templateFactoryMock = $this->createMock(TemplateFactory::class); - $this->authContextServiceMock = $this->createMock(AuthContextService::class); - - $this->clientEntityMock = $this->createMock(ClientEntity::class); - $this->serverRequestMock = $this->createMock(ServerRequest::class); - $this->templateMock = $this->createMock(Template::class); - } - - protected function getStubbedInstance(): ShowController - { - return new ShowController( - $this->clientRepositoryMock, - $this->allowedOriginRepositoryMock, - $this->templateFactoryMock, - $this->authContextServiceMock, - ); - } - - public function testItIsInitializable(): void - { - $this->assertInstanceOf( - ShowController::class, - $this->getStubbedInstance(), - ); - } - - /** - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException - * @throws \JsonException - */ - public function testItShowsClientDescription(): void - { - $this->serverRequestMock - ->expects($this->once()) - ->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - $this->clientEntityMock->expects($this->once())->method('getIdentifier')->willReturn('clientid'); - $this->clientRepositoryMock - ->expects($this->once()) - ->method('findById') - ->willReturn($this->clientEntityMock); - $this->allowedOriginRepositoryMock - ->expects($this->once()) - ->method('get') - ->with('clientid') - ->willReturn([]); - $this->templateFactoryMock - ->expects($this->once()) - ->method('render') - ->with( - 'oidc:clients/show.twig', - [ - 'client' => $this->clientEntityMock, - 'allowedOrigins' => [], - ], - )->willReturn($this->templateMock); - - $this->assertSame( - $this->templateMock, - $this->getStubbedInstance()->__invoke($this->serverRequestMock), - ); - } - - /** - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Error\NotFound - * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException - * @throws \JsonException - */ - public function testItThrowsIdNotFoundException(): void - { - $this->serverRequestMock->expects($this->once())->method('getQueryParams')->willReturn([]); - - $this->expectException(BadRequest::class); - $this->getStubbedInstance()->__invoke($this->serverRequestMock); - } - - /** - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\Exception - * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException - * @throws \JsonException - */ - public function testItThrowsClientNotFoundException(): void - { - $this->serverRequestMock - ->expects($this->once()) - ->method('getQueryParams') - ->willReturn(['client_id' => 'clientid']); - $this->clientRepositoryMock - ->expects($this->once()) - ->method('findById') - ->with('clientid') - ->willReturn(null); - - $this->expectException(OidcServerException::class); - $this->getStubbedInstance()->__invoke($this->serverRequestMock); - } -} diff --git a/tests/unit/src/Controller/Federation/EntityStatementControllerTest.php b/tests/unit/src/Controller/Federation/EntityStatementControllerTest.php deleted file mode 100644 index a8bbcbdb..00000000 --- a/tests/unit/src/Controller/Federation/EntityStatementControllerTest.php +++ /dev/null @@ -1,18 +0,0 @@ -markTestIncomplete(); - } -} diff --git a/tests/unit/src/Controller/InstallerControllerTest.php b/tests/unit/src/Controller/InstallerControllerTest.php deleted file mode 100644 index 097d65d5..00000000 --- a/tests/unit/src/Controller/InstallerControllerTest.php +++ /dev/null @@ -1,160 +0,0 @@ -templateFactoryMock = $this->createMock(TemplateFactory::class); - $this->sessionMessagesService = $this->createMock(SessionMessagesService::class); - $this->databaseMigrationMock = $this->createMock(DatabaseMigration::class); - $this->databaseLegacyOAuth2ImportMock = $this->createMock(DatabaseLegacyOAuth2Import::class); - - $this->serverRequestMock = $this->createMock(ServerRequest::class); - $this->templateMock = $this->createMock(Template::class); - } - - protected function createStubbedInstance(): InstallerController - { - return new InstallerController( - $this->templateFactoryMock, - $this->sessionMessagesService, - $this->databaseMigrationMock, - $this->databaseLegacyOAuth2ImportMock, - ); - } - - public function testItIsInitializable(): void - { - $this->assertInstanceOf( - InstallerController::class, - $this->createStubbedInstance(), - ); - } - - /** - * @throws \Exception - */ - public function testItReturnsToMainPageIfAlreadyUpdated(): void - { - $this->databaseMigrationMock - ->expects($this->once()) - ->method('isUpdated') - ->willReturn(true); - - $this->assertInstanceOf( - RedirectResponse::class, - $this->createStubbedInstance()->__invoke($this->serverRequestMock), - ); - } - - /** - * @throws \Exception - */ - public function testItShowsInformationPage(): void - { - $this->serverRequestMock->expects($this->once())->method('getParsedBody'); - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('GET'); - $this->templateFactoryMock - ->expects($this->once()) - ->method('render') - ->with('oidc:install.twig', ['oauth2_enabled' => false,]) - ->willReturn($this->templateMock); - - $this->assertSame( - $this->templateMock, - $this->createStubbedInstance()->__invoke($this->serverRequestMock), - ); - } - - /** - * @throws \Exception - */ - public function testItRequiresConfirmationBeforeInstallSchema(): void - { - $this->serverRequestMock->expects($this->once())->method('getParsedBody'); - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('POST'); - $this->databaseMigrationMock->expects($this->never())->method('migrate'); - $this->templateFactoryMock - ->expects($this->once()) - ->method('render') - ->with('oidc:install.twig', ['oauth2_enabled' => false,]) - ->willReturn($this->templateMock); - - $this->assertSame( - $this->templateMock, - $this->createStubbedInstance()->__invoke($this->serverRequestMock), - ); - } - - /** - * @throws \Exception - */ - public function testItCreatesSchema(): void - { - $this->serverRequestMock->expects($this->once())->method('getParsedBody')->willReturn(['migrate' => true,]); - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('POST'); - $this->databaseMigrationMock->expects($this->once())->method('migrate'); - $this->databaseLegacyOAuth2ImportMock->expects($this->never())->method('import'); - $this->sessionMessagesService->expects($this->once())->method('addMessage')->with('{oidc:install:finished}'); - - $this->assertInstanceOf( - RedirectResponse::class, - $this->createStubbedInstance()->__invoke($this->serverRequestMock), - ); - } - - /** - * @throws \Exception - */ - public function testItImportsDataFromOauth2Module(): void - { - $this->serverRequestMock - ->expects($this->once()) - ->method('getParsedBody') - ->willReturn(['migrate' => true, 'oauth2_migrate' => true,]); - $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('POST'); - $this->databaseMigrationMock->expects($this->once())->method('migrate'); - $this->databaseLegacyOAuth2ImportMock->expects($this->once())->method('import'); - $this->sessionMessagesService - ->expects($this->atLeast(2)) - ->method('addMessage') - ->with( - $this->callback( - fn($message) => in_array($message, ['{oidc:install:finished}', '{oidc:import:finished}']), - ), - ); - - $this->assertInstanceOf( - RedirectResponse::class, - $this->createStubbedInstance()->__invoke($this->serverRequestMock), - ); - } -} diff --git a/tests/unit/src/Controller/AccessTokenControllerTest.php b/tests/unit/src/Controllers/AccessTokenControllerTest.php similarity index 93% rename from tests/unit/src/Controller/AccessTokenControllerTest.php rename to tests/unit/src/Controllers/AccessTokenControllerTest.php index fa3998a0..15c1a2c4 100644 --- a/tests/unit/src/Controller/AccessTokenControllerTest.php +++ b/tests/unit/src/Controllers/AccessTokenControllerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\oidc\unit\Controller; +namespace SimpleSAML\Test\Module\oidc\unit\Controllers; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; @@ -11,14 +11,14 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use SimpleSAML\Module\oidc\Bridges\PsrHttpBridge; -use SimpleSAML\Module\oidc\Controller\AccessTokenController; -use SimpleSAML\Module\oidc\Controller\Traits\RequestTrait; +use SimpleSAML\Module\oidc\Controllers\AccessTokenController; +use SimpleSAML\Module\oidc\Controllers\Traits\RequestTrait; use SimpleSAML\Module\oidc\Repositories\AllowedOriginRepository; use SimpleSAML\Module\oidc\Server\AuthorizationServer; use SimpleSAML\Module\oidc\Services\ErrorResponder; /** - * @covers \SimpleSAML\Module\oidc\Controller\AccessTokenController + * @covers \SimpleSAML\Module\oidc\Controllers\AccessTokenController */ class AccessTokenControllerTest extends TestCase { diff --git a/tests/unit/src/Controllers/Admin/ClientControllerTest.php b/tests/unit/src/Controllers/Admin/ClientControllerTest.php new file mode 100644 index 00000000..f591f966 --- /dev/null +++ b/tests/unit/src/Controllers/Admin/ClientControllerTest.php @@ -0,0 +1,428 @@ + 'Name', + 'description' => 'Description', + 'redirect_uri' => [0 => 'https://example.com/callback',], + 'is_enabled' => true, + 'is_confidential' => true, + 'auth_source' => null, + 'scopes' => [0 => 'openid', 1 => 'profile',], + 'owner' => '', + 'post_logout_redirect_uri' => [0 => 'https://example.com/',], + 'allowed_origin' => [], + 'backchannel_logout_uri' => 'https://example.com/logout', + 'entity_identifier' => 'https://example.com/', + 'client_registration_types' => [0 => 'automatic', 1 => 'explicit',], + 'federation_jwks' => [ + 'keys' => [ + 0 => [ + 'kty' => 'RSA', + 'n' => '...', + 'e' => 'AQAB', + 'kid' => 'fed123', + 'use' => 'sig', + 'alg' => 'RS256', + ], + ], + ], + 'jwks' => [ + 'keys' => [ + 0 => [ + 'kty' => 'RSA', + 'n' => '...', + 'e' => 'AQAB', + 'kid' => 'prot123', + 'use' => 'sig', + 'alg' => 'RS256', + ], + ], + ], + 'jwks_uri' => 'https://example.com/jwks', + 'signed_jwks_uri' => 'https://example.com/signed-jwks', + 'is_federated' => true, + ]; + + protected function setUp(): void + { + $this->templateFactoryMock = $this->createMock(TemplateFactory::class); + $this->authorizationMock = $this->createMock(Authorization::class); + $this->clientRepositoryMock = $this->createMock(ClientRepository::class); + $this->clientEntityFactoryMock = $this->createMock(ClientEntityFactory::class); + $this->allowedOriginRepositoryMock = $this->createMock(AllowedOriginRepository::class); + $this->formFactoryMock = $this->createMock(FormFactory::class); + $this->sspBridgeMock = $this->createMock(SspBridge::class); + $this->sessionMessagesServiceMock = $this->createMock(SessionMessagesService::class); + $this->routesMock = $this->createMock(Routes::class); + $this->helpersMock = $this->createMock(Helpers::class); + $this->loggerMock = $this->createMock(LoggerService::class); + + $this->clientEntityMock = $this->createMock(ClientEntityInterface::class); + + $this->requestMock = $this->createMock(Request::class); + $this->queryInputBagMock = $this->createMock(ParameterBag::class); + $this->requestMock->query = $this->queryInputBagMock; + $this->requestInputBagMock = $this->createMock(ParameterBag::class); + $this->requestMock->request = $this->requestInputBagMock; + + $this->clientFormMock = $this->createMock(ClientForm::class); + $this->formFactoryMock->method('build')->willReturn($this->clientFormMock); + } + + protected function sut( + ?TemplateFactory $templateFactory = null, + ?Authorization $authorization = null, + ?ClientRepository $clientRepository = null, + ?ClientEntityFactory $clientEntityFactory = null, + ?AllowedOriginRepository $allowedOriginRepository = null, + ?FormFactory $formFactory = null, + ?SspBridge $sspBridge = null, + ?SessionMessagesService $sessionMessagesService = null, + ?Routes $routes = null, + ?Helpers $helpers = null, + ?LoggerService $logger = null, + ): ClientController { + $templateFactory ??= $this->templateFactoryMock; + $authorization ??= $this->authorizationMock; + $clientRepository ??= $this->clientRepositoryMock; + $clientEntityFactory ??= $this->clientEntityFactoryMock; + $allowedOriginRepository ??= $this->allowedOriginRepositoryMock; + $formFactory ??= $this->formFactoryMock; + $sspBridge ??= $this->sspBridgeMock; + $sessionMessagesService ??= $this->sessionMessagesServiceMock; + $routes ??= $this->routesMock; + $helpers ??= $this->helpersMock; + $logger ??= $this->loggerMock; + + return new ClientController( + $templateFactory, + $authorization, + $clientRepository, + $clientEntityFactory, + $allowedOriginRepository, + $formFactory, + $sspBridge, + $sessionMessagesService, + $routes, + $helpers, + $logger, + ); + } + + public function testCanCreateInstance(): void + { + $this->authorizationMock->expects($this->once())->method('requireAdminOrUserWithPermission'); + $this->assertInstanceOf(ClientController::class, $this->sut()); + } + + public function testIndex(): void + { + $this->queryInputBagMock->expects($this->once())->method('getInt')->with('page') + ->willReturn(1); + $this->queryInputBagMock->expects($this->once())->method('getString')->with('q') + ->willReturn('abc'); + $this->clientRepositoryMock->expects($this->once())->method('findPaginated') + ->with(1, 'abc', null)->willReturn([ + 'items' => [$this->clientEntityMock], + 'numPages' => 1, + 'currentPage' => 1, + 'query' => 'abc', + ]); + $this->templateFactoryMock->expects($this->once())->method('build') + ->with('oidc:clients.twig'); + + $this->sut()->index($this->requestMock); + } + + public function testShow(): void + { + $this->queryInputBagMock->expects($this->once())->method('getString')->willReturn('clientId'); + $this->clientEntityMock->expects($this->once())->method('getIdentifier')->willReturn('clientId'); + $this->clientRepositoryMock->expects($this->once())->method('findById')->with('clientId') + ->willReturn($this->clientEntityMock); + $this->templateFactoryMock->expects($this->once())->method('build') + ->with('oidc:clients/show.twig'); + + $this->sut()->show($this->requestMock); + } + + public function testShowThrowsIfClientIdNotProvided(): void + { + $this->expectException(OidcException::class); + $this->expectExceptionMessage('Client ID'); + + $this->sut()->show($this->requestMock); + } + + public function testCanResetSecret(): void + { + $this->queryInputBagMock->expects($this->once())->method('getString')->willReturn('clientId'); + $this->clientEntityMock->expects($this->once())->method('getSecret')->willReturn('123'); + $this->clientRepositoryMock->expects($this->once())->method('findById')->with('clientId') + ->willReturn($this->clientEntityMock); + $this->requestInputBagMock->expects($this->once())->method('getString') + ->with('secret')->willReturn('123'); + $this->clientEntityMock->expects($this->once())->method('restoreSecret'); + $this->clientRepositoryMock->expects($this->once())->method('update') + ->with($this->clientEntityMock); + $this->sessionMessagesServiceMock->expects($this->once())->method('addMessage') + ->with($this->stringContains('secret')); + + $this->sut()->resetSecret($this->requestMock); + } + + public function testResetSecretThrowsIfCurrentSecretNotValid(): void + { + $this->queryInputBagMock->expects($this->once())->method('getString')->willReturn('clientId'); + $this->clientEntityMock->expects($this->once())->method('getSecret')->willReturn('123'); + $this->clientRepositoryMock->expects($this->once())->method('findById')->with('clientId') + ->willReturn($this->clientEntityMock); + $this->requestInputBagMock->expects($this->once())->method('getString') + ->with('secret')->willReturn('321'); + + $this->expectException(OidcException::class); + $this->expectExceptionMessage('Client secret'); + + $this->sut()->resetSecret($this->requestMock); + } + + public function testCanDelete(): void + { + $this->queryInputBagMock->expects($this->once())->method('getString')->willReturn('clientId'); + $this->clientEntityMock->expects($this->once())->method('getSecret')->willReturn('123'); + $this->clientRepositoryMock->expects($this->once())->method('findById')->with('clientId') + ->willReturn($this->clientEntityMock); + $this->requestInputBagMock->expects($this->once())->method('getString') + ->with('secret')->willReturn('123'); + $this->sessionMessagesServiceMock->expects($this->once())->method('addMessage') + ->with($this->stringContains('deleted')); + $this->clientRepositoryMock->expects($this->once())->method('delete') + ->with($this->clientEntityMock); + + $this->sut()->delete($this->requestMock); + } + + public function testDeleteThrowsIfCurrentSecretNotValid(): void + { + $this->queryInputBagMock->expects($this->once())->method('getString')->willReturn('clientId'); + $this->clientEntityMock->expects($this->once())->method('getSecret')->willReturn('123'); + $this->clientRepositoryMock->expects($this->once())->method('findById')->with('clientId') + ->willReturn($this->clientEntityMock); + $this->requestInputBagMock->expects($this->once())->method('getString') + ->with('secret')->willReturn('321'); + + $this->expectException(OidcException::class); + $this->expectExceptionMessage('Client secret'); + + $this->sut()->delete($this->requestMock); + } + + public function testCanAdd(): void + { + $this->clientFormMock->expects($this->once())->method('isSuccess')->willReturn(true); + $this->clientFormMock->method('getValues')->willReturn($this->sampleFormData); + $this->clientEntityMock->method('getIdentifier')->willReturn('clientId'); + $this->clientEntityFactoryMock->expects($this->once())->method('fromData') + ->willReturn($this->clientEntityMock); + + $this->sessionMessagesServiceMock->expects($this->once())->method('addMessage') + ->with($this->stringContains('added')); + + $this->clientRepositoryMock->expects($this->once())->method('add') + ->with($this->clientEntityMock); + + $this->allowedOriginRepositoryMock->expects($this->once())->method('set') + ->with('clientId'); + + $this->sut()->add(); + } + + public function testCanShowAddForm(): void + { + $this->clientFormMock->expects($this->once())->method('isSuccess')->willReturn(false); + + $this->templateFactoryMock->expects($this->once())->method('build') + ->with('oidc:clients/add.twig'); + + $this->sut()->add(); + } + + public function testWontAddIfClientIdentifierExists(): void + { + $this->clientFormMock->expects($this->once())->method('isSuccess')->willReturn(true); + $this->clientFormMock->method('getValues')->willReturn($this->sampleFormData); + $this->clientEntityMock->method('getIdentifier')->willReturn('clientId'); + $this->clientEntityFactoryMock->expects($this->once())->method('fromData') + ->willReturn($this->clientEntityMock); + + $this->clientRepositoryMock->expects($this->once())->method('findById') + ->willReturn($this->createMock(ClientEntityInterface::class)); + + $this->sessionMessagesServiceMock->expects($this->once())->method('addMessage') + ->with($this->stringContains('exists')); + + $this->clientRepositoryMock->expects($this->never())->method('add'); + + $this->sut()->add(); + } + + public function testWontAddIfClientEntityIdentifierExists(): void + { + $this->clientFormMock->expects($this->once())->method('isSuccess')->willReturn(true); + $this->clientFormMock->method('getValues')->willReturn($this->sampleFormData); + $this->clientEntityMock->method('getIdentifier')->willReturn('clientId'); + $this->clientEntityMock->method('getEntityIdentifier')->willReturn('https://example.com'); + $this->clientEntityFactoryMock->expects($this->once())->method('fromData') + ->willReturn($this->clientEntityMock); + + $this->clientRepositoryMock->expects($this->once())->method('findByEntityIdentifier') + ->willReturn($this->createMock(ClientEntityInterface::class)); + + $this->sessionMessagesServiceMock->expects($this->once())->method('addMessage') + ->with($this->stringContains('exists')); + + $this->clientRepositoryMock->expects($this->never())->method('add'); + $this->allowedOriginRepositoryMock->expects($this->never())->method('set'); + + $this->sut()->add(); + } + + public function testThrowsForInvalidClientData(): void + { + $data = $this->sampleFormData; + $data['name'] = null; + $this->clientFormMock->expects($this->once())->method('isSuccess')->willReturn(true); + $this->clientFormMock->method('getValues')->willReturn($data); + + $this->expectException(OidcException::class); + $this->expectExceptionMessage('data'); + + $this->sut()->add(); + } + + public function testCanEdit(): void + { + // Original client. + // Enum can't be doubled :/. + $this->clientEntityMock->method('getRegistrationType')->willReturn(RegistrationTypeEnum::Manual); + $this->queryInputBagMock->expects($this->once())->method('getString')->willReturn('clientId'); + $this->clientEntityMock->method('getIdentifier')->willReturn('clientId'); + $this->clientRepositoryMock->expects($this->once())->method('findById')->with('clientId') + ->willReturn($this->clientEntityMock); + + // Updated client. + $updatedClientMock = $this->createMock(ClientEntityInterface::class); + $updatedClientMock->method('getIdentifier')->willReturn('clientId'); + $this->clientFormMock->expects($this->once())->method('isSuccess')->willReturn(true); + $this->clientFormMock->method('getValues')->willReturn($this->sampleFormData); + $this->clientEntityFactoryMock->expects($this->once())->method('fromData') + ->willReturn($updatedClientMock); + + $this->sessionMessagesServiceMock->expects($this->once())->method('addMessage') + ->with($this->stringContains('updated')); + + $this->clientRepositoryMock->expects($this->once())->method('update') + ->with($updatedClientMock); + + $this->allowedOriginRepositoryMock->expects($this->once())->method('set') + ->with('clientId'); + + $this->sut()->edit($this->requestMock); + } + + public function testWontEditIfClientEntityIdentifierExists(): void + { + // Original client. + // Enum can't be doubled :/. + $this->clientEntityMock->method('getRegistrationType')->willReturn(RegistrationTypeEnum::Manual); + $this->queryInputBagMock->expects($this->once())->method('getString')->willReturn('clientId'); + $this->clientEntityMock->method('getIdentifier')->willReturn('clientId'); + $this->clientRepositoryMock->expects($this->once())->method('findById')->with('clientId') + ->willReturn($this->clientEntityMock); + + // Updated client. + $updatedClientMock = $this->createMock(ClientEntityInterface::class); + $updatedClientMock->method('getIdentifier')->willReturn('clientId'); + $updatedClientMock->method('getEntityIdentifier')->willReturn('https://example.com'); + $this->clientFormMock->expects($this->once())->method('isSuccess')->willReturn(true); + $this->clientFormMock->method('getValues')->willReturn($this->sampleFormData); + $this->clientEntityFactoryMock->expects($this->once())->method('fromData') + ->willReturn($updatedClientMock); + + // Additional client with same entity identifier. + $clientWithEntityIdentifier = $this->createMock(ClientEntityInterface::class); + $clientWithEntityIdentifier->method('getEntityIdentifier')->willReturn('https://example.com'); + $this->clientRepositoryMock->expects($this->once())->method('findByEntityIdentifier') + ->with('https://example.com') + ->willReturn($clientWithEntityIdentifier); + + $this->clientRepositoryMock->expects($this->never())->method('update'); + $this->allowedOriginRepositoryMock->expects($this->never())->method('set'); + + $this->sessionMessagesServiceMock->expects($this->once())->method('addMessage') + ->with($this->stringContains('exists')); + + $this->sut()->edit($this->requestMock); + } + + public function testCanShowEditForm(): void + { + $this->queryInputBagMock->expects($this->once())->method('getString')->willReturn('clientId'); + $this->clientEntityMock->method('getIdentifier')->willReturn('clientId'); + $this->clientRepositoryMock->expects($this->once())->method('findById')->with('clientId') + ->willReturn($this->clientEntityMock); + + $this->clientFormMock->expects($this->once())->method('isSuccess')->willReturn(false); + + $this->templateFactoryMock->expects($this->once())->method('build') + ->with('oidc:clients/edit.twig'); + + $this->sut()->edit($this->requestMock); + } +} diff --git a/tests/unit/src/Controllers/Admin/ConfigControllerTest.php b/tests/unit/src/Controllers/Admin/ConfigControllerTest.php new file mode 100644 index 00000000..b648fd57 --- /dev/null +++ b/tests/unit/src/Controllers/Admin/ConfigControllerTest.php @@ -0,0 +1,132 @@ +moduleConfigMock = $this->createMock(ModuleConfig::class); + $this->templateFactoryMock = $this->createMock(TemplateFactory::class); + $this->authorizationMock = $this->createMock(Authorization::class); + $this->databaseMigrationMock = $this->createMock(DatabaseMigration::class); + $this->sessionMessagesServiceMock = $this->createMock(SessionMessagesService::class); + $this->federationMock = $this->createMock(Federation::class); + $this->routesMock = $this->createMock(Routes::class); + + $this->trustMarkFactoryMock = $this->createMock(TrustMarkFactory::class); + $this->federationMock->method('trustMarkFactory')->willReturn($this->trustMarkFactoryMock); + } + + public function sut( + ?ModuleConfig $moduleConfig = null, + ?TemplateFactory $templateFactory = null, + ?Authorization $authorization = null, + ?DatabaseMigration $databaseMigration = null, + ?SessionMessagesService $sessionMessagesService = null, + ?Federation $federation = null, + ?Routes $routes = null, + ): ConfigController { + $moduleConfig ??= $this->moduleConfigMock; + $templateFactory ??= $this->templateFactoryMock; + $authorization ??= $this->authorizationMock; + $databaseMigration ??= $this->databaseMigrationMock; + $sessionMessagesService ??= $this->sessionMessagesServiceMock; + $federation ??= $this->federationMock; + $routes ??= $this->routesMock; + + return new ConfigController( + $moduleConfig, + $templateFactory, + $authorization, + $databaseMigration, + $sessionMessagesService, + $federation, + $routes, + ); + } + + public function testCanCreateInstance(): void + { + $this->authorizationMock->expects($this->once())->method('requireAdmin'); + $this->assertInstanceOf(ConfigController::class, $this->sut()); + } + + public function testCanShowMigrationsScreen(): void + { + $this->templateFactoryMock->expects($this->once())->method('build') + ->with('oidc:config/migrations.twig'); + + $this->sut()->migrations(); + } + + public function testCanRunMigrations(): void + { + $this->databaseMigrationMock->expects($this->once())->method('migrate'); + $this->sessionMessagesServiceMock->expects($this->once())->method('addMessage') + ->with($this->stringContains('migrated')); + + $this->sut()->runMigrations(); + } + + public function testWontRunMigrationsIfAlreadyMigrated(): void + { + $this->databaseMigrationMock->expects($this->once())->method('isMigrated')->willReturn(true); + $this->databaseMigrationMock->expects($this->never())->method('migrate'); + + $this->sut()->runMigrations(); + } + + public function testCanShowProtocolSettingsScreen(): void + { + $this->templateFactoryMock->expects($this->once())->method('build') + ->with('oidc:config/protocol.twig'); + + $this->sut()->protocolSettings(); + } + + public function testCanShowFederationSettingsScreen(): void + { + $this->templateFactoryMock->expects($this->once())->method('build') + ->with('oidc:config/federation.twig'); + + $this->sut()->federationSettings(); + } + + public function testCanIncludeTrustMarksInFederationSettings(): void + { + $this->moduleConfigMock->method('getFederationTrustMarkTokens')->willReturn(['token']); + $this->trustMarkFactoryMock->expects($this->once())->method('fromToken') + ->with($this->stringContains('token')); + + $this->templateFactoryMock->expects($this->once())->method('build') + ->with('oidc:config/federation.twig'); + + $this->sut()->federationSettings(); + } +} diff --git a/tests/unit/src/Controller/AuthorizationControllerTest.php b/tests/unit/src/Controllers/AuthorizationControllerTest.php similarity index 99% rename from tests/unit/src/Controller/AuthorizationControllerTest.php rename to tests/unit/src/Controllers/AuthorizationControllerTest.php index 3f06647c..af04ba46 100644 --- a/tests/unit/src/Controller/AuthorizationControllerTest.php +++ b/tests/unit/src/Controllers/AuthorizationControllerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\oidc\unit\Controller; +namespace SimpleSAML\Test\Module\oidc\unit\Controllers; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\Attributes\DataProvider; @@ -12,7 +12,7 @@ use Psr\Http\Message\ResponseInterface; use SimpleSAML\Auth\ProcessingChain; use SimpleSAML\Module\oidc\Bridges\PsrHttpBridge; -use SimpleSAML\Module\oidc\Controller\AuthorizationController; +use SimpleSAML\Module\oidc\Controllers\AuthorizationController; use SimpleSAML\Module\oidc\Entities\UserEntity; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Server\AuthorizationServer; @@ -23,7 +23,7 @@ use SimpleSAML\Module\oidc\Services\LoggerService; /** - * @covers \SimpleSAML\Module\oidc\Controller\AuthorizationController + * @covers \SimpleSAML\Module\oidc\Controllers\AuthorizationController */ class AuthorizationControllerTest extends TestCase { diff --git a/tests/unit/src/Controller/ConfigurationDiscoveryControllerTest.php b/tests/unit/src/Controllers/ConfigurationDiscoveryControllerTest.php similarity index 91% rename from tests/unit/src/Controller/ConfigurationDiscoveryControllerTest.php rename to tests/unit/src/Controllers/ConfigurationDiscoveryControllerTest.php index 9484b1f9..86040b12 100644 --- a/tests/unit/src/Controller/ConfigurationDiscoveryControllerTest.php +++ b/tests/unit/src/Controllers/ConfigurationDiscoveryControllerTest.php @@ -2,16 +2,16 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\oidc\unit\Controller; +namespace SimpleSAML\Test\Module\oidc\unit\Controllers; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\oidc\Controller\ConfigurationDiscoveryController; +use SimpleSAML\Module\oidc\Controllers\ConfigurationDiscoveryController; use SimpleSAML\Module\oidc\Services\OpMetadataService; /** - * @covers \SimpleSAML\Module\oidc\Controller\ConfigurationDiscoveryController + * @covers \SimpleSAML\Module\oidc\Controllers\ConfigurationDiscoveryController */ class ConfigurationDiscoveryControllerTest extends TestCase { diff --git a/tests/unit/src/Controller/EndSessionControllerTest.php b/tests/unit/src/Controllers/EndSessionControllerTest.php similarity index 97% rename from tests/unit/src/Controller/EndSessionControllerTest.php rename to tests/unit/src/Controllers/EndSessionControllerTest.php index f29ceec1..1d62f7fd 100644 --- a/tests/unit/src/Controller/EndSessionControllerTest.php +++ b/tests/unit/src/Controllers/EndSessionControllerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\oidc\unit\Controller; +namespace SimpleSAML\Test\Module\oidc\unit\Controllers; use Exception; use Laminas\Diactoros\ServerRequest; @@ -13,7 +13,7 @@ use PHPUnit\Framework\TestCase; use SimpleSAML\Error\BadRequest; use SimpleSAML\Module\oidc\Bridges\PsrHttpBridge; -use SimpleSAML\Module\oidc\Controller\EndSessionController; +use SimpleSAML\Module\oidc\Controllers\EndSessionController; use SimpleSAML\Module\oidc\Factories\TemplateFactory; use SimpleSAML\Module\oidc\Server\AuthorizationServer; use SimpleSAML\Module\oidc\Server\RequestTypes\LogoutRequest; @@ -27,7 +27,7 @@ use Symfony\Component\HttpFoundation\Response; /** - * @covers \SimpleSAML\Module\oidc\Controller\EndSessionController + * @covers \SimpleSAML\Module\oidc\Controllers\EndSessionController */ class EndSessionControllerTest extends TestCase { diff --git a/tests/unit/src/Controllers/Federation/EntityStatementControllerTest.php b/tests/unit/src/Controllers/Federation/EntityStatementControllerTest.php new file mode 100644 index 00000000..a2fdc291 --- /dev/null +++ b/tests/unit/src/Controllers/Federation/EntityStatementControllerTest.php @@ -0,0 +1,105 @@ +moduleConfigMock = $this->createMock(ModuleConfig::class); + $this->jsonWebTokenBuilderServiceMock = $this->createMock(JsonWebTokenBuilderService::class); + $this->jsonWebKeySetServiceMock = $this->createMock(JsonWebKeySetService::class); + $this->opMetadataServiceMock = $this->createMock(OpMetadataService::class); + $this->clientRepositoryMock = $this->createMock(ClientRepository::class); + $this->helpersMock = $this->createMock(Helpers::class); + $this->routesMock = $this->createMock(Routes::class); + $this->federationMock = $this->createMock(Federation::class); + $this->federationCacheMock = $this->createMock(FederationCache::class); + } + + protected function sut( + ?ModuleConfig $moduleConfig = null, + ?JsonWebTokenBuilderService $jsonWebTokenBuilderService = null, + ?JsonWebKeySetService $jsonWebKeySetService = null, + ?OpMetadataService $opMetadataService = null, + ?ClientRepository $clientRepository = null, + ?Helpers $helpers = null, + ?Routes $routes = null, + ?Federation $federation = null, + ?FederationCache $federationCache = null, + ): EntityStatementController { + $moduleConfig ??= $this->moduleConfigMock; + $jsonWebTokenBuilderService ??= $this->jsonWebTokenBuilderServiceMock; + $jsonWebKeySetService ??= $this->jsonWebKeySetServiceMock; + $opMetadataService ??= $this->opMetadataServiceMock; + $clientRepository ??= $this->clientRepositoryMock; + $helpers ??= $this->helpersMock; + $routes ??= $this->routesMock; + $federation ??= $this->federationMock; + $federationCache ??= $this->federationCacheMock; + + return new EntityStatementController( + $moduleConfig, + $jsonWebTokenBuilderService, + $jsonWebKeySetService, + $opMetadataService, + $clientRepository, + $helpers, + $routes, + $federation, + $federationCache, + ); + } + + public function testCanCreateInstance(): void + { + $this->moduleConfigMock->expects($this->once())->method('getFederationEnabled')->willReturn(true); + $this->assertInstanceOf(EntityStatementController::class, $this->sut()); + } + + public function testThrowsIfFederationNotEnabled(): void + { + $this->moduleConfigMock->expects($this->once())->method('getFederationEnabled')->willReturn(false); + $this->expectException(OidcServerException::class); + $this->expectExceptionMessage('refused'); + + $this->sut(); + } + + public function testCanGetConfigurationStatement(): void + { + $this->moduleConfigMock->expects($this->once())->method('getFederationEnabled')->willReturn(true); + $this->federationCacheMock->expects($this->once())->method('get')->willReturn(null); + + // TODO mivanci + $this->markTestIncomplete('Move to simplesamlphp/openid library for building entity statements.'); + } +} diff --git a/tests/unit/src/Controller/JwksControllerTest.php b/tests/unit/src/Controllers/JwksControllerTest.php similarity index 90% rename from tests/unit/src/Controller/JwksControllerTest.php rename to tests/unit/src/Controllers/JwksControllerTest.php index 1788eb60..15fe5580 100644 --- a/tests/unit/src/Controller/JwksControllerTest.php +++ b/tests/unit/src/Controllers/JwksControllerTest.php @@ -2,17 +2,17 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\oidc\unit\Controller; +namespace SimpleSAML\Test\Module\oidc\unit\Controllers; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Bridges\PsrHttpBridge; -use SimpleSAML\Module\oidc\Controller\JwksController; +use SimpleSAML\Module\oidc\Controllers\JwksController; use SimpleSAML\Module\oidc\Services\JsonWebKeySetService; /** - * @covers \SimpleSAML\Module\oidc\Controller\JwksController + * @covers \SimpleSAML\Module\oidc\Controllers\JwksController */ class JwksControllerTest extends TestCase { diff --git a/tests/unit/src/Controller/Traits/RequestTraitTest.php b/tests/unit/src/Controllers/Traits/RequestTraitTest.php similarity index 95% rename from tests/unit/src/Controller/Traits/RequestTraitTest.php rename to tests/unit/src/Controllers/Traits/RequestTraitTest.php index 75f8de8a..61217b1e 100644 --- a/tests/unit/src/Controller/Traits/RequestTraitTest.php +++ b/tests/unit/src/Controllers/Traits/RequestTraitTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\oidc\unit\Controller\Traits; +namespace SimpleSAML\Test\Module\oidc\unit\Controllers\Traits; use Exception; use Laminas\Diactoros\Response; @@ -12,12 +12,12 @@ use Psr\Http\Message\ResponseFactoryInterface; use ReflectionMethod; use SimpleSAML\Module\oidc\Bridges\PsrHttpBridge; -use SimpleSAML\Module\oidc\Controller\Traits\RequestTrait; +use SimpleSAML\Module\oidc\Controllers\Traits\RequestTrait; use SimpleSAML\Module\oidc\Repositories\AllowedOriginRepository; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; /** - * @covers \SimpleSAML\Module\oidc\Controller\Traits\RequestTrait + * @covers \SimpleSAML\Module\oidc\Controllers\Traits\RequestTrait */ class RequestTraitTest extends TestCase { diff --git a/tests/unit/src/Controller/UserInfoControllerTest.php b/tests/unit/src/Controllers/UserInfoControllerTest.php similarity index 97% rename from tests/unit/src/Controller/UserInfoControllerTest.php rename to tests/unit/src/Controllers/UserInfoControllerTest.php index 5ad6a7fc..08b08bb3 100644 --- a/tests/unit/src/Controller/UserInfoControllerTest.php +++ b/tests/unit/src/Controllers/UserInfoControllerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\oidc\unit\Controller; +namespace SimpleSAML\Test\Module\oidc\unit\Controllers; use Laminas\Diactoros\ServerRequest; use League\OAuth2\Server\ResourceServer; @@ -11,8 +11,8 @@ use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Error\UserNotFound; use SimpleSAML\Module\oidc\Bridges\PsrHttpBridge; -use SimpleSAML\Module\oidc\Controller\Traits\RequestTrait; -use SimpleSAML\Module\oidc\Controller\UserInfoController; +use SimpleSAML\Module\oidc\Controllers\Traits\RequestTrait; +use SimpleSAML\Module\oidc\Controllers\UserInfoController; use SimpleSAML\Module\oidc\Entities\AccessTokenEntity; use SimpleSAML\Module\oidc\Entities\UserEntity; use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; @@ -22,7 +22,7 @@ use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; /** - * @covers \SimpleSAML\Module\oidc\Controller\UserInfoController + * @covers \SimpleSAML\Module\oidc\Controllers\UserInfoController */ class UserInfoControllerTest extends TestCase { diff --git a/tests/unit/src/Entities/ClaimSetEntityTest.php b/tests/unit/src/Entities/ClaimSetEntityTest.php new file mode 100644 index 00000000..ea7ee3cf --- /dev/null +++ b/tests/unit/src/Entities/ClaimSetEntityTest.php @@ -0,0 +1,22 @@ +assertInstanceOf(ClaimSetEntity::class, $sut); + $this->assertSame('scope', $sut->getScope()); + $this->assertSame(['claim'], $sut->getClaims()); + } +} diff --git a/tests/unit/src/Factories/ClaimTranslatorExtractorFactoryTest.php b/tests/unit/src/Factories/ClaimTranslatorExtractorFactoryTest.php index 5a7dc19b..bd81fef5 100644 --- a/tests/unit/src/Factories/ClaimTranslatorExtractorFactoryTest.php +++ b/tests/unit/src/Factories/ClaimTranslatorExtractorFactoryTest.php @@ -50,7 +50,7 @@ protected function setUp(): void ), ); $this->moduleConfigMock - ->method('getOpenIDPrivateScopes') + ->method('getPrivateScopes') ->willReturn( [ 'customScope1' => [ diff --git a/tests/unit/src/Factories/TemplateFactoryTest.php b/tests/unit/src/Factories/TemplateFactoryTest.php new file mode 100644 index 00000000..c2a40c74 --- /dev/null +++ b/tests/unit/src/Factories/TemplateFactoryTest.php @@ -0,0 +1,117 @@ +sspConfiguration = Configuration::getInstance(); + + $this->moduleConfigMock = $this->createMock(ModuleConfig::class); + $this->menuMock = $this->createMock(Menu::class); + $this->sspBridgeMock = $this->createMock(SspBridge::class); + $this->sessionMessagesServiceMock = $this->createMock(SessionMessagesService::class); + $this->routes = $this->createMock(Routes::class); + + $this->sspBridgeModuleMock = $this->createMock(SspBridge\Module::class); + $this->sspBridgeMock->method('module')->willReturn($this->sspBridgeModuleMock); + $this->sspBridgeModuleAdminMock = $this->createMock(SspBridge\Module\Admin::class); + $this->sspBridgeModuleMock->method('admin')->willReturn($this->sspBridgeModuleAdminMock); + } + + protected function sut( + ?Configuration $configuration = null, + ?ModuleConfig $moduleConfig = null, + ?Menu $menu = null, + ?SspBridge $sspBridge = null, + ?SessionMessagesService $sessionMessagesService = null, + ?Routes $routes = null, + ): TemplateFactory { + $configuration ??= $this->sspConfiguration; + $moduleConfig ??= $this->moduleConfigMock; + $menu ??= $this->menuMock; + $sspBridge ??= $this->sspBridgeMock; + $sessionMessagesService ??= $this->sessionMessagesServiceMock; + $routes ??= $this->routes; + + return new TemplateFactory( + $configuration, + $moduleConfig, + $menu, + $sspBridge, + $sessionMessagesService, + $routes, + ); + } + + public function testCanCreateInstance(): void + { + $this->assertInstanceOf(TemplateFactory::class, $this->sut()); + } + + public function testCanBuildTemplate(): void + { + $template = $this->sut()->build('oidc:clients.twig', [], 'path'); + + $this->assertInstanceOf(Template::class, $template); + } + + public function testCanAddTemplatesFromAdminModule(): void + { + $this->sspBridgeModuleMock->expects($this->once())->method('isModuleEnabled') + ->with('admin')->willReturn(true); + $this->sspBridgeModuleAdminMock->expects($this->once())->method('buildSspAdminMenu') + ->willReturn(new \SimpleSAML\Module\admin\Controller\Menu()); // SSP Admin Menu is final so can't be mocked. + + $this->sut()->build('oidc:clients.twig'); + } + + public function testCanSetActiveHrefPath(): void + { + $this->menuMock->expects($this->once())->method('setActiveHrefPath'); + $this->menuMock->expects($this->once())->method('getActiveHrefPath'); + + $sut = $this->sut(); + $sut->setActiveHrefPath('path'); + $sut->getActiveHrefPath(); + } + + public function testCanSetTemplateFactoryProperties(): void + { + $sut = $this->sut(); + $this->assertInstanceOf(TemplateFactory::class, $sut->setShowMenu(true)); + $this->assertInstanceOf(TemplateFactory::class, $sut->setIncludeDefaultMenuItems(true)); + $this->assertInstanceOf(TemplateFactory::class, $sut->setShowModuleName(true)); + $this->assertInstanceOf(TemplateFactory::class, $sut->setShowSubPageTitle(true)); + } +} diff --git a/tests/unit/src/ModuleConfigTest.php b/tests/unit/src/ModuleConfigTest.php index 334be5db..4c6e0a81 100644 --- a/tests/unit/src/ModuleConfigTest.php +++ b/tests/unit/src/ModuleConfigTest.php @@ -134,7 +134,7 @@ public function testCanGetModuleUrl(): void public function testCanGetOpenIdScopes(): void { - $this->assertNotEmpty($this->mock()->getOpenIDScopes()); + $this->assertNotEmpty($this->mock()->getScopes()); } public function testCanGetProtocolSigner(): void diff --git a/tests/unit/src/Services/OpMetadataServiceTest.php b/tests/unit/src/Services/OpMetadataServiceTest.php index 3a6c1f16..c56aa882 100644 --- a/tests/unit/src/Services/OpMetadataServiceTest.php +++ b/tests/unit/src/Services/OpMetadataServiceTest.php @@ -25,7 +25,7 @@ public function setUp(): void { $this->moduleConfigMock = $this->createMock(ModuleConfig::class); - $this->moduleConfigMock->expects($this->once())->method('getOpenIDScopes') + $this->moduleConfigMock->expects($this->once())->method('getScopes') ->willReturn(['openid' => 'openid']); $this->moduleConfigMock->expects($this->once())->method('getIssuer') ->willReturn('http://localhost'); From 21b84cd77cdfb4ab8eb69b921dd57d755b5036c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Thu, 5 Dec 2024 15:39:40 +0100 Subject: [PATCH 37/70] Change error code as per OIDF draft 41 --- src/Server/Exceptions/OidcServerException.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Server/Exceptions/OidcServerException.php b/src/Server/Exceptions/OidcServerException.php index 695b5e69..adcce46c 100644 --- a/src/Server/Exceptions/OidcServerException.php +++ b/src/Server/Exceptions/OidcServerException.php @@ -6,6 +6,7 @@ use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseInterface; +use SimpleSAML\OpenID\Codebooks\ErrorsEnum; use Throwable; use function http_build_query; @@ -253,7 +254,16 @@ public static function invalidTrustChain( ): OidcServerException { $errorMessage = 'Trust chain validation failed.'; - $e = new self($errorMessage, 12, 'trust_chain_validation_failed', 400, $hint, $redirectUri, $previous, $state); + $e = new self( + $errorMessage, + 12, + ErrorsEnum::InvalidTrustChain->value, + 400, + $hint, + $redirectUri, + $previous, + $state, + ); $e->useFragmentInHttpResponses($useFragment); return $e; From 74d2f56b444a0d0a18ba2cd8b478a3a62d2537c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Thu, 5 Dec 2024 16:15:36 +0100 Subject: [PATCH 38/70] Explicitly mark nullable parameters --- src/Admin/Menu.php | 2 +- src/Entities/AccessTokenEntity.php | 10 ++-- src/Entities/AuthCodeEntity.php | 6 +- .../Entities/AccessTokenEntityFactory.php | 8 +-- .../Entities/AuthCodeEntityFactory.php | 6 +- src/Factories/Entities/ScopeEntityFactory.php | 4 +- src/Factories/TemplateFactory.php | 2 +- src/ModuleConfig.php | 4 +- src/Repositories/AccessTokenRepository.php | 8 +-- .../AccessTokenRepositoryInterface.php | 4 +- src/Server/AuthorizationServer.php | 4 +- src/Server/Exceptions/OidcServerException.php | 56 +++++++++---------- src/Server/Grants/AuthCodeGrant.php | 4 +- .../Grants/Traits/IssueAccessTokenTrait.php | 4 +- .../BackChannelLogoutHandler.php | 2 +- .../TokenIssuers/RefreshTokenIssuer.php | 2 +- .../Validators/BearerTokenValidator.php | 2 +- src/Services/DatabaseMigration.php | 2 +- .../src/Repositories/UserRepositoryTest.php | 10 ++-- .../src/Utils/RequestParamsResolverTest.php | 6 +- 20 files changed, 73 insertions(+), 73 deletions(-) diff --git a/src/Admin/Menu.php b/src/Admin/Menu.php index 0c5e15a6..0ccbb8ae 100644 --- a/src/Admin/Menu.php +++ b/src/Admin/Menu.php @@ -20,7 +20,7 @@ public function __construct(Item ...$items) array_push($this->items, ...$items); } - public function addItem(Item $menuItem, int $offset = null): void + public function addItem(Item $menuItem, ?int $offset = null): void { $offset ??= count($this->items); diff --git a/src/Entities/AccessTokenEntity.php b/src/Entities/AccessTokenEntity.php index 5873044e..67630acd 100644 --- a/src/Entities/AccessTokenEntity.php +++ b/src/Entities/AccessTokenEntity.php @@ -65,11 +65,11 @@ public function __construct( DateTimeImmutable $expiryDateTime, CryptKey $privateKey, protected JsonWebTokenBuilderService $jsonWebTokenBuilderService, - int|string $userIdentifier = null, - string $authCodeId = null, - array $requestedClaims = null, - bool $isRevoked = false, - Configuration $jwtConfiguration = null, + int|string|null $userIdentifier = null, + ?string $authCodeId = null, + ?array $requestedClaims = null, + ?bool $isRevoked = false, + ?Configuration $jwtConfiguration = null, ) { $this->setIdentifier($id); $this->setClient($clientEntity); diff --git a/src/Entities/AuthCodeEntity.php b/src/Entities/AuthCodeEntity.php index c96488c7..c0bf7c0a 100644 --- a/src/Entities/AuthCodeEntity.php +++ b/src/Entities/AuthCodeEntity.php @@ -40,9 +40,9 @@ public function __construct( OAuth2ClientEntityInterface $client, array $scopes, DateTimeImmutable $expiryDateTime, - string $userIdentifier = null, - string $redirectUri = null, - string $nonce = null, + ?string $userIdentifier = null, + ?string $redirectUri = null, + ?string $nonce = null, bool $isRevoked = false, ) { $this->identifier = $id; diff --git a/src/Factories/Entities/AccessTokenEntityFactory.php b/src/Factories/Entities/AccessTokenEntityFactory.php index f656fa12..f4672e98 100644 --- a/src/Factories/Entities/AccessTokenEntityFactory.php +++ b/src/Factories/Entities/AccessTokenEntityFactory.php @@ -31,10 +31,10 @@ public function fromData( OAuth2ClientEntityInterface $clientEntity, array $scopes, DateTimeImmutable $expiryDateTime, - int|string $userIdentifier = null, - string $authCodeId = null, - array $requestedClaims = null, - bool $isRevoked = false, + int|string|null $userIdentifier = null, + ?string $authCodeId = null, + ?array $requestedClaims = null, + ?bool $isRevoked = false, ): AccessTokenEntity { return new AccessTokenEntity( $id, diff --git a/src/Factories/Entities/AuthCodeEntityFactory.php b/src/Factories/Entities/AuthCodeEntityFactory.php index 30d65939..be0cdee2 100644 --- a/src/Factories/Entities/AuthCodeEntityFactory.php +++ b/src/Factories/Entities/AuthCodeEntityFactory.php @@ -27,9 +27,9 @@ public function fromData( OAuth2ClientEntityInterface $client, array $scopes, DateTimeImmutable $expiryDateTime, - string $userIdentifier = null, - string $redirectUri = null, - string $nonce = null, + ?string $userIdentifier = null, + ?string $redirectUri = null, + ?string $nonce = null, bool $isRevoked = false, ): AuthCodeEntity { return new AuthCodeEntity( diff --git a/src/Factories/Entities/ScopeEntityFactory.php b/src/Factories/Entities/ScopeEntityFactory.php index 36e4da7f..b12ef45a 100644 --- a/src/Factories/Entities/ScopeEntityFactory.php +++ b/src/Factories/Entities/ScopeEntityFactory.php @@ -13,8 +13,8 @@ class ScopeEntityFactory */ public function fromData( string $identifier, - string $description = null, - string $icon = null, + ?string $description = null, + ?string $icon = null, array $claims = [], ): ScopeEntity { return new ScopeEntity( diff --git a/src/Factories/TemplateFactory.php b/src/Factories/TemplateFactory.php index de0d223c..a3039779 100644 --- a/src/Factories/TemplateFactory.php +++ b/src/Factories/TemplateFactory.php @@ -49,7 +49,7 @@ public function __construct( public function build( string $templateName, array $data = [], - string $activeHrefPath = null, + ?string $activeHrefPath = null, ?bool $includeDefaultMenuItems = null, ?bool $showMenu = null, ?bool $showModuleName = null, diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php index 6196ddb2..973d1f16 100644 --- a/src/ModuleConfig.php +++ b/src/ModuleConfig.php @@ -118,7 +118,7 @@ class ModuleConfig public function __construct( string $fileName = self::DEFAULT_FILE_NAME, // Primarily used for easy (unit) testing overrides. array $overrides = [], // Primarily used for easy (unit) testing overrides. - Configuration $sspConfig = null, + ?Configuration $sspConfig = null, private readonly SspBridge $sspBridge = new SspBridge(), ) { $this->moduleConfig = Configuration::loadFromArray( @@ -225,7 +225,7 @@ public function config(): Configuration } // TODO mivanci Move to dedicated \SimpleSAML\Module\oidc\Utils\Routes::getModuleUrl - public function getModuleUrl(string $path = null): string + public function getModuleUrl(?string $path = null): string { $base = $this->sspBridge->module()->getModuleURL(self::MODULE_NAME); diff --git a/src/Repositories/AccessTokenRepository.php b/src/Repositories/AccessTokenRepository.php index 3e1ac577..4298211e 100644 --- a/src/Repositories/AccessTokenRepository.php +++ b/src/Repositories/AccessTokenRepository.php @@ -63,10 +63,10 @@ public function getNewToken( OAuth2ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null, - string $authCodeId = null, - array $requestedClaims = null, - string $id = null, - DateTimeImmutable $expiryDateTime = null, + ?string $authCodeId = null, + ?array $requestedClaims = null, + ?string $id = null, + ?DateTimeImmutable $expiryDateTime = null, ): AccessTokenEntityInterface { if (!is_null($userIdentifier)) { $userIdentifier = (string)$userIdentifier; diff --git a/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php b/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php index dae29026..18453a20 100644 --- a/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php +++ b/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php @@ -29,7 +29,7 @@ public function getNewToken( OAuth2ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null, - string $authCodeId = null, - array $requestedClaims = null, + ?string $authCodeId = null, + ?array $requestedClaims = null, ): AccessTokenEntityInterface; } diff --git a/src/Server/AuthorizationServer.php b/src/Server/AuthorizationServer.php index 70d946e1..4c444e70 100644 --- a/src/Server/AuthorizationServer.php +++ b/src/Server/AuthorizationServer.php @@ -49,8 +49,8 @@ public function __construct( ScopeRepositoryInterface $scopeRepository, CryptKey|string $privateKey, Key|string $encryptionKey, - ResponseTypeInterface $responseType = null, - RequestRulesManager $requestRulesManager = null, + ?ResponseTypeInterface $responseType = null, + ?RequestRulesManager $requestRulesManager = null, ) { parent::__construct( $clientRepository, diff --git a/src/Server/Exceptions/OidcServerException.php b/src/Server/Exceptions/OidcServerException.php index adcce46c..5a9be60d 100644 --- a/src/Server/Exceptions/OidcServerException.php +++ b/src/Server/Exceptions/OidcServerException.php @@ -58,10 +58,10 @@ public function __construct( int $code, string $errorType, int $httpStatusCode = 400, - string $hint = null, - string $redirectUri = null, - Throwable $previous = null, - string $state = null, + ?string $hint = null, + ?string $redirectUri = null, + ?Throwable $previous = null, + ?string $state = null, ) { parent::__construct($message, $code, $errorType, $httpStatusCode, $hint, $redirectUri, $previous); @@ -94,8 +94,8 @@ public function __construct( * @return self */ public static function unsupportedResponseType( - string $redirectUri = null, - string $state = null, + ?string $redirectUri = null, + ?string $state = null, bool $useFragment = false, ): OidcServerException { $errorMessage = 'The response type is not supported by the authorization server.'; @@ -118,7 +118,7 @@ public static function unsupportedResponseType( public static function invalidScope( $scope, $redirectUri = null, - string $state = null, + ?string $state = null, bool $useFragment = false, ): OidcServerException { // OAuthServerException correctly implements this error, however, it misses state parameter. @@ -143,9 +143,9 @@ public static function invalidScope( public static function invalidRequest( $parameter, $hint = null, - Throwable $previous = null, - string $redirectUri = null, - string $state = null, + ?Throwable $previous = null, + ?string $redirectUri = null, + ?string $state = null, bool $useFragment = false, ): OidcServerException { $e = parent::invalidRequest($parameter, $hint, $previous); @@ -168,8 +168,8 @@ public static function invalidRequest( public static function accessDenied( $hint = null, $redirectUri = null, - Throwable $previous = null, - string $state = null, + ?Throwable $previous = null, + ?string $state = null, bool $useFragment = false, ): OidcServerException { $e = parent::accessDenied($hint, $redirectUri, $previous); @@ -191,10 +191,10 @@ public static function accessDenied( * @return self */ public static function loginRequired( - string $hint = null, - string $redirectUri = null, - Throwable $previous = null, - string $state = null, + ?string $hint = null, + ?string $redirectUri = null, + ?Throwable $previous = null, + ?string $state = null, bool $useFragment = false, ): OidcServerException { $errorMessage = "End-User is not already authenticated."; @@ -217,10 +217,10 @@ public static function loginRequired( * @return self */ public static function requestNotSupported( - string $hint = null, - string $redirectUri = null, - Throwable $previous = null, - string $state = null, + ?string $hint = null, + ?string $redirectUri = null, + ?Throwable $previous = null, + ?string $state = null, bool $useFragment = false, ): OidcServerException { $errorMessage = "Request object not supported."; @@ -240,16 +240,16 @@ public static function requestNotSupported( * @return self * @psalm-suppress LessSpecificImplementedReturnType */ - public static function invalidRefreshToken($hint = null, Throwable $previous = null): OidcServerException + public static function invalidRefreshToken($hint = null, ?Throwable $previous = null): OidcServerException { return new self('The refresh token is invalid.', 8, 'invalid_grant', 400, $hint, null, $previous); } public static function invalidTrustChain( - string $hint = null, - string $redirectUri = null, - Throwable $previous = null, - string $state = null, + ?string $hint = null, + ?string $redirectUri = null, + ?Throwable $previous = null, + ?string $state = null, bool $useFragment = false, ): OidcServerException { $errorMessage = 'Trust chain validation failed.'; @@ -278,7 +278,7 @@ public static function invalidTrustChain( * @return self * @psalm-suppress LessSpecificImplementedReturnType */ - public static function forbidden(string $hint = null, Throwable $previous = null): OidcServerException + public static function forbidden(?string $hint = null, ?Throwable $previous = null): OidcServerException { return new self( 'Request understood, but refused to process it.', @@ -314,7 +314,7 @@ public function setPayload(array $payload): void /** * @param string|null $redirectUri Set to string, or unset it with null */ - public function setRedirectUri(string $redirectUri = null): void + public function setRedirectUri(?string $redirectUri = null): void { $this->redirectUri = $redirectUri; } @@ -347,7 +347,7 @@ public function getRedirectUri(): ?string /** * @param string|null $state Set to string, or unset it with null */ - public function setState(string $state = null): void + public function setState(?string $state = null): void { if ($state === null) { unset($this->payload['state']); diff --git a/src/Server/Grants/AuthCodeGrant.php b/src/Server/Grants/AuthCodeGrant.php index aec720b9..5d73bcaf 100644 --- a/src/Server/Grants/AuthCodeGrant.php +++ b/src/Server/Grants/AuthCodeGrant.php @@ -314,7 +314,7 @@ protected function issueOidcAuthCode( string $userIdentifier, string $redirectUri, array $scopes = [], - string $nonce = null, + ?string $nonce = null, ): AuthCodeEntityInterface { $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; @@ -748,7 +748,7 @@ public function validateAuthorizationRequestWithCheckerResultBag( */ protected function issueRefreshToken( OAuth2AccessTokenEntityInterface $accessToken, - string $authCodeId = null, + ?string $authCodeId = null, ): ?RefreshTokenEntityInterface { if (! is_a($accessToken, AccessTokenEntityInterface::class)) { throw OidcServerException::serverError('Unexpected access token entity type.'); diff --git a/src/Server/Grants/Traits/IssueAccessTokenTrait.php b/src/Server/Grants/Traits/IssueAccessTokenTrait.php index 742c756b..6660ec92 100644 --- a/src/Server/Grants/Traits/IssueAccessTokenTrait.php +++ b/src/Server/Grants/Traits/IssueAccessTokenTrait.php @@ -48,8 +48,8 @@ protected function issueAccessToken( ClientEntityInterface $client, $userIdentifier = null, array $scopes = [], - string $authCodeId = null, - array $requestedClaims = null, + ?string $authCodeId = null, + ?array $requestedClaims = null, ): AccessTokenEntityInterface { $maxGenerationAttempts = AbstractGrant::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; diff --git a/src/Server/LogoutHandlers/BackChannelLogoutHandler.php b/src/Server/LogoutHandlers/BackChannelLogoutHandler.php index a0987572..e1fb8478 100644 --- a/src/Server/LogoutHandlers/BackChannelLogoutHandler.php +++ b/src/Server/LogoutHandlers/BackChannelLogoutHandler.php @@ -29,7 +29,7 @@ public function __construct( * @param \GuzzleHttp\HandlerStack|null $handlerStack For easier testing * @throws \League\OAuth2\Server\Exception\OAuthServerException */ - public function handle(array $relyingPartyAssociations, HandlerStack $handlerStack = null): void + public function handle(array $relyingPartyAssociations, ?HandlerStack $handlerStack = null): void { $clientConfig = ['timeout' => 3, 'verify' => false, 'handler' => $handlerStack]; diff --git a/src/Server/TokenIssuers/RefreshTokenIssuer.php b/src/Server/TokenIssuers/RefreshTokenIssuer.php index 8aad35d2..f136dded 100644 --- a/src/Server/TokenIssuers/RefreshTokenIssuer.php +++ b/src/Server/TokenIssuers/RefreshTokenIssuer.php @@ -35,7 +35,7 @@ public function __construct( public function issue( Oauth2TokenEntityInterface $accessToken, DateInterval $refreshTokenTtl, - string $authCodeId = null, + ?string $authCodeId = null, int $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS, ): ?RefreshTokenEntityInterface { if (! is_a($accessToken, AccessTokenEntityInterface::class)) { diff --git a/src/Server/Validators/BearerTokenValidator.php b/src/Server/Validators/BearerTokenValidator.php index c6a80572..94c7b183 100644 --- a/src/Server/Validators/BearerTokenValidator.php +++ b/src/Server/Validators/BearerTokenValidator.php @@ -44,7 +44,7 @@ class BearerTokenValidator extends OAuth2BearerTokenValidator public function __construct( AccessTokenRepositoryInterface $accessTokenRepository, CryptKey $publicKey, - DateInterval $jwtValidAtDateLeeway = null, + ?DateInterval $jwtValidAtDateLeeway = null, protected LoggerService $loggerService = new LoggerService(), ) { parent::__construct($accessTokenRepository, $jwtValidAtDateLeeway); diff --git a/src/Services/DatabaseMigration.php b/src/Services/DatabaseMigration.php index 8f4f3a74..a4936e88 100644 --- a/src/Services/DatabaseMigration.php +++ b/src/Services/DatabaseMigration.php @@ -30,7 +30,7 @@ class DatabaseMigration { private readonly Database $database; - public function __construct(Database $database = null) + public function __construct(?Database $database = null) { $this->database = $database ?? Database::getInstance(); } diff --git a/tests/unit/src/Repositories/UserRepositoryTest.php b/tests/unit/src/Repositories/UserRepositoryTest.php index fc2e7270..ec2189b0 100644 --- a/tests/unit/src/Repositories/UserRepositoryTest.php +++ b/tests/unit/src/Repositories/UserRepositoryTest.php @@ -79,11 +79,11 @@ protected function setUp(): void } protected function mock( - ModuleConfig|MockObject $moduleConfig = null, - Database|MockObject $database = null, - ProtocolCache|MockObject $protocolCache = null, - Helpers|MockObject $helpers = null, - UserEntityFactory|MockObject $userEntityFactory = null, + ?ModuleConfig $moduleConfig = null, + ?Database $database = null, + ?ProtocolCache $protocolCache = null, + ?Helpers $helpers = null, + ?UserEntityFactory $userEntityFactory = null, ): UserRepository { $moduleConfig ??= $this->moduleConfigMock; $database ??= $this->database; // Let's use real database instance for tests by default. diff --git a/tests/unit/src/Utils/RequestParamsResolverTest.php b/tests/unit/src/Utils/RequestParamsResolverTest.php index da084d8e..0cbc269a 100644 --- a/tests/unit/src/Utils/RequestParamsResolverTest.php +++ b/tests/unit/src/Utils/RequestParamsResolverTest.php @@ -57,9 +57,9 @@ protected function setUp(): void } protected function mock( - MockObject $helpersMock = null, - MockObject $coreMock = null, - MockObject $federationMock = null, + ?MockObject $helpersMock = null, + ?MockObject $coreMock = null, + ?MockObject $federationMock = null, ): RequestParamsResolver { $helpersMock ??= $this->helpersMock; $coreMock ??= $this->coreMock; From e9dd29e14bd32579d755664cbd95a49fcfcf8cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Thu, 5 Dec 2024 16:17:15 +0100 Subject: [PATCH 39/70] Add PHP v8.4 to GH PHP version matrix --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c8f89f8c..b8ac67af 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ["8.2", "8.3"] + php-versions: ["8.2", "8.3", "8.4"] steps: - name: Setup PHP, with composer and extensions From 143c7dc0f53319f9f64b5728039159d54d1c5ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Thu, 5 Dec 2024 16:31:13 +0100 Subject: [PATCH 40/70] Skip PHP v8.4 GH action check for now as psalm is not ready --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b8ac67af..c8f89f8c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ["8.2", "8.3", "8.4"] + php-versions: ["8.2", "8.3"] steps: - name: Setup PHP, with composer and extensions From 20c6cbf40a719435418f8b9d9ddbb1e0084c8269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Thu, 5 Dec 2024 16:42:28 +0100 Subject: [PATCH 41/70] Start testing with SSP v2.3 --- README.md | 4 ++-- UPGRADE.md | 2 +- composer.json | 7 ++++--- docker/Dockerfile | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 797bdfea..6b3c0e94 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ PHP version requirement changes in minor releases for SimpleSAMLphp. | OIDC module | Tested SimpleSAMLphp | PHP | Note | |:------------|:---------------------|:------:|-----------------------------| -| v6.\* | v2.2.\* | \>=8.2 | Recommended | +| v6.\* | v2.3.\* | \>=8.2 | Recommended | | v5.\* | v2.1.\* | \>=8.1 | | | v4.\* | v2.0.\* | \>=8.0 | | | v3.\* | v2.0.\* | \>=7.4 | Abandoned from August 2023. | @@ -329,7 +329,7 @@ docker run --name ssp-oidc-dev \ --mount type=bind,source="$(pwd)/docker/ssp/oidc_module.crt",target=/var/simplesamlphp/cert/oidc_module.crt,readonly \ --mount type=bind,source="$(pwd)/docker/ssp/oidc_module.key",target=/var/simplesamlphp/cert/oidc_module.key,readonly \ --mount type=bind,source="$(pwd)/docker/apache-override.cf",target=/etc/apache2/sites-enabled/ssp-override.cf,readonly \ - -p 443:443 cirrusid/simplesamlphp:v2.2.2 + -p 443:443 cirrusid/simplesamlphp:v2.3.5 ``` Visit https://localhost/simplesaml/ and confirm you get the default page. diff --git a/UPGRADE.md b/UPGRADE.md index a6f42115..9a766943 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -91,7 +91,7 @@ has been refactored: - upgraded to v5 of lcobucci/jwt https://github.com/lcobucci/jwt - upgraded to v3 of laminas/laminas-diactoros https://github.com/laminas/laminas-diactoros -- SimpleSAMLphp version used during development was bumped to v2.2 +- SimpleSAMLphp version used during development was bumped to v2.3 - In Authorization Code Flow, a new validation was added which checks for 'openid' value in 'scope' parameter. Up to now, 'openid' value was dynamically added if not present. In Implicit Code Flow this validation was already present. diff --git a/composer.json b/composer.json index 8c09654f..69de0580 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "friendsofphp/php-cs-fixer": "^3", "phpunit/phpunit": "^10", "rector/rector": "^0.18.3", - "simplesamlphp/simplesamlphp": "2.2.*", + "simplesamlphp/simplesamlphp": "2.3.*", "simplesamlphp/simplesamlphp-test-framework": "^1.5", "squizlabs/php_codesniffer": "^3", "vimeo/psalm": "^5", @@ -56,9 +56,10 @@ }, "sort-packages": true, "allow-plugins": { - "simplesamlphp/composer-module-installer": true, "dealerdirect/phpcodesniffer-composer-installer": true, - "phpstan/extension-installer": true + "phpstan/extension-installer": true, + "simplesamlphp/composer-module-installer": true, + "simplesamlphp/composer-xmlprovider-installer": true }, "cache-dir": "build/composer" }, diff --git a/docker/Dockerfile b/docker/Dockerfile index c8a12a77..46543010 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ -#FROM cirrusid/simplesamlphp:v2.2.2 -FROM cicnavi/simplesamlphp:dev +FROM cirrusid/simplesamlphp:v2.3.5 +#FROM cicnavi/simplesamlphp:dev RUN apt-get update && apt-get install -y sqlite3 # Prepopulate the DB with items needed for testing From 4cdf7a4305f651f5f4ec81ccc8e962a3323d7b91 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Fri, 6 Dec 2024 12:36:54 +0100 Subject: [PATCH 42/70] Start testing with SSP 2.3 (#268) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change error code as per OIDF draft 41 * Explicitly mark nullable parameters * Add PHP v8.4 to GH PHP version matrix * Skip PHP v8.4 GH action check for now as psalm is not ready * Start testing with SSP v2.3 --------- Co-authored-by: Marko Ivančić --- README.md | 4 +- UPGRADE.md | 2 +- composer.json | 7 +- docker/Dockerfile | 4 +- src/Admin/Menu.php | 2 +- src/Entities/AccessTokenEntity.php | 10 +-- src/Entities/AuthCodeEntity.php | 6 +- .../Entities/AccessTokenEntityFactory.php | 8 +-- .../Entities/AuthCodeEntityFactory.php | 6 +- src/Factories/Entities/ScopeEntityFactory.php | 4 +- src/Factories/TemplateFactory.php | 2 +- src/ModuleConfig.php | 4 +- src/Repositories/AccessTokenRepository.php | 8 +-- .../AccessTokenRepositoryInterface.php | 4 +- src/Server/AuthorizationServer.php | 4 +- src/Server/Exceptions/OidcServerException.php | 68 +++++++++++-------- src/Server/Grants/AuthCodeGrant.php | 4 +- .../Grants/Traits/IssueAccessTokenTrait.php | 4 +- .../BackChannelLogoutHandler.php | 2 +- .../TokenIssuers/RefreshTokenIssuer.php | 2 +- .../Validators/BearerTokenValidator.php | 2 +- src/Services/DatabaseMigration.php | 2 +- .../src/Repositories/UserRepositoryTest.php | 10 +-- .../src/Utils/RequestParamsResolverTest.php | 6 +- 24 files changed, 93 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 797bdfea..6b3c0e94 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ PHP version requirement changes in minor releases for SimpleSAMLphp. | OIDC module | Tested SimpleSAMLphp | PHP | Note | |:------------|:---------------------|:------:|-----------------------------| -| v6.\* | v2.2.\* | \>=8.2 | Recommended | +| v6.\* | v2.3.\* | \>=8.2 | Recommended | | v5.\* | v2.1.\* | \>=8.1 | | | v4.\* | v2.0.\* | \>=8.0 | | | v3.\* | v2.0.\* | \>=7.4 | Abandoned from August 2023. | @@ -329,7 +329,7 @@ docker run --name ssp-oidc-dev \ --mount type=bind,source="$(pwd)/docker/ssp/oidc_module.crt",target=/var/simplesamlphp/cert/oidc_module.crt,readonly \ --mount type=bind,source="$(pwd)/docker/ssp/oidc_module.key",target=/var/simplesamlphp/cert/oidc_module.key,readonly \ --mount type=bind,source="$(pwd)/docker/apache-override.cf",target=/etc/apache2/sites-enabled/ssp-override.cf,readonly \ - -p 443:443 cirrusid/simplesamlphp:v2.2.2 + -p 443:443 cirrusid/simplesamlphp:v2.3.5 ``` Visit https://localhost/simplesaml/ and confirm you get the default page. diff --git a/UPGRADE.md b/UPGRADE.md index a6f42115..9a766943 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -91,7 +91,7 @@ has been refactored: - upgraded to v5 of lcobucci/jwt https://github.com/lcobucci/jwt - upgraded to v3 of laminas/laminas-diactoros https://github.com/laminas/laminas-diactoros -- SimpleSAMLphp version used during development was bumped to v2.2 +- SimpleSAMLphp version used during development was bumped to v2.3 - In Authorization Code Flow, a new validation was added which checks for 'openid' value in 'scope' parameter. Up to now, 'openid' value was dynamically added if not present. In Implicit Code Flow this validation was already present. diff --git a/composer.json b/composer.json index 8c09654f..69de0580 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "friendsofphp/php-cs-fixer": "^3", "phpunit/phpunit": "^10", "rector/rector": "^0.18.3", - "simplesamlphp/simplesamlphp": "2.2.*", + "simplesamlphp/simplesamlphp": "2.3.*", "simplesamlphp/simplesamlphp-test-framework": "^1.5", "squizlabs/php_codesniffer": "^3", "vimeo/psalm": "^5", @@ -56,9 +56,10 @@ }, "sort-packages": true, "allow-plugins": { - "simplesamlphp/composer-module-installer": true, "dealerdirect/phpcodesniffer-composer-installer": true, - "phpstan/extension-installer": true + "phpstan/extension-installer": true, + "simplesamlphp/composer-module-installer": true, + "simplesamlphp/composer-xmlprovider-installer": true }, "cache-dir": "build/composer" }, diff --git a/docker/Dockerfile b/docker/Dockerfile index c8a12a77..46543010 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ -#FROM cirrusid/simplesamlphp:v2.2.2 -FROM cicnavi/simplesamlphp:dev +FROM cirrusid/simplesamlphp:v2.3.5 +#FROM cicnavi/simplesamlphp:dev RUN apt-get update && apt-get install -y sqlite3 # Prepopulate the DB with items needed for testing diff --git a/src/Admin/Menu.php b/src/Admin/Menu.php index 0c5e15a6..0ccbb8ae 100644 --- a/src/Admin/Menu.php +++ b/src/Admin/Menu.php @@ -20,7 +20,7 @@ public function __construct(Item ...$items) array_push($this->items, ...$items); } - public function addItem(Item $menuItem, int $offset = null): void + public function addItem(Item $menuItem, ?int $offset = null): void { $offset ??= count($this->items); diff --git a/src/Entities/AccessTokenEntity.php b/src/Entities/AccessTokenEntity.php index 5873044e..67630acd 100644 --- a/src/Entities/AccessTokenEntity.php +++ b/src/Entities/AccessTokenEntity.php @@ -65,11 +65,11 @@ public function __construct( DateTimeImmutable $expiryDateTime, CryptKey $privateKey, protected JsonWebTokenBuilderService $jsonWebTokenBuilderService, - int|string $userIdentifier = null, - string $authCodeId = null, - array $requestedClaims = null, - bool $isRevoked = false, - Configuration $jwtConfiguration = null, + int|string|null $userIdentifier = null, + ?string $authCodeId = null, + ?array $requestedClaims = null, + ?bool $isRevoked = false, + ?Configuration $jwtConfiguration = null, ) { $this->setIdentifier($id); $this->setClient($clientEntity); diff --git a/src/Entities/AuthCodeEntity.php b/src/Entities/AuthCodeEntity.php index c96488c7..c0bf7c0a 100644 --- a/src/Entities/AuthCodeEntity.php +++ b/src/Entities/AuthCodeEntity.php @@ -40,9 +40,9 @@ public function __construct( OAuth2ClientEntityInterface $client, array $scopes, DateTimeImmutable $expiryDateTime, - string $userIdentifier = null, - string $redirectUri = null, - string $nonce = null, + ?string $userIdentifier = null, + ?string $redirectUri = null, + ?string $nonce = null, bool $isRevoked = false, ) { $this->identifier = $id; diff --git a/src/Factories/Entities/AccessTokenEntityFactory.php b/src/Factories/Entities/AccessTokenEntityFactory.php index f656fa12..f4672e98 100644 --- a/src/Factories/Entities/AccessTokenEntityFactory.php +++ b/src/Factories/Entities/AccessTokenEntityFactory.php @@ -31,10 +31,10 @@ public function fromData( OAuth2ClientEntityInterface $clientEntity, array $scopes, DateTimeImmutable $expiryDateTime, - int|string $userIdentifier = null, - string $authCodeId = null, - array $requestedClaims = null, - bool $isRevoked = false, + int|string|null $userIdentifier = null, + ?string $authCodeId = null, + ?array $requestedClaims = null, + ?bool $isRevoked = false, ): AccessTokenEntity { return new AccessTokenEntity( $id, diff --git a/src/Factories/Entities/AuthCodeEntityFactory.php b/src/Factories/Entities/AuthCodeEntityFactory.php index 30d65939..be0cdee2 100644 --- a/src/Factories/Entities/AuthCodeEntityFactory.php +++ b/src/Factories/Entities/AuthCodeEntityFactory.php @@ -27,9 +27,9 @@ public function fromData( OAuth2ClientEntityInterface $client, array $scopes, DateTimeImmutable $expiryDateTime, - string $userIdentifier = null, - string $redirectUri = null, - string $nonce = null, + ?string $userIdentifier = null, + ?string $redirectUri = null, + ?string $nonce = null, bool $isRevoked = false, ): AuthCodeEntity { return new AuthCodeEntity( diff --git a/src/Factories/Entities/ScopeEntityFactory.php b/src/Factories/Entities/ScopeEntityFactory.php index 36e4da7f..b12ef45a 100644 --- a/src/Factories/Entities/ScopeEntityFactory.php +++ b/src/Factories/Entities/ScopeEntityFactory.php @@ -13,8 +13,8 @@ class ScopeEntityFactory */ public function fromData( string $identifier, - string $description = null, - string $icon = null, + ?string $description = null, + ?string $icon = null, array $claims = [], ): ScopeEntity { return new ScopeEntity( diff --git a/src/Factories/TemplateFactory.php b/src/Factories/TemplateFactory.php index de0d223c..a3039779 100644 --- a/src/Factories/TemplateFactory.php +++ b/src/Factories/TemplateFactory.php @@ -49,7 +49,7 @@ public function __construct( public function build( string $templateName, array $data = [], - string $activeHrefPath = null, + ?string $activeHrefPath = null, ?bool $includeDefaultMenuItems = null, ?bool $showMenu = null, ?bool $showModuleName = null, diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php index 6196ddb2..973d1f16 100644 --- a/src/ModuleConfig.php +++ b/src/ModuleConfig.php @@ -118,7 +118,7 @@ class ModuleConfig public function __construct( string $fileName = self::DEFAULT_FILE_NAME, // Primarily used for easy (unit) testing overrides. array $overrides = [], // Primarily used for easy (unit) testing overrides. - Configuration $sspConfig = null, + ?Configuration $sspConfig = null, private readonly SspBridge $sspBridge = new SspBridge(), ) { $this->moduleConfig = Configuration::loadFromArray( @@ -225,7 +225,7 @@ public function config(): Configuration } // TODO mivanci Move to dedicated \SimpleSAML\Module\oidc\Utils\Routes::getModuleUrl - public function getModuleUrl(string $path = null): string + public function getModuleUrl(?string $path = null): string { $base = $this->sspBridge->module()->getModuleURL(self::MODULE_NAME); diff --git a/src/Repositories/AccessTokenRepository.php b/src/Repositories/AccessTokenRepository.php index 3e1ac577..4298211e 100644 --- a/src/Repositories/AccessTokenRepository.php +++ b/src/Repositories/AccessTokenRepository.php @@ -63,10 +63,10 @@ public function getNewToken( OAuth2ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null, - string $authCodeId = null, - array $requestedClaims = null, - string $id = null, - DateTimeImmutable $expiryDateTime = null, + ?string $authCodeId = null, + ?array $requestedClaims = null, + ?string $id = null, + ?DateTimeImmutable $expiryDateTime = null, ): AccessTokenEntityInterface { if (!is_null($userIdentifier)) { $userIdentifier = (string)$userIdentifier; diff --git a/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php b/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php index dae29026..18453a20 100644 --- a/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php +++ b/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php @@ -29,7 +29,7 @@ public function getNewToken( OAuth2ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null, - string $authCodeId = null, - array $requestedClaims = null, + ?string $authCodeId = null, + ?array $requestedClaims = null, ): AccessTokenEntityInterface; } diff --git a/src/Server/AuthorizationServer.php b/src/Server/AuthorizationServer.php index 70d946e1..4c444e70 100644 --- a/src/Server/AuthorizationServer.php +++ b/src/Server/AuthorizationServer.php @@ -49,8 +49,8 @@ public function __construct( ScopeRepositoryInterface $scopeRepository, CryptKey|string $privateKey, Key|string $encryptionKey, - ResponseTypeInterface $responseType = null, - RequestRulesManager $requestRulesManager = null, + ?ResponseTypeInterface $responseType = null, + ?RequestRulesManager $requestRulesManager = null, ) { parent::__construct( $clientRepository, diff --git a/src/Server/Exceptions/OidcServerException.php b/src/Server/Exceptions/OidcServerException.php index 695b5e69..5a9be60d 100644 --- a/src/Server/Exceptions/OidcServerException.php +++ b/src/Server/Exceptions/OidcServerException.php @@ -6,6 +6,7 @@ use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseInterface; +use SimpleSAML\OpenID\Codebooks\ErrorsEnum; use Throwable; use function http_build_query; @@ -57,10 +58,10 @@ public function __construct( int $code, string $errorType, int $httpStatusCode = 400, - string $hint = null, - string $redirectUri = null, - Throwable $previous = null, - string $state = null, + ?string $hint = null, + ?string $redirectUri = null, + ?Throwable $previous = null, + ?string $state = null, ) { parent::__construct($message, $code, $errorType, $httpStatusCode, $hint, $redirectUri, $previous); @@ -93,8 +94,8 @@ public function __construct( * @return self */ public static function unsupportedResponseType( - string $redirectUri = null, - string $state = null, + ?string $redirectUri = null, + ?string $state = null, bool $useFragment = false, ): OidcServerException { $errorMessage = 'The response type is not supported by the authorization server.'; @@ -117,7 +118,7 @@ public static function unsupportedResponseType( public static function invalidScope( $scope, $redirectUri = null, - string $state = null, + ?string $state = null, bool $useFragment = false, ): OidcServerException { // OAuthServerException correctly implements this error, however, it misses state parameter. @@ -142,9 +143,9 @@ public static function invalidScope( public static function invalidRequest( $parameter, $hint = null, - Throwable $previous = null, - string $redirectUri = null, - string $state = null, + ?Throwable $previous = null, + ?string $redirectUri = null, + ?string $state = null, bool $useFragment = false, ): OidcServerException { $e = parent::invalidRequest($parameter, $hint, $previous); @@ -167,8 +168,8 @@ public static function invalidRequest( public static function accessDenied( $hint = null, $redirectUri = null, - Throwable $previous = null, - string $state = null, + ?Throwable $previous = null, + ?string $state = null, bool $useFragment = false, ): OidcServerException { $e = parent::accessDenied($hint, $redirectUri, $previous); @@ -190,10 +191,10 @@ public static function accessDenied( * @return self */ public static function loginRequired( - string $hint = null, - string $redirectUri = null, - Throwable $previous = null, - string $state = null, + ?string $hint = null, + ?string $redirectUri = null, + ?Throwable $previous = null, + ?string $state = null, bool $useFragment = false, ): OidcServerException { $errorMessage = "End-User is not already authenticated."; @@ -216,10 +217,10 @@ public static function loginRequired( * @return self */ public static function requestNotSupported( - string $hint = null, - string $redirectUri = null, - Throwable $previous = null, - string $state = null, + ?string $hint = null, + ?string $redirectUri = null, + ?Throwable $previous = null, + ?string $state = null, bool $useFragment = false, ): OidcServerException { $errorMessage = "Request object not supported."; @@ -239,21 +240,30 @@ public static function requestNotSupported( * @return self * @psalm-suppress LessSpecificImplementedReturnType */ - public static function invalidRefreshToken($hint = null, Throwable $previous = null): OidcServerException + public static function invalidRefreshToken($hint = null, ?Throwable $previous = null): OidcServerException { return new self('The refresh token is invalid.', 8, 'invalid_grant', 400, $hint, null, $previous); } public static function invalidTrustChain( - string $hint = null, - string $redirectUri = null, - Throwable $previous = null, - string $state = null, + ?string $hint = null, + ?string $redirectUri = null, + ?Throwable $previous = null, + ?string $state = null, bool $useFragment = false, ): OidcServerException { $errorMessage = 'Trust chain validation failed.'; - $e = new self($errorMessage, 12, 'trust_chain_validation_failed', 400, $hint, $redirectUri, $previous, $state); + $e = new self( + $errorMessage, + 12, + ErrorsEnum::InvalidTrustChain->value, + 400, + $hint, + $redirectUri, + $previous, + $state, + ); $e->useFragmentInHttpResponses($useFragment); return $e; @@ -268,7 +278,7 @@ public static function invalidTrustChain( * @return self * @psalm-suppress LessSpecificImplementedReturnType */ - public static function forbidden(string $hint = null, Throwable $previous = null): OidcServerException + public static function forbidden(?string $hint = null, ?Throwable $previous = null): OidcServerException { return new self( 'Request understood, but refused to process it.', @@ -304,7 +314,7 @@ public function setPayload(array $payload): void /** * @param string|null $redirectUri Set to string, or unset it with null */ - public function setRedirectUri(string $redirectUri = null): void + public function setRedirectUri(?string $redirectUri = null): void { $this->redirectUri = $redirectUri; } @@ -337,7 +347,7 @@ public function getRedirectUri(): ?string /** * @param string|null $state Set to string, or unset it with null */ - public function setState(string $state = null): void + public function setState(?string $state = null): void { if ($state === null) { unset($this->payload['state']); diff --git a/src/Server/Grants/AuthCodeGrant.php b/src/Server/Grants/AuthCodeGrant.php index aec720b9..5d73bcaf 100644 --- a/src/Server/Grants/AuthCodeGrant.php +++ b/src/Server/Grants/AuthCodeGrant.php @@ -314,7 +314,7 @@ protected function issueOidcAuthCode( string $userIdentifier, string $redirectUri, array $scopes = [], - string $nonce = null, + ?string $nonce = null, ): AuthCodeEntityInterface { $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; @@ -748,7 +748,7 @@ public function validateAuthorizationRequestWithCheckerResultBag( */ protected function issueRefreshToken( OAuth2AccessTokenEntityInterface $accessToken, - string $authCodeId = null, + ?string $authCodeId = null, ): ?RefreshTokenEntityInterface { if (! is_a($accessToken, AccessTokenEntityInterface::class)) { throw OidcServerException::serverError('Unexpected access token entity type.'); diff --git a/src/Server/Grants/Traits/IssueAccessTokenTrait.php b/src/Server/Grants/Traits/IssueAccessTokenTrait.php index 742c756b..6660ec92 100644 --- a/src/Server/Grants/Traits/IssueAccessTokenTrait.php +++ b/src/Server/Grants/Traits/IssueAccessTokenTrait.php @@ -48,8 +48,8 @@ protected function issueAccessToken( ClientEntityInterface $client, $userIdentifier = null, array $scopes = [], - string $authCodeId = null, - array $requestedClaims = null, + ?string $authCodeId = null, + ?array $requestedClaims = null, ): AccessTokenEntityInterface { $maxGenerationAttempts = AbstractGrant::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; diff --git a/src/Server/LogoutHandlers/BackChannelLogoutHandler.php b/src/Server/LogoutHandlers/BackChannelLogoutHandler.php index a0987572..e1fb8478 100644 --- a/src/Server/LogoutHandlers/BackChannelLogoutHandler.php +++ b/src/Server/LogoutHandlers/BackChannelLogoutHandler.php @@ -29,7 +29,7 @@ public function __construct( * @param \GuzzleHttp\HandlerStack|null $handlerStack For easier testing * @throws \League\OAuth2\Server\Exception\OAuthServerException */ - public function handle(array $relyingPartyAssociations, HandlerStack $handlerStack = null): void + public function handle(array $relyingPartyAssociations, ?HandlerStack $handlerStack = null): void { $clientConfig = ['timeout' => 3, 'verify' => false, 'handler' => $handlerStack]; diff --git a/src/Server/TokenIssuers/RefreshTokenIssuer.php b/src/Server/TokenIssuers/RefreshTokenIssuer.php index 8aad35d2..f136dded 100644 --- a/src/Server/TokenIssuers/RefreshTokenIssuer.php +++ b/src/Server/TokenIssuers/RefreshTokenIssuer.php @@ -35,7 +35,7 @@ public function __construct( public function issue( Oauth2TokenEntityInterface $accessToken, DateInterval $refreshTokenTtl, - string $authCodeId = null, + ?string $authCodeId = null, int $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS, ): ?RefreshTokenEntityInterface { if (! is_a($accessToken, AccessTokenEntityInterface::class)) { diff --git a/src/Server/Validators/BearerTokenValidator.php b/src/Server/Validators/BearerTokenValidator.php index c6a80572..94c7b183 100644 --- a/src/Server/Validators/BearerTokenValidator.php +++ b/src/Server/Validators/BearerTokenValidator.php @@ -44,7 +44,7 @@ class BearerTokenValidator extends OAuth2BearerTokenValidator public function __construct( AccessTokenRepositoryInterface $accessTokenRepository, CryptKey $publicKey, - DateInterval $jwtValidAtDateLeeway = null, + ?DateInterval $jwtValidAtDateLeeway = null, protected LoggerService $loggerService = new LoggerService(), ) { parent::__construct($accessTokenRepository, $jwtValidAtDateLeeway); diff --git a/src/Services/DatabaseMigration.php b/src/Services/DatabaseMigration.php index 8f4f3a74..a4936e88 100644 --- a/src/Services/DatabaseMigration.php +++ b/src/Services/DatabaseMigration.php @@ -30,7 +30,7 @@ class DatabaseMigration { private readonly Database $database; - public function __construct(Database $database = null) + public function __construct(?Database $database = null) { $this->database = $database ?? Database::getInstance(); } diff --git a/tests/unit/src/Repositories/UserRepositoryTest.php b/tests/unit/src/Repositories/UserRepositoryTest.php index fc2e7270..ec2189b0 100644 --- a/tests/unit/src/Repositories/UserRepositoryTest.php +++ b/tests/unit/src/Repositories/UserRepositoryTest.php @@ -79,11 +79,11 @@ protected function setUp(): void } protected function mock( - ModuleConfig|MockObject $moduleConfig = null, - Database|MockObject $database = null, - ProtocolCache|MockObject $protocolCache = null, - Helpers|MockObject $helpers = null, - UserEntityFactory|MockObject $userEntityFactory = null, + ?ModuleConfig $moduleConfig = null, + ?Database $database = null, + ?ProtocolCache $protocolCache = null, + ?Helpers $helpers = null, + ?UserEntityFactory $userEntityFactory = null, ): UserRepository { $moduleConfig ??= $this->moduleConfigMock; $database ??= $this->database; // Let's use real database instance for tests by default. diff --git a/tests/unit/src/Utils/RequestParamsResolverTest.php b/tests/unit/src/Utils/RequestParamsResolverTest.php index da084d8e..0cbc269a 100644 --- a/tests/unit/src/Utils/RequestParamsResolverTest.php +++ b/tests/unit/src/Utils/RequestParamsResolverTest.php @@ -57,9 +57,9 @@ protected function setUp(): void } protected function mock( - MockObject $helpersMock = null, - MockObject $coreMock = null, - MockObject $federationMock = null, + ?MockObject $helpersMock = null, + ?MockObject $coreMock = null, + ?MockObject $federationMock = null, ): RequestParamsResolver { $helpersMock ??= $this->helpersMock; $coreMock ??= $this->coreMock; From 6edc11529198f19acc899c83e4c00264d6dddd6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Sat, 14 Dec 2024 18:07:56 +0100 Subject: [PATCH 43/70] Enable testing trust chain resolution in admin UI --- config-templates/module_oidc.php | 27 +++ routing/routes/routes.php | 7 + routing/services/services.yml | 2 + src/Codebooks/LimitsEnum.php | 11 ++ src/Codebooks/RoutesEnum.php | 4 + src/Controllers/Admin/TestController.php | 111 ++++++++++++ src/Controllers/Federation/Test.php | 16 +- src/Factories/RequestRulesManagerFactory.php | 3 + src/Factories/TemplateFactory.php | 11 +- src/Forms/ClientForm.php | 1 + src/Helpers/Arr.php | 21 +++ src/Helpers/Str.php | 12 ++ src/ModuleConfig.php | 33 +++- .../RequestRules/Rules/ClientIdRule.php | 15 +- src/Services/Container.php | 7 + src/Utils/Debug/ArrayLogger.php | 160 ++++++++++++++++++ .../FederationParticipationValidator.php | 38 +++++ src/Utils/Routes.php | 7 + templates/tests/trust-chain-resolution.twig | 91 ++++++++++ tests/unit/src/Helpers/ArrTest.php | 49 ++++++ .../RequestRules/Rules/ClientIdRuleTest.php | 14 +- .../unit/src/Utils/Debug/ArrayLoggerTest.php | 89 ++++++++++ 22 files changed, 711 insertions(+), 18 deletions(-) create mode 100644 src/Codebooks/LimitsEnum.php create mode 100644 src/Controllers/Admin/TestController.php create mode 100644 src/Utils/Debug/ArrayLogger.php create mode 100644 src/Utils/FederationParticipationValidator.php create mode 100644 templates/tests/trust-chain-resolution.twig create mode 100644 tests/unit/src/Helpers/ArrTest.php create mode 100644 tests/unit/src/Utils/Debug/ArrayLoggerTest.php diff --git a/config-templates/module_oidc.php b/config-templates/module_oidc.php index ad985a17..30064f74 100644 --- a/config-templates/module_oidc.php +++ b/config-templates/module_oidc.php @@ -368,6 +368,33 @@ // 'eyJ...GHg', ], + // (optional) Federation participation limit by Trust Marks. This is an array with the following format: + // [ + // 'trust-anchor-id' => [ + // 'limit-id' => [ + // 'trust-mark-id', + // 'trust-mark-id-2', + // ], + // ], + // ], + // Check example below on how this can be used. If federation participation limit is configured for particular + // Trust Anchor ID, at least one combination of "limit ID" => "trust mark list" should be defined. + ModuleConfig::OPTION_FEDERATION_PARTICIPATION_LIMIT_BY_TRUST_MARKS => [ + // We are limiting federation participation using Trust Marks for 'https://ta.example.org/'. + 'https://ta.example.org/' => [ + // Entities must have (at least) one Trust Mark from the list below. + \SimpleSAML\Module\oidc\Codebooks\LimitsEnum::OneOf->value => [ + 'trust-mark-id', + 'trust-mark-id-2', + ], + // Entities must have all Trust Marks from the list below. + \SimpleSAML\Module\oidc\Codebooks\LimitsEnum::AllOf->value => [ + 'trust-mark-id-3', + 'trust-mark-id-4', + ], + ], + ], + // (optional) Dedicated federation cache adapter, used to cache federation artifacts like trust chains, entity // statements, etc. It will also be used for token reuse check in federation context. Setting this option is // recommended in production environments. If set to null, no caching will be used. Can be set to any diff --git a/routing/routes/routes.php b/routing/routes/routes.php index 12f96743..6d52f78a 100644 --- a/routing/routes/routes.php +++ b/routing/routes/routes.php @@ -10,6 +10,7 @@ use SimpleSAML\Module\oidc\Controllers\AccessTokenController; use SimpleSAML\Module\oidc\Controllers\Admin\ClientController; use SimpleSAML\Module\oidc\Controllers\Admin\ConfigController; +use SimpleSAML\Module\oidc\Controllers\Admin\TestController; use SimpleSAML\Module\oidc\Controllers\AuthorizationController; use SimpleSAML\Module\oidc\Controllers\ConfigurationDiscoveryController; use SimpleSAML\Module\oidc\Controllers\EndSessionController; @@ -57,6 +58,12 @@ ->controller([ClientController::class, 'delete']) ->methods([HttpMethodsEnum::POST->value]); + // Testing + + $routes->add(RoutesEnum::AdminTestTrustChainResolution->name, RoutesEnum::AdminTestTrustChainResolution->value) + ->controller([TestController::class, 'trustChainResolution']) + ->methods([HttpMethodsEnum::GET->value, HttpMethodsEnum::POST->value]); + /***************************************************************************************************************** * OpenID Connect ****************************************************************************************************************/ diff --git a/routing/services/services.yml b/routing/services/services.yml index 75e6030e..f120f146 100644 --- a/routing/services/services.yml +++ b/routing/services/services.yml @@ -99,6 +99,8 @@ services: factory: ['@SimpleSAML\Module\oidc\Factories\ResourceServerFactory', 'build'] # Utils + SimpleSAML\Module\oidc\Utils\Debug\ArrayLogger: ~ + SimpleSAML\Module\oidc\Utils\FederationParticipationValidator: ~ SimpleSAML\Module\oidc\Utils\Routes: ~ SimpleSAML\Module\oidc\Utils\RequestParamsResolver: ~ SimpleSAML\Module\oidc\Utils\ClassInstanceBuilder: ~ diff --git a/src/Codebooks/LimitsEnum.php b/src/Codebooks/LimitsEnum.php new file mode 100644 index 00000000..90dd4993 --- /dev/null +++ b/src/Codebooks/LimitsEnum.php @@ -0,0 +1,11 @@ +authorization->requireAdmin(true); + } + + /** + * @throws \SimpleSAML\Error\ConfigurationError + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \SimpleSAML\Module\oidc\Exceptions\OidcException + */ + public function trustChainResolution(Request $request): Response + { + $this->arrayLogger->setWeight(ArrayLogger::WEIGHT_WARNING); + // Let's create new Federation instance so we can inject our debug logger and go without cache. + $federation = new Federation( + supportedAlgorithms: $this->federation->supportedAlgorithms(), + cache: null, + logger: $this->arrayLogger, + ); + + $leafEntityId = $this->moduleConfig->getIssuer(); + $trustChainBag = null; + $resolvedMetadata = []; + $isFormSubmitted = false; + + try { + $trustAnchorIds = $this->moduleConfig->getFederationTrustAnchorIds(); + } catch (\Throwable $exception) { + $this->arrayLogger->error('Module config error: ' . $exception->getMessage()); + $trustAnchorIds = []; + } + + if ($request->isMethod(Request::METHOD_POST)) { + $isFormSubmitted = true; + + !empty($leafEntityId = $request->request->getString('leafEntityId')) || + throw new OidcException('Empty leaf entity ID.'); + !empty($rawTrustAnchorIds = $request->request->getString('trustAnchorIds')) || + throw new OidcException('Empty Trust Anchor IDs.'); + + /** @var non-empty-array $trustAnchorIds */ + $trustAnchorIds = $this->helpers->str()->convertTextToArray($rawTrustAnchorIds); + + try { + $trustChainBag = $federation->trustChainResolver()->for($leafEntityId, $trustAnchorIds); + + foreach ($trustChainBag->getAll() as $index => $trustChain) { + $metadataEntries = []; + foreach (EntityTypesEnum::cases() as $entityTypeEnum) { + try { + $metadataEntries[$entityTypeEnum->value] = + $trustChain->getResolvedMetadata($entityTypeEnum); + } catch (\Throwable $exception) { + $this->arrayLogger->error( + 'Metadata resolving error: ' . $exception->getMessage(), + compact('index', 'entityTypeEnum'), + ); + continue; + } + } + $resolvedMetadata[$index] = array_filter($metadataEntries); + } + } catch (TrustChainException $exception) { + $this->arrayLogger->error('Trust chain error: ' . $exception->getMessage()); + } + } + + $trustAnchorIds = implode("\n", $trustAnchorIds); + $logMessages = $this->arrayLogger->getEntries(); +//dd($this->arrayLogger->getEntries()); + return $this->templateFactory->build( + 'oidc:tests/trust-chain-resolution.twig', + compact( + 'leafEntityId', + 'trustAnchorIds', + 'trustChainBag', + 'resolvedMetadata', + 'logMessages', + 'isFormSubmitted', + ), + RoutesEnum::AdminTestTrustChainResolution->value, + ); + } +} diff --git a/src/Controllers/Federation/Test.php b/src/Controllers/Federation/Test.php index f0e06be4..97eaaee4 100644 --- a/src/Controllers/Federation/Test.php +++ b/src/Controllers/Federation/Test.php @@ -65,25 +65,25 @@ public function __invoke(): Response // $requestObject = $requestObjectFactory->fromToken($unprotectedJws); // dd($requestObject, $requestObject->getPayload(), $requestObject->getHeader()); -// $cache->clear(); + $this->federationCache?->cache->clear(); $trustChain = $this->federation ->trustChainResolver() ->for( - 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ALeaf/', +// 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ALeaf/', // 'https://trust-anchor.testbed.oidcfed.incubator.geant.org/oidc/rp/', // 'https://relying-party-php.testbed.oidcfed.incubator.geant.org/', -// 'https://gorp.testbed.oidcfed.incubator.geant.org', + 'https://gorp.testbed.oidcfed.incubator.geant.org', // 'https://maiv1.incubator.geant.org', [ -// 'https://trust-anchor.testbed.oidcfed.incubator.geant.org/', + 'https://trust-anchor.testbed.oidcfed.incubator.geant.org/', 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ABTrustAnchor/', -// 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/CTrustAnchor/', + 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/CTrustAnchor/', ], - ); - + )->getAll(); +dd($trustChain); $leaf = $trustChain->getResolvedLeaf(); -// dd($leaf); + dd($leaf->getPayload()); $leafFederationJwks = $leaf->getJwks(); // dd($leafFederationJwks); // /** @psalm-suppress PossiblyNullArgument */ diff --git a/src/Factories/RequestRulesManagerFactory.php b/src/Factories/RequestRulesManagerFactory.php index ffc2ec4f..8aa57b32 100644 --- a/src/Factories/RequestRulesManagerFactory.php +++ b/src/Factories/RequestRulesManagerFactory.php @@ -36,6 +36,7 @@ use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; use SimpleSAML\Module\oidc\Utils\FederationCache; +use SimpleSAML\Module\oidc\Utils\FederationParticipationValidator; use SimpleSAML\Module\oidc\Utils\JwksResolver; use SimpleSAML\Module\oidc\Utils\ProtocolCache; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -58,6 +59,7 @@ public function __construct( private readonly Federation $federation, private readonly Helpers $helpers, private readonly JwksResolver $jwksResolver, + private readonly FederationParticipationValidator $federationParticipationValidator, private readonly ?FederationCache $federationCache = null, private readonly ?ProtocolCache $protocolCache = null, ) { @@ -88,6 +90,7 @@ private function getDefaultRules(): array $this->federation, $this->helpers, $this->jwksResolver, + $this->federationParticipationValidator, $this->federationCache, ), new RedirectUriRule($this->requestParamsResolver), diff --git a/src/Factories/TemplateFactory.php b/src/Factories/TemplateFactory.php index a3039779..0350af95 100644 --- a/src/Factories/TemplateFactory.php +++ b/src/Factories/TemplateFactory.php @@ -107,6 +107,13 @@ protected function includeDefaultMenuItems(): void ), ); + $this->oidcMenu->addItem( + $this->oidcMenu->buildItem( + $this->moduleConfig->getModuleUrl(RoutesEnum::AdminClients->value), + Translate::noop('Client Registry'), + ), + ); + $this->oidcMenu->addItem( $this->oidcMenu->buildItem( $this->moduleConfig->getModuleUrl(RoutesEnum::AdminConfigProtocol->value), @@ -123,8 +130,8 @@ protected function includeDefaultMenuItems(): void $this->oidcMenu->addItem( $this->oidcMenu->buildItem( - $this->moduleConfig->getModuleUrl(RoutesEnum::AdminClients->value), - Translate::noop('Client Registry'), + $this->moduleConfig->getModuleUrl(RoutesEnum::AdminTestTrustChainResolution->value), + Translate::noop('Test Trust Chain Resolution'), ), ); } diff --git a/src/Forms/ClientForm.php b/src/Forms/ClientForm.php index 3285f028..c0a272d3 100644 --- a/src/Forms/ClientForm.php +++ b/src/Forms/ClientForm.php @@ -414,6 +414,7 @@ protected function getScopes(): array } /** + * TODO mivanci Move to Str helper. * @return string[] */ protected function convertTextToArrayWithLinesAsValues(string $text): array diff --git a/src/Helpers/Arr.php b/src/Helpers/Arr.php index e15185e5..c7df69ac 100644 --- a/src/Helpers/Arr.php +++ b/src/Helpers/Arr.php @@ -14,4 +14,25 @@ public function ensureStringValues(array $values): array { return array_map(fn(mixed $value): string => (string)$value, $values); } + + public function isValueOneOf(mixed $value, array $set): bool + { + $value = is_array($value) ? $value : [$value]; + return !empty(array_intersect($value, $set)); + } + + public function isValueSubsetOf(mixed $value, array $superset): bool + { + $value = is_array($value) ? $value : [$value]; + + return empty(array_diff($value, $superset)); + } + + public function isValueSupersetOf(mixed $value, array $subset): bool + { + $value = is_array($value) ? $value : [$value]; + + // Opposite of subset... + return $this->isValueSubsetOf($subset, $value); + } } diff --git a/src/Helpers/Str.php b/src/Helpers/Str.php index 4674c786..9218119d 100644 --- a/src/Helpers/Str.php +++ b/src/Helpers/Str.php @@ -16,4 +16,16 @@ public function convertScopesStringToArray(string $scopes, string $delimiter = ' { return array_filter(explode($delimiter, trim($scopes)), fn($scope) => !empty($scope)); } + + /** + * @param non-empty-string $pattern + * @return string[] + */ + public function convertTextToArray(string $text, string $pattern = "/[\t\r\n]+/"): array + { + return array_filter( + preg_split($pattern, $text), + fn(string $line): bool => !empty(trim($line)), + ); + } } diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php index 973d1f16..0ed106de 100644 --- a/src/ModuleConfig.php +++ b/src/ModuleConfig.php @@ -81,6 +81,8 @@ class ModuleConfig final public const OPTION_PROTOCOL_CACHE_ADAPTER = 'protocol_cache_adapter'; final public const OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS = 'protocol_cache_adapter_arguments'; final public const OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION = 'protocol_user_entity_cache_duration'; + final public const OPTION_FEDERATION_PARTICIPATION_LIMIT_BY_TRUST_MARKS = + 'federation_participation_limit_by_trust_marks'; protected static array $standardScopes = [ ScopesEnum::OpenId->value => [ @@ -465,7 +467,7 @@ public function getProtocolUserEntityCacheDuration(): DateInterval /***************************************************************************************************************** - * OpenID Connect related config. + * OpenID Federation related config. ****************************************************************************************************************/ public function getFederationEnabled(): bool @@ -669,4 +671,33 @@ public function getTrustAnchorJwks(string $trustAnchorId): ?array sprintf('Unexpected JWKS format for Trust Anchor %s: %s', $trustAnchorId, var_export($jwks, true)), ); } + + public function getFederationParticipationLimitByTrustMarks(): array + { + return $this->config()->getOptionalArray( + self::OPTION_FEDERATION_PARTICIPATION_LIMIT_BY_TRUST_MARKS, + [], + ); + } + + /** + * @throws \SimpleSAML\Error\ConfigurationError + */ + public function getTrustMarksNeededForFederationParticipationFor(string $trustAnchorId): array + { + $participationLimit = $this->getFederationParticipationLimitByTrustMarks()[$trustAnchorId] ?? []; + if (!is_array($participationLimit)) { + throw new ConfigurationError('Invalid configuration for federation participation limit.'); + } + + return $participationLimit; + } + + /** + * @throws \SimpleSAML\Error\ConfigurationError + */ + public function isFederationParticipationLimitedByTrustMarksFor(string $trustAnchorId): bool + { + return !empty($this->getTrustMarksNeededForFederationParticipationFor($trustAnchorId)); + } } diff --git a/src/Server/RequestRules/Rules/ClientIdRule.php b/src/Server/RequestRules/Rules/ClientIdRule.php index 7f64bb61..bd66acec 100644 --- a/src/Server/RequestRules/Rules/ClientIdRule.php +++ b/src/Server/RequestRules/Rules/ClientIdRule.php @@ -19,6 +19,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Result; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\FederationCache; +use SimpleSAML\Module\oidc\Utils\FederationParticipationValidator; use SimpleSAML\Module\oidc\Utils\JwksResolver; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Codebooks\EntityTypesEnum; @@ -39,6 +40,7 @@ public function __construct( protected Federation $federation, protected Helpers $helpers, protected JwksResolver $jwksResolver, + protected FederationParticipationValidator $federationParticipationValidator, protected ?FederationCache $federationCache = null, ) { parent::__construct($requestParamsResolver); @@ -125,7 +127,7 @@ public function checkRule( $trustChain = $this->federation->trustChainResolver()->for( $clientEntityId, $this->moduleConfig->getFederationTrustAnchorIds(), - ); + )->getShortest(); } catch (ConfigurationError $exception) { throw OidcServerException::serverError( 'invalid OIDC configuration: ' . $exception->getMessage(), @@ -191,7 +193,16 @@ public function checkRule( // Verify signature on Request Object using client JWKS. $requestObject->verifyWithKeySet($clientJwks); - // Signature verified, we can persist (new) client registration. + // Check if federation participation is limited by Trust Marks. + if ( + $this->moduleConfig->isFederationParticipationLimitedByTrustMarksFor( + $trustChain->getResolvedTrustAnchor()->getIssuer(), + ) + ) { + $this->federationParticipationValidator->byTrustMarksFor($trustChain); + } + + // All is verified, We can persist (new) client registration. if ($existingClient) { $this->clientRepository->update($registrationClient); } else { diff --git a/src/Services/Container.php b/src/Services/Container.php index e9a3faa1..5a4a46cb 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -103,6 +103,7 @@ use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; use SimpleSAML\Module\oidc\Utils\ClassInstanceBuilder; use SimpleSAML\Module\oidc\Utils\FederationCache; +use SimpleSAML\Module\oidc\Utils\FederationParticipationValidator; use SimpleSAML\Module\oidc\Utils\JwksResolver; use SimpleSAML\Module\oidc\Utils\ProtocolCache; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -346,6 +347,11 @@ public function __construct() $jwksResolver = new JwksResolver($jwks); $this->services[JwksResolver::class] = $jwksResolver; + $federationParticipationValidator = new FederationParticipationValidator( + $moduleConfig, + $loggerService, + ); + $this->services[FederationParticipationValidator::class] = $federationParticipationValidator; $requestRules = [ new StateRule($requestParamsResolver), @@ -357,6 +363,7 @@ public function __construct() $federation, $helpers, $jwksResolver, + $federationParticipationValidator, $federationCache, ), new RedirectUriRule($requestParamsResolver), diff --git a/src/Utils/Debug/ArrayLogger.php b/src/Utils/Debug/ArrayLogger.php new file mode 100644 index 00000000..d228693f --- /dev/null +++ b/src/Utils/Debug/ArrayLogger.php @@ -0,0 +1,160 @@ +setWeight($weight); + } + + public function setWeight(int $weight): void + { + $this->weight = max(self::WEIGHT_DEBUG, min($weight, self::WEIGHT_EMERGENCY)); + } + + /** + * @inheritDoc + */ + public function emergency(\Stringable|string $message, array $context = []): void + { + // Always log emergency. + $this->entries[] = $this->prepareEntry(LogLevel::EMERGENCY, $message, $context); + } + + /** + * @inheritDoc + */ + public function alert(\Stringable|string $message, array $context = []): void + { + if ($this->weight > self::WEIGH_ALERT) { + return; + } + $this->entries[] = $this->prepareEntry(LogLevel::ALERT, $message, $context); + } + + /** + * @inheritDoc + */ + public function critical(\Stringable|string $message, array $context = []): void + { + if ($this->weight > self::WEIGHT_CRITICAL) { + return; + } + $this->entries[] = $this->prepareEntry(LogLevel::CRITICAL, $message, $context); + } + + /** + * @inheritDoc + */ + public function error(\Stringable|string $message, array $context = []): void + { + if ($this->weight > self::WEIGHT_ERROR) { + return; + } + $this->entries[] = $this->prepareEntry(LogLevel::ERROR, $message, $context); + } + + /** + * @inheritDoc + */ + public function warning(\Stringable|string $message, array $context = []): void + { + if ($this->weight > self::WEIGHT_WARNING) { + return; + } + $this->entries[] = $this->prepareEntry(LogLevel::WARNING, $message, $context); + } + + /** + * @inheritDoc + */ + public function notice(\Stringable|string $message, array $context = []): void + { + if ($this->weight > self::WEIGHT_NOTICE) { + return; + } + $this->entries[] = $this->prepareEntry(LogLevel::NOTICE, $message, $context); + } + + /** + * @inheritDoc + */ + public function info(\Stringable|string $message, array $context = []): void + { + if ($this->weight > self::WEIGHT_INFO) { + return; + } + $this->entries[] = $this->prepareEntry(LogLevel::INFO, $message, $context); + } + + /** + * @inheritDoc + */ + public function debug(\Stringable|string $message, array $context = []): void + { + if ($this->weight > self::WEIGHT_DEBUG) { + return; + } + $this->entries[] = $this->prepareEntry(LogLevel::DEBUG, $message, $context); + } + + /** + * @inheritDoc + */ + public function log($level, \Stringable|string $message, array $context = []): void + { + match ($level) { + LogLevel::EMERGENCY => $this->emergency($message, $context), + LogLevel::ALERT => $this->alert($message, $context), + LogLevel::CRITICAL => $this->critical($message, $context), + LogLevel::ERROR => $this->error($message, $context), + LogLevel::WARNING => $this->warning($message, $context), + LogLevel::NOTICE => $this->notice($message, $context), + LogLevel::INFO => $this->info($message, $context), + LogLevel::DEBUG => $this->debug($message, $context), + default => throw new InvalidArgumentException("Unrecognized log level '$level''"), + }; + } + + public function getEntries(): array + { + return $this->entries; + } + + protected function prepareEntry(string $logLevel, \Stringable|string $message, array $context = []): string + { + return sprintf( + '%s %s %s %s', + $this->helpers->dateTime()->getUtc()->format(DateTimeInterface::RFC3339_EXTENDED), + strtoupper($logLevel), + $message, + empty($context) ? '' : 'Context: ' . var_export($context, true), + ); + } +} diff --git a/src/Utils/FederationParticipationValidator.php b/src/Utils/FederationParticipationValidator.php new file mode 100644 index 00000000..06bd37a5 --- /dev/null +++ b/src/Utils/FederationParticipationValidator.php @@ -0,0 +1,38 @@ +getResolvedTrustAnchor(); + + $trustMarkLimitsRules = $this->moduleConfig + ->getTrustMarksNeededForFederationParticipationFor($trustAnchor->getIssuer()); + + if (empty($trustMarkLimitsRules)) { + $this->loggerService->debug('No Trust Mark limits emposed for ' . $trustAnchor->getIssuer()); + return; + } + + $this->loggerService->debug('Trust Mark limits for ' . $trustAnchor->getIssuer(), $trustMarkLimitsRules); + + //$leaf = $trustChain->getResolvedLeaf(); + //$leafTrustMarks = $leaf->getTrustMarks(); + + // TODO mivanci continue + } +} diff --git a/src/Utils/Routes.php b/src/Utils/Routes.php index d256adf9..7b87f514 100644 --- a/src/Utils/Routes.php +++ b/src/Utils/Routes.php @@ -134,6 +134,13 @@ public function urlAdminClientsDelete(string $clientId, array $parameters = []): return $this->getModuleUrl(RoutesEnum::AdminClientsDelete->value, $parameters); } + // Testing + + public function urlAdminTestTrustChainResolution(array $parameters = []): string + { + return $this->getModuleUrl(RoutesEnum::AdminTestTrustChainResolution->value, $parameters); + } + /***************************************************************************************************************** * OpenID Connect URLs. ****************************************************************************************************************/ diff --git a/templates/tests/trust-chain-resolution.twig b/templates/tests/trust-chain-resolution.twig new file mode 100644 index 00000000..972aa720 --- /dev/null +++ b/templates/tests/trust-chain-resolution.twig @@ -0,0 +1,91 @@ +{% set subPageTitle = 'Test Trust Chain Resolution'|trans %} + +{% extends "@oidc/base.twig" %} + +{% block oidcContent %} + +

+ {{ 'You can use the form below to test Trust Chain resolution from a leaf entity ID to Trust Anchors.'|trans }} + {{ 'By default, form is populated with current OP issuer and configured Trust Anchors, but you are free to adjust entries as needed.'|trans }} + {{ 'Log messages will show if any warnings or errors were raised during chain resolution.'|trans }} +

+ +
+ +
+ + + + + + + {{ 'Enter one Trust Anchor ID per line.'|trans }} + +
+ +
+
+ + {% if isFormSubmitted|default %} + +

{{ 'Log messages'|trans }}

+

+ {% if logMessages|default %} + + {{- logMessages|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}} + + {% else %} + {{ 'No entries.'|trans }} + {% endif %} +

+ +

{{ 'Resolved chains'|trans }}

+ {% if trustChainBag|default %} +

+ {{ 'Total chains:'|trans }} {{ trustChainBag.getCount }} +

+ {% for index, trustChain in trustChainBag.getAll %} +

+ {{ loop.index }}. {{ 'Trust Anchor ID:'|trans }} {{ trustChain.getResolvedTrustAnchor.getIssuer }} +

+ {{ 'Path:'|trans }} +
+ {% for entity in trustChain.getEntities %} + {% if loop.index > 1 %} + ⇘ {{ loop.index0 }}. {{ entity.getSubject }}
+ {% endif %} + {% endfor %} + +
+ {{ 'Resolved metadata:' }}
+ {% if resolvedMetadata[index]|default is not empty %} + + {{- resolvedMetadata[index]|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}} + + {% else %} + {{ 'N/A'|trans }} + {% endif %} +

+ {% if not loop.last %} +

+ {% endif %} + {% endfor %} + {% else %} +

{{ 'No entries.'|trans }}

+ {% endif %} + + {% endif %} + +{% endblock oidcContent -%} diff --git a/tests/unit/src/Helpers/ArrTest.php b/tests/unit/src/Helpers/ArrTest.php new file mode 100644 index 00000000..a6fdd7e1 --- /dev/null +++ b/tests/unit/src/Helpers/ArrTest.php @@ -0,0 +1,49 @@ +assertTrue($this->sut()->isValueOneOf('a', ['a'])); + $this->assertTrue($this->sut()->isValueOneOf(['a'], ['a'])); + $this->assertTrue($this->sut()->isValueOneOf(['a', 'b'], ['a'])); + + $this->assertFalse($this->sut()->isValueOneOf('a', ['b'])); + $this->assertFalse($this->sut()->isValueOneOf(['a'], ['b'])); + } + + public function testIsValueSubsetOf(): void + { + $this->assertTrue($this->sut()->isValueSubsetOf('a', ['a', 'b', 'c'])); + $this->assertTrue($this->sut()->isValueSubsetOf(['a'], ['a', 'b', 'c'])); + $this->assertTrue($this->sut()->isValueSubsetOf(['a', 'b'], ['a', 'b', 'c'])); + + $this->assertFalse($this->sut()->isValueSubsetOf('a', [])); + $this->assertFalse($this->sut()->isValueSubsetOf('a', ['b'])); + $this->assertFalse($this->sut()->isValueSubsetOf(['a', 'c'], ['b'])); + } + + public function testIsValueSupersetOf(): void + { + $this->assertTrue($this->sut()->isValueSupersetOf('a', ['a'])); + $this->assertTrue($this->sut()->isValueSupersetOf(['a'], ['a'])); + $this->assertTrue($this->sut()->isValueSupersetOf(['a', 'b'], ['a'])); + + $this->assertFalse($this->sut()->isValueSupersetOf('a', ['b'])); + $this->assertFalse($this->sut()->isValueSupersetOf(['a'], ['b'])); + } +} diff --git a/tests/unit/src/Server/RequestRules/Rules/ClientIdRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/ClientIdRuleTest.php index 5bf55763..fda3d8ae 100644 --- a/tests/unit/src/Server/RequestRules/Rules/ClientIdRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/ClientIdRuleTest.php @@ -18,6 +18,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientIdRule; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\FederationCache; +use SimpleSAML\Module\oidc\Utils\FederationParticipationValidator; use SimpleSAML\Module\oidc\Utils\JwksResolver; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Federation; @@ -39,6 +40,7 @@ class ClientIdRuleTest extends TestCase protected Stub $clientEntityFactoryStub; protected Stub $helpersStub; protected Stub $jwksResolverStub; + protected Stub $federationParticipationValidatorStub; /** * @throws \Exception @@ -57,9 +59,10 @@ protected function setUp(): void $this->clientEntityFactoryStub = $this->createStub(ClientEntityFactory::class); $this->helpersStub = $this->createStub(Helpers::class); $this->jwksResolverStub = $this->createStub(JwksResolver::class); + $this->federationParticipationValidatorStub = $this->createStub(FederationParticipationValidator::class); } - protected function mock(): ClientIdRule + protected function sut(): ClientIdRule { return new ClientIdRule( $this->requestParamsResolverStub, @@ -69,20 +72,21 @@ protected function mock(): ClientIdRule $this->federationStub, $this->helpersStub, $this->jwksResolverStub, + $this->federationParticipationValidatorStub, $this->federationCacheStub, ); } public function testConstruct(): void { - $this->assertInstanceOf(ClientIdRule::class, $this->mock()); + $this->assertInstanceOf(ClientIdRule::class, $this->sut()); } public function testCheckRuleEmptyClientIdThrows(): void { $this->requestParamsResolverStub->method('getBasedOnAllowedMethods')->willReturn(null); $this->expectException(OidcServerException::class); - $this->mock()->checkRule( + $this->sut()->checkRule( $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, @@ -94,7 +98,7 @@ public function testCheckRuleInvalidClientThrows(): void $this->requestParamsResolverStub->method('getBasedOnAllowedMethods')->willReturn('123'); $this->clientRepositoryStub->method('getClientEntity')->willReturn('invalid'); $this->expectException(OidcServerException::class); - $this->mock()->checkRule( + $this->sut()->checkRule( $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, @@ -110,7 +114,7 @@ public function testCheckRuleForValidClientId(): void $this->requestParamsResolverStub->method('getBasedOnAllowedMethods')->willReturn('123'); $this->clientRepositoryStub->method('getClientEntity')->willReturn($this->clientEntityStub); - $result = $this->mock()->checkRule( + $result = $this->sut()->checkRule( $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, diff --git a/tests/unit/src/Utils/Debug/ArrayLoggerTest.php b/tests/unit/src/Utils/Debug/ArrayLoggerTest.php new file mode 100644 index 00000000..bd519163 --- /dev/null +++ b/tests/unit/src/Utils/Debug/ArrayLoggerTest.php @@ -0,0 +1,89 @@ +helpersMock = $this->createMock(Helpers::class); + $this->dateTimeMock = $this->createMock(Helpers\DateTime::class); + $this->helpersMock->method('dateTime')->willReturn($this->dateTimeMock); + $this->dateTimeMock->method('getUtc')->willReturn(new \DateTimeImmutable()); + $this->weight = ArrayLogger::WEIGHT_DEBUG; + } + + protected function sut( + ?Helpers $helpers = null, + ?int $weight = null, + ): ArrayLogger { + $helpers ??= $this->helpersMock; + $weight ??= $this->weight; + + return new ArrayLogger($helpers, $weight); + } + + public function testCanCreateInstance(): void + { + $this->assertInstanceOf(ArrayLogger::class, $this->sut()); + } + + public function testCanLogEntriesBasedOnWeight(): void + { + $sut = $this->sut(); + $this->assertEmpty($sut->getEntries()); + + $sut->debug('debug message'); + $sut->info('info message'); + $sut->notice('notice message'); + $sut->warning('warning message'); + $sut->error('error message'); + $sut->critical('critical message'); + $sut->alert('alert message'); + $sut->emergency('emergency message'); + $sut->log(LogLevel::DEBUG, 'debug message'); + + $this->assertCount(9, $sut->getEntries()); + + } + + public function testWontLogLessThanEmergency(): void + { + $sut = $this->sut(weight: ArrayLogger::WEIGHT_EMERGENCY); + + $sut->debug('debug message'); + $sut->info('info message'); + $sut->notice('notice message'); + $sut->warning('warning message'); + $sut->error('error message'); + $sut->critical('critical message'); + $sut->alert('alert message'); + + $this->assertEmpty($sut->getEntries()); + + $sut->emergency('emergency message'); + $this->assertNotEmpty($sut->getEntries()); + } + + public function testThrowsOnInvalidLogLevel(): void + { + $this->expectException(InvalidArgumentException::class); + + $this->sut()->log('invalid', 'message'); + } +} From e5e652995a598526b9fd8f6d754b02b6a3655b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Sat, 14 Dec 2024 18:17:07 +0100 Subject: [PATCH 44/70] Fix phpcs --- src/Controllers/Admin/TestController.php | 6 +++--- tests/unit/src/Utils/Debug/ArrayLoggerTest.php | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Controllers/Admin/TestController.php b/src/Controllers/Admin/TestController.php index bbb30af8..7da4c8d5 100644 --- a/src/Controllers/Admin/TestController.php +++ b/src/Controllers/Admin/TestController.php @@ -61,9 +61,9 @@ public function trustChainResolution(Request $request): Response $isFormSubmitted = true; !empty($leafEntityId = $request->request->getString('leafEntityId')) || - throw new OidcException('Empty leaf entity ID.'); + throw new OidcException('Empty leaf entity ID.'); !empty($rawTrustAnchorIds = $request->request->getString('trustAnchorIds')) || - throw new OidcException('Empty Trust Anchor IDs.'); + throw new OidcException('Empty Trust Anchor IDs.'); /** @var non-empty-array $trustAnchorIds */ $trustAnchorIds = $this->helpers->str()->convertTextToArray($rawTrustAnchorIds); @@ -76,7 +76,7 @@ public function trustChainResolution(Request $request): Response foreach (EntityTypesEnum::cases() as $entityTypeEnum) { try { $metadataEntries[$entityTypeEnum->value] = - $trustChain->getResolvedMetadata($entityTypeEnum); + $trustChain->getResolvedMetadata($entityTypeEnum); } catch (\Throwable $exception) { $this->arrayLogger->error( 'Metadata resolving error: ' . $exception->getMessage(), diff --git a/tests/unit/src/Utils/Debug/ArrayLoggerTest.php b/tests/unit/src/Utils/Debug/ArrayLoggerTest.php index bd519163..0a090174 100644 --- a/tests/unit/src/Utils/Debug/ArrayLoggerTest.php +++ b/tests/unit/src/Utils/Debug/ArrayLoggerTest.php @@ -59,7 +59,6 @@ public function testCanLogEntriesBasedOnWeight(): void $sut->log(LogLevel::DEBUG, 'debug message'); $this->assertCount(9, $sut->getEntries()); - } public function testWontLogLessThanEmergency(): void From e6bed6326f00d193e2e49219d74b86bbfb546e0d Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Sat, 14 Dec 2024 18:26:38 +0100 Subject: [PATCH 45/70] Enable testing trust chain resolution in admin UI (#269) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enable testing trust chain resolution in admin UI * Fix phpcs --------- Co-authored-by: Marko Ivančić --- config-templates/module_oidc.php | 27 +++ routing/routes/routes.php | 7 + routing/services/services.yml | 2 + src/Codebooks/LimitsEnum.php | 11 ++ src/Codebooks/RoutesEnum.php | 4 + src/Controllers/Admin/TestController.php | 111 ++++++++++++ src/Controllers/Federation/Test.php | 16 +- src/Factories/RequestRulesManagerFactory.php | 3 + src/Factories/TemplateFactory.php | 11 +- src/Forms/ClientForm.php | 1 + src/Helpers/Arr.php | 21 +++ src/Helpers/Str.php | 12 ++ src/ModuleConfig.php | 33 +++- .../RequestRules/Rules/ClientIdRule.php | 15 +- src/Services/Container.php | 7 + src/Utils/Debug/ArrayLogger.php | 160 ++++++++++++++++++ .../FederationParticipationValidator.php | 38 +++++ src/Utils/Routes.php | 7 + templates/tests/trust-chain-resolution.twig | 91 ++++++++++ tests/unit/src/Helpers/ArrTest.php | 49 ++++++ .../RequestRules/Rules/ClientIdRuleTest.php | 14 +- .../unit/src/Utils/Debug/ArrayLoggerTest.php | 88 ++++++++++ 22 files changed, 710 insertions(+), 18 deletions(-) create mode 100644 src/Codebooks/LimitsEnum.php create mode 100644 src/Controllers/Admin/TestController.php create mode 100644 src/Utils/Debug/ArrayLogger.php create mode 100644 src/Utils/FederationParticipationValidator.php create mode 100644 templates/tests/trust-chain-resolution.twig create mode 100644 tests/unit/src/Helpers/ArrTest.php create mode 100644 tests/unit/src/Utils/Debug/ArrayLoggerTest.php diff --git a/config-templates/module_oidc.php b/config-templates/module_oidc.php index ad985a17..30064f74 100644 --- a/config-templates/module_oidc.php +++ b/config-templates/module_oidc.php @@ -368,6 +368,33 @@ // 'eyJ...GHg', ], + // (optional) Federation participation limit by Trust Marks. This is an array with the following format: + // [ + // 'trust-anchor-id' => [ + // 'limit-id' => [ + // 'trust-mark-id', + // 'trust-mark-id-2', + // ], + // ], + // ], + // Check example below on how this can be used. If federation participation limit is configured for particular + // Trust Anchor ID, at least one combination of "limit ID" => "trust mark list" should be defined. + ModuleConfig::OPTION_FEDERATION_PARTICIPATION_LIMIT_BY_TRUST_MARKS => [ + // We are limiting federation participation using Trust Marks for 'https://ta.example.org/'. + 'https://ta.example.org/' => [ + // Entities must have (at least) one Trust Mark from the list below. + \SimpleSAML\Module\oidc\Codebooks\LimitsEnum::OneOf->value => [ + 'trust-mark-id', + 'trust-mark-id-2', + ], + // Entities must have all Trust Marks from the list below. + \SimpleSAML\Module\oidc\Codebooks\LimitsEnum::AllOf->value => [ + 'trust-mark-id-3', + 'trust-mark-id-4', + ], + ], + ], + // (optional) Dedicated federation cache adapter, used to cache federation artifacts like trust chains, entity // statements, etc. It will also be used for token reuse check in federation context. Setting this option is // recommended in production environments. If set to null, no caching will be used. Can be set to any diff --git a/routing/routes/routes.php b/routing/routes/routes.php index 12f96743..6d52f78a 100644 --- a/routing/routes/routes.php +++ b/routing/routes/routes.php @@ -10,6 +10,7 @@ use SimpleSAML\Module\oidc\Controllers\AccessTokenController; use SimpleSAML\Module\oidc\Controllers\Admin\ClientController; use SimpleSAML\Module\oidc\Controllers\Admin\ConfigController; +use SimpleSAML\Module\oidc\Controllers\Admin\TestController; use SimpleSAML\Module\oidc\Controllers\AuthorizationController; use SimpleSAML\Module\oidc\Controllers\ConfigurationDiscoveryController; use SimpleSAML\Module\oidc\Controllers\EndSessionController; @@ -57,6 +58,12 @@ ->controller([ClientController::class, 'delete']) ->methods([HttpMethodsEnum::POST->value]); + // Testing + + $routes->add(RoutesEnum::AdminTestTrustChainResolution->name, RoutesEnum::AdminTestTrustChainResolution->value) + ->controller([TestController::class, 'trustChainResolution']) + ->methods([HttpMethodsEnum::GET->value, HttpMethodsEnum::POST->value]); + /***************************************************************************************************************** * OpenID Connect ****************************************************************************************************************/ diff --git a/routing/services/services.yml b/routing/services/services.yml index 75e6030e..f120f146 100644 --- a/routing/services/services.yml +++ b/routing/services/services.yml @@ -99,6 +99,8 @@ services: factory: ['@SimpleSAML\Module\oidc\Factories\ResourceServerFactory', 'build'] # Utils + SimpleSAML\Module\oidc\Utils\Debug\ArrayLogger: ~ + SimpleSAML\Module\oidc\Utils\FederationParticipationValidator: ~ SimpleSAML\Module\oidc\Utils\Routes: ~ SimpleSAML\Module\oidc\Utils\RequestParamsResolver: ~ SimpleSAML\Module\oidc\Utils\ClassInstanceBuilder: ~ diff --git a/src/Codebooks/LimitsEnum.php b/src/Codebooks/LimitsEnum.php new file mode 100644 index 00000000..90dd4993 --- /dev/null +++ b/src/Codebooks/LimitsEnum.php @@ -0,0 +1,11 @@ +authorization->requireAdmin(true); + } + + /** + * @throws \SimpleSAML\Error\ConfigurationError + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \SimpleSAML\Module\oidc\Exceptions\OidcException + */ + public function trustChainResolution(Request $request): Response + { + $this->arrayLogger->setWeight(ArrayLogger::WEIGHT_WARNING); + // Let's create new Federation instance so we can inject our debug logger and go without cache. + $federation = new Federation( + supportedAlgorithms: $this->federation->supportedAlgorithms(), + cache: null, + logger: $this->arrayLogger, + ); + + $leafEntityId = $this->moduleConfig->getIssuer(); + $trustChainBag = null; + $resolvedMetadata = []; + $isFormSubmitted = false; + + try { + $trustAnchorIds = $this->moduleConfig->getFederationTrustAnchorIds(); + } catch (\Throwable $exception) { + $this->arrayLogger->error('Module config error: ' . $exception->getMessage()); + $trustAnchorIds = []; + } + + if ($request->isMethod(Request::METHOD_POST)) { + $isFormSubmitted = true; + + !empty($leafEntityId = $request->request->getString('leafEntityId')) || + throw new OidcException('Empty leaf entity ID.'); + !empty($rawTrustAnchorIds = $request->request->getString('trustAnchorIds')) || + throw new OidcException('Empty Trust Anchor IDs.'); + + /** @var non-empty-array $trustAnchorIds */ + $trustAnchorIds = $this->helpers->str()->convertTextToArray($rawTrustAnchorIds); + + try { + $trustChainBag = $federation->trustChainResolver()->for($leafEntityId, $trustAnchorIds); + + foreach ($trustChainBag->getAll() as $index => $trustChain) { + $metadataEntries = []; + foreach (EntityTypesEnum::cases() as $entityTypeEnum) { + try { + $metadataEntries[$entityTypeEnum->value] = + $trustChain->getResolvedMetadata($entityTypeEnum); + } catch (\Throwable $exception) { + $this->arrayLogger->error( + 'Metadata resolving error: ' . $exception->getMessage(), + compact('index', 'entityTypeEnum'), + ); + continue; + } + } + $resolvedMetadata[$index] = array_filter($metadataEntries); + } + } catch (TrustChainException $exception) { + $this->arrayLogger->error('Trust chain error: ' . $exception->getMessage()); + } + } + + $trustAnchorIds = implode("\n", $trustAnchorIds); + $logMessages = $this->arrayLogger->getEntries(); +//dd($this->arrayLogger->getEntries()); + return $this->templateFactory->build( + 'oidc:tests/trust-chain-resolution.twig', + compact( + 'leafEntityId', + 'trustAnchorIds', + 'trustChainBag', + 'resolvedMetadata', + 'logMessages', + 'isFormSubmitted', + ), + RoutesEnum::AdminTestTrustChainResolution->value, + ); + } +} diff --git a/src/Controllers/Federation/Test.php b/src/Controllers/Federation/Test.php index f0e06be4..97eaaee4 100644 --- a/src/Controllers/Federation/Test.php +++ b/src/Controllers/Federation/Test.php @@ -65,25 +65,25 @@ public function __invoke(): Response // $requestObject = $requestObjectFactory->fromToken($unprotectedJws); // dd($requestObject, $requestObject->getPayload(), $requestObject->getHeader()); -// $cache->clear(); + $this->federationCache?->cache->clear(); $trustChain = $this->federation ->trustChainResolver() ->for( - 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ALeaf/', +// 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ALeaf/', // 'https://trust-anchor.testbed.oidcfed.incubator.geant.org/oidc/rp/', // 'https://relying-party-php.testbed.oidcfed.incubator.geant.org/', -// 'https://gorp.testbed.oidcfed.incubator.geant.org', + 'https://gorp.testbed.oidcfed.incubator.geant.org', // 'https://maiv1.incubator.geant.org', [ -// 'https://trust-anchor.testbed.oidcfed.incubator.geant.org/', + 'https://trust-anchor.testbed.oidcfed.incubator.geant.org/', 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ABTrustAnchor/', -// 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/CTrustAnchor/', + 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/CTrustAnchor/', ], - ); - + )->getAll(); +dd($trustChain); $leaf = $trustChain->getResolvedLeaf(); -// dd($leaf); + dd($leaf->getPayload()); $leafFederationJwks = $leaf->getJwks(); // dd($leafFederationJwks); // /** @psalm-suppress PossiblyNullArgument */ diff --git a/src/Factories/RequestRulesManagerFactory.php b/src/Factories/RequestRulesManagerFactory.php index ffc2ec4f..8aa57b32 100644 --- a/src/Factories/RequestRulesManagerFactory.php +++ b/src/Factories/RequestRulesManagerFactory.php @@ -36,6 +36,7 @@ use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; use SimpleSAML\Module\oidc\Utils\FederationCache; +use SimpleSAML\Module\oidc\Utils\FederationParticipationValidator; use SimpleSAML\Module\oidc\Utils\JwksResolver; use SimpleSAML\Module\oidc\Utils\ProtocolCache; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -58,6 +59,7 @@ public function __construct( private readonly Federation $federation, private readonly Helpers $helpers, private readonly JwksResolver $jwksResolver, + private readonly FederationParticipationValidator $federationParticipationValidator, private readonly ?FederationCache $federationCache = null, private readonly ?ProtocolCache $protocolCache = null, ) { @@ -88,6 +90,7 @@ private function getDefaultRules(): array $this->federation, $this->helpers, $this->jwksResolver, + $this->federationParticipationValidator, $this->federationCache, ), new RedirectUriRule($this->requestParamsResolver), diff --git a/src/Factories/TemplateFactory.php b/src/Factories/TemplateFactory.php index a3039779..0350af95 100644 --- a/src/Factories/TemplateFactory.php +++ b/src/Factories/TemplateFactory.php @@ -107,6 +107,13 @@ protected function includeDefaultMenuItems(): void ), ); + $this->oidcMenu->addItem( + $this->oidcMenu->buildItem( + $this->moduleConfig->getModuleUrl(RoutesEnum::AdminClients->value), + Translate::noop('Client Registry'), + ), + ); + $this->oidcMenu->addItem( $this->oidcMenu->buildItem( $this->moduleConfig->getModuleUrl(RoutesEnum::AdminConfigProtocol->value), @@ -123,8 +130,8 @@ protected function includeDefaultMenuItems(): void $this->oidcMenu->addItem( $this->oidcMenu->buildItem( - $this->moduleConfig->getModuleUrl(RoutesEnum::AdminClients->value), - Translate::noop('Client Registry'), + $this->moduleConfig->getModuleUrl(RoutesEnum::AdminTestTrustChainResolution->value), + Translate::noop('Test Trust Chain Resolution'), ), ); } diff --git a/src/Forms/ClientForm.php b/src/Forms/ClientForm.php index 3285f028..c0a272d3 100644 --- a/src/Forms/ClientForm.php +++ b/src/Forms/ClientForm.php @@ -414,6 +414,7 @@ protected function getScopes(): array } /** + * TODO mivanci Move to Str helper. * @return string[] */ protected function convertTextToArrayWithLinesAsValues(string $text): array diff --git a/src/Helpers/Arr.php b/src/Helpers/Arr.php index e15185e5..c7df69ac 100644 --- a/src/Helpers/Arr.php +++ b/src/Helpers/Arr.php @@ -14,4 +14,25 @@ public function ensureStringValues(array $values): array { return array_map(fn(mixed $value): string => (string)$value, $values); } + + public function isValueOneOf(mixed $value, array $set): bool + { + $value = is_array($value) ? $value : [$value]; + return !empty(array_intersect($value, $set)); + } + + public function isValueSubsetOf(mixed $value, array $superset): bool + { + $value = is_array($value) ? $value : [$value]; + + return empty(array_diff($value, $superset)); + } + + public function isValueSupersetOf(mixed $value, array $subset): bool + { + $value = is_array($value) ? $value : [$value]; + + // Opposite of subset... + return $this->isValueSubsetOf($subset, $value); + } } diff --git a/src/Helpers/Str.php b/src/Helpers/Str.php index 4674c786..9218119d 100644 --- a/src/Helpers/Str.php +++ b/src/Helpers/Str.php @@ -16,4 +16,16 @@ public function convertScopesStringToArray(string $scopes, string $delimiter = ' { return array_filter(explode($delimiter, trim($scopes)), fn($scope) => !empty($scope)); } + + /** + * @param non-empty-string $pattern + * @return string[] + */ + public function convertTextToArray(string $text, string $pattern = "/[\t\r\n]+/"): array + { + return array_filter( + preg_split($pattern, $text), + fn(string $line): bool => !empty(trim($line)), + ); + } } diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php index 973d1f16..0ed106de 100644 --- a/src/ModuleConfig.php +++ b/src/ModuleConfig.php @@ -81,6 +81,8 @@ class ModuleConfig final public const OPTION_PROTOCOL_CACHE_ADAPTER = 'protocol_cache_adapter'; final public const OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS = 'protocol_cache_adapter_arguments'; final public const OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION = 'protocol_user_entity_cache_duration'; + final public const OPTION_FEDERATION_PARTICIPATION_LIMIT_BY_TRUST_MARKS = + 'federation_participation_limit_by_trust_marks'; protected static array $standardScopes = [ ScopesEnum::OpenId->value => [ @@ -465,7 +467,7 @@ public function getProtocolUserEntityCacheDuration(): DateInterval /***************************************************************************************************************** - * OpenID Connect related config. + * OpenID Federation related config. ****************************************************************************************************************/ public function getFederationEnabled(): bool @@ -669,4 +671,33 @@ public function getTrustAnchorJwks(string $trustAnchorId): ?array sprintf('Unexpected JWKS format for Trust Anchor %s: %s', $trustAnchorId, var_export($jwks, true)), ); } + + public function getFederationParticipationLimitByTrustMarks(): array + { + return $this->config()->getOptionalArray( + self::OPTION_FEDERATION_PARTICIPATION_LIMIT_BY_TRUST_MARKS, + [], + ); + } + + /** + * @throws \SimpleSAML\Error\ConfigurationError + */ + public function getTrustMarksNeededForFederationParticipationFor(string $trustAnchorId): array + { + $participationLimit = $this->getFederationParticipationLimitByTrustMarks()[$trustAnchorId] ?? []; + if (!is_array($participationLimit)) { + throw new ConfigurationError('Invalid configuration for federation participation limit.'); + } + + return $participationLimit; + } + + /** + * @throws \SimpleSAML\Error\ConfigurationError + */ + public function isFederationParticipationLimitedByTrustMarksFor(string $trustAnchorId): bool + { + return !empty($this->getTrustMarksNeededForFederationParticipationFor($trustAnchorId)); + } } diff --git a/src/Server/RequestRules/Rules/ClientIdRule.php b/src/Server/RequestRules/Rules/ClientIdRule.php index 7f64bb61..bd66acec 100644 --- a/src/Server/RequestRules/Rules/ClientIdRule.php +++ b/src/Server/RequestRules/Rules/ClientIdRule.php @@ -19,6 +19,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Result; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\FederationCache; +use SimpleSAML\Module\oidc\Utils\FederationParticipationValidator; use SimpleSAML\Module\oidc\Utils\JwksResolver; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Codebooks\EntityTypesEnum; @@ -39,6 +40,7 @@ public function __construct( protected Federation $federation, protected Helpers $helpers, protected JwksResolver $jwksResolver, + protected FederationParticipationValidator $federationParticipationValidator, protected ?FederationCache $federationCache = null, ) { parent::__construct($requestParamsResolver); @@ -125,7 +127,7 @@ public function checkRule( $trustChain = $this->federation->trustChainResolver()->for( $clientEntityId, $this->moduleConfig->getFederationTrustAnchorIds(), - ); + )->getShortest(); } catch (ConfigurationError $exception) { throw OidcServerException::serverError( 'invalid OIDC configuration: ' . $exception->getMessage(), @@ -191,7 +193,16 @@ public function checkRule( // Verify signature on Request Object using client JWKS. $requestObject->verifyWithKeySet($clientJwks); - // Signature verified, we can persist (new) client registration. + // Check if federation participation is limited by Trust Marks. + if ( + $this->moduleConfig->isFederationParticipationLimitedByTrustMarksFor( + $trustChain->getResolvedTrustAnchor()->getIssuer(), + ) + ) { + $this->federationParticipationValidator->byTrustMarksFor($trustChain); + } + + // All is verified, We can persist (new) client registration. if ($existingClient) { $this->clientRepository->update($registrationClient); } else { diff --git a/src/Services/Container.php b/src/Services/Container.php index e9a3faa1..5a4a46cb 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -103,6 +103,7 @@ use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; use SimpleSAML\Module\oidc\Utils\ClassInstanceBuilder; use SimpleSAML\Module\oidc\Utils\FederationCache; +use SimpleSAML\Module\oidc\Utils\FederationParticipationValidator; use SimpleSAML\Module\oidc\Utils\JwksResolver; use SimpleSAML\Module\oidc\Utils\ProtocolCache; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; @@ -346,6 +347,11 @@ public function __construct() $jwksResolver = new JwksResolver($jwks); $this->services[JwksResolver::class] = $jwksResolver; + $federationParticipationValidator = new FederationParticipationValidator( + $moduleConfig, + $loggerService, + ); + $this->services[FederationParticipationValidator::class] = $federationParticipationValidator; $requestRules = [ new StateRule($requestParamsResolver), @@ -357,6 +363,7 @@ public function __construct() $federation, $helpers, $jwksResolver, + $federationParticipationValidator, $federationCache, ), new RedirectUriRule($requestParamsResolver), diff --git a/src/Utils/Debug/ArrayLogger.php b/src/Utils/Debug/ArrayLogger.php new file mode 100644 index 00000000..d228693f --- /dev/null +++ b/src/Utils/Debug/ArrayLogger.php @@ -0,0 +1,160 @@ +setWeight($weight); + } + + public function setWeight(int $weight): void + { + $this->weight = max(self::WEIGHT_DEBUG, min($weight, self::WEIGHT_EMERGENCY)); + } + + /** + * @inheritDoc + */ + public function emergency(\Stringable|string $message, array $context = []): void + { + // Always log emergency. + $this->entries[] = $this->prepareEntry(LogLevel::EMERGENCY, $message, $context); + } + + /** + * @inheritDoc + */ + public function alert(\Stringable|string $message, array $context = []): void + { + if ($this->weight > self::WEIGH_ALERT) { + return; + } + $this->entries[] = $this->prepareEntry(LogLevel::ALERT, $message, $context); + } + + /** + * @inheritDoc + */ + public function critical(\Stringable|string $message, array $context = []): void + { + if ($this->weight > self::WEIGHT_CRITICAL) { + return; + } + $this->entries[] = $this->prepareEntry(LogLevel::CRITICAL, $message, $context); + } + + /** + * @inheritDoc + */ + public function error(\Stringable|string $message, array $context = []): void + { + if ($this->weight > self::WEIGHT_ERROR) { + return; + } + $this->entries[] = $this->prepareEntry(LogLevel::ERROR, $message, $context); + } + + /** + * @inheritDoc + */ + public function warning(\Stringable|string $message, array $context = []): void + { + if ($this->weight > self::WEIGHT_WARNING) { + return; + } + $this->entries[] = $this->prepareEntry(LogLevel::WARNING, $message, $context); + } + + /** + * @inheritDoc + */ + public function notice(\Stringable|string $message, array $context = []): void + { + if ($this->weight > self::WEIGHT_NOTICE) { + return; + } + $this->entries[] = $this->prepareEntry(LogLevel::NOTICE, $message, $context); + } + + /** + * @inheritDoc + */ + public function info(\Stringable|string $message, array $context = []): void + { + if ($this->weight > self::WEIGHT_INFO) { + return; + } + $this->entries[] = $this->prepareEntry(LogLevel::INFO, $message, $context); + } + + /** + * @inheritDoc + */ + public function debug(\Stringable|string $message, array $context = []): void + { + if ($this->weight > self::WEIGHT_DEBUG) { + return; + } + $this->entries[] = $this->prepareEntry(LogLevel::DEBUG, $message, $context); + } + + /** + * @inheritDoc + */ + public function log($level, \Stringable|string $message, array $context = []): void + { + match ($level) { + LogLevel::EMERGENCY => $this->emergency($message, $context), + LogLevel::ALERT => $this->alert($message, $context), + LogLevel::CRITICAL => $this->critical($message, $context), + LogLevel::ERROR => $this->error($message, $context), + LogLevel::WARNING => $this->warning($message, $context), + LogLevel::NOTICE => $this->notice($message, $context), + LogLevel::INFO => $this->info($message, $context), + LogLevel::DEBUG => $this->debug($message, $context), + default => throw new InvalidArgumentException("Unrecognized log level '$level''"), + }; + } + + public function getEntries(): array + { + return $this->entries; + } + + protected function prepareEntry(string $logLevel, \Stringable|string $message, array $context = []): string + { + return sprintf( + '%s %s %s %s', + $this->helpers->dateTime()->getUtc()->format(DateTimeInterface::RFC3339_EXTENDED), + strtoupper($logLevel), + $message, + empty($context) ? '' : 'Context: ' . var_export($context, true), + ); + } +} diff --git a/src/Utils/FederationParticipationValidator.php b/src/Utils/FederationParticipationValidator.php new file mode 100644 index 00000000..06bd37a5 --- /dev/null +++ b/src/Utils/FederationParticipationValidator.php @@ -0,0 +1,38 @@ +getResolvedTrustAnchor(); + + $trustMarkLimitsRules = $this->moduleConfig + ->getTrustMarksNeededForFederationParticipationFor($trustAnchor->getIssuer()); + + if (empty($trustMarkLimitsRules)) { + $this->loggerService->debug('No Trust Mark limits emposed for ' . $trustAnchor->getIssuer()); + return; + } + + $this->loggerService->debug('Trust Mark limits for ' . $trustAnchor->getIssuer(), $trustMarkLimitsRules); + + //$leaf = $trustChain->getResolvedLeaf(); + //$leafTrustMarks = $leaf->getTrustMarks(); + + // TODO mivanci continue + } +} diff --git a/src/Utils/Routes.php b/src/Utils/Routes.php index d256adf9..7b87f514 100644 --- a/src/Utils/Routes.php +++ b/src/Utils/Routes.php @@ -134,6 +134,13 @@ public function urlAdminClientsDelete(string $clientId, array $parameters = []): return $this->getModuleUrl(RoutesEnum::AdminClientsDelete->value, $parameters); } + // Testing + + public function urlAdminTestTrustChainResolution(array $parameters = []): string + { + return $this->getModuleUrl(RoutesEnum::AdminTestTrustChainResolution->value, $parameters); + } + /***************************************************************************************************************** * OpenID Connect URLs. ****************************************************************************************************************/ diff --git a/templates/tests/trust-chain-resolution.twig b/templates/tests/trust-chain-resolution.twig new file mode 100644 index 00000000..972aa720 --- /dev/null +++ b/templates/tests/trust-chain-resolution.twig @@ -0,0 +1,91 @@ +{% set subPageTitle = 'Test Trust Chain Resolution'|trans %} + +{% extends "@oidc/base.twig" %} + +{% block oidcContent %} + +

+ {{ 'You can use the form below to test Trust Chain resolution from a leaf entity ID to Trust Anchors.'|trans }} + {{ 'By default, form is populated with current OP issuer and configured Trust Anchors, but you are free to adjust entries as needed.'|trans }} + {{ 'Log messages will show if any warnings or errors were raised during chain resolution.'|trans }} +

+ +
+ +
+ + + + + + + {{ 'Enter one Trust Anchor ID per line.'|trans }} + +
+ +
+
+ + {% if isFormSubmitted|default %} + +

{{ 'Log messages'|trans }}

+

+ {% if logMessages|default %} + + {{- logMessages|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}} + + {% else %} + {{ 'No entries.'|trans }} + {% endif %} +

+ +

{{ 'Resolved chains'|trans }}

+ {% if trustChainBag|default %} +

+ {{ 'Total chains:'|trans }} {{ trustChainBag.getCount }} +

+ {% for index, trustChain in trustChainBag.getAll %} +

+ {{ loop.index }}. {{ 'Trust Anchor ID:'|trans }} {{ trustChain.getResolvedTrustAnchor.getIssuer }} +

+ {{ 'Path:'|trans }} +
+ {% for entity in trustChain.getEntities %} + {% if loop.index > 1 %} + ⇘ {{ loop.index0 }}. {{ entity.getSubject }}
+ {% endif %} + {% endfor %} + +
+ {{ 'Resolved metadata:' }}
+ {% if resolvedMetadata[index]|default is not empty %} + + {{- resolvedMetadata[index]|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}} + + {% else %} + {{ 'N/A'|trans }} + {% endif %} +

+ {% if not loop.last %} +

+ {% endif %} + {% endfor %} + {% else %} +

{{ 'No entries.'|trans }}

+ {% endif %} + + {% endif %} + +{% endblock oidcContent -%} diff --git a/tests/unit/src/Helpers/ArrTest.php b/tests/unit/src/Helpers/ArrTest.php new file mode 100644 index 00000000..a6fdd7e1 --- /dev/null +++ b/tests/unit/src/Helpers/ArrTest.php @@ -0,0 +1,49 @@ +assertTrue($this->sut()->isValueOneOf('a', ['a'])); + $this->assertTrue($this->sut()->isValueOneOf(['a'], ['a'])); + $this->assertTrue($this->sut()->isValueOneOf(['a', 'b'], ['a'])); + + $this->assertFalse($this->sut()->isValueOneOf('a', ['b'])); + $this->assertFalse($this->sut()->isValueOneOf(['a'], ['b'])); + } + + public function testIsValueSubsetOf(): void + { + $this->assertTrue($this->sut()->isValueSubsetOf('a', ['a', 'b', 'c'])); + $this->assertTrue($this->sut()->isValueSubsetOf(['a'], ['a', 'b', 'c'])); + $this->assertTrue($this->sut()->isValueSubsetOf(['a', 'b'], ['a', 'b', 'c'])); + + $this->assertFalse($this->sut()->isValueSubsetOf('a', [])); + $this->assertFalse($this->sut()->isValueSubsetOf('a', ['b'])); + $this->assertFalse($this->sut()->isValueSubsetOf(['a', 'c'], ['b'])); + } + + public function testIsValueSupersetOf(): void + { + $this->assertTrue($this->sut()->isValueSupersetOf('a', ['a'])); + $this->assertTrue($this->sut()->isValueSupersetOf(['a'], ['a'])); + $this->assertTrue($this->sut()->isValueSupersetOf(['a', 'b'], ['a'])); + + $this->assertFalse($this->sut()->isValueSupersetOf('a', ['b'])); + $this->assertFalse($this->sut()->isValueSupersetOf(['a'], ['b'])); + } +} diff --git a/tests/unit/src/Server/RequestRules/Rules/ClientIdRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/ClientIdRuleTest.php index 5bf55763..fda3d8ae 100644 --- a/tests/unit/src/Server/RequestRules/Rules/ClientIdRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/ClientIdRuleTest.php @@ -18,6 +18,7 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientIdRule; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\FederationCache; +use SimpleSAML\Module\oidc\Utils\FederationParticipationValidator; use SimpleSAML\Module\oidc\Utils\JwksResolver; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Federation; @@ -39,6 +40,7 @@ class ClientIdRuleTest extends TestCase protected Stub $clientEntityFactoryStub; protected Stub $helpersStub; protected Stub $jwksResolverStub; + protected Stub $federationParticipationValidatorStub; /** * @throws \Exception @@ -57,9 +59,10 @@ protected function setUp(): void $this->clientEntityFactoryStub = $this->createStub(ClientEntityFactory::class); $this->helpersStub = $this->createStub(Helpers::class); $this->jwksResolverStub = $this->createStub(JwksResolver::class); + $this->federationParticipationValidatorStub = $this->createStub(FederationParticipationValidator::class); } - protected function mock(): ClientIdRule + protected function sut(): ClientIdRule { return new ClientIdRule( $this->requestParamsResolverStub, @@ -69,20 +72,21 @@ protected function mock(): ClientIdRule $this->federationStub, $this->helpersStub, $this->jwksResolverStub, + $this->federationParticipationValidatorStub, $this->federationCacheStub, ); } public function testConstruct(): void { - $this->assertInstanceOf(ClientIdRule::class, $this->mock()); + $this->assertInstanceOf(ClientIdRule::class, $this->sut()); } public function testCheckRuleEmptyClientIdThrows(): void { $this->requestParamsResolverStub->method('getBasedOnAllowedMethods')->willReturn(null); $this->expectException(OidcServerException::class); - $this->mock()->checkRule( + $this->sut()->checkRule( $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, @@ -94,7 +98,7 @@ public function testCheckRuleInvalidClientThrows(): void $this->requestParamsResolverStub->method('getBasedOnAllowedMethods')->willReturn('123'); $this->clientRepositoryStub->method('getClientEntity')->willReturn('invalid'); $this->expectException(OidcServerException::class); - $this->mock()->checkRule( + $this->sut()->checkRule( $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, @@ -110,7 +114,7 @@ public function testCheckRuleForValidClientId(): void $this->requestParamsResolverStub->method('getBasedOnAllowedMethods')->willReturn('123'); $this->clientRepositoryStub->method('getClientEntity')->willReturn($this->clientEntityStub); - $result = $this->mock()->checkRule( + $result = $this->sut()->checkRule( $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, diff --git a/tests/unit/src/Utils/Debug/ArrayLoggerTest.php b/tests/unit/src/Utils/Debug/ArrayLoggerTest.php new file mode 100644 index 00000000..0a090174 --- /dev/null +++ b/tests/unit/src/Utils/Debug/ArrayLoggerTest.php @@ -0,0 +1,88 @@ +helpersMock = $this->createMock(Helpers::class); + $this->dateTimeMock = $this->createMock(Helpers\DateTime::class); + $this->helpersMock->method('dateTime')->willReturn($this->dateTimeMock); + $this->dateTimeMock->method('getUtc')->willReturn(new \DateTimeImmutable()); + $this->weight = ArrayLogger::WEIGHT_DEBUG; + } + + protected function sut( + ?Helpers $helpers = null, + ?int $weight = null, + ): ArrayLogger { + $helpers ??= $this->helpersMock; + $weight ??= $this->weight; + + return new ArrayLogger($helpers, $weight); + } + + public function testCanCreateInstance(): void + { + $this->assertInstanceOf(ArrayLogger::class, $this->sut()); + } + + public function testCanLogEntriesBasedOnWeight(): void + { + $sut = $this->sut(); + $this->assertEmpty($sut->getEntries()); + + $sut->debug('debug message'); + $sut->info('info message'); + $sut->notice('notice message'); + $sut->warning('warning message'); + $sut->error('error message'); + $sut->critical('critical message'); + $sut->alert('alert message'); + $sut->emergency('emergency message'); + $sut->log(LogLevel::DEBUG, 'debug message'); + + $this->assertCount(9, $sut->getEntries()); + } + + public function testWontLogLessThanEmergency(): void + { + $sut = $this->sut(weight: ArrayLogger::WEIGHT_EMERGENCY); + + $sut->debug('debug message'); + $sut->info('info message'); + $sut->notice('notice message'); + $sut->warning('warning message'); + $sut->error('error message'); + $sut->critical('critical message'); + $sut->alert('alert message'); + + $this->assertEmpty($sut->getEntries()); + + $sut->emergency('emergency message'); + $this->assertNotEmpty($sut->getEntries()); + } + + public function testThrowsOnInvalidLogLevel(): void + { + $this->expectException(InvalidArgumentException::class); + + $this->sut()->log('invalid', 'message'); + } +} From 33f20810d3a36febecc65cc958c6b1a0519eb325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Wed, 18 Dec 2024 11:29:11 +0100 Subject: [PATCH 46/70] Use JWKS JSON string as Trust Anchor JWKS config --- config-templates/module_oidc.php | 32 ++++--------------- src/ModuleConfig.php | 11 ++----- .../RequestRules/Rules/ClientIdRule.php | 13 ++++++++ templates/config/federation.twig | 2 +- 4 files changed, 24 insertions(+), 34 deletions(-) diff --git a/config-templates/module_oidc.php b/config-templates/module_oidc.php index 30064f74..c4f8d03b 100644 --- a/config-templates/module_oidc.php +++ b/config-templates/module_oidc.php @@ -327,32 +327,14 @@ ModuleConfig::OPTION_FEDERATION_ENABLED => false, // Trust Anchors which are valid for this entity. The key represents the Trust Anchor Entity ID, while the value can - // be the Trust Anchor's JWKS array value, or null. If JWKS is provided, it will be used to validate Trust Anchor - // Configuration Statement in addition to using JWKS acquired during Trust Chain resolution. If JWKS is not - // provided (value null), the validity of Trust Anchor Configuration Statement will "only" be validated - // by the JWKS acquired during Trust Chain resolution, meaning that security will rely "only" on - // protection implied from using TLS on endpoints used during Trust Chain resolution. + // be the Trust Anchor's JWKS JSON object string value, or null. If JWKS is provided, it will be used to validate + // Trust Anchor Configuration Statement in addition to using JWKS acquired during Trust Chain resolution. If + // JWKS is not provided (value null), the validity of Trust Anchor Configuration Statement will "only" be + // validated by the JWKS acquired during Trust Chain resolution, meaning that security will rely "only" + // on protection implied from using TLS on endpoints used during Trust Chain resolution. ModuleConfig::OPTION_FEDERATION_TRUST_ANCHORS => [ -// 'https://ta.example.org/' => [ -// 'keys' => [ -// [ -// 'alg' => 'RS256', -// 'use' => 'sig', -// 'kty' => 'RSA', -// 'n' => 'abc...def', -// 'e' => 'AQAB', -// 'kid' => '123', -// ], -// [ -// 'alg' => 'RS256', -// 'use' => 'sig', -// 'kty' => 'RSA', -// 'n' => 'ghi...jkl', -// 'e' => 'AQAB', -// 'kid' => '456', -// ], -// ], -// ], + // phpcs:ignore +// 'https://ta.example.org/' => '{"keys":[{"kty": "RSA","alg": "RS256","use": "sig","kid": "Nzb...9Xs","e": "AQAB","n": "pnXB...ub9J"}]}', // 'https://ta2.example.org/' => null, ], diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php index 0ed106de..6c905eb3 100644 --- a/src/ModuleConfig.php +++ b/src/ModuleConfig.php @@ -24,7 +24,6 @@ use SimpleSAML\Error\ConfigurationError; use SimpleSAML\Module\oidc\Bridges\SspBridge; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; -use SimpleSAML\OpenID\Codebooks\ClaimsEnum; use SimpleSAML\OpenID\Codebooks\ScopesEnum; class ModuleConfig @@ -650,20 +649,16 @@ public function getFederationTrustAnchorIds(): array /** * @throws \SimpleSAML\Error\ConfigurationError */ - public function getTrustAnchorJwks(string $trustAnchorId): ?array + public function getTrustAnchorJwksJson(string $trustAnchorId): ?string { /** @psalm-suppress MixedAssignment */ $jwks = $this->getFederationTrustAnchors()[$trustAnchorId] ?? null; - if ($jwks === null) { + if (is_null($jwks)) { return null; } - if ( - is_array($jwks) && - array_key_exists(ClaimsEnum::Keys->value, $jwks) && - (!empty($jwks[ClaimsEnum::Keys->value])) - ) { + if (is_string($jwks)) { return $jwks; } diff --git a/src/Server/RequestRules/Rules/ClientIdRule.php b/src/Server/RequestRules/Rules/ClientIdRule.php index bd66acec..199e9cde 100644 --- a/src/Server/RequestRules/Rules/ClientIdRule.php +++ b/src/Server/RequestRules/Rules/ClientIdRule.php @@ -141,6 +141,19 @@ public function checkRule( ); } + // Validate TA with locally saved JWKS, if available. + $trustAnchorEntityConfiguration = $trustChain->getResolvedTrustAnchor(); + $localTrustAnchorJwksJson = $this->moduleConfig + ->getTrustAnchorJwksJson($trustAnchorEntityConfiguration->getIssuer()); + if (!is_null($localTrustAnchorJwksJson)) { + /** @psalm-suppress MixedArgument */ + $localTrustAnchorJwks = $this->federation->helpers()->json()->decode($localTrustAnchorJwksJson); + if (!is_array($localTrustAnchorJwks)) { + throw OidcServerException::serverError('Unexpected JWKS format.'); + } + $trustAnchorEntityConfiguration->verifyWithKeySet($localTrustAnchorJwks); + } + $clientFederationEntity = $trustChain->getResolvedLeaf(); if ($clientFederationEntity->getIssuer() !== $clientEntityId) { diff --git a/templates/config/federation.twig b/templates/config/federation.twig index 51a0d116..8bb739ee 100644 --- a/templates/config/federation.twig +++ b/templates/config/federation.twig @@ -64,7 +64,7 @@ {{ 'JWKS'|trans }}: {% if jwks|default is not empty %} - {{- jwks|json_encode(constant('JSON_PRETTY_PRINT')) -}} + {{- jwks -}} {% else %} {{ 'N/A'|trans }} From 3b7d297f4293de03e4fbd8a3f68fe327c8aad8a7 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Mon, 27 Jan 2025 10:55:56 +0100 Subject: [PATCH 47/70] Fix OIDF federation cert name in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b3c0e94..9fbf651c 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ If you want to provide a passphrase for your private key, run this command inste Now you need to extract the public key from the private key: openssl rsa -in cert/oidc_module.key -pubout -out cert/oidc_module.crt - openssl rsa -in cert/oidc_module_federation.key -pubout -out cert/oidc_module.crt + openssl rsa -in cert/oidc_module_federation.key -pubout -out cert/oidc_module_federation.crt or use your passphrase if provided on private key generation: From b69583a939714d3028d1270e4167e021d07612d3 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Mon, 27 Jan 2025 13:31:15 +0100 Subject: [PATCH 48/70] Add caching support for protocol artifacts (#270) * Add cache to client repository * Add cache to access token repository * Add cache to AllowedOriginRepository * Add cache to AuthCodeRepository * Add cache to RefreshTokenRepository * Move PDO logic from entities to repositories * Get rid off RevokeTokenByAuthCodeIdTrait * Add coverage * Update readme --- README.md | 41 +++-- UPGRADE.md | 8 +- config-templates/module_oidc.php | 58 +++--- src/Entities/AccessTokenEntity.php | 3 +- src/Entities/AuthCodeEntity.php | 3 +- src/Entities/ClientEntity.php | 7 +- src/Entities/RefreshTokenEntity.php | 3 +- src/Helpers/Client.php | 2 +- src/Helpers/Random.php | 2 + src/ModuleConfig.php | 16 ++ .../AbstractDatabaseRepository.php | 7 + src/Repositories/AccessTokenRepository.php | 84 +++++++-- src/Repositories/AllowedOriginRepository.php | 26 ++- src/Repositories/AuthCodeRepository.php | 70 ++++++-- src/Repositories/ClientRepository.php | 84 ++++++++- src/Repositories/RefreshTokenRepository.php | 84 +++++++-- .../Traits/RevokeTokenByAuthCodeIdTrait.php | 39 ---- src/Repositories/UserRepository.php | 7 +- tests/config/module_oidc.php | 65 ++++++- ...Test.php => AccessTokenRepositoryTest.php} | 51 ++---- .../unit/src/Entities/AuthCodeEntityTest.php | 3 +- tests/unit/src/Entities/ClientEntityTest.php | 7 +- .../src/Entities/RefreshTokenEntityTest.php | 3 +- tests/unit/src/Helpers/ArrTest.php | 8 + tests/unit/src/Helpers/ClientTest.php | 78 ++++++++ tests/unit/src/Helpers/DateTimeTest.php | 48 +++++ tests/unit/src/Helpers/HttpTest.php | 87 +++++++++ tests/unit/src/Helpers/RandomTest.php | 33 ++++ tests/unit/src/Helpers/StrTest.php | 34 ++++ tests/unit/src/HelpersTest.php | 35 ++++ tests/unit/src/ModuleConfigTest.php | 166 ++++++++++++++---- .../AbstractDatabaseRepositoryTest.php | 51 ++++++ .../AccessTokenRepositoryTest.php | 163 ++++++++++++++--- .../AllowedOriginRepositoryTest.php | 23 ++- .../Repositories/AuthCodeRepositoryTest.php | 29 ++- .../src/Repositories/ClientRepositoryTest.php | 123 ++++++++++--- .../CodeChallengeVerifiersRepositoryTest.php | 46 +++++ .../RefreshTokenRepositoryTest.php | 44 +++++ .../src/Repositories/ScopeRepositoryTest.php | 14 ++ .../src/Repositories/UserRepositoryTest.php | 14 ++ 40 files changed, 1381 insertions(+), 288 deletions(-) delete mode 100644 src/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php rename tests/integration/src/Repositories/{Traits/RevokeTokenByAuthCodeIdTraitTest.php => AccessTokenRepositoryTest.php} (89%) create mode 100644 tests/unit/src/Helpers/ClientTest.php create mode 100644 tests/unit/src/Helpers/DateTimeTest.php create mode 100644 tests/unit/src/Helpers/HttpTest.php create mode 100644 tests/unit/src/Helpers/RandomTest.php create mode 100644 tests/unit/src/Helpers/StrTest.php create mode 100644 tests/unit/src/HelpersTest.php create mode 100644 tests/unit/src/Repositories/AbstractDatabaseRepositoryTest.php create mode 100644 tests/unit/src/Repositories/CodeChallengeVerifiersRepositoryTest.php diff --git a/README.md b/README.md index 9fbf651c..915a7f01 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Currently supported flows are: [![Build Status](https://github.com/simplesamlphp/simplesamlphp-module-oidc/actions/workflows/test.yaml/badge.svg)](https://github.com/simplesamlphp/simplesamlphp-module-oidc/actions/workflows/test.yaml) [![Coverage Status](https://codecov.io/gh/simplesamlphp/simplesamlphp-module-oidc/branch/master/graph/badge.svg)](https://app.codecov.io/gh/simplesamlphp/simplesamlphp-module-oidc) -[![SimpleSAMLphp](https://img.shields.io/badge/simplesamlphp-2.1-brightgreen)](https://simplesamlphp.org/) +[![SimpleSAMLphp](https://img.shields.io/badge/simplesamlphp-2.3-brightgreen)](https://simplesamlphp.org/) ![Main screen capture](docs/oidc.png) @@ -112,17 +112,27 @@ Once the module is enabled, the database migrations must be run. ### Run database migrations The module comes with some default SQL migrations which set up needed tables in the configured database. To run them, -go to `OIDC` > `Database Migrations`, and press the available button. +in the SimpleSAMLphp administration area go to `OIDC` > `Database Migrations`, and press the available button. Alternatively, in case of automatic / scripted deployments, you can run the 'install.php' script from the command line: php modules/oidc/bin/install.php +### Protocol Artifacts Caching + +The configured database serves as the primary storage for protocol artifacts, such as access tokens, authorization +codes, refresh tokens, clients, and user data. In production environments, it is recommended to also set up caching +for these artifacts. The cache layer operates in front of the database, improving performance, particularly during +sudden surges of users attempting to authenticate. The implementation leverages the Symfony Cache component, allowing +the use of any compatible Symfony cache adapter. For more details on configuring the protocol cache, refer to the +module configuration file. + ### Relying Party (RP) Administration The module lets you manage (create, read, update and delete) approved RPs from the module user interface itself. -Once the database schema has been created, you can go to `OIDC` > `Client Registry`. +Once the database schema has been created, in the SimpleSAMLphp administration area go to `OIDC` > +`Client Registry`. Note that clients can be marked as confidential or public. If the client is not marked as confidential (it is public), and is using Authorization Code flow, it will have to provide PKCE parameters during the flow. @@ -136,12 +146,9 @@ to be enabled and configured. ### Endpoint locations -Once you deployed the module, you will need the exact endpoint urls the module provides to configure the relying parties. -You can visit the discovery endpoint to learn this information: - -`/module.php/oidc/.well-known/openid-configuration` - -This endpoint can be used to set up a `.well-known` URL (see below). +Once you deploy the module, in the SimpleSAMLphp administration area go to `OIDC` and then select the +Protocol / Federation Settings page to see the available discovery URLs. These URLs can then be used to set up a +`.well-known` URLs (see below). ### Note when using Apache web server @@ -161,6 +168,20 @@ SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 ``` Choose the one which works for you. If you don't set it, you'll get a warnings about this situation in your logs. +### Note on OpenID Federation (OIDF) support + +OpenID Federation support is in "draft" phase, as is the +[specification](https://openid.net/specs/openid-federation-1_0) itself. This means that you can expect braking changes +in future releases related to OIDF capabilities. You can enable / disable OIDF support at any time in module +configuration. + +Currently, the following OIDF features are supported: +* endpoint for issuing configuration entity statement (statement about itself) +* fetch endpoint for issuing statements about subordinates (registered clients) +* automatic client registration using a Request Object + +OIDF support is implemented using the underlying [SimpleSAMLphp OpenID library](https://github.com/simplesamlphp/openid). + ## Additional considerations ### Private scopes @@ -343,7 +364,7 @@ You may view the OIDC configuration endpoint at `https://localhost/.well-known/o To test local changes against another DB, such as Postgres, we need to: * Create a docker network layer -* Run a DB container ( and create a DB if one doesn't exist) +* Run a DB container (and create a DB if one doesn't exist) * Run SSP and use the DB container ``` diff --git a/UPGRADE.md b/UPGRADE.md index 9a766943..5c166c34 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -12,7 +12,11 @@ # Version 5 to 6 ## New features - +- Caching support for OIDC protocol artifacts like Access Tokens, Authorization Codes, Refresh Tokens, but also + client and user data. The cache layer stands in front of the database store, so it can improve performance, especially + in cases of sudden surge of users trying to authenticate. Implementation is based on Symfony Cache component, so any + compatible Symfony cache adapter can be used. Check the module config file for more information on how to set the + protocol cache. - OpenID capabilities - New federation endpoints: - endpoint for issuing configuration entity statement (statement about itself) @@ -40,7 +44,7 @@ https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication - (optional) Issuer - you can now override the issuer (OP identifier). If not set, it falls back to current scheme, host and optionally a port (as in all previous module versions). -- protocol caching adapter and its arguments +- (optional) Protocol caching adapter and its arguments - (optional) OpenID Federation related options (needed if federation capabilities are to be used): - enabled or disabled federation capabilities - valid trust anchors diff --git a/config-templates/module_oidc.php b/config-templates/module_oidc.php index c4f8d03b..a48b6169 100644 --- a/config-templates/module_oidc.php +++ b/config-templates/module_oidc.php @@ -258,42 +258,48 @@ // also give proper adapter arguments for its instantiation below. // @see https://symfony.com/doc/current/components/cache.html#available-cache-adapters ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => null, - //ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\FilesystemAdapter::class, - //ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\MemcachedAdapter::class, +// ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\FilesystemAdapter::class, +// ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\MemcachedAdapter::class, - // Federation cache adapter arguments used for adapter instantiation. Refer to documentation for particular + // Protocol cache adapter arguments used for adapter instantiation. Refer to documentation for particular // adapter on which arguments are needed to create its instance, in the order of constructor arguments. // See examples below. ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS => [ // Adapter arguments here... ], // Example for FileSystemAdapter: - //ModuleConfig::OPTION_FEDERATION_CACHE_ADAPTER_ARGUMENTS => [ - // 'openidFederation', // Namespace, subdirectory of main cache directory - // 60 * 60 * 6, // Default lifetime in seconds (used when particular cache item doesn't define its own lifetime) - // '/path/to/main/cache/directory' // Must be writable. Can be set to null to use system temporary directory. - //], - // Example for MemcachedAdapter: - //ModuleConfig::OPTION_FEDERATION_CACHE_ADAPTER_ARGUMENTS => [ - // // First argument is a connection instance, so we can use the helper method to create it. In this example a - // // single server is used. Refer to documentation on how to use multiple servers, and / or to provide other - // // options. - // \Symfony\Component\Cache\Adapter\MemcachedAdapter::createConnection( - // 'memcached://localhost' - // // the DSN can include config options (pass them as a query string): - // // 'memcached://localhost:11222?retry_timeout=10' - // // 'memcached://localhost:11222?socket_recv_size=1&socket_send_size=2' - // ), - // 'openidFederation', // Namespace, key prefix. - // 60 * 60 * 6, // Default lifetime in seconds (used when particular cache item doesn't define its own lifetime) - //], +// ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS => [ +// 'openidFederation', // Namespace, subdirectory of main cache directory +// 60 * 60 * 6, // Default lifetime in seconds (used when particular cache item doesn't define its own lifetime) +// '/path/to/main/cache/directory' // Must be writable. Can be set to null to use system temporary directory. +// ], +// Example for MemcachedAdapter: +// ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS => [ +// // First argument is a connection instance, so we can use the helper method to create it. In this example a +// // single server is used. Refer to documentation on how to use multiple servers, and / or to provide other +// // options. +// \Symfony\Component\Cache\Adapter\MemcachedAdapter::createConnection( +// 'memcached://localhost' +// // the DSN can include config options (pass them as a query string): +// // 'memcached://localhost:11222?retry_timeout=10' +// // 'memcached://localhost:11222?socket_recv_size=1&socket_send_size=2' +// ), +// 'openidProtocol', // Namespace, key prefix. +// 60 * 60 * 6, // Default lifetime in seconds (used when particular cache item doesn't define its own lifetime) +// ], + /** + * Protocol cache duration for particular entities. This is only relevant if protocol cache adapter is set up. + * For duration format info, check https://www.php.net/manual/en/dateinterval.construct.php. + */ // Cache duration for user entities (authenticated users data). If not set, cache duration will be the same as - // session duration. This is used to avoid fetching user data from database on every authentication event. - // This is only relevant if protocol cache adapter is set up. For duration format info, check - // https://www.php.net/manual/en/dateinterval.construct.php. + // session duration. // ModuleConfig::OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION => 'PT1H', // 1 hour - ModuleConfig::OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION => null, // fallback to session duration + ModuleConfig::OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION => null, // Fallback to session duration + // Cache duration for client entities, with given default. + ModuleConfig::OPTION_PROTOCOL_CLIENT_ENTITY_CACHE_DURATION => 'PT10M', // 10 minutes + // Cache duration for Authorization Code, Access Token, and Refresh Token will fall back to their TTL. + /** * Cron related options. diff --git a/src/Entities/AccessTokenEntity.php b/src/Entities/AccessTokenEntity.php index 67630acd..b98fe7cf 100644 --- a/src/Entities/AccessTokenEntity.php +++ b/src/Entities/AccessTokenEntity.php @@ -24,7 +24,6 @@ use League\OAuth2\Server\Entities\Traits\AccessTokenTrait; use League\OAuth2\Server\Entities\Traits\EntityTrait; use League\OAuth2\Server\Entities\Traits\TokenEntityTrait; -use PDO; use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\EntityStringRepresentationInterface; use SimpleSAML\Module\oidc\Entities\Traits\AssociateWithAuthCodeTrait; @@ -112,7 +111,7 @@ public function getState(): array 'expires_at' => $this->getExpiryDateTime()->format('Y-m-d H:i:s'), 'user_id' => $this->getUserIdentifier(), 'client_id' => $this->getClient()->getIdentifier(), - 'is_revoked' => [$this->isRevoked(), PDO::PARAM_BOOL], + 'is_revoked' => $this->isRevoked(), 'auth_code_id' => $this->getAuthCodeId(), 'requested_claims' => json_encode($this->requestedClaims, JSON_THROW_ON_ERROR), ]; diff --git a/src/Entities/AuthCodeEntity.php b/src/Entities/AuthCodeEntity.php index c0bf7c0a..d98fe347 100644 --- a/src/Entities/AuthCodeEntity.php +++ b/src/Entities/AuthCodeEntity.php @@ -19,7 +19,6 @@ use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface; use League\OAuth2\Server\Entities\Traits\EntityTrait; use League\OAuth2\Server\Entities\Traits\TokenEntityTrait; -use PDO; use SimpleSAML\Module\oidc\Entities\Interfaces\AuthCodeEntityInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\MementoInterface; use SimpleSAML\Module\oidc\Entities\Traits\OidcAuthCodeTrait; @@ -66,7 +65,7 @@ public function getState(): array 'expires_at' => $this->getExpiryDateTime()->format('Y-m-d H:i:s'), 'user_id' => $this->getUserIdentifier(), 'client_id' => $this->client->getIdentifier(), - 'is_revoked' => [$this->isRevoked(), PDO::PARAM_BOOL], + 'is_revoked' => $this->isRevoked(), 'redirect_uri' => $this->getRedirectUri(), 'nonce' => $this->getNonce(), ]; diff --git a/src/Entities/ClientEntity.php b/src/Entities/ClientEntity.php index 3aa7267c..d834d41e 100644 --- a/src/Entities/ClientEntity.php +++ b/src/Entities/ClientEntity.php @@ -19,7 +19,6 @@ use DateTimeImmutable; use League\OAuth2\Server\Entities\Traits\ClientTrait; use League\OAuth2\Server\Entities\Traits\EntityTrait; -use PDO; use SimpleSAML\Module\oidc\Codebooks\RegistrationTypeEnum; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\OpenID\Codebooks\ClientRegistrationTypesEnum; @@ -167,8 +166,8 @@ public function getState(): array self::KEY_AUTH_SOURCE => $this->getAuthSourceId(), self::KEY_REDIRECT_URI => json_encode($this->getRedirectUri(), JSON_THROW_ON_ERROR), self::KEY_SCOPES => json_encode($this->getScopes(), JSON_THROW_ON_ERROR), - self::KEY_IS_ENABLED => [$this->isEnabled(), PDO::PARAM_BOOL], - self::KEY_IS_CONFIDENTIAL => [$this->isConfidential(), PDO::PARAM_BOOL], + self::KEY_IS_ENABLED => $this->isEnabled(), + self::KEY_IS_CONFIDENTIAL => $this->isConfidential(), self::KEY_OWNER => $this->getOwner(), self::KEY_POST_LOGOUT_REDIRECT_URI => json_encode($this->getPostLogoutRedirectUri(), JSON_THROW_ON_ERROR), self::KEY_BACKCHANNEL_LOGOUT_URI => $this->getBackChannelLogoutUri(), @@ -188,7 +187,7 @@ public function getState(): array self::KEY_UPDATED_AT => $this->getUpdatedAt()?->format('Y-m-d H:i:s'), self::KEY_CREATED_AT => $this->getCreatedAt()?->format('Y-m-d H:i:s'), self::KEY_EXPIRES_AT => $this->getExpiresAt()?->format('Y-m-d H:i:s'), - self::KEY_IS_FEDERATED => [$this->isFederated(), PDO::PARAM_BOOL], + self::KEY_IS_FEDERATED => $this->isFederated(), ]; } diff --git a/src/Entities/RefreshTokenEntity.php b/src/Entities/RefreshTokenEntity.php index c2094c12..de12e766 100644 --- a/src/Entities/RefreshTokenEntity.php +++ b/src/Entities/RefreshTokenEntity.php @@ -19,7 +19,6 @@ use DateTimeImmutable; use League\OAuth2\Server\Entities\Traits\EntityTrait; use League\OAuth2\Server\Entities\Traits\RefreshTokenTrait; -use PDO; use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\RefreshTokenEntityInterface; use SimpleSAML\Module\oidc\Entities\Traits\AssociateWithAuthCodeTrait; @@ -52,7 +51,7 @@ public function getState(): array 'id' => $this->getIdentifier(), 'expires_at' => $this->getExpiryDateTime()->format('Y-m-d H:i:s'), 'access_token_id' => $this->getAccessToken()->getIdentifier(), - 'is_revoked' => [$this->isRevoked(), PDO::PARAM_BOOL], + 'is_revoked' => $this->isRevoked(), 'auth_code_id' => $this->getAuthCodeId(), ]; } diff --git a/src/Helpers/Client.php b/src/Helpers/Client.php index 45d98d48..8928154f 100644 --- a/src/Helpers/Client.php +++ b/src/Helpers/Client.php @@ -30,7 +30,7 @@ public function getFromRequest( $clientId = empty($params['client_id']) ? null : (string)$params['client_id']; if (!is_string($clientId)) { - throw new BadRequest('Client id is missing.'); + throw new BadRequest('Client ID is missing.'); } $client = $clientRepository->findById($clientId); diff --git a/src/Helpers/Random.php b/src/Helpers/Random.php index f6c0b68d..16c617a8 100644 --- a/src/Helpers/Random.php +++ b/src/Helpers/Random.php @@ -20,8 +20,10 @@ public function getIdentifier(int $length = 40): string try { return bin2hex(random_bytes($length)); + // @codeCoverageIgnoreStart } catch (Throwable $e) { throw OidcServerException::serverError('Could not generate a random string', $e); } + // @codeCoverageIgnoreEnd } } diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php index 6c905eb3..dcb2de51 100644 --- a/src/ModuleConfig.php +++ b/src/ModuleConfig.php @@ -80,6 +80,7 @@ class ModuleConfig final public const OPTION_PROTOCOL_CACHE_ADAPTER = 'protocol_cache_adapter'; final public const OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS = 'protocol_cache_adapter_arguments'; final public const OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION = 'protocol_user_entity_cache_duration'; + final public const OPTION_PROTOCOL_CLIENT_ENTITY_CACHE_DURATION = 'protocol_client_entity_cache_duration'; final public const OPTION_FEDERATION_PARTICIPATION_LIMIT_BY_TRUST_MARKS = 'federation_participation_limit_by_trust_marks'; @@ -464,6 +465,21 @@ public function getProtocolUserEntityCacheDuration(): DateInterval ); } + /** + * Get cache duration for client entities (user data), with given default + * + * @throws \Exception + */ + public function getProtocolClientEntityCacheDuration(): DateInterval + { + return new DateInterval( + $this->config()->getOptionalString( + self::OPTION_PROTOCOL_CLIENT_ENTITY_CACHE_DURATION, + null, + ) ?? 'PT10M', + ); + } + /***************************************************************************************************************** * OpenID Federation related config. diff --git a/src/Repositories/AbstractDatabaseRepository.php b/src/Repositories/AbstractDatabaseRepository.php index 61d8b416..9434eafb 100644 --- a/src/Repositories/AbstractDatabaseRepository.php +++ b/src/Repositories/AbstractDatabaseRepository.php @@ -32,5 +32,12 @@ public function __construct( ) { } + public function getCacheKey(string $identifier): string + { + return is_string($tableName = $this->getTableName()) ? + $tableName . '_' . $identifier : + $identifier; + } + abstract public function getTableName(): ?string; } diff --git a/src/Repositories/AccessTokenRepository.php b/src/Repositories/AccessTokenRepository.php index 4298211e..6c7d16e5 100644 --- a/src/Repositories/AccessTokenRepository.php +++ b/src/Repositories/AccessTokenRepository.php @@ -19,6 +19,7 @@ use DateTimeImmutable; use League\OAuth2\Server\Entities\AccessTokenEntityInterface as OAuth2AccessTokenEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface; +use PDO; use RuntimeException; use SimpleSAML\Database; use SimpleSAML\Error\Error; @@ -29,14 +30,11 @@ use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\Interfaces\AccessTokenRepositoryInterface; -use SimpleSAML\Module\oidc\Repositories\Traits\RevokeTokenByAuthCodeIdTrait; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Utils\ProtocolCache; class AccessTokenRepository extends AbstractDatabaseRepository implements AccessTokenRepositoryInterface { - use RevokeTokenByAuthCodeIdTrait; - final public const TABLE_NAME = 'oidc_access_token'; public function __construct( @@ -98,7 +96,7 @@ public function getNewToken( */ public function persistNewAccessToken(OAuth2AccessTokenEntityInterface $accessTokenEntity): void { - if (!$accessTokenEntity instanceof AccessTokenEntity) { + if (!($accessTokenEntity instanceof AccessTokenEntity)) { throw new Error('Invalid AccessTokenEntity'); } @@ -110,7 +108,15 @@ public function persistNewAccessToken(OAuth2AccessTokenEntityInterface $accessTo $this->database->write( $stmt, + $this->preparePdoState($accessTokenEntity->getState()), + ); + + $this->protocolCache?->set( $accessTokenEntity->getState(), + $this->helpers->dateTime()->getSecondsToExpirationTime( + $accessTokenEntity->getExpiryDateTime()->getTimestamp(), + ), + $this->getCacheKey((string)$accessTokenEntity->getIdentifier()), ); } @@ -121,22 +127,38 @@ public function persistNewAccessToken(OAuth2AccessTokenEntityInterface $accessTo */ public function findById(string $tokenId): ?AccessTokenEntity { - $stmt = $this->database->read( - "SELECT * FROM {$this->getTableName()} WHERE id = :id", - [ - 'id' => $tokenId, - ], - ); + /** @var ?array $data */ + $data = $this->protocolCache?->get(null, $this->getCacheKey($tokenId)); + + if (!is_array($data)) { + $stmt = $this->database->read( + "SELECT * FROM {$this->getTableName()} WHERE id = :id", + [ + 'id' => $tokenId, + ], + ); - if (empty($rows = $stmt->fetchAll())) { - return null; + if (empty($rows = $stmt->fetchAll())) { + return null; + } + + /** @var array $data */ + $data = current($rows); } - /** @var array $data */ - $data = current($rows); $data['client'] = $this->clientRepository->findById((string)$data['client_id']); - return $this->accessTokenEntityFactory->fromState($data); + $accessTokenEntity = $this->accessTokenEntityFactory->fromState($data); + + $this->protocolCache?->set( + $accessTokenEntity->getState(), + $this->helpers->dateTime()->getSecondsToExpirationTime( + $accessTokenEntity->getExpiryDateTime()->getTimestamp(), + ), + $this->getCacheKey((string)$accessTokenEntity->getIdentifier()), + ); + + return $accessTokenEntity; } /** @@ -156,6 +178,22 @@ public function revokeAccessToken($tokenId): void $this->update($accessToken); } + /** + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \JsonException + */ + public function revokeByAuthCodeId(string $authCodeId): void + { + $stmt = $this->database->read( + "SELECT id FROM {$this->getTableName()} WHERE auth_code_id = :auth_code_id", + ['auth_code_id' => $authCodeId], + ); + + foreach ($stmt->fetchAll(PDO::FETCH_COLUMN, 0) as $id) { + $this->revokeAccessToken((string)$id); + } + } + /** * {@inheritdoc} * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException @@ -207,7 +245,23 @@ private function update(AccessTokenEntity $accessTokenEntity): void $this->database->write( $stmt, + $this->preparePdoState($accessTokenEntity->getState()), + ); + + $this->protocolCache?->set( $accessTokenEntity->getState(), + $this->helpers->dateTime()->getSecondsToExpirationTime( + $accessTokenEntity->getExpiryDateTime()->getTimestamp(), + ), + $this->getCacheKey((string)$accessTokenEntity->getIdentifier()), ); } + + protected function preparePdoState(array $state): array + { + $isRevoked = (bool)($state['is_revoked'] ?? true); + $state['is_revoked'] = [$isRevoked, PDO::PARAM_BOOL]; + + return $state; + } } diff --git a/src/Repositories/AllowedOriginRepository.php b/src/Repositories/AllowedOriginRepository.php index 4dcc2628..30299dcb 100644 --- a/src/Repositories/AllowedOriginRepository.php +++ b/src/Repositories/AllowedOriginRepository.php @@ -21,6 +21,7 @@ public function getTableName(): string public function set(string $clientId, array $origins): void { $this->delete($clientId); + $this->clearCache($origins); $origins = array_unique(array_filter(array_values($origins))); @@ -65,11 +66,34 @@ public function get(string $clientId): array public function has(string $origin): bool { + // We only cache this method since it is used in authentication flow. + $has = $this->protocolCache?->get(null, $this->getCacheKey($origin)); + + if ($has !== null) { + return (bool) $has; + } + $stmt = $this->database->read( "SELECT origin FROM {$this->getTableName()} WHERE origin = :origin LIMIT 1", ['origin' => $origin], ); - return (bool) count($stmt->fetchAll(PDO::FETCH_COLUMN, 0)); + $has = (bool) count($stmt->fetchAll(PDO::FETCH_COLUMN, 0)); + + $this->protocolCache?->set( + $has, + $this->moduleConfig->getProtocolClientEntityCacheDuration(), + $this->getCacheKey($origin), + ); + + return $has; + } + + protected function clearCache(array $origins): void + { + /** @var string $origin */ + foreach ($origins as $origin) { + $this->protocolCache?->delete($this->getCacheKey($origin)); + } } } diff --git a/src/Repositories/AuthCodeRepository.php b/src/Repositories/AuthCodeRepository.php index f1b95fba..46d46832 100644 --- a/src/Repositories/AuthCodeRepository.php +++ b/src/Repositories/AuthCodeRepository.php @@ -17,6 +17,7 @@ namespace SimpleSAML\Module\oidc\Repositories; use League\OAuth2\Server\Entities\AuthCodeEntityInterface as OAuth2AuthCodeEntityInterface; +use PDO; use RuntimeException; use SimpleSAML\Database; use SimpleSAML\Error\Error; @@ -31,6 +32,8 @@ class AuthCodeRepository extends AbstractDatabaseRepository implements AuthCodeRepositoryInterface { + final public const TABLE_NAME = 'oidc_auth_code'; + public function __construct( ModuleConfig $moduleConfig, Database $database, @@ -42,8 +45,6 @@ public function __construct( parent::__construct($moduleConfig, $database, $protocolCache); } - final public const TABLE_NAME = 'oidc_auth_code'; - public function getTableName(): string { return $this->database->applyPrefix(self::TABLE_NAME); @@ -76,7 +77,15 @@ public function persistNewAuthCode(OAuth2AuthCodeEntityInterface $authCodeEntity $this->database->write( $stmt, + $this->preparePdoState($authCodeEntity->getState()), + ); + + $this->protocolCache?->set( $authCodeEntity->getState(), + $this->helpers->dateTime()->getSecondsToExpirationTime( + $authCodeEntity->getExpiryDateTime()->getTimestamp(), + ), + $this->getCacheKey((string)$authCodeEntity->getIdentifier()), ); } @@ -86,22 +95,38 @@ public function persistNewAuthCode(OAuth2AuthCodeEntityInterface $authCodeEntity */ public function findById(string $codeId): ?AuthCodeEntityInterface { - $stmt = $this->database->read( - "SELECT * FROM {$this->getTableName()} WHERE id = :id", - [ - 'id' => $codeId, - ], - ); - - if (empty($rows = $stmt->fetchAll())) { - return null; + /** @var ?array $data */ + $data = $this->protocolCache?->get(null, $this->getCacheKey($codeId)); + + if (!is_array($data)) { + $stmt = $this->database->read( + "SELECT * FROM {$this->getTableName()} WHERE id = :id", + [ + 'id' => $codeId, + ], + ); + + if (empty($rows = $stmt->fetchAll())) { + return null; + } + + /** @var array $data */ + $data = current($rows); } - /** @var array $data */ - $data = current($rows); $data['client'] = $this->clientRepository->findById((string)$data['client_id']); - return $this->authCodeEntityFactory->fromState($data); + $authCodeEntity = $this->authCodeEntityFactory->fromState($data); + + $this->protocolCache?->set( + $authCodeEntity->getState(), + $this->helpers->dateTime()->getSecondsToExpirationTime( + $authCodeEntity->getExpiryDateTime()->getTimestamp(), + ), + $this->getCacheKey((string)$authCodeEntity->getIdentifier()), + ); + + return $authCodeEntity; } /** @@ -174,7 +199,24 @@ private function update(AuthCodeEntity $authCodeEntity): void $this->database->write( $stmt, + $this->preparePdoState($authCodeEntity->getState()), + ); + + $this->protocolCache?->set( $authCodeEntity->getState(), + $this->helpers->dateTime()->getSecondsToExpirationTime( + $authCodeEntity->getExpiryDateTime()->getTimestamp(), + ), + $this->getCacheKey((string)$authCodeEntity->getIdentifier()), ); } + + protected function preparePdoState(array $state): array + { + $isRevoked = (bool)($state['is_revoked'] ?? true); + + $state['is_revoked'] = [$isRevoked, PDO::PARAM_BOOL]; + + return $state; + } } diff --git a/src/Repositories/ClientRepository.php b/src/Repositories/ClientRepository.php index 97a62766..49654731 100644 --- a/src/Repositories/ClientRepository.php +++ b/src/Repositories/ClientRepository.php @@ -92,6 +92,13 @@ public function validateClient($clientIdentifier, $clientSecret, $grantType): bo */ public function findById(string $clientIdentifier, ?string $owner = null): ?ClientEntityInterface { + /** @var ?array $cachedState */ + $cachedState = $this->protocolCache?->get(null, $this->getCacheKey($clientIdentifier)); + + if (is_array($cachedState)) { + return $this->clientEntityFactory->fromState($cachedState); + } + /** * @var string $query * @var array $params @@ -112,15 +119,32 @@ public function findById(string $clientIdentifier, ?string $owner = null): ?Clie $row = current($rows); + // @codeCoverageIgnoreStart if (!is_array($row)) { return null; } + // @codeCoverageIgnoreEnd + + $clientEntity = $this->clientEntityFactory->fromState($row); - return $this->clientEntityFactory->fromState($row); + $this->protocolCache?->set( + $clientEntity->getState(), + $this->moduleConfig->getProtocolClientEntityCacheDuration(), + $this->getCacheKey($clientEntity->getIdentifier()), + ); + + return $clientEntity; } public function findByEntityIdentifier(string $entityIdentifier, ?string $owner = null): ?ClientEntityInterface { + /** @var ?array $cachedState */ + $cachedState = $this->protocolCache?->get(null, $this->getCacheKey($entityIdentifier)); + + if (is_array($cachedState)) { + return $this->clientEntityFactory->fromState($cachedState); + } + /** * @var string $query * @var array $params @@ -149,11 +173,21 @@ public function findByEntityIdentifier(string $entityIdentifier, ?string $owner $row = current($rows); + // @codeCoverageIgnoreStart if (!is_array($row)) { return null; } + // @codeCoverageIgnoreEnd - return $this->clientEntityFactory->fromState($row); + $clientEntity = $this->clientEntityFactory->fromState($row); + + $this->protocolCache?->set( + $clientEntity->getState(), + $this->moduleConfig->getProtocolClientEntityCacheDuration(), + $this->getCacheKey($entityIdentifier), + ); + + return $clientEntity; } private function addOwnerWhereClause(string $query, array $params, ?string $owner = null): array @@ -300,8 +334,21 @@ public function add(ClientEntityInterface $client): void ); $this->database->write( $stmt, + $this->preparePdoState($client->getState()), + ); + + $this->protocolCache?->set( $client->getState(), + $this->moduleConfig->getProtocolClientEntityCacheDuration(), + $this->getCacheKey($client->getIdentifier()), ); + if (($entityIdentifier = $client->getEntityIdentifier()) !== null) { + $this->protocolCache?->set( + $client->getState(), + $this->moduleConfig->getProtocolClientEntityCacheDuration(), + $this->getCacheKey($entityIdentifier), + ); + } } public function delete(ClientEntityInterface $client, ?string $owner = null): void @@ -318,6 +365,11 @@ public function delete(ClientEntityInterface $client, ?string $owner = null): vo $owner, ); $this->database->write($sqlQuery, $params); + + $this->protocolCache?->delete($this->getCacheKey($client->getIdentifier())); + if (($entityIdentifier = $client->getEntityIdentifier()) !== null) { + $this->protocolCache?->delete($this->getCacheKey($entityIdentifier)); + } } public function update(ClientEntityInterface $client, ?string $owner = null): void @@ -359,13 +411,26 @@ public function update(ClientEntityInterface $client, ?string $owner = null): vo */ [$sqlQuery, $params] = $this->addOwnerWhereClause( $stmt, - $client->getState(), + $this->preparePdoState($client->getState()), $owner, ); $this->database->write( $sqlQuery, $params, ); + + $this->protocolCache?->set( + $client->getState(), + $this->moduleConfig->getProtocolClientEntityCacheDuration(), + $this->getCacheKey($client->getIdentifier()), + ); + if (($entityIdentifier = $client->getEntityIdentifier()) !== null) { + $this->protocolCache?->set( + $client->getState(), + $this->moduleConfig->getProtocolClientEntityCacheDuration(), + $this->getCacheKey($entityIdentifier), + ); + } } private function count(string $query, ?string $owner): int @@ -421,4 +486,17 @@ private function calculateOffset(int $page, int $limit): float|int { return ($page - 1) * $limit; } + + protected function preparePdoState(array $state): array + { + $isEnabled = (bool)($state[ClientEntity::KEY_IS_ENABLED] ?? false); + $isConfidential = (bool)($state[ClientEntity::KEY_IS_CONFIDENTIAL] ?? false); + $isFederated = (bool)($state[ClientEntity::KEY_IS_FEDERATED] ?? false); + + $state[ClientEntity::KEY_IS_ENABLED] = [$isEnabled, PDO::PARAM_BOOL]; + $state[ClientEntity::KEY_IS_CONFIDENTIAL] = [$isConfidential, PDO::PARAM_BOOL]; + $state[ClientEntity::KEY_IS_FEDERATED] = [$isFederated, PDO::PARAM_BOOL]; + + return $state; + } } diff --git a/src/Repositories/RefreshTokenRepository.php b/src/Repositories/RefreshTokenRepository.php index e20bb23c..0d1ed120 100644 --- a/src/Repositories/RefreshTokenRepository.php +++ b/src/Repositories/RefreshTokenRepository.php @@ -18,6 +18,7 @@ use League\OAuth2\Server\Entities\RefreshTokenEntityInterface as OAuth2RefreshTokenEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; +use PDO; use RuntimeException; use SimpleSAML\Database; use SimpleSAML\Module\oidc\Codebooks\DateFormatsEnum; @@ -27,13 +28,10 @@ use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\Interfaces\RefreshTokenRepositoryInterface; -use SimpleSAML\Module\oidc\Repositories\Traits\RevokeTokenByAuthCodeIdTrait; use SimpleSAML\Module\oidc\Utils\ProtocolCache; class RefreshTokenRepository extends AbstractDatabaseRepository implements RefreshTokenRepositoryInterface { - use RevokeTokenByAuthCodeIdTrait; - final public const TABLE_NAME = 'oidc_refresh_token'; public function __construct( @@ -81,7 +79,15 @@ public function persistNewRefreshToken(OAuth2RefreshTokenEntityInterface $refres $this->database->write( $stmt, + $this->preparePdoState($refreshTokenEntity->getState()), + ); + + $this->protocolCache?->set( $refreshTokenEntity->getState(), + $this->helpers->dateTime()->getSecondsToExpirationTime( + $refreshTokenEntity->getExpiryDateTime()->getTimestamp(), + ), + $this->getCacheKey((string)$refreshTokenEntity->getIdentifier()), ); } @@ -92,22 +98,38 @@ public function persistNewRefreshToken(OAuth2RefreshTokenEntityInterface $refres */ public function findById(string $tokenId): ?RefreshTokenEntityInterface { - $stmt = $this->database->read( - "SELECT * FROM {$this->getTableName()} WHERE id = :id", - [ - 'id' => $tokenId, - ], - ); - - if (empty($rows = $stmt->fetchAll())) { - return null; + /** @var ?array $data */ + $data = $this->protocolCache?->get(null, $this->getCacheKey($tokenId)); + + if (!is_array($data)) { + $stmt = $this->database->read( + "SELECT * FROM {$this->getTableName()} WHERE id = :id", + [ + 'id' => $tokenId, + ], + ); + + if (empty($rows = $stmt->fetchAll())) { + return null; + } + + /** @var array $data */ + $data = current($rows); } - /** @var array $data */ - $data = current($rows); $data['access_token'] = $this->accessTokenRepository->findById((string)$data['access_token_id']); - return $this->refreshTokenEntityFactory->fromState($data); + $refreshTokenEntity = $this->refreshTokenEntityFactory->fromState($data); + + $this->protocolCache?->set( + $refreshTokenEntity->getState(), + $this->helpers->dateTime()->getSecondsToExpirationTime( + $refreshTokenEntity->getExpiryDateTime()->getTimestamp(), + ), + $this->getCacheKey((string)$refreshTokenEntity->getIdentifier()), + ); + + return $refreshTokenEntity; } /** @@ -126,6 +148,21 @@ public function revokeRefreshToken($tokenId): void $this->update($refreshToken); } + /** + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + */ + public function revokeByAuthCodeId(string $authCodeId): void + { + $stmt = $this->database->read( + "SELECT id FROM {$this->getTableName()} WHERE auth_code_id = :auth_code_id", + ['auth_code_id' => $authCodeId], + ); + + foreach ($stmt->fetchAll(PDO::FETCH_COLUMN, 0) as $id) { + $this->revokeRefreshToken((string)$id); + } + } + /** * {@inheritdoc} * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException @@ -165,7 +202,24 @@ private function update(RefreshTokenEntityInterface $refreshTokenEntity): void $this->database->write( $stmt, + $this->preparePdoState($refreshTokenEntity->getState()), + ); + + $this->protocolCache?->set( $refreshTokenEntity->getState(), + $this->helpers->dateTime()->getSecondsToExpirationTime( + $refreshTokenEntity->getExpiryDateTime()->getTimestamp(), + ), + $this->getCacheKey($refreshTokenEntity->getIdentifier()), ); } + + protected function preparePdoState(array $state): array + { + $isRevoked = (bool)($state['is_revoked'] ?? true); + + $state['is_revoked'] = [$isRevoked, PDO::PARAM_BOOL]; + + return $state; + } } diff --git a/src/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php b/src/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php deleted file mode 100644 index 8830c8af..00000000 --- a/src/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php +++ /dev/null @@ -1,39 +0,0 @@ -generateQuery($authCodeId, $revokedParam); - $this->database->write((string)$query, (array)$bindParam); - } - - /** - * @param string $authCodeId - * @param array $revokedParam - * - * @return array - */ - protected function generateQuery(string $authCodeId, array $revokedParam): array - { - $query = sprintf( - 'UPDATE %s SET is_revoked = :is_revoked WHERE auth_code_id = :auth_code_id', - $this->getTableName(), - ); - $bindParam = ['auth_code_id' => $authCodeId, 'is_revoked' => $revokedParam]; - - return [$query, $bindParam]; - } -} diff --git a/src/Repositories/UserRepository.php b/src/Repositories/UserRepository.php index 420e02f5..db644bdb 100644 --- a/src/Repositories/UserRepository.php +++ b/src/Repositories/UserRepository.php @@ -48,11 +48,6 @@ public function getTableName(): string return $this->database->applyPrefix(self::TABLE_NAME); } - public function getCacheKey(string $identifier): string - { - return $this->getTableName() . '_' . $identifier; - } - /** * @param string $identifier * @@ -81,9 +76,11 @@ public function getUserEntityByIdentifier(string $identifier): ?UserEntity $row = current($rows); + // @codeCoverageIgnoreStart if (!is_array($row)) { return null; } + // @codeCoverageIgnoreEnd $userEntity = $this->userEntityFactory->fromState($row); diff --git a/tests/config/module_oidc.php b/tests/config/module_oidc.php index 31c5eb16..8efd6ee2 100644 --- a/tests/config/module_oidc.php +++ b/tests/config/module_oidc.php @@ -23,8 +23,6 @@ ModuleConfig::OPTION_TOKEN_REFRESH_TOKEN_TTL => 'P1M', ModuleConfig::OPTION_TOKEN_ACCESS_TOKEN_TTL => 'PT1H', - ModuleConfig::OPTION_CRON_TAG => 'hourly', - ModuleConfig::OPTION_TOKEN_SIGNER => Sha256::class, ModuleConfig::OPTION_AUTH_SOURCE => 'default-sp', @@ -44,15 +42,70 @@ ModuleConfig::OPTION_AUTH_FORCED_ACR_VALUE_FOR_COOKIE_AUTHENTICATION => null, - ModuleConfig::OPTION_FEDERATION_TOKEN_SIGNER => Sha256::class, + ModuleConfig::OPTION_AUTH_PROCESSING_FILTERS => [ + ], + + ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\ArrayAdapter::class, + ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS => [], + ModuleConfig::OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION => null, + ModuleConfig::OPTION_PROTOCOL_CLIENT_ENTITY_CACHE_DURATION => 'PT10M', + + ModuleConfig::OPTION_CRON_TAG => 'hourly', + + ModuleConfig::OPTION_ADMIN_UI_PERMISSIONS => [ + 'attribute' => 'eduPersonEntitlement', + 'client' => ['urn:example:oidc:manage:client'], + ], + + ModuleConfig::OPTION_ADMIN_UI_PAGINATION_ITEMS_PER_PAGE => 20, + + ModuleConfig::OPTION_FEDERATION_ENABLED => false, + + ModuleConfig::OPTION_FEDERATION_TRUST_ANCHORS => [ + // phpcs:ignore + 'https://ta.example.org/' => '{"keys":[{"kty": "RSA","alg": "RS256","use": "sig","kid": "Nzb...9Xs","e": "AQAB","n": "pnXB...ub9J"}]}', + 'https://ta2.example.org/' => null, + ], + + ModuleConfig::OPTION_FEDERATION_AUTHORITY_HINTS => [ + 'https://intermediate.example.org/', + ], + + ModuleConfig::OPTION_FEDERATION_TRUST_MARK_TOKENS => [ + 'eyJ...GHg', + ], + + ModuleConfig::OPTION_FEDERATION_PARTICIPATION_LIMIT_BY_TRUST_MARKS => [ + // We are limiting federation participation using Trust Marks for 'https://ta.example.org/'. + 'https://ta.example.org/' => [ + // Entities must have (at least) one Trust Mark from the list below. + \SimpleSAML\Module\oidc\Codebooks\LimitsEnum::OneOf->value => [ + 'trust-mark-id', + 'trust-mark-id-2', + ], + // Entities must have all Trust Marks from the list below. + \SimpleSAML\Module\oidc\Codebooks\LimitsEnum::AllOf->value => [ + 'trust-mark-id-3', + 'trust-mark-id-4', + ], + ], + ], + + ModuleConfig::OPTION_FEDERATION_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\ArrayAdapter::class, + ModuleConfig::OPTION_FEDERATION_CACHE_ADAPTER_ARGUMENTS => [], + ModuleConfig::OPTION_FEDERATION_ENTITY_STATEMENT_DURATION => 'P1D', + ModuleConfig::OPTION_FEDERATION_CACHE_DURATION_FOR_PRODUCED => 'PT2M', + + ModuleConfig::OPTION_FEDERATION_CACHE_MAX_DURATION_FOR_FETCHED => 'PT6H', + ModuleConfig::OPTION_PKI_FEDERATION_PRIVATE_KEY_FILENAME => ModuleConfig::DEFAULT_PKI_FEDERATION_PRIVATE_KEY_FILENAME, ModuleConfig::OPTION_PKI_FEDERATION_PRIVATE_KEY_PASSPHRASE => 'abc123', ModuleConfig::OPTION_PKI_FEDERATION_CERTIFICATE_FILENAME => ModuleConfig::DEFAULT_PKI_FEDERATION_CERTIFICATE_FILENAME, - ModuleConfig::OPTION_FEDERATION_AUTHORITY_HINTS => [ - 'abc123', - ], + + ModuleConfig::OPTION_FEDERATION_TOKEN_SIGNER => Sha256::class, + ModuleConfig::OPTION_ORGANIZATION_NAME => 'Foo corp', ModuleConfig::OPTION_CONTACTS => [ 'John Doe jdoe@example.org', diff --git a/tests/integration/src/Repositories/Traits/RevokeTokenByAuthCodeIdTraitTest.php b/tests/integration/src/Repositories/AccessTokenRepositoryTest.php similarity index 89% rename from tests/integration/src/Repositories/Traits/RevokeTokenByAuthCodeIdTraitTest.php rename to tests/integration/src/Repositories/AccessTokenRepositoryTest.php index c0354d28..4b3a19b9 100644 --- a/tests/integration/src/Repositories/Traits/RevokeTokenByAuthCodeIdTraitTest.php +++ b/tests/integration/src/Repositories/AccessTokenRepositoryTest.php @@ -2,10 +2,11 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\oidc\integration\Repositories\Traits; +namespace SimpleSAML\Test\Module\oidc\integration\Repositories; use League\OAuth2\Server\CryptKey; use PDO; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -25,7 +26,6 @@ use SimpleSAML\Module\oidc\Repositories\AbstractDatabaseRepository; use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; use SimpleSAML\Module\oidc\Repositories\ClientRepository; -use SimpleSAML\Module\oidc\Repositories\Traits\RevokeTokenByAuthCodeIdTrait; use SimpleSAML\Module\oidc\Repositories\UserRepository; use SimpleSAML\Module\oidc\Services\DatabaseMigration; use SimpleSAML\Module\oidc\Services\JsonWebTokenBuilderService; @@ -34,10 +34,8 @@ use Testcontainers\Wait\WaitForHealthCheck; use Testcontainers\Wait\WaitForLog; -/** - * @covers \SimpleSAML\Module\oidc\Repositories\Traits\RevokeTokenByAuthCodeIdTrait - */ -class RevokeTokenByAuthCodeIdTraitTest extends TestCase +#[CoversClass(AccessTokenRepository::class)] +class AccessTokenRepositoryTest extends TestCase { protected array $state; protected array $scopes; @@ -72,11 +70,15 @@ class RevokeTokenByAuthCodeIdTraitTest extends TestCase public static function setUpBeforeClass(): void { + self::$containerAddress = getenv('HOSTADDRESS') ?: null; self::$mysqlPort = getenv('HOSTPORT_MY') ?: null; self::$postgresPort = getenv('HOSTPORT_PG') ?: null; // Mac docker seems to require connecting to localhost and mapped port to access containers - if (PHP_OS_FAMILY === 'Darwin' && getenv('HOSTADDRESS') === false) { + if ( + in_array(PHP_OS_FAMILY, ['Darwin', 'Linux']) && + getenv('HOSTADDRESS') === false + ) { //phpcs:ignore Generic.Files.LineLength.TooLong echo "Defaulting docker host address to 127.0.0.1. Disable this behavior by setting HOSTADDRESS to a blank.\n\tHOSTADDRESS= ./vendor/bin/phpunit"; self::$containerAddress = "127.0.0.1"; @@ -85,7 +87,7 @@ public static function setUpBeforeClass(): void self::$mysqlPort ??= "3306"; self::$postgresPort ??= "5432"; } - Configuration::setConfigDir(__DIR__ . '/../../../../../config-templates'); + Configuration::setConfigDir(__DIR__ . '/../../../../config-templates'); self::$pgConfig = self::loadPGDatabase(); self::$mysqlConfig = self::loadMySqlDatabase(); self::$sqliteConfig = self::loadSqliteDatabase(); @@ -111,14 +113,14 @@ public function setUp(): void 'expires_at' => date('Y-m-d H:i:s', time() - 60), // expired... 'user_id' => self::USER_ID, 'client_id' => self::CLIENT_ID, - 'is_revoked' => [false, PDO::PARAM_BOOL], + 'is_revoked' => false, 'auth_code_id' => self::AUTH_CODE_ID, 'requested_claims' => '[]', ]; $this->accessTokenEntityMock = $this->createMock(AccessTokenEntity::class); $this->accessTokenEntityFactoryMock = $this->createMock(AccessTokenEntityFactory::class); - $certFolder = dirname(__DIR__, 5) . '/docker/ssp/'; + $certFolder = dirname(__DIR__, 4) . '/docker/ssp/'; $privateKeyPath = $certFolder . ModuleConfig::DEFAULT_PKI_PRIVATE_KEY_FILENAME; $this->privateKey = new CryptKey($privateKeyPath); $this->accessTokenEntityFactory = new AccessTokenEntityFactory( @@ -139,18 +141,11 @@ public function useDatabase($config): void $moduleConfig = new ModuleConfig(); $this->mock = new class ($moduleConfig, $database, null) extends AbstractDatabaseRepository { - use RevokeTokenByAuthCodeIdTrait; - public function getTableName(): ?string { return $this->database->applyPrefix('oidc_access_token'); } - public function generateQueryWrapper(string $authCodeId, array $revokedParam): array - { - return $this->generateQuery($authCodeId, $revokedParam); - } - public function getDatabase(): Database { return $this->database; @@ -291,26 +286,6 @@ public static function databaseToTest(): array ]; } - #[DataProvider('databaseToTest')] - public function testItGenerateQuery(string $database): void - { - $this->useDatabase(self::$$database); - - $revokedParam = [self::IS_REVOKED, PDO::PARAM_BOOL]; - $expected = [ - 'UPDATE phpunit_oidc_access_token SET is_revoked = :is_revoked WHERE auth_code_id = :auth_code_id', - [ - 'auth_code_id' => self::AUTH_CODE_ID, - 'is_revoked' => $revokedParam, - ], - ]; - - $this->assertEquals( - $expected, - $this->mock->generateQueryWrapper(self::AUTH_CODE_ID, $revokedParam), - ); - } - /** * @throws \JsonException * @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException @@ -331,7 +306,7 @@ public function testRevokeByAuthCodeId(string $database): void $this->assertFalse($isRevoked); // Revoke the access token - $this->mock->revokeByAuthCodeId(self::AUTH_CODE_ID); + $this->accessTokenRepository->revokeByAuthCodeId(self::AUTH_CODE_ID); $isRevoked = $this->accessTokenRepository->isAccessTokenRevoked(self::ACCESS_TOKEN_ID); $this->assertTrue($isRevoked); diff --git a/tests/unit/src/Entities/AuthCodeEntityTest.php b/tests/unit/src/Entities/AuthCodeEntityTest.php index 5a494bcb..b9cc457e 100644 --- a/tests/unit/src/Entities/AuthCodeEntityTest.php +++ b/tests/unit/src/Entities/AuthCodeEntityTest.php @@ -6,7 +6,6 @@ use DateTimeImmutable; use DateTimeZone; -use PDO; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; @@ -96,7 +95,7 @@ public function testCanGetState(): void 'expires_at' => '1970-01-01 00:00:00', 'user_id' => 'user_id', 'client_id' => 'client_id', - 'is_revoked' => [false, PDO::PARAM_BOOL], + 'is_revoked' => false, 'redirect_uri' => 'https://localhost/redirect', 'nonce' => 'nonce', ], diff --git a/tests/unit/src/Entities/ClientEntityTest.php b/tests/unit/src/Entities/ClientEntityTest.php index 45d28284..49a709cc 100644 --- a/tests/unit/src/Entities/ClientEntityTest.php +++ b/tests/unit/src/Entities/ClientEntityTest.php @@ -5,7 +5,6 @@ namespace SimpleSAML\Test\Module\oidc\unit\Entities; use DateTimeImmutable; -use PDO; use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Codebooks\RegistrationTypeEnum; use SimpleSAML\Module\oidc\Entities\ClientEntity; @@ -168,8 +167,8 @@ public function testCanGetState(): void 'auth_source' => 'auth_source', 'redirect_uri' => json_encode(['https://localhost/redirect']), 'scopes' => json_encode([]), - 'is_enabled' => [$this->state['is_enabled'], PDO::PARAM_BOOL], - 'is_confidential' => [$this->state['is_confidential'], PDO::PARAM_BOOL], + 'is_enabled' => $this->state['is_enabled'], + 'is_confidential' => $this->state['is_confidential'], 'owner' => 'user@test.com', 'post_logout_redirect_uri' => json_encode([]), 'backchannel_logout_uri' => null, @@ -183,7 +182,7 @@ public function testCanGetState(): void 'updated_at' => null, 'created_at' => null, 'expires_at' => null, - 'is_federated' => [$this->state['is_federated'], PDO::PARAM_BOOL], + 'is_federated' => $this->state['is_federated'], ], ); } diff --git a/tests/unit/src/Entities/RefreshTokenEntityTest.php b/tests/unit/src/Entities/RefreshTokenEntityTest.php index 7abed99f..31c32bc8 100644 --- a/tests/unit/src/Entities/RefreshTokenEntityTest.php +++ b/tests/unit/src/Entities/RefreshTokenEntityTest.php @@ -6,7 +6,6 @@ use DateTimeImmutable; use DateTimeZone; -use PDO; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Entities\AccessTokenEntity; @@ -73,7 +72,7 @@ public function testCanGetState(): void 'id' => $this->id, 'expires_at' => '1970-01-01 00:00:00', 'access_token_id' => $this->accessTokenEntityMock->getIdentifier(), - 'is_revoked' => [$this->isRevoked, PDO::PARAM_BOOL], + 'is_revoked' => $this->isRevoked, 'auth_code_id' => $this->authCodeId, ], ); diff --git a/tests/unit/src/Helpers/ArrTest.php b/tests/unit/src/Helpers/ArrTest.php index a6fdd7e1..e3555dd9 100644 --- a/tests/unit/src/Helpers/ArrTest.php +++ b/tests/unit/src/Helpers/ArrTest.php @@ -16,6 +16,14 @@ protected function sut(): Arr return new Arr(); } + public function testEnsureStringValues(): void + { + $this->assertSame( + ['1', '2'], + $this->sut()->ensureStringValues([1, 2]), + ); + } + public function testIsValueOneOf(): void { $this->assertTrue($this->sut()->isValueOneOf('a', ['a'])); diff --git a/tests/unit/src/Helpers/ClientTest.php b/tests/unit/src/Helpers/ClientTest.php new file mode 100644 index 00000000..7916e678 --- /dev/null +++ b/tests/unit/src/Helpers/ClientTest.php @@ -0,0 +1,78 @@ +httpMock; + + return new Client($http); + } + + protected function setUp(): void + { + $this->httpMock = $this->createMock(Http::class); + $this->requestMock = $this->createMock(ServerRequestInterface::class); + $this->clientRepositoryMock = $this->createMock(ClientRepository::class); + $this->clientEntityMock = $this->createMock(ClientEntity::class); + } + + public function testCanGetFromRequest(): void + { + $this->httpMock->expects($this->once())->method('getAllRequestParams') + ->willReturn(['client_id' => 'clientId']); + + $this->clientRepositoryMock->expects($this->once())->method('findById') + ->with('clientId') + ->willReturn($this->clientEntityMock); + + $this->assertInstanceOf( + ClientEntity::class, + $this->sut()->getFromRequest($this->requestMock, $this->clientRepositoryMock), + ); + } + + public function testGetFromRequestThrowsIfNoClientId(): void + { + $this->expectException(BadRequest::class); + $this->expectExceptionMessage('Client ID'); + + $this->sut()->getFromRequest($this->requestMock, $this->clientRepositoryMock); + } + + public function testGetFromRequestThrowsIfClientNotFound(): void + { + $this->expectException(NotFound::class); + $this->expectExceptionMessage('Client not found'); + + $this->httpMock->expects($this->once())->method('getAllRequestParams') + ->willReturn(['client_id' => 'clientId']); + $this->clientRepositoryMock->expects($this->once())->method('findById') + ->with('clientId') + ->willReturn(null); + + $this->sut()->getFromRequest($this->requestMock, $this->clientRepositoryMock); + } +} diff --git a/tests/unit/src/Helpers/DateTimeTest.php b/tests/unit/src/Helpers/DateTimeTest.php new file mode 100644 index 00000000..f5d673f6 --- /dev/null +++ b/tests/unit/src/Helpers/DateTimeTest.php @@ -0,0 +1,48 @@ +assertInstanceOf(\DateTimeImmutable::class, $this->sut()->getUtc()); + $this->assertSame( + 'UTC', + $this->sut()->getUtc()->getTimezone()->getName(), + ); + } + + public function testCanGetFromTimestamp(): void + { + $timestamp = (new DateTimeImmutable())->getTimestamp(); + + $this->assertSame( + $timestamp, + $this->sut()->getFromTimestamp($timestamp)->getTimestamp(), + ); + } + + public function testCanGetSecondsToExpirationTime(): void + { + $expirationTime = (new DateTimeImmutable())->getTimestamp() + 60; + + $this->assertSame( + 60, + $this->sut()->getSecondsToExpirationTime($expirationTime), + ); + } +} diff --git a/tests/unit/src/Helpers/HttpTest.php b/tests/unit/src/Helpers/HttpTest.php new file mode 100644 index 00000000..2e4143e5 --- /dev/null +++ b/tests/unit/src/Helpers/HttpTest.php @@ -0,0 +1,87 @@ +serverRequestMock = $this->createMock(ServerRequestInterface::class); + } + + protected function sut(): Http + { + return new Http(); + } + + public function testCanGetAllRequestParams(): void + { + $this->serverRequestMock->expects($this->once())->method('getQueryParams') + ->willReturn(['a' => 'b']); + + $this->serverRequestMock->expects($this->once())->method('getParsedBody') + ->willReturn(['c' => 'd']); + + $this->assertSame( + ['a' => 'b', 'c' => 'd'], + $this->sut()->getAllRequestParams($this->serverRequestMock), + ); + } + + public function testCanGetAllRequestParamsBasedOnAllowedMethodsForGet(): void + { + $this->serverRequestMock->expects($this->once())->method('getMethod') + ->willReturn(HttpMethodsEnum::GET->value); + + $this->serverRequestMock->expects($this->once())->method('getQueryParams') + ->willReturn(['a' => 'b']); + + $this->assertSame( + ['a' => 'b'], + $this->sut()->getAllRequestParamsBasedOnAllowedMethods( + $this->serverRequestMock, + [HttpMethodsEnum::GET, HttpMethodsEnum::POST], + ), + ); + } + + public function testCanGetAllRequestParamsBasedOnAllowedMethodsForPost(): void + { + $this->serverRequestMock->expects($this->once())->method('getMethod') + ->willReturn(HttpMethodsEnum::POST->value); + + $this->serverRequestMock->expects($this->once())->method('getParsedBody') + ->willReturn(['c' => 'd']); + + $this->assertSame( + ['c' => 'd'], + $this->sut()->getAllRequestParamsBasedOnAllowedMethods( + $this->serverRequestMock, + [HttpMethodsEnum::GET, HttpMethodsEnum::POST], + ), + ); + } + + public function testGerAllRequestParamsBasedOnAllowedMethodsReturnsNullForNonAllowedMethod(): void + { + $this->serverRequestMock->expects($this->once())->method('getMethod') + ->willReturn(HttpMethodsEnum::POST->value); + + $this->assertNull( + $this->sut()->getAllRequestParamsBasedOnAllowedMethods( + $this->serverRequestMock, + [HttpMethodsEnum::GET], + ), + ); + } +} diff --git a/tests/unit/src/Helpers/RandomTest.php b/tests/unit/src/Helpers/RandomTest.php new file mode 100644 index 00000000..0960bc5c --- /dev/null +++ b/tests/unit/src/Helpers/RandomTest.php @@ -0,0 +1,33 @@ +assertNotEmpty( + $this->sut()->getIdentifier(), + ); + } + + public function testGetIdentifierThrowsOnInvalidLength(): void + { + $this->expectException(OidcServerException::class); + $this->expectExceptionMessage('Random'); + + $this->sut()->getIdentifier(0); + } +} diff --git a/tests/unit/src/Helpers/StrTest.php b/tests/unit/src/Helpers/StrTest.php new file mode 100644 index 00000000..f3397a9d --- /dev/null +++ b/tests/unit/src/Helpers/StrTest.php @@ -0,0 +1,34 @@ +assertSame( + ['a', 'b'], + $this->sut()->convertScopesStringToArray('a b'), + ); + } + + public function testCanConvertTextToArray(): void + { + $this->assertSame( + ['a', 'b', 'c', 'd'], + $this->sut()->convertTextToArray("a\tb\nc\rd"), + ); + } +} diff --git a/tests/unit/src/HelpersTest.php b/tests/unit/src/HelpersTest.php new file mode 100644 index 00000000..643ad853 --- /dev/null +++ b/tests/unit/src/HelpersTest.php @@ -0,0 +1,35 @@ +assertInstanceOf(Helpers\Http::class, $this->sut()->http()); + $this->assertInstanceOf(Helpers\Client::class, $this->sut()->client()); + $this->assertInstanceOf(Helpers\DateTime::class, $this->sut()->dateTime()); + $this->assertInstanceOf(Helpers\Str::class, $this->sut()->str()); + $this->assertInstanceOf(Helpers\Arr::class, $this->sut()->arr()); + $this->assertInstanceOf(Helpers\Random::class, $this->sut()->random()); + } +} diff --git a/tests/unit/src/ModuleConfigTest.php b/tests/unit/src/ModuleConfigTest.php index 4c6e0a81..f13c2d41 100644 --- a/tests/unit/src/ModuleConfigTest.php +++ b/tests/unit/src/ModuleConfigTest.php @@ -4,6 +4,7 @@ namespace SimpleSAML\Test\Module\oidc\unit; +use DateInterval; use Lcobucci\JWT\Signer; use Lcobucci\JWT\Signer\Rsa\Sha256; use PHPUnit\Framework\Attributes\CoversClass; @@ -62,6 +63,11 @@ class ModuleConfigTest extends TestCase ModuleConfig::OPTION_FEDERATION_AUTHORITY_HINTS => [ 'abc123', ], + + ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\ArrayAdapter::class, + ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS => [], + ModuleConfig::OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION => null, + ModuleConfig::OPTION_PROTOCOL_CLIENT_ENTITY_CACHE_DURATION => null, ]; private MockObject $sspBridgeMock; private MockObject $sspBridgeUtilsMock; @@ -95,9 +101,37 @@ protected function setUp(): void $this->sspBridgeUtilsMock->method('config')->willReturn($this->sspBridgeUtilsConfigMock); } - protected function mock(): ModuleConfig + protected function sut( + ?string $fileName = null, + ?array $overrides = null, + ?Configuration $sspConfig = null, + ?SspBridge $sspBridge = null, + ): ModuleConfig { + $fileName ??= $this->fileName; + $overrides ??= $this->overrides; + $sspConfig ??= $this->sspConfigMock; + $sspBridge ??= $this->sspBridgeMock; + + return new ModuleConfig( + $fileName, + $overrides, + $sspConfig, + $sspBridge, + ); + } + + public function testCanGetCommonOptions(): void { - return new ModuleConfig($this->fileName, $this->overrides, $this->sspConfigMock, $this->sspBridgeMock); + $this->assertSame(ModuleConfig::MODULE_NAME, $this->sut()->moduleName()); + + $this->assertInstanceOf(DateInterval::class, $this->sut()->getAuthCodeDuration()); + $this->assertInstanceOf(DateInterval::class, $this->sut()->getAccessTokenDuration()); + $this->assertInstanceOf(DateInterval::class, $this->sut()->getRefreshTokenDuration()); + + $this->assertSame( + $this->moduleConfig[ModuleConfig::OPTION_AUTH_SOURCE], + $this->sut()->getDefaultAuthSourceId(), + ); } /** @@ -108,54 +142,54 @@ public function testSigningKeyNameCanBeCustomized(): void // Test default cert and pem $this->assertStringContainsString( ModuleConfig::DEFAULT_PKI_CERTIFICATE_FILENAME, - $this->mock()->getProtocolCertPath(), + $this->sut()->getProtocolCertPath(), ); $this->assertStringContainsString( ModuleConfig::DEFAULT_PKI_PRIVATE_KEY_FILENAME, - $this->mock()->getProtocolPrivateKeyPath(), + $this->sut()->getProtocolPrivateKeyPath(), ); // Set customized $this->overrides[ModuleConfig::OPTION_PKI_PRIVATE_KEY_FILENAME] = 'myPrivateKey.key'; $this->overrides[ModuleConfig::OPTION_PKI_CERTIFICATE_FILENAME] = 'myCertificate.crt'; - $this->assertStringContainsString('myCertificate.crt', $this->mock()->getProtocolCertPath()); - $this->assertStringContainsString('myPrivateKey.key', $this->mock()->getProtocolPrivateKeyPath()); + $this->assertStringContainsString('myCertificate.crt', $this->sut()->getProtocolCertPath()); + $this->assertStringContainsString('myPrivateKey.key', $this->sut()->getProtocolPrivateKeyPath()); } public function testCanGetSspConfig(): void { - $this->assertInstanceOf(Configuration::class, $this->mock()->sspConfig()); + $this->assertInstanceOf(Configuration::class, $this->sut()->sspConfig()); } public function testCanGetModuleUrl(): void { - $this->assertStringContainsString(ModuleConfig::MODULE_NAME, $this->mock()->getModuleUrl('test')); + $this->assertStringContainsString(ModuleConfig::MODULE_NAME, $this->sut()->getModuleUrl('test')); } public function testCanGetOpenIdScopes(): void { - $this->assertNotEmpty($this->mock()->getScopes()); + $this->assertNotEmpty($this->sut()->getScopes()); } public function testCanGetProtocolSigner(): void { - $this->assertInstanceOf(Signer::class, $this->mock()->getProtocolSigner()); + $this->assertInstanceOf(Signer::class, $this->sut()->getProtocolSigner()); } public function testCanGetProtocolPrivateKeyPassphrase(): void { $this->overrides[ModuleConfig::OPTION_PKI_PRIVATE_KEY_PASSPHRASE] = 'test'; - $this->assertNotEmpty($this->mock()->getProtocolPrivateKeyPassPhrase()); + $this->assertNotEmpty($this->sut()->getProtocolPrivateKeyPassPhrase()); } public function testCanGetAuthProcFilters(): void { - $this->assertIsArray($this->mock()->getAuthProcFilters()); + $this->assertIsArray($this->sut()->getAuthProcFilters()); } public function testCanGetIssuer(): void { - $this->assertNotEmpty($this->mock()->getIssuer()); + $this->assertNotEmpty($this->sut()->getIssuer()); } public function testGetsCurrentHostIfIssuerNotSetInConfig(): void @@ -163,7 +197,7 @@ public function testGetsCurrentHostIfIssuerNotSetInConfig(): void $this->sspBridgeUtilsHttpMock->expects($this->once())->method('getSelfURLHost') ->willReturn('sample'); $this->overrides[ModuleConfig::OPTION_ISSUER] = null; - $this->mock()->getIssuer(); + $this->sut()->getIssuer(); } public function testThrowsOnEmptyIssuer(): void @@ -171,41 +205,82 @@ public function testThrowsOnEmptyIssuer(): void $this->overrides[ModuleConfig::OPTION_ISSUER] = ''; $this->expectException(OidcServerException::class); - $this->mock()->getIssuer(); + $this->sut()->getIssuer(); } public function testCanGetForcedAcrValueForCookieAuthentication(): void { $this->overrides[ModuleConfig::OPTION_AUTH_FORCED_ACR_VALUE_FOR_COOKIE_AUTHENTICATION] = '1a'; $this->overrides[ModuleConfig::OPTION_AUTH_ACR_VALUES_SUPPORTED] = ['1a']; - $this->assertEquals('1a', $this->mock()->getForcedAcrValueForCookieAuthentication()); + $this->assertEquals('1a', $this->sut()->getForcedAcrValueForCookieAuthentication()); } public function testCanGetUserIdentifierAttribute(): void { $this->overrides[ModuleConfig::OPTION_AUTH_USER_IDENTIFIER_ATTRIBUTE] = 'sample'; - $this->assertEquals('sample', $this->mock()->getUserIdentifierAttribute()); + $this->assertEquals('sample', $this->sut()->getUserIdentifierAttribute()); } public function testCanGetCommonFederationOptions(): void { - $this->assertInstanceOf(Signer::class, $this->mock()->getFederationSigner()); + $this->assertFalse($this->sut()->getFederationEnabled()); + $this->assertInstanceOf(Signer::class, $this->sut()->getFederationSigner()); $this->assertStringContainsString( ModuleConfig::DEFAULT_PKI_FEDERATION_PRIVATE_KEY_FILENAME, - $this->mock()->getFederationPrivateKeyPath(), + $this->sut()->getFederationPrivateKeyPath(), ); - $this->assertNotEmpty($this->mock()->getFederationPrivateKeyPassPhrase()); + $this->assertNotEmpty($this->sut()->getFederationPrivateKeyPassPhrase()); $this->assertStringContainsString( ModuleConfig::DEFAULT_PKI_FEDERATION_CERTIFICATE_FILENAME, - $this->mock()->getFederationCertPath(), + $this->sut()->getFederationCertPath(), ); - $this->assertNotEmpty($this->mock()->getFederationEntityStatementDuration()); - $this->assertNotEmpty($this->mock()->getFederationAuthorityHints()); - $this->assertNotEmpty($this->mock()->getOrganizationName()); - $this->assertNotEmpty($this->mock()->getContacts()); - $this->assertNotEmpty($this->mock()->getLogoUri()); - $this->assertNotEmpty($this->mock()->getPolicyUri()); - $this->assertNotEmpty($this->mock()->getHomepageUri()); + $this->assertNotEmpty($this->sut()->getFederationEntityStatementDuration()); + $this->assertNotEmpty($this->sut()->getFederationEntityStatementCacheDurationForProduced()); + $this->assertNotEmpty($this->sut()->getFederationAuthorityHints()); + $this->assertNotEmpty($this->sut()->getFederationTrustMarkTokens()); + $this->assertNotEmpty($this->sut()->getOrganizationName()); + $this->assertNotEmpty($this->sut()->getContacts()); + $this->assertNotEmpty($this->sut()->getLogoUri()); + $this->assertNotEmpty($this->sut()->getPolicyUri()); + $this->assertNotEmpty($this->sut()->getHomepageUri()); + $this->assertNotEmpty($this->sut()->getFederationCacheAdapterClass()); + $this->assertIsArray($this->sut()->getFederationCacheAdapterArguments()); + $this->assertNotEmpty($this->sut()->getFederationCacheMaxDurationForFetched()); + $this->assertNotEmpty($this->sut()->getFederationTrustAnchors()); + $this->assertNotEmpty($this->sut()->getFederationTrustAnchorIds()); + } + + public function testGetFederationTrustAnchorsThrowsOnEmptyIfFederationEnabled(): void + { + $this->expectException(ConfigurationError::class); + $this->expectExceptionMessage('No Trust Anchors'); + + $this->sut( + overrides: [ + ModuleConfig::OPTION_FEDERATION_ENABLED => true, + ModuleConfig::OPTION_FEDERATION_TRUST_ANCHORS => [], + ], + )->getFederationTrustAnchors(); + } + + + + public function testCanGetTrustAnchorJwksJson(): void + { + $this->assertNotEmpty($this->sut()->getTrustAnchorJwksJson('https://ta.example.org/')); + $this->assertEmpty($this->sut()->getTrustAnchorJwksJson('invalid')); + } + + public function testGetTrustAnchorJwksJsonThrowsOnInvalidData(): void + { + $this->expectException(ConfigurationError::class); + $this->expectExceptionMessage('format'); + + $this->sut( + overrides: [ + ModuleConfig::OPTION_FEDERATION_TRUST_ANCHORS => ['ta' => 123], + ], + )->getTrustAnchorJwksJson('ta'); } public function testThrowsIfTryingToOverrideProtectedScopes(): void @@ -217,7 +292,7 @@ public function testThrowsIfTryingToOverrideProtectedScopes(): void ]; $this->expectException(ConfigurationError::class); - $this->mock(); + $this->sut(); } public function testThrowsIfCustomScopeDoesNotHaveDescription(): void @@ -227,7 +302,7 @@ public function testThrowsIfCustomScopeDoesNotHaveDescription(): void ]; $this->expectException(ConfigurationError::class); - $this->mock(); + $this->sut(); } public function testThrowsIfAcrIsNotString(): void @@ -235,35 +310,35 @@ public function testThrowsIfAcrIsNotString(): void $this->overrides[ModuleConfig::OPTION_AUTH_ACR_VALUES_SUPPORTED] = [123]; $this->expectException(ConfigurationError::class); - $this->mock(); + $this->sut(); } public function testThrowsIfAuthSourceNotString(): void { $this->overrides[ModuleConfig::OPTION_AUTH_SOURCES_TO_ACR_VALUES_MAP] = [123 => []]; $this->expectException(ConfigurationError::class); - $this->mock(); + $this->sut(); } public function testThrowsIfAuthSourceToAcrMapAcrNotArray(): void { $this->overrides[ModuleConfig::OPTION_AUTH_SOURCES_TO_ACR_VALUES_MAP] = ['abc' => 123]; $this->expectException(ConfigurationError::class); - $this->mock(); + $this->sut(); } public function testThrowsIfAuthSourceToAcrMapAcrNotString(): void { $this->overrides[ModuleConfig::OPTION_AUTH_SOURCES_TO_ACR_VALUES_MAP] = ['abc' => [123]]; $this->expectException(ConfigurationError::class); - $this->mock(); + $this->sut(); } public function testThrowsIfAuthSourceToAcrMapAcrNotAllowed(): void { $this->overrides[ModuleConfig::OPTION_AUTH_SOURCES_TO_ACR_VALUES_MAP] = ['abc' => ['acr']]; $this->expectException(ConfigurationError::class); - $this->mock(); + $this->sut(); } public function testThrowsIForcedAcrValueForCookieAuthenticationNotAllowed(): void @@ -271,13 +346,30 @@ public function testThrowsIForcedAcrValueForCookieAuthenticationNotAllowed(): vo $this->overrides[ModuleConfig::OPTION_AUTH_ACR_VALUES_SUPPORTED] = ['abc']; $this->overrides[ModuleConfig::OPTION_AUTH_FORCED_ACR_VALUE_FOR_COOKIE_AUTHENTICATION] = 'cba'; $this->expectException(ConfigurationError::class); - $this->mock(); + $this->sut(); } public function testThrowsIfInvalidSignerProvided(): void { $this->overrides[ModuleConfig::OPTION_TOKEN_SIGNER] = stdClass::class; $this->expectException(ConfigurationError::class); - $this->mock()->getProtocolSigner(); + $this->sut()->getProtocolSigner(); + } + + public function testCanGetEncryptionKey(): void + { + $this->sspBridgeUtilsConfigMock->expects($this->once())->method('getSecretSalt') + ->willReturn('secretSalt'); + + $this->assertSame('secretSalt', $this->sut()->getEncryptionKey()); + } + + public function testCanGetProtocolCacheConfiguration(): void + { + $this->assertNotEmpty($this->sut()->getProtocolCacheAdapterClass()); + $this->assertIsArray($this->sut()->getProtocolCacheAdapterArguments()); + + $this->assertInstanceOf(DateInterval::class, $this->sut()->getProtocolUserEntityCacheDuration()); + $this->assertInstanceOf(DateInterval::class, $this->sut()->getProtocolClientEntityCacheDuration()); } } diff --git a/tests/unit/src/Repositories/AbstractDatabaseRepositoryTest.php b/tests/unit/src/Repositories/AbstractDatabaseRepositoryTest.php new file mode 100644 index 00000000..c54f34a0 --- /dev/null +++ b/tests/unit/src/Repositories/AbstractDatabaseRepositoryTest.php @@ -0,0 +1,51 @@ +moduleConfigMock = $this->createMock(ModuleConfig::class); + $this->databaseMock = $this->createMock(Database::class); + $this->protocolCacheMock = $this->createMock(ProtocolCache::class); + } + + protected function sut( + ?ModuleConfig $moduleConfig = null, + ?Database $database = null, + ?ProtocolCache $protocolCache = null, + ): AbstractDatabaseRepository { + $moduleConfig ??= $this->moduleConfigMock; + $database ??= $this->databaseMock; + $protocolCache ??= $this->protocolCacheMock; + + return new class ($moduleConfig, $database, $protocolCache) extends AbstractDatabaseRepository + { + public function getTableName(): ?string + { + return 'sut'; + } + }; + } + + public function testCanGetCacheKey(): void + { + $this->assertSame('sut_something', $this->sut()->getCacheKey('something')); + } +} diff --git a/tests/unit/src/Repositories/AccessTokenRepositoryTest.php b/tests/unit/src/Repositories/AccessTokenRepositoryTest.php index 7da2cf77..efe6fe32 100644 --- a/tests/unit/src/Repositories/AccessTokenRepositoryTest.php +++ b/tests/unit/src/Repositories/AccessTokenRepositoryTest.php @@ -17,31 +17,33 @@ use DateTimeImmutable; use Exception; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; use SimpleSAML\Database; +use SimpleSAML\Error\Error; use SimpleSAML\Module\oidc\Codebooks\DateFormatsEnum; use SimpleSAML\Module\oidc\Entities\AccessTokenEntity; -use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; +use SimpleSAML\Module\oidc\Entities\ClientEntity; +use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface; use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory; use SimpleSAML\Module\oidc\Factories\Entities\ClientEntityFactory; use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; use SimpleSAML\Module\oidc\Repositories\ClientRepository; +use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\DatabaseMigration; +use SimpleSAML\Module\oidc\Utils\ProtocolCache; -/** - * @covers \SimpleSAML\Module\oidc\Repositories\AccessTokenRepository - */ +#[CoversClass(AccessTokenRepository::class)] class AccessTokenRepositoryTest extends TestCase { final public const CLIENT_ID = 'access_token_client_id'; final public const USER_ID = 'access_token_user_id'; final public const ACCESS_TOKEN_ID = 'access_token_id'; - - protected AccessTokenRepository $repository; + final public const AUTH_CODE_ID = 'auth_code_id'; protected MockObject $moduleConfigMock; protected MockObject $clientRepositoryMock; @@ -52,8 +54,10 @@ class AccessTokenRepositoryTest extends TestCase protected MockObject $dateTimeHelperMock; protected static bool $dbSeeded = false; - protected ClientEntityInterface $clientEntity; + protected MockObject $clientEntityMock; protected array $accessTokenState; + protected Database $database; + protected MockObject $protocolCacheMock; /** * @throws \Exception @@ -79,10 +83,10 @@ protected function setUp(): void $this->clientEntityFactoryMock = $this->createMock(ClientEntityFactory::class); $this->clientRepositoryMock = $this->createMock(ClientRepository::class); - $this->clientEntity = ClientRepositoryTest::getClient(self::CLIENT_ID); - $this->clientRepositoryMock->method('findById')->willReturn($this->clientEntity); + $this->clientEntityMock = $this->createMock(ClientEntity::class); + $this->clientRepositoryMock->method('findById')->willReturn($this->clientEntityMock); - $this->clientEntityFactoryMock->method('fromState')->willReturn($this->clientEntity); + $this->clientEntityFactoryMock->method('fromState')->willReturn($this->clientEntityMock); $this->accessTokenEntityMock = $this->createMock(AccessTokenEntity::class); $this->accessTokenEntityFactoryMock = $this->createMock(AccessTokenEntityFactory::class); @@ -96,28 +100,45 @@ protected function setUp(): void 'user_id' => 'user123', 'client_id' => self::CLIENT_ID, 'is_revoked' => false, - 'auth_code_id' => 'authCode123', + 'auth_code_id' => self::AUTH_CODE_ID, ]; $this->helpersMock = $this->createMock(Helpers::class); $this->dateTimeHelperMock = $this->createMock(Helpers\DateTime::class); $this->helpersMock->method('dateTime')->willReturn($this->dateTimeHelperMock); - $database = Database::getInstance(); + $this->database = Database::getInstance(); + $this->protocolCacheMock = $this->createMock(ProtocolCache::class); + } - $this->repository = new AccessTokenRepository( - $this->moduleConfigMock, + protected function sut( + ?ModuleConfig $moduleConfig = null, + ?Database $database = null, + ?ProtocolCache $protocolCache = null, + ?ClientRepository $clientRepository = null, + ?AccessTokenEntityFactory $accessTokenEntityFactory = null, + ?Helpers $helpers = null, + ): AccessTokenRepository { + $moduleConfig ??= $this->moduleConfigMock; + $database ??= $this->database; + $protocolCache ??= $this->protocolCacheMock; + $clientRepository ??= $this->clientRepositoryMock; + $accessTokenEntityFactory ??= $this->accessTokenEntityFactoryMock; + $helpers ??= $this->helpersMock; + + return new AccessTokenRepository( + $moduleConfig, $database, - null, - $this->clientRepositoryMock, - $this->accessTokenEntityFactoryMock, - $this->helpersMock, + $protocolCache, + $clientRepository, + $accessTokenEntityFactory, + $helpers, ); } public function testGetTableName(): void { - $this->assertSame('phpunit_oidc_access_token', $this->repository->getTableName()); + $this->assertSame('phpunit_oidc_access_token', $this->sut()->getTableName()); } /** @@ -130,20 +151,33 @@ public function testGetTableName(): void public function testAddAndFound(): void { $this->accessTokenEntityMock->method('getState')->willReturn($this->accessTokenState); + $this->accessTokenEntityMock->method('getExpiryDateTime') + ->willReturn(new DateTimeImmutable()); - $this->repository->persistNewAccessToken($this->accessTokenEntityMock); + $sut = $this->sut(); + $sut->persistNewAccessToken($this->accessTokenEntityMock); - $foundAccessToken = $this->repository->findById(self::ACCESS_TOKEN_ID); + $foundAccessToken = $sut->findById(self::ACCESS_TOKEN_ID); $this->assertEquals($this->accessTokenEntityMock, $foundAccessToken); } + public function testPersistNewAccessTokenThrowsIfNotAccessTokenEntity(): void + { + $oAuthAccessTokenEntity = $this->createMock(\League\OAuth2\Server\Entities\AccessTokenEntityInterface::class); + + $this->expectException(Error::class); + $this->expectExceptionMessage('Invalid'); + + $this->sut()->persistNewAccessToken($oAuthAccessTokenEntity); + } + /** * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testAddAndNotFound(): void { - $notFoundAccessToken = $this->repository->findById('notoken'); + $notFoundAccessToken = $this->sut()->findById('notoken'); $this->assertNull($notFoundAccessToken); } @@ -155,13 +189,17 @@ public function testAddAndNotFound(): void public function testRevokeToken(): void { $this->accessTokenEntityMock->expects($this->once())->method('revoke'); + $this->accessTokenEntityMock->method('getExpiryDateTime') + ->willReturn(new DateTimeImmutable()); + $state = $this->accessTokenState; $state['is_revoked'] = true; $this->accessTokenEntityMock->method('getState')->willReturn($state); $this->accessTokenEntityMock->method('isRevoked')->willReturn(true); - $this->repository->revokeAccessToken(self::ACCESS_TOKEN_ID); - $isRevoked = $this->repository->isAccessTokenRevoked(self::ACCESS_TOKEN_ID); + $sut = $this->sut(); + $sut->revokeAccessToken(self::ACCESS_TOKEN_ID); + $isRevoked = $sut->isAccessTokenRevoked(self::ACCESS_TOKEN_ID); $this->assertTrue($isRevoked); } @@ -174,7 +212,7 @@ public function testErrorRevokeInvalidToken(): void { $this->expectException(Exception::class); - $this->repository->revokeAccessToken('notoken'); + $this->sut()->revokeAccessToken('notoken'); } /** @@ -184,7 +222,7 @@ public function testErrorCheckIsRevokedInvalidToken(): void { $this->expectException(Exception::class); - $this->repository->isAccessTokenRevoked('notoken'); + $this->sut()->isAccessTokenRevoked('notoken'); } /** @@ -199,9 +237,78 @@ public function testRemoveExpired(): void $this->dateTimeHelperMock->expects($this->once())->method('getUtc') ->willReturn($dateTimeMock); - $this->repository->removeExpired(); - $notFoundAccessToken = $this->repository->findById(self::ACCESS_TOKEN_ID); + $sut = $this->sut(); + $sut->removeExpired(); + $notFoundAccessToken = $sut->findById(self::ACCESS_TOKEN_ID); $this->assertNull($notFoundAccessToken); } + + public function testCanGetNewToken() + { + $this->accessTokenEntityFactoryMock->expects($this->once())->method('fromData') + ->willReturn($this->accessTokenEntityMock); + + $this->assertInstanceOf( + AccessTokenEntityInterface::class, + $this->sut()->getNewToken( + $this->clientEntityMock, + [], + 'userId', + 'authCodeId', + [], + 'id', + new DateTimeImmutable(), + ), + ); + } + + public function testCanGetNewTokenForEmptyUserId(): void + { + $this->accessTokenEntityFactoryMock->expects($this->once())->method('fromData') + ->willReturn($this->accessTokenEntityMock); + + $this->assertInstanceOf( + AccessTokenEntityInterface::class, + $this->sut()->getNewToken( + $this->clientEntityMock, + [], + '', + 'authCodeId', + [], + 'id', + new DateTimeImmutable(), + ), + ); + } + + public function testCanGetNewTokenThrowsForEmptyId(): void + { + $this->expectException(OidcServerException::class); + $this->expectExceptionMessage('Invalid'); + + $this->sut()->getNewToken( + $this->clientEntityMock, + [], + '', + 'authCodeId', + [], + null, + new DateTimeImmutable(), + ); + } + + public function testCanRevokeByAuthCodeId(): void + { + $this->accessTokenEntityMock->method('getState')->willReturn($this->accessTokenState); + $this->accessTokenEntityMock->method('getExpiryDateTime') + ->willReturn(new DateTimeImmutable()); + + $this->accessTokenEntityMock->expects($this->once())->method('revoke'); + + $sut = $this->sut(); + $sut->persistNewAccessToken($this->accessTokenEntityMock); + + $sut->revokeByAuthCodeId(self::AUTH_CODE_ID); + } } diff --git a/tests/unit/src/Repositories/AllowedOriginRepositoryTest.php b/tests/unit/src/Repositories/AllowedOriginRepositoryTest.php index 2c6f438c..47b3cc8c 100644 --- a/tests/unit/src/Repositories/AllowedOriginRepositoryTest.php +++ b/tests/unit/src/Repositories/AllowedOriginRepositoryTest.php @@ -4,12 +4,14 @@ namespace SimpleSAML\Test\Module\oidc\unit\Repositories; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; use SimpleSAML\Database; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\AllowedOriginRepository; use SimpleSAML\Module\oidc\Services\DatabaseMigration; +use SimpleSAML\Module\oidc\Utils\ProtocolCache; /** * @covers \SimpleSAML\Module\oidc\Repositories\AllowedOriginRepository @@ -18,6 +20,10 @@ class AllowedOriginRepositoryTest extends TestCase { final public const CLIENT_ID = 'some_client_id'; + protected MockObject $moduleConfigMock; + protected MockObject $protocolCacheMock; + + final public const ORIGINS = [ 'https://example.org', 'https://sample.com', @@ -45,12 +51,15 @@ public static function setUpBeforeClass(): void protected function setUp(): void { - $moduleConfigMock = $this->createMock(ModuleConfig::class); + $this->moduleConfigMock = $this->createMock(ModuleConfig::class); + $this->protocolCacheMock = $this->createMock(ProtocolCache::class); + $database = Database::getInstance(); + $this->repository = new AllowedOriginRepository( - $moduleConfigMock, + $this->moduleConfigMock, $database, - null, + $this->protocolCacheMock, ); } @@ -79,4 +88,12 @@ public function testSetGetHasDelete(): void $this->assertFalse($this->repository->has(self::ORIGINS[0])); $this->assertFalse($this->repository->has(self::ORIGINS[1])); } + + public function testHasCanReturnFromCache(): void + { + $this->protocolCacheMock->expects($this->once())->method('get') + ->willReturn(true); + + $this->assertTrue($this->repository->has('origin')); + } } diff --git a/tests/unit/src/Repositories/AuthCodeRepositoryTest.php b/tests/unit/src/Repositories/AuthCodeRepositoryTest.php index 99cce538..966ed188 100644 --- a/tests/unit/src/Repositories/AuthCodeRepositoryTest.php +++ b/tests/unit/src/Repositories/AuthCodeRepositoryTest.php @@ -18,10 +18,12 @@ use DateTimeImmutable; use DateTimeZone; use Exception; +use League\OAuth2\Server\Entities\AuthCodeEntityInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; use SimpleSAML\Database; +use SimpleSAML\Error\Error; use SimpleSAML\Module\oidc\Codebooks\DateFormatsEnum; use SimpleSAML\Module\oidc\Entities\AuthCodeEntity; use SimpleSAML\Module\oidc\Entities\ClientEntity; @@ -32,6 +34,7 @@ use SimpleSAML\Module\oidc\Repositories\AuthCodeRepository; use SimpleSAML\Module\oidc\Repositories\ClientRepository; use SimpleSAML\Module\oidc\Services\DatabaseMigration; +use SimpleSAML\Module\oidc\Utils\ProtocolCache; /** * @covers \SimpleSAML\Module\oidc\Repositories\AuthCodeRepository @@ -48,6 +51,8 @@ class AuthCodeRepositoryTest extends TestCase protected MockObject $clientRepositoryMock; protected MockObject $authCodeEntityFactoryMock; protected MockObject $helpersMock; + protected MockObject $moduleConfigMock; + protected MockObject $protocolCacheMock; protected MockObject $dateTimeHelperMock; /** @var \League\OAuth2\Server\Entities\ScopeEntityInterface[] */ protected array $scopes; @@ -72,6 +77,9 @@ public static function setUpBeforeClass(): void protected function setUp(): void { + $this->moduleConfigMock = $this->createMock(ModuleConfig::class); + $this->protocolCacheMock = $this->createMock(ProtocolCache::class); + $this->clientEntityMock = $this->createMock(ClientEntity::class); $this->clientEntityMock->method('getIdentifier')->willReturn(self::CLIENT_ID); $this->clientRepositoryMock = $this->createMock(ClientRepository::class); @@ -88,9 +96,9 @@ protected function setUp(): void $database = Database::getInstance(); $this->repository = new AuthCodeRepository( - $this->createMock(ModuleConfig::class), + $this->moduleConfigMock, $database, - null, + $this->protocolCacheMock, $this->clientRepositoryMock, $this->authCodeEntityFactoryMock, $this->helpersMock, @@ -216,4 +224,21 @@ public function testRemoveExpired(): void $this->assertNull($notFoundAuthCode); } + + public function testGetNewAuthCodeThrows(): void + { + $this->expectException(\RuntimeException::class); + + $this->repository->getNewAuthCode(); + } + + public function testPersistNewAuthCodeThrowsIfNotAuthCodeEntity(): void + { + $this->expectException(Error::class); + $this->expectExceptionMessage('Invalid'); + + $this->repository->persistNewAuthCode( + $this->createMock(AuthCodeEntityInterface::class), + ); + } } diff --git a/tests/unit/src/Repositories/ClientRepositoryTest.php b/tests/unit/src/Repositories/ClientRepositoryTest.php index 2d18cd8e..d8a493bb 100644 --- a/tests/unit/src/Repositories/ClientRepositoryTest.php +++ b/tests/unit/src/Repositories/ClientRepositoryTest.php @@ -25,6 +25,7 @@ use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\ClientRepository; use SimpleSAML\Module\oidc\Services\DatabaseMigration; +use SimpleSAML\Module\oidc\Utils\ProtocolCache; /** * @covers \SimpleSAML\Module\oidc\Repositories\ClientRepository @@ -35,7 +36,6 @@ class ClientRepositoryTest extends TestCase protected MockObject $clientEntityMock; protected MockObject $clientEntityFactoryMock; - /** * @throws \Exception */ @@ -121,6 +121,20 @@ public function testGetClientEntity(): void $this->assertNotNull($client); } + public function testGetClientEntityReturnsNullForExpiredClient(): void + { + $this->clientEntityMock->expects($this->once())->method('isExpired')->willReturn(true); + + $this->clientEntityFactoryMock->expects($this->once())->method('fromState') + ->willReturn($this->clientEntityMock); + + // Just so we have a client with this ID in repo. + $client = self::getClient('clientid'); + $this->repository->add($client); + + $this->assertNull($this->repository->getClientEntity('clientid')); + } + /** * @throws \JsonException */ @@ -263,24 +277,25 @@ public function testFindPaginationWithEmptyList() */ public function testUpdate(): void { - $client = self::getClient('clientid'); + $client = self::getClient(id: 'clientId', entityId: 'entityId'); $this->repository->add($client); $client = new ClientEntity( - 'clientid', - 'newclientsecret', - 'Client', - 'Description', - ['http://localhost/redirect'], - ['openid'], - true, - false, - 'admin', + identifier: 'clientId', + secret: 'newclientsecret', + name: 'Client', + description: 'Description', + redirectUri: ['http://localhost/redirect'], + scopes: ['openid'], + isEnabled: true, + isConfidential: false, + authSource: 'admin', + entityIdentifier: 'newEntityId', ); $this->repository->update($client); $this->clientEntityFactoryMock->expects($this->once())->method('fromState')->willReturn($client); - $foundClient = $this->repository->findById('clientid'); + $foundClient = $this->repository->findById('clientId'); $this->assertEquals($client, $foundClient); } @@ -291,13 +306,13 @@ public function testUpdate(): void */ public function testDelete(): void { - $client = self::getClient('clientid'); + $client = self::getClient(id: 'clientId', entityId: 'entityId'); $this->repository->add($client); $this->clientEntityFactoryMock->expects($this->once())->method('fromState')->willReturn($client); - $client = $this->repository->findById('clientid'); + $client = $this->repository->findById('clientId'); $this->repository->delete($client); - $foundClient = $this->repository->findById('clientid'); + $foundClient = $this->repository->findById('clientId'); $this->assertNull($foundClient); } @@ -354,23 +369,81 @@ public function testCrudWithOwner(): void $this->assertNotNull($foundClient); } + public function testCanFindByIdFromCache(): void + { + $protocolCacheMock = $this->createMock(ProtocolCache::class); + $protocolCacheMock->expects($this->once())->method('get')->willReturn(['state']); + + + $this->clientEntityFactoryMock->expects($this->once())->method('fromState') + ->with(['state']) + ->willReturn($this->clientEntityMock); + + $sut = new ClientRepository( + new ModuleConfig(), + Database::getInstance(), + $protocolCacheMock, + $this->clientEntityFactoryMock, + ); + + $this->assertInstanceOf(ClientEntityInterface::class, $sut->findById('clientid')); + } + + public function testCanFindByEntityIdentifier(): void + { + $client = self::getClient(id: 'clientId', entityId: 'entityId', isFederated: true); + $this->repository->add($client); + + $this->clientEntityFactoryMock->expects($this->once())->method('fromState')->willReturn($client); + + $this->assertSame( + $client, + $this->repository->findByEntityIdentifier('entityId'), + ); + + $this->assertNull($this->repository->findByEntityIdentifier('nonExistingEntityId')); + } + + public function testCanFindByEntityIdFromCache(): void + { + $protocolCacheMock = $this->createMock(ProtocolCache::class); + $protocolCacheMock->expects($this->once())->method('get')->willReturn(['state']); + + $this->clientEntityFactoryMock->expects($this->once())->method('fromState') + ->with(['state']) + ->willReturn($this->clientEntityMock); + + $sut = new ClientRepository( + new ModuleConfig(), + Database::getInstance(), + $protocolCacheMock, + $this->clientEntityFactoryMock, + ); + + $this->assertInstanceOf(ClientEntityInterface::class, $sut->findByEntityIdentifier('entityId')); + } + public static function getClient( string $id, bool $enabled = true, bool $confidential = false, ?string $owner = null, + ?string $entityId = null, + bool $isFederated = false, ): ClientEntityInterface { return new ClientEntity( - $id, - 'clientsecret', - 'Client', - 'Description', - ['http://localhost/redirect'], - ['openid'], - $enabled, - $confidential, - 'admin', - $owner, + identifier: $id, + secret: 'clientsecret', + name: 'Client', + description: 'Description', + redirectUri: ['http://localhost/redirect'], + scopes: ['openid'], + isEnabled: $enabled, + isConfidential: $confidential, + authSource: 'admin', + owner: $owner, + entityIdentifier: $entityId, + isFederated: $isFederated, ); } } diff --git a/tests/unit/src/Repositories/CodeChallengeVerifiersRepositoryTest.php b/tests/unit/src/Repositories/CodeChallengeVerifiersRepositoryTest.php new file mode 100644 index 00000000..06b34126 --- /dev/null +++ b/tests/unit/src/Repositories/CodeChallengeVerifiersRepositoryTest.php @@ -0,0 +1,46 @@ +assertInstanceOf(CodeChallengeVerifiersRepository::class, $this->sut()); + } + + public function testCanGetCodeChallengeVerifier(): void + { + $this->assertInstanceOf( + CodeChallengeVerifierInterface::class, + $this->sut()->get('S256'), + ); + $this->assertTrue($this->sut()->has('S256')); + + $this->assertInstanceOf( + CodeChallengeVerifierInterface::class, + $this->sut()->get('plain'), + ); + $this->assertTrue($this->sut()->has('plain')); + + $this->assertNotEmpty($this->sut()->getAll()); + } + + public function testReturnsNullForUnsuportedVerifier(): void + { + $this->assertNull($this->sut()->get('unsuported')); + } +} diff --git a/tests/unit/src/Repositories/RefreshTokenRepositoryTest.php b/tests/unit/src/Repositories/RefreshTokenRepositoryTest.php index ce04ccbd..6c706c84 100644 --- a/tests/unit/src/Repositories/RefreshTokenRepositoryTest.php +++ b/tests/unit/src/Repositories/RefreshTokenRepositoryTest.php @@ -17,6 +17,8 @@ use DateTimeImmutable; use DateTimeZone; +use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; +use League\OAuth2\Server\Exception\OAuthServerException; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use RuntimeException; @@ -40,6 +42,7 @@ class RefreshTokenRepositoryTest extends TestCase final public const USER_ID = 'refresh_token_user_id'; final public const ACCESS_TOKEN_ID = 'refresh_token_access_token_id'; final public const REFRESH_TOKEN_ID = 'refresh_token_id'; + final public const AUTH_CODE_ID = 'auth_code_id'; protected RefreshTokenRepository $repository; protected MockObject $accessTokenMock; @@ -184,4 +187,45 @@ public function testRemoveExpired(): void $this->assertNull($notFoundRefreshToken); } + + public function testGetNewRefreshTokenThrows(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Not implemented'); + + $this->repository->getNewRefreshToken(); + } + + public function testPersistNewRefreshTokenThrowsIfNotRefreshTokenEntity(): void + { + $this->expectException(OAuthServerException::class); + + $oAuthRefreshTokenEntity = $this->createMock(RefreshTokenEntityInterface::class); + + $this->repository->persistNewRefreshToken($oAuthRefreshTokenEntity); + } + + public function testCanRevokeByAuthCodeId(): void + { + $refreshToken = new RefreshTokenEntity( + self::REFRESH_TOKEN_ID, + new DateTimeImmutable('tomorrow', new DateTimeZone('UTC')), + $this->accessTokenMock, + self::AUTH_CODE_ID, + ); + + $this->repository->persistNewRefreshToken($refreshToken); + + $this->refreshTokenEntityFactoryMock->expects($this->once()) + ->method('fromState') + ->with($this->callback(function (array $state): bool { + return $state['id'] === self::REFRESH_TOKEN_ID; + }))->willReturn($this->refreshTokenEntityMock); + + $this->accessTokenRepositoryMock->method('findById')->willReturn($this->accessTokenMock); + + $this->refreshTokenEntityMock->expects($this->once())->method('revoke'); + + $this->repository->revokeByAuthCodeId(self::AUTH_CODE_ID); + } } diff --git a/tests/unit/src/Repositories/ScopeRepositoryTest.php b/tests/unit/src/Repositories/ScopeRepositoryTest.php index 4615fdff..308c32df 100644 --- a/tests/unit/src/Repositories/ScopeRepositoryTest.php +++ b/tests/unit/src/Repositories/ScopeRepositoryTest.php @@ -15,6 +15,7 @@ */ namespace SimpleSAML\Test\Module\oidc\unit\Repositories; +use League\OAuth2\Server\Entities\ClientEntityInterface; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; use SimpleSAML\Module\oidc\Entities\ScopeEntity; @@ -90,4 +91,17 @@ public function testFinalizeScopes(): void ]; $this->assertEquals($expectedScopes, $finalizedScopes); } + + public function testFinalizeScopesReturnsEmptyIfNotClientEntity(): void + { + $scopeRepository = new ScopeRepository(new ModuleConfig(), new ScopeEntityFactory()); + $scopes = [ + new ScopeEntity('openid'), + new ScopeEntity('basic'), + ]; + + $clientMock = $this->createMock(ClientEntityInterface::class); + + $this->assertEmpty($scopeRepository->finalizeScopes($scopes, 'any', $clientMock)); + } } diff --git a/tests/unit/src/Repositories/UserRepositoryTest.php b/tests/unit/src/Repositories/UserRepositoryTest.php index ec2189b0..9daed4eb 100644 --- a/tests/unit/src/Repositories/UserRepositoryTest.php +++ b/tests/unit/src/Repositories/UserRepositoryTest.php @@ -22,6 +22,7 @@ use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; use SimpleSAML\Database; +use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\Entities\UserEntity; use SimpleSAML\Module\oidc\Factories\Entities\UserEntityFactory; use SimpleSAML\Module\oidc\Helpers; @@ -307,4 +308,17 @@ public function testWillDeleteFromDatabaseAndCache(): void protocolCache: $this->protocolCacheMock, )->delete($this->userEntityMock); } + + public function testGetUserEntityByUserCredentialsThrows(): void + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Not supported'); + + $this->mock()->getUserEntityByUserCredentials( + 'username', + 'password', + 'grantType', + $this->createMock(ClientEntityInterface::class), + ); + } } From 4e6bdaa6ac941354a4c691dbe94b595e97be2aff Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Wed, 5 Feb 2025 10:33:50 +0100 Subject: [PATCH 49/70] Add Trust Mark validation capabilities (#278) * Add Trust Mark validation capabilities * Add federation participation limiting capabilities based on Trust Marks --- UPGRADE.md | 7 +- composer.json | 4 +- routing/routes/routes.php | 3 + src/Codebooks/RoutesEnum.php | 1 + src/Controllers/Admin/TestController.php | 81 +++++++- src/Controllers/Federation/Test.php | 35 +++- src/Factories/TemplateFactory.php | 7 + .../RequestRules/Rules/ClientIdRule.php | 16 +- .../Validators/BearerTokenValidator.php | 2 +- src/Services/Container.php | 1 + .../FederationParticipationValidator.php | 184 +++++++++++++++++- src/Utils/Routes.php | 5 + templates/tests/trust-mark-validation.twig | 68 +++++++ .../Services/AuthenticationServiceTest.php | 4 +- .../FederationParticipationValidatorTest.php | 162 +++++++++++++++ 15 files changed, 543 insertions(+), 37 deletions(-) create mode 100644 templates/tests/trust-mark-validation.twig create mode 100644 tests/unit/src/Utils/FederationParticipationValidatorTest.php diff --git a/UPGRADE.md b/UPGRADE.md index 5c166c34..467a23bc 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,9 +1,5 @@ # TODO -- upgrade to v9 of oauth2-server https://github.com/thephpleague/oauth2-server/releases/tag/9.0.0 -- implement key rollover -- implement token introspection -- implement dynamic client registration -- move request rules to templates (generics) for proper static type handling + - remove dependency on laminas/laminas-httphandlerrunner - create a bridge towards SSP utility classes, so they can be easily mocked - move away from SSP database as store; move to DBAL @@ -51,6 +47,7 @@ and optionally a port (as in all previous module versions). - authority hints - federation caching adapter and its arguments - PKI keys - federation keys used for example to sign federation entity statements + - federation participation limiting based on Trust Marks for RPs - signer algorithm - entity statement duration - organization name diff --git a/composer.json b/composer.json index 69de0580..3c229366 100644 --- a/composer.json +++ b/composer.json @@ -80,8 +80,10 @@ }, "scripts": { "pre-commit": [ + "vendor/bin/phpcbf", + "vendor/bin/phpcs -p", "vendor/bin/psalm", - "vendor/bin/phpcs -p" + "vendor/bin/phpunit" ], "tests": [ "vendor/bin/phpunit --no-coverage" diff --git a/routing/routes/routes.php b/routing/routes/routes.php index 6d52f78a..d014c72d 100644 --- a/routing/routes/routes.php +++ b/routing/routes/routes.php @@ -63,6 +63,9 @@ $routes->add(RoutesEnum::AdminTestTrustChainResolution->name, RoutesEnum::AdminTestTrustChainResolution->value) ->controller([TestController::class, 'trustChainResolution']) ->methods([HttpMethodsEnum::GET->value, HttpMethodsEnum::POST->value]); + $routes->add(RoutesEnum::AdminTestTrustMarkValidation->name, RoutesEnum::AdminTestTrustMarkValidation->value) + ->controller([TestController::class, 'trustMarkValidation']) + ->methods([HttpMethodsEnum::GET->value, HttpMethodsEnum::POST->value]); /***************************************************************************************************************** * OpenID Connect diff --git a/src/Codebooks/RoutesEnum.php b/src/Codebooks/RoutesEnum.php index 918d320e..f81a98b8 100644 --- a/src/Codebooks/RoutesEnum.php +++ b/src/Codebooks/RoutesEnum.php @@ -26,6 +26,7 @@ enum RoutesEnum: string // Testing case AdminTestTrustChainResolution = 'admin/test/trust-chain-resolution'; + case AdminTestTrustMarkValidation = 'admin/test/trust-mark-validation'; /***************************************************************************************************************** diff --git a/src/Controllers/Admin/TestController.php b/src/Controllers/Admin/TestController.php index 7da4c8d5..e05a6dc1 100644 --- a/src/Controllers/Admin/TestController.php +++ b/src/Controllers/Admin/TestController.php @@ -19,6 +19,8 @@ class TestController { + protected readonly Federation $federationWithArrayLogger; + public function __construct( protected readonly ModuleConfig $moduleConfig, protected readonly TemplateFactory $templateFactory, @@ -28,6 +30,14 @@ public function __construct( protected readonly ArrayLogger $arrayLogger, ) { $this->authorization->requireAdmin(true); + + $this->arrayLogger->setWeight(ArrayLogger::WEIGHT_WARNING); + // Let's create new Federation instance so we can inject our debug logger and go without cache. + $this->federationWithArrayLogger = new Federation( + supportedAlgorithms: $this->federation->supportedAlgorithms(), + cache: null, + logger: $this->arrayLogger, + ); } /** @@ -37,14 +47,6 @@ public function __construct( */ public function trustChainResolution(Request $request): Response { - $this->arrayLogger->setWeight(ArrayLogger::WEIGHT_WARNING); - // Let's create new Federation instance so we can inject our debug logger and go without cache. - $federation = new Federation( - supportedAlgorithms: $this->federation->supportedAlgorithms(), - cache: null, - logger: $this->arrayLogger, - ); - $leafEntityId = $this->moduleConfig->getIssuer(); $trustChainBag = null; $resolvedMetadata = []; @@ -69,7 +71,8 @@ public function trustChainResolution(Request $request): Response $trustAnchorIds = $this->helpers->str()->convertTextToArray($rawTrustAnchorIds); try { - $trustChainBag = $federation->trustChainResolver()->for($leafEntityId, $trustAnchorIds); + $trustChainBag = $this->federationWithArrayLogger->trustChainResolver() + ->for($leafEntityId, $trustAnchorIds); foreach ($trustChainBag->getAll() as $index => $trustChain) { $metadataEntries = []; @@ -94,7 +97,7 @@ public function trustChainResolution(Request $request): Response $trustAnchorIds = implode("\n", $trustAnchorIds); $logMessages = $this->arrayLogger->getEntries(); -//dd($this->arrayLogger->getEntries()); + return $this->templateFactory->build( 'oidc:tests/trust-chain-resolution.twig', compact( @@ -108,4 +111,62 @@ public function trustChainResolution(Request $request): Response RoutesEnum::AdminTestTrustChainResolution->value, ); } + + public function trustMarkValidation(Request $request): Response + { + $trustMarkId = null; + $leafEntityId = null; + $trustAnchorId = null; + $isFormSubmitted = false; + + if ($request->isMethod(Request::METHOD_POST)) { + $isFormSubmitted = true; + + !empty($trustMarkId = $request->request->getString('trustMarkId')) || + throw new OidcException('Empty Trust Mark ID.'); + !empty($leafEntityId = $request->request->getString('leafEntityId')) || + throw new OidcException('Empty leaf entity ID.'); + !empty($trustAnchorId = $request->request->getString('trustAnchorId')) || + throw new OidcException('Empty Trust Anchor ID.'); + + try { + // We should not try to validate Trust Marks until we have resolved trust chain between leaf and TA. + $trustChain = $this->federation->trustChainResolver()->for( + $leafEntityId, + [$trustAnchorId], + )->getShortest(); + + try { + $this->federationWithArrayLogger->trustMarkValidator()->doForTrustMarkId( + $trustMarkId, + $trustChain->getResolvedLeaf(), + $trustChain->getResolvedTrustAnchor(), + ); + } catch (\Throwable $exception) { + $this->arrayLogger->error('Trust Mark validation error: ' . $exception->getMessage()); + } + } catch (TrustChainException $exception) { + $this->arrayLogger->error(sprintf( + 'Could not resolve Trust Chain for leaf entity %s under Trust Anchor %s. Error was %s', + $leafEntityId, + $trustAnchorId, + $exception->getMessage(), + )); + } + } + + $logMessages = $this->arrayLogger->getEntries(); + + return $this->templateFactory->build( + 'oidc:tests/trust-mark-validation.twig', + compact( + 'trustMarkId', + 'leafEntityId', + 'trustAnchorId', + 'logMessages', + 'isFormSubmitted', + ), + RoutesEnum::AdminTestTrustMarkValidation->value, + ); + } } diff --git a/src/Controllers/Federation/Test.php b/src/Controllers/Federation/Test.php index 97eaaee4..8c10fd36 100644 --- a/src/Controllers/Federation/Test.php +++ b/src/Controllers/Federation/Test.php @@ -12,6 +12,7 @@ use SimpleSAML\Module\oidc\Factories\Entities\ClientEntityFactory; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\FederationCache; +use SimpleSAML\Module\oidc\Utils\FederationParticipationValidator; use SimpleSAML\Module\oidc\Utils\ProtocolCache; use SimpleSAML\OpenID\Codebooks\EntityTypesEnum; use SimpleSAML\OpenID\Core; @@ -37,6 +38,7 @@ public function __construct( protected Database $database, protected ClientEntityFactory $clientEntityFactory, protected CoreFactory $coreFactory, + protected FederationParticipationValidator $federationParticipationValidator, protected \DateInterval $maxCacheDuration = new \DateInterval('PT30S'), ) { } @@ -70,21 +72,40 @@ public function __invoke(): Response $trustChain = $this->federation ->trustChainResolver() ->for( -// 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ALeaf/', + 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ALeaf/', // 'https://trust-anchor.testbed.oidcfed.incubator.geant.org/oidc/rp/', // 'https://relying-party-php.testbed.oidcfed.incubator.geant.org/', - 'https://gorp.testbed.oidcfed.incubator.geant.org', +// 'https://gorp.testbed.oidcfed.incubator.geant.org', // 'https://maiv1.incubator.geant.org', [ - 'https://trust-anchor.testbed.oidcfed.incubator.geant.org/', +// 'https://trust-anchor.testbed.oidcfed.incubator.geant.org/', 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ABTrustAnchor/', - 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/CTrustAnchor/', +// 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/CTrustAnchor/', ], - )->getAll(); -dd($trustChain); + ) + //->getAll(); + ->getShortestByTrustAnchorPriority( + 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ABTrustAnchor/', + ); + $leaf = $trustChain->getResolvedLeaf(); + $trustAnchor = $trustChain->getResolvedTrustAnchor(); + + $this->federationParticipationValidator->validateForAllOfLimit( + ['https://08-dap.localhost.markoivancic.from.hr/openid/entities/ATrustMarkIssuer/trust-mark/member'], + $leaf, + $trustAnchor, + ); + dd($leaf->getPayload()); - $leafFederationJwks = $leaf->getJwks(); + + $this->federation->trustMarkValidator()->fromCacheOrDoForTrustMarkId( + 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ATrustMarkIssuer/trust-mark/member', + $leaf, + $trustAnchor, + ); + +// $leafFederationJwks = $leaf->getJwks(); // dd($leafFederationJwks); // /** @psalm-suppress PossiblyNullArgument */ $resolvedMetadata = $trustChain->getResolvedMetadata(EntityTypesEnum::OpenIdRelyingParty); diff --git a/src/Factories/TemplateFactory.php b/src/Factories/TemplateFactory.php index 0350af95..48986fcb 100644 --- a/src/Factories/TemplateFactory.php +++ b/src/Factories/TemplateFactory.php @@ -134,6 +134,13 @@ protected function includeDefaultMenuItems(): void Translate::noop('Test Trust Chain Resolution'), ), ); + + $this->oidcMenu->addItem( + $this->oidcMenu->buildItem( + $this->moduleConfig->getModuleUrl(RoutesEnum::AdminTestTrustMarkValidation->value), + Translate::noop('Test Trust Mark Validation'), + ), + ); } public function setShowMenu(bool $showMenu): TemplateFactory diff --git a/src/Server/RequestRules/Rules/ClientIdRule.php b/src/Server/RequestRules/Rules/ClientIdRule.php index 199e9cde..9905cf82 100644 --- a/src/Server/RequestRules/Rules/ClientIdRule.php +++ b/src/Server/RequestRules/Rules/ClientIdRule.php @@ -48,6 +48,18 @@ public function __construct( /** * @inheritDoc + * @throws \JsonException + * @throws \League\OAuth2\Server\Exception\OAuthServerException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \SimpleSAML\Error\ConfigurationError + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \SimpleSAML\OpenID\Exceptions\EntityStatementException + * @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException + * @throws \SimpleSAML\OpenID\Exceptions\JwksException + * @throws \SimpleSAML\OpenID\Exceptions\JwsException + * @throws \SimpleSAML\OpenID\Exceptions\RequestObjectException + * @throws \SimpleSAML\OpenID\Exceptions\TrustChainException + * @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException */ public function checkRule( ServerRequestInterface $request, @@ -196,7 +208,7 @@ public function checkRule( $this->helpers->dateTime()->getFromTimestamp($trustChain->getResolvedExpirationTime()), $existingClient, $clientEntityId, - $clientFederationEntity->getJwks(), + $clientFederationEntity->getJwks()->getValue(), $request, ); @@ -209,7 +221,7 @@ public function checkRule( // Check if federation participation is limited by Trust Marks. if ( $this->moduleConfig->isFederationParticipationLimitedByTrustMarksFor( - $trustChain->getResolvedTrustAnchor()->getIssuer(), + $trustAnchorEntityConfiguration->getIssuer(), ) ) { $this->federationParticipationValidator->byTrustMarksFor($trustChain); diff --git a/src/Server/Validators/BearerTokenValidator.php b/src/Server/Validators/BearerTokenValidator.php index 94c7b183..0a371aa4 100644 --- a/src/Server/Validators/BearerTokenValidator.php +++ b/src/Server/Validators/BearerTokenValidator.php @@ -76,7 +76,7 @@ protected function initJwtConfiguration(): void InMemory::plainText('empty', 'empty'), ); - /** @psalm-suppress ArgumentTypeCoercion */ + /** @psalm-suppress DeprecatedMethod, ArgumentTypeCoercion */ $this->jwtConfiguration->setValidationConstraints( new StrictValidAt(new SystemClock(new DateTimeZone(date_default_timezone_get()))), new SignedWith( diff --git a/src/Services/Container.php b/src/Services/Container.php index 5a4a46cb..8a6c5dae 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -349,6 +349,7 @@ public function __construct() $this->services[JwksResolver::class] = $jwksResolver; $federationParticipationValidator = new FederationParticipationValidator( $moduleConfig, + $federation, $loggerService, ); $this->services[FederationParticipationValidator::class] = $federationParticipationValidator; diff --git a/src/Utils/FederationParticipationValidator.php b/src/Utils/FederationParticipationValidator.php index 06bd37a5..9fd55934 100644 --- a/src/Utils/FederationParticipationValidator.php +++ b/src/Utils/FederationParticipationValidator.php @@ -4,35 +4,203 @@ namespace SimpleSAML\Module\oidc\Utils; +use SimpleSAML\Module\oidc\Codebooks\LimitsEnum; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Services\LoggerService; +use SimpleSAML\OpenID\Exceptions\TrustMarkException; +use SimpleSAML\OpenID\Federation; +use SimpleSAML\OpenID\Federation\EntityStatement; use SimpleSAML\OpenID\Federation\TrustChain; class FederationParticipationValidator { public function __construct( protected readonly ModuleConfig $moduleConfig, + protected readonly Federation $federation, protected readonly LoggerService $loggerService, ) { } + /** + * @throws \SimpleSAML\Error\ConfigurationError + * @throws \SimpleSAML\OpenID\Exceptions\EntityStatementException + * @throws \SimpleSAML\OpenID\Exceptions\TrustChainException + * @throws \SimpleSAML\OpenID\Exceptions\JwsException + * @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException + */ public function byTrustMarksFor(TrustChain $trustChain): void { - $trustAnchor = $trustChain->getResolvedTrustAnchor(); + $leafEntityConfiguration = $trustChain->getResolvedLeaf(); + $trustAnchorEntityConfiguration = $trustChain->getResolvedTrustAnchor(); - $trustMarkLimitsRules = $this->moduleConfig - ->getTrustMarksNeededForFederationParticipationFor($trustAnchor->getIssuer()); + $this->loggerService->debug( + sprintf( + 'Validating federation participation by Trust Marks for leaf %s and Trust Anchor %s.', + $leafEntityConfiguration->getIssuer(), + $trustAnchorEntityConfiguration->getIssuer(), + ), + ); + + $trustMarkLimitsRules = $this->moduleConfig->getTrustMarksNeededForFederationParticipationFor( + $trustAnchorEntityConfiguration->getIssuer(), + ); if (empty($trustMarkLimitsRules)) { - $this->loggerService->debug('No Trust Mark limits emposed for ' . $trustAnchor->getIssuer()); + $this->loggerService->debug( + 'No Trust Mark limits imposed for ' . $trustAnchorEntityConfiguration->getIssuer(), + ); + return; + } + + $this->loggerService->debug( + 'Trust Mark limits for ' . $trustAnchorEntityConfiguration->getIssuer(), + $trustMarkLimitsRules, + ); + + + /** + * @var string $limitId + * @var non-empty-string[] $limitedTrustMarkIds + */ + foreach ($trustMarkLimitsRules as $limitId => $limitedTrustMarkIds) { + $limit = LimitsEnum::from($limitId); + + if ($limit === LimitsEnum::OneOf) { + $this->validateForOneOfLimit( + $limitedTrustMarkIds, + $leafEntityConfiguration, + $trustAnchorEntityConfiguration, + ); + } else { + $this->validateForAllOfLimit( + $limitedTrustMarkIds, + $leafEntityConfiguration, + $trustAnchorEntityConfiguration, + ); + } + } + } + + /** + * @param non-empty-string[] $limitedTrustMarkIds + * @throws \SimpleSAML\OpenID\Exceptions\EntityStatementException + * @throws \SimpleSAML\OpenID\Exceptions\JwsException + * @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException + */ + public function validateForOneOfLimit( + array $limitedTrustMarkIds, + EntityStatement $leafEntityConfiguration, + EntityStatement $trustAnchorEntityConfiguration, + ): void { + if (empty($limitedTrustMarkIds)) { + $this->loggerService->debug('No Trust Mark limits given for OneOf limit rule, nothing to do.'); + return; + } + + $this->loggerService->debug( + sprintf( + 'Validating that entity %s has at least one valid Trust Mark for Trust Anchor %s.', + $leafEntityConfiguration->getIssuer(), + $trustAnchorEntityConfiguration->getIssuer(), + ), + ['limitedTrustMarkIds' => $limitedTrustMarkIds], + ); + + foreach ($limitedTrustMarkIds as $limitedTrustMarkId) { + try { + $this->federation->trustMarkValidator()->fromCacheOrDoForTrustMarkId( + $limitedTrustMarkId, + $leafEntityConfiguration, + $trustAnchorEntityConfiguration, + ); + + $this->loggerService->debug( + sprintf( + 'Trust Mark ID %s validated using OneOf limit rule for entity %s under Trust Anchor %s.', + $limitedTrustMarkId, + $leafEntityConfiguration->getIssuer(), + $trustAnchorEntityConfiguration->getIssuer(), + ), + ); + return; + } catch (\Throwable $exception) { + $this->loggerService->debug( + sprintf( + 'Trust Mark ID %s validation failed with error: %s. Trying next if available.', + $limitedTrustMarkId, + $exception->getMessage(), + ), + ); + continue; + } + } + + $error = sprintf( + 'Leaf entity %s does not have any valid Trust Marks from the given list (%s). OneOf limit rule failed.', + $leafEntityConfiguration->getIssuer(), + implode(',', $limitedTrustMarkIds), + ); + + $this->loggerService->error($error); + throw new TrustMarkException($error); + } + + /** + * @param non-empty-string[] $limitedTrustMarkIds + * @throws \SimpleSAML\OpenID\Exceptions\EntityStatementException + * @throws \SimpleSAML\OpenID\Exceptions\JwsException + * @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException + */ + public function validateForAllOfLimit( + array $limitedTrustMarkIds, + EntityStatement $leafEntityConfiguration, + EntityStatement $trustAnchorEntityConfiguration, + ): void { + if (empty($limitedTrustMarkIds)) { + $this->loggerService->debug('No Trust Mark limits given for AllOf limit rule, nothing to do.'); return; } - $this->loggerService->debug('Trust Mark limits for ' . $trustAnchor->getIssuer(), $trustMarkLimitsRules); + $this->loggerService->debug( + sprintf( + 'Validating that entity %s has all valid Trust Marks for Trust Anchor %s.', + $leafEntityConfiguration->getIssuer(), + $trustAnchorEntityConfiguration->getIssuer(), + ), + ['limitedTrustMarkIds' => $limitedTrustMarkIds], + ); - //$leaf = $trustChain->getResolvedLeaf(); - //$leafTrustMarks = $leaf->getTrustMarks(); + foreach ($limitedTrustMarkIds as $limitedTrustMarkId) { + try { + $this->federation->trustMarkValidator()->fromCacheOrDoForTrustMarkId( + $limitedTrustMarkId, + $leafEntityConfiguration, + $trustAnchorEntityConfiguration, + ); + + $this->loggerService->debug( + sprintf( + 'Trust Mark ID %s validated. Trying next if available.', + $limitedTrustMarkId, + ), + ); + } catch (\Throwable $exception) { + $error = sprintf( + 'Trust Mark ID %s validation failed with error: %s. AllOf limit rule failed.', + $limitedTrustMarkId, + $exception->getMessage(), + ); + $this->loggerService->error($error); + throw new TrustMarkException($error); + } + } - // TODO mivanci continue + $this->loggerService->debug( + sprintf( + 'Entity %s has all valid Trust Marks for Trust Anchor %s.', + $leafEntityConfiguration->getIssuer(), + $trustAnchorEntityConfiguration->getIssuer(), + ), + ); } } diff --git a/src/Utils/Routes.php b/src/Utils/Routes.php index 7b87f514..a9fea448 100644 --- a/src/Utils/Routes.php +++ b/src/Utils/Routes.php @@ -141,6 +141,11 @@ public function urlAdminTestTrustChainResolution(array $parameters = []): string return $this->getModuleUrl(RoutesEnum::AdminTestTrustChainResolution->value, $parameters); } + public function urlAdminTestTrustMarkValidation(array $parameters = []): string + { + return $this->getModuleUrl(RoutesEnum::AdminTestTrustMarkValidation->value, $parameters); + } + /***************************************************************************************************************** * OpenID Connect URLs. ****************************************************************************************************************/ diff --git a/templates/tests/trust-mark-validation.twig b/templates/tests/trust-mark-validation.twig new file mode 100644 index 00000000..9a0cd219 --- /dev/null +++ b/templates/tests/trust-mark-validation.twig @@ -0,0 +1,68 @@ +{% set subPageTitle = 'Test Trust Mark Validation'|trans %} + +{% extends "@oidc/base.twig" %} + +{% block oidcContent %} + +

+ {{ 'You can use the form below to test Trust Mark validation for particular entity under given Trust Anchor.'|trans }} + {{ 'Log messages will show if any warnings or errors were raised during validation.'|trans }} + {{ 'Note that this will first resolve Trust Chain between given entity and Trust Anchor, and only then do the Trust Mark validation.'|trans }} +

+ +
+ +
+ + + + + + + + + + + + +
+ +
+
+ + {% if isFormSubmitted|default %} + +

{{ 'Log messages'|trans }}

+

+ {% if logMessages|default %} + + {{- logMessages|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}} + + {% else %} + {{ 'Trust Mark validation passed (there were no warnings or errors during validation).'|trans }} + {% endif %} +

+ + {% endif %} + +{% endblock oidcContent -%} diff --git a/tests/unit/src/Services/AuthenticationServiceTest.php b/tests/unit/src/Services/AuthenticationServiceTest.php index 29623db4..962ba1db 100644 --- a/tests/unit/src/Services/AuthenticationServiceTest.php +++ b/tests/unit/src/Services/AuthenticationServiceTest.php @@ -326,9 +326,7 @@ public function testGetAuthenticateUserItThrowsIfClaimsNotExist(): void unset($invalidState['Attributes'][self::USER_ID_ATTR]); $this->expectException(Exception::class); - $this->expectExceptionMessageMatches( - "/Attribute `useridattr` doesn\'t exists in claims. Available attributes are:/", - ); + $this->expectExceptionMessageMatches("/User identifier attribute /"); $this->mock()->getAuthenticateUser($invalidState); } diff --git a/tests/unit/src/Utils/FederationParticipationValidatorTest.php b/tests/unit/src/Utils/FederationParticipationValidatorTest.php new file mode 100644 index 00000000..dad0053b --- /dev/null +++ b/tests/unit/src/Utils/FederationParticipationValidatorTest.php @@ -0,0 +1,162 @@ +moduleConfigMock = $this->createMock(ModuleConfig::class); + $this->federationMock = $this->createMock(Federation::class); + $this->loggerMock = $this->createMock(LoggerService::class); + + $this->trustMarkValidatorMock = $this->createMock(TrustMarkValidator::class); + $this->federationMock->method('trustMarkValidator')->willReturn($this->trustMarkValidatorMock); + + $this->leafEntityConfiguration = $this->createMock(EntityStatement::class); + $this->leafEntityConfiguration->method('getIssuer')->willReturn('leafId'); + $this->trustAnchorEntityConfiguration = $this->createMock(EntityStatement::class); + $this->trustAnchorEntityConfiguration->method('getIssuer')->willReturn('trustAnchorId'); + + $this->trustChainMock = $this->createMock(TrustChain::class); + $this->trustChainMock->method('getResolvedLeaf') + ->willReturn($this->leafEntityConfiguration); + $this->trustChainMock->method('getResolvedTrustAnchor') + ->willReturn($this->trustAnchorEntityConfiguration); + } + + protected function sut( + ?ModuleConfig $moduleConfig = null, + ?Federation $federation = null, + ?LoggerService $logger = null, + ): FederationParticipationValidator { + $moduleConfig ??= $this->moduleConfigMock; + $federation ??= $this->federationMock; + $logger ??= $this->loggerMock; + + return new FederationParticipationValidator( + $moduleConfig, + $federation, + $logger, + ); + } + + public function testCanConstruct(): void + { + $this->assertInstanceOf(FederationParticipationValidator::class, $this->sut()); + } + + public function testByTrustMarksFor(): void + { + $this->moduleConfigMock->expects($this->once()) + ->method('getTrustMarksNeededForFederationParticipationFor') + ->with('trustAnchorId') + ->willReturn([ + LimitsEnum::OneOf->value => ['trustMarkId1'], + LimitsEnum::AllOf->value => ['trustMarkId2'], + ]); + + $this->trustMarkValidatorMock->expects($this->atLeastOnce()) + ->method('fromCacheOrDoForTrustMarkId') + ->with($this->callback( + fn(string $trustMarkId): bool => in_array($trustMarkId, ['trustMarkId1', 'trustMarkId2']), + )); + + $this->sut()->byTrustMarksFor($this->trustChainMock); + } + + public function testByTrustMarksForEmptyLimitsDoesNotRunValidations(): void + { + $this->moduleConfigMock->expects($this->once()) + ->method('getTrustMarksNeededForFederationParticipationFor') + ->with('trustAnchorId') + ->willReturn([]); + + $this->trustMarkValidatorMock->expects($this->never()) + ->method('fromCacheOrDoForTrustMarkId'); + + $this->sut()->byTrustMarksFor($this->trustChainMock); + } + + public function testValidateForOneOfLimitDoesNotRunValidationOnEmptyLimit(): void + { + $this->trustMarkValidatorMock->expects($this->never()) + ->method('fromCacheOrDoForTrustMarkId'); + + $this->sut()->validateForOneOfLimit( + [], + $this->leafEntityConfiguration, + $this->trustAnchorEntityConfiguration, + ); + } + + public function testValidateForOneOfLimitThrowsIfNoneAreValid(): void + { + $this->trustMarkValidatorMock->expects($this->atLeastOnce()) + ->method('fromCacheOrDoForTrustMarkId') + ->with('trustMarkId') + ->willThrowException(new \Exception('error')); + + $this->expectException(TrustMarkException::class); + $this->expectExceptionMessage('OneOf limit rule failed'); + + $this->sut()->validateForOneOfLimit( + ['trustMarkId'], + $this->leafEntityConfiguration, + $this->trustAnchorEntityConfiguration, + ); + } + + public function testValidateForAllOfLimitDoesNotRunValidationOnEmptyLimit(): void + { + $this->trustMarkValidatorMock->expects($this->never()) + ->method('fromCacheOrDoForTrustMarkId'); + + $this->sut()->validateForAllOfLimit( + [], + $this->leafEntityConfiguration, + $this->trustAnchorEntityConfiguration, + ); + } + + public function testValidateForAllOfLimitThrowsIfAnyIsInvalid(): void + { + $this->trustMarkValidatorMock->expects($this->atLeastOnce()) + ->method('fromCacheOrDoForTrustMarkId') + ->with('trustMarkId') + ->willThrowException(new \Exception('error')); + + $this->expectException(TrustMarkException::class); + $this->expectExceptionMessage('AllOf limit rule failed'); + + $this->sut()->validateForAllOfLimit( + ['trustMarkId'], + $this->leafEntityConfiguration, + $this->trustAnchorEntityConfiguration, + ); + } +} From 02526ffa146e337c9acfe98b3036182e9b8de8f8 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Wed, 5 Feb 2025 16:53:43 +0100 Subject: [PATCH 50/70] Move module_oidc.php to module_oidc.php.dist (#279) * Move module_oidc.php to module_oidc.php.dist * Adjust path to config in tests * Adjust path to config in integration tests --- README.md | 2 +- UPGRADE.md | 8 ++++++-- .../module_oidc.php => config/module_oidc.php.dist | 0 phpcs.xml | 2 +- psalm.xml | 8 +------- .../src/Repositories/AccessTokenRepositoryTest.php | 2 +- tests/unit/src/Repositories/ScopeRepositoryTest.php | 2 +- 7 files changed, 11 insertions(+), 13 deletions(-) rename config-templates/module_oidc.php => config/module_oidc.php.dist (100%) diff --git a/README.md b/README.md index 915a7f01..2e71b576 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Installation can be as easy as executing: Copy the module config template file to the SimpleSAMLphp config directory: - cp modules/oidc/config-templates/module_oidc.php config/ + cp modules/oidc/config/module_oidc.php.dist config/ The options are self-explanatory, so make sure to go through the file and edit them as appropriate. diff --git a/UPGRADE.md b/UPGRADE.md index 467a23bc..3a71139b 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -81,10 +81,14 @@ key `authproc.oidc` ## Low impact changes -In an effort to move to SimpleSAMLphp way of working with user interface (UI), the client management UI was updated +- In an effort to move to SimpleSAMLphp way of working with user interface (UI), the client management UI was updated to extend from the SimpleSAMLphp base template. In addition, we have also introduced some configuration overview pages where you can take a quick view of some of the configuration values for the module. OIDC related pages are now available -from the main SimpleSAMLphp menu in Administration area. +from the main SimpleSAMLphp menu in Administration area. + +- The OIDC config template file has been moved from `config-templates/module_oidc.php` to `config/module_oidc.php.dist`. +This is only relevant for new installations, since initially it is needed to copy the template file to default SSP +config dir. README has been updated to reflect that change. Below are also some internal changes that should not have impact for the OIDC OP implementors. However, if you are using this module as a library or extending from it, you will probably encounter breaking changes, since a lot of code diff --git a/config-templates/module_oidc.php b/config/module_oidc.php.dist similarity index 100% rename from config-templates/module_oidc.php rename to config/module_oidc.php.dist diff --git a/phpcs.xml b/phpcs.xml index 6d2a0fd6..b1904153 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -9,7 +9,7 @@ - config-templates + config hooks src tests diff --git a/psalm.xml b/psalm.xml index 7c7bf4a9..7ea003ea 100644 --- a/psalm.xml +++ b/psalm.xml @@ -10,7 +10,7 @@ xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" > - + @@ -29,12 +29,6 @@ - - - - - - diff --git a/tests/integration/src/Repositories/AccessTokenRepositoryTest.php b/tests/integration/src/Repositories/AccessTokenRepositoryTest.php index 4b3a19b9..2c096334 100644 --- a/tests/integration/src/Repositories/AccessTokenRepositoryTest.php +++ b/tests/integration/src/Repositories/AccessTokenRepositoryTest.php @@ -87,7 +87,7 @@ public static function setUpBeforeClass(): void self::$mysqlPort ??= "3306"; self::$postgresPort ??= "5432"; } - Configuration::setConfigDir(__DIR__ . '/../../../../config-templates'); + Configuration::setConfigDir(__DIR__ . '/../../../config'); self::$pgConfig = self::loadPGDatabase(); self::$mysqlConfig = self::loadMySqlDatabase(); self::$sqliteConfig = self::loadSqliteDatabase(); diff --git a/tests/unit/src/Repositories/ScopeRepositoryTest.php b/tests/unit/src/Repositories/ScopeRepositoryTest.php index 308c32df..3618df14 100644 --- a/tests/unit/src/Repositories/ScopeRepositoryTest.php +++ b/tests/unit/src/Repositories/ScopeRepositoryTest.php @@ -41,7 +41,7 @@ public static function setUpBeforeClass(): void ]; Configuration::loadFromArray($config, '', 'simplesaml'); - Configuration::setConfigDir(__DIR__ . '/../../../../config-templates'); + Configuration::setConfigDir(__DIR__ . '/../../../config'); (new DatabaseMigration())->migrate(); } From 9989c827bab5848bbf89073e2c449863c3031c30 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Thu, 6 Feb 2025 15:17:18 +0100 Subject: [PATCH 51/70] Remove globals from unit tests (#282) --- ...AuthenticatedGetClientFromRequestTrait.php | 58 ------------------- src/Helpers/Client.php | 11 ++-- src/Services/AuthenticationService.php | 9 +-- .../src/Entities/AccessTokenEntityTest.php | 2 - tests/unit/src/Forms/ClientFormTest.php | 7 --- tests/unit/src/Helpers/ClientTest.php | 7 +-- .../Validators/BearerTokenValidatorTest.php | 6 -- .../Services/AuthenticationServiceTest.php | 14 +---- 8 files changed, 14 insertions(+), 100 deletions(-) delete mode 100644 src/Controllers/Traits/AuthenticatedGetClientFromRequestTrait.php diff --git a/src/Controllers/Traits/AuthenticatedGetClientFromRequestTrait.php b/src/Controllers/Traits/AuthenticatedGetClientFromRequestTrait.php deleted file mode 100644 index 2c95bf00..00000000 --- a/src/Controllers/Traits/AuthenticatedGetClientFromRequestTrait.php +++ /dev/null @@ -1,58 +0,0 @@ -getQueryParams(); - $clientId = empty($params['client_id']) ? null : (string)$params['client_id']; - - if (!is_string($clientId)) { - throw new Error\BadRequest('Client id is missing.'); - } - $authedUser = null; - if (!$this->authContextService->isSspAdmin()) { - $authedUser = $this->authContextService->getAuthUserId(); - } - $client = $this->clientRepository->findById($clientId, $authedUser); - if (!$client) { - throw OidcServerException::invalidClient($request); - } - - return $client; - } -} diff --git a/src/Helpers/Client.php b/src/Helpers/Client.php index 8928154f..a2afddbc 100644 --- a/src/Helpers/Client.php +++ b/src/Helpers/Client.php @@ -5,9 +5,8 @@ namespace SimpleSAML\Module\oidc\Helpers; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Error\BadRequest; -use SimpleSAML\Error\NotFound; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; +use SimpleSAML\Module\oidc\Exceptions\OidcException; use SimpleSAML\Module\oidc\Repositories\ClientRepository; class Client @@ -18,9 +17,7 @@ public function __construct(protected Http $http) /** * @throws \JsonException - * @throws \SimpleSAML\Error\BadRequest - * @throws \SimpleSAML\Error\NotFound - * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \SimpleSAML\Module\oidc\Exceptions\OidcException */ public function getFromRequest( ServerRequestInterface $request, @@ -30,13 +27,13 @@ public function getFromRequest( $clientId = empty($params['client_id']) ? null : (string)$params['client_id']; if (!is_string($clientId)) { - throw new BadRequest('Client ID is missing.'); + throw new OidcException('Client ID is missing.'); } $client = $clientRepository->findById($clientId); if (!$client) { - throw new NotFound('Client not found.'); + throw new OidcException('Client not found.'); } return $client; diff --git a/src/Services/AuthenticationService.php b/src/Services/AuthenticationService.php index 06ffd8ef..7d8c7afa 100644 --- a/src/Services/AuthenticationService.php +++ b/src/Services/AuthenticationService.php @@ -28,6 +28,7 @@ use SimpleSAML\Module\oidc\Controllers\EndSessionController; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\Entities\UserEntity; +use SimpleSAML\Module\oidc\Exceptions\OidcException; use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory; use SimpleSAML\Module\oidc\Factories\Entities\UserEntityFactory; use SimpleSAML\Module\oidc\Factories\ProcessingChainFactory; @@ -78,12 +79,11 @@ public function __construct( * * @return array * @throws Error\AuthSource - * @throws Error\BadRequest - * @throws Error\NotFound * @throws Exception * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException * @throws Error\UnserializableException * @throws \JsonException + * @throws \SimpleSAML\Module\oidc\Exceptions\OidcException */ public function processRequest( ServerRequestInterface $request, @@ -117,13 +117,14 @@ public function processRequest( /** - * @param array|null $state + * @param array|null $state * * @return UserEntity * @throws Error\NotFound * @throws Exception * @throws \JsonException * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \SimpleSAML\Module\oidc\Exceptions\OidcException */ public function getAuthenticateUser( ?array $state, @@ -164,7 +165,7 @@ public function getAuthenticateUser( $client = $this->clientRepository->findById((string)$state['Oidc']['RelyingPartyMetadata']['id']); if (!$client) { - throw new Error\NotFound('Client not found.'); + throw new OidcException('Client not found.'); } $this->addRelyingPartyAssociation($client, $user); diff --git a/tests/unit/src/Entities/AccessTokenEntityTest.php b/tests/unit/src/Entities/AccessTokenEntityTest.php index 1bb4bebf..b7d6d1c1 100644 --- a/tests/unit/src/Entities/AccessTokenEntityTest.php +++ b/tests/unit/src/Entities/AccessTokenEntityTest.php @@ -18,8 +18,6 @@ /** * @covers \SimpleSAML\Module\oidc\Entities\AccessTokenEntity - * - * @backupGlobals enabled */ class AccessTokenEntityTest extends TestCase { diff --git a/tests/unit/src/Forms/ClientFormTest.php b/tests/unit/src/Forms/ClientFormTest.php index 4f24c893..59700f2e 100644 --- a/tests/unit/src/Forms/ClientFormTest.php +++ b/tests/unit/src/Forms/ClientFormTest.php @@ -40,13 +40,6 @@ public function setUp(): void $this->serverRequestMock = $this->createMock(ServerRequest::class); } - public static function setUpBeforeClass(): void - { - // To make lib/SimpleSAML/Utils/HTTP::getSelfURL() work... - global $_SERVER; - $_SERVER['REQUEST_URI'] = '/'; - } - public static function validateOriginProvider(): array { return [ diff --git a/tests/unit/src/Helpers/ClientTest.php b/tests/unit/src/Helpers/ClientTest.php index 7916e678..b79b052a 100644 --- a/tests/unit/src/Helpers/ClientTest.php +++ b/tests/unit/src/Helpers/ClientTest.php @@ -8,9 +8,8 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; -use SimpleSAML\Error\BadRequest; -use SimpleSAML\Error\NotFound; use SimpleSAML\Module\oidc\Entities\ClientEntity; +use SimpleSAML\Module\oidc\Exceptions\OidcException; use SimpleSAML\Module\oidc\Helpers\Client; use SimpleSAML\Module\oidc\Helpers\Http; use SimpleSAML\Module\oidc\Repositories\ClientRepository; @@ -56,7 +55,7 @@ public function testCanGetFromRequest(): void public function testGetFromRequestThrowsIfNoClientId(): void { - $this->expectException(BadRequest::class); + $this->expectException(OidcException::class); $this->expectExceptionMessage('Client ID'); $this->sut()->getFromRequest($this->requestMock, $this->clientRepositoryMock); @@ -64,7 +63,7 @@ public function testGetFromRequestThrowsIfNoClientId(): void public function testGetFromRequestThrowsIfClientNotFound(): void { - $this->expectException(NotFound::class); + $this->expectException(OidcException::class); $this->expectExceptionMessage('Client not found'); $this->httpMock->expects($this->once())->method('getAllRequestParams') diff --git a/tests/unit/src/Server/Validators/BearerTokenValidatorTest.php b/tests/unit/src/Server/Validators/BearerTokenValidatorTest.php index a47b3174..daa8bf19 100644 --- a/tests/unit/src/Server/Validators/BearerTokenValidatorTest.php +++ b/tests/unit/src/Server/Validators/BearerTokenValidatorTest.php @@ -24,8 +24,6 @@ /** * @covers \SimpleSAML\Module\oidc\Server\Validators\BearerTokenValidator - * - * @backupGlobals enabled */ class BearerTokenValidatorTest extends TestCase { @@ -60,10 +58,6 @@ public function setUp(): void */ public static function setUpBeforeClass(): void { - // To make lib/SimpleSAML/Utils/HTTP::getSelfURL() work... - global $_SERVER; - $_SERVER['REQUEST_URI'] = ''; - $tempDir = sys_get_temp_dir(); // Plant certdir config for JsonWebTokenBuilderService (since we don't inject it) diff --git a/tests/unit/src/Services/AuthenticationServiceTest.php b/tests/unit/src/Services/AuthenticationServiceTest.php index 962ba1db..fd9b2024 100644 --- a/tests/unit/src/Services/AuthenticationServiceTest.php +++ b/tests/unit/src/Services/AuthenticationServiceTest.php @@ -17,9 +17,9 @@ use SimpleSAML\Auth\State; use SimpleSAML\Error\Exception; use SimpleSAML\Error\NoState; -use SimpleSAML\Error\NotFound; use SimpleSAML\Module\oidc\Entities\ClientEntity; use SimpleSAML\Module\oidc\Entities\UserEntity; +use SimpleSAML\Module\oidc\Exceptions\OidcException; use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory; use SimpleSAML\Module\oidc\Factories\Entities\UserEntityFactory; use SimpleSAML\Module\oidc\Factories\ProcessingChainFactory; @@ -84,16 +84,6 @@ class AuthenticationServiceTest extends TestCase protected MockObject $requestParamsResolverMock; protected MockObject $userEntityFactoryMock; - /** - * @return void - */ - public static function setUpBeforeClass(): void - { - // To make lib/SimpleSAML/Utils/HTTP::getSelfURL() work... - global $_SERVER; - $_SERVER['REQUEST_URI'] = ''; - } - /** * @throws \PHPUnit\Framework\MockObject\Exception */ @@ -280,7 +270,7 @@ public static function getUserState(): array 'AuthorizationRequestParameters' => self::AUTHZ_REQUEST_PARAMS, ], ], - NotFound::class, + OidcException::class, '/Client not found./', ], ]; From 444968fc46f557c4783bd6d64e615af9384fd80c Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Thu, 6 Feb 2025 16:06:09 +0100 Subject: [PATCH 52/70] Reset client auth source if not valid (#280) --- src/Bridges/SspBridge.php | 7 + src/Bridges/SspBridge/Auth.php | 17 + src/Bridges/SspBridge/Auth/Source.php | 13 + src/Forms/ClientForm.php | 20 +- tests/config/authsources.php | 348 ++++++++++++++++++ .../src/Bridges/SspBridge/Auth/SourceTest.php | 23 ++ tests/unit/src/Bridges/SspBridge/AuthTest.php | 28 ++ tests/unit/src/Bridges/SspBridgeTest.php | 5 + tests/unit/src/Forms/ClientFormTest.php | 97 ++++- 9 files changed, 536 insertions(+), 22 deletions(-) create mode 100644 src/Bridges/SspBridge/Auth.php create mode 100644 src/Bridges/SspBridge/Auth/Source.php create mode 100644 tests/config/authsources.php create mode 100644 tests/unit/src/Bridges/SspBridge/Auth/SourceTest.php create mode 100644 tests/unit/src/Bridges/SspBridge/AuthTest.php diff --git a/src/Bridges/SspBridge.php b/src/Bridges/SspBridge.php index 1d0a6173..6b5b7ffc 100644 --- a/src/Bridges/SspBridge.php +++ b/src/Bridges/SspBridge.php @@ -4,6 +4,7 @@ namespace SimpleSAML\Module\oidc\Bridges; +use SimpleSAML\Module\oidc\Bridges\SspBridge\Auth; use SimpleSAML\Module\oidc\Bridges\SspBridge\Module; use SimpleSAML\Module\oidc\Bridges\SspBridge\Utils; @@ -13,6 +14,7 @@ */ class SspBridge { + protected static ?Auth $auth = null; protected static ?Utils $utils = null; protected static ?Module $module = null; @@ -25,4 +27,9 @@ public function module(): Module { return self::$module ??= new Module(); } + + public function auth(): Auth + { + return self::$auth ??= new Auth(); + } } diff --git a/src/Bridges/SspBridge/Auth.php b/src/Bridges/SspBridge/Auth.php new file mode 100644 index 00000000..d3be17d2 --- /dev/null +++ b/src/Bridges/SspBridge/Auth.php @@ -0,0 +1,17 @@ +buildForm(); @@ -315,6 +318,14 @@ public function setDefaults(object|array $data, bool $erase = false): static $data['jwks'] = is_array($data['jwks']) ? json_encode($data['jwks']) : null; + if ( + $data['auth_source'] !== null && + (!in_array($data['auth_source'], $this->sspBridge->auth()->source()->getSources())) + ) { + // Possible auth source name change without prior update in clients, resetting. + $data['auth_source'] = null; + } + parent::setDefaults($data, $erase); return $this; @@ -355,10 +366,9 @@ protected function buildForm(): void $this->addCheckbox('is_confidential', '{oidc:client:is_confidential}'); - // TODO mivanci Source::getSource() move to SSP Bridge. $this->addSelect('auth_source', '{oidc:client:auth_source}:') ->setHtmlAttribute('class', 'full-width') - ->setItems(Source::getSources(), false) + ->setItems($this->sspBridge->auth()->source()->getSources(), false) ->setPrompt(Translate::noop('-')); $scopes = $this->getScopes(); diff --git a/tests/config/authsources.php b/tests/config/authsources.php new file mode 100644 index 00000000..6d5b7481 --- /dev/null +++ b/tests/config/authsources.php @@ -0,0 +1,348 @@ + [ + // The default is to use core:AdminPassword, but it can be replaced with + // any authentication source. + + 'core:AdminPassword', + ], + + + // An authentication source which can authenticate against SAML 2.0 IdPs. + 'default-sp' => [ + 'saml:SP', + + // The entity ID of this SP. + 'entityID' => 'https://myapp.example.org/', + + // The entity ID of the IdP this SP should contact. + // Can be NULL/unset, in which case the user will be shown a list of available IdPs. + 'idp' => null, + + // The URL to the discovery service. + // Can be NULL/unset, in which case a builtin discovery service will be used. + 'discoURL' => null, + + /* + * If SP behind the SimpleSAMLphp in IdP/SP proxy mode requests + * AuthnContextClassRef, decide whether the AuthnContextClassRef will be + * processed by the IdP/SP proxy or if it will be passed to the original + * IdP in front of the IdP/SP proxy. + */ + 'proxymode.passAuthnContextClassRef' => false, + + /* + * The attributes parameter must contain an array of desired attributes by the SP. + * The attributes can be expressed as an array of names or as an associative array + * in the form of 'friendlyName' => 'name'. This feature requires 'name' to be set. + * The metadata will then be created as follows: + * + */ + /* + 'name' => [ + 'en' => 'A service', + 'no' => 'En tjeneste', + ], + + 'attributes' => [ + 'attrname' => 'urn:oid:x.x.x.x', + ], + 'attributes.required' => [ + 'urn:oid:x.x.x.x', + ], + */ + ], + + + /* + 'example-sql' => [ + 'sqlauth:SQL', + 'dsn' => 'pgsql:host=sql.example.org;port=5432;dbname=simplesaml', + 'username' => 'simplesaml', + 'password' => 'secretpassword', + 'query' => 'SELECT uid, givenName, email, eduPersonPrincipalName FROM users WHERE uid = :username ' . + 'AND password = SHA2(CONCAT((SELECT salt FROM users WHERE uid = :username), :password), 256);', + ], + */ + + /* + 'example-static' => [ + 'exampleauth:StaticSource', + 'uid' => ['testuser'], + 'eduPersonAffiliation' => ['member', 'employee'], + 'cn' => ['Test User'], + ], + */ + + /* + 'example-userpass' => [ + 'exampleauth:UserPass', + + // Give the user an option to save their username for future login attempts + // And when enabled, what should the default be, to save the username or not + //'remember.username.enabled' => false, + //'remember.username.checked' => false, + + 'users' => [ + 'student:studentpass' => [ + 'uid' => ['test'], + 'eduPersonAffiliation' => ['member', 'student'], + ], + 'employee:employeepass' => [ + 'uid' => ['employee'], + 'eduPersonAffiliation' => ['member', 'employee'], + ], + ], + ], + */ + + /* + 'crypto-hash' => [ + 'authcrypt:Hash', + // hashed version of 'verysecret', made with bin/pwgen.php + 'professor:{SSHA256}P6FDTEEIY2EnER9a6P2GwHhI5JDrwBgjQ913oVQjBngmCtrNBUMowA==' => [ + 'uid' => ['prof_a'], + 'eduPersonAffiliation' => ['member', 'employee', 'board'], + ], + ], + */ + + /* + 'htpasswd' => [ + 'authcrypt:Htpasswd', + 'htpasswd_file' => '/var/www/foo.edu/legacy_app/.htpasswd', + 'static_attributes' => [ + 'eduPersonAffiliation' => ['member', 'employee'], + 'Organization' => ['University of Foo'], + ], + ], + */ + + /* + // This authentication source serves as an example of integration with an + // external authentication engine. Take a look at the comment in the beginning + // of modules/exampleauth/lib/Auth/Source/External.php for a description of + // how to adjust it to your own site. + 'example-external' => [ + 'exampleauth:External', + ], + */ + + /* + 'yubikey' => [ + 'authYubiKey:YubiKey', + 'id' => '000', + // 'key' => '012345678', + ], + */ + + /* + 'facebook' => [ + 'authfacebook:Facebook', + // Register your Facebook application on http://www.facebook.com/developers + // App ID or API key (requests with App ID should be faster; https://github.com/facebook/php-sdk/issues/214) + 'api_key' => 'xxxxxxxxxxxxxxxx', + // App Secret + 'secret' => 'xxxxxxxxxxxxxxxx', + // which additional data permissions to request from user + // see http://developers.facebook.com/docs/authentication/permissions/ for the full list + // 'req_perms' => 'email,user_birthday', + // Which additional user profile fields to request. + // When empty, only the app-specific user id and name will be returned + // See https://developers.facebook.com/docs/graph-api/reference/v2.6/user for the full list + // 'user_fields' => 'email,birthday,third_party_id,name,first_name,last_name', + ], + */ + + /* + // Twitter OAuth Authentication API. + // Register your application to get an API key here: + // http://twitter.com/oauth_clients + 'twitter' => [ + 'authtwitter:Twitter', + 'key' => 'xxxxxxxxxxxxxxxx', + 'secret' => 'xxxxxxxxxxxxxxxx', + // Forces the user to enter their credentials to ensure the correct users account is authorized. + // Details: https://dev.twitter.com/docs/api/1/get/oauth/authenticate + 'force_login' => false, + ], + */ + + /* + // Microsoft Account (Windows Live ID) Authentication API. + // Register your application to get an API key here: + // https://apps.dev.microsoft.com/ + 'windowslive' => [ + 'authwindowslive:LiveID', + 'key' => 'xxxxxxxxxxxxxxxx', + 'secret' => 'xxxxxxxxxxxxxxxx', + ], + */ + + /* + // Example of a LDAP authentication source. + 'example-ldap' => [ + 'ldap:Ldap', + + // The connection string for the LDAP-server. + // You can add multiple by separating them with a space. + 'connection_string' => 'ldap.example.org', + + // Whether SSL/TLS should be used when contacting the LDAP server. + // Possible values are 'ssl', 'tls' or 'none' + 'encryption' => 'ssl', + + // The LDAP version to use when interfacing the LDAP-server. + // Defaults to 3 + 'version' => 3, + + // Set to TRUE to enable LDAP debug level. Passed to the LDAP connector class. + // + // Default: FALSE + // Required: No + 'ldap.debug' => false, + + // The LDAP-options to pass when setting up a connection + // See [Symfony documentation][1] + 'options' => [ + + // Set whether to follow referrals. + // AD Controllers may require 0x00 to function. + // Possible values are 0x00 (NEVER), 0x01 (SEARCHING), + // 0x02 (FINDING) or 0x03 (ALWAYS). + 'referrals' => 0x00, + + 'network_timeout' => 3, + ], + + // The connector to use. + // Defaults to '\SimpleSAML\Module\ldap\Connector\Ldap', but can be set + // to '\SimpleSAML\Module\ldap\Connector\ActiveDirectory' when + // authenticating against Microsoft Active Directory. This will + // provide you with more specific error messages. + 'connector' => '\SimpleSAML\Module\ldap\Connector\Ldap', + + // Which attributes should be retrieved from the LDAP server. + // This can be an array of attribute names, or NULL, in which case + // all attributes are fetched. + 'attributes' => null, + + // Which attributes should be base64 encoded after retrieval from + // the LDAP server. + 'attributes.binary' => [ + 'jpegPhoto', + 'objectGUID', + 'objectSid', + 'mS-DS-ConsistencyGuid' + ], + + // The pattern which should be used to create the user's DN given + // the username. %username% in this pattern will be replaced with + // the user's username. + // + // This option is not used if the search.enable option is set to TRUE. + 'dnpattern' => 'uid=%username%,ou=people,dc=example,dc=org', + + // As an alternative to specifying a pattern for the users DN, it is + // possible to search for the username in a set of attributes. This is + // enabled by this option. + 'search.enable' => false, + + // An array on DNs which will be used as a base for the search. In + // case of multiple strings, they will be searched in the order given. + 'search.base' => [ + 'ou=people,dc=example,dc=org', + ], + + // The scope of the search. Valid values are 'sub' and 'one' and + // 'base', first one being the default if no value is set. + 'search.scope' => 'sub', + + // The attribute(s) the username should match against. + // + // This is an array with one or more attribute names. Any of the + // attributes in the array may match the value the username. + 'search.attributes' => ['uid', 'mail'], + + // Additional filters that must match for the entire LDAP search to + // be true. + // + // This should be a single string conforming to [RFC 1960][2] + // and [RFC 2544][3]. The string is appended to the search attributes + 'search.filter' => '(&(objectClass=Person)(|(sn=Doe)(cn=John *)))', + + // The username & password where SimpleSAMLphp should bind to before + // searching. If this is left NULL, no bind will be performed before + // searching. + 'search.username' => null, + 'search.password' => null, + ], + */ + + /* + // Example of an LDAPMulti authentication source. + 'example-ldapmulti' => [ + 'ldap:LdapMulti', + + // The way the organization as part of the username should be handled. + // Three possible values: + // - 'none': No handling of the organization. Allows '@' to be part + // of the username. + // - 'allow': Will allow users to type 'username@organization'. + // - 'force': Force users to type 'username@organization'. The dropdown + // list will be hidden. + // + // The default is 'none'. + 'username_organization_method' => 'none', + + // Whether the organization should be included as part of the username + // when authenticating. If this is set to TRUE, the username will be on + // the form @. If this is FALSE, the + // username will be used as the user enters it. + // + // The default is FALSE. + 'include_organization_in_username' => false, + + // A list of available LDAP servers. + // + // The index is an identifier for the organization/group. When + // 'username_organization_method' is set to something other than 'none', + // the organization-part of the username is matched against the index. + // + // The value of each element is an array in the same format as an LDAP + // authentication source. + 'mapping' => [ + 'employees' => [ + // A short name/description for this group. Will be shown in a + // dropdown list when the user logs on. + // + // This option can be a string or an array with + // language => text mappings. + 'description' => 'Employees', + 'authsource' => 'example-ldap', + ], + + 'students' => [ + 'description' => 'Students', + 'authsource' => 'example-ldap-2', + ], + ], + ], + */ +]; diff --git a/tests/unit/src/Bridges/SspBridge/Auth/SourceTest.php b/tests/unit/src/Bridges/SspBridge/Auth/SourceTest.php new file mode 100644 index 00000000..63c0989d --- /dev/null +++ b/tests/unit/src/Bridges/SspBridge/Auth/SourceTest.php @@ -0,0 +1,23 @@ +assertTrue(in_array('admin', $this->sut()->getSources())); + } +} diff --git a/tests/unit/src/Bridges/SspBridge/AuthTest.php b/tests/unit/src/Bridges/SspBridge/AuthTest.php new file mode 100644 index 00000000..d573f64b --- /dev/null +++ b/tests/unit/src/Bridges/SspBridge/AuthTest.php @@ -0,0 +1,28 @@ +assertInstanceOf(Auth::class, $this->sut()); + } + + public function testCanBuildSourceInstance(): void + { + $this->assertInstanceOf(Auth\Source::class, $this->sut()->source()); + } +} diff --git a/tests/unit/src/Bridges/SspBridgeTest.php b/tests/unit/src/Bridges/SspBridgeTest.php index bd4da0aa..12ab86a5 100644 --- a/tests/unit/src/Bridges/SspBridgeTest.php +++ b/tests/unit/src/Bridges/SspBridgeTest.php @@ -30,4 +30,9 @@ public function testCanBuildModuleInstance(): void { $this->assertInstanceOf(SspBridge\Module::class, $this->sut()->module()); } + + public function testCanBuildAuthInstance(): void + { + $this->assertInstanceOf(SspBridge\Auth::class, $this->sut()->auth()); + } } diff --git a/tests/unit/src/Forms/ClientFormTest.php b/tests/unit/src/Forms/ClientFormTest.php index 59700f2e..fbe200dc 100644 --- a/tests/unit/src/Forms/ClientFormTest.php +++ b/tests/unit/src/Forms/ClientFormTest.php @@ -4,12 +4,14 @@ namespace SimpleSAML\Test\Module\oidc\unit\Forms; +use DateTimeImmutable; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use SimpleSAML\Configuration; +use SimpleSAML\Module\oidc\Bridges\SspBridge; +use SimpleSAML\Module\oidc\Codebooks\RegistrationTypeEnum; use SimpleSAML\Module\oidc\Forms\ClientForm; use SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection; use SimpleSAML\Module\oidc\ModuleConfig; @@ -19,14 +21,16 @@ */ class ClientFormTest extends TestCase { - /** @var \PHPUnit\Framework\MockObject\MockObject */ - protected MockObject $csrfProtection; + protected MockObject $csrfProtectionMock; - /** @var \PHPUnit\Framework\MockObject\MockObject */ - protected MockObject $moduleConfig; + protected MockObject $moduleConfigMock; - /** @var \PHPUnit\Framework\MockObject\MockObject */ protected MockObject $serverRequestMock; + protected MockObject $sspBridgeMock; + protected MockObject $sspBridgeAuthMock; + protected MockObject $sspBridgeAuthSourceMock; + + protected array $clientDataSample; /** * @throws \Exception @@ -34,10 +38,63 @@ class ClientFormTest extends TestCase public function setUp(): void { parent::setUp(); - Configuration::clearInternalState(); - $this->csrfProtection = $this->createMock(CsrfProtection::class); - $this->moduleConfig = $this->createMock(ModuleConfig::class); + $this->csrfProtectionMock = $this->createMock(CsrfProtection::class); + $this->moduleConfigMock = $this->createMock(ModuleConfig::class); $this->serverRequestMock = $this->createMock(ServerRequest::class); + $this->sspBridgeMock = $this->createMock(SspBridge::class); + + $this->sspBridgeAuthMock = $this->createMock(SspBridge\Auth::class); + $this->sspBridgeMock->method('auth')->willReturn($this->sspBridgeAuthMock); + + $this->sspBridgeAuthSourceMock = $this->createMock(SspBridge\Auth\Source::class); + $this->sspBridgeAuthMock->method('source')->willReturn($this->sspBridgeAuthSourceMock); + + $this->clientDataSample = [ + 'id' => 'clientId', + 'secret' => 'clientSecret', + 'name' => 'Test', + 'description' => 'Test', + 'auth_source' => 'default-sp', + 'redirect_uri' => [0 => 'https://example.com/redirect',], + 'scopes' => [0 => 'openid', 1 => 'offline_access', 2 => 'profile',], + 'is_enabled' => false, + 'is_confidential' => true, + 'owner' => null, + 'post_logout_redirect_uri' => [0 => 'https://example.com/',], + 'backchannel_logout_uri' => 'https://example.com/logout', + 'entity_identifier' => 'https://example.com/', + 'client_registration_types' => [0 => 'automatic',], + 'federation_jwks' => ['keys' => [0 => [],],], + 'jwks' => ['keys' => [0 => [],],], + 'jwks_uri' => 'https://example.com/jwks', + 'signed_jwks_uri' => 'https://example.com/signed-jwks', + 'registration_type' => RegistrationTypeEnum::Manual, + 'updated_at' => DateTimeImmutable::__set_state( + ['date' => '2025-02-05 15:05:27.000000', 'timezone_type' => 3, 'timezone' => 'UTC',], + ), + 'created_at' => DateTimeImmutable::__set_state( + ['date' => '2024-12-01 11:54:12.000000', 'timezone_type' => 3, 'timezone' => 'UTC',], + ), + 'expires_at' => null, + 'is_federated' => false, + 'allowed_origin' => [], + ]; + } + + protected function sut( + ?ModuleConfig $moduleConfig = null, + ?CsrfProtection $csrfProtection = null, + ?SspBridge $sspBridge = null, + ): ClientForm { + $moduleConfig ??= $this->moduleConfigMock; + $csrfProtection ??= $this->csrfProtectionMock; + $sspBridge ??= $this->sspBridgeMock; + + return new ClientForm( + $moduleConfig, + $csrfProtection, + $sspBridge, + ); } public static function validateOriginProvider(): array @@ -74,7 +131,6 @@ public static function validateOriginProvider(): array ]; } - /** * @param string $url * @param bool $isValid @@ -86,19 +142,26 @@ public static function validateOriginProvider(): array #[TestDox('Allowed Origin URL: $url is expected to be $isValid')] public function testValidateOrigin(string $url, bool $isValid): void { - $clientForm = $this->prepareMockedInstance(); + $clientForm = $this->sut(); $clientForm->setValues(['allowed_origin' => $url]); $clientForm->validateAllowedOrigin($clientForm); $this->assertEquals(!$isValid, $clientForm->hasErrors(), $url); } - /** - * @return \SimpleSAML\Module\oidc\Forms\ClientForm - * @throws \Exception - */ - protected function prepareMockedInstance(): ClientForm + public function testSetDefaultsLeavesValidAuthSourceValue(): void { - return new ClientForm($this->moduleConfig, $this->csrfProtection); + $this->sspBridgeAuthSourceMock->method('getSources')->willReturn(['default-sp']); + + $sut = $this->sut()->setDefaults($this->clientDataSample); + + $this->assertSame('default-sp', $sut->getValues()['auth_source']); + } + + public function testSetDefaultsUnsetsAuthSourceIfNotValid(): void + { + $sut = $this->sut()->setDefaults($this->clientDataSample); + + $this->assertNull($sut->getValues()['auth_source']); } } From d22585d31570e9dc46f50dd89f8bd903969fa70a Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Fri, 7 Feb 2025 19:37:35 +0100 Subject: [PATCH 53/70] Enable defining additional private / public key pair for key rollover scenario (#283) * Add key rollover options * Add coverage * Update docs --- README.md | 38 ++++--- UPGRADE.md | 5 +- config/module_oidc.php.dist | 33 +++++- src/ModuleConfig.php | 44 +++++++- src/Services/JsonWebKeySetService.php | 104 +++++++++++++----- .../src/Services/JsonWebKeySetServiceTest.php | 78 ++++++++++++- 6 files changed, 246 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 2e71b576..81f947b8 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,20 @@ Currently supported flows are: ![Main screen capture](docs/oidc.png) +### Note on OpenID Federation (OIDF) support + +OpenID Federation support is in "draft" phase, as is the +[specification](https://openid.net/specs/openid-federation-1_0) itself. This means that you can expect braking changes +in future releases related to OIDF capabilities. You can enable / disable OIDF support at any time in module +configuration. + +Currently, the following OIDF features are supported: +* automatic client registration using a Request Object (passing it by value) +* endpoint for issuing configuration entity statement (statement about itself) +* fetch endpoint for issuing statements about subordinates (registered clients) + +OIDF support is implemented using the underlying [SimpleSAMLphp OpenID library](https://github.com/simplesamlphp/openid). + ## Version compatibility Minor versions of SimpleSAMLphp noted below means that the module has been tested with that version of SimpleSAMLphp @@ -150,6 +164,16 @@ Once you deploy the module, in the SimpleSAMLphp administration area go to `OIDC Protocol / Federation Settings page to see the available discovery URLs. These URLs can then be used to set up a `.well-known` URLs (see below). +### Key rollover + +The module supports defining additional (new) private / public key pair to be published on relevant JWKS endpoint +or contained in relevant JWKS property. In this way, you can "announce" new public key which can then be fetched +by RPs in order to prepare for the switch of the keys (until the switch of keys, all artifacts continue to be +signed with the "old" private key). + +In this way, after RPs fetch new JWKS (JWKS with "old" and "new" key), you can do the switch of keys when you find +appropriate. + ### Note when using Apache web server If you are using Apache web server, you might encounter situations in which Apache strips of Authorization header @@ -168,20 +192,6 @@ SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 ``` Choose the one which works for you. If you don't set it, you'll get a warnings about this situation in your logs. -### Note on OpenID Federation (OIDF) support - -OpenID Federation support is in "draft" phase, as is the -[specification](https://openid.net/specs/openid-federation-1_0) itself. This means that you can expect braking changes -in future releases related to OIDF capabilities. You can enable / disable OIDF support at any time in module -configuration. - -Currently, the following OIDF features are supported: -* endpoint for issuing configuration entity statement (statement about itself) -* fetch endpoint for issuing statements about subordinates (registered clients) -* automatic client registration using a Request Object - -OIDF support is implemented using the underlying [SimpleSAMLphp OpenID library](https://github.com/simplesamlphp/openid). - ## Additional considerations ### Private scopes diff --git a/UPGRADE.md b/UPGRADE.md index 3a71139b..bc43e419 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -12,7 +12,10 @@ client and user data. The cache layer stands in front of the database store, so it can improve performance, especially in cases of sudden surge of users trying to authenticate. Implementation is based on Symfony Cache component, so any compatible Symfony cache adapter can be used. Check the module config file for more information on how to set the - protocol cache. + protocol cache. +- Key rollover support - you can now define additional (new) private / public key pair which will be published on +relevant JWKS endpoint or contained in JWKS property. In this way, you can "announce" new public key which can then +be fetched by RPs, and do the switch between "old" and "new" key pair when you find appropriate. - OpenID capabilities - New federation endpoints: - endpoint for issuing configuration entity statement (statement about itself) diff --git a/config/module_oidc.php.dist b/config/module_oidc.php.dist index a48b6169..caea6492 100644 --- a/config/module_oidc.php.dist +++ b/config/module_oidc.php.dist @@ -27,18 +27,30 @@ $config = [ * is a case-sensitive URL using the https scheme that contains scheme, host, and optionally, port number and * path components and no query or fragment components." */ - //ModuleConfig::OPTION_ISSUER => 'https://op.example.org', +// ModuleConfig::OPTION_ISSUER => 'https://op.example.org', /** * PKI (public / private key) settings related to OIDC protocol. These keys will be used, for example, to * sign ID Token JWT. */ // (optional) The private key passphrase. - //ModuleConfig::OPTION_PKI_PRIVATE_KEY_PASSPHRASE => 'secret', +// ModuleConfig::OPTION_PKI_PRIVATE_KEY_PASSPHRASE => 'secret', // The certificate and private key filenames, with given defaults. ModuleConfig::OPTION_PKI_PRIVATE_KEY_FILENAME => ModuleConfig::DEFAULT_PKI_PRIVATE_KEY_FILENAME, ModuleConfig::OPTION_PKI_CERTIFICATE_FILENAME => ModuleConfig::DEFAULT_PKI_CERTIFICATE_FILENAME, + /** + * (optional) Key rollover settings related to OIDC protocol. If set, this new private / public key pair will only + * be published on JWKS endpoint as available, so Relying Parties can pick them up for future use. The signing + * of artifacts will still be done using the 'current' private key (settings above). After some time, when all + * RPs have fetched all public keys from JWKS endpoint, simply set these new keys as active values for above + * PKI options. + */ +// // (optional) The (new) private key passphrase. +// ModuleConfig::OPTION_PKI_NEW_PRIVATE_KEY_PASSPHRASE => 'new-secret', +// ModuleConfig::OPTION_PKI_NEW_PRIVATE_KEY_FILENAME => 'new_oidc_module.key', +// ModuleConfig::OPTION_PKI_NEW_CERTIFICATE_FILENAME => 'new_oidc_module.crt', + /** * Token related options. */ @@ -51,8 +63,8 @@ $config = [ // Token signer, with given default. // See Lcobucci\JWT\Signer algorithms in https://github.com/lcobucci/jwt/tree/master/src/Signer ModuleConfig::OPTION_TOKEN_SIGNER => \Lcobucci\JWT\Signer\Rsa\Sha256::class, - //ModuleConfig::OPTION_TOKEN_SIGNER => \Lcobucci\JWT\Signer\Hmac\Sha256::class, - //ModuleConfig::OPTION_TOKEN_SIGNER => \Lcobucci\JWT\Signer\Ecdsa\Sha256::class, +// ModuleConfig::OPTION_TOKEN_SIGNER => \Lcobucci\JWT\Signer\Hmac\Sha256::class, +// ModuleConfig::OPTION_TOKEN_SIGNER => \Lcobucci\JWT\Signer\Ecdsa\Sha256::class, /** * Authentication related options. @@ -347,7 +359,7 @@ $config = [ // Federation authority hints. An array of strings representing the Entity Identifiers of Intermediate Entities // (or Trust Anchors). Required if this entity has a Superior entity above it. ModuleConfig::OPTION_FEDERATION_AUTHORITY_HINTS => [ - //'https://intermediate.example.org/', +// 'https://intermediate.example.org/', ], // (optional) Federation Trust Mark tokens. An array of tokens (signed JWTs), each representing a Trust Mark @@ -411,13 +423,22 @@ $config = [ * entity statements. Note that these keys SHOULD NOT be the same as the ones used in OIDC protocol itself. */ // The federation private key passphrase (optional). - //ModuleConfig::OPTION_PKI_FEDERATION_PRIVATE_KEY_PASSPHRASE => 'secret', +// ModuleConfig::OPTION_PKI_FEDERATION_PRIVATE_KEY_PASSPHRASE => 'secret', // The federation certificate and private key filenames, with given defaults. ModuleConfig::OPTION_PKI_FEDERATION_PRIVATE_KEY_FILENAME => ModuleConfig::DEFAULT_PKI_FEDERATION_PRIVATE_KEY_FILENAME, ModuleConfig::OPTION_PKI_FEDERATION_CERTIFICATE_FILENAME => ModuleConfig::DEFAULT_PKI_FEDERATION_CERTIFICATE_FILENAME, + /** + * (optional) Key rollover settings related to OpenID Federation. Check the OIDC protocol key rollover description + * on how this works. + */ + // The federation (new) private key passphrase (optional). +// ModuleConfig::OPTION_PKI_FEDERATION_NEW_PRIVATE_KEY_PASSPHRASE => 'new-secret', +// ModuleConfig::OPTION_PKI_FEDERATION_NEW_PRIVATE_KEY_FILENAME => 'new_oidc_module_federation.key', +// ModuleConfig::OPTION_PKI_FEDERATION_NEW_CERTIFICATE_FILENAME => 'new_oidc_module_federation.crt', + // Federation token signer, with given default. ModuleConfig::OPTION_FEDERATION_TOKEN_SIGNER => \Lcobucci\JWT\Signer\Rsa\Sha256::class, diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php index dcb2de51..730758a2 100644 --- a/src/ModuleConfig.php +++ b/src/ModuleConfig.php @@ -84,6 +84,14 @@ class ModuleConfig final public const OPTION_FEDERATION_PARTICIPATION_LIMIT_BY_TRUST_MARKS = 'federation_participation_limit_by_trust_marks'; + final public const OPTION_PKI_NEW_PRIVATE_KEY_PASSPHRASE = 'new_private_key_passphrase'; + final public const OPTION_PKI_NEW_PRIVATE_KEY_FILENAME = 'new_privatekey'; + final public const OPTION_PKI_NEW_CERTIFICATE_FILENAME = 'new_certificate'; + + final public const OPTION_PKI_FEDERATION_NEW_PRIVATE_KEY_PASSPHRASE = 'federation_new_private_key_passphrase'; + final public const OPTION_PKI_FEDERATION_NEW_PRIVATE_KEY_FILENAME = 'federation_new_private_key_filename'; + final public const OPTION_PKI_FEDERATION_NEW_CERTIFICATE_FILENAME = 'federation_new_certificate_filename'; + protected static array $standardScopes = [ ScopesEnum::OpenId->value => [ self::KEY_DESCRIPTION => 'openid', @@ -365,6 +373,22 @@ public function getProtocolCertPath(): string return $this->sspBridge->utils()->config()->getCertPath($certName); } + /** + * Get the path to the new public certificate to be used in OIDC protocol. + * @return ?string Null if not set, or file system path + * @throws \Exception + */ + public function getProtocolNewCertPath(): ?string + { + $certName = $this->config()->getOptionalString(self::OPTION_PKI_NEW_CERTIFICATE_FILENAME, null); + + if (is_string($certName)) { + return $this->sspBridge->utils()->config()->getCertPath($certName); + } + + return null; + } + /** * Get supported Authentication Context Class References (ACRs). * @@ -522,7 +546,6 @@ public function getFederationPrivateKeyPassPhrase(): ?string /** * Return the path to the federation public certificate - * @return string The file system path or null if not set. * @throws \Exception */ public function getFederationCertPath(): string @@ -535,6 +558,25 @@ public function getFederationCertPath(): string return $this->sspBridge->utils()->config()->getCertPath($certName); } + /** + * Return the path to the new federation public certificate + * @return ?string The file system path or null if not set. + * @throws \Exception + */ + public function getFederationNewCertPath(): ?string + { + $certName = $this->config()->getOptionalString( + self::OPTION_PKI_FEDERATION_NEW_CERTIFICATE_FILENAME, + null, + ); + + if (is_string($certName)) { + return $this->sspBridge->utils()->config()->getCertPath($certName); + } + + return null; + } + /** * @throws \Exception */ diff --git a/src/Services/JsonWebKeySetService.php b/src/Services/JsonWebKeySetService.php index f87b1428..f02f9e05 100644 --- a/src/Services/JsonWebKeySetService.php +++ b/src/Services/JsonWebKeySetService.php @@ -27,42 +27,20 @@ class JsonWebKeySetService { /** @var JWKSet JWKS for OIDC protocol. */ - private readonly JWKSet $protocolJwkSet; + protected JWKSet $protocolJwkSet; /** @var JWKSet|null JWKS for OpenID Federation. */ - private ?JWKSet $federationJwkSet = null; + protected ?JWKSet $federationJwkSet = null; /** * @throws \SimpleSAML\Error\Exception * @throws \Exception */ - public function __construct(ModuleConfig $moduleConfig) - { - $publicKeyPath = $moduleConfig->getProtocolCertPath(); - if (!file_exists($publicKeyPath)) { - throw new Error\Exception("OIDC protocol public key file does not exists: $publicKeyPath."); - } + public function __construct( + protected readonly ModuleConfig $moduleConfig, + ) { + $this->prepareProtocolJwkSet(); - $jwk = JWKFactory::createFromKeyFile($publicKeyPath, null, [ - ClaimsEnum::Kid->value => FingerprintGenerator::forFile($publicKeyPath), - ClaimsEnum::Use->value => PublicKeyUseEnum::Signature->value, - ClaimsEnum::Alg->value => $moduleConfig->getProtocolSigner()->algorithmId(), - ]); - - $this->protocolJwkSet = new JWKSet([$jwk]); - - if ( - ($federationPublicKeyPath = $moduleConfig->getFederationCertPath()) && - file_exists($federationPublicKeyPath) && - ($federationSigner = $moduleConfig->getFederationSigner()) - ) { - $federationJwk = JWKFactory::createFromKeyFile($federationPublicKeyPath, null, [ - ClaimsEnum::Kid->value => FingerprintGenerator::forFile($federationPublicKeyPath), - ClaimsEnum::Use->value => PublicKeyUseEnum::Signature->value, - ClaimsEnum::Alg->value => $federationSigner->algorithmId(), - ]); - - $this->federationJwkSet = new JWKSet([$federationJwk]); - } + $this->prepareFederationJwkSet(); } /** @@ -84,4 +62,72 @@ public function federationKeys(): array return $this->federationJwkSet->all(); } + + /** + * @throws \ReflectionException + * @throws \SimpleSAML\Error\Exception + */ + protected function prepareProtocolJwkSet(): void + { + $protocolPublicKeyPath = $this->moduleConfig->getProtocolCertPath(); + + if (!file_exists($protocolPublicKeyPath)) { + throw new Error\Exception("OIDC protocol public key file does not exists: $protocolPublicKeyPath."); + } + + $jwk = JWKFactory::createFromKeyFile($protocolPublicKeyPath, null, [ + ClaimsEnum::Kid->value => FingerprintGenerator::forFile($protocolPublicKeyPath), + ClaimsEnum::Use->value => PublicKeyUseEnum::Signature->value, + ClaimsEnum::Alg->value => $this->moduleConfig->getProtocolSigner()->algorithmId(), + ]); + + $keys = [$jwk]; + + if ( + ($protocolNewPublicKeyPath = $this->moduleConfig->getProtocolNewCertPath()) && + file_exists($protocolNewPublicKeyPath) + ) { + $newJwk = JWKFactory::createFromKeyFile($protocolNewPublicKeyPath, null, [ + ClaimsEnum::Kid->value => FingerprintGenerator::forFile($protocolNewPublicKeyPath), + ClaimsEnum::Use->value => PublicKeyUseEnum::Signature->value, + ClaimsEnum::Alg->value => $this->moduleConfig->getProtocolSigner()->algorithmId(), + ]); + + $keys[] = $newJwk; + } + + $this->protocolJwkSet = new JWKSet($keys); + } + + protected function prepareFederationJwkSet(): void + { + $federationPublicKeyPath = $this->moduleConfig->getFederationCertPath(); + + if (!file_exists($federationPublicKeyPath)) { + return; + } + + $federationJwk = JWKFactory::createFromKeyFile($federationPublicKeyPath, null, [ + ClaimsEnum::Kid->value => FingerprintGenerator::forFile($federationPublicKeyPath), + ClaimsEnum::Use->value => PublicKeyUseEnum::Signature->value, + ClaimsEnum::Alg->value => $this->moduleConfig->getFederationSigner()->algorithmId(), + ]); + + $keys = [$federationJwk]; + + if ( + ($federationNewPublicKeyPath = $this->moduleConfig->getFederationNewCertPath()) && + file_exists($federationNewPublicKeyPath) + ) { + $federationNewJwk = JWKFactory::createFromKeyFile($federationNewPublicKeyPath, null, [ + ClaimsEnum::Kid->value => FingerprintGenerator::forFile($federationNewPublicKeyPath), + ClaimsEnum::Use->value => PublicKeyUseEnum::Signature->value, + ClaimsEnum::Alg->value => $this->moduleConfig->getFederationSigner()->algorithmId(), + ]); + + $keys[] = $federationNewJwk; + } + + $this->federationJwkSet = new JWKSet($keys); + } } diff --git a/tests/unit/src/Services/JsonWebKeySetServiceTest.php b/tests/unit/src/Services/JsonWebKeySetServiceTest.php index 4503ca97..4aca2450 100644 --- a/tests/unit/src/Services/JsonWebKeySetServiceTest.php +++ b/tests/unit/src/Services/JsonWebKeySetServiceTest.php @@ -30,6 +30,9 @@ class JsonWebKeySetServiceTest extends TestCase { private static string $pkGeneratePublic; + private static string $pkGeneratePublicNew; + private static string $pkGeneratePublicFederation; + private static string $pkGeneratePublicFederationNew; /** * @return void @@ -42,15 +45,42 @@ public static function setUpBeforeClass(): void 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); + $pkGenerateNew = openssl_pkey_new([ + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ]); + $pkGenerateFederation = openssl_pkey_new([ + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ]); + $pkGenerateFederationNew = openssl_pkey_new([ + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ]); // get the public key $pkGenerateDetails = openssl_pkey_get_details($pkGenerate); + $pkGenerateDetailsNew = openssl_pkey_get_details($pkGenerateNew); + $pkGenerateDetailsFederation = openssl_pkey_get_details($pkGenerateFederation); + $pkGenerateDetailsFederationNew = openssl_pkey_get_details($pkGenerateFederationNew); self::$pkGeneratePublic = $pkGenerateDetails['key']; + self::$pkGeneratePublicNew = $pkGenerateDetailsNew['key']; + self::$pkGeneratePublicFederation = $pkGenerateDetailsFederation['key']; + self::$pkGeneratePublicFederationNew = $pkGenerateDetailsFederationNew['key']; file_put_contents(sys_get_temp_dir() . '/oidc_module.crt', self::$pkGeneratePublic); + file_put_contents(sys_get_temp_dir() . '/new_oidc_module.crt', self::$pkGeneratePublicNew); + file_put_contents(sys_get_temp_dir() . '/oidc_module_federation.crt', self::$pkGeneratePublicFederation); + file_put_contents( + sys_get_temp_dir() . '/new_oidc_module_federation.crt', + self::$pkGeneratePublicFederationNew, + ); Configuration::setPreLoadedConfig( - Configuration::loadFromArray([]), + Configuration::loadFromArray([ + ModuleConfig::OPTION_PKI_NEW_CERTIFICATE_FILENAME => 'new_oidc_module.crt', + ModuleConfig::OPTION_PKI_FEDERATION_NEW_CERTIFICATE_FILENAME => 'new_oidc_module_federation.crt', + ]), ModuleConfig::DEFAULT_FILE_NAME, ); } @@ -62,13 +92,16 @@ public static function tearDownAfterClass(): void { Configuration::clearInternalState(); unlink(sys_get_temp_dir() . '/oidc_module.crt'); + unlink(sys_get_temp_dir() . '/new_oidc_module.crt'); + unlink(sys_get_temp_dir() . '/oidc_module_federation.crt'); + unlink(sys_get_temp_dir() . '/new_oidc_module_federation.crt'); } /** * @return void * @throws \SimpleSAML\Error\Exception */ - public function testKeys() + public function testProtocolKeys() { $config = [ 'certdir' => sys_get_temp_dir(), @@ -76,13 +109,20 @@ public function testKeys() Configuration::loadFromArray($config, '', 'simplesaml'); $kid = FingerprintGenerator::forString(self::$pkGeneratePublic); - $jwk = JWKFactory::createFromKey(self::$pkGeneratePublic, null, [ 'kid' => $kid, 'use' => 'sig', 'alg' => 'RS256', ]); - $JWKSet = new JWKSet([$jwk]); + + $kidNew = FingerprintGenerator::forString(self::$pkGeneratePublicNew); + $jwkNew = JWKFactory::createFromKey(self::$pkGeneratePublicNew, null, [ + 'kid' => $kidNew, + 'use' => 'sig', + 'alg' => 'RS256', + ]); + + $JWKSet = new JWKSet([$jwk, $jwkNew]); $jsonWebKeySetService = new JsonWebKeySetService(new ModuleConfig()); @@ -92,7 +132,7 @@ public function testKeys() /** * @throws \SimpleSAML\Error\Exception */ - public function testCertificationFileNotFound(): void + public function testProtocolCertificateFileNotFound(): void { $this->expectException(Exception::class); $this->expectExceptionMessageMatches('/OIDC protocol public key file does not exists/'); @@ -104,4 +144,32 @@ public function testCertificationFileNotFound(): void new JsonWebKeySetService(new ModuleConfig()); } + + public function testFederationKeys(): void + { + $config = [ + 'certdir' => sys_get_temp_dir(), + ]; + Configuration::loadFromArray($config, '', 'simplesaml'); + + $kid = FingerprintGenerator::forString(self::$pkGeneratePublicFederation); + $jwk = JWKFactory::createFromKey(self::$pkGeneratePublicFederation, null, [ + 'kid' => $kid, + 'use' => 'sig', + 'alg' => 'RS256', + ]); + + $kidNew = FingerprintGenerator::forString(self::$pkGeneratePublicFederationNew); + $jwkNew = JWKFactory::createFromKey(self::$pkGeneratePublicFederationNew, null, [ + 'kid' => $kidNew, + 'use' => 'sig', + 'alg' => 'RS256', + ]); + + $JWKSet = new JWKSet([$jwk, $jwkNew]); + + $jsonWebKeySetService = new JsonWebKeySetService(new ModuleConfig()); + + $this->assertEquals($JWKSet->all(), $jsonWebKeySetService->federationKeys()); + } } From df90903949a43d51beb3f82f078bd8f31734a927 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Mon, 10 Feb 2025 10:02:13 +0100 Subject: [PATCH 54/70] Remove DatabaseLegacyOAuth2Import service (#288) --- UPGRADE.md | 12 ++-- src/Services/Container.php | 3 - src/Services/DatabaseLegacyOAuth2Import.php | 66 --------------------- 3 files changed, 4 insertions(+), 77 deletions(-) delete mode 100644 src/Services/DatabaseLegacyOAuth2Import.php diff --git a/UPGRADE.md b/UPGRADE.md index bc43e419..6904dea5 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,9 +1,3 @@ -# TODO - -- remove dependency on laminas/laminas-httphandlerrunner -- create a bridge towards SSP utility classes, so they can be easily mocked -- move away from SSP database as store; move to DBAL -- move to phpstan instead of psalm (as SSP) # Version 5 to 6 @@ -97,11 +91,13 @@ Below are also some internal changes that should not have impact for the OIDC OP this module as a library or extending from it, you will probably encounter breaking changes, since a lot of code has been refactored: -- upgraded to v5 of lcobucci/jwt https://github.com/lcobucci/jwt -- upgraded to v3 of laminas/laminas-diactoros https://github.com/laminas/laminas-diactoros +- Upgraded to v5 of lcobucci/jwt https://github.com/lcobucci/jwt +- Upgraded to v3 of laminas/laminas-diactoros https://github.com/laminas/laminas-diactoros - SimpleSAMLphp version used during development was bumped to v2.3 - In Authorization Code Flow, a new validation was added which checks for 'openid' value in 'scope' parameter. Up to now, 'openid' value was dynamically added if not present. In Implicit Code Flow this validation was already present. +- Removed importer from legacy OAuth2 module, as it is very unlikely that someone will upgrade from legacy OAuth2 +module to v6 of oidc module. If needed, one can upgrade to earlier versions of oidc module, and then to v6. # Version 4 to 5 diff --git a/src/Services/Container.php b/src/Services/Container.php index 8a6c5dae..0e3bf969 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -317,9 +317,6 @@ public function __construct() $databaseMigration = new DatabaseMigration($database); $this->services[DatabaseMigration::class] = $databaseMigration; - $databaseLegacyOAuth2Import = new DatabaseLegacyOAuth2Import($clientRepository, $clientEntityFactory); - $this->services[DatabaseLegacyOAuth2Import::class] = $databaseLegacyOAuth2Import; - $authenticationService = new AuthenticationService( $userRepository, $authSimpleFactory, diff --git a/src/Services/DatabaseLegacyOAuth2Import.php b/src/Services/DatabaseLegacyOAuth2Import.php deleted file mode 100644 index cfd6ec3f..00000000 --- a/src/Services/DatabaseLegacyOAuth2Import.php +++ /dev/null @@ -1,66 +0,0 @@ -findAll(); - - foreach ($clients as $client) { - if ($this->clientRepository->findById($client['id'])) { - continue; - } - - $this->clientRepository->add($this->clientEntityFactory->fromData( - $client['id'], - $client['secret'], - $client['name'], - $client['description'], - $client['redirect_uri'], - $client['scopes'], - true, - false, - $client['auth_source'], - )); - } - } -} From af500d388fe6a3a2a3ea8a8d537b191f54d8605b Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Mon, 10 Feb 2025 14:04:56 +0100 Subject: [PATCH 55/70] Remove OAuth2 Implicit flow (#290) * Remove OAuth2 Implicit flow * Update docs * Add some coverage --- README.md | 1 - UPGRADE.md | 4 +- routing/services/services.yml | 2 - src/Factories/AuthorizationServerFactory.php | 7 - .../Grant/OAuth2ImplicitGrantFactory.php | 34 ---- src/Server/AuthorizationServer.php | 6 +- src/Server/Grants/AuthCodeGrant.php | 6 +- src/Server/Grants/ImplicitGrant.php | 49 ++++-- ...horizationValidatableWithRequestRules.php} | 4 +- src/Server/Grants/OAuth2ImplicitGrant.php | 131 -------------- src/Services/Container.php | 6 - .../src/Server/Grants/ImplicitGrantTest.php | 166 +++++++++++++++++- .../Server/Grants/OAuth2ImplicitGrantTest.php | 18 -- 13 files changed, 211 insertions(+), 223 deletions(-) delete mode 100644 src/Factories/Grant/OAuth2ImplicitGrantFactory.php rename src/Server/Grants/Interfaces/{AuthorizationValidatableWithCheckerResultBagInterface.php => AuthorizationValidatableWithRequestRules.php} (84%) delete mode 100644 src/Server/Grants/OAuth2ImplicitGrant.php delete mode 100644 tests/unit/src/Server/Grants/OAuth2ImplicitGrantTest.php diff --git a/README.md b/README.md index 81f947b8..e372db67 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ through a SimpleSAMLphp module installable through Composer. It is based on Currently supported flows are: * Authorization Code flow, with PKCE support (response_type 'code') * Implicit flow (response_type 'id_token token' or 'id_token') -* Plain OAuth2 Implicit flow (response_type 'token') * Refresh Token flow [![Build Status](https://github.com/simplesamlphp/simplesamlphp-module-oidc/actions/workflows/test.yaml/badge.svg)](https://github.com/simplesamlphp/simplesamlphp-module-oidc/actions/workflows/test.yaml) diff --git a/UPGRADE.md b/UPGRADE.md index 6904dea5..14ac992c 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -75,6 +75,8 @@ known 'issue': https://github.com/symfony/symfony/issues/19693). If you don't se about this situation in your logs. - The new authproc filter processing will look in an additional location for filters, in the main `config.php` under key `authproc.oidc` +- Removed support for plain OAuth2 Implicit flow (response_type `token`), because of very low usage. Note that the OIDC +Implicit flow is still supported (response_type `id_token token` or `id_token`). ## Low impact changes @@ -95,7 +97,7 @@ has been refactored: - Upgraded to v3 of laminas/laminas-diactoros https://github.com/laminas/laminas-diactoros - SimpleSAMLphp version used during development was bumped to v2.3 - In Authorization Code Flow, a new validation was added which checks for 'openid' value in 'scope' parameter. Up to -now, 'openid' value was dynamically added if not present. In Implicit Code Flow this validation was already present. +now, 'openid' value was dynamically added if not present. In Implicit Code Flow this validation was already present. - Removed importer from legacy OAuth2 module, as it is very unlikely that someone will upgrade from legacy OAuth2 module to v6 of oidc module. If needed, one can upgrade to earlier versions of oidc module, and then to v6. diff --git a/routing/services/services.yml b/routing/services/services.yml index f120f146..60f08be9 100644 --- a/routing/services/services.yml +++ b/routing/services/services.yml @@ -56,8 +56,6 @@ services: # Grants SimpleSAML\Module\oidc\Server\Grants\AuthCodeGrant: factory: ['@SimpleSAML\Module\oidc\Factories\Grant\AuthCodeGrantFactory', 'build'] - SimpleSAML\Module\oidc\Server\Grants\OAuth2ImplicitGrant: - factory: ['@SimpleSAML\Module\oidc\Factories\Grant\OAuth2ImplicitGrantFactory', 'build'] SimpleSAML\Module\oidc\Server\Grants\ImplicitGrant: factory: ['@SimpleSAML\Module\oidc\Factories\Grant\ImplicitGrantFactory', 'build'] SimpleSAML\Module\oidc\Server\Grants\RefreshTokenGrant: diff --git a/src/Factories/AuthorizationServerFactory.php b/src/Factories/AuthorizationServerFactory.php index c2fa0f7e..54cf5d38 100644 --- a/src/Factories/AuthorizationServerFactory.php +++ b/src/Factories/AuthorizationServerFactory.php @@ -24,7 +24,6 @@ use SimpleSAML\Module\oidc\Server\AuthorizationServer; use SimpleSAML\Module\oidc\Server\Grants\AuthCodeGrant; use SimpleSAML\Module\oidc\Server\Grants\ImplicitGrant; -use SimpleSAML\Module\oidc\Server\Grants\OAuth2ImplicitGrant; use SimpleSAML\Module\oidc\Server\Grants\RefreshTokenGrant; use SimpleSAML\Module\oidc\Server\RequestRules\RequestRulesManager; use SimpleSAML\Module\oidc\Server\ResponseTypes\IdTokenResponse; @@ -37,7 +36,6 @@ public function __construct( private readonly AccessTokenRepository $accessTokenRepository, private readonly ScopeRepository $scopeRepository, private readonly AuthCodeGrant $authCodeGrant, - private readonly OAuth2ImplicitGrant $oAuth2ImplicitGrant, private readonly ImplicitGrant $implicitGrant, private readonly RefreshTokenGrant $refreshTokenGrant, private readonly IdTokenResponse $idTokenResponse, @@ -63,11 +61,6 @@ public function build(): AuthorizationServer $this->moduleConfig->getAccessTokenDuration(), ); - $authorizationServer->enableGrantType( - $this->oAuth2ImplicitGrant, - $this->moduleConfig->getAccessTokenDuration(), - ); - $authorizationServer->enableGrantType( $this->implicitGrant, $this->moduleConfig->getAccessTokenDuration(), diff --git a/src/Factories/Grant/OAuth2ImplicitGrantFactory.php b/src/Factories/Grant/OAuth2ImplicitGrantFactory.php deleted file mode 100644 index 3beaad1c..00000000 --- a/src/Factories/Grant/OAuth2ImplicitGrantFactory.php +++ /dev/null @@ -1,34 +0,0 @@ -moduleConfig->getAccessTokenDuration(), '#', $this->requestRulesManager); - } -} diff --git a/src/Server/AuthorizationServer.php b/src/Server/AuthorizationServer.php index 4c444e70..65c83e98 100644 --- a/src/Server/AuthorizationServer.php +++ b/src/Server/AuthorizationServer.php @@ -16,7 +16,7 @@ use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Error\BadRequest; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; -use SimpleSAML\Module\oidc\Server\Grants\Interfaces\AuthorizationValidatableWithCheckerResultBagInterface; +use SimpleSAML\Module\oidc\Server\Grants\Interfaces\AuthorizationValidatableWithRequestRules; use SimpleSAML\Module\oidc\Server\RequestRules\RequestRulesManager; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientIdRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\IdTokenHintRule; @@ -103,12 +103,12 @@ public function validateAuthorizationRequest(ServerRequestInterface $request): O foreach ($this->enabledGrantTypes as $grantType) { if ($grantType->canRespondToAuthorizationRequest($request)) { - if (! $grantType instanceof AuthorizationValidatableWithCheckerResultBagInterface) { + if (! $grantType instanceof AuthorizationValidatableWithRequestRules) { throw OidcServerException::serverError('grant type must be validatable with already validated ' . 'result bag'); } - return $grantType->validateAuthorizationRequestWithCheckerResultBag($request, $resultBag); + return $grantType->validateAuthorizationRequestWithRequestRules($request, $resultBag); } } diff --git a/src/Server/Grants/AuthCodeGrant.php b/src/Server/Grants/AuthCodeGrant.php index 5d73bcaf..e056081e 100644 --- a/src/Server/Grants/AuthCodeGrant.php +++ b/src/Server/Grants/AuthCodeGrant.php @@ -31,7 +31,7 @@ use SimpleSAML\Module\oidc\Repositories\Interfaces\AuthCodeRepositoryInterface; use SimpleSAML\Module\oidc\Repositories\Interfaces\RefreshTokenRepositoryInterface; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; -use SimpleSAML\Module\oidc\Server\Grants\Interfaces\AuthorizationValidatableWithCheckerResultBagInterface; +use SimpleSAML\Module\oidc\Server\Grants\Interfaces\AuthorizationValidatableWithRequestRules; use SimpleSAML\Module\oidc\Server\Grants\Interfaces\OidcCapableGrantTypeInterface; use SimpleSAML\Module\oidc\Server\Grants\Interfaces\PkceEnabledGrantTypeInterface; use SimpleSAML\Module\oidc\Server\Grants\Traits\IssueAccessTokenTrait; @@ -72,7 +72,7 @@ class AuthCodeGrant extends OAuth2AuthCodeGrant implements // phpcs:ignore OidcCapableGrantTypeInterface, // phpcs:ignore - AuthorizationValidatableWithCheckerResultBagInterface + AuthorizationValidatableWithRequestRules { use IssueAccessTokenTrait; @@ -641,7 +641,7 @@ protected function validateAuthorizationCode( * @inheritDoc * @throws \Throwable */ - public function validateAuthorizationRequestWithCheckerResultBag( + public function validateAuthorizationRequestWithRequestRules( ServerRequestInterface $request, ResultBagInterface $resultBag, ): OAuth2AuthorizationRequest { diff --git a/src/Server/Grants/ImplicitGrant.php b/src/Server/Grants/ImplicitGrant.php index c4872748..4e2026bc 100644 --- a/src/Server/Grants/ImplicitGrant.php +++ b/src/Server/Grants/ImplicitGrant.php @@ -5,6 +5,7 @@ namespace SimpleSAML\Module\oidc\Server\Grants; 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; @@ -17,24 +18,32 @@ use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory; use SimpleSAML\Module\oidc\Repositories\Interfaces\AccessTokenRepositoryInterface; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; +use SimpleSAML\Module\oidc\Server\Grants\Interfaces\AuthorizationValidatableWithRequestRules; use SimpleSAML\Module\oidc\Server\Grants\Traits\IssueAccessTokenTrait; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\RequestRulesManager; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\AcrValuesRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\AddClaimsToIdTokenRule; +use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientIdRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\MaxAgeRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\PromptRule; +use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RedirectUriRule; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\RequestedClaimsRule; 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\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\Services\IdTokenBuilder; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; -class ImplicitGrant extends OAuth2ImplicitGrant +/** + * @psalm-suppress PropertyNotSetInConstructor + */ +class ImplicitGrant extends OAuth2ImplicitGrant implements AuthorizationValidatableWithRequestRules { use IssueAccessTokenTrait; @@ -49,14 +58,15 @@ class ImplicitGrant extends OAuth2ImplicitGrant public function __construct( protected IdTokenBuilder $idTokenBuilder, - DateInterval $accessTokenTTL, + protected DateInterval $accessTokenTTL, AccessTokenRepositoryInterface $accessTokenRepository, - RequestRulesManager $requestRulesManager, + protected RequestRulesManager $requestRulesManager, protected RequestParamsResolver $requestParamsResolver, - string $queryDelimiter, + protected string $queryDelimiter, AccessTokenEntityFactory $accessTokenEntityFactory, ) { - parent::__construct($accessTokenTTL, $queryDelimiter, $requestRulesManager); + parent::__construct($accessTokenTTL, $queryDelimiter); + $this->accessTokenRepository = $accessTokenRepository; $this->accessTokenEntityFactory = $accessTokenEntityFactory; } @@ -108,14 +118,12 @@ public function completeAuthorizationRequest( * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException * @throws \Throwable */ - public function validateAuthorizationRequestWithCheckerResultBag( + public function validateAuthorizationRequestWithRequestRules( ServerRequestInterface $request, ResultBagInterface $resultBag, ): OAuth2AuthorizationRequest { - $oAuth2AuthorizationRequest = - parent::validateAuthorizationRequestWithCheckerResultBag($request, $resultBag); - $rulesToExecute = [ + ScopeRule::class, RequestObjectRule::class, PromptRule::class, MaxAgeRule::class, @@ -129,6 +137,17 @@ public function validateAuthorizationRequestWithCheckerResultBag( $this->requestRulesManager->predefineResultBag($resultBag); + /** @var string $redirectUri */ + $redirectUri = $resultBag->getOrFail(RedirectUriRule::class)->getValue(); + /** @var string|null $state */ + $state = $resultBag->getOrFail(StateRule::class)->getValue(); + /** @var \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface $client */ + $client = $resultBag->getOrFail(ClientIdRule::class)->getValue(); + + // Some rules need certain things available in order to work properly... + $this->requestRulesManager->setData('default_scope', $this->defaultScope); + $this->requestRulesManager->setData('scope_delimiter_string', self::SCOPE_DELIMITER_STRING); + $resultBag = $this->requestRulesManager->check( $request, $rulesToExecute, @@ -136,7 +155,17 @@ public function validateAuthorizationRequestWithCheckerResultBag( $this->allowedAuthorizationHttpMethods, ); - $authorizationRequest = AuthorizationRequest::fromOAuth2AuthorizationRequest($oAuth2AuthorizationRequest); + /** @var \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes */ + $scopes = $resultBag->getOrFail(ScopeRule::class)->getValue(); + + $authorizationRequest = new AuthorizationRequest(); + $authorizationRequest->setClient($client); + $authorizationRequest->setRedirectUri($redirectUri); + $authorizationRequest->setScopes($scopes); + $authorizationRequest->setGrantTypeId($this->getIdentifier()); + if ($state !== null) { + $authorizationRequest->setState($state); + } // nonce existence is validated using a rule, so we can get it from there. $authorizationRequest->setNonce((string)$resultBag->getOrFail(RequiredNonceRule::class)->getValue()); diff --git a/src/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php b/src/Server/Grants/Interfaces/AuthorizationValidatableWithRequestRules.php similarity index 84% rename from src/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php rename to src/Server/Grants/Interfaces/AuthorizationValidatableWithRequestRules.php index 3a2af68b..c0e1d49b 100644 --- a/src/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php +++ b/src/Server/Grants/Interfaces/AuthorizationValidatableWithRequestRules.php @@ -8,14 +8,14 @@ use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; -interface AuthorizationValidatableWithCheckerResultBagInterface +interface AuthorizationValidatableWithRequestRules { /** * Validate authorization request using an existing ResultBag instance (with already validated checkers). * This is to evade usage of original validateAuthorizationRequest() method in which it is expected to * validate client and redirect_uri (which was already validated). */ - public function validateAuthorizationRequestWithCheckerResultBag( + public function validateAuthorizationRequestWithRequestRules( ServerRequestInterface $request, ResultBagInterface $resultBag, ): OAuth2AuthorizationRequest; diff --git a/src/Server/Grants/OAuth2ImplicitGrant.php b/src/Server/Grants/OAuth2ImplicitGrant.php deleted file mode 100644 index 5914dccc..00000000 --- a/src/Server/Grants/OAuth2ImplicitGrant.php +++ /dev/null @@ -1,131 +0,0 @@ -accessTokenTTL = $accessTokenTTL; - $this->queryDelimiter = $queryDelimiter; - $this->requestRulesManager = $requestRulesManager; - } - - /** - * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException - * @throws \Throwable - */ - public function validateAuthorizationRequestWithCheckerResultBag( - ServerRequestInterface $request, - ResultBagInterface $resultBag, - ): OAuth2AuthorizationRequest { - $rulesToExecute = [ - ScopeRule::class, - ]; - - // Since we have already validated redirect_uri, and we have state, make it available for other checkers. - $this->requestRulesManager->predefineResultBag($resultBag); - - /** @var string $redirectUri */ - $redirectUri = $resultBag->getOrFail(RedirectUriRule::class)->getValue(); - /** @var string|null $state */ - $state = $resultBag->getOrFail(StateRule::class)->getValue(); - /** @var \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface $client */ - $client = $resultBag->getOrFail(ClientIdRule::class)->getValue(); - - // Some rules have to have certain things available in order to work properly... - $this->requestRulesManager->setData('default_scope', $this->defaultScope); - $this->requestRulesManager->setData('scope_delimiter_string', self::SCOPE_DELIMITER_STRING); - - $resultBag = $this->requestRulesManager->check( - $request, - $rulesToExecute, - false, - [HttpMethodsEnum::GET, HttpMethodsEnum::POST], - ); - - /** @var \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes */ - $scopes = $resultBag->getOrFail(ScopeRule::class)->getValue(); - - $oAuth2AuthorizationRequest = new OAuth2AuthorizationRequest(); - - $oAuth2AuthorizationRequest->setClient($client); - $oAuth2AuthorizationRequest->setRedirectUri($redirectUri); - $oAuth2AuthorizationRequest->setScopes($scopes); - $oAuth2AuthorizationRequest->setGrantTypeId($this->getIdentifier()); - - if ($state !== null) { - $oAuth2AuthorizationRequest->setState($state); - } - - return $oAuth2AuthorizationRequest; - } -} diff --git a/src/Services/Container.php b/src/Services/Container.php index 0e3bf969..78f57483 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -50,7 +50,6 @@ use SimpleSAML\Module\oidc\Factories\FormFactory; use SimpleSAML\Module\oidc\Factories\Grant\AuthCodeGrantFactory; use SimpleSAML\Module\oidc\Factories\Grant\ImplicitGrantFactory; -use SimpleSAML\Module\oidc\Factories\Grant\OAuth2ImplicitGrantFactory; use SimpleSAML\Module\oidc\Factories\Grant\RefreshTokenGrantFactory; use SimpleSAML\Module\oidc\Factories\IdTokenResponseFactory; use SimpleSAML\Module\oidc\Factories\JwksFactory; @@ -71,7 +70,6 @@ use SimpleSAML\Module\oidc\Server\AuthorizationServer; use SimpleSAML\Module\oidc\Server\Grants\AuthCodeGrant; use SimpleSAML\Module\oidc\Server\Grants\ImplicitGrant; -use SimpleSAML\Module\oidc\Server\Grants\OAuth2ImplicitGrant; use SimpleSAML\Module\oidc\Server\Grants\RefreshTokenGrant; use SimpleSAML\Module\oidc\Server\RequestRules\RequestRulesManager; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\AcrValuesRule; @@ -436,9 +434,6 @@ public function __construct() ); $this->services[AuthCodeGrant::class] = $authCodeGrantFactory->build(); - $oAuth2ImplicitGrantFactory = new OAuth2ImplicitGrantFactory($moduleConfig, $requestRuleManager); - $this->services[OAuth2ImplicitGrant::class] = $oAuth2ImplicitGrantFactory->build(); - $implicitGrantFactory = new ImplicitGrantFactory( $moduleConfig, $this->services[IdTokenBuilder::class], @@ -463,7 +458,6 @@ public function __construct() $accessTokenRepository, $scopeRepository, $this->services[AuthCodeGrant::class], - $this->services[OAuth2ImplicitGrant::class], $this->services[ImplicitGrant::class], $this->services[RefreshTokenGrant::class], $this->services[IdTokenResponse::class], diff --git a/tests/unit/src/Server/Grants/ImplicitGrantTest.php b/tests/unit/src/Server/Grants/ImplicitGrantTest.php index 6c7045d4..d28dabda 100644 --- a/tests/unit/src/Server/Grants/ImplicitGrantTest.php +++ b/tests/unit/src/Server/Grants/ImplicitGrantTest.php @@ -4,15 +4,171 @@ namespace SimpleSAML\Test\Module\oidc\unit\Server\Grants; +use League\OAuth2\Server\Entities\ScopeEntityInterface; +use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; +use League\OAuth2\Server\ResponseTypes\RedirectResponse; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Entities\ClientEntity; +use SimpleSAML\Module\oidc\Entities\UserEntity; +use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory; +use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; +use SimpleSAML\Module\oidc\Repositories\Interfaces\AccessTokenRepositoryInterface; +use SimpleSAML\Module\oidc\Server\Grants\ImplicitGrant; +use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; +use SimpleSAML\Module\oidc\Server\RequestRules\RequestRulesManager; +use SimpleSAML\Module\oidc\Server\RequestTypes\AuthorizationRequest; +use SimpleSAML\Module\oidc\Services\IdTokenBuilder; +use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; -/** - * @covers \SimpleSAML\Module\oidc\Server\Grants\ImplicitGrant - */ +#[CoversClass(ImplicitGrant::class)] class ImplicitGrantTest extends TestCase { - public function testIncomplete(): never + protected MockObject $idTokenBuilderMock; + protected \DateInterval $accessTokenTtl1h; + protected MockObject $accessTokenRepositoryMock; + protected MockObject $requestRulesManagerMock; + protected MockObject $requestParamsResolverMock; + protected string $queryDelimiter; + protected MockObject $accessTokenEntityFactoryMock; + protected MockObject $scopeRepositoryMock; + protected MockObject $serverRequestMock; + protected MockObject $authorizationRequestMock; + protected MockObject $userEntityMock; + protected MockObject $scopeEntityMock; + protected MockObject $clientEntityMock; + protected MockObject $resultBagMock; + + protected function setUp(): void + { + $this->idTokenBuilderMock = $this->createMock(IdTokenBuilder::class); + $this->accessTokenTtl1h = new \DateInterval('PT1H'); + $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); + + $this->serverRequestMock = $this->createMock(ServerRequestInterface::class); + $this->authorizationRequestMock = $this->createMock(AuthorizationRequest::class); + $this->userEntityMock = $this->createMock(UserEntity::class); + $this->scopeEntityMock = $this->createMock(ScopeEntityInterface::class); + $this->clientEntityMock = $this->createMock(ClientEntity::class); + $this->resultBagMock = $this->createMock(ResultBagInterface::class); + } + + protected function sut( + ?IdTokenBuilder $idTokenBuilder = null, + ?\DateInterval $accessTokenTtl = null, + ?AccessTokenRepositoryInterface $accessTokenRepository = null, + ?RequestRulesManager $requestRulesManager = null, + ?RequestParamsResolver $requestParamsResolver = null, + ?string $queryDelimiter = null, + ?AccessTokenEntityFactory $accessTokenEntityFactory = null, + ?ScopeRepositoryInterface $scopeRepository = null, + ): ImplicitGrant { + $idTokenBuilder ??= $this->idTokenBuilderMock; + $accessTokenTtl ??= $this->accessTokenTtl1h; + $accessTokenRepository ??= $this->accessTokenRepositoryMock; + $requestRulesManager ??= $this->requestRulesManagerMock; + $requestParamsResolver ??= $this->requestParamsResolverMock; + $queryDelimiter ??= $this->queryDelimiter; + $accessTokenEntityFactory ??= $this->accessTokenEntityFactoryMock; + $scopeRepository ??= $this->scopeRepositoryMock; + + + $implicitGrant = new ImplicitGrant( + $idTokenBuilder, + $accessTokenTtl, + $accessTokenRepository, + $requestRulesManager, + $requestParamsResolver, + $queryDelimiter, + $accessTokenEntityFactory, + ); + + $implicitGrant->setScopeRepository($scopeRepository); + + return $implicitGrant; + } + + public function testCanConstruct(): void + { + $this->assertInstanceOf(ImplicitGrant::class, $this->sut()); + } + + public function testCanRespondToAuthorizationRequestForIdTokenTokenResponseType(): void + { + $this->requestParamsResolverMock->expects($this->once()) + ->method('getAllBasedOnAllowedMethods') + ->willReturn(['client_id' => 'clientId', 'response_type' => 'id_token token']); + + $this->assertTrue($this->sut()->canRespondToAuthorizationRequest($this->serverRequestMock)); + } + + public function testCanRespondToAuthorizationRequestForIdTokenResponseType(): void + { + $this->requestParamsResolverMock->expects($this->once()) + ->method('getAllBasedOnAllowedMethods') + ->willReturn(['client_id' => 'clientId', 'response_type' => 'id_token']); + + $this->assertTrue($this->sut()->canRespondToAuthorizationRequest($this->serverRequestMock)); + } + + public function testCanRespondToAuthorizationRequestReturnsFalseIfNoClientId(): void + { + $this->requestParamsResolverMock->expects($this->once()) + ->method('getAllBasedOnAllowedMethods') + ->willReturn(['response_type' => 'id_token']); + + $this->assertFalse($this->sut()->canRespondToAuthorizationRequest($this->serverRequestMock)); + } + + public function testCanRespondToAuthorizationRequestReturnsFalseForHybridFlow(): void + { + $this->requestParamsResolverMock->expects($this->once()) + ->method('getAllBasedOnAllowedMethods') + ->willReturn(['response_type' => 'code id_token']); + + $this->assertFalse($this->sut()->canRespondToAuthorizationRequest($this->serverRequestMock)); + } + + public function testCompleteAuthorizationRequestThrowsForNonOidcRequests(): void + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Unexpected'); + + $this->sut()->completeAuthorizationRequest($this->createMock( + \League\OAuth2\Server\RequestTypes\AuthorizationRequest::class, + )); + } + + public function testCanCompleteAuthorizationRequest(): void + { + $this->authorizationRequestMock->expects($this->once())->method('getUser') + ->willReturn($this->userEntityMock); + $this->authorizationRequestMock->expects($this->once())->method('getRedirectUri') + ->willReturn('redirectUri'); + $this->authorizationRequestMock->expects($this->once())->method('isAuthorizationApproved') + ->willReturn(true); + $this->authorizationRequestMock->expects($this->once())->method('getScopes') + ->willReturn([$this->scopeEntityMock]); + $this->authorizationRequestMock->method('getClient') + ->willReturn($this->clientEntityMock); + $this->scopeRepositoryMock->expects($this->once())->method('finalizeScopes') + ->willReturn([$this->scopeEntityMock]); + + $this->assertInstanceOf( + RedirectResponse::class, + $this->sut()->completeAuthorizationRequest($this->authorizationRequestMock), + ); + } + + public function testCanValidateAuthorizationRequestWithRequestRules(): void { - $this->markTestIncomplete(); + $this->markTestIncomplete('RequestRulesManager needs to be refactored so it can be strongly typed.'); } } diff --git a/tests/unit/src/Server/Grants/OAuth2ImplicitGrantTest.php b/tests/unit/src/Server/Grants/OAuth2ImplicitGrantTest.php deleted file mode 100644 index 50732b35..00000000 --- a/tests/unit/src/Server/Grants/OAuth2ImplicitGrantTest.php +++ /dev/null @@ -1,18 +0,0 @@ -markTestIncomplete(); - } -} From 8fd3b221df94894c7348df2c439d202a5b1ce1a9 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Mon, 10 Feb 2025 14:47:18 +0100 Subject: [PATCH 56/70] Update docs --- README.md | 1 + UPGRADE.md | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e372db67..34810467 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ configuration. Currently, the following OIDF features are supported: * automatic client registration using a Request Object (passing it by value) +* federation participation limiting based on Trust Marks * endpoint for issuing configuration entity statement (statement about itself) * fetch endpoint for issuing statements about subordinates (registered clients) diff --git a/UPGRADE.md b/UPGRADE.md index 14ac992c..9cc95e34 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -10,10 +10,11 @@ - Key rollover support - you can now define additional (new) private / public key pair which will be published on relevant JWKS endpoint or contained in JWKS property. In this way, you can "announce" new public key which can then be fetched by RPs, and do the switch between "old" and "new" key pair when you find appropriate. -- OpenID capabilities - - New federation endpoints: - - endpoint for issuing configuration entity statement (statement about itself) - - fetch endpoint for issuing statements about subordinates (registered clients) +- OpenID Federation capabilities: + - Automatic client registration using a Request Object (passing it by value) + - Federation participation limiting based on Trust Marks + - Endpoint for issuing configuration entity statement (statement about itself) + - Fetch endpoint for issuing statements about subordinates (registered clients) - Clients can now be configured with new properties: - Entity Identifier - Supported OpenID Federation Registration Types From ef72119e0ed0086294000dc840352d981a84c1bd Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Mon, 10 Feb 2025 15:23:40 +0100 Subject: [PATCH 57/70] Remove Test controller --- routing/routes/routes.php | 4 - src/Controllers/Federation/Test.php | 152 ---------------------------- 2 files changed, 156 deletions(-) delete mode 100644 src/Controllers/Federation/Test.php diff --git a/routing/routes/routes.php b/routing/routes/routes.php index d014c72d..f05ac6ac 100644 --- a/routing/routes/routes.php +++ b/routing/routes/routes.php @@ -96,8 +96,4 @@ $routes->add(RoutesEnum::FederationFetch->name, RoutesEnum::FederationFetch->value) ->controller([EntityStatementController::class, 'fetch']) ->methods([HttpMethodsEnum::GET->value]); - - // TODO mivanci delete - $routes->add('test', 'test') - ->controller(\SimpleSAML\Module\oidc\Controllers\Federation\Test::class); }; diff --git a/src/Controllers/Federation/Test.php b/src/Controllers/Federation/Test.php deleted file mode 100644 index 8c10fd36..00000000 --- a/src/Controllers/Federation/Test.php +++ /dev/null @@ -1,152 +0,0 @@ -coreFactory->build()); -// $t = 'eyJ0eXAiOiJlbnRpdHktc3RhdGVtZW50K2p3dCIsImFsZyI6IlJTMjU2Iiwia2lkIjoiYzRhZmYzY2M3NDM5MWI3M2UxM2FhODE2OTdkYmYzODIifQ.eyJpc3MiOiJodHRwczovLzgyLWRhcC5sb2NhbGhvc3QubWFya29pdmFuY2ljLmZyb20uaHIiLCJpYXQiOjE3MjY4NTM0NTUsImp0aSI6IjQ0ZDQyNDQxOGIyZDEwOWY4M2FhNDMzY2Y0YTVhODNiMTI4YjgzZDZiZDExOTRjMDI1NTgzMTQ1YmZkMjNjMzZjZDg1Y2UzMzBjN2ZlOTc4Iiwic3ViIjoiaHR0cHM6Ly84Mi1kYXAubG9jYWxob3N0Lm1hcmtvaXZhbmNpYy5mcm9tLmhyIiwiZXhwIjoxNzI2OTM5ODU1LCJqd2tzIjp7ImtleXMiOlt7Imt0eSI6IlJTQSIsIm4iOiJzTHpnc0NiaW40Y0l1YUlFZ0w3QzBvaXZSazNyN09HSTBUdWJ0TFBYMkJiMmI5QmtPVElUcnhqSjIwenVVblVLbUJ5eGdyaFJUZGtVWW9EcFJOenVIUENyeVdwU0NQSDB5SUZPUVdxbEFxWHEzXzJheHcwTzlCMFVYVzYzQWNaRVBERVlVWGFsNHNaazE3OG9ZMTNhUlk0Um9NZm8yZkZ1cDlyb2RpSFJqU0gweWsxS2tEOWR5NjZGM1ZmaTF6SHRGQzhkV000clE5cW1OS3pyVFpXMzFsVmQ3N3ZvajZsNE1BOFlYWFVuM2dVMHRocUxMRFI3WnhJcFdUcU1VbzVDRXFJZ0pZS0FRUG5sZldvQ2JiMVhWSl9qMFNQZzZ0M29GNTUwNGd3SFp3M1dDSHJEbUxzdTdpa29CcmdrRWZnS05ISWlra3hXalB0bGNmbmlXUjl2b1EiLCJlIjoiQVFBQiIsImtpZCI6ImM0YWZmM2NjNzQzOTFiNzNlMTNhYTgxNjk3ZGJmMzgyIiwidXNlIjoic2lnIiwiYWxnIjoiUlMyNTYifV19LCJtZXRhZGF0YSI6eyJmZWRlcmF0aW9uX2VudGl0eSI6eyJvcmdhbml6YXRpb25fbmFtZSI6IkZvbyBjb3JwIiwiY29udGFjdHMiOlsiSm9obiBEb2UgamRvZUBleGFtcGxlLm9yZyJdLCJsb2dvX3VyaSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvbG9nbyIsInBvbGljeV91cmkiOiJodHRwczovL2V4YW1wbGUub3JnL3BvbGljeSIsImhvbWVwYWdlX3VyaSI6Imh0dHBzOi8vZXhhbXBsZS5vcmciLCJmZWRlcmF0aW9uX2ZldGNoX2VuZHBvaW50IjoiaHR0cHM6Ly84Mi1kYXAubG9jYWxob3N0Lm1hcmtvaXZhbmNpYy5mcm9tLmhyL3NpbXBsZXNhbWxwaHAvc2ltcGxlc2FtbHBocC0yLjIvbW9kdWxlLnBocC9vaWRjL2ZlZGVyYXRpb24vZmV0Y2gifSwib3BlbmlkX3Byb3ZpZGVyIjp7Imlzc3VlciI6Imh0dHBzOi8vODItZGFwLmxvY2FsaG9zdC5tYXJrb2l2YW5jaWMuZnJvbS5ociIsImF1dGhvcml6YXRpb25fZW5kcG9pbnQiOiJodHRwczovLzgyLWRhcC5sb2NhbGhvc3QubWFya29pdmFuY2ljLmZyb20uaHIvc2ltcGxlc2FtbHBocC9zaW1wbGVzYW1scGhwLTIuMi9tb2R1bGUucGhwL29pZGMvYXV0aG9yaXphdGlvbiIsInRva2VuX2VuZHBvaW50IjoiaHR0cHM6Ly84Mi1kYXAubG9jYWxob3N0Lm1hcmtvaXZhbmNpYy5mcm9tLmhyL3NpbXBsZXNhbWxwaHAvc2ltcGxlc2FtbHBocC0yLjIvbW9kdWxlLnBocC9vaWRjL3Rva2VuIiwidXNlcmluZm9fZW5kcG9pbnQiOiJodHRwczovLzgyLWRhcC5sb2NhbGhvc3QubWFya29pdmFuY2ljLmZyb20uaHIvc2ltcGxlc2FtbHBocC9zaW1wbGVzYW1scGhwLTIuMi9tb2R1bGUucGhwL29pZGMvdXNlcmluZm8iLCJlbmRfc2Vzc2lvbl9lbmRwb2ludCI6Imh0dHBzOi8vODItZGFwLmxvY2FsaG9zdC5tYXJrb2l2YW5jaWMuZnJvbS5oci9zaW1wbGVzYW1scGhwL3NpbXBsZXNhbWxwaHAtMi4yL21vZHVsZS5waHAvb2lkYy9lbmQtc2Vzc2lvbiIsImp3a3NfdXJpIjoiaHR0cHM6Ly84Mi1kYXAubG9jYWxob3N0Lm1hcmtvaXZhbmNpYy5mcm9tLmhyL3NpbXBsZXNhbWxwaHAvc2ltcGxlc2FtbHBocC0yLjIvbW9kdWxlLnBocC9vaWRjL2p3a3MiLCJzY29wZXNfc3VwcG9ydGVkIjpbIm9wZW5pZCIsIm9mZmxpbmVfYWNjZXNzIiwicHJvZmlsZSIsImVtYWlsIiwiYWRkcmVzcyIsInBob25lIiwiaHJFZHVQZXJzb25VbmlxdWVJRCIsInVpZCIsImNuIiwic24iLCJnaXZlbk5hbWUiLCJtYWlsIiwidGVsZXBob25lTnVtYmVyIiwiaHJFZHVQZXJzb25FeHRlbnNpb25OdW1iZXIiLCJtb2JpbGUiLCJmYWNzaW1pbGVUZWxlcGhvbmVOdW1iZXIiLCJockVkdVBlcnNvblVuaXF1ZU51bWJlciIsImhyRWR1UGVyc29uT0lCIiwiaHJFZHVQZXJzb25EYXRlT2ZCaXJ0aCIsImhyRWR1UGVyc29uR2VuZGVyIiwianBlZ1Bob3RvIiwidXNlckNlcnRpZmljYXRlIiwibGFiZWxlZFVSSSIsImhyRWR1UGVyc29uUHJvZmVzc2lvbmFsU3RhdHVzIiwiaHJFZHVQZXJzb25BY2FkZW1pY1N0YXR1cyIsImhyRWR1UGVyc29uU2NpZW5jZUFyZWEiLCJockVkdVBlcnNvbkFmZmlsaWF0aW9uIiwiaHJFZHVQZXJzb25QcmltYXJ5QWZmaWxpYXRpb24iLCJockVkdVBlcnNvblN0dWRlbnRDYXRlZ29yeSIsImhyRWR1UGVyc29uRXhwaXJlRGF0ZSIsImhyRWR1UGVyc29uVGl0bGUiLCJockVkdVBlcnNvblJvbGUiLCJockVkdVBlcnNvblN0YWZmQ2F0ZWdvcnkiLCJockVkdVBlcnNvbkdyb3VwTWVtYmVyIiwibyIsImhyRWR1UGVyc29uSG9tZU9yZyIsIm91Iiwicm9vbU51bWJlciIsInBvc3RhbEFkZHJlc3MiLCJsIiwicG9zdGFsQ29kZSIsInN0cmVldCIsImhvbWVQb3N0YWxBZGRyZXNzIiwiaG9tZVRlbGVwaG9uZU51bWJlciIsImhyRWR1UGVyc29uQ29tbVVSSSIsImhyRWR1UGVyc29uUHJpdmFjeSIsImhyRWR1UGVyc29uUGVyc2lzdGVudElEIiwiZGlzcGxheU5hbWUiLCJzY2hhY1VzZXJQcmVzZW5jZUlEIiwiaHJFZHVQZXJzb25DYXJkTnVtIiwiZm9ybWF0ZWRUZXN0Il0sInJlc3BvbnNlX3R5cGVzX3N1cHBvcnRlZCI6WyJjb2RlIiwidG9rZW4iLCJpZF90b2tlbiIsImlkX3Rva2VuIHRva2VuIl0sInN1YmplY3RfdHlwZXNfc3VwcG9ydGVkIjpbInB1YmxpYyJdLCJpZF90b2tlbl9zaWduaW5nX2FsZ192YWx1ZXNfc3VwcG9ydGVkIjpbIlJTMjU2Il0sImNvZGVfY2hhbGxlbmdlX21ldGhvZHNfc3VwcG9ydGVkIjpbInBsYWluIiwiUzI1NiJdLCJ0b2tlbl9lbmRwb2ludF9hdXRoX21ldGhvZHNfc3VwcG9ydGVkIjpbImNsaWVudF9zZWNyZXRfcG9zdCIsImNsaWVudF9zZWNyZXRfYmFzaWMiLCJwcml2YXRlX2tleV9qd3QiXSwicmVxdWVzdF9wYXJhbWV0ZXJfc3VwcG9ydGVkIjp0cnVlLCJyZXF1ZXN0X29iamVjdF9zaWduaW5nX2FsZ192YWx1ZXNfc3VwcG9ydGVkIjpbIm5vbmUiLCJSUzI1NiJdLCJyZXF1ZXN0X3VyaV9wYXJhbWV0ZXJfc3VwcG9ydGVkIjpmYWxzZSwiZ3JhbnRfdHlwZXNfc3VwcG9ydGVkIjpbImF1dGhvcml6YXRpb25fY29kZSIsInJlZnJlc2hfdG9rZW4iXSwiY2xhaW1zX3BhcmFtZXRlcl9zdXBwb3J0ZWQiOnRydWUsImFjcl92YWx1ZXNfc3VwcG9ydGVkIjpbIjEiLCIwIl0sImJhY2tjaGFubmVsX2xvZ291dF9zdXBwb3J0ZWQiOnRydWUsImJhY2tjaGFubmVsX2xvZ291dF9zZXNzaW9uX3N1cHBvcnRlZCI6dHJ1ZSwiY2xpZW50X3JlZ2lzdHJhdGlvbl90eXBlc19zdXBwb3J0ZWQiOlsiYXV0b21hdGljIl0sInJlcXVlc3RfYXV0aGVudGljYXRpb25fbWV0aG9kc19zdXBwb3J0ZWQiOnsiYXV0aG9yaXphdGlvbl9lbmRwb2ludCI6WyJyZXF1ZXN0X29iamVjdCJdfSwicmVxdWVzdF9hdXRoZW50aWNhdGlvbl9zaWduaW5nX2FsZ192YWx1ZXNfc3VwcG9ydGVkIjpbIlJTMjU2Il19fSwiYXV0aG9yaXR5X2hpbnRzIjpbImh0dHBzOi8vZWR1Z2Fpbi5vcmcvIiwiaHR0cHM6Ly84Mi1kYXAubG9jYWxob3N0Lm1hcmtvaXZhbmNpYy5mcm9tLmhyL3NpbXBsZXNhbWxwaHAvc2ltcGxlc2FtbHBocC0yLjIvbW9kdWxlLnBocC9vaWRjLyJdfQ.QOC5hPVzoGe5jJ4o_TkYMyRPWyd7HxqD4flduSAKhF1MIVRkBxgDfV3G1obJd875MsCq_Syb9wZfTP544-nY0z6ulSZm1L08ymzSlWwltcDW-l8rSjuCXErX5UDFNzBwc8ht7F7FfWpNCHrn6-A6t-m5E588IueGZfCqQrKUHRzsObQ8ZCNCkU_hjXgkM-FyERu2_Dnle9wpQ1GszOpNAJAuyMUfissgkokBRrXWwvDbj_7yA8prhgoLhOqtqf_ljMXlx_RggWknd-3zqvBi3U3msHwNnBCQ25E_TH7V_2onASfVOjr2TxyZ5diSkBqoSU9Vqr3bmH3cmcFodu_mvg'; -// $es = $this->federation->entityStatementFetcher()->fromNetwork('https://82-dap.localhost.markoivancic.from.hr/simplesamlphp/simplesamlphp-2.2/module.php/oidc/.well-known/openid-federation'); -// $es = $this->federation->entityStatementFetcher()->fromNetwork('https://maiv1.incubator.geant.org/.well-known/openid-federation'); -// dd($es->getPayload(), $es->verifyWithKeySet()); - -// $this->federationCache->cache->clear(); - //$this->protocolCache->set('value', 10, 'test'); - //dd($this->protocolCache, $this->protocolCache->get(null, 'test')); - - -// $requestObjectFactory = (new Core())->requestObjectFactory(); - - // {"alg":"none"}, {"iss":"joe", - // "exp":1300819380, - // "http://example.com/is_root":true} -// $unprotectedJws = 'eyJhbGciOiJub25lIn0.' . -// 'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.'; - -// $requestObject = $requestObjectFactory->fromToken($unprotectedJws); - -// dd($requestObject, $requestObject->getPayload(), $requestObject->getHeader()); - $this->federationCache?->cache->clear(); - - $trustChain = $this->federation - ->trustChainResolver() - ->for( - 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ALeaf/', -// 'https://trust-anchor.testbed.oidcfed.incubator.geant.org/oidc/rp/', -// 'https://relying-party-php.testbed.oidcfed.incubator.geant.org/', -// 'https://gorp.testbed.oidcfed.incubator.geant.org', -// 'https://maiv1.incubator.geant.org', - [ -// 'https://trust-anchor.testbed.oidcfed.incubator.geant.org/', - 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ABTrustAnchor/', -// 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/CTrustAnchor/', - ], - ) - //->getAll(); - ->getShortestByTrustAnchorPriority( - 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ABTrustAnchor/', - ); - - $leaf = $trustChain->getResolvedLeaf(); - $trustAnchor = $trustChain->getResolvedTrustAnchor(); - - $this->federationParticipationValidator->validateForAllOfLimit( - ['https://08-dap.localhost.markoivancic.from.hr/openid/entities/ATrustMarkIssuer/trust-mark/member'], - $leaf, - $trustAnchor, - ); - - dd($leaf->getPayload()); - - $this->federation->trustMarkValidator()->fromCacheOrDoForTrustMarkId( - 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ATrustMarkIssuer/trust-mark/member', - $leaf, - $trustAnchor, - ); - -// $leafFederationJwks = $leaf->getJwks(); -// dd($leafFederationJwks); -// /** @psalm-suppress PossiblyNullArgument */ - $resolvedMetadata = $trustChain->getResolvedMetadata(EntityTypesEnum::OpenIdRelyingParty); - $clientEntity = $this->clientEntityFactory->fromRegistrationData( - $resolvedMetadata, - RegistrationTypeEnum::FederatedAutomatic, - ); -// dd($resolvedMetadata, $clientEntity); - $jwksUri = $resolvedMetadata['jwks_uri'] ?? null; - $signedJwksUri = $resolvedMetadata['signed_jwks_uri'] ?? null; -// dd($leaf, $leafFederationJwks, $resolvedMetadata, $jwksUri, $signedJwksUri); -// $cachedJwks = $jwksUri ? $this->jwks->jwksFetcher()->fromCache($jwksUri) : null; -// $jwks = $jwksUri ? $this->jwks->jwksFetcher()->fromJwksUri($jwksUri) : null; - -//$leafFederationJwks = [ -// 'keys' => -// [ -// 0 => -// [ -// 'alg' => 'RS256', -// 'use' => 'sig', -// 'kty' => 'RSA', -// 'n' => 'pJgG9F_lwc2cFEC1l6q0fjJYxKPbtVGqJpDggDpDR8MgfbH0jUZP_RvhJGpl_09Bp-PfibLiwxchHZlrCx-fHQyGMaBRivUfq_p12ECEXMaFUcasCP6cyNrDfa5Uchumau4WeC21nYI1NMawiMiWFcHpLCQ7Ul8NMaCM_dkeruhm_xG0ZCqfwu30jOyCsnZdE0izJwPTfBRLpLyivu8eHpwjoIzmwqo8H-ZsbqR0vdRu20-MNS78ppTxwK3QmJhU6VO2r730F6WH9xJd_XUDuVeM4_6Z6WVDXw3kQF-jlpfcssPP303nbqVmfFZSUgS8buToErpMqevMIKREShsjMQ', -// 'e' => 'AQAB', -// 'kid' => 'F4VFObNusj3PHmrHxpqh4GNiuFHlfh-2s6xMJ95fLYA', -// ], -// ], -//]; -// $signedJwksUri = 'https://08-dap.localhost.markoivancic.from.hr/openid/entities/ALeaf/signed-jwks'; - $signedJwks = $signedJwksUri ? $this->jwks->jwksFetcher() - ->fromSignedJwksUri($signedJwksUri, $leafFederationJwks) : null; - $cachedSignedJwks = $signedJwksUri ? $this->jwks->jwksFetcher()->fromCache($signedJwksUri) : null; - dd($signedJwksUri, $cachedSignedJwks, $signedJwks); -// dd( -// $signedJwksUri, -// $cachedSignedJwks, -// $signedJwks, -// ); - - return new JsonResponse( - $trustChain->getResolvedMetadata(EntityTypesEnum::OpenIdRelyingParty), - ); - } -} From 5b77e8674dc06463c9f2072d121a84d649734d43 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Mon, 10 Feb 2025 15:24:22 +0100 Subject: [PATCH 58/70] Move to Str helper in ClientForm --- src/Factories/FormFactory.php | 17 ++++++++++++++--- src/Forms/ClientForm.php | 20 +++++--------------- src/Services/Container.php | 18 ++++++++++++------ tests/unit/src/Forms/ClientFormTest.php | 13 ++++++++++--- 4 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/Factories/FormFactory.php b/src/Factories/FormFactory.php index 4a164d74..13e9c3b2 100644 --- a/src/Factories/FormFactory.php +++ b/src/Factories/FormFactory.php @@ -18,13 +18,19 @@ use Nette\Forms\Form; use SimpleSAML\Error\Exception; +use SimpleSAML\Module\oidc\Bridges\SspBridge; use SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; class FormFactory { - public function __construct(private readonly ModuleConfig $moduleConfig, protected CsrfProtection $csrfProtection) - { + public function __construct( + protected readonly ModuleConfig $moduleConfig, + protected readonly CsrfProtection $csrfProtection, + protected readonly SspBridge $sspBridge, + protected readonly Helpers $helpers, + ) { } /** @@ -39,6 +45,11 @@ public function build(string $classname): Form } /** @psalm-suppress UnsafeInstantiation */ - return new $classname($this->moduleConfig, $this->csrfProtection); + return new $classname( + $this->moduleConfig, + $this->csrfProtection, + $this->sspBridge, + $this->helpers, + ); } } diff --git a/src/Forms/ClientForm.php b/src/Forms/ClientForm.php index f4afb751..05f54d3b 100644 --- a/src/Forms/ClientForm.php +++ b/src/Forms/ClientForm.php @@ -20,6 +20,7 @@ use SimpleSAML\Locale\Translate; use SimpleSAML\Module\oidc\Bridges\SspBridge; use SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\OpenID\Codebooks\ClientRegistrationTypesEnum; use Traversable; @@ -62,6 +63,7 @@ public function __construct( protected readonly ModuleConfig $moduleConfig, protected CsrfProtection $csrfProtection, protected SspBridge $sspBridge, + protected Helpers $helpers, ) { parent::__construct(); @@ -217,14 +219,14 @@ public function getValues(string|object|bool|null $returnType = null, ?array $co $values = parent::getValues(self::TYPE_ARRAY); // Sanitize redirect_uri and allowed_origin - $values['redirect_uri'] = $this->convertTextToArrayWithLinesAsValues((string)$values['redirect_uri']); + $values['redirect_uri'] = $this->helpers->str()->convertTextToArray((string)$values['redirect_uri']); if (! $values['is_confidential'] && isset($values['allowed_origin'])) { - $values['allowed_origin'] = $this->convertTextToArrayWithLinesAsValues((string)$values['allowed_origin']); + $values['allowed_origin'] = $this->helpers->str()->convertTextToArray((string)$values['allowed_origin']); } else { $values['allowed_origin'] = []; } $values['post_logout_redirect_uri'] = - $this->convertTextToArrayWithLinesAsValues((string)$values['post_logout_redirect_uri']); + $this->helpers->str()->convertTextToArray((string)$values['post_logout_redirect_uri']); $bclUri = trim((string)$values['backchannel_logout_uri']); $values['backchannel_logout_uri'] = empty($bclUri) ? null : $bclUri; @@ -423,18 +425,6 @@ protected function getScopes(): array ); } - /** - * TODO mivanci Move to Str helper. - * @return string[] - */ - protected function convertTextToArrayWithLinesAsValues(string $text): array - { - return array_filter( - preg_split("/[\t\r\n]+/", $text), - fn(string $line): bool => !empty(trim($line)), - ); - } - /** * @return string[] */ diff --git a/src/Services/Container.php b/src/Services/Container.php index 78f57483..f73855de 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -137,8 +137,19 @@ public function __construct() $session = Session::getSessionFromRequest(); $this->services[Session::class] = $session; + $sspBridge = new SspBridge(); + $this->services[SspBridge::class] = $sspBridge; + + $helpers = new Helpers(); + $this->services[Helpers::class] = $helpers; + $csrfProtection = new CsrfProtection('{oidc:client:csrf_error}', $session); - $formFactory = new FormFactory($moduleConfig, $csrfProtection); + $formFactory = new FormFactory( + $moduleConfig, + $csrfProtection, + $sspBridge, + $helpers, + ); $this->services[FormFactory::class] = $formFactory; $jsonWebKeySetService = new JsonWebKeySetService($moduleConfig); @@ -150,9 +161,6 @@ public function __construct() $sessionMessagesService = new SessionMessagesService($session); $this->services[SessionMessagesService::class] = $sessionMessagesService; - $sspBridge = new SspBridge(); - $this->services[SspBridge::class] = $sspBridge; - $oidcMenu = new Menu(); $this->services[Menu::class] = $oidcMenu; @@ -193,8 +201,6 @@ public function __construct() $stateService = new StateService(); $this->services[StateService::class] = $stateService; - $helpers = new Helpers(); - $core = new Core(); $this->services[Core::class] = $core; $classInstanceBuilder = new ClassInstanceBuilder(); diff --git a/tests/unit/src/Forms/ClientFormTest.php b/tests/unit/src/Forms/ClientFormTest.php index fbe200dc..e8f1f944 100644 --- a/tests/unit/src/Forms/ClientFormTest.php +++ b/tests/unit/src/Forms/ClientFormTest.php @@ -6,19 +6,21 @@ use DateTimeImmutable; use Laminas\Diactoros\ServerRequest; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Bridges\SspBridge; use SimpleSAML\Module\oidc\Codebooks\RegistrationTypeEnum; use SimpleSAML\Module\oidc\Forms\ClientForm; use SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; -/** - * @covers \SimpleSAML\Module\oidc\Forms\ClientForm - */ +#[CoversClass(ClientForm::class)] +#[UsesClass(Helpers::class)] class ClientFormTest extends TestCase { protected MockObject $csrfProtectionMock; @@ -29,6 +31,7 @@ class ClientFormTest extends TestCase protected MockObject $sspBridgeMock; protected MockObject $sspBridgeAuthMock; protected MockObject $sspBridgeAuthSourceMock; + protected Helpers $helpers; protected array $clientDataSample; @@ -42,6 +45,7 @@ public function setUp(): void $this->moduleConfigMock = $this->createMock(ModuleConfig::class); $this->serverRequestMock = $this->createMock(ServerRequest::class); $this->sspBridgeMock = $this->createMock(SspBridge::class); + $this->helpers = new Helpers(); $this->sspBridgeAuthMock = $this->createMock(SspBridge\Auth::class); $this->sspBridgeMock->method('auth')->willReturn($this->sspBridgeAuthMock); @@ -85,15 +89,18 @@ protected function sut( ?ModuleConfig $moduleConfig = null, ?CsrfProtection $csrfProtection = null, ?SspBridge $sspBridge = null, + ?Helpers $helpers = null, ): ClientForm { $moduleConfig ??= $this->moduleConfigMock; $csrfProtection ??= $this->csrfProtectionMock; $sspBridge ??= $this->sspBridgeMock; + $helpers ??= $this->helpers; return new ClientForm( $moduleConfig, $csrfProtection, $sspBridge, + $helpers, ); } From 3a7f255c2fa6ee89a9d4d7d71adc31afc5f7af09 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Mon, 10 Feb 2025 16:04:03 +0100 Subject: [PATCH 59/70] Add Helpers to Request Rules --- src/Controllers/Admin/ClientController.php | 2 +- .../Federation/EntityStatementController.php | 8 +-- src/Factories/RequestRulesManagerFactory.php | 61 ++++++++++++------- .../RequestRules/Rules/AbstractRule.php | 7 ++- .../Rules/ClientAuthenticationRule.php | 4 +- .../RequestRules/Rules/ClientIdRule.php | 8 +-- .../Rules/CodeChallengeMethodRule.php | 4 +- .../RequestRules/Rules/IdTokenHintRule.php | 6 +- src/Server/RequestRules/Rules/MaxAgeRule.php | 4 +- .../Rules/PostLogoutRedirectUriRule.php | 4 +- src/Server/RequestRules/Rules/PromptRule.php | 4 +- .../RequestRules/Rules/RequestObjectRule.php | 4 +- .../Rules/RequestedClaimsRule.php | 4 +- src/Server/RequestRules/Rules/ScopeRule.php | 4 +- src/Services/Container.php | 42 ++++++------- .../RequestRules/Rules/AcrValuesRuleTest.php | 23 +++++-- .../Rules/AddClaimsToIdTokenRuleTest.php | 23 +++++-- .../RequestRules/Rules/ClientIdRuleTest.php | 2 +- .../Rules/CodeChallengeMethodRuleTest.php | 27 +++++--- .../Rules/CodeChallengeRuleTest.php | 27 +++++--- .../Rules/IdTokenHintRuleTest.php | 37 +++++++---- .../Rules/PostLogoutRedirectUriRuleTest.php | 32 +++++++--- .../Rules/RedirectUriRuleTest.php | 29 ++++++--- .../Rules/RequestObjectRuleTest.php | 31 +++++++--- .../Rules/RequestedClaimsRuleTest.php | 26 +++++--- .../Rules/RequiredNonceRuleTest.php | 25 +++++--- .../Rules/RequiredOpenIdScopeRuleTest.php | 23 ++++--- .../Rules/ResponseTypeRuleTest.php | 21 +++++-- .../Rules/ScopeOfflineAccessRuleTest.php | 26 +++++--- .../RequestRules/Rules/ScopeRuleTest.php | 27 +++++--- .../RequestRules/Rules/StateRuleTest.php | 23 +++++-- .../RequestRules/Rules/UiLocalesRuleTest.php | 21 +++++-- 32 files changed, 401 insertions(+), 188 deletions(-) diff --git a/src/Controllers/Admin/ClientController.php b/src/Controllers/Admin/ClientController.php index 903a9712..ec0f2c74 100644 --- a/src/Controllers/Admin/ClientController.php +++ b/src/Controllers/Admin/ClientController.php @@ -299,7 +299,7 @@ public function edit(Request $request): Response } /** - * TODO mivanci Move to ClientEntityFactory::fromRegistrationData on dynamic client registration implementation. + * TODO v7 mivanci Move to ClientEntityFactory::fromRegistrationData on dynamic client registration implementation. * @throws \SimpleSAML\Module\oidc\Exceptions\OidcException */ protected function buildClientEntityFromFormData( diff --git a/src/Controllers/Federation/EntityStatementController.php b/src/Controllers/Federation/EntityStatementController.php index 25419f5b..cf7cee86 100644 --- a/src/Controllers/Federation/EntityStatementController.php +++ b/src/Controllers/Federation/EntityStatementController.php @@ -95,7 +95,7 @@ public function configuration(): Response )), ClaimsEnum::FederationFetchEndpoint->value => $this->moduleConfig->getModuleUrl(RoutesEnum::FederationFetch->value), - // TODO mivanci Add when ready. Use ClaimsEnum for keys. + // TODO v7 mivanci Add when ready. Use ClaimsEnum for keys. // https://openid.net/specs/openid-federation-1_0.html#name-federation-entity //'federation_list_endpoint', //'federation_resolve_endpoint', @@ -149,7 +149,7 @@ public function configuration(): Response $builder = $builder->withClaim(ClaimsEnum::TrustMarks->value, $trustMarks); } - // TODO mivanci Continue + // TODO v7 mivanci Continue // Remaining claims, add if / when ready. // * crit @@ -235,14 +235,14 @@ public function fetch(Request $request): Response ClaimsEnum::PostLogoutRedirectUris->value => $client->getPostLogoutRedirectUri(), ], )), - // TODO mivanci Continue + // TODO v7 mivanci Continue // https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata // https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#client-metadata ], ], ); - // TODO mivanci Continue + // TODO v7 mivanci Continue // Note: claims which can be present in subordinate statements: // * metadata_policy // * constraints diff --git a/src/Factories/RequestRulesManagerFactory.php b/src/Factories/RequestRulesManagerFactory.php index 8aa57b32..ebcae807 100644 --- a/src/Factories/RequestRulesManagerFactory.php +++ b/src/Factories/RequestRulesManagerFactory.php @@ -81,43 +81,62 @@ public function build(?array $rules = null): RequestRulesManager private function getDefaultRules(): array { return [ - new StateRule($this->requestParamsResolver), + new StateRule($this->requestParamsResolver, $this->helpers), new ClientIdRule( $this->requestParamsResolver, + $this->helpers, $this->clientRepository, $this->moduleConfig, $this->clientEntityFactory, $this->federation, - $this->helpers, $this->jwksResolver, $this->federationParticipationValidator, $this->federationCache, ), - new RedirectUriRule($this->requestParamsResolver), - new RequestObjectRule($this->requestParamsResolver, $this->jwksResolver), - new PromptRule($this->requestParamsResolver, $this->authSimpleFactory, $this->authenticationService), - new MaxAgeRule($this->requestParamsResolver, $this->authSimpleFactory, $this->authenticationService), - new ScopeRule($this->requestParamsResolver, $this->scopeRepository, $this->helpers), - new RequiredOpenIdScopeRule($this->requestParamsResolver), - new CodeChallengeRule($this->requestParamsResolver), - new CodeChallengeMethodRule($this->requestParamsResolver, $this->codeChallengeVerifiersRepository), - new RequestedClaimsRule($this->requestParamsResolver, $this->claimTranslatorExtractor), - new AddClaimsToIdTokenRule($this->requestParamsResolver), - new RequiredNonceRule($this->requestParamsResolver), - new ResponseTypeRule($this->requestParamsResolver), - new IdTokenHintRule($this->requestParamsResolver, $this->moduleConfig, $this->cryptKeyFactory), - new PostLogoutRedirectUriRule($this->requestParamsResolver, $this->clientRepository), - new UiLocalesRule($this->requestParamsResolver), - new AcrValuesRule($this->requestParamsResolver), - new ScopeOfflineAccessRule($this->requestParamsResolver), + new RedirectUriRule($this->requestParamsResolver, $this->helpers), + new RequestObjectRule($this->requestParamsResolver, $this->helpers, $this->jwksResolver), + new PromptRule( + $this->requestParamsResolver, + $this->helpers, + $this->authSimpleFactory, + $this->authenticationService, + ), + new MaxAgeRule( + $this->requestParamsResolver, + $this->helpers, + $this->authSimpleFactory, + $this->authenticationService, + ), + new ScopeRule($this->requestParamsResolver, $this->helpers, $this->scopeRepository), + new RequiredOpenIdScopeRule($this->requestParamsResolver, $this->helpers), + new CodeChallengeRule($this->requestParamsResolver, $this->helpers), + new CodeChallengeMethodRule( + $this->requestParamsResolver, + $this->helpers, + $this->codeChallengeVerifiersRepository, + ), + new RequestedClaimsRule($this->requestParamsResolver, $this->helpers, $this->claimTranslatorExtractor), + new AddClaimsToIdTokenRule($this->requestParamsResolver, $this->helpers), + new RequiredNonceRule($this->requestParamsResolver, $this->helpers), + new ResponseTypeRule($this->requestParamsResolver, $this->helpers), + new IdTokenHintRule( + $this->requestParamsResolver, + $this->helpers, + $this->moduleConfig, + $this->cryptKeyFactory, + ), + new PostLogoutRedirectUriRule($this->requestParamsResolver, $this->helpers, $this->clientRepository), + new UiLocalesRule($this->requestParamsResolver, $this->helpers), + new AcrValuesRule($this->requestParamsResolver, $this->helpers), + new ScopeOfflineAccessRule($this->requestParamsResolver, $this->helpers), new ClientAuthenticationRule( $this->requestParamsResolver, + $this->helpers, $this->moduleConfig, $this->jwksResolver, - $this->helpers, $this->protocolCache, ), - new CodeVerifierRule($this->requestParamsResolver), + new CodeVerifierRule($this->requestParamsResolver, $this->helpers), ]; } } diff --git a/src/Server/RequestRules/Rules/AbstractRule.php b/src/Server/RequestRules/Rules/AbstractRule.php index e9ba45ac..3882cdf9 100644 --- a/src/Server/RequestRules/Rules/AbstractRule.php +++ b/src/Server/RequestRules/Rules/AbstractRule.php @@ -4,13 +4,16 @@ namespace SimpleSAML\Module\oidc\Server\RequestRules\Rules; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\RequestRuleInterface; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; abstract class AbstractRule implements RequestRuleInterface { - public function __construct(protected RequestParamsResolver $requestParamsResolver) - { + public function __construct( + protected RequestParamsResolver $requestParamsResolver, + protected Helpers $helpers, + ) { } /** diff --git a/src/Server/RequestRules/Rules/ClientAuthenticationRule.php b/src/Server/RequestRules/Rules/ClientAuthenticationRule.php index cb92a1ce..62b522ca 100644 --- a/src/Server/RequestRules/Rules/ClientAuthenticationRule.php +++ b/src/Server/RequestRules/Rules/ClientAuthenticationRule.php @@ -26,12 +26,12 @@ class ClientAuthenticationRule extends AbstractRule public function __construct( RequestParamsResolver $requestParamsResolver, + Helpers $helpers, protected ModuleConfig $moduleConfig, protected JwksResolver $jwksResolver, - protected Helpers $helpers, protected ?ProtocolCache $protocolCache, ) { - parent::__construct($requestParamsResolver); + parent::__construct($requestParamsResolver, $helpers); } /** diff --git a/src/Server/RequestRules/Rules/ClientIdRule.php b/src/Server/RequestRules/Rules/ClientIdRule.php index 9905cf82..b377377f 100644 --- a/src/Server/RequestRules/Rules/ClientIdRule.php +++ b/src/Server/RequestRules/Rules/ClientIdRule.php @@ -34,16 +34,16 @@ class ClientIdRule extends AbstractRule public function __construct( RequestParamsResolver $requestParamsResolver, + Helpers $helpers, protected ClientRepository $clientRepository, protected ModuleConfig $moduleConfig, protected ClientEntityFactory $clientEntityFactory, protected Federation $federation, - protected Helpers $helpers, protected JwksResolver $jwksResolver, protected FederationParticipationValidator $federationParticipationValidator, protected ?FederationCache $federationCache = null, ) { - parent::__construct($requestParamsResolver); + parent::__construct($requestParamsResolver, $helpers); } /** @@ -132,8 +132,8 @@ public function checkRule( throw OidcServerException::invalidRequest(ParamsEnum::Request->value, 'Client ID is not valid URI.'); // We are ready to resolve trust chain. - // TODO mivanci Request Object can contain trust_chain claim, so also implement resolving using that claim. Note - // that this is only possible if we have JWKS configured for common TA, so we can check TA Configuration + // TODO mivanci v7 Request Object can contain trust_chain claim, so also implement resolving using that claim. + // Note that this is only possible if we have JWKS configured for common TA, so we can check TA Configuration // signature. try { $trustChain = $this->federation->trustChainResolver()->for( diff --git a/src/Server/RequestRules/Rules/CodeChallengeMethodRule.php b/src/Server/RequestRules/Rules/CodeChallengeMethodRule.php index 5f69a134..ed087d3e 100644 --- a/src/Server/RequestRules/Rules/CodeChallengeMethodRule.php +++ b/src/Server/RequestRules/Rules/CodeChallengeMethodRule.php @@ -5,6 +5,7 @@ namespace SimpleSAML\Module\oidc\Server\RequestRules\Rules; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Repositories\CodeChallengeVerifiersRepository; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; @@ -19,9 +20,10 @@ class CodeChallengeMethodRule extends AbstractRule { public function __construct( RequestParamsResolver $requestParamsResolver, + Helpers $helpers, protected CodeChallengeVerifiersRepository $codeChallengeVerifiersRepository, ) { - parent::__construct($requestParamsResolver); + parent::__construct($requestParamsResolver, $helpers); } /** diff --git a/src/Server/RequestRules/Rules/IdTokenHintRule.php b/src/Server/RequestRules/Rules/IdTokenHintRule.php index 97a77575..c1160b01 100644 --- a/src/Server/RequestRules/Rules/IdTokenHintRule.php +++ b/src/Server/RequestRules/Rules/IdTokenHintRule.php @@ -10,6 +10,7 @@ use Lcobucci\JWT\Validation\Constraint\SignedWith; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Factories\CryptKeyFactory; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; @@ -25,10 +26,11 @@ class IdTokenHintRule extends AbstractRule { public function __construct( RequestParamsResolver $requestParamsResolver, + Helpers $helpers, protected ModuleConfig $moduleConfig, protected CryptKeyFactory $cryptKeyFactory, ) { - parent::__construct($requestParamsResolver); + parent::__construct($requestParamsResolver, $helpers); } /** @@ -56,7 +58,7 @@ public function checkRule( return new Result($this->getKey(), $idTokenHintParam); } - // TODO mivanci Fix: unmockable services... inject instead. + // TODO v7 mivanci Fix: unmockable services... inject instead. $privateKey = $this->cryptKeyFactory->buildPrivateKey(); $publicKey = $this->cryptKeyFactory->buildPublicKey(); /** @psalm-suppress ArgumentTypeCoercion */ diff --git a/src/Server/RequestRules/Rules/MaxAgeRule.php b/src/Server/RequestRules/Rules/MaxAgeRule.php index 695c9f50..5742e431 100644 --- a/src/Server/RequestRules/Rules/MaxAgeRule.php +++ b/src/Server/RequestRules/Rules/MaxAgeRule.php @@ -6,6 +6,7 @@ use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; @@ -21,10 +22,11 @@ class MaxAgeRule extends AbstractRule { public function __construct( RequestParamsResolver $requestParamsResolver, + Helpers $helpers, private readonly AuthSimpleFactory $authSimpleFactory, private readonly AuthenticationService $authenticationService, ) { - parent::__construct($requestParamsResolver); + parent::__construct($requestParamsResolver, $helpers); } /** diff --git a/src/Server/RequestRules/Rules/PostLogoutRedirectUriRule.php b/src/Server/RequestRules/Rules/PostLogoutRedirectUriRule.php index db0c13fa..d27dace8 100644 --- a/src/Server/RequestRules/Rules/PostLogoutRedirectUriRule.php +++ b/src/Server/RequestRules/Rules/PostLogoutRedirectUriRule.php @@ -5,6 +5,7 @@ namespace SimpleSAML\Module\oidc\Server\RequestRules\Rules; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Repositories\ClientRepository; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; @@ -19,9 +20,10 @@ class PostLogoutRedirectUriRule extends AbstractRule { public function __construct( RequestParamsResolver $requestParamsResolver, + Helpers $helpers, protected ClientRepository $clientRepository, ) { - parent::__construct($requestParamsResolver); + parent::__construct($requestParamsResolver, $helpers); } /** diff --git a/src/Server/RequestRules/Rules/PromptRule.php b/src/Server/RequestRules/Rules/PromptRule.php index de8137b6..b2311bbd 100644 --- a/src/Server/RequestRules/Rules/PromptRule.php +++ b/src/Server/RequestRules/Rules/PromptRule.php @@ -7,6 +7,7 @@ use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; @@ -21,10 +22,11 @@ class PromptRule extends AbstractRule { public function __construct( RequestParamsResolver $requestParamsResolver, + Helpers $helpers, private readonly AuthSimpleFactory $authSimpleFactory, private readonly AuthenticationService $authenticationService, ) { - parent::__construct($requestParamsResolver); + parent::__construct($requestParamsResolver, $helpers); } /** diff --git a/src/Server/RequestRules/Rules/RequestObjectRule.php b/src/Server/RequestRules/Rules/RequestObjectRule.php index 944bb19e..a1f74a24 100644 --- a/src/Server/RequestRules/Rules/RequestObjectRule.php +++ b/src/Server/RequestRules/Rules/RequestObjectRule.php @@ -5,6 +5,7 @@ namespace SimpleSAML\Module\oidc\Server\RequestRules\Rules; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; @@ -19,9 +20,10 @@ class RequestObjectRule extends AbstractRule { public function __construct( RequestParamsResolver $requestParamsResolver, + Helpers $helpers, protected JwksResolver $jwksResolver, ) { - parent::__construct($requestParamsResolver); + parent::__construct($requestParamsResolver, $helpers); } /** diff --git a/src/Server/RequestRules/Rules/RequestedClaimsRule.php b/src/Server/RequestRules/Rules/RequestedClaimsRule.php index fee6c21c..d8b27970 100644 --- a/src/Server/RequestRules/Rules/RequestedClaimsRule.php +++ b/src/Server/RequestRules/Rules/RequestedClaimsRule.php @@ -5,6 +5,7 @@ namespace SimpleSAML\Module\oidc\Server\RequestRules\Rules; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; @@ -18,9 +19,10 @@ class RequestedClaimsRule extends AbstractRule { public function __construct( RequestParamsResolver $requestParamsResolver, + Helpers $helpers, private readonly ClaimTranslatorExtractor $claimExtractor, ) { - parent::__construct($requestParamsResolver); + parent::__construct($requestParamsResolver, $helpers); } diff --git a/src/Server/RequestRules/Rules/ScopeRule.php b/src/Server/RequestRules/Rules/ScopeRule.php index 4ad66244..e1eb7884 100644 --- a/src/Server/RequestRules/Rules/ScopeRule.php +++ b/src/Server/RequestRules/Rules/ScopeRule.php @@ -21,10 +21,10 @@ class ScopeRule extends AbstractRule { public function __construct( RequestParamsResolver $requestParamsResolver, + Helpers $helpers, protected ScopeRepositoryInterface $scopeRepository, - protected Helpers $helpers, ) { - parent::__construct($requestParamsResolver); + parent::__construct($requestParamsResolver, $helpers); } /** diff --git a/src/Services/Container.php b/src/Services/Container.php index f73855de..b43bb9ea 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -356,43 +356,43 @@ public function __construct() $this->services[FederationParticipationValidator::class] = $federationParticipationValidator; $requestRules = [ - new StateRule($requestParamsResolver), + new StateRule($requestParamsResolver, $helpers), new ClientIdRule( $requestParamsResolver, + $helpers, $clientRepository, $moduleConfig, $clientEntityFactory, $federation, - $helpers, $jwksResolver, $federationParticipationValidator, $federationCache, ), - new RedirectUriRule($requestParamsResolver), - new RequestObjectRule($requestParamsResolver, $jwksResolver), - new PromptRule($requestParamsResolver, $authSimpleFactory, $authenticationService), - new MaxAgeRule($requestParamsResolver, $authSimpleFactory, $authenticationService), - new ScopeRule($requestParamsResolver, $scopeRepository, $helpers), - new RequiredOpenIdScopeRule($requestParamsResolver), - new CodeChallengeRule($requestParamsResolver), - new CodeChallengeMethodRule($requestParamsResolver, $codeChallengeVerifiersRepository), - new RequestedClaimsRule($requestParamsResolver, $claimTranslatorExtractor), - new AddClaimsToIdTokenRule($requestParamsResolver), - new RequiredNonceRule($requestParamsResolver), - new ResponseTypeRule($requestParamsResolver), - new IdTokenHintRule($requestParamsResolver, $moduleConfig, $cryptKeyFactory), - new PostLogoutRedirectUriRule($requestParamsResolver, $clientRepository), - new UiLocalesRule($requestParamsResolver), - new AcrValuesRule($requestParamsResolver), - new ScopeOfflineAccessRule($requestParamsResolver), + new RedirectUriRule($requestParamsResolver, $helpers), + new RequestObjectRule($requestParamsResolver, $helpers, $jwksResolver), + new PromptRule($requestParamsResolver, $helpers, $authSimpleFactory, $authenticationService), + new MaxAgeRule($requestParamsResolver, $helpers, $authSimpleFactory, $authenticationService), + new ScopeRule($requestParamsResolver, $helpers, $scopeRepository), + new RequiredOpenIdScopeRule($requestParamsResolver, $helpers), + new CodeChallengeRule($requestParamsResolver, $helpers), + new CodeChallengeMethodRule($requestParamsResolver, $helpers, $codeChallengeVerifiersRepository), + new RequestedClaimsRule($requestParamsResolver, $helpers, $claimTranslatorExtractor), + new AddClaimsToIdTokenRule($requestParamsResolver, $helpers), + new RequiredNonceRule($requestParamsResolver, $helpers), + new ResponseTypeRule($requestParamsResolver, $helpers), + new IdTokenHintRule($requestParamsResolver, $helpers, $moduleConfig, $cryptKeyFactory), + new PostLogoutRedirectUriRule($requestParamsResolver, $helpers, $clientRepository), + new UiLocalesRule($requestParamsResolver, $helpers), + new AcrValuesRule($requestParamsResolver, $helpers), + new ScopeOfflineAccessRule($requestParamsResolver, $helpers), new ClientAuthenticationRule( $requestParamsResolver, + $helpers, $moduleConfig, $jwksResolver, - $helpers, $protocolCache, ), - new CodeVerifierRule($requestParamsResolver), + new CodeVerifierRule($requestParamsResolver, $helpers), ]; $requestRuleManager = new RequestRulesManager($requestRules, $loggerService); $this->services[RequestRulesManager::class] = $requestRuleManager; diff --git a/tests/unit/src/Server/RequestRules/Rules/AcrValuesRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/AcrValuesRuleTest.php index 3bc4eea0..302eaa30 100644 --- a/tests/unit/src/Server/RequestRules/Rules/AcrValuesRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/AcrValuesRuleTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; @@ -24,6 +25,7 @@ class AcrValuesRuleTest extends TestCase protected Stub $resultStub; protected Stub $loggerServiceStub; protected Stub $requestParamsResolverStub; + protected Helpers $helpers; /** * @throws \Exception @@ -35,11 +37,20 @@ protected function setUp(): void $this->resultStub = $this->createStub(ResultInterface::class); $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); + $this->helpers = new Helpers(); } - protected function mock(): AcrValuesRule - { - return new AcrValuesRule($this->requestParamsResolverStub); + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ): AcrValuesRule { + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpers; + + return new AcrValuesRule( + $requestParamsResolver, + $helpers, + ); } /** @@ -47,7 +58,7 @@ protected function mock(): AcrValuesRule */ public function testNoAcrIsSetIfAcrValuesNotRequested(): void { - $result = $this->mock()->checkRule( + $result = $this->sut()->checkRule( $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, @@ -64,7 +75,7 @@ public function testPopulatesAcrValuesFromClaimsParameter(): void $this->resultStub->method('getValue')->willReturn($claims); $this->resultBagStub->method('get')->willReturn($this->resultStub); - $result = $this->mock()->checkRule( + $result = $this->sut()->checkRule( $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, @@ -81,7 +92,7 @@ public function testPopulatesAcrValuesFromAcrValuesRequestParameter(): void { $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn('1 0'); - $result = $this->mock()->checkRule( + $result = $this->sut()->checkRule( $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, diff --git a/tests/unit/src/Server/RequestRules/Rules/AddClaimsToIdTokenRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/AddClaimsToIdTokenRuleTest.php index 20d8a55e..fb54da45 100644 --- a/tests/unit/src/Server/RequestRules/Rules/AddClaimsToIdTokenRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/AddClaimsToIdTokenRuleTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\RequestRules\Result; use SimpleSAML\Module\oidc\Server\RequestRules\ResultBag; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\AddClaimsToIdTokenRule; @@ -22,6 +23,7 @@ class AddClaimsToIdTokenRuleTest extends TestCase { protected Stub $requestStub; protected Stub $requestParamsResolverStub; + protected Helpers $helpers; protected array $requestParams = [ 'client_id' => 'client123', @@ -57,11 +59,20 @@ protected function setUp(): void $this->resultBag = new ResultBag(); $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); + $this->helpers = new Helpers(); } - protected function mock(): AddClaimsToIdTokenRule - { - return new AddClaimsToIdTokenRule($this->requestParamsResolverStub); + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ): AddClaimsToIdTokenRule { + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpers; + + return new AddClaimsToIdTokenRule( + $requestParamsResolver, + $helpers, + ); } /** @@ -72,7 +83,7 @@ public function testAddClaimsToIdTokenRuleTest($responseType) { $this->resultBag->add(new Result(ResponseTypeRule::class, $responseType)); - $result = $this->mock()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? new Result(AddClaimsToIdTokenRule::class, null); $this->assertTrue($result->getValue()); } @@ -92,7 +103,7 @@ public function testDoNotAddClaimsToIdTokenRuleTest($responseType) { $this->resultBag->add(new Result(ResponseTypeRule::class, $responseType)); - $result = $this->mock()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? new Result(AddClaimsToIdTokenRule::class, null); $this->assertFalse($result->getValue()); @@ -117,6 +128,6 @@ public static function invalidResponseTypeProvider(): array public function testAddClaimsToIdTokenRuleThrowsWithNoResponseTypeParamTest() { $this->expectException(LogicException::class); - $this->mock()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); } } diff --git a/tests/unit/src/Server/RequestRules/Rules/ClientIdRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/ClientIdRuleTest.php index fda3d8ae..9556d8d3 100644 --- a/tests/unit/src/Server/RequestRules/Rules/ClientIdRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/ClientIdRuleTest.php @@ -66,11 +66,11 @@ protected function sut(): ClientIdRule { return new ClientIdRule( $this->requestParamsResolverStub, + $this->helpersStub, $this->clientRepositoryStub, $this->moduleConfigStub, $this->clientEntityFactoryStub, $this->federationStub, - $this->helpersStub, $this->jwksResolverStub, $this->federationParticipationValidatorStub, $this->federationCacheStub, diff --git a/tests/unit/src/Server/RequestRules/Rules/CodeChallengeMethodRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/CodeChallengeMethodRuleTest.php index bd7906ea..4d0217d7 100644 --- a/tests/unit/src/Server/RequestRules/Rules/CodeChallengeMethodRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/CodeChallengeMethodRuleTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Repositories\CodeChallengeVerifiersRepository; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; @@ -34,6 +35,7 @@ class CodeChallengeMethodRuleTest extends TestCase protected Stub $loggerServiceStub; protected Stub $requestParamsResolverStub; protected MockObject $codeChallengeVerifiersRepositoryMock; + protected Helpers $helpers; /** * @throws \Exception */ @@ -46,13 +48,22 @@ protected function setUp(): void $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); $this->codeChallengeVerifiersRepositoryMock = $this->createMock(CodeChallengeVerifiersRepository::class); + $this->helpers = new Helpers(); } - protected function mock(): CodeChallengeMethodRule - { + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ?CodeChallengeVerifiersRepository $codeChallengeVerifiersRepository = null, + ): CodeChallengeMethodRule { + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpers; + $codeChallengeVerifiersRepository ??= $this->codeChallengeVerifiersRepositoryMock; + return new CodeChallengeMethodRule( - $this->requestParamsResolverStub, - $this->codeChallengeVerifiersRepositoryMock, + $requestParamsResolver, + $helpers, + $codeChallengeVerifiersRepository, ); } @@ -64,7 +75,7 @@ public function testCheckRuleRedirectUriDependency(): void { $resultBag = new ResultBag(); $this->expectException(LogicException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -76,7 +87,7 @@ public function testCheckRuleStateDependency(): void $resultBag = new ResultBag(); $resultBag->add($this->redirectUriResult); $this->expectException(LogicException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -89,7 +100,7 @@ public function testCheckRuleWithInvalidCodeChallengeMethodThrows(): void $this->codeChallengeVerifiersRepositoryMock->expects($this->once())->method('has') ->with('invalid')->willReturn(false); $this->expectException(OidcServerException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -102,7 +113,7 @@ public function testCheckRuleForValidCodeChallengeMethod(): void $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn('plain'); $this->codeChallengeVerifiersRepositoryMock->expects($this->once())->method('has') ->with('plain')->willReturn(true); - $result = $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $result = $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); $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 1ace20d8..671badb7 100644 --- a/tests/unit/src/Server/RequestRules/Rules/CodeChallengeRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/CodeChallengeRuleTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; @@ -37,6 +38,7 @@ class CodeChallengeRuleTest extends TestCase protected Stub $requestParamsResolverStub; protected Stub $clientStub; protected Result $clientIdResult; + protected Helpers $helpers; /** * @throws \Exception @@ -51,11 +53,20 @@ protected function setUp(): void $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); $this->clientStub = $this->createStub(ClientEntityInterface::class); $this->clientIdResult = new Result(ClientIdRule::class, $this->clientStub); + $this->helpers = new Helpers(); } - protected function mock(): CodeChallengeRule - { - return new CodeChallengeRule($this->requestParamsResolverStub); + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ): CodeChallengeRule { + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpers; + + return new CodeChallengeRule( + $requestParamsResolver, + $helpers, + ); } /** @@ -66,7 +77,7 @@ public function testCheckRuleRedirectUriDependency(): void { $resultBag = new ResultBag(); $this->expectException(LogicException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -78,7 +89,7 @@ public function testCheckRuleStateDependency(): void $resultBag = new ResultBag(); $resultBag->add($this->redirectUriResult); $this->expectException(LogicException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -89,7 +100,7 @@ public function testCheckRuleNoCodeReturnsNullForConfidentialClients(): void $this->clientStub->method('isConfidential')->willReturn(true); $resultBag = $this->prepareValidResultBag(); $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn(null); - $result = $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $result = $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertNull($result->getValue()); } @@ -102,7 +113,7 @@ public function testCheckRuleInvalidCodeChallengeThrows(): void $resultBag = $this->prepareValidResultBag(); $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn('too-short'); $this->expectException(OidcServerException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -114,7 +125,7 @@ public function testCheckRuleForValidCodeChallenge(): void $resultBag = $this->prepareValidResultBag(); $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn($this->codeChallenge); - $result = $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $result = $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); $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 d07f525e..bee541f9 100644 --- a/tests/unit/src/Server/RequestRules/Rules/IdTokenHintRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/IdTokenHintRuleTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Factories\CryptKeyFactory; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; @@ -42,6 +43,7 @@ class IdTokenHintRuleTest extends TestCase protected Stub $loggerServiceStub; protected Stub $requestParamsResolverStub; + protected Helpers $helpers; public static function setUpBeforeClass(): void { @@ -78,20 +80,33 @@ protected function setUp(): void $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); + + $this->helpers = new Helpers(); } - protected function mock(): IdTokenHintRule - { + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ?ModuleConfig $moduleConfig = null, + ?CryptKeyFactory $cryptKeyFactory = null, + ): IdTokenHintRule { + + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpers; + $moduleConfig ??= $this->moduleConfigStub; + $cryptKeyFactory ??= $this->cryptKeyFactoryStub; + return new IdTokenHintRule( - $this->requestParamsResolverStub, - $this->moduleConfigStub, - $this->cryptKeyFactoryStub, + $requestParamsResolver, + $helpers, + $moduleConfig, + $cryptKeyFactory, ); } public function testConstruct(): void { - $this->assertInstanceOf(IdTokenHintRule::class, $this->mock()); + $this->assertInstanceOf(IdTokenHintRule::class, $this->sut()); } /** @@ -100,7 +115,7 @@ public function testConstruct(): void */ public function testCheckRuleIsNullWhenParamNotSet(): void { - $result = $this->mock()->checkRule( + $result = $this->sut()->checkRule( $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, @@ -116,7 +131,7 @@ public function testCheckRuleThrowsForMalformedIdToken(): void { $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn('malformed'); $this->expectException(Throwable::class); - $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); } /** @@ -133,7 +148,7 @@ public function testCheckRuleThrowsForIdTokenWithInvalidSignature(): void $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn($invalidSignatureJwt); $this->expectException(Throwable::class); - $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); } /** @@ -150,7 +165,7 @@ public function testCheckRuleThrowsForIdTokenWithInvalidIssuer(): void )->toString(); $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn($invalidIssuerJwt); $this->expectException(Throwable::class); - $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); } /** @@ -166,7 +181,7 @@ public function testCheckRulePassesForValidIdToken(): void )->toString(); $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn($idToken); - $result = $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? new Result(IdTokenHintRule::class); $this->assertInstanceOf(UnencryptedToken::class, $result->getValue()); diff --git a/tests/unit/src/Server/RequestRules/Rules/PostLogoutRedirectUriRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/PostLogoutRedirectUriRuleTest.php index 2ffccbc3..b14a3a57 100644 --- a/tests/unit/src/Server/RequestRules/Rules/PostLogoutRedirectUriRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/PostLogoutRedirectUriRuleTest.php @@ -12,6 +12,7 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\ClientRepository; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; @@ -45,6 +46,7 @@ class PostLogoutRedirectUriRuleTest extends TestCase protected Stub $loggerServiceStub; protected Stub $requestParamsResolverStub; + protected Helpers $helpers; public static function setUpBeforeClass(): void { @@ -74,13 +76,23 @@ protected function setUp(): void $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); + + $this->helpers = new Helpers(); } - protected function mock(): PostLogoutRedirectUriRule - { + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ?ClientRepository $clientRepository = null, + ): PostLogoutRedirectUriRule { + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpers; + $clientRepository ??= $this->clientRepositoryStub; + return new PostLogoutRedirectUriRule( - $this->requestParamsResolverStub, - $this->clientRepositoryStub, + $requestParamsResolver, + $helpers, + $clientRepository, ); } @@ -90,7 +102,7 @@ protected function mock(): PostLogoutRedirectUriRule */ public function testCheckRuleReturnsNullIfNoParamSet(): void { - $result = $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? (new Result(PostLogoutRedirectUriRule::class)); $this->assertNull($result->getValue()); @@ -106,7 +118,7 @@ public function testCheckRuleThrowsWhenIdTokenHintNotAvailable(): void $this->expectException(Throwable::class); - $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? (new Result(PostLogoutRedirectUriRule::class)); } @@ -131,7 +143,7 @@ public function testCheckRuleThrowsWhenAudClaimNotValid(): void $this->expectException(Throwable::class); - $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? (new Result(PostLogoutRedirectUriRule::class)); } @@ -159,7 +171,7 @@ public function testCheckRuleThrowsWhenClientNotFound(): void $this->expectException(Throwable::class); - $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? (new Result(PostLogoutRedirectUriRule::class)); } @@ -192,7 +204,7 @@ public function testCheckRuleThrowsWhenPostLogoutRegisteredUriNotRegistered(): v $this->expectException(Throwable::class); - $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? (new Result(PostLogoutRedirectUriRule::class)); } @@ -224,7 +236,7 @@ public function testCheckRuleReturnsForRegisteredPostLogoutRedirectUri(): void new Result(IdTokenHintRule::class, $jwt), ); - $result = $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? (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 5fdb1e71..3650edde 100644 --- a/tests/unit/src/Server/RequestRules/Rules/RedirectUriRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/RedirectUriRuleTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; @@ -30,6 +31,7 @@ class RedirectUriRuleTest extends TestCase protected string $redirectUri = 'https://some-redirect-uri.org'; protected Stub $loggerServiceStub; protected Stub $requestParamsResolverStub; + protected Helpers $helpers; /** @@ -42,11 +44,20 @@ 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(); } - protected function mock(): RedirectUriRule - { - return new RedirectUriRule($this->requestParamsResolverStub); + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ): RedirectUriRule { + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpers; + + return new RedirectUriRule( + $requestParamsResolver, + $helpers, + ); } /** @@ -56,7 +67,7 @@ protected function mock(): RedirectUriRule public function testCheckRuleClientIdDependency(): void { $this->expectException(LogicException::class); - $this->mock()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); } /** @@ -67,7 +78,7 @@ public function testCheckRuleWithInvalidClientDependancy(): void { $this->resultBag->add(new Result(ClientIdRule::class, 'invalid')); $this->expectException(LogicException::class); - $this->mock()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); } /** @@ -78,7 +89,7 @@ public function testCheckRuleRedirectUriNotSetThrows(): void $resultBag = $this->prepareValidResultBag(); $this->expectException(OidcServerException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -90,7 +101,7 @@ public function testCheckRuleDifferentClientRedirectUriThrows(): void $resultBag = $this->prepareValidResultBag(); $this->expectException(OidcServerException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -104,7 +115,7 @@ public function testCheckRuleDifferentClientRedirectUriArrayThrows(): void $this->resultBag->add(new Result(ClientIdRule::class, $this->clientStub)); $this->expectException(OidcServerException::class); - $this->mock()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); } /** @@ -117,7 +128,7 @@ public function testCheckRuleWithValidRedirectUri(): void $resultBag = $this->prepareValidResultBag(); - $result = $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $result = $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); $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 1c3b97b4..69c9392c 100644 --- a/tests/unit/src/Server/RequestRules/Rules/RequestObjectRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/RequestObjectRuleTest.php @@ -10,6 +10,7 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Result; use SimpleSAML\Module\oidc\Server\RequestRules\ResultBag; @@ -31,6 +32,7 @@ class RequestObjectRuleTest extends TestCase protected Stub $requestStub; protected Stub $loggerServiceStub; protected MockObject $jwksResolverMock; + protected Helpers $helpers; protected function setUp(): void { @@ -46,24 +48,33 @@ protected function setUp(): void $this->requestStub = $this->createStub(ServerRequestInterface::class); $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->jwksResolverMock = $this->createMock(JwksResolver::class); + $this->helpers = new Helpers(); } - protected function mock(): RequestObjectRule - { + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ?JwksResolver $jwksResolver = null, + ): RequestObjectRule { + $requestParamsResolver ??= $this->requestParamsResolverMock; + $helpers ??= $this->helpers; + $jwksResolver ??= $this->jwksResolverMock; + return new RequestObjectRule( - $this->requestParamsResolverMock, - $this->jwksResolverMock, + $requestParamsResolver, + $helpers, + $jwksResolver, ); } public function testCanCreateInstance(): void { - $this->assertInstanceOf(RequestObjectRule::class, $this->mock()); + $this->assertInstanceOf(RequestObjectRule::class, $this->sut()); } public function testRequestParamCanBeAbsent(): void { - $result = $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); $this->assertNull($result); } @@ -74,7 +85,7 @@ public function testUnprotectedRequestParamCanBeUsed(): void $this->requestParamsResolverMock->expects($this->once())->method('parseRequestObjectToken') ->with('token')->willReturn($this->requestObjectMock); - $result = $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); $this->assertInstanceOf(Result::class, $result); $this->assertIsArray($result->getValue()); $this->assertNotEmpty($result->getValue()); @@ -89,7 +100,7 @@ public function testMissingClientJwksThrows(): void $this->clientStub->expects($this->once())->method('getJwks')->willReturn(null); $this->expectException(OidcServerException::class); - $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); } public function testThrowsForInvalidRequestObject(): void @@ -105,7 +116,7 @@ public function testThrowsForInvalidRequestObject(): void ->willReturn(['jwks']); $this->expectException(OidcServerException::class); - $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); } public function testReturnsValidRequestObject(): void @@ -121,7 +132,7 @@ public function testReturnsValidRequestObject(): void ->with($this->clientStub) ->willReturn(['jwks']); - $result = $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); + $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); $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 74648c30..20d165c1 100644 --- a/tests/unit/src/Server/RequestRules/Rules/RequestedClaimsRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/RequestedClaimsRuleTest.php @@ -10,6 +10,7 @@ use SimpleSAML\Module\oidc\Entities\ClaimSetEntity; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\Factories\Entities\ClaimSetEntityFactory; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\RequestRules\Result; use SimpleSAML\Module\oidc\Server\RequestRules\ResultBag; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\ClientIdRule; @@ -31,6 +32,7 @@ class RequestedClaimsRuleTest extends TestCase protected static string $userIdAttr = 'uid'; protected Stub $requestParamsResolverStub; protected Stub $claimSetEntityFactoryStub; + protected Helpers $helpers; /** @@ -53,13 +55,23 @@ protected function setUp(): void $claimSetEntityStub->method('getClaims')->willReturn($claims); return $claimSetEntityStub; }); + + $this->helpers = new Helpers(); } - protected function mock(): RequestedClaimsRule - { + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ?ClaimTranslatorExtractor $claimTranslatorExtractor = null, + ): RequestedClaimsRule { + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpers; + $claimTranslatorExtractor ??= new ClaimTranslatorExtractor(self::$userIdAttr, $this->claimSetEntityFactoryStub); + return new RequestedClaimsRule( - $this->requestParamsResolverStub, - new ClaimTranslatorExtractor(self::$userIdAttr, $this->claimSetEntityFactoryStub), + $requestParamsResolver, + $helpers, + $claimTranslatorExtractor, ); } @@ -68,7 +80,7 @@ protected function mock(): RequestedClaimsRule */ public function testNoRequestedClaims(): void { - $result = $this->mock()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $result = $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); $this->assertNull($result); } @@ -101,7 +113,7 @@ public function testWithClaims(): void $this->requestParamsResolverStub->method('getBasedOnAllowedMethods')->willReturn(json_encode($requestedClaims)); - $result = $this->mock()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $result = $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); $this->assertNotNull($result); $this->assertEquals($expectedClaims, $result->getValue()); } @@ -120,7 +132,7 @@ public function testOnlyWithNonStandardClaimRequest(): void $requestedClaims = $expectedClaims; $this->requestParamsResolverStub->method('getBasedOnAllowedMethods')->willReturn(json_encode($requestedClaims)); - $result = $this->mock()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $result = $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); $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 72cd83c0..8a6d377d 100644 --- a/tests/unit/src/Server/RequestRules/Rules/RequiredNonceRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/RequiredNonceRuleTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Result; use SimpleSAML\Module\oidc\Server\RequestRules\ResultBag; @@ -27,6 +28,7 @@ class RequiredNonceRuleTest extends TestCase protected Result $stateResult; protected Stub $requestStub; + protected Helpers $helpers; protected array $requestQueryParams = [ 'nonce' => 'nonce123', @@ -51,11 +53,20 @@ protected function setUp(): void $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); + $this->helpers = new Helpers(); } - protected function mock(): RequiredNonceRule - { - return new RequiredNonceRule($this->requestParamsResolverStub); + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ): RequiredNonceRule { + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpers; + + return new RequiredNonceRule( + $requestParamsResolver, + $helpers, + ); } /** @@ -66,7 +77,7 @@ public function testCheckRuleRedirectUriDependency(): void { $resultBag = new ResultBag(); $this->expectException(LogicException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -78,7 +89,7 @@ public function testCheckRuleStateDependency(): void $resultBag = new ResultBag(); $resultBag->add($this->redirectUriResult); $this->expectException(LogicException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -90,7 +101,7 @@ public function testCheckRulePassesWhenNonceIsPresent() $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods') ->willReturn($this->requestQueryParams['nonce']); - $result = $this->mock()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? new Result(RequiredNonceRule::class, null); $this->assertEquals($this->requestQueryParams['nonce'], $result->getValue()); @@ -103,6 +114,6 @@ public function testCheckRuleThrowsWhenNonceIsNotPresent() { $this->expectException(OidcServerException::class); - $this->mock()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); } } diff --git a/tests/unit/src/Server/RequestRules/Rules/RequiredOpenIdScopeRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/RequiredOpenIdScopeRuleTest.php index d9455abe..9f6dcedf 100644 --- a/tests/unit/src/Server/RequestRules/Rules/RequiredOpenIdScopeRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/RequiredOpenIdScopeRuleTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Module\oidc\Entities\ScopeEntity; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Result; use SimpleSAML\Module\oidc\Server\RequestRules\ResultBag; @@ -34,6 +35,7 @@ class RequiredOpenIdScopeRuleTest extends TestCase protected Stub $loggerServiceStub; protected Stub $requestParamsResolverStub; + protected Helpers $helpers; /** * @throws \Exception @@ -50,12 +52,19 @@ protected function setUp(): void $this->scopeResult = new Result(ScopeRule::class, $this->scopeEntities); $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); + $this->helpers = new Helpers(); } - protected function mock(): RequiredOpenIdScopeRule - { + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ): RequiredOpenIdScopeRule { + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpers; + return new RequiredOpenIdScopeRule( - $this->requestParamsResolverStub, + $requestParamsResolver, + $helpers, ); } @@ -67,7 +76,7 @@ public function testCheckRuleRedirectUriDependency(): void { $resultBag = new ResultBag(); $this->expectException(LogicException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -79,7 +88,7 @@ public function testCheckRuleStateDependency(): void $resultBag = new ResultBag(); $resultBag->add($this->redirectUriResult); $this->expectException(LogicException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -93,7 +102,7 @@ public function testCheckRulePassesWhenOpenIdScopeIsPresent() $resultBag->add($this->stateResult); $resultBag->add($this->scopeResult); - $result = $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub) ?? new Result(RequiredOpenIdScopeRule::class, null); $this->assertTrue($result->getValue()); @@ -114,6 +123,6 @@ public function testCheckRuleThrowsWhenOpenIdScopeIsNotPresent() $this->expectException(OidcServerException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } } diff --git a/tests/unit/src/Server/RequestRules/Rules/ResponseTypeRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/ResponseTypeRuleTest.php index a1688b6d..ecd2d1e6 100644 --- a/tests/unit/src/Server/RequestRules/Rules/ResponseTypeRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/ResponseTypeRuleTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Result; use SimpleSAML\Module\oidc\Server\RequestRules\ResultBag; @@ -21,6 +22,7 @@ class ResponseTypeRuleTest extends TestCase { protected Stub $requestStub; protected Stub $requestParamsResolverStub; + protected Helpers $helpers; protected array $requestParams = [ 'client_id' => 'client123', @@ -58,11 +60,20 @@ protected function setUp(): void $this->resultBag = new ResultBag(); $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); + $this->helpers = new Helpers(); } - protected function mock(): ResponseTypeRule - { - return new ResponseTypeRule($this->requestParamsResolverStub); + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ): ResponseTypeRule { + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpers; + + return new ResponseTypeRule( + $requestParamsResolver, + $helpers, + ); } /** @@ -73,7 +84,7 @@ public function testResponseTypeRuleTest($responseType) { $this->requestParams['response_type'] = $responseType; $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($this->requestParams); - $result = $this->mock()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? new Result(ResponseTypeRule::class, null); $this->assertSame($responseType, $result->getValue()); } @@ -92,6 +103,6 @@ public function testResponseTypeRuleThrowsWithNoResponseTypeParamTest() unset($params['response_type']); $this->requestParamsResolverStub->method('getAllBasedOnAllowedMethods')->willReturn($params); $this->expectException(OidcServerException::class); - $this->mock()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); + $this->sut()->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); } } diff --git a/tests/unit/src/Server/RequestRules/Rules/ScopeOfflineAccessRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/ScopeOfflineAccessRuleTest.php index 5b0a216d..e9bddd38 100644 --- a/tests/unit/src/Server/RequestRules/Rules/ScopeOfflineAccessRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/ScopeOfflineAccessRuleTest.php @@ -11,6 +11,7 @@ use Psr\Http\Message\ServerRequestInterface; use SimpleSAML\Configuration; use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; @@ -37,6 +38,7 @@ class ScopeOfflineAccessRuleTest extends TestCase protected Stub $moduleConfigStub; protected Stub $openIdConfigurationStub; protected Stub $requestParamsResolverStub; + protected Helpers $helpers; /** * @throws \Exception @@ -65,18 +67,28 @@ protected function setUp(): void $this->moduleConfigStub = $this->createStub(ModuleConfig::class); $this->openIdConfigurationStub = $this->createStub(Configuration::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); + + $this->helpers = new Helpers(); } - protected function mock(): ScopeOfflineAccessRule - { - return new ScopeOfflineAccessRule($this->requestParamsResolverStub); + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ): ScopeOfflineAccessRule { + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpers; + + return new ScopeOfflineAccessRule( + $requestParamsResolver, + $helpers, + ); } public function testCanCreateInstance(): void { $this->assertInstanceOf( ScopeOfflineAccessRule::class, - $this->mock(), + $this->sut(), ); } @@ -103,7 +115,7 @@ public function testReturnsFalseWhenOfflineAccessScopeNotPresent(): void $this->moduleConfigStub->method('config') ->willReturn($this->openIdConfigurationStub); - $result = $this->mock()->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); + $result = $this->sut()->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); $this->assertNotNull($result); $this->assertFalse($result->getValue()); @@ -134,7 +146,7 @@ public function testThrowsWhenClientDoesntHaveOfflineAccessScopeRegistered(): vo $this->expectException(OidcServerException::class); - $this->mock()->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); + $this->sut()->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); } /** @@ -161,7 +173,7 @@ public function testReturnsTrueWhenClientDoesHaveOfflineAccessScopeRegistered(): $this->moduleConfigStub->method('config') ->willReturn($this->openIdConfigurationStub); - $result = $this->mock()->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); + $result = $this->sut()->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); $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 47761bcf..c40143b9 100644 --- a/tests/unit/src/Server/RequestRules/Rules/ScopeRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/ScopeRuleTest.php @@ -69,18 +69,25 @@ protected function setUp(): void $this->helpersStub->method('str')->willReturn($this->strHelperMock); } - protected function mock(): ScopeRule - { + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ?ScopeRepositoryInterface $scopeRepository = null, + ): ScopeRule { + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpersStub; + $scopeRepository ??= $this->scopeRepositoryStub; + return new ScopeRule( - $this->requestParamsResolverStub, - $this->scopeRepositoryStub, - $this->helpersStub, + $requestParamsResolver, + $helpers, + $scopeRepository, ); } public function testConstruct(): void { - $this->assertInstanceOf(ScopeRule::class, $this->mock()); + $this->assertInstanceOf(ScopeRule::class, $this->sut()); } /** @@ -91,7 +98,7 @@ public function testCheckRuleRedirectUriDependency(): void { $resultBag = new ResultBag(); $this->expectException(LogicException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, $this->data); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, $this->data); } /** @@ -103,7 +110,7 @@ public function testCheckRuleStateDependency(): void $resultBag = new ResultBag(); $resultBag->add($this->redirectUriResult); $this->expectException(LogicException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, $this->data); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, $this->data); } /** @@ -127,7 +134,7 @@ public function testValidScopes(): void ), ); - $result = $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, $this->data); + $result = $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, $this->data); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertIsArray($result->getValue()); $this->assertSame($this->scopeEntities['openid'], $result->getValue()[0]); @@ -154,7 +161,7 @@ public function testInvalidScopeThrows(): void ); $this->expectException(OidcServerException::class); - $this->mock()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, $this->data); + $this->sut()->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, $this->data); } 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 10fae4c3..aa38de1b 100644 --- a/tests/unit/src/Server/RequestRules/Rules/StateRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/StateRuleTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\ResultBag; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\StateRule; @@ -21,6 +22,7 @@ class StateRuleTest extends TestCase { protected Stub $loggerServiceStub; protected Stub $requestParamsResolverStub; + protected Helpers $helpers; /** * @throws \Exception @@ -29,16 +31,25 @@ public function setUp(): void { $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); + $this->helpers = new Helpers(); } - protected function mock(): StateRule - { - return new StateRule($this->requestParamsResolverStub); + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ): StateRule { + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpers; + + return new StateRule( + $requestParamsResolver, + $helpers, + ); } public function testGetKey(): void { - $this->assertSame(StateRule::class, $this->mock()->getKey()); + $this->assertSame(StateRule::class, $this->sut()->getKey()); } /** @@ -54,7 +65,7 @@ public function testCheckRuleHasValue(): void $resultBag = new ResultBag(); $data = []; - $result = $this->mock()->checkRule($request, $resultBag, $this->loggerServiceStub, $data); + $result = $this->sut()->checkRule($request, $resultBag, $this->loggerServiceStub, $data); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertSame($value, $result->getValue()); @@ -70,7 +81,7 @@ public function testCheckRulePostMethod(): void $this->requestParamsResolverStub->method('getAsStringBasedOnAllowedMethods')->willReturn(null); $resultBag = new ResultBag(); - $result = $this->mock()->checkRule( + $result = $this->sut()->checkRule( $request, $resultBag, $this->loggerServiceStub, diff --git a/tests/unit/src/Server/RequestRules/Rules/UiLocalesRuleTest.php b/tests/unit/src/Server/RequestRules/Rules/UiLocalesRuleTest.php index 681f5f04..a4eefd8c 100644 --- a/tests/unit/src/Server/RequestRules/Rules/UiLocalesRuleTest.php +++ b/tests/unit/src/Server/RequestRules/Rules/UiLocalesRuleTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultBagInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; use SimpleSAML\Module\oidc\Server\RequestRules\Rules\UiLocalesRule; @@ -22,6 +23,7 @@ class UiLocalesRuleTest extends TestCase protected Stub $resultBagStub; protected Stub $loggerServiceStub; protected Stub $requestParamsResolverStub; + protected Helpers $helpers; /** * @throws \Exception @@ -34,11 +36,20 @@ protected function setUp(): void $this->resultBagStub = $this->createStub(ResultBagInterface::class); $this->loggerServiceStub = $this->createStub(LoggerService::class); $this->requestParamsResolverStub = $this->createStub(RequestParamsResolver::class); + $this->helpers = new Helpers(); } - protected function mock(): UiLocalesRule - { - return new UiLocalesRule($this->requestParamsResolverStub); + protected function sut( + ?RequestParamsResolver $requestParamsResolver = null, + ?Helpers $helpers = null, + ): UiLocalesRule { + $requestParamsResolver ??= $this->requestParamsResolverStub; + $helpers ??= $this->helpers; + + return new UiLocalesRule( + $requestParamsResolver, + $helpers, + ); } /** @@ -48,7 +59,7 @@ public function testCheckRuleReturnsResultWhenParamSet() { $this->requestParamsResolverStub->method('getBasedOnAllowedMethods')->willReturn('en'); - $result = $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? new Result(UiLocalesRule::class); $this->assertEquals('en', $result->getValue()); @@ -61,7 +72,7 @@ public function testCheckRuleReturnsNullWhenParamNotSet() { $this->requestStub->method('getQueryParams')->willReturn([]); - $result = $this->mock()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? + $result = $this->sut()->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? new Result(UiLocalesRule::class); $this->assertNull($result->getValue()); From 913962e936303614c5683f2e2efd763ae8bd115f Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Mon, 10 Feb 2025 16:47:23 +0100 Subject: [PATCH 60/70] Use already resolved client for authentication --- src/Server/RequestRules/Rules/MaxAgeRule.php | 2 +- src/Server/RequestRules/Rules/PromptRule.php | 2 +- src/Services/AuthenticationService.php | 12 +++--------- .../unit/src/Services/AuthenticationServiceTest.php | 5 +---- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/Server/RequestRules/Rules/MaxAgeRule.php b/src/Server/RequestRules/Rules/MaxAgeRule.php index 5742e431..b3903767 100644 --- a/src/Server/RequestRules/Rules/MaxAgeRule.php +++ b/src/Server/RequestRules/Rules/MaxAgeRule.php @@ -92,7 +92,7 @@ public function checkRule( $loginParams['ReturnTo'] = (new HTTP()) ->addURLParameters((new HTTP())->getSelfURLNoQuery(), $requestParams); - $this->authenticationService->authenticate($request, $loginParams); + $this->authenticationService->authenticate($client, $loginParams); } return new Result($this->getKey(), $lastAuth); diff --git a/src/Server/RequestRules/Rules/PromptRule.php b/src/Server/RequestRules/Rules/PromptRule.php index b2311bbd..224a3e97 100644 --- a/src/Server/RequestRules/Rules/PromptRule.php +++ b/src/Server/RequestRules/Rules/PromptRule.php @@ -86,7 +86,7 @@ public function checkRule( $loginParams['ReturnTo'] = (new HTTP()) ->addURLParameters((new HTTP())->getSelfURLNoQuery(), $requestParams); - $this->authenticationService->authenticate($request, $loginParams); + $this->authenticationService->authenticate($client, $loginParams); } return null; diff --git a/src/Services/AuthenticationService.php b/src/Services/AuthenticationService.php index 7d8c7afa..a455660e 100644 --- a/src/Services/AuthenticationService.php +++ b/src/Services/AuthenticationService.php @@ -96,7 +96,7 @@ public function processRequest( $this->authSourceId = $authSimple->getAuthSource()->getAuthId(); if (! $authSimple->isAuthenticated()) { - $this->authenticate($request); + $this->authenticate($oidcClient); } elseif ($this->sessionService->getIsAuthnPerformedInPreviousRequest()) { $this->sessionService->setIsAuthnPerformedInPreviousRequest(false); @@ -268,10 +268,6 @@ public function getSessionId(): ?string } /** - * @param ServerRequestInterface $request - * @param array $loginParams - * - * @return void * @throws Error\BadRequest * @throws Error\NotFound * @throws \JsonException @@ -279,12 +275,10 @@ public function getSessionId(): ?string */ public function authenticate( - ServerRequestInterface $request, + ClientEntityInterface $clientEntity, array $loginParams = [], ): void { - // TODO mivanci Fix: client has already been resolved up to this point, but we are again fetching it from DB. - $oidcClient = $this->helpers->client()->getFromRequest($request, $this->clientRepository); - $authSimple = $this->authSimpleFactory->build($oidcClient); + $authSimple = $this->authSimpleFactory->build($clientEntity); $this->sessionService->setIsCookieBasedAuthn(false); $this->sessionService->setIsAuthnPerformedInPreviousRequest(true); diff --git a/tests/unit/src/Services/AuthenticationServiceTest.php b/tests/unit/src/Services/AuthenticationServiceTest.php index fd9b2024..deae3f3d 100644 --- a/tests/unit/src/Services/AuthenticationServiceTest.php +++ b/tests/unit/src/Services/AuthenticationServiceTest.php @@ -331,11 +331,8 @@ public function testGetAuthenticateUserItThrowsIfClaimsNotExist(): void public function testItAuthenticates(): void { $this->authSimpleMock->expects($this->once())->method('login')->with([]); - $this->clientHelperMock->expects($this->once()) - ->method('getFromRequest') - ->willReturn($this->clientEntityMock); - $this->mock()->authenticate($this->serverRequestMock); + $this->mock()->authenticate($this->clientEntityMock); } /** From 15e141c5fb852ee26446014d61104e2414767eda Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Mon, 10 Feb 2025 17:33:28 +0100 Subject: [PATCH 61/70] Move to SspBridge in AuthContextService --- src/Bridges/SspBridge/Utils.php | 7 +++ src/Services/AuthContextService.php | 12 +++-- src/Services/Container.php | 12 +++-- .../unit/src/Bridges/SspBridge/UtilsTest.php | 6 +++ .../src/Services/AuthContextServiceTest.php | 53 ++++++++++++++----- 5 files changed, 69 insertions(+), 21 deletions(-) diff --git a/src/Bridges/SspBridge/Utils.php b/src/Bridges/SspBridge/Utils.php index 7b7a3705..f201faa6 100644 --- a/src/Bridges/SspBridge/Utils.php +++ b/src/Bridges/SspBridge/Utils.php @@ -4,6 +4,7 @@ namespace SimpleSAML\Module\oidc\Bridges\SspBridge; +use SimpleSAML\Utils\Attributes; use SimpleSAML\Utils\Auth; use SimpleSAML\Utils\Config; use SimpleSAML\Utils\HTTP; @@ -15,6 +16,7 @@ class Utils protected static ?HTTP $http = null; protected static ?Random $random = null; protected static ?Auth $auth = null; + protected static ?Attributes $attributes = null; public function config(): Config { @@ -35,4 +37,9 @@ public function auth(): Auth { return self::$auth ??= new Auth(); } + + public function attributes(): Attributes + { + return self::$attributes ??= new Attributes(); + } } diff --git a/src/Services/AuthContextService.php b/src/Services/AuthContextService.php index 10af36de..b0d6e6db 100644 --- a/src/Services/AuthContextService.php +++ b/src/Services/AuthContextService.php @@ -6,10 +6,9 @@ use RuntimeException; use SimpleSAML\Auth\Simple; +use SimpleSAML\Module\oidc\Bridges\SspBridge; use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory; use SimpleSAML\Module\oidc\ModuleConfig; -use SimpleSAML\Utils\Attributes; -use SimpleSAML\Utils\Auth; /** * Provide contextual authentication information for administration interface. @@ -28,13 +27,13 @@ class AuthContextService public function __construct( private readonly ModuleConfig $moduleConfig, private readonly AuthSimpleFactory $authSimpleFactory, + private readonly SspBridge $sspBridge, ) { } public function isSspAdmin(): bool { - // TODO mivanci make bridge to SSP utility classes (search for SSP namespace through the codebase) - return (new Auth())->isAdmin(); + return $this->sspBridge->utils()->auth()->isAdmin(); } /** @@ -45,7 +44,10 @@ public function getAuthUserId(): string { $simple = $this->authenticate(); $userIdAttr = $this->moduleConfig->getUserIdentifierAttribute(); - return (string)(new Attributes())->getExpectedAttribute($simple->getAttributes(), $userIdAttr); + return (string)$this->sspBridge->utils()->attributes()->getExpectedAttribute( + $simple->getAttributes(), + $userIdAttr, + ); } /** diff --git a/src/Services/Container.php b/src/Services/Container.php index b43bb9ea..fa73cf47 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -131,15 +131,19 @@ public function __construct() $authSimpleFactory = new AuthSimpleFactory($moduleConfig); $this->services[AuthSimpleFactory::class] = $authSimpleFactory; - $authContextService = new AuthContextService($moduleConfig, $authSimpleFactory); + $sspBridge = new SspBridge(); + $this->services[SspBridge::class] = $sspBridge; + + $authContextService = new AuthContextService( + $moduleConfig, + $authSimpleFactory, + $sspBridge, + ); $this->services[AuthContextService::class] = $authContextService; $session = Session::getSessionFromRequest(); $this->services[Session::class] = $session; - $sspBridge = new SspBridge(); - $this->services[SspBridge::class] = $sspBridge; - $helpers = new Helpers(); $this->services[Helpers::class] = $helpers; diff --git a/tests/unit/src/Bridges/SspBridge/UtilsTest.php b/tests/unit/src/Bridges/SspBridge/UtilsTest.php index 3fc4941d..e9cb8be9 100644 --- a/tests/unit/src/Bridges/SspBridge/UtilsTest.php +++ b/tests/unit/src/Bridges/SspBridge/UtilsTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Bridges\SspBridge\Utils; +use SimpleSAML\Utils\Attributes; use SimpleSAML\Utils\Auth; use SimpleSAML\Utils\Config; use SimpleSAML\Utils\HTTP; @@ -44,4 +45,9 @@ public function testCanBuildAuthInstance(): void { $this->assertInstanceOf(Auth::class, $this->sut()->auth()); } + + public function testCanBuileAttributesInstance(): void + { + $this->assertInstanceOf(Attributes::class, $this->sut()->attributes()); + } } diff --git a/tests/unit/src/Services/AuthContextServiceTest.php b/tests/unit/src/Services/AuthContextServiceTest.php index 494a9a03..0fcf54fb 100644 --- a/tests/unit/src/Services/AuthContextServiceTest.php +++ b/tests/unit/src/Services/AuthContextServiceTest.php @@ -10,9 +10,11 @@ use SimpleSAML\Auth\Simple; use SimpleSAML\Configuration; use SimpleSAML\Error\Exception; +use SimpleSAML\Module\oidc\Bridges\SspBridge; use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Services\AuthContextService; +use SimpleSAML\Utils\Attributes; /** * @covers \SimpleSAML\Module\oidc\Services\AuthContextService @@ -28,6 +30,9 @@ class AuthContextServiceTest extends TestCase protected MockObject $moduleConfigMock; protected MockObject $authSimpleService; protected MockObject $authSimpleFactory; + protected MockObject $sspBridgeMock; + protected MockObject $sspBridgeUtilsMock; + protected MockObject $sspBridgeUtilsAttributesMock; /** * @throws \PHPUnit\Framework\MockObject\Exception @@ -52,13 +57,27 @@ protected function setUp(): void $this->authSimpleFactory = $this->createMock(AuthSimpleFactory::class); $this->authSimpleFactory->method('getDefaultAuthSource')->willReturn($this->authSimpleService); + + $this->sspBridgeMock = $this->createMock(SspBridge::class); + $this->sspBridgeUtilsMock = $this->createMock(SspBridge\Utils::class); + $this->sspBridgeMock->method('utils')->willReturn($this->sspBridgeUtilsMock); + $this->sspBridgeUtilsAttributesMock = $this->createMock(Attributes::class); + $this->sspBridgeUtilsMock->method('attributes')->willReturn($this->sspBridgeUtilsAttributesMock); } - protected function prepareMockedInstance(): AuthContextService - { + protected function sut( + ?ModuleConfig $moduleConfig = null, + ?AuthSimpleFactory $authSimpleFactory = null, + ?SspBridge $sspBridge = null, + ): AuthContextService { + $moduleConfig ??= $this->moduleConfigMock; + $authSimpleFactory ??= $this->authSimpleFactory; + $sspBridge ??= $this->sspBridgeMock; + return new AuthContextService( - $this->moduleConfigMock, - $this->authSimpleFactory, + $moduleConfig, + $authSimpleFactory, + $sspBridge, ); } @@ -66,7 +85,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( AuthContextService::class, - $this->prepareMockedInstance(), + $this->sut(), ); } @@ -77,9 +96,15 @@ public function testItReturnsUsername(): void { $this->moduleConfigMock->method('getUserIdentifierAttribute')->willReturn('idAttribute'); $this->authSimpleService->method('getAttributes')->willReturn(self::AUTHORIZED_USER); + $this->sspBridgeUtilsAttributesMock->expects($this->once())->method('getExpectedAttribute') + ->with( + self::AUTHORIZED_USER, + 'idAttribute', + ) + ->willReturn(self::AUTHORIZED_USER['idAttribute'][0]); $this->assertSame( - $this->prepareMockedInstance()->getAuthUserId(), + $this->sut()->getAuthUserId(), 'myUsername', ); } @@ -94,8 +119,12 @@ public function testItThrowsWhenNoUsername(): void ->willReturn('attributeNotSet'); $this->authSimpleService->method('getAttributes')->willReturn(self::AUTHORIZED_USER); + $this->sspBridgeUtilsAttributesMock->expects($this->once())->method('getExpectedAttribute') + ->with(self::AUTHORIZED_USER) + ->willThrowException(new Exception('error')); + $this->expectException(Exception::class); - $this->prepareMockedInstance()->getAuthUserId(); + $this->sut()->getAuthUserId(); } /** @@ -108,7 +137,7 @@ public function testPermissionsOk(): void ->willReturn($this->permissions); $this->authSimpleService->method('getAttributes')->willReturn(self::AUTHORIZED_USER); - $this->prepareMockedInstance()->requirePermission('client'); + $this->sut()->requirePermission('client'); $this->expectNotToPerformAssertions(); } @@ -121,7 +150,7 @@ public function testItThrowsIfNotAuthorizedForPermission(): void ->with(ModuleConfig::OPTION_ADMIN_UI_PERMISSIONS, null) ->willReturn($this->permissions); $this->expectException(RuntimeException::class); - $this->prepareMockedInstance()->requirePermission('no-match'); + $this->sut()->requirePermission('no-match'); } /** @@ -141,7 +170,7 @@ public function testItThrowsForWrongEntitlements(): void ); $this->expectException(RuntimeException::class); - $this->prepareMockedInstance()->requirePermission('client'); + $this->sut()->requirePermission('client'); } /** @@ -160,7 +189,7 @@ public function testItThrowsForNotHavingEntitlementAttribute(): void ); $this->expectException(RuntimeException::class); - $this->prepareMockedInstance()->requirePermission('client'); + $this->sut()->requirePermission('client'); } /** @@ -173,6 +202,6 @@ public function testThrowsForNotHavingEnabledPermissions(): void ->willReturn(Configuration::loadFromArray([])); $this->expectException(RuntimeException::class); - $this->prepareMockedInstance()->requirePermission('client'); + $this->sut()->requirePermission('client'); } } From 8d5b48b21ec89da2148b30a8628acd7cafac2f42 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Tue, 11 Feb 2025 09:56:02 +0100 Subject: [PATCH 62/70] Add version constraint for openid library --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3c229366..f1c3a1a6 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "psr/container": "^2.0", "psr/log": "^3", "simplesamlphp/composer-module-installer": "^1.3", - "simplesamlphp/openid": "dev-master", + "simplesamlphp/openid": "^0", "spomky-labs/base64url": "^2.0", "symfony/expression-language": "^6.3", "symfony/psr-http-message-bridge": "^7.1", From 19ccd619b82e0ed0ea2688bf74b0b95fc7ca67b8 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Tue, 11 Feb 2025 10:07:47 +0100 Subject: [PATCH 63/70] Move UniqueIdentifierGenerator to Random helper --- src/Services/JsonWebTokenBuilderService.php | 5 +-- src/Utils/UniqueIdentifierGenerator.php | 32 ------------------- .../Utils/UniqueIdentifierGeneratorTest.php | 25 --------------- 3 files changed, 3 insertions(+), 59 deletions(-) delete mode 100644 src/Utils/UniqueIdentifierGenerator.php delete mode 100644 tests/unit/src/Utils/UniqueIdentifierGeneratorTest.php diff --git a/src/Services/JsonWebTokenBuilderService.php b/src/Services/JsonWebTokenBuilderService.php index cf4135b5..c371a10c 100644 --- a/src/Services/JsonWebTokenBuilderService.php +++ b/src/Services/JsonWebTokenBuilderService.php @@ -11,10 +11,10 @@ use Lcobucci\JWT\Signer; use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\UnencryptedToken; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Utils\FingerprintGenerator; -use SimpleSAML\Module\oidc\Utils\UniqueIdentifierGenerator; use SimpleSAML\OpenID\Codebooks\ClaimsEnum; class JsonWebTokenBuilderService @@ -37,6 +37,7 @@ class JsonWebTokenBuilderService */ public function __construct( protected ModuleConfig $moduleConfig = new ModuleConfig(), + protected Helpers $helpers = new Helpers(), ) { $this->protocolJwtConfig = Configuration::forAsymmetricSigner( $this->moduleConfig->getProtocolSigner(), @@ -97,7 +98,7 @@ public function getDefaultJwtBuilder(Configuration $configuration): Builder return $configuration->builder(ChainedFormatter::withUnixTimestampDates()) ->issuedBy($this->moduleConfig->getIssuer()) ->issuedAt(new DateTimeImmutable('now')) - ->identifiedBy(UniqueIdentifierGenerator::hitMe()); + ->identifiedBy($this->helpers->random()->getIdentifier()); } /** diff --git a/src/Utils/UniqueIdentifierGenerator.php b/src/Utils/UniqueIdentifierGenerator.php deleted file mode 100644 index d160caff..00000000 --- a/src/Utils/UniqueIdentifierGenerator.php +++ /dev/null @@ -1,32 +0,0 @@ -assertNotEquals($id1, $id2); - } -} From d736cca25c9f558cb1f9ddcf9acf9d5d65bbdb4a Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Tue, 11 Feb 2025 10:31:53 +0100 Subject: [PATCH 64/70] Move Arr::find to Arr helper --- src/Factories/Grant/AuthCodeGrantFactory.php | 3 ++ src/Helpers/Arr.php | 17 ++++++++++ src/Server/Grants/AuthCodeGrant.php | 5 +-- src/Services/Container.php | 1 + src/Utils/Arr.php | 31 ------------------- tests/unit/src/Helpers/ArrTest.php | 16 ++++++++++ .../src/Server/Grants/AuthCodeGrantTest.php | 4 +++ 7 files changed, 44 insertions(+), 33 deletions(-) delete mode 100644 src/Utils/Arr.php diff --git a/src/Factories/Grant/AuthCodeGrantFactory.php b/src/Factories/Grant/AuthCodeGrantFactory.php index f519b687..a72a53c4 100644 --- a/src/Factories/Grant/AuthCodeGrantFactory.php +++ b/src/Factories/Grant/AuthCodeGrantFactory.php @@ -18,6 +18,7 @@ use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory; use SimpleSAML\Module\oidc\Factories\Entities\AuthCodeEntityFactory; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; use SimpleSAML\Module\oidc\Repositories\AuthCodeRepository; @@ -39,6 +40,7 @@ public function __construct( private readonly AccessTokenEntityFactory $accessTokenEntityFactory, private readonly AuthCodeEntityFactory $authCodeEntityFactory, private readonly RefreshTokenIssuer $refreshTokenIssuer, + private readonly Helpers $helpers, ) { } @@ -57,6 +59,7 @@ public function build(): AuthCodeGrant $this->accessTokenEntityFactory, $this->authCodeEntityFactory, $this->refreshTokenIssuer, + $this->helpers, ); $authCodeGrant->setRefreshTokenTTL($this->moduleConfig->getRefreshTokenDuration()); diff --git a/src/Helpers/Arr.php b/src/Helpers/Arr.php index c7df69ac..96cadff8 100644 --- a/src/Helpers/Arr.php +++ b/src/Helpers/Arr.php @@ -6,6 +6,23 @@ class Arr { + /** + * Find item in array using the given callable. + * + * @return mixed|null + */ + public function findByCallback(array $arr, callable $fn): mixed + { + /** @psalm-suppress MixedAssignment */ + foreach ($arr as $x) { + if (call_user_func($fn, $x) === true) { + return $x; + } + } + + return null; + } + /** * @param array $values * @return string[] diff --git a/src/Server/Grants/AuthCodeGrant.php b/src/Server/Grants/AuthCodeGrant.php index e056081e..38a04490 100644 --- a/src/Server/Grants/AuthCodeGrant.php +++ b/src/Server/Grants/AuthCodeGrant.php @@ -27,6 +27,7 @@ use SimpleSAML\Module\oidc\Entities\UserEntity; use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory; use SimpleSAML\Module\oidc\Factories\Entities\AuthCodeEntityFactory; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Repositories\Interfaces\AccessTokenRepositoryInterface; use SimpleSAML\Module\oidc\Repositories\Interfaces\AuthCodeRepositoryInterface; use SimpleSAML\Module\oidc\Repositories\Interfaces\RefreshTokenRepositoryInterface; @@ -58,7 +59,6 @@ use SimpleSAML\Module\oidc\Server\ResponseTypes\Interfaces\NonceResponseTypeInterface; use SimpleSAML\Module\oidc\Server\ResponseTypes\Interfaces\SessionIdResponseTypeInterface; use SimpleSAML\Module\oidc\Server\TokenIssuers\RefreshTokenIssuer; -use SimpleSAML\Module\oidc\Utils\Arr; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\Module\oidc\Utils\ScopeHelper; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -165,6 +165,7 @@ public function __construct( AccessTokenEntityFactory $accessTokenEntityFactory, protected AuthCodeEntityFactory $authCodeEntityFactory, protected RefreshTokenIssuer $refreshTokenIssuer, + protected Helpers $helpers, ) { parent::__construct($authCodeRepository, $refreshTokenRepository, $authCodeTTL); @@ -211,7 +212,7 @@ public function isOidcCandidate( OAuth2AuthorizationRequest $authorizationRequest, ): bool { // Check if the scopes contain 'oidc' scope - return (bool) Arr::find( + return (bool) $this->helpers->arr()->findByCallback( $authorizationRequest->getScopes(), fn(ScopeEntityInterface $scope) => $scope->getIdentifier() === 'openid', ); diff --git a/src/Services/Container.php b/src/Services/Container.php index fa73cf47..951fc125 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -441,6 +441,7 @@ public function __construct() $accessTokenEntityFactory, $authCodeEntityFactory, $refreshTokenIssuer, + $helpers, ); $this->services[AuthCodeGrant::class] = $authCodeGrantFactory->build(); diff --git a/src/Utils/Arr.php b/src/Utils/Arr.php deleted file mode 100644 index 32032d4c..00000000 --- a/src/Utils/Arr.php +++ /dev/null @@ -1,31 +0,0 @@ -assertSame( + 'a', + $this->sut()->findByCallback( + ['a', 'b', 'c'], + fn($item): bool => $item === 'a' + ), + ); + + $this->assertNull($this->sut()->findByCallback( + ['a', 'b', 'c'], + fn($item): bool => $item === 'd' + )); + } + public function testEnsureStringValues(): void { $this->assertSame( diff --git a/tests/unit/src/Server/Grants/AuthCodeGrantTest.php b/tests/unit/src/Server/Grants/AuthCodeGrantTest.php index a0f1b8ed..4479ddf3 100644 --- a/tests/unit/src/Server/Grants/AuthCodeGrantTest.php +++ b/tests/unit/src/Server/Grants/AuthCodeGrantTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory; use SimpleSAML\Module\oidc\Factories\Entities\AuthCodeEntityFactory; +use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\Interfaces\AccessTokenRepositoryInterface; use SimpleSAML\Module\oidc\Repositories\Interfaces\AuthCodeRepositoryInterface; @@ -33,6 +34,7 @@ class AuthCodeGrantTest extends TestCase protected Stub $accessTokenEntityFactoryStub; protected Stub $authCodeEntityFactoryStub; protected Stub $refreshTokenIssuerStub; + protected Stub $helpersStub; /** * @throws \Exception @@ -49,6 +51,7 @@ protected function setUp(): void $this->accessTokenEntityFactoryStub = $this->createStub(AccessTokenEntityFactory::class); $this->authCodeEntityFactoryStub = $this->createStub(AuthcodeEntityFactory::class); $this->refreshTokenIssuerStub = $this->createStub(RefreshTokenIssuer::class); + $this->helpersStub = $this->createStub(Helpers::class); } /** @@ -68,6 +71,7 @@ public function testCanCreateInstance(): void $this->accessTokenEntityFactoryStub, $this->authCodeEntityFactoryStub, $this->refreshTokenIssuerStub, + $this->helpersStub, ), ); } From 5e603ad941ac766c02145205f54d2d1a65f28dcb Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Tue, 11 Feb 2025 10:43:13 +0100 Subject: [PATCH 65/70] Move to Scope helper --- src/Controllers/EndSessionController.php | 4 ++-- src/Helpers.php | 7 ++++++ .../ScopeHelper.php => Helpers/Scope.php} | 6 ++--- src/Server/Grants/AuthCodeGrant.php | 3 +-- .../Rules/ScopeOfflineAccessRule.php | 3 +-- tests/unit/src/Helpers/ArrTest.php | 4 ++-- .../ScopeTest.php} | 24 +++++++++++-------- tests/unit/src/HelpersTest.php | 2 ++ 8 files changed, 32 insertions(+), 21 deletions(-) rename src/{Utils/ScopeHelper.php => Helpers/Scope.php} (82%) rename tests/unit/src/{Utils/ScopeHelperTest.php => Helpers/ScopeTest.php} (67%) diff --git a/src/Controllers/EndSessionController.php b/src/Controllers/EndSessionController.php index dbe6e910..267aa57f 100644 --- a/src/Controllers/EndSessionController.php +++ b/src/Controllers/EndSessionController.php @@ -41,7 +41,7 @@ public function __construct( */ public function __invoke(ServerRequestInterface $request): Response { - // TODO Back-Channel Logout: https://openid.net/specs/openid-connect-backchannel-1_0.html + // TODO v7 Back-Channel Logout: https://openid.net/specs/openid-connect-backchannel-1_0.html // [] Refresh tokens issued without the offline_access property to a session being logged out SHOULD // be revoked. Refresh tokens issued with the offline_access property normally SHOULD NOT be revoked. // - offline_access scope is now handled. @@ -147,7 +147,7 @@ public static function logoutHandler(): void $sessionLogoutTickets = $sessionLogoutTicketStore->getAll(); if (!empty($sessionLogoutTickets)) { - // TODO low mivanci This could brake since interface does not mandate type. Move to strong typing. + // TODO v7 low mivanci This could brake since interface does not mandate type. Move to strong typing. /** @var array $sessionLogoutTicket */ foreach ($sessionLogoutTickets as $sessionLogoutTicket) { $sid = (string)$sessionLogoutTicket['sid']; diff --git a/src/Helpers.php b/src/Helpers.php index 7a5e153a..5a55e766 100644 --- a/src/Helpers.php +++ b/src/Helpers.php @@ -9,6 +9,7 @@ use SimpleSAML\Module\oidc\Helpers\DateTime; use SimpleSAML\Module\oidc\Helpers\Http; use SimpleSAML\Module\oidc\Helpers\Random; +use SimpleSAML\Module\oidc\Helpers\Scope; use SimpleSAML\Module\oidc\Helpers\Str; class Helpers @@ -19,6 +20,7 @@ class Helpers protected static ?Str $str = null; protected static ?Arr $arr = null; protected static ?Random $random = null; + protected static ?Scope $scope = null; public function http(): Http { @@ -51,4 +53,9 @@ public function random(): Random { return static::$random ??= new Random(); } + + public function scope(): Scope + { + return static::$scope ??= new Scope(); + } } diff --git a/src/Utils/ScopeHelper.php b/src/Helpers/Scope.php similarity index 82% rename from src/Utils/ScopeHelper.php rename to src/Helpers/Scope.php index 339b6ffb..bdd36a72 100644 --- a/src/Utils/ScopeHelper.php +++ b/src/Helpers/Scope.php @@ -2,18 +2,18 @@ declare(strict_types=1); -namespace SimpleSAML\Module\oidc\Utils; +namespace SimpleSAML\Module\oidc\Helpers; use League\OAuth2\Server\Entities\ScopeEntityInterface; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; -class ScopeHelper +class Scope { /** * @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ - public static function scopeExists(array $scopes, string $scopeIdentifier): bool + public function exists(array $scopes, string $scopeIdentifier): bool { foreach ($scopes as $scope) { if (! $scope instanceof ScopeEntityInterface) { diff --git a/src/Server/Grants/AuthCodeGrant.php b/src/Server/Grants/AuthCodeGrant.php index 38a04490..dfaac1cf 100644 --- a/src/Server/Grants/AuthCodeGrant.php +++ b/src/Server/Grants/AuthCodeGrant.php @@ -60,7 +60,6 @@ use SimpleSAML\Module\oidc\Server\ResponseTypes\Interfaces\SessionIdResponseTypeInterface; use SimpleSAML\Module\oidc\Server\TokenIssuers\RefreshTokenIssuer; use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; -use SimpleSAML\Module\oidc\Utils\ScopeHelper; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; use SimpleSAML\OpenID\Codebooks\ParamsEnum; @@ -555,7 +554,7 @@ public function respondToAccessTokenRequest( } // Release refresh token if it is requested by using offline_access scope. - if (ScopeHelper::scopeExists($scopes, 'offline_access')) { + if ($this->helpers->scope()->exists($scopes, 'offline_access')) { // Issue and persist new refresh token if given $refreshToken = $this->issueRefreshToken($accessToken, $authCodePayload->auth_code_id); diff --git a/src/Server/RequestRules/Rules/ScopeOfflineAccessRule.php b/src/Server/RequestRules/Rules/ScopeOfflineAccessRule.php index 127ab59e..ae67f588 100644 --- a/src/Server/RequestRules/Rules/ScopeOfflineAccessRule.php +++ b/src/Server/RequestRules/Rules/ScopeOfflineAccessRule.php @@ -10,7 +10,6 @@ use SimpleSAML\Module\oidc\Server\RequestRules\Interfaces\ResultInterface; use SimpleSAML\Module\oidc\Server\RequestRules\Result; use SimpleSAML\Module\oidc\Services\LoggerService; -use SimpleSAML\Module\oidc\Utils\ScopeHelper; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; class ScopeOfflineAccessRule extends AbstractRule @@ -37,7 +36,7 @@ public function checkRule( $validScopes = $currentResultBag->getOrFail(ScopeRule::class)->getValue(); // Check if offline_access scope is used. If not, we don't have to check anything else. - if (! ScopeHelper::scopeExists($validScopes, 'offline_access')) { + if (! $this->helpers->scope()->exists($validScopes, 'offline_access')) { return new Result($this->getKey(), false); } diff --git a/tests/unit/src/Helpers/ArrTest.php b/tests/unit/src/Helpers/ArrTest.php index 53e2625c..cac1613a 100644 --- a/tests/unit/src/Helpers/ArrTest.php +++ b/tests/unit/src/Helpers/ArrTest.php @@ -22,13 +22,13 @@ public function testCanFindByCallback(): void 'a', $this->sut()->findByCallback( ['a', 'b', 'c'], - fn($item): bool => $item === 'a' + fn($item): bool => $item === 'a', ), ); $this->assertNull($this->sut()->findByCallback( ['a', 'b', 'c'], - fn($item): bool => $item === 'd' + fn($item): bool => $item === 'd', )); } diff --git a/tests/unit/src/Utils/ScopeHelperTest.php b/tests/unit/src/Helpers/ScopeTest.php similarity index 67% rename from tests/unit/src/Utils/ScopeHelperTest.php rename to tests/unit/src/Helpers/ScopeTest.php index 06736cf6..814d9792 100644 --- a/tests/unit/src/Utils/ScopeHelperTest.php +++ b/tests/unit/src/Helpers/ScopeTest.php @@ -2,18 +2,17 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\oidc\unit\Utils; +namespace SimpleSAML\Test\Module\oidc\unit\Helpers; use League\OAuth2\Server\Entities\ScopeEntityInterface; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\oidc\Helpers\Scope; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; -use SimpleSAML\Module\oidc\Utils\ScopeHelper; -/** - * @covers \SimpleSAML\Module\oidc\Utils\ScopeHelper - */ -class ScopeHelperTest extends TestCase +#[CoversClass(Scope::class)] +class ScopeTest extends TestCase { protected Stub $scopeEntityOpenIdStub; protected Stub $scopeEntityProfileStub; @@ -34,20 +33,25 @@ protected function setUp(): void ]; } + protected function sut(): Scope + { + return new Scope(); + } + /** * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException */ public function testCanCheckScopeExistence(): void { - $this->assertTrue(ScopeHelper::scopeExists($this->scopeEntitiesArray, 'openid')); - $this->assertTrue(ScopeHelper::scopeExists($this->scopeEntitiesArray, 'profile')); - $this->assertFalse(ScopeHelper::scopeExists($this->scopeEntitiesArray, 'invalid')); + $this->assertTrue($this->sut()->exists($this->scopeEntitiesArray, 'openid')); + $this->assertTrue($this->sut()->exists($this->scopeEntitiesArray, 'profile')); + $this->assertFalse($this->sut()->exists($this->scopeEntitiesArray, 'invalid')); } public function testThrowsForInvalidScopeEntity(): void { $this->expectException(OidcServerException::class); - ScopeHelper::scopeExists(['invalid'], 'test'); + $this->sut()->exists(['invalid'], 'test'); } } diff --git a/tests/unit/src/HelpersTest.php b/tests/unit/src/HelpersTest.php index 643ad853..9fb4271d 100644 --- a/tests/unit/src/HelpersTest.php +++ b/tests/unit/src/HelpersTest.php @@ -16,6 +16,7 @@ #[UsesClass(Helpers\Str::class)] #[UsesClass(Helpers\Arr::class)] #[UsesClass(Helpers\Random::class)] +#[UsesClass(Helpers\Scope::class)] class HelpersTest extends TestCase { protected function sut(): Helpers @@ -31,5 +32,6 @@ public function testCanBuildHelpers(): void $this->assertInstanceOf(Helpers\Str::class, $this->sut()->str()); $this->assertInstanceOf(Helpers\Arr::class, $this->sut()->arr()); $this->assertInstanceOf(Helpers\Random::class, $this->sut()->random()); + $this->assertInstanceOf(Helpers\Scope::class, $this->sut()->scope()); } } From bd91ddaf8b4a3bba6725d6d88840818394bcb603 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Tue, 11 Feb 2025 10:50:24 +0100 Subject: [PATCH 66/70] Move to SspBridge in MaxAgeRule --- src/Factories/RequestRulesManagerFactory.php | 3 +++ src/Server/RequestRules/Rules/MaxAgeRule.php | 10 ++++++---- src/Services/Container.php | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Factories/RequestRulesManagerFactory.php b/src/Factories/RequestRulesManagerFactory.php index ebcae807..79dfb9af 100644 --- a/src/Factories/RequestRulesManagerFactory.php +++ b/src/Factories/RequestRulesManagerFactory.php @@ -4,6 +4,7 @@ namespace SimpleSAML\Module\oidc\Factories; +use SimpleSAML\Module\oidc\Bridges\SspBridge; use SimpleSAML\Module\oidc\Factories\Entities\ClientEntityFactory; use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; @@ -60,6 +61,7 @@ public function __construct( private readonly Helpers $helpers, private readonly JwksResolver $jwksResolver, private readonly FederationParticipationValidator $federationParticipationValidator, + private readonly SspBridge $sspBridge, private readonly ?FederationCache $federationCache = null, private readonly ?ProtocolCache $protocolCache = null, ) { @@ -106,6 +108,7 @@ private function getDefaultRules(): array $this->helpers, $this->authSimpleFactory, $this->authenticationService, + $this->sspBridge, ), new ScopeRule($this->requestParamsResolver, $this->helpers, $this->scopeRepository), new RequiredOpenIdScopeRule($this->requestParamsResolver, $this->helpers), diff --git a/src/Server/RequestRules/Rules/MaxAgeRule.php b/src/Server/RequestRules/Rules/MaxAgeRule.php index b3903767..38c4a809 100644 --- a/src/Server/RequestRules/Rules/MaxAgeRule.php +++ b/src/Server/RequestRules/Rules/MaxAgeRule.php @@ -5,6 +5,7 @@ namespace SimpleSAML\Module\oidc\Server\RequestRules\Rules; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Bridges\SspBridge; use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory; use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; @@ -16,7 +17,6 @@ use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; use SimpleSAML\OpenID\Codebooks\ParamsEnum; -use SimpleSAML\Utils\HTTP; class MaxAgeRule extends AbstractRule { @@ -25,6 +25,7 @@ public function __construct( Helpers $helpers, private readonly AuthSimpleFactory $authSimpleFactory, private readonly AuthenticationService $authenticationService, + private readonly SspBridge $sspBridge, ) { parent::__construct($requestParamsResolver, $helpers); } @@ -88,9 +89,10 @@ public function checkRule( if ($isExpired) { unset($requestParams['prompt']); $loginParams = []; - // TODO mivanci Move to SspBridge - $loginParams['ReturnTo'] = (new HTTP()) - ->addURLParameters((new HTTP())->getSelfURLNoQuery(), $requestParams); + $loginParams['ReturnTo'] = $this->sspBridge->utils()->http()->addURLParameters( + $this->sspBridge->utils()->http()->getSelfURLNoQuery(), + $requestParams, + ); $this->authenticationService->authenticate($client, $loginParams); } diff --git a/src/Services/Container.php b/src/Services/Container.php index 951fc125..5f0efc02 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -375,7 +375,7 @@ public function __construct() new RedirectUriRule($requestParamsResolver, $helpers), new RequestObjectRule($requestParamsResolver, $helpers, $jwksResolver), new PromptRule($requestParamsResolver, $helpers, $authSimpleFactory, $authenticationService), - new MaxAgeRule($requestParamsResolver, $helpers, $authSimpleFactory, $authenticationService), + new MaxAgeRule($requestParamsResolver, $helpers, $authSimpleFactory, $authenticationService, $sspBridge), new ScopeRule($requestParamsResolver, $helpers, $scopeRepository), new RequiredOpenIdScopeRule($requestParamsResolver, $helpers), new CodeChallengeRule($requestParamsResolver, $helpers), From 765513f3ad6b5ccbf003626465bc8c7700913c2d Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Tue, 11 Feb 2025 11:00:57 +0100 Subject: [PATCH 67/70] Move to SspBridge in PromptRule --- rector.php | 2 +- src/Factories/RequestRulesManagerFactory.php | 1 + src/ModuleConfig.php | 2 +- src/Server/RequestRules/Rules/PromptRule.php | 10 ++++++---- src/Server/RequestRules/Rules/ResponseTypeRule.php | 2 +- src/Services/AuthenticationService.php | 2 +- src/Services/Container.php | 2 +- templates/config/protocol.twig | 2 +- .../Federation/EntityStatementControllerTest.php | 2 +- 9 files changed, 14 insertions(+), 11 deletions(-) diff --git a/rector.php b/rector.php index a65521ec..5fefbb9f 100644 --- a/rector.php +++ b/rector.php @@ -16,7 +16,7 @@ ]); $rectorConfig->paths([ - // TODO mivanci also go trough commented out paths... + // TODO v7 mivanci also go trough commented out paths... //__DIR__ . '/docker', //__DIR__ . '/hooks', //__DIR__ . '/public', diff --git a/src/Factories/RequestRulesManagerFactory.php b/src/Factories/RequestRulesManagerFactory.php index 79dfb9af..bf28d4da 100644 --- a/src/Factories/RequestRulesManagerFactory.php +++ b/src/Factories/RequestRulesManagerFactory.php @@ -102,6 +102,7 @@ private function getDefaultRules(): array $this->helpers, $this->authSimpleFactory, $this->authenticationService, + $this->sspBridge, ), new MaxAgeRule( $this->requestParamsResolver, diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php index 730758a2..1e50289c 100644 --- a/src/ModuleConfig.php +++ b/src/ModuleConfig.php @@ -234,7 +234,7 @@ public function config(): Configuration return $this->moduleConfig; } - // TODO mivanci Move to dedicated \SimpleSAML\Module\oidc\Utils\Routes::getModuleUrl + // TODO mivanci v7 Move to dedicated \SimpleSAML\Module\oidc\Utils\Routes::getModuleUrl public function getModuleUrl(?string $path = null): string { $base = $this->sspBridge->module()->getModuleURL(self::MODULE_NAME); diff --git a/src/Server/RequestRules/Rules/PromptRule.php b/src/Server/RequestRules/Rules/PromptRule.php index 224a3e97..60fd38cc 100644 --- a/src/Server/RequestRules/Rules/PromptRule.php +++ b/src/Server/RequestRules/Rules/PromptRule.php @@ -6,6 +6,7 @@ use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ServerRequestInterface; +use SimpleSAML\Module\oidc\Bridges\SspBridge; use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory; use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; @@ -16,7 +17,6 @@ use SimpleSAML\Module\oidc\Utils\RequestParamsResolver; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; use SimpleSAML\OpenID\Codebooks\ParamsEnum; -use SimpleSAML\Utils\HTTP; class PromptRule extends AbstractRule { @@ -25,6 +25,7 @@ public function __construct( Helpers $helpers, private readonly AuthSimpleFactory $authSimpleFactory, private readonly AuthenticationService $authenticationService, + private readonly SspBridge $sspBridge, ) { parent::__construct($requestParamsResolver, $helpers); } @@ -82,9 +83,10 @@ public function checkRule( if (in_array('login', $prompt, true) && $authSimple->isAuthenticated()) { unset($requestParams[ParamsEnum::Prompt->value]); $loginParams = []; - // TODO mivanci move to SSP Bridge - $loginParams['ReturnTo'] = (new HTTP()) - ->addURLParameters((new HTTP())->getSelfURLNoQuery(), $requestParams); + $loginParams['ReturnTo'] = $this->sspBridge->utils()->http()->addURLParameters( + $this->sspBridge->utils()->http()->getSelfURLNoQuery(), + $requestParams, + ); $this->authenticationService->authenticate($client, $loginParams); } diff --git a/src/Server/RequestRules/Rules/ResponseTypeRule.php b/src/Server/RequestRules/Rules/ResponseTypeRule.php index a7cc81e5..30acb5ad 100644 --- a/src/Server/RequestRules/Rules/ResponseTypeRule.php +++ b/src/Server/RequestRules/Rules/ResponseTypeRule.php @@ -38,7 +38,7 @@ public function checkRule( throw OidcServerException::invalidRequest('Missing response_type or client_id'); } - // TODO consider checking for supported response types, for example, from configuration... + // TODO v7 consider checking for supported response types, for example, from configuration... return new Result($this->getKey(), $requestParams[ParamsEnum::ResponseType->value]); } diff --git a/src/Services/AuthenticationService.php b/src/Services/AuthenticationService.php index a455660e..4dbba7d9 100644 --- a/src/Services/AuthenticationService.php +++ b/src/Services/AuthenticationService.php @@ -89,7 +89,7 @@ public function processRequest( ServerRequestInterface $request, OAuth2AuthorizationRequest $authorizationRequest, ): array { - // TODO mivanci Fix: client has already been resolved up to this point, but we are again fetching it from DB. + // TODO mivanci v7 Fix: client has already been resolved up to this point, but we are again fetching it from DB. $oidcClient = $this->helpers->client()->getFromRequest($request, $this->clientRepository); $authSimple = $this->authSimpleFactory->build($oidcClient); diff --git a/src/Services/Container.php b/src/Services/Container.php index 5f0efc02..d08a82cb 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -374,7 +374,7 @@ public function __construct() ), new RedirectUriRule($requestParamsResolver, $helpers), new RequestObjectRule($requestParamsResolver, $helpers, $jwksResolver), - new PromptRule($requestParamsResolver, $helpers, $authSimpleFactory, $authenticationService), + new PromptRule($requestParamsResolver, $helpers, $authSimpleFactory, $authenticationService, $sspBridge), new MaxAgeRule($requestParamsResolver, $helpers, $authSimpleFactory, $authenticationService, $sspBridge), new ScopeRule($requestParamsResolver, $helpers, $scopeRepository), new RequiredOpenIdScopeRule($requestParamsResolver, $helpers), diff --git a/templates/config/protocol.twig b/templates/config/protocol.twig index 1c5c1a1c..bf168ab5 100644 --- a/templates/config/protocol.twig +++ b/templates/config/protocol.twig @@ -93,7 +93,7 @@

{% for scope, claims in moduleConfig.getScopes %} {{ scope }}{{ loop.last ? '' : ', ' }} - {# TODO mivanci Add claims or extract scopes to sepparate page. #} + {# TODO v7 mivanci Add claims or extract scopes to sepparate page. #} {% endfor %}

diff --git a/tests/unit/src/Controllers/Federation/EntityStatementControllerTest.php b/tests/unit/src/Controllers/Federation/EntityStatementControllerTest.php index a2fdc291..e2170067 100644 --- a/tests/unit/src/Controllers/Federation/EntityStatementControllerTest.php +++ b/tests/unit/src/Controllers/Federation/EntityStatementControllerTest.php @@ -99,7 +99,7 @@ public function testCanGetConfigurationStatement(): void $this->moduleConfigMock->expects($this->once())->method('getFederationEnabled')->willReturn(true); $this->federationCacheMock->expects($this->once())->method('get')->willReturn(null); - // TODO mivanci + // TODO v7 mivanci $this->markTestIncomplete('Move to simplesamlphp/openid library for building entity statements.'); } } From b98b53e32349b094c83697f0dcf8b7b42cb529e0 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Tue, 11 Feb 2025 11:44:00 +0100 Subject: [PATCH 68/70] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34810467..46550471 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Installation can be as easy as executing: Copy the module config template file to the SimpleSAMLphp config directory: - cp modules/oidc/config/module_oidc.php.dist config/ + cp modules/oidc/config/module_oidc.php.dist config/module_oidc.php The options are self-explanatory, so make sure to go through the file and edit them as appropriate. From d3cdf5f6cafdc621ae1a5ea6e5ddf960b59b2af9 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Tue, 11 Feb 2025 11:57:27 +0100 Subject: [PATCH 69/70] Add client-form.js on Client add form --- templates/clients/add.twig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/templates/clients/add.twig b/templates/clients/add.twig index 9b9d4b4f..2bc49de9 100644 --- a/templates/clients/add.twig +++ b/templates/clients/add.twig @@ -21,3 +21,9 @@ {% include "@oidc/clients/includes/form.twig" %} {% endblock oidcContent -%} + +{% block postload %} + {{ parent() }} + + +{% endblock %} \ No newline at end of file From 512012408029d4fe637aaef73a0bcfd828ea68ce Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Wed, 12 Feb 2025 10:40:07 +0100 Subject: [PATCH 70/70] Update translations (#285) * Start with text message IDs * Update translations * Fix some of the Dutch translations * Add Croatian language * Add es, fr, it langs back as empty translation files --------- Co-authored-by: Tim van Dijen --- locales/en/LC_MESSAGES/oidc.po | 645 ++++++++++++++----- locales/es/LC_MESSAGES/oidc.po | 614 +++++++++++++++--- locales/fr/LC_MESSAGES/oidc.po | 642 ++++++++++++++---- locales/hr/LC_MESSAGES/oidc.po | 614 ++++++++++++++++++ locales/it/LC_MESSAGES/oidc.po | 608 ++++++++++++++--- locales/nl/LC_MESSAGES/oidc.po | 556 +++++++++++++--- src/Forms/ClientForm.php | 20 +- src/Services/Container.php | 7 +- templates/clients.twig | 8 +- templates/clients/includes/form.twig | 4 +- templates/clients/show.twig | 8 +- templates/config/migrations.twig | 4 +- templates/includes/menu.twig | 2 +- templates/logout.twig | 10 +- templates/tests/trust-chain-resolution.twig | 8 +- tests/unit/src/Factories/FormFactoryTest.php | 66 ++ 16 files changed, 3244 insertions(+), 572 deletions(-) create mode 100644 locales/hr/LC_MESSAGES/oidc.po create mode 100644 tests/unit/src/Factories/FormFactoryTest.php diff --git a/locales/en/LC_MESSAGES/oidc.po b/locales/en/LC_MESSAGES/oidc.po index ec4a6a11..a754c677 100644 --- a/locales/en/LC_MESSAGES/oidc.po +++ b/locales/en/LC_MESSAGES/oidc.po @@ -1,219 +1,566 @@ -msgid "{oidc:add_client}" -msgstr "Add client" +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Domain: oidc\n" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:372 +msgid "-" +msgstr "-" + +msgid "" +"A globally unique URI that is bound to the entity. URI must have https or " +"http scheme and host / domain. It can contain path, but no query, or " +"fragment component." +msgstr "" -msgid "{oidc:search}" -msgstr "Search" +msgid "Access Token" +msgstr "" -msgid "{oidc:no_clients}" -msgstr "No clients" +msgid "Activated" +msgstr "" -msgid "{oidc:client_list}" -msgstr "Client list" +msgid "Add Client" +msgstr "" -msgid "{oidc:client:name}" -msgstr "Name" +msgid "Administrator" +msgstr "" -msgid "{oidc:client:description}" -msgstr "Description" +msgid "All database migrations are implemented." +msgstr "" -msgid "{oidc:client:identifier}" -msgstr "Client ID" +msgid "Allowed Origins" +msgstr "" -msgid "{oidc:client:secret}" -msgstr "Client secret" +msgid "Allowed Origins (for public client)" +msgstr "" -msgid "{oidc:client:auth_source}" -msgstr "Authentication source" +msgid "Allowed origins for public clients" +msgstr "" -msgid "{oidc:client:redirect_uri}" -msgstr "Redirect URIs" +msgid "" +"Allowed redirect URIs to use after client initiated logout. Must be a valid " +"URI, one per line. Example: https://example.org/foo?bar=1" +msgstr "" -msgid "{oidc:client:scopes}" -msgstr "Scopes" +msgid "" +"Allowed redirect URIs to which the authorization response will be sent. Must " +"be a valid URI, one per line. Example: https://example.org/foo?bar=1" +msgstr "" -msgid "{oidc:client:owner}" -msgstr "Owner" +msgid "Are you sure you want to delete this client?" +msgstr "" -msgid "{oidc:submit}" -msgstr "Submit" +msgid "Are you sure you want to reset client secret?" +msgstr "" -msgid "{oidc:create}" -msgstr "Create" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:363 +msgid "At least one redirect URI is required." +msgstr "" -msgid "{oidc:save}" -msgstr "Save" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:378 +msgid "At least one scope is required." +msgstr "" -msgid "{oidc:return}" -msgstr "Return" +msgid "Authentication" +msgstr "" -msgid "{oidc:install}" -msgstr "Install" +msgid "Authentication Context Class References (ACRs)" +msgstr "" -msgid "{oidc:copy}" -msgstr "Copy code" +msgid "Authentication Processing Filters" +msgstr "" -msgid "{oidc:copied}" -msgstr "Copied!" +msgid "Authentication Source" +msgstr "" -msgid "{oidc:confirm}" -msgstr "Confirm" +msgid "Authentication Sources to ACRs Map" +msgstr "" -msgid "{oidc:client:delete}" -msgstr "Delete OpenID Connect Client" +msgid "" +"Authentication source for this particular client. If no authentication " +"source is selected, the default one from configuration file will be used." +msgstr "" -msgid "{oidc:client:confirm_delete}" -msgstr "Please, confirm than you want to delete this client. This action cannot be undone." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:369 +msgid "Authentication source" +msgstr "" -msgid "{oidc:edit}" -msgstr "Edit" +msgid "Authority Hints" +msgstr "" -msgid "{oidc:delete}" -msgstr "Delete" +msgid "Authorization Code" +msgstr "" -msgid "{oidc:client:added}" -msgstr "The client was added successfully." +msgid "Back" +msgstr "" -msgid "{oidc:client:removed}" -msgstr "The client was removed successfully." +msgid "Back-channel Logout URI" +msgstr "" -msgid "{oidc:client:updated}" -msgstr "The client was updated successfully." +msgid "Before running the migrations, make sure that the database user has proper privileges to change the scheme (for example alter, create, drop, index). After running the migrations, it is a good practice to remove those privileges." +msgstr "" -msgid "{oidc:client:redirect_uri_help}" +msgid "" +"By default, form is populated with current OP issuer and configured Trust " +"Anchors, but you are free to adjust entries as needed." msgstr "" -"Allowed redirect URIs to which the authorization response will be sent. Must be a valid URI, one per line. " -"Example: https://example.org/foo?bar=1" -"" -msgid "{oidc:client:auth_source_help}" +msgid "Cache" msgstr "" -"Authentication source for this particular client. " -"If no authentication source is selected, the default one from configuration file will be used." -"" -msgid "{oidc:client:name_not_empty}" -msgstr "Please, enter a name." +msgid "Cache Adapter" +msgstr "" -msgid "{oidc:client:redirect_uri_not_empty}" -msgstr "Please, enter an URI at least." +msgid "Cache Duration For Produced Artifacts" +msgstr "" -msgid "{oidc:client:redirect_uri_not_valid}" -msgstr "Some of the redirect URIs are not valid." +msgid "" +"Choose if client is confidential or public. Confidential clients are capable " +"of maintaining the confidentiality of their credentials (e.g., client " +"implemented on a secure server with restricted access to the client " +"credentials), or capable of secure client authentication using other means. " +"Public clients are incapable of maintaining the confidentiality of their " +"credentials (e.g., clients executing on the device used by the resource " +"owner, such as an installed native application or a web browser-based " +"application), and incapable of secure client authentication via any other " +"means." +msgstr "" -msgid "{oidc:client:auth_source_not_empty}" -msgstr "Please, select an Auth Source." +msgid "" +"Choose if the client is allowed to participate in federation context or not." +msgstr "" -msgid "{oidc:client:scopes_not_empty}" -msgstr "Please, select a scope at least." +msgid "Client" +msgstr "" -msgid "{oidc:client:reset_secret}" -msgstr "Reset secret" +msgid "Client Registration Types" +msgstr "" -msgid "{oidc:client:reset_secret_warning}" -msgstr "This action will change your client secret and it can not be undone." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:113 +msgid "Client Registry" +msgstr "" -msgid "{oidc:client:secret_updated}" -msgstr "The client secret was updated successfully." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:201 +msgid "Client has been added." +msgstr "" -msgid "{oidc:install:oauth2}" -msgstr "Check if you want to migrate data from legacy oauth2 module" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:147 +msgid "Client has been deleted." +msgstr "" -msgid "{oidc:install:description}" -msgstr "This wizard will help you create the database and migrate information if necessary." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:277 +msgid "Client has been updated." +msgstr "" -msgid "{oidc:install:finished}" -msgstr "The database has been created." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:120 +msgid "Client secret has been reset." +msgstr "" -msgid "{oidc:import:finished}" -msgstr "Old oauth2 module clients has been imported." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:183 +msgid "Client with generated ID already exists." +msgstr "" -msgid "{oidc:client:is_enabled}" -msgstr "Activated" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:190 +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:265 +msgid "Client with given entity identifier already exists." +msgstr "" -msgid "{oidc:client:deactivated}" -msgstr "Deactivated" +msgid "Confidential" +msgstr "" -msgid "{oidc:title}" -msgstr "OpenID Connect Client Registry" +msgid "Configuration URL" +msgstr "" -msgid "{oidc:client:confidential}" -msgstr "Confidential" +msgid "Contacts" +msgstr "" -msgid "{oidc:client:confidential_help}" +msgid "Created at" msgstr "" -"Choose if client is confidential or public. Confidential clients are capable of maintaining the confidentiality " -"of their credentials (e.g., client implemented on a secure server with restricted access to the client credentials), " -"or capable of secure client authentication using other means. Public clients are incapable of maintaining the " -"confidentiality of their credentials (e.g., clients executing on the device used by the resource owner, such as an " -"installed native application or a web browser-based application), and incapable of secure client authentication via " -"any other means. " -"" -msgid "{oidc:client:public}" -msgstr "Public" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:106 +msgid "Database Migrations" +msgstr "" -msgid "{oidc:client:type}" -msgstr "Type" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ConfigController.php:46 +msgid "Database is already migrated." +msgstr "" -msgid "{oidc:client:client}" -msgstr "Client" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ConfigController.php:52 +msgid "Database migrated successfully." +msgstr "" -msgid "{oidc:client:state}" -msgstr "State" +msgid "Default Authentication Source" +msgstr "" -msgid "{oidc:client:csrf_error}" -msgstr "Your session has expired. Please return to the home page and try again." +msgid "Delete" +msgstr "" -msgid "{oidc:client:allowed_origin}" -msgstr "Allowed origins for public clients" +msgid "Description" +msgstr "" -msgid "{oidc:client:allowed_origin_help}" +msgid "Disabled" msgstr "" -"URLs as allowed origins for CORS requests, for public clients running in browser. Must have http:// or https:// " -"scheme, and at least one 'domain.top-level-domain' pair, or more subdomains. Top-level-domain may end with '.'. " -"No userinfo, path, query or fragment components allowed. May end with port number. One per line. Example: " -"https://example.org" -"" -msgid "{oidc:client:allowed_origin_not_valid}" -msgstr "Some of the allowed origins are not valid." +msgid "Discovery URL" +msgstr "" -msgid "{oidc:client:post_logout_redirect_uri}" -msgstr "Post-logout redirect URIs" +msgid "Edit" +msgstr "" -msgid "{oidc:client:post_logout_redirect_uri_help}" +msgid "Edit Client" msgstr "" -"Allowed redirect URIs to use after client initiated logout. Must be a valid URI, one per line. " -"Example: https://example.org/foo?bar=1" -"" -msgid "{oidc:client:post_logout_redirect_uri_not_valid}" -msgstr "Some of the post-logout redirect URIs are not valid." +msgid "Enabled" +msgstr "" -msgid "{oidc:client:backchannel_logout_uri}" -msgstr "Back-Channel Logout URI" +msgid "" +"Enter if client supports Back-Channel Logout specification. When logout is " +"initiated at the OpenID Provider, it will send a Logout Token to this URI in " +"order to notify the client about that event. Must be a valid URI. Example: " +"https://example.org/foo?bar=1" +msgstr "" -msgid "{oidc:client:backchannel_logout_uri_help}" +msgid "Enter one Trust Anchor ID per line." msgstr "" -"Enter if client supports Back-Channel Logout specification. When logout is initiated at the OpenID Provider, it will " -"send a Logout Token to this URI in order to notify the client about that event. Must be a valid URI. " -"Example: https://example.org/foo?bar=1" -"" -msgid "{oidc:client:backchannel_logout_uri_not_valid}" -msgstr "Back-Channel Logout URI is not valid." +msgid "Entity" +msgstr "" -msgid "{oidc:logout:page_title_success}" -msgstr "Logout Successful" +msgid "Entity Identifier" +msgstr "" -msgid "{oidc:logout:page_title_fail}" -msgstr "Logout Failed" +msgid "Entity Statement Duration" +msgstr "" -msgid "{oidc:logout:info_title}" -msgstr "Info" +msgid "Expires at" +msgstr "" -msgid "{oidc:logout:info_message_success}" -msgstr "You can now close this window or navigate to another page." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Codebooks/RegistrationTypeEnum.php:18 +msgid "Federated Automatic" +msgstr "" -msgid "{oidc:logout:info_message_fail}" -msgstr "Requested session was not found or it is expired." \ No newline at end of file +msgid "Federation Enabled" +msgstr "" + +msgid "Federation JWKS" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:127 +msgid "Federation Settings" +msgstr "" + +msgid "Forced ACR For Cookie Authentication" +msgstr "" + +msgid "Homepage URI" +msgstr "" + +msgid "Identifier" +msgstr "" + +msgid "Info" +msgstr "" + +msgid "Is Federated" +msgstr "" + +msgid "Issuer" +msgstr "" + +msgid "" +"JSON object (string) representing JWKS document containing protocol public " +"keys. Note that this should be different from Federation JWKS. Will be used " +"if JWKS URI is not set. Example: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\"," +"\"e\": \"AQAB\",\"kid\": \"pro123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" +msgstr "" + +msgid "" +"JSON object (string) representing federation JWKS. This can be used, for " +"example, in entity statements. Note that this should be different from " +"Protocol JWKS. Example: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\",\"e\": " +"\"AQAB\",\"kid\": \"fed123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" +msgstr "" + +msgid "JWKS" +msgstr "" + +msgid "JWKS URI" +msgstr "" + +msgid "Leaf Entity ID" +msgstr "" + +msgid "Log messages" +msgstr "" + +msgid "" +"Log messages will show if any warnings or errors were raised during chain " +"resolution." +msgstr "" + +msgid "" +"Log messages will show if any warnings or errors were raised during " +"validation." +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:90 +msgid "Log out" +msgstr "" + +msgid "Logo URI" +msgstr "" + +msgid "Logout Failed" +msgstr "" + +msgid "Logout Info" +msgstr "" + +msgid "Logout Successful" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Codebooks/RegistrationTypeEnum.php:17 +msgid "Manual" +msgstr "" + +msgid "Maximum Cache Duration For Fetched Artifacts" +msgstr "" + +msgid "N/A" +msgstr "" + +msgid "Name" +msgstr "" + +msgid "Name and description" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:357 +msgid "Name is required." +msgstr "" + +msgid "never" +msgstr "" + +msgid "No" +msgstr "" + +msgid "No clients registered." +msgstr "" + +msgid "No entries." +msgstr "" + +msgid "" +"Note that this will first resolve Trust Chain between given entity and Trust " +"Anchor, and only then do the Trust Mark validation." +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_adminmenu.php:24 +msgid "OIDC" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_federationpage.php:34 +msgid "OIDC Client Registry" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_federationpage.php:38 +msgid "OIDC Installation" +msgstr "" + +msgid "" +"One or more values from the list. If not selected, falls back to 'automatic'" +msgstr "" + +msgid "OpenID Federation Related Properties" +msgstr "" + +msgid "Organization Name" +msgstr "" + +msgid "Owner" +msgstr "" + +msgid "PKI" +msgstr "" + +msgid "Path" +msgstr "" + +msgid "Policy URI" +msgstr "" + +msgid "Post-logout Redirect URIs" +msgstr "" + +msgid "Private Key" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:120 +msgid "Protocol Settings" +msgstr "" + +msgid "Public" +msgstr "" + +msgid "Public Key" +msgstr "" + +msgid "Redirect URI" +msgstr "" + +msgid "Redirect URIs" +msgstr "" + +msgid "Refresh Token" +msgstr "" + +msgid "Registration Types" +msgstr "" + +msgid "Registration" +msgstr "" + +msgid "Requested session was not found or it is expired." +msgstr "" + +msgid "Reset" +msgstr "" + +msgid "Resolved chains" +msgstr "" + +msgid "Run migrations" +msgstr "" + +msgid "Scopes" +msgstr "" + +msgid "Secret" +msgstr "" + +msgid "Signed JWKS URI" +msgstr "" + +msgid "Signing Algorithm" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:44 +msgid "SimpleSAMLphp admin access required." +msgstr "" + +msgid "Status" +msgstr "" + +msgid "Supported ACRs" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:134 +msgid "Test Trust Chain Resolution" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:141 +msgid "Test Trust Mark Validation" +msgstr "" + +msgid "" +"There are database migrations that have not been implemented.\n" +" Use the button below to run them now." +msgstr "" + +msgid "Tokens Time-To-Live (TTL)" +msgstr "" + +msgid "Total chains" +msgstr "" + +msgid "Trust Anchor ID" +msgstr "" + +msgid "Trust Anchor IDs" +msgstr "" + +msgid "Trust Anchors" +msgstr "" + +msgid "Trust Mark ID" +msgstr "" + +msgid "" +"Trust Mark validation passed (there were no warnings or errors during " +"validation)." +msgstr "" + +msgid "Trust Marks" +msgstr "" + +msgid "Type" +msgstr "Type" + +msgid "" +"URL to a JWKS document containing protocol public keys. Will be used if " +"Signed JWKS URI is not set. Example: https://example.org/jwks" +msgstr "" + +msgid "" +"URL to a JWS document containing protocol public keys in JWKS format (claim " +"'keys'). Example: https://example.org/signed-jwks" +msgstr "" + +msgid "" +"URLs as allowed origins for CORS requests, for public clients running in " +"browser. Must have http:// or https:// scheme, and at least one 'domain.top-" +"level-domain' pair, or more subdomains. Top-level-domain may end with '.'. " +"No userinfo, path, query or fragment components allowed. May end with port " +"number. One per line. Example: https://example.org" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:36 +msgid "Unable to initiate SimpleSAMLphp admin authentication." +msgstr "" + +msgid "Updated at" +msgstr "" + +msgid "User Entity Cache Duration" +msgstr "" + +msgid "User Identifier Attribute" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:61 +msgid "User not authorized." +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "You can now close this window or navigate to another page." +msgstr "" + +msgid "" +"You can use the form below to test Trust Chain resolution from a leaf entity " +"ID to Trust Anchors." +msgstr "" + +msgid "" +"You can use the form below to test Trust Mark validation for particular " +"entity under given Trust Anchor." +msgstr "" + +msgid "Your session has expired. Please return to the home page and try again." +msgstr "" + +msgid "disabled" +msgstr "" + +msgid "enabled" +msgstr "" diff --git a/locales/es/LC_MESSAGES/oidc.po b/locales/es/LC_MESSAGES/oidc.po index 49358c34..299a0f78 100644 --- a/locales/es/LC_MESSAGES/oidc.po +++ b/locales/es/LC_MESSAGES/oidc.po @@ -1,140 +1,566 @@ -msgid "OpenID Connect Client Registry" -msgstr "Registro de clientes OpenID Connect" +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Domain: oidc\n" -msgid "{oidc:add_client}" -msgstr "Añadir cliente" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:372 +msgid "-" +msgstr "-" -msgid "{oidc:search}" -msgstr "Buscar" +msgid "" +"A globally unique URI that is bound to the entity. URI must have https or " +"http scheme and host / domain. It can contain path, but no query, or " +"fragment component." +msgstr "" -msgid "{oidc:no_clients}" -msgstr "No hay clientes" +msgid "Access Token" +msgstr "" -msgid "{oidc:client_list}" -msgstr "Lista de clientes" +msgid "Activated" +msgstr "" -msgid "{oidc:client:name}" -msgstr "Nombre" +msgid "Add Client" +msgstr "" -msgid "{oidc:client:description}" -msgstr "Descripción" +msgid "Administrator" +msgstr "" -msgid "{oidc:client:identifier}" -msgstr "Client id." +msgid "All database migrations are implemented." +msgstr "" -msgid "{oidc:client:secret}" -msgstr "Client secret" +msgid "Allowed Origins" +msgstr "" -msgid "{oidc:client:auth_source}" -msgstr "Auth. source" +msgid "Allowed Origins (for public client)" +msgstr "" -msgid "{oidc:client:redirect_uri}" -msgstr "URI de redirección" +msgid "Allowed origins for public clients" +msgstr "" -msgid "{oidc:client:scopes}" -msgstr "Scopes" +msgid "" +"Allowed redirect URIs to use after client initiated logout. Must be a valid " +"URI, one per line. Example: https://example.org/foo?bar=1" +msgstr "" -msgid "{oidc:submit}" -msgstr "Enviar" +msgid "" +"Allowed redirect URIs to which the authorization response will be sent. Must " +"be a valid URI, one per line. Example: https://example.org/foo?bar=1" +msgstr "" -msgid "{oidc:create}" -msgstr "Crear" +msgid "Are you sure you want to delete this client?" +msgstr "" -msgid "{oidc:save}" -msgstr "Guardar" +msgid "Are you sure you want to reset client secret?" +msgstr "" -msgid "{oidc:return}" -msgstr "Volver" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:363 +msgid "At least one redirect URI is required." +msgstr "" -msgid "{oidc:install}" -msgstr "Instalar" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:378 +msgid "At least one scope is required." +msgstr "" -msgid "{oidc:copy}" -msgstr "Copiar código" +msgid "Authentication" +msgstr "" -msgid "{oidc:copied}" -msgstr "¡Copiado!" +msgid "Authentication Context Class References (ACRs)" +msgstr "" -msgid "{oidc:confirm}" -msgstr "Confirmar" +msgid "Authentication Processing Filters" +msgstr "" -msgid "{oidc:client:delete}" -msgstr "Borrar Client OpenID Connect" +msgid "Authentication Source" +msgstr "" -msgid "{oidc:client:confirm_delete}" -msgstr "Por favor, confirme que desea borrar este cliente. Esta acción no se puede deshacer." +msgid "Authentication Sources to ACRs Map" +msgstr "" -msgid "{oidc:edit}" -msgstr "Editar" +msgid "" +"Authentication source for this particular client. If no authentication " +"source is selected, the default one from configuration file will be used." +msgstr "" -msgid "{oidc:delete}" -msgstr "Borrar" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:369 +msgid "Authentication source" +msgstr "" -msgid "{oidc:client:added}" -msgstr "El cliente fue añadido con éxito." +msgid "Authority Hints" +msgstr "" -msgid "{oidc:client:removed}" -msgstr "El cliente fue eliminado con éxito." +msgid "Authorization Code" +msgstr "" -msgid "{oidc:client:updated}" -msgstr "El cliente fue actualizado con éxito." +msgid "Back" +msgstr "" -msgid "{oidc:client:redirect_uri_help}" -msgstr "Añada una URI válida por línea" +msgid "Back-channel Logout URI" +msgstr "" -msgid "{oidc:client:auth_source_help}" -msgstr "If no auth. source is selected, the default one from configuration file will be used." +msgid "Before running the migrations, make sure that the database user has proper privileges to change the scheme (for example alter, create, drop, index). After running the migrations, it is a good practice to remove those privileges." +msgstr "" -msgid "{oidc:client:name_not_empty}" -msgstr "El nombre no puede estar en blanco." +msgid "" +"By default, form is populated with current OP issuer and configured Trust " +"Anchors, but you are free to adjust entries as needed." +msgstr "" -msgid "{oidc:client:redirect_uri_not_empty}" -msgstr "Añada al menos una dirección." +msgid "Cache" +msgstr "" -msgid "{oidc:client:redirect_uri_not_valid}" -msgstr "Algunas de las direcciones de redirección no son válidas." +msgid "Cache Adapter" +msgstr "" -msgid "{oidc:client:auth_source_not_empty}" -msgstr "Seleccione un AuthSource." +msgid "Cache Duration For Produced Artifacts" +msgstr "" -msgid "{oidc:client:scopes_not_empty}" -msgstr "Seleccione al menos un scope." +msgid "" +"Choose if client is confidential or public. Confidential clients are capable " +"of maintaining the confidentiality of their credentials (e.g., client " +"implemented on a secure server with restricted access to the client " +"credentials), or capable of secure client authentication using other means. " +"Public clients are incapable of maintaining the confidentiality of their " +"credentials (e.g., clients executing on the device used by the resource " +"owner, such as an installed native application or a web browser-based " +"application), and incapable of secure client authentication via any other " +"means." +msgstr "" -msgid "{oidc:client:reset_secret}" -msgstr "Resetear secreto" +msgid "" +"Choose if the client is allowed to participate in federation context or not." +msgstr "" -msgid "{oidc:client:reset_secret_warning}" -msgstr "Esta acción cambiará su secreto de cliente y no puede deshacerse." +msgid "Client" +msgstr "" -msgid "{oidc:client:secret_updated}" -msgstr "El secreto de cliente fue actualizado con éxito." +msgid "Client Registration Types" +msgstr "" -msgid "{oidc:install:oauth2}" -msgstr "Marque si quiere migrar los datos del módulo obsoleto oauth2" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:113 +msgid "Client Registry" +msgstr "" -msgid "{oidc:install:description}" -msgstr "Este asistente le ayudará a crear la base de datos y a migrar información si fuera necesario." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:201 +msgid "Client has been added." +msgstr "" -msgid "{oidc:install:finished}" -msgstr "La base de datos ha sido creada." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:147 +msgid "Client has been deleted." +msgstr "" -msgid "{oidc:import:finished}" -msgstr "Los clientes del módulo oauth2 han sido importados." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:277 +msgid "Client has been updated." +msgstr "" -msgid "{oidc:client:is_enabled}" -msgstr "Activado" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:120 +msgid "Client secret has been reset." +msgstr "" -msgid "{oidc:oidc:title}" -msgstr "Registro de clientes OpenID Connect" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:183 +msgid "Client with generated ID already exists." +msgstr "" -msgid "{oidc:client:is_confidential}" -msgstr "Cliente confidencial" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:190 +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:265 +msgid "Client with given entity identifier already exists." +msgstr "" -msgid "{oidc:client:is_confidential_help}" -msgstr "Elija si el cliente es confidencial o público." +msgid "Confidential" +msgstr "" -msgid "{oidc:client:state}" -msgstr "Estado" +msgid "Configuration URL" +msgstr "" -msgid "{oidc:client:csrf_error}" -msgstr "Su sesión ha expirado. Por favor, vuelva a la página de inicio e inténtelo de nuevo." +msgid "Contacts" +msgstr "" + +msgid "Created at" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:106 +msgid "Database Migrations" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ConfigController.php:46 +msgid "Database is already migrated." +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ConfigController.php:52 +msgid "Database migrated successfully." +msgstr "" + +msgid "Default Authentication Source" +msgstr "" + +msgid "Delete" +msgstr "" + +msgid "Description" +msgstr "" + +msgid "Disabled" +msgstr "" + +msgid "Discovery URL" +msgstr "" + +msgid "Edit" +msgstr "" + +msgid "Edit Client" +msgstr "" + +msgid "Enabled" +msgstr "" + +msgid "" +"Enter if client supports Back-Channel Logout specification. When logout is " +"initiated at the OpenID Provider, it will send a Logout Token to this URI in " +"order to notify the client about that event. Must be a valid URI. Example: " +"https://example.org/foo?bar=1" +msgstr "" + +msgid "Enter one Trust Anchor ID per line." +msgstr "" + +msgid "Entity" +msgstr "" + +msgid "Entity Identifier" +msgstr "" + +msgid "Entity Statement Duration" +msgstr "" + +msgid "Expires at" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Codebooks/RegistrationTypeEnum.php:18 +msgid "Federated Automatic" +msgstr "" + +msgid "Federation Enabled" +msgstr "" + +msgid "Federation JWKS" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:127 +msgid "Federation Settings" +msgstr "" + +msgid "Forced ACR For Cookie Authentication" +msgstr "" + +msgid "Homepage URI" +msgstr "" + +msgid "Identifier" +msgstr "" + +msgid "Info" +msgstr "" + +msgid "Is Federated" +msgstr "" + +msgid "Issuer" +msgstr "" + +msgid "" +"JSON object (string) representing JWKS document containing protocol public " +"keys. Note that this should be different from Federation JWKS. Will be used " +"if JWKS URI is not set. Example: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\"," +"\"e\": \"AQAB\",\"kid\": \"pro123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" +msgstr "" + +msgid "" +"JSON object (string) representing federation JWKS. This can be used, for " +"example, in entity statements. Note that this should be different from " +"Protocol JWKS. Example: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\",\"e\": " +"\"AQAB\",\"kid\": \"fed123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" +msgstr "" + +msgid "JWKS" +msgstr "" + +msgid "JWKS URI" +msgstr "" + +msgid "Leaf Entity ID" +msgstr "" + +msgid "Log messages" +msgstr "" + +msgid "" +"Log messages will show if any warnings or errors were raised during chain " +"resolution." +msgstr "" + +msgid "" +"Log messages will show if any warnings or errors were raised during " +"validation." +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:90 +msgid "Log out" +msgstr "" + +msgid "Logo URI" +msgstr "" + +msgid "Logout Failed" +msgstr "" + +msgid "Logout Info" +msgstr "" + +msgid "Logout Successful" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Codebooks/RegistrationTypeEnum.php:17 +msgid "Manual" +msgstr "" + +msgid "Maximum Cache Duration For Fetched Artifacts" +msgstr "" + +msgid "N/A" +msgstr "" + +msgid "Name" +msgstr "" + +msgid "Name and description" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:357 +msgid "Name is required." +msgstr "" + +msgid "never" +msgstr "" + +msgid "No" +msgstr "" + +msgid "No clients registered." +msgstr "" + +msgid "No entries." +msgstr "" + +msgid "" +"Note that this will first resolve Trust Chain between given entity and Trust " +"Anchor, and only then do the Trust Mark validation." +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_adminmenu.php:24 +msgid "OIDC" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_federationpage.php:34 +msgid "OIDC Client Registry" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_federationpage.php:38 +msgid "OIDC Installation" +msgstr "" + +msgid "" +"One or more values from the list. If not selected, falls back to 'automatic'" +msgstr "" + +msgid "OpenID Federation Related Properties" +msgstr "" + +msgid "Organization Name" +msgstr "" + +msgid "Owner" +msgstr "" + +msgid "PKI" +msgstr "" + +msgid "Path" +msgstr "" + +msgid "Policy URI" +msgstr "" + +msgid "Post-logout Redirect URIs" +msgstr "" + +msgid "Private Key" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:120 +msgid "Protocol Settings" +msgstr "" + +msgid "Public" +msgstr "" + +msgid "Public Key" +msgstr "" + +msgid "Redirect URI" +msgstr "" + +msgid "Redirect URIs" +msgstr "" + +msgid "Refresh Token" +msgstr "" + +msgid "Registration Types" +msgstr "" + +msgid "Registration" +msgstr "" + +msgid "Requested session was not found or it is expired." +msgstr "" + +msgid "Reset" +msgstr "" + +msgid "Resolved chains" +msgstr "" + +msgid "Run migrations" +msgstr "" + +msgid "Scopes" +msgstr "" + +msgid "Secret" +msgstr "" + +msgid "Signed JWKS URI" +msgstr "" + +msgid "Signing Algorithm" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:44 +msgid "SimpleSAMLphp admin access required." +msgstr "" + +msgid "Status" +msgstr "" + +msgid "Supported ACRs" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:134 +msgid "Test Trust Chain Resolution" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:141 +msgid "Test Trust Mark Validation" +msgstr "" + +msgid "" +"There are database migrations that have not been implemented.\n" +" Use the button below to run them now." +msgstr "" + +msgid "Tokens Time-To-Live (TTL)" +msgstr "" + +msgid "Total chains" +msgstr "" + +msgid "Trust Anchor ID" +msgstr "" + +msgid "Trust Anchor IDs" +msgstr "" + +msgid "Trust Anchors" +msgstr "" + +msgid "Trust Mark ID" +msgstr "" + +msgid "" +"Trust Mark validation passed (there were no warnings or errors during " +"validation)." +msgstr "" + +msgid "Trust Marks" +msgstr "" + +msgid "Type" +msgstr "Type" + +msgid "" +"URL to a JWKS document containing protocol public keys. Will be used if " +"Signed JWKS URI is not set. Example: https://example.org/jwks" +msgstr "" + +msgid "" +"URL to a JWS document containing protocol public keys in JWKS format (claim " +"'keys'). Example: https://example.org/signed-jwks" +msgstr "" + +msgid "" +"URLs as allowed origins for CORS requests, for public clients running in " +"browser. Must have http:// or https:// scheme, and at least one 'domain.top-" +"level-domain' pair, or more subdomains. Top-level-domain may end with '.'. " +"No userinfo, path, query or fragment components allowed. May end with port " +"number. One per line. Example: https://example.org" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:36 +msgid "Unable to initiate SimpleSAMLphp admin authentication." +msgstr "" + +msgid "Updated at" +msgstr "" + +msgid "User Entity Cache Duration" +msgstr "" + +msgid "User Identifier Attribute" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:61 +msgid "User not authorized." +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "You can now close this window or navigate to another page." +msgstr "" + +msgid "" +"You can use the form below to test Trust Chain resolution from a leaf entity " +"ID to Trust Anchors." +msgstr "" + +msgid "" +"You can use the form below to test Trust Mark validation for particular " +"entity under given Trust Anchor." +msgstr "" + +msgid "Your session has expired. Please return to the home page and try again." +msgstr "" + +msgid "disabled" +msgstr "" + +msgid "enabled" +msgstr "" diff --git a/locales/fr/LC_MESSAGES/oidc.po b/locales/fr/LC_MESSAGES/oidc.po index d171b146..fe3cd317 100644 --- a/locales/fr/LC_MESSAGES/oidc.po +++ b/locales/fr/LC_MESSAGES/oidc.po @@ -5,188 +5,562 @@ msgstr "" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: \n" +"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: fr\n" -"X-Generator: Poedit 2.2.1\n" +"X-Domain: oidc\n" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:372 +msgid "-" +msgstr "-" + +msgid "" +"A globally unique URI that is bound to the entity. URI must have https or " +"http scheme and host / domain. It can contain path, but no query, or " +"fragment component." +msgstr "" + +msgid "Access Token" +msgstr "" + +msgid "Activated" +msgstr "" + +msgid "Add Client" +msgstr "" + +msgid "Administrator" +msgstr "" + +msgid "All database migrations are implemented." +msgstr "" + +msgid "Allowed Origins" +msgstr "" -#, fuzzy -msgid "{oidc:add_client}" -msgstr "Ajouter un client" +msgid "Allowed Origins (for public client)" +msgstr "" -#, fuzzy -msgid "{oidc:search}" -msgstr "Rechercher" +msgid "Allowed origins for public clients" +msgstr "" -#, fuzzy -msgid "{oidc:no_clients}" -msgstr "Aucun client" +msgid "" +"Allowed redirect URIs to use after client initiated logout. Must be a valid " +"URI, one per line. Example: https://example.org/foo?bar=1" +msgstr "" -#, fuzzy -msgid "{oidc:client_list}" -msgstr "Liste des clients" +msgid "" +"Allowed redirect URIs to which the authorization response will be sent. Must " +"be a valid URI, one per line. Example: https://example.org/foo?bar=1" +msgstr "" + +msgid "Are you sure you want to delete this client?" +msgstr "" -#, fuzzy -msgid "{oidc:client:name}" -msgstr "Nom" +msgid "Are you sure you want to reset client secret?" +msgstr "" -msgid "{oidc:client:description}" -msgstr "Description" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:363 +msgid "At least one redirect URI is required." +msgstr "" -msgid "{oidc:client:identifier}" -msgstr "Client id." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:378 +msgid "At least one scope is required." +msgstr "" -msgid "{oidc:client:secret}" -msgstr "Client secret" +msgid "Authentication" +msgstr "" -msgid "{oidc:client:auth_source}" -msgstr "Auth. source" +msgid "Authentication Context Class References (ACRs)" +msgstr "" -msgid "{oidc:client:redirect_uri}" -msgstr "Redirect URI" +msgid "Authentication Processing Filters" +msgstr "" -msgid "{oidc:client:scopes}" -msgstr "Scopes" +msgid "Authentication Source" +msgstr "" -#, fuzzy -msgid "{oidc:submit}" -msgstr "Soumettre" +msgid "Authentication Sources to ACRs Map" +msgstr "" -#, fuzzy -msgid "{oidc:create}" -msgstr "Créer" +msgid "" +"Authentication source for this particular client. If no authentication " +"source is selected, the default one from configuration file will be used." +msgstr "" -#, fuzzy -msgid "{oidc:save}" -msgstr "Enregistrer" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:369 +msgid "Authentication source" +msgstr "" -#, fuzzy -msgid "{oidc:return}" -msgstr "Retour" +msgid "Authority Hints" +msgstr "" -#, fuzzy -msgid "{oidc:install}" -msgstr "Installer" +msgid "Authorization Code" +msgstr "" -#, fuzzy -msgid "{oidc:copy}" -msgstr "Copier le code" +msgid "Back" +msgstr "" -#, fuzzy -msgid "{oidc:copied}" -msgstr "Copié!" +msgid "Back-channel Logout URI" +msgstr "" -#, fuzzy -msgid "{oidc:confirm}" -msgstr "Confirmer" +msgid "Before running the migrations, make sure that the database user has proper privileges to change the scheme (for example alter, create, drop, index). After running the migrations, it is a good practice to remove those privileges." +msgstr "" -msgid "{oidc:client:delete}" -msgstr "Détruire le client OpenID Connect" +msgid "" +"By default, form is populated with current OP issuer and configured Trust " +"Anchors, but you are free to adjust entries as needed." +msgstr "" -#, fuzzy -msgid "{oidc:client:confirm_delete}" -msgstr "Confirmer la suppression du client. Cette action est irréversible." +msgid "Cache" +msgstr "" -#, fuzzy -msgid "{oidc:delete}" -msgstr "Supprimer" +msgid "Cache Adapter" +msgstr "" -msgid "{oidc:edit}" -msgstr "Modifier" +msgid "Cache Duration For Produced Artifacts" +msgstr "" -#, fuzzy -msgid "{oidc:client:added}" -msgstr "Ajout du client réussi." +msgid "" +"Choose if client is confidential or public. Confidential clients are capable " +"of maintaining the confidentiality of their credentials (e.g., client " +"implemented on a secure server with restricted access to the client " +"credentials), or capable of secure client authentication using other means. " +"Public clients are incapable of maintaining the confidentiality of their " +"credentials (e.g., clients executing on the device used by the resource " +"owner, such as an installed native application or a web browser-based " +"application), and incapable of secure client authentication via any other " +"means." +msgstr "" -#, fuzzy -msgid "{oidc:client:removed}" -msgstr "Suppression du client réussie." +msgid "" +"Choose if the client is allowed to participate in federation context or not." +msgstr "" -#, fuzzy -msgid "{oidc:client:updated}" -msgstr "Mise à jour du client réussie." +msgid "Client" +msgstr "" -#, fuzzy -msgid "{oidc:client:redirect_uri_help}" -msgstr "Ajouter un URI valide." +msgid "Client Registration Types" +msgstr "" -msgid "{oidc:client:auth_source_help}" -msgstr "If no auth. source is selected, the default one from configuration file will be used." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:113 +msgid "Client Registry" +msgstr "" -#, fuzzy -msgid "{oidc:client:name_not_empty}" -msgstr "Saisir un nom pour le client." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:201 +msgid "Client has been added." +msgstr "" -#, fuzzy -msgid "{oidc:client:redirect_uri_not_empty}" -msgstr "Saisir au moins un URI." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:147 +msgid "Client has been deleted." +msgstr "" -#, fuzzy -msgid "{oidc:client:redirect_uri_not_valid}" -msgstr "Certaines des adresses de redirection ne sont pas valides." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:277 +msgid "Client has been updated." +msgstr "" -#, fuzzy -msgid "{oidc:client:auth_source_not_empty}" -msgstr "Sélectionner un Auth. source." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:120 +msgid "Client secret has been reset." +msgstr "" -#, fuzzy -msgid "{oidc:client:scopes_not_empty}" -msgstr "Sélectionner au moins une valeur de scope" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:183 +msgid "Client with generated ID already exists." +msgstr "" -#, fuzzy -msgid "{oidc:client:reset_secret}" -msgstr "Réinitialiser la valeur de Client secret." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:190 +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:265 +msgid "Client with given entity identifier already exists." +msgstr "" -#, fuzzy -msgid "{oidc:client:reset_secret_warning}" -msgstr "Cette action modifiera la valeur de Client secret et est irréversible." +msgid "Confidential" +msgstr "" -#, fuzzy -msgid "{oidc:client:secret_updated}" -msgstr "Mise à jour réussie de la valeur de Client secret." +msgid "Configuration URL" +msgstr "" -msgid "{oidc:install:oauth2}" -msgstr "Vérifier si vous voulez migrer les données depuis le module désuet oauth2." +msgid "Contacts" +msgstr "" -#, fuzzy -msgid "{oidc:install:description}" -msgstr "L'installateur aidera à créer la base de données et à migrer les informations, si nécessaire. " +msgid "Created at" +msgstr "" -#, fuzzy -msgid "{oidc:install:finished}" -msgstr "La base de données a été créée." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:106 +msgid "Database Migrations" +msgstr "" -#, fuzzy -msgid "{oidc:import:finished}" -msgstr "Les clients du module désuet oauth2 ont été importés." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ConfigController.php:46 +msgid "Database is already migrated." +msgstr "" -#, fuzzy -msgid "{oidc:client:is_enabled}" -msgstr "Activé" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ConfigController.php:52 +msgid "Database migrated successfully." +msgstr "" -msgid "{oidc:client:deactivated}" -msgstr "Désactivé" +msgid "Default Authentication Source" +msgstr "" -msgid "{oidc:client:confidential}" -msgstr "Confidentiel" +msgid "Delete" +msgstr "" -msgid "{oidc:client:client}" -msgstr "Client" +msgid "Description" +msgstr "" -msgid "{oidc:client:type}" -msgstr "Taper" +msgid "Disabled" +msgstr "" -msgid "{oidc:client:confidential_help}" +msgid "Discovery URL" msgstr "" -"Choisissez si le client est confidentiel ou public. Les clients confidentiels sont capables de maintenir la " -"confidentialité de leurs informations d'identification (par exemple, client implémenté sur un serveur sécurisé avec " -"un accès restreint aux informations d'identification du client), ou capable de sécuriser l'authentification du client " -"par d'autres moyens. Les clients publics sont incapables de maintenir le confidentialité de leurs informations " -"d'identification (par exemple, les clients s'exécutant sur le périphérique utilisé par le propriétaire de la " -"ressource, tel qu'un application native installée ou application basée sur un navigateur Web), et incapable de " -"sécuriser l'authentification du client via tout autre moyen." -"" -msgid "{oidc:client:public_client}" -msgstr "Client public" \ No newline at end of file +msgid "Edit" +msgstr "" + +msgid "Edit Client" +msgstr "" + +msgid "Enabled" +msgstr "" + +msgid "" +"Enter if client supports Back-Channel Logout specification. When logout is " +"initiated at the OpenID Provider, it will send a Logout Token to this URI in " +"order to notify the client about that event. Must be a valid URI. Example: " +"https://example.org/foo?bar=1" +msgstr "" + +msgid "Enter one Trust Anchor ID per line." +msgstr "" + +msgid "Entity" +msgstr "" + +msgid "Entity Identifier" +msgstr "" + +msgid "Entity Statement Duration" +msgstr "" + +msgid "Expires at" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Codebooks/RegistrationTypeEnum.php:18 +msgid "Federated Automatic" +msgstr "" + +msgid "Federation Enabled" +msgstr "" + +msgid "Federation JWKS" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:127 +msgid "Federation Settings" +msgstr "" + +msgid "Forced ACR For Cookie Authentication" +msgstr "" + +msgid "Homepage URI" +msgstr "" + +msgid "Identifier" +msgstr "" + +msgid "Info" +msgstr "" + +msgid "Is Federated" +msgstr "" + +msgid "Issuer" +msgstr "" + +msgid "" +"JSON object (string) representing JWKS document containing protocol public " +"keys. Note that this should be different from Federation JWKS. Will be used " +"if JWKS URI is not set. Example: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\"," +"\"e\": \"AQAB\",\"kid\": \"pro123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" +msgstr "" + +msgid "" +"JSON object (string) representing federation JWKS. This can be used, for " +"example, in entity statements. Note that this should be different from " +"Protocol JWKS. Example: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\",\"e\": " +"\"AQAB\",\"kid\": \"fed123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" +msgstr "" + +msgid "JWKS" +msgstr "" + +msgid "JWKS URI" +msgstr "" + +msgid "Leaf Entity ID" +msgstr "" + +msgid "Log messages" +msgstr "" + +msgid "" +"Log messages will show if any warnings or errors were raised during chain " +"resolution." +msgstr "" + +msgid "" +"Log messages will show if any warnings or errors were raised during " +"validation." +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:90 +msgid "Log out" +msgstr "" + +msgid "Logo URI" +msgstr "" + +msgid "Logout Failed" +msgstr "" + +msgid "Logout Info" +msgstr "" + +msgid "Logout Successful" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Codebooks/RegistrationTypeEnum.php:17 +msgid "Manual" +msgstr "" + +msgid "Maximum Cache Duration For Fetched Artifacts" +msgstr "" + +msgid "N/A" +msgstr "" + +msgid "Name" +msgstr "" + +msgid "Name and description" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:357 +msgid "Name is required." +msgstr "" + +msgid "never" +msgstr "" + +msgid "No" +msgstr "" + +msgid "No clients registered." +msgstr "" + +msgid "No entries." +msgstr "" + +msgid "" +"Note that this will first resolve Trust Chain between given entity and Trust " +"Anchor, and only then do the Trust Mark validation." +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_adminmenu.php:24 +msgid "OIDC" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_federationpage.php:34 +msgid "OIDC Client Registry" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_federationpage.php:38 +msgid "OIDC Installation" +msgstr "" + +msgid "" +"One or more values from the list. If not selected, falls back to 'automatic'" +msgstr "" + +msgid "OpenID Federation Related Properties" +msgstr "" + +msgid "Organization Name" +msgstr "" + +msgid "Owner" +msgstr "" + +msgid "PKI" +msgstr "" + +msgid "Path" +msgstr "" + +msgid "Policy URI" +msgstr "" + +msgid "Post-logout Redirect URIs" +msgstr "" + +msgid "Private Key" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:120 +msgid "Protocol Settings" +msgstr "" + +msgid "Public" +msgstr "" + +msgid "Public Key" +msgstr "" + +msgid "Redirect URI" +msgstr "" + +msgid "Redirect URIs" +msgstr "" + +msgid "Refresh Token" +msgstr "" + +msgid "Registration Types" +msgstr "" + +msgid "Registration" +msgstr "" + +msgid "Requested session was not found or it is expired." +msgstr "" + +msgid "Reset" +msgstr "" + +msgid "Resolved chains" +msgstr "" + +msgid "Run migrations" +msgstr "" + +msgid "Scopes" +msgstr "" + +msgid "Secret" +msgstr "" + +msgid "Signed JWKS URI" +msgstr "" + +msgid "Signing Algorithm" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:44 +msgid "SimpleSAMLphp admin access required." +msgstr "" + +msgid "Status" +msgstr "" + +msgid "Supported ACRs" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:134 +msgid "Test Trust Chain Resolution" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:141 +msgid "Test Trust Mark Validation" +msgstr "" + +msgid "" +"There are database migrations that have not been implemented.\n" +" Use the button below to run them now." +msgstr "" + +msgid "Tokens Time-To-Live (TTL)" +msgstr "" + +msgid "Total chains" +msgstr "" + +msgid "Trust Anchor ID" +msgstr "" + +msgid "Trust Anchor IDs" +msgstr "" + +msgid "Trust Anchors" +msgstr "" + +msgid "Trust Mark ID" +msgstr "" + +msgid "" +"Trust Mark validation passed (there were no warnings or errors during " +"validation)." +msgstr "" + +msgid "Trust Marks" +msgstr "" + +msgid "Type" +msgstr "Type" + +msgid "" +"URL to a JWKS document containing protocol public keys. Will be used if " +"Signed JWKS URI is not set. Example: https://example.org/jwks" +msgstr "" + +msgid "" +"URL to a JWS document containing protocol public keys in JWKS format (claim " +"'keys'). Example: https://example.org/signed-jwks" +msgstr "" + +msgid "" +"URLs as allowed origins for CORS requests, for public clients running in " +"browser. Must have http:// or https:// scheme, and at least one 'domain.top-" +"level-domain' pair, or more subdomains. Top-level-domain may end with '.'. " +"No userinfo, path, query or fragment components allowed. May end with port " +"number. One per line. Example: https://example.org" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:36 +msgid "Unable to initiate SimpleSAMLphp admin authentication." +msgstr "" + +msgid "Updated at" +msgstr "" + +msgid "User Entity Cache Duration" +msgstr "" + +msgid "User Identifier Attribute" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:61 +msgid "User not authorized." +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "You can now close this window or navigate to another page." +msgstr "" + +msgid "" +"You can use the form below to test Trust Chain resolution from a leaf entity " +"ID to Trust Anchors." +msgstr "" + +msgid "" +"You can use the form below to test Trust Mark validation for particular " +"entity under given Trust Anchor." +msgstr "" + +msgid "Your session has expired. Please return to the home page and try again." +msgstr "" + +msgid "disabled" +msgstr "" + +msgid "enabled" +msgstr "" diff --git a/locales/hr/LC_MESSAGES/oidc.po b/locales/hr/LC_MESSAGES/oidc.po new file mode 100644 index 00000000..4d9ac306 --- /dev/null +++ b/locales/hr/LC_MESSAGES/oidc.po @@ -0,0 +1,614 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: hr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Domain: oidc\n" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:372 +msgid "-" +msgstr "-" + +msgid "" +"A globally unique URI that is bound to the entity. URI must have https or " +"http scheme and host / domain. It can contain path, but no query, or " +"fragment component." +msgstr "Globalno jedinstveni URI koji je vezan za entitet. URI mora imati https ili " +"http shemu i naziv poslužitelja / domena. Može sadržavati putanju, ali ne i query ili " +"fragment komponentu." + +msgid "Access Token" +msgstr "Pristupni token" + +msgid "Activated" +msgstr "Aktiviran" + +msgid "Add Client" +msgstr "Dodaj klijenta" + +msgid "Administrator" +msgstr "Administrator" + +msgid "All database migrations are implemented." +msgstr "Sve migracije baze podataka su implementirane." + +msgid "Allowed Origins" +msgstr "Dopuštena izvorišta" + +msgid "Allowed Origins (for public client)" +msgstr "Dopuštena izvorišta (za javne klijente)" + +msgid "Allowed origins for public clients" +msgstr "Dopuštena izvorišta za javne klijente" + +msgid "" +"Allowed redirect URIs to use after client initiated logout. Must be a valid " +"URI, one per line. Example: https://example.org/foo?bar=1" +msgstr "" +"Dopušteni URIji za preusmjeravnje nakon odjave koju pokrene klijent. Mora biti valjan " +"URI, jedan po retku. Primjer: https://example.org/foo?bar=1" + +msgid "" +"Allowed redirect URIs to which the authorization response will be sent. Must " +"be a valid URI, one per line. Example: https://example.org/foo?bar=1" +msgstr "" +"Dopušteni URIji za preusmjeravanje na koje će biti poslan autorizacijski odgovor. Mora " +"biti valjan URI, jedan po retku. Primjer: https://example.org/foo?bar=1" + +msgid "Are you sure you want to delete this client?" +msgstr "Jeste li sigurni da želite izbrisati ovog klijenta?" + +msgid "Are you sure you want to reset client secret?" +msgstr "Jeste li sigurni da želite resetirati tajnu klijenta?" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:363 +msgid "At least one redirect URI is required." +msgstr "Potreban je najmanje jedan URI za preusmjeravanje." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:378 +msgid "At least one scope is required." +msgstr "Potreban je najmanje jedan opseg." + +msgid "Authentication" +msgstr "Autentikacija" + +msgid "Authentication Context Class References (ACRs)" +msgstr "Reference klase konteksta autentikacije (ACRovi)" + +msgid "Authentication Processing Filters" +msgstr "Filteri za obradu autentikacije" + +msgid "Authentication Source" +msgstr "Autentikacijski izvor" + +msgid "Authentication Sources to ACRs Map" +msgstr "Mapa autentikacijskih izvora na ACRove" + +msgid "" +"Authentication source for this particular client. If no authentication " +"source is selected, the default one from configuration file will be used." +msgstr "" +"Autentikacijski izvor za ovog klijenta. Ako nije odabran autentikacijski " +"izvor, koristit će se zadani iz konfiguracijske datoteke." + + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:369 +msgid "Authentication source" +msgstr "Autentikacijski izvor" + +msgid "Authority Hints" +msgstr "Sugestije autoriteta" + +msgid "Authorization Code" +msgstr "Autorizacijski kod" + +msgid "Back" +msgstr "Natrag" + +msgid "Back-channel Logout URI" +msgstr "URI za odjavu u pozadinskom kanalu" + +msgid "Before running the migrations, make sure that the database user has proper privileges to change the scheme (for example alter, create, drop, index). After running the migrations, it is a good practice to remove those privileges." +msgstr "Prije pokretanja migracija, provjerite ima li korisnik baze podataka odgovarajuće privilegije za promjenu sheme (na primjer, alter, create, drop, index). Nakon pokretanja migracija, dobra je praksa ukloniti te privilegije." + +msgid "" +"By default, form is populated with current OP issuer and configured Trust " +"Anchors, but you are free to adjust entries as needed." +msgstr "" +"Prema zadanim postavkama, obrazac je popunjen trenutnim izdavateljem OP-a i konfiguriranim Sidrom" +"povjerenja, ali vrijednosti možete slobodno prilagoditi prema potrebi." + +msgid "Cache" +msgstr "Predmemorija" + +msgid "Cache Adapter" +msgstr "Adapter za predmemoriju" + +msgid "Cache Duration For Produced Artifacts" +msgstr "Trajanje predmemorije za proizvedene artefakte " + +msgid "" +"Choose if client is confidential or public. Confidential clients are capable " +"of maintaining the confidentiality of their credentials (e.g., client " +"implemented on a secure server with restricted access to the client " +"credentials), or capable of secure client authentication using other means. " +"Public clients are incapable of maintaining the confidentiality of their " +"credentials (e.g., clients executing on the device used by the resource " +"owner, such as an installed native application or a web browser-based " +"application), and incapable of secure client authentication via any other " +"means." +msgstr "" +"Odaberite je li klijent povjerljiv ili javan. Povjerljivi klijenti su sposobni " +"održavati tajnost njihovih vjerodajnica (npr. klijent " +"implementiran na sigurnom poslužitelju s ograničenim pristupom klijentskim " +"vjerodajnicama) ili sposobni za autentikaciju klijenta korištenjem drugih sredstava. " +"Javni klijenti nisu u stanju održavati tajnost svojih " +"vjerodajnica (npr. klijenti koji se izvršavaju na uređaju kojeg koristi vlasnik resursa, " +"kao što je instalirana izvorna aplikacija ili web-preglednik) " +"i koji nisu sposobni za autentikacijju klijenta putem bilo kojeg drugog sredstva." + +msgid "" +"Choose if the client is allowed to participate in federation context or not." +msgstr "Odaberite smije li klijent sudjelovati u kontekstu federacije ili ne." + +msgid "Client" +msgstr "Klijent" + +msgid "Client Registration Types" +msgstr "Tipovi registracije klijenta" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:113 +msgid "Client Registry" +msgstr "Registar klijenata" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:201 +msgid "Client has been added." +msgstr "Klijent je dodan." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:147 +msgid "Client has been deleted." +msgstr "Klijen je obrisan." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:277 +msgid "Client has been updated." +msgstr "Klijent je ažuriran." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:120 +msgid "Client secret has been reset." +msgstr "Tajna klijenta je resetirana" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:183 +msgid "Client with generated ID already exists." +msgstr "Klijent s generiranim ID-om već postoji." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:190 +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:265 +msgid "Client with given entity identifier already exists." +msgstr "Klijent s danim identifikatorom entiteta već postoji." + +msgid "Confidential" +msgstr "Povjerljiv" + +msgid "Configuration URL" +msgstr "Konfiguracijski URL" + +msgid "Contacts" +msgstr "Kontakti" + +msgid "Created at" +msgstr "Stvoreno u" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:106 +msgid "Database Migrations" +msgstr "Migracije baze podataka" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ConfigController.php:46 +msgid "Database is already migrated." +msgstr "Baza podataka je već migrirana." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ConfigController.php:52 +msgid "Database migrated successfully." +msgstr "Baza podataka je uspješno migrirana." + +msgid "Default Authentication Source" +msgstr "Zadani autentikacijski izvor" + +msgid "Delete" +msgstr "Obriši" + +msgid "Description" +msgstr "Opis" + +msgid "Disabled" +msgstr "Onemogućeno" + +msgid "Discovery URL" +msgstr "URL za otkrivanje" + +msgid "Edit" +msgstr "Uredi" + +msgid "Edit Client" +msgstr "Uredi klijenta" + +msgid "Enabled" +msgstr "Omogućeno" + +msgid "" +"Enter if client supports Back-Channel Logout specification. When logout is " +"initiated at the OpenID Provider, it will send a Logout Token to this URI in " +"order to notify the client about that event. Must be a valid URI. Example: " +"https://example.org/foo?bar=1" +msgstr "" +"Unesite ako klijent podržava odjavu u pozadinskom kanalu. Kada je odjava " +"pokrenuta kod pružatelja OpenID-a, poslat će token za odjavu na ovaj URI " +"kako bi obavijestio klijenta o tom događaju. Mora biti važeći URI. Primjer: " +"https://example.org/foo?bar=1" + +msgid "Enter one Trust Anchor ID per line." +msgstr "Unesi jedno sidro povjerenja po retku" + +msgid "Entity" +msgstr "Entitet" + +msgid "Entity Identifier" +msgstr "Identifikator entiteta" + +msgid "Entity Statement Duration" +msgstr "Trajanje izjave o entitetu" + +msgid "Expires at" +msgstr "Ističe u" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Codebooks/RegistrationTypeEnum.php:18 +msgid "Federated Automatic" +msgstr "Federirano automatski" + +msgid "Federation Enabled" +msgstr "Federacija omogućena" + +msgid "Federation JWKS" +msgstr "Federacijski JWKS" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:127 +msgid "Federation Settings" +msgstr "Federacijske postavke" + +msgid "Forced ACR For Cookie Authentication" +msgstr "Forsirani ACR za autentikaciju putem kolačića" + +msgid "Homepage URI" +msgstr "URI početne stranice" + +msgid "Identifier" +msgstr "Identifikator" + +msgid "Info" +msgstr "Informacije" + +msgid "Is Federated" +msgstr "Je li federiran" + +msgid "Issuer" +msgstr "Izdavatelj" + +msgid "" +"JSON object (string) representing JWKS document containing protocol public " +"keys. Note that this should be different from Federation JWKS. Will be used " +"if JWKS URI is not set. Example: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\"," +"\"e\": \"AQAB\",\"kid\": \"pro123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" +msgstr "" +"JSON objekt (niz znakova) koji predstavlja JWKS dokument koji sadrži javni ključ za prtokol. " +"Imajte na umu da bi se ovo trebalo razlikovati od federacijskog JWKS. Koristit će se " +"ako JWKS URI nije postavljen. Primjer: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\"," +"\"e\": \"AQAB\",\"kid\": \"pro123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" + +msgid "" +"JSON object (string) representing federation JWKS. This can be used, for " +"example, in entity statements. Note that this should be different from " +"Protocol JWKS. Example: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\",\"e\": " +"\"AQAB\",\"kid\": \"fed123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" +msgstr "" +"JSON objekt (niz znakova) koji predstavlja federaciju JWKS. Ovo se može koristiti, na " +"primjer, u izjavama o entitetu. Imajte na umu da bi se ovo trebalo razlikovati od " +"JWKS protokola. Primjer: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\",\"e\": " +"\"AQAB\",\"kid\": \"fed123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" + +msgid "JWKS" +msgstr "JWKS" + +msgid "JWKS URI" +msgstr "JWKS URI" + +msgid "Leaf Entity ID" +msgstr "ID entiteta lista" + +msgid "Log messages" +msgstr "Dnevnik poruka" + +msgid "" +"Log messages will show if any warnings or errors were raised during chain " +"resolution." +msgstr "Dnevnik poruka pokazat će jesu li se tijekom razrješenja lanca povjerenja dogodila ikakva upozorenja ili pogreške." + +msgid "" +"Log messages will show if any warnings or errors were raised during " +"validation." +msgstr "Dnevnik poruka pokazat će jesu li se tijekom validacije dogodila ikakva upozorenja ili pogreške." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:90 +msgid "Log out" +msgstr "Odjavi se" + +msgid "Logo URI" +msgstr "URI logotipa" + +msgid "Logout Failed" +msgstr "Odjava nije uspjela" + +msgid "Logout Info" +msgstr "Informacije o odjavi" + +msgid "Logout Successful" +msgstr "Odjava uspješna" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Codebooks/RegistrationTypeEnum.php:17 +msgid "Manual" +msgstr "Ručno" + +msgid "Maximum Cache Duration For Fetched Artifacts" +msgstr "Maksimalno trajanje predmemorije za dohvaćene artefakte" + +msgid "N/A" +msgstr "N/A" + +msgid "Name" +msgstr "Ime" + +msgid "Name and description" +msgstr "Ime i opis" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:357 +msgid "Name is required." +msgstr "Ime je obavezno." + +msgid "never" +msgstr "nikad" + +msgid "No" +msgstr "Ne" + +msgid "No clients registered." +msgstr "Nema registriranih klijenata" + +msgid "No entries." +msgstr "Nema unosa." + +msgid "" +"Note that this will first resolve Trust Chain between given entity and Trust " +"Anchor, and only then do the Trust Mark validation." +msgstr "" +"Imajte na umu da će ovo prvo razriješiti lanac povjerenja između danog entiteta i sidra povjerenja, " +"a tek onda izvršiti provjeru oznaku povjerenja." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_adminmenu.php:24 +msgid "OIDC" +msgstr "OIDC" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_federationpage.php:34 +msgid "OIDC Client Registry" +msgstr "OIDC registar klijenata" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_federationpage.php:38 +msgid "OIDC Installation" +msgstr "OIDC instalacija" + +msgid "" +"One or more values from the list. If not selected, falls back to 'automatic'" +msgstr "Jedna ili više vrijednosti s popisa. Ako nije odabrano, postavlja se na 'automatski'" + +msgid "OpenID Federation Related Properties" +msgstr "Svojstva povezane s OpenID federacijom" + +msgid "Organization Name" +msgstr "Ime organizacije" + +msgid "Owner" +msgstr "Vlasnik" + +msgid "PKI" +msgstr "PKI" + +msgid "Path" +msgstr "Putanja" + +msgid "Policy URI" +msgstr "URI na pravila" + +msgid "Post-logout Redirect URIs" +msgstr "URIji za preusmjeravanje nakon odjave" + +msgid "Private Key" +msgstr "Privatni ključ" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:120 +msgid "Protocol Settings" +msgstr "Postavke protokola" + +msgid "Public" +msgstr "Javan" + +msgid "Public Key" +msgstr "Javni ključ" + +msgid "Redirect URI" +msgstr "URI za preusmjeravanje" + +msgid "Redirect URIs" +msgstr "URIji za preusmjeravanje" + +msgid "Refresh Token" +msgstr "Token za osvježavanje" + +msgid "Registration Types" +msgstr "Tipovi registracije" + +msgid "Registration" +msgstr "Registracija" + +msgid "Requested session was not found or it is expired." +msgstr "Tražena sesija nije pronađena ili je istekla." + +msgid "Reset" +msgstr "Resetiraj" + +msgid "Resolved chains" +msgstr "Razriješeni lanci povjerenja" + +msgid "Run migrations" +msgstr "Pokreni migracije" + +msgid "Scopes" +msgstr "Opsezi" + +msgid "Secret" +msgstr "Tajna" + +msgid "Signed JWKS URI" +msgstr "Potpisani JWKS URI" + +msgid "Signing Algorithm" +msgstr "Algoritam potpisivanja" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:44 +msgid "SimpleSAMLphp admin access required." +msgstr "Potreban SimpleSAMLphp administratorski pristup." + +msgid "Status" +msgstr "Status" + +msgid "Supported ACRs" +msgstr "Podržani ACRovi" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:134 +msgid "Test Trust Chain Resolution" +msgstr "Testiraj razrješenje lanca povjerenja" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:141 +msgid "Test Trust Mark Validation" +msgstr "Provjera valjanosti oznake povjerenja" + +msgid "" +"There are database migrations that have not been implemented.\n" +" Use the button below to run them now." +msgstr "Postoje migracije baze podataka koje još nisu implementirane.\n" +" Upotrijebite gumb u nastavku da ih sada pokrenete." + +msgid "Tokens Time-To-Live (TTL)" +msgstr "Vrijeme trajanja tokena (TTL)" + +msgid "Total chains" +msgstr "Ukupno lanaca" + +msgid "Trust Anchor ID" +msgstr "ID sidra povjerenja" + +msgid "Trust Anchor IDs" +msgstr "IDevi sidra povjerenja" + +msgid "Trust Anchors" +msgstr "Sidra povjerenja" + +msgid "Trust Mark ID" +msgstr "ID oznake povjerenja" + +msgid "" +"Trust Mark validation passed (there were no warnings or errors during " +"validation)." +msgstr "" +"Provjera oznake povjerenja je prošla (nije bilo upozorenja ili pogrešaka tijekom " +"provjere valjanosti)." + +msgid "Trust Marks" +msgstr "Oznake povjerenja" + +msgid "Type" +msgstr "Tip" + +msgid "" +"URL to a JWKS document containing protocol public keys. Will be used if " +"Signed JWKS URI is not set. Example: https://example.org/jwks" +msgstr "" +"URL do JWKS dokumenta koji sadrži javne ključeve protokola. Koristit će se ako " +"potpisani JWKS URI nije postavljen. Primjer: https://example.org/jwks" + +msgid "" +"URL to a JWS document containing protocol public keys in JWKS format (claim " +"'keys'). Example: https://example.org/signed-jwks" +msgstr "" +"URL do JWS dokumenta koji sadrži javne ključeve protokola u JWKS formatu (tvrdnja " +"'keys'). Primjer: https://example.org/signed-jwks" + +msgid "" +"URLs as allowed origins for CORS requests, for public clients running in " +"browser. Must have http:// or https:// scheme, and at least one 'domain.top-" +"level-domain' pair, or more subdomains. Top-level-domain may end with '.'. " +"No userinfo, path, query or fragment components allowed. May end with port " +"number. One per line. Example: https://example.org" +msgstr "" +"URL-ovi kao dopušteni izvori za CORS zahtjeve, za javne klijente koji se izvršavaju u " +"web-pregledniku. Mora imati http:// ili https:// shemu i barem jednu razinu 'pod-domena.top-domena'" +"ili više poddomena. Domena najviše razine može završavati s '.'." +"Nisu dopuštene korisničke informacije, putanja, 'query' ili fragment fragmenta. Može završiti s oznakom " +"porta. Jedan po retku. Primjer: https://example.org" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:36 +msgid "Unable to initiate SimpleSAMLphp admin authentication." +msgstr "Nije moguće inicirati SimpleSAMLphp administratorsku autentikaciju" + +msgid "Updated at" +msgstr "Ažurirano u" + +msgid "User Entity Cache Duration" +msgstr "Trajanje predmemorije korisničkog entiteta" + +msgid "User Identifier Attribute" +msgstr "Atribut identifikator korisnika" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:61 +msgid "User not authorized." +msgstr "Korisnik nije autoriziran." + +msgid "Yes" +msgstr "Da" + +msgid "You can now close this window or navigate to another page." +msgstr "Sada možete zatvoriti ovaj prozor ili otići na drugu stranicu." + +msgid "" +"You can use the form below to test Trust Chain resolution from a leaf entity " +"ID to Trust Anchors." +msgstr "" +"Možete upotrijebiti obrazac u nastavku za testiranje razrješenja lanca povjerenja od entiteta lista do" +"sidra povjerenja." + +msgid "" +"You can use the form below to test Trust Mark validation for particular " +"entity under given Trust Anchor." +msgstr "" +"Možete upotrijebiti obrazac u nastavku za testiranje validacije za određeni entitet pod sidrom povjerenja." + +msgid "Your session has expired. Please return to the home page and try again." +msgstr "Vaša je sjednica istekla. Vratite se na početnu stranicu i pokušajte ponovno." + +msgid "disabled" +msgstr "onemogućeno" + +msgid "enabled" +msgstr "omogućeno" diff --git a/locales/it/LC_MESSAGES/oidc.po b/locales/it/LC_MESSAGES/oidc.po index 7f9101ec..68b0da58 100644 --- a/locales/it/LC_MESSAGES/oidc.po +++ b/locales/it/LC_MESSAGES/oidc.po @@ -1,122 +1,566 @@ -msgid "{oidc:add_client}" -msgstr "Aggiungi client" +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Domain: oidc\n" -msgid "{oidc:search}" -msgstr "Cerca" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:372 +msgid "-" +msgstr "-" -msgid "{oidc:no_clients}" -msgstr "No clients" +msgid "" +"A globally unique URI that is bound to the entity. URI must have https or " +"http scheme and host / domain. It can contain path, but no query, or " +"fragment component." +msgstr "" -msgid "{oidc:client_list}" -msgstr "Lista client" +msgid "Access Token" +msgstr "" -msgid "{oidc:client:name}" -msgstr "Nome" +msgid "Activated" +msgstr "" -msgid "{oidc:client:description}" -msgstr "Descrizione" +msgid "Add Client" +msgstr "" -msgid "{oidc:client:identifier}" -msgstr "Client id." +msgid "Administrator" +msgstr "" -msgid "{oidc:client:secret}" -msgstr "Client secret" +msgid "All database migrations are implemented." +msgstr "" -msgid "{oidc:client:auth_source}" -msgstr "Auth. source" +msgid "Allowed Origins" +msgstr "" -msgid "{oidc:client:redirect_uri}" -msgstr "Redirect URI" +msgid "Allowed Origins (for public client)" +msgstr "" -msgid "{oidc:client:scopes}" -msgstr "Scopes" +msgid "Allowed origins for public clients" +msgstr "" -msgid "{oidc:submit}" -msgstr "Invia" +msgid "" +"Allowed redirect URIs to use after client initiated logout. Must be a valid " +"URI, one per line. Example: https://example.org/foo?bar=1" +msgstr "" -msgid "{oidc:create}" -msgstr "Crea" +msgid "" +"Allowed redirect URIs to which the authorization response will be sent. Must " +"be a valid URI, one per line. Example: https://example.org/foo?bar=1" +msgstr "" -msgid "{oidc:save}" -msgstr "Salva" +msgid "Are you sure you want to delete this client?" +msgstr "" -msgid "{oidc:return}" -msgstr "Indietro" +msgid "Are you sure you want to reset client secret?" +msgstr "" -msgid "{oidc:install}" -msgstr "Installa" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:363 +msgid "At least one redirect URI is required." +msgstr "" -msgid "{oidc:copy}" -msgstr "Copia codice" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:378 +msgid "At least one scope is required." +msgstr "" -msgid "{oidc:copied}" -msgstr "Copiato!" +msgid "Authentication" +msgstr "" -msgid "{oidc:confirm}" -msgstr "Conferma" +msgid "Authentication Context Class References (ACRs)" +msgstr "" -msgid "{oidc:client:delete}" -msgstr "Cancella Client OpenID Connect" +msgid "Authentication Processing Filters" +msgstr "" -msgid "{oidc:client:confirm_delete}" -msgstr "Gentilmente, conferma che vuoi cancellare questo client. Questa cancellazione non può essere ripristinata." +msgid "Authentication Source" +msgstr "" -msgid "{oidc:edit}" -msgstr "Modifica" +msgid "Authentication Sources to ACRs Map" +msgstr "" -msgid "{oidc:delete}" -msgstr "Cancella" +msgid "" +"Authentication source for this particular client. If no authentication " +"source is selected, the default one from configuration file will be used." +msgstr "" -msgid "{oidc:client:added}" -msgstr "Il client è stato aggiunto con successo." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:369 +msgid "Authentication source" +msgstr "" -msgid "{oidc:client:removed}" -msgstr "Il client è stato rimosso con successo." +msgid "Authority Hints" +msgstr "" -msgid "{oidc:client:updated}" -msgstr "Il client è stato aggiornato con successo." +msgid "Authorization Code" +msgstr "" -msgid "{oidc:client:redirect_uri_help}" -msgstr "Aggiungi una URI valida" +msgid "Back" +msgstr "" -msgid "{oidc:client:auth_source_help}" -msgstr "If no auth. source is selected, the default one from configuration file will be used." +msgid "Back-channel Logout URI" +msgstr "" -msgid "{oidc:client:name_not_empty}" -msgstr "Per favore aggiungi un nome." +msgid "Before running the migrations, make sure that the database user has proper privileges to change the scheme (for example alter, create, drop, index). After running the migrations, it is a good practice to remove those privileges." +msgstr "" -msgid "{oidc:client:redirect_uri_not_empty}" -msgstr "Per favore, aggiungi almeno una URI." +msgid "" +"By default, form is populated with current OP issuer and configured Trust " +"Anchors, but you are free to adjust entries as needed." +msgstr "" -msgid "{oidc:client:redirect_uri_not_valid}" -msgstr "Qualche indirizzo di redirezione non è valido." +msgid "Cache" +msgstr "" -msgid "{oidc:client:auth_source_not_empty}" -msgstr "Per favore seleziona una Auth Source." +msgid "Cache Adapter" +msgstr "" -msgid "{oidc:client:scopes_not_empty}" -msgstr "Per favore, seleziona almeno uno scope." +msgid "Cache Duration For Produced Artifacts" +msgstr "" -msgid "{oidc:client:reset_secret}" -msgstr "Ripristina secret" +msgid "" +"Choose if client is confidential or public. Confidential clients are capable " +"of maintaining the confidentiality of their credentials (e.g., client " +"implemented on a secure server with restricted access to the client " +"credentials), or capable of secure client authentication using other means. " +"Public clients are incapable of maintaining the confidentiality of their " +"credentials (e.g., clients executing on the device used by the resource " +"owner, such as an installed native application or a web browser-based " +"application), and incapable of secure client authentication via any other " +"means." +msgstr "" -msgid "{oidc:client:reset_secret_warning}" -msgstr "Questa operazione cambierà il tuo client secret è non potrà essere ripristinata." +msgid "" +"Choose if the client is allowed to participate in federation context or not." +msgstr "" -msgid "{oidc:client:secret_updated}" -msgstr "Il client secret è stato aggiornato con successo." +msgid "Client" +msgstr "" -msgid "{oidc:install:oauth2}" -msgstr "Controlla se vuoi migrare dati dal vecchio modulo oauth2" +msgid "Client Registration Types" +msgstr "" -msgid "{oidc:install:description}" -msgstr "This wizard will help you create the database and migrate information if necessary." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:113 +msgid "Client Registry" +msgstr "" -msgid "{oidc:install:finished}" -msgstr "Database creato con successo." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:201 +msgid "Client has been added." +msgstr "" -msgid "{oidc:import:finished}" -msgstr "I clients del vecchio modulo oauth2 sono stati importati con successo." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:147 +msgid "Client has been deleted." +msgstr "" -msgid "{oidc:client:is_enabled}" -msgstr "Attivato" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:277 +msgid "Client has been updated." +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:120 +msgid "Client secret has been reset." +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:183 +msgid "Client with generated ID already exists." +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:190 +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:265 +msgid "Client with given entity identifier already exists." +msgstr "" + +msgid "Confidential" +msgstr "" + +msgid "Configuration URL" +msgstr "" + +msgid "Contacts" +msgstr "" + +msgid "Created at" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:106 +msgid "Database Migrations" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ConfigController.php:46 +msgid "Database is already migrated." +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ConfigController.php:52 +msgid "Database migrated successfully." +msgstr "" + +msgid "Default Authentication Source" +msgstr "" + +msgid "Delete" +msgstr "" + +msgid "Description" +msgstr "" + +msgid "Disabled" +msgstr "" + +msgid "Discovery URL" +msgstr "" + +msgid "Edit" +msgstr "" + +msgid "Edit Client" +msgstr "" + +msgid "Enabled" +msgstr "" + +msgid "" +"Enter if client supports Back-Channel Logout specification. When logout is " +"initiated at the OpenID Provider, it will send a Logout Token to this URI in " +"order to notify the client about that event. Must be a valid URI. Example: " +"https://example.org/foo?bar=1" +msgstr "" + +msgid "Enter one Trust Anchor ID per line." +msgstr "" + +msgid "Entity" +msgstr "" + +msgid "Entity Identifier" +msgstr "" + +msgid "Entity Statement Duration" +msgstr "" + +msgid "Expires at" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Codebooks/RegistrationTypeEnum.php:18 +msgid "Federated Automatic" +msgstr "" + +msgid "Federation Enabled" +msgstr "" + +msgid "Federation JWKS" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:127 +msgid "Federation Settings" +msgstr "" + +msgid "Forced ACR For Cookie Authentication" +msgstr "" + +msgid "Homepage URI" +msgstr "" + +msgid "Identifier" +msgstr "" + +msgid "Info" +msgstr "" + +msgid "Is Federated" +msgstr "" + +msgid "Issuer" +msgstr "" + +msgid "" +"JSON object (string) representing JWKS document containing protocol public " +"keys. Note that this should be different from Federation JWKS. Will be used " +"if JWKS URI is not set. Example: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\"," +"\"e\": \"AQAB\",\"kid\": \"pro123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" +msgstr "" + +msgid "" +"JSON object (string) representing federation JWKS. This can be used, for " +"example, in entity statements. Note that this should be different from " +"Protocol JWKS. Example: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\",\"e\": " +"\"AQAB\",\"kid\": \"fed123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" +msgstr "" + +msgid "JWKS" +msgstr "" + +msgid "JWKS URI" +msgstr "" + +msgid "Leaf Entity ID" +msgstr "" + +msgid "Log messages" +msgstr "" + +msgid "" +"Log messages will show if any warnings or errors were raised during chain " +"resolution." +msgstr "" + +msgid "" +"Log messages will show if any warnings or errors were raised during " +"validation." +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:90 +msgid "Log out" +msgstr "" + +msgid "Logo URI" +msgstr "" + +msgid "Logout Failed" +msgstr "" + +msgid "Logout Info" +msgstr "" + +msgid "Logout Successful" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Codebooks/RegistrationTypeEnum.php:17 +msgid "Manual" +msgstr "" + +msgid "Maximum Cache Duration For Fetched Artifacts" +msgstr "" + +msgid "N/A" +msgstr "" + +msgid "Name" +msgstr "" + +msgid "Name and description" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:357 +msgid "Name is required." +msgstr "" + +msgid "never" +msgstr "" + +msgid "No" +msgstr "" + +msgid "No clients registered." +msgstr "" + +msgid "No entries." +msgstr "" + +msgid "" +"Note that this will first resolve Trust Chain between given entity and Trust " +"Anchor, and only then do the Trust Mark validation." +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_adminmenu.php:24 +msgid "OIDC" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_federationpage.php:34 +msgid "OIDC Client Registry" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_federationpage.php:38 +msgid "OIDC Installation" +msgstr "" + +msgid "" +"One or more values from the list. If not selected, falls back to 'automatic'" +msgstr "" + +msgid "OpenID Federation Related Properties" +msgstr "" + +msgid "Organization Name" +msgstr "" + +msgid "Owner" +msgstr "" + +msgid "PKI" +msgstr "" + +msgid "Path" +msgstr "" + +msgid "Policy URI" +msgstr "" + +msgid "Post-logout Redirect URIs" +msgstr "" + +msgid "Private Key" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:120 +msgid "Protocol Settings" +msgstr "" + +msgid "Public" +msgstr "" + +msgid "Public Key" +msgstr "" + +msgid "Redirect URI" +msgstr "" + +msgid "Redirect URIs" +msgstr "" + +msgid "Refresh Token" +msgstr "" + +msgid "Registration Types" +msgstr "" + +msgid "Registration" +msgstr "" + +msgid "Requested session was not found or it is expired." +msgstr "" + +msgid "Reset" +msgstr "" + +msgid "Resolved chains" +msgstr "" + +msgid "Run migrations" +msgstr "" + +msgid "Scopes" +msgstr "" + +msgid "Secret" +msgstr "" + +msgid "Signed JWKS URI" +msgstr "" + +msgid "Signing Algorithm" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:44 +msgid "SimpleSAMLphp admin access required." +msgstr "" + +msgid "Status" +msgstr "" + +msgid "Supported ACRs" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:134 +msgid "Test Trust Chain Resolution" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:141 +msgid "Test Trust Mark Validation" +msgstr "" + +msgid "" +"There are database migrations that have not been implemented.\n" +" Use the button below to run them now." +msgstr "" + +msgid "Tokens Time-To-Live (TTL)" +msgstr "" + +msgid "Total chains" +msgstr "" + +msgid "Trust Anchor ID" +msgstr "" + +msgid "Trust Anchor IDs" +msgstr "" + +msgid "Trust Anchors" +msgstr "" + +msgid "Trust Mark ID" +msgstr "" + +msgid "" +"Trust Mark validation passed (there were no warnings or errors during " +"validation)." +msgstr "" + +msgid "Trust Marks" +msgstr "" + +msgid "Type" +msgstr "Type" + +msgid "" +"URL to a JWKS document containing protocol public keys. Will be used if " +"Signed JWKS URI is not set. Example: https://example.org/jwks" +msgstr "" + +msgid "" +"URL to a JWS document containing protocol public keys in JWKS format (claim " +"'keys'). Example: https://example.org/signed-jwks" +msgstr "" + +msgid "" +"URLs as allowed origins for CORS requests, for public clients running in " +"browser. Must have http:// or https:// scheme, and at least one 'domain.top-" +"level-domain' pair, or more subdomains. Top-level-domain may end with '.'. " +"No userinfo, path, query or fragment components allowed. May end with port " +"number. One per line. Example: https://example.org" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:36 +msgid "Unable to initiate SimpleSAMLphp admin authentication." +msgstr "" + +msgid "Updated at" +msgstr "" + +msgid "User Entity Cache Duration" +msgstr "" + +msgid "User Identifier Attribute" +msgstr "" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:61 +msgid "User not authorized." +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "You can now close this window or navigate to another page." +msgstr "" + +msgid "" +"You can use the form below to test Trust Chain resolution from a leaf entity " +"ID to Trust Anchors." +msgstr "" + +msgid "" +"You can use the form below to test Trust Mark validation for particular " +"entity under given Trust Anchor." +msgstr "" + +msgid "Your session has expired. Please return to the home page and try again." +msgstr "" + +msgid "disabled" +msgstr "" + +msgid "enabled" +msgstr "" diff --git a/locales/nl/LC_MESSAGES/oidc.po b/locales/nl/LC_MESSAGES/oidc.po index c3eebb8c..b4d5964a 100644 --- a/locales/nl/LC_MESSAGES/oidc.po +++ b/locales/nl/LC_MESSAGES/oidc.po @@ -1,122 +1,520 @@ -msgid "{oidc:add_client}" +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Domain: oidc\n" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:372 +msgid "-" +msgstr "-" + +msgid "A globally unique URI that is bound to the entity. URI must have https or http scheme and host / domain. It can contain path, but no query, or fragment component." +msgstr "Een wereldwijd unieke URI die aan de entiteit is gebonden. URI moet een https- of http-schema en host/domein hebben. Het kan een pad bevatten, maar geen query of fragmentcomponent." + +msgid "Access Token" +msgstr "Toegangstoken" + +msgid "Activated" +msgstr "Geactiveerd" + +msgid "Add Client" msgstr "Client toevoegen" -msgid "{oidc:search}" -msgstr "Zoeken" +msgid "Administrator" +msgstr "Beheerder" -msgid "{oidc:no_clients}" -msgstr "Geen clients" +msgid "All database migrations are implemented." +msgstr "Alle databasemigraties worden uitgevoerd." -msgid "{oidc:client_list}" -msgstr "Client-overzicht" +msgid "Allowed Origins" +msgstr "Toegestane bronnen" -msgid "{oidc:client:name}" -msgstr "Naam" +msgid "Allowed Origins (for public client)" +msgstr "Toegestane beonnen (voor openbare client)" -msgid "{oidc:client:description}" -msgstr "Omschrijving" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:384 +msgid "Allowed origins for public clients" +msgstr "Toegestane bronnen voor openbare clients" + +msgid "Allowed redirect URIs to use after client initiated logout. Must be a valid URI, one per line. Example: https://example.org/foo?bar=1" +msgstr "Toegestane redirect-URI's om te gebruiken na client-geïnitieerde uitlog. Moet een geldige URI zijn, één per regel. Voorbeeld: https://example.org/foo?bar=1" + +msgid "Allowed redirect URIs to which the authorization response will be sent. Must be a valid URI, one per line. Example: https://example.org/foo?bar=1" +msgstr "Toegestane redirect-URI's waarnaar de autorisatierespons wordt verzonden. Moet een geldige URI zijn, één per regel. Voorbeeld: https://example.org/foo?bar=1" + +msgid "Are you sure you want to delete this client?" +msgstr "Weet u zeker dat u deze client wilt verwijderen?" + +msgid "Are you sure you want to reset client secret?" +msgstr "Weet u zeker dat u het clientgeheim wilt resetten?" -msgid "{oidc:client:identifier}" -msgstr "Client ID" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:363 +msgid "At least one redirect URI is required." +msgstr "Er is minimaal één redirect-URI vereist." -msgid "{oidc:client:secret}" -msgstr "Client secret" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:378 +msgid "At least one scope is required." +msgstr "Er is minimaal één scope vereist." -msgid "{oidc:client:auth_source}" +msgid "Authentication" +msgstr "Authenticatie" + +msgid "Authentication Context Class References (ACRs)" +msgstr "Authentication Context Class References (ACRs)" + +msgid "Authentication Processing Filters" +msgstr "Authentication Processing Filters" + +msgid "Authentication Source" +msgstr "Authenticatiebron" + +msgid "Authentication Sources to ACRs Map" +msgstr "Authenticatiebronnen naar ACR's mapping" + +msgid "Authentication source for this particular client. If no authentication source is selected, the default one from configuration file will be used." +msgstr "Authenticatiebron voor deze specifieke client. Als er geen authenticatiebron is geselecteerd, wordt de standaardauthenticatiebron uit het configuratiebestand gebruikt." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:369 +msgid "Authentication source" msgstr "Authenticatiebron" -msgid "{oidc:client:redirect_uri}" -msgstr "Redirect URI" +msgid "Authority Hints" +msgstr "Authority Hints" -msgid "{oidc:client:scopes}" -msgstr "Scopes" +msgid "Authorization Code" +msgstr "Autorisatiecode" -msgid "{oidc:submit}" -msgstr "Verzenden" +msgid "Back" +msgstr "Terug" -msgid "{oidc:create}" -msgstr "Nieuw" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:387 +msgid "Back-channel Logout URI" +msgstr "Back-channel Logout-URI" -msgid "{oidc:save}" -msgstr "Opslaan" +msgid "Before running the migrations, make sure that the database user has proper privileges to change the scheme (for example alter, create, drop, index). After running the migrations, it is a good practice to remove those privileges." +msgstr "Controleer voordat u de migraties uitvoert of de databasegebruiker de juiste rechten heeft om het schema te wijzigen (bijvoorbeeld alter, create, drop, index). Nadat u de migraties hebt uitgevoerd, is het een goede gewoonte om deze rechten te verwijderen." -msgid "{oidc:return}" -msgstr "Vorige" +msgid "By default, form is populated with current OP issuer and configured Trust Anchors, but you are free to adjust entries as needed." +msgstr "Standaard wordt het formulier ingevuld met de huidige OP-issuer en geconfigureerde Trust Anchors, maar u kunt de invoer indien nodig aanpassen." -msgid "{oidc:install}" -msgstr "Installeren" +msgid "Cache" +msgstr "Cache" -msgid "{oidc:copy}" -msgstr "Code kopiëren" +msgid "Cache Adapter" +msgstr "Cache-adapter" -msgid "{oidc:copied}" -msgstr "Gekopieerd!" +msgid "Cache Duration For Produced Artifacts" +msgstr "Cacheduur voor geproduceerde artefacten" -msgid "{oidc:confirm}" -msgstr "Bevestigen" +msgid "Choose if client is confidential or public. Confidential clients are capable of maintaining the confidentiality of their credentials (e.g., client implemented on a secure server with restricted access to the client credentials), or capable of secure client authentication using other means. Public clients are incapable of maintaining the confidentiality of their credentials (e.g., clients executing on the device used by the resource owner, such as an installed native application or a web browser-based application), and incapable of secure client authentication via any other means." +msgstr "Kies of de client vertrouwelijk of openbaar is. Vertrouwelijke clients zijn in staat om de vertrouwelijkheid van hun referenties te handhaven (bijv. client geïmplementeerd op een beveiligde server met beperkte toegang tot de clientreferenties), of in staat tot veilige clientauthenticatie met behulp van andere middelen. Openbare clients zijn niet in staat om de vertrouwelijkheid van hun referenties te handhaven (bijv. clients die worden uitgevoerd op het apparaat dat wordt gebruikt door de resource-eigenaar, zoals een geïnstalleerde native applicatie of een op een webbrowser gebaseerde applicatie), en niet in staat tot veilige clientauthenticatie via andere middelen." -msgid "{oidc:client:delete}" -msgstr "OpenID Connect Client verwijderen" +msgid "Choose if the client is allowed to participate in federation context or not." +msgstr "Selecteer of de cliënt mag deelnemen aan de federatiecontext of niet." -msgid "{oidc:client:confirm_delete}" -msgstr "Deze actie kan niet ongedaan gemaakt worden! Weet je zeker dat u deze client wilt verwijderen?" +msgid "Client" +msgstr "Cliënt" -msgid "{oidc:edit}" -msgstr "Bewerken" +msgid "Client Registration Types" +msgstr "Typen clientregistratie" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:113 +msgid "Client Registry" +msgstr "Client-register" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:201 +msgid "Client has been added." +msgstr "Client is toegevoegd." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:147 +msgid "Client has been deleted." +msgstr "Client is verwijderd." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:277 +msgid "Client has been updated." +msgstr "Client is bijgewerkt." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:120 +msgid "Client secret has been reset." +msgstr "Clientgeheim is gereset." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:183 +msgid "Client with generated ID already exists." +msgstr "Client met gegenereerde ID bestaat al." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:190 +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ClientController.php:265 +msgid "Client with given entity identifier already exists." +msgstr "Client met opgegeven entiteits-ID bestaat al." + +msgid "Confidential" +msgstr "Vertrouwelijk" + +msgid "Configuration URL" +msgstr "Configuratie-URL" + +msgid "Contacts" +msgstr "Contacten" + +msgid "Created at" +msgstr "Gemaakt op" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:106 +msgid "Database Migrations" +msgstr "Database migraties" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ConfigController.php:46 +msgid "Database is already migrated." +msgstr "Database is al gemigreerd." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Controllers/Admin/ConfigController.php:52 +msgid "Database migrated successfully." +msgstr "Database succesvol gemigreerd." + +msgid "Default Authentication Source" +msgstr "Standaard authenticatiebron" -msgid "{oidc:delete}" +msgid "Delete" msgstr "Verwijderen" -msgid "{oidc:client:added}" -msgstr "Client is succesvol aangemaakt." +msgid "Description" +msgstr "Omschrijving" + +msgid "Disabled" +msgstr "Gehandicapt" -msgid "{oidc:client:removed}" -msgstr "Client is succesvol verwijderd." +msgid "Discovery URL" +msgstr "Ontdekkings-URL" -msgid "{oidc:client:updated}" -msgstr "Client is succesvol bijgewerkt." +msgid "Edit" +msgstr "Bewerken" -msgid "{oidc:client:redirect_uri_help}" -msgstr "Voer een geldige URI in" +msgid "Edit Client" +msgstr "Client bewerken" -msgid "{oidc:client:auth_source_help}" -msgstr "If no auth. source is selected, the default one from configuration file will be used." +msgid "Enabled" +msgstr "Ingeschakeld" -msgid "{oidc:client:name_not_empty}" -msgstr "Voer een naam in." +msgid "Enter if client supports Back-Channel Logout specification. When logout is initiated at the OpenID Provider, it will send a Logout Token to this URI in order to notify the client about that event. Must be a valid URI. Example: https://example.org/foo?bar=1" +msgstr "Voer in of de client Back-Channel Logout-specificatie ondersteunt. Wanneer logout wordt gestart bij de OpenID Provider, wordt een Logout Token naar deze URI verzonden om de client op de hoogte te stellen van die gebeurtenis. Moet een geldige URI zijn. Voorbeeld: https://example.org/foo?bar=1" -msgid "{oidc:client:redirect_uri_not_empty}" -msgstr "Voer minimaal een URI in" +msgid "Enter one Trust Anchor ID per line." +msgstr "Voer één Trust Anchor ID per regel in." -msgid "{oidc:client:redirect_uri_not_valid}" -msgstr "Sommige redirect adressen zijn ongeldig." +msgid "Entity" +msgstr "Entiteit" -msgid "{oidc:client:auth_source_not_empty}" -msgstr "Selecteer een authenticatiebron." +msgid "Entity Identifier" +msgstr "Entiteits-ID" -msgid "{oidc:client:scopes_not_empty}" -msgstr "Selecteer minimaal één scope." +msgid "Entity Statement Duration" +msgstr "Entiteitsverklaring Duur" -msgid "{oidc:client:reset_secret}" -msgstr "Secret vernieuwen" +msgid "Expires at" +msgstr "Verloopt op" -msgid "{oidc:client:reset_secret_warning}" -msgstr "Deze actie kan niet ongedaan gemaakt worden! Wil je het client secret vernieuwen?" +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Codebooks/RegistrationTypeEnum.php:18 +msgid "Federated Automatic" +msgstr "Gefedereerde automatische" -msgid "{oidc:client:secret_updated}" -msgstr "Het client secret is succesvol gewijzigd." +msgid "Federation Enabled" +msgstr "Federatie ingeschakeld" -msgid "{oidc:install:oauth2}" -msgstr "Vink aan om gegevens vanuit de legacy oauth2 module te migreren" +msgid "Federation JWKS" +msgstr "Federatie JWKS" -msgid "{oidc:install:description}" -msgstr "Deze wizard zal de database aanmaken en indien nodig een migratie uitvoeren." +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:127 +msgid "Federation Settings" +msgstr "Federatie-instellingen" -msgid "{oidc:install:finished}" -msgstr "De database is aangemaakt." +msgid "Forced ACR For Cookie Authentication" +msgstr "Geforceerde ACR voor cookie-authenticatie" -msgid "{oidc:import:finished}" -msgstr "Clients van de oude oauth2 module zijn geïmporteerd." +msgid "Homepage URI" +msgstr "Startpagina-URI" -msgid "{oidc:client:is_enabled}" -msgstr "Geactiveerd" +msgid "Identifier" +msgstr "Identificatie" + +msgid "Info" +msgstr "Informatie" + +msgid "Is Federated" +msgstr "Is gefedereerd" + +msgid "Issuer" +msgstr "Uitgever" + +msgid "JSON object (string) representing JWKS document containing protocol public keys. Note that this should be different from Federation JWKS. Will be used if JWKS URI is not set. Example: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\",\"e\": \"AQAB\",\"kid\": \"pro123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" +msgstr "JSON-object (tekenreeks) dat een JWKS-document vertegenwoordigt met openbare protocolsleutels. Let op: dit moet anders zijn dan Federation JWKS. Wordt gebruikt als de JWKS-URI niet is ingesteld. Voorbeeld: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\",\"e\": \"AQAB\",\"kid\": \"pro123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" + +msgid "JSON object (string) representing federation JWKS. This can be used, for example, in entity statements. Note that this should be different from Protocol JWKS. Example: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\",\"e\": \"AQAB\",\"kid\": \"fed123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" +msgstr "JSON-object (tekenreeks) dat federatie-JWKS vertegenwoordigt. Dit kan bijvoorbeeld worden gebruikt in entiteitsverklaringen. Let op dat dit anders moet zijn dan Protocol JWKS. Voorbeeld: {\"keys\":[{\"kty\": \"RSA\",\"n\": \"...\",\"e\": \"AQAB\",\"kid\": \"fed123\",\"use\": \"sig\",\"alg\": \"RS256\"}]}" + +msgid "JWKS" +msgstr "JWKS" + +msgid "JWKS URI" +msgstr "JWKS URI" + +msgid "Leaf Entity ID" +msgstr "Leaf Entity ID" + +msgid "Log messages" +msgstr "Logberichten" + +msgid "Log messages will show if any warnings or errors were raised during chain resolution." +msgstr "In logberichten wordt aangegeven of er waarschuwingen of fouten zijn opgetreden tijdens het oplossen van de keten." + +msgid "Log messages will show if any warnings or errors were raised during validation." +msgstr "In logberichten wordt aangegeven of er tijdens de validatie waarschuwingen of fouten zijn opgetreden." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:90 +msgid "Log out" +msgstr "Uitloggen" + +msgid "Logo URI" +msgstr "Logo-URI" + +msgid "Logout Failed" +msgstr "Uitloggen mislukt" + +msgid "Logout Info" +msgstr "Uitloggen Info" + +msgid "Logout Successful" +msgstr "Uitloggen succesvol" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Codebooks/RegistrationTypeEnum.php:17 +msgid "Manual" +msgstr "Handmatig" + +msgid "Maximum Cache Duration For Fetched Artifacts" +msgstr "Maximale cacheduur voor opgehaalde artefacten" + +msgid "N/A" +msgstr "N/A" + +msgid "Name" +msgstr "Naam" + +msgid "Name and description" +msgstr "Naam en beschrijving" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:357 +msgid "Name is required." +msgstr "Naam is verplicht." + +msgid "never" +msgstr "nooit" + +msgid "No" +msgstr "Nee" + +msgid "No clients registered." +msgstr "Er zijn geen klanten geregistreerd." + +msgid "No entries." +msgstr "Geen invoer." + +msgid "Note that this will first resolve Trust Chain between given entity and Trust Anchor, and only then do the Trust Mark validation." +msgstr "Houd er rekening mee dat hiermee eerst de Trust Chain tussen de gegeven entiteit en het Trust Anchor wordt opgelost en pas daarna de Trust Mark-validatie wordt uitgevoerd." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_adminmenu.php:24 +msgid "OIDC" +msgstr "OIDC" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_federationpage.php:34 +msgid "OIDC Client Registry" +msgstr "OIDC-clientregister" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/hooks/hook_federationpage.php:38 +msgid "OIDC Installation" +msgstr "OIDC-installatie" + +msgid "One or more values from the list. If not selected, falls back to 'automatic'" +msgstr "Een of meer waarden uit de lijst. Indien niet geselecteerd, terugvallen op 'automatisch'" + +msgid "OpenID Federation Related Properties" +msgstr "OpenID Federation-gerelateerde eigenschappen" + +msgid "Organization Name" +msgstr "Organisatienaam" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Forms/ClientForm.php:380 +msgid "Owner" +msgstr "Eigenaar" + +msgid "PKI" +msgstr "PKI" + +msgid "Path" +msgstr "Pad" + +msgid "Policy URI" +msgstr "Beleids-URI" + +msgid "Post-logout Redirect URIs" +msgstr "Post-logout Redirect-URI's" + +msgid "Private Key" +msgstr "Privésleutel" + +msgid "Private Key Password Set" +msgstr "Wachtwoord voor privésleutel instellen" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:120 +msgid "Protocol Settings" +msgstr "Protocolinstellingen" + +msgid "Public" +msgstr "Openbaar" + +msgid "Public Key" +msgstr "Publieke sleutel" + +msgid "Redirect URI" +msgstr "Omleidings-URI" + +msgid "Redirect URIs" +msgstr "URI's omleiden" + +msgid "Refresh Token" +msgstr "Token vernieuwen" + +msgid "Registration Types" +msgstr "Registratietypen" + +msgid "Registration" +msgstr "Registratie" + +msgid "Requested session was not found or it is expired." +msgstr "De gevraagde sessie is niet gevonden of is verlopen." + +msgid "Reset" +msgstr "Opnieuw instellen" + +msgid "Resolved chains" +msgstr "Opgeloste ketens" + +msgid "Run migrations" +msgstr "Migraties uitvoeren" + +msgid "Scopes" +msgstr "Scopen" + +msgid "Secret" +msgstr "Geheim" + +msgid "Signed JWKS URI" +msgstr "Ondertekende JWKS URI" + +msgid "Signing Algorithm" +msgstr "Ondertekeningsalgoritme" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:44 +msgid "SimpleSAMLphp admin access required." +msgstr "SimpleSAMLphp-beheerdersrechten vereist." + +msgid "Status" +msgstr "Status" + +msgid "Supported ACRs" +msgstr "Ondersteunde ACR's" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:134 +msgid "Test Trust Chain Resolution" +msgstr "Test Vertrouwensketen Resolutie" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Factories/TemplateFactory.php:141 +msgid "Test Trust Mark Validation" +msgstr "Test Trust Mark-validatie" + +msgid "" +"There are database migrations that have not been implemented.\n" +" Use the button below to run them now." +msgstr "" +"Er zijn databasemigraties die nog niet zijn geïmplementeerd.\n" +" Gebruik de onderstaande knop om ze nu uit te voeren." + +msgid "Tokens Time-To-Live (TTL)" +msgstr "Tokens Time-To-Live (TTL)" + +msgid "Total chains" +msgstr "Totaal aantal vertrouwensketens" + +msgid "Trust Anchor ID" +msgstr "Vertrouw op anker-ID" + +msgid "Trust Anchor IDs" +msgstr "Vertrouwde anker-ID's" + +msgid "Trust Anchors" +msgstr "Vertrouw op ankers" + +msgid "Trust Mark ID" +msgstr "Vertrouwensmerk-ID" + +msgid "Trust Mark validation passed (there were no warnings or errors during validation)." +msgstr "Validatie van het Trust Mark is geslaagd (er zijn geen waarschuwingen of fouten opgetreden tijdens de validatie)." + +msgid "Trust Marks" +msgstr "Vertrouwensmerken" + +msgid "Type" +msgstr "Type" + +msgid "URL to a JWKS document containing protocol public keys. Will be used if Signed JWKS URI is not set. Example: https://example.org/jwks" +msgstr "URL naar een JWKS-document met openbare protocolsleutels. Wordt gebruikt als Signed JWKS URI niet is ingesteld. Voorbeeld: https://example.org/jwks" + +msgid "URL to a JWS document containing protocol public keys in JWKS format (claim 'keys'). Example: https://example.org/signed-jwks" +msgstr "URL naar een JWS-document met openbare protocolsleutels in JWKS-formaat (claim 'keys'). Voorbeeld: https://example.org/signed-jwks" + +msgid "URLs as allowed origins for CORS requests, for public clients running in browser. Must have http:// or https:// scheme, and at least one 'domain.top-level-domain' pair, or more subdomains. Top-level-domain may end with '.'. No userinfo, path, query or fragment components allowed. May end with port number. One per line. Example: https://example.org" +msgstr "URL's als toegestane bronnen voor CORS-verzoeken, voor openbare clients die in de browser worden uitgevoerd. Moet een http://- of https://-schema hebben en ten minste één 'domein.top-level-domein'-paar, of meer subdomeinen. Top-level-domein mag eindigen op '.'. Geen gebruikersinfo-, pad-, query- of fragmentcomponenten toegestaan. Mag eindigen op poortnummer. Eén per regel. Voorbeeld: https://example.org" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:36 +msgid "Unable to initiate SimpleSAMLphp admin authentication." +msgstr "Kan SimpleSAMLphp-beheerdersauthenticatie niet starten." + +msgid "Updated at" +msgstr "Bijgewerkt op" + +msgid "User Entity Cache Duration" +msgstr "Duur cache gebruikersentiteit" + +msgid "User Identifier Attribute" +msgstr "Gebruikers-ID-kenmerk" + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Admin/Authorization.php:61 +msgid "User not authorized." +msgstr "Gebruiker niet geautoriseerd." + +msgid "Yes" +msgstr "Ja" + +msgid "You can now close this window or navigate to another page." +msgstr "U kunt nu dit venster sluiten of naar een andere pagina navigeren." + +msgid "You can use the form below to test Trust Chain resolution from a leaf entity ID to Trust Anchors." +msgstr "U kunt het onderstaande formulier gebruiken om de Trust Chain-resolutie van een leaf-entiteits-ID naar Trust Anchors te testen." + +msgid "You can use the form below to test Trust Mark validation for particular entity under given Trust Anchor." +msgstr "U kunt het onderstaande formulier gebruiken om de Trust Mark-validatie voor een specifieke entiteit onder het opgegeven Trust Anchor te testen." + +#: /var/www/projects/simplesamlphp/simplesamlphp-2.3/modules/oidc/src/Services/Container.php:143 +msgid "Your session has expired. Please return to the home page and try again." +msgstr "Uw sessie is verlopen. Ga terug naar de startpagina en probeer het opnieuw." + +msgid "disabled" +msgstr "uitgeschakeld" + +msgid "enabled" +msgstr "ingeschakeld" diff --git a/src/Forms/ClientForm.php b/src/Forms/ClientForm.php index 05f54d3b..71844403 100644 --- a/src/Forms/ClientForm.php +++ b/src/Forms/ClientForm.php @@ -353,40 +353,40 @@ protected function buildForm(): void $this->setMethod('POST'); $this->addComponent($this->csrfProtection, Form::ProtectorId); - $this->addText('name', '{oidc:client:name}') + $this->addText('name', Translate::noop('Name')) ->setHtmlAttribute('class', 'full-width') ->setMaxLength(255) ->setRequired(Translate::noop('Name is required.')); - $this->addTextArea('description', '{oidc:client:description}', null, 3) + $this->addTextArea('description', Translate::noop('Description'), null, 3) ->setHtmlAttribute('class', 'full-width'); - $this->addTextArea('redirect_uri', '{oidc:client:redirect_uri}', null, 5) + $this->addTextArea('redirect_uri', Translate::noop('Redirect URI'), null, 5) ->setHtmlAttribute('class', 'full-width') ->setRequired(Translate::noop('At least one redirect URI is required.')); - $this->addCheckbox('is_enabled', '{oidc:client:is_enabled}'); + $this->addCheckbox('is_enabled', Translate::noop('Activated')); $this->addCheckbox('is_confidential', '{oidc:client:is_confidential}'); - $this->addSelect('auth_source', '{oidc:client:auth_source}:') + $this->addSelect('auth_source', Translate::noop('Authentication source')) ->setHtmlAttribute('class', 'full-width') ->setItems($this->sspBridge->auth()->source()->getSources(), false) ->setPrompt(Translate::noop('-')); $scopes = $this->getScopes(); - $this->addMultiSelect('scopes', '{oidc:client:scopes}', $scopes, 10) + $this->addMultiSelect('scopes', Translate::noop('Scopes'), $scopes, 10) ->setHtmlAttribute('class', 'full-width') ->setRequired(Translate::noop('At least one scope is required.')); - $this->addText('owner', '{oidc:client:owner}') + $this->addText('owner', Translate::noop('Owner')) ->setMaxLength(190); - $this->addTextArea('post_logout_redirect_uri', '{oidc:client:post_logout_redirect_uri}', null, 5) + $this->addTextArea('post_logout_redirect_uri', Translate::noop('Post-logout Redirect URIs'), null, 5) ->setHtmlAttribute('class', 'full-width'); - $this->addTextArea('allowed_origin', '{oidc:client:allowed_origin}', null, 5) + $this->addTextArea('allowed_origin', Translate::noop('Allowed origins for public clients'), null, 5) ->setHtmlAttribute('class', 'full-width'); - $this->addText('backchannel_logout_uri', '{oidc:client:backchannel_logout_uri}') + $this->addText('backchannel_logout_uri', Translate::noop('Back-Channel Logout URI')) ->setHtmlAttribute('class', 'full-width'); $this->addText('entity_identifier', 'Entity Identifier') diff --git a/src/Services/Container.php b/src/Services/Container.php index d08a82cb..c1d5bf4b 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -30,6 +30,7 @@ use SimpleSAML\Configuration; use SimpleSAML\Database; use SimpleSAML\Error\Exception; +use SimpleSAML\Locale\Translate; use SimpleSAML\Metadata\MetaDataStorageHandler; use SimpleSAML\Module\oidc\Admin\Menu; use SimpleSAML\Module\oidc\Bridges\PsrHttpBridge; @@ -147,7 +148,11 @@ public function __construct() $helpers = new Helpers(); $this->services[Helpers::class] = $helpers; - $csrfProtection = new CsrfProtection('{oidc:client:csrf_error}', $session); + $csrfProtection = new CsrfProtection( + Translate::noop('Your session has expired. Please return to the home page and try again.'), + $session, + ); + $formFactory = new FormFactory( $moduleConfig, $csrfProtection, diff --git a/templates/clients.twig b/templates/clients.twig index 2de33798..84cdcb7e 100644 --- a/templates/clients.twig +++ b/templates/clients.twig @@ -53,10 +53,10 @@ {{ client.description }}
- {{ 'Registration:'|trans }} {{ client.registrationType.description }} | - {{ 'Created at:'|trans }} {{ client.createdAt ? client.createdAt|date() : 'n/a' }} | - {{ 'Updated at:'|trans }} {{ client.updatedAt ? client.updatedAt|date() : 'n/a' }} | - {{ 'Expires at:'|trans }} {{ client.expiresAt ? client.expiresAt|date() : 'never' }} + {{ 'Registration'|trans }}: {{ client.registrationType.description }} | + {{ 'Created at'|trans }}: {{ client.createdAt ? client.createdAt|date() : 'n/a' }} | + {{ 'Updated at'|trans }}: {{ client.updatedAt ? client.updatedAt|date() : 'n/a' }} | + {{ 'Expires at'|trans }}: {{ client.expiresAt ? client.expiresAt|date() : 'never'|trans }} diff --git a/templates/clients/includes/form.twig b/templates/clients/includes/form.twig index 1eb8fc97..c5049b3d 100644 --- a/templates/clients/includes/form.twig +++ b/templates/clients/includes/form.twig @@ -87,7 +87,7 @@ {{ form.scopes.getError }} {% endif %} - + {{ form.backchannel_logout_uri.control | raw }} {% trans %}Enter if client supports Back-Channel Logout specification. When logout is initiated at the OpenID Provider, it will send a Logout Token to this URI in order to notify the client about that event. Must be a valid URI. Example: https://example.org/foo?bar=1{% endtrans %} @@ -96,7 +96,7 @@ {{ form.backchannel_logout_uri.getError }} {% endif %} - + {{ form.post_logout_redirect_uri.control | raw }} {% trans %}Allowed redirect URIs to use after client initiated logout. Must be a valid URI, one per line. Example: https://example.org/foo?bar=1{% endtrans %} diff --git a/templates/clients/show.twig b/templates/clients/show.twig index 525c8c1c..f9b2d5ff 100644 --- a/templates/clients/show.twig +++ b/templates/clients/show.twig @@ -40,10 +40,10 @@
- {{ 'Registration:'|trans }} {{ client.registrationType.description }} | - {{ 'Created at:'|trans }} {{ client.createdAt ? client.createdAt|date() : 'n/a' }} | - {{ 'Updated at:'|trans }} {{ client.updatedAt ? client.updatedAt|date() : 'n/a' }} | - {{ 'Expires at:'|trans }} {{ client.expiresAt ? client.expiresAt|date() : 'never' }} + {{ 'Registration'|trans }}: {{ client.registrationType.description }} | + {{ 'Created at'|trans }}: {{ client.createdAt ? client.createdAt|date() : 'n/a' }} | + {{ 'Updated at'|trans }}: {{ client.updatedAt ? client.updatedAt|date() : 'n/a' }} | + {{ 'Expires at'|trans }}: {{ client.expiresAt ? client.expiresAt|date() : 'never'|trans }}

diff --git a/templates/config/migrations.twig b/templates/config/migrations.twig index 007495b5..127b11bc 100644 --- a/templates/config/migrations.twig +++ b/templates/config/migrations.twig @@ -22,9 +22,7 @@ {% endif %}
- Before running the migrations, make sure that the database user has proper privileges to change the scheme - (for example, alter, create, drop, index). After running the migrations, it is a good practice to remove - those privileges. + {{ 'Before running the migrations, make sure that the database user has proper privileges to change the scheme (for example, alter, create, drop, index). After running the migrations, it is a good practice to remove those privileges.'|trans }}
{% endblock oidcContent -%} diff --git a/templates/includes/menu.twig b/templates/includes/menu.twig index 014fd0a0..a1d1b25c 100644 --- a/templates/includes/menu.twig +++ b/templates/includes/menu.twig @@ -5,7 +5,7 @@
  • {{ item.label }} + > {{ item.label|trans }}
  • {% endfor %} diff --git a/templates/logout.twig b/templates/logout.twig index db712fb4..6e13ef29 100644 --- a/templates/logout.twig +++ b/templates/logout.twig @@ -5,22 +5,22 @@ {% block oidcContent %}

    {% if wasLogoutActionCalled %} - {{ '{oidc:logout:page_title_success}'|trans }} + {{ 'Logout Successful'|trans }} {% else %} - {{ '{oidc:logout:page_title_fail}'|trans }} + {{ 'Logout Failed'|trans }} {% endif %}

    - {{ '{oidc:logout:info_title}'|trans }} + {{ 'Info'|trans }}

    {% if wasLogoutActionCalled %} - {{ '{oidc:logout:info_message_success}'|trans }} + {{ 'You can now close this window or navigate to another page.'|trans }} {% else %} - {{ '{oidc:logout:info_message_fail}'|trans }} + {{ 'Requested session was not found or it is expired.'|trans }} {% endif %}

    diff --git a/templates/tests/trust-chain-resolution.twig b/templates/tests/trust-chain-resolution.twig index 972aa720..fde0f21d 100644 --- a/templates/tests/trust-chain-resolution.twig +++ b/templates/tests/trust-chain-resolution.twig @@ -54,13 +54,13 @@

    {{ 'Resolved chains'|trans }}

    {% if trustChainBag|default %}

    - {{ 'Total chains:'|trans }} {{ trustChainBag.getCount }} + {{ 'Total chains'|trans }}: {{ trustChainBag.getCount }}

    {% for index, trustChain in trustChainBag.getAll %}

    - {{ loop.index }}. {{ 'Trust Anchor ID:'|trans }} {{ trustChain.getResolvedTrustAnchor.getIssuer }} + {{ loop.index }}. {{ 'Trust Anchor ID'|trans }}: {{ trustChain.getResolvedTrustAnchor.getIssuer }}

    - {{ 'Path:'|trans }} + {{ 'Path'|trans }}:
    {% for entity in trustChain.getEntities %} {% if loop.index > 1 %} @@ -69,7 +69,7 @@ {% endfor %}
    - {{ 'Resolved metadata:' }}
    + {{ 'Resolved metadata' }}:
    {% if resolvedMetadata[index]|default is not empty %} {{- resolvedMetadata[index]|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}} diff --git a/tests/unit/src/Factories/FormFactoryTest.php b/tests/unit/src/Factories/FormFactoryTest.php new file mode 100644 index 00000000..dbeb5623 --- /dev/null +++ b/tests/unit/src/Factories/FormFactoryTest.php @@ -0,0 +1,66 @@ +moduleConfigMock = $this->createMock(ModuleConfig::class); + $this->csrfProtectionMock = $this->createMock(CsrfProtection::class); + $this->sspBridgeMock = $this->createMock(SspBridge::class); + $this->helpersMock = $this->createMock(Helpers::class); + } + + protected function sut( + ?ModuleConfig $moduleConfig = null, + ?CsrfProtection $csrfProtection = null, + ?SspBridge $sspBridge = null, + ?Helpers $helpers = null, + ): FormFactory { + $moduleConfig ??= $this->moduleConfigMock; + $csrfProtection ??= $this->csrfProtectionMock; + $sspBridge ??= $this->sspBridgeMock; + $helpers ??= $this->helpersMock; + + return new FormFactory( + $moduleConfig, + $csrfProtection, + $sspBridge, + $helpers, + ); + } + + public function testCanConstruct(): void + { + $this->assertInstanceOf(FormFactory::class, $this->sut()); + } + + public function testCanBuildClientForm(): void + { + $this->assertInstanceOf( + ClientForm::class, + $this->sut()->build(ClientForm::class), + ); + } +}