From d2236db274c9cbf23f09f8a8584d92f4f0e012a1 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Thu, 3 Aug 2023 22:01:34 +0200 Subject: [PATCH 001/130] Move to PHPv8 (#206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bump to PHP 8.0 * Add version compatibility note to readme * Fix psalm error * Update composer.json --------- Co-authored-by: Marko Ivančić --- .github/workflows/test.yaml | 12 ++++++------ README.md | 8 ++++++++ composer.json | 10 +++++----- src/Form/ClientForm.php | 6 ++++++ 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ad302b9a..f5b6b2a4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ["7.4", "8.0", "8.1"] + php-versions: ["8.0", "8.1", "8.2"] 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 != '7.4' }} + if: ${{ matrix.php-versions != '8.0' }} 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 == '7.4' }} + if: ${{ matrix.php-versions == '8.0' }} uses: actions/upload-artifact@v1 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: "7.4" + php-version: "8.0" 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: "7.4" + php-version: "8.0" 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: "7.4" + php-version: "8.0" tools: composer:v2 extensions: mbstring, xml diff --git a/README.md b/README.md index 95ab8697..bf014b0d 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,14 @@ Currently supported flows are: ![Main screen capture](docs/oidc.png) +## Version compatibility + +| OIDC module | SimpleSAMLphp | PHP | Note | +|:------------|:--------------|:------:|-----------------------------| +| v4.\* | v2.\* | \>=8.0 | Recommended | +| v3.\* | v2.\* | \>=7.4 | Abandoned from August 2023. | +| v2.\* | v1.19.\* | \>=7.4 | | + ## Installation Installation can be as easy as executing: diff --git a/composer.json b/composer.json index 7e3a6778..d59c56cd 100644 --- a/composer.json +++ b/composer.json @@ -17,16 +17,16 @@ } ], "require": { - "php": ">=7.4", - "ext-curl": ">=7.4", + "php": "^8.0", + "ext-curl": "*", "ext-json": "*", "ext-openssl": "*", "ext-pdo": "*", "guzzlehttp/guzzle": "^7.0", - "laminas/laminas-diactoros": "^2.2.1", - "laminas/laminas-httphandlerrunner": "^1.1.0", + "laminas/laminas-diactoros": "^2.25.2", + "laminas/laminas-httphandlerrunner": "^2", "lcobucci/jwt": "^4.1", - "league/oauth2-server": "^8.1.0", + "league/oauth2-server": "^8.5.3", "nette/forms": "^3", "psr/container": "^1.0", "psr/log": "^1.1", diff --git a/src/Form/ClientForm.php b/src/Form/ClientForm.php index a9ae469c..532498cb 100644 --- a/src/Form/ClientForm.php +++ b/src/Form/ClientForm.php @@ -102,6 +102,12 @@ public function validateBackChannelLogoutUri(Form $form): void } } + /** + * @param array $values + * @param non-empty-string $regex + * @param string $messagePrefix + * @return void + */ protected function validateByMatchingRegex( array $values, string $regex, From 8bb1c24bc715537d895516fdfb185ee7bec4aedb Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Thu, 17 Aug 2023 13:20:35 +0200 Subject: [PATCH 002/130] Update upgrade log (#207) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marko Ivančić --- UPGRADE.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/UPGRADE.md b/UPGRADE.md index 6ae4f253..920e75bc 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,3 +1,7 @@ +# Version 3 to 4 +- PHP version requirement was bumped to v8.0 to enable updating important dependant packages like 'league/oauth2-server' + which has already moved to PHPv8 between their minor releases. + # Version 2 to 3 - Module code was refactored to make it compatible with SimpleSAMLphp v2 - Default key name was changed from oidc_module.pem to oidc_module.key. If you don't set custom From 895abfd084c33d32eb3938f76bc7358499f49601 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Fri, 1 Sep 2023 16:22:06 +0200 Subject: [PATCH 003/130] Set SSP version requirement to v2.0.* (#208) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marko Ivančić --- README.md | 4 ++-- UPGRADE.md | 1 + composer.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bf014b0d..1692c5ec 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ Currently supported flows are: | OIDC module | SimpleSAMLphp | PHP | Note | |:------------|:--------------|:------:|-----------------------------| -| v4.\* | v2.\* | \>=8.0 | Recommended | -| v3.\* | v2.\* | \>=7.4 | Abandoned from August 2023. | +| v4.\* | v2.0.\* | \>=8.0 | Recommended | +| v3.\* | v2.0.0 | \>=7.4 | Abandoned from August 2023. | | v2.\* | v1.19.\* | \>=7.4 | | ## Installation diff --git a/UPGRADE.md b/UPGRADE.md index 920e75bc..ebc7c234 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,6 +1,7 @@ # Version 3 to 4 - PHP version requirement was bumped to v8.0 to enable updating important dependant packages like 'league/oauth2-server' which has already moved to PHPv8 between their minor releases. +- SimpleSAMLphp version fixed to v2.0.* # Version 2 to 3 - Module code was refactored to make it compatible with SimpleSAMLphp v2 diff --git a/composer.json b/composer.json index d59c56cd..000c2345 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "phpunit/php-code-coverage": "^9.0.0", "phpunit/phpcov": "^8.2.0", "phpunit/phpunit": "^9.0.0", - "simplesamlphp/simplesamlphp": "^v2.0.0", + "simplesamlphp/simplesamlphp": "2.0.*", "simplesamlphp/simplesamlphp-test-framework": "^1.2.1", "squizlabs/php_codesniffer": "^3.7", "vimeo/psalm": "^5.8" From 9b3cd12813398d67b345558175494a70bb7f5e4b Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Sun, 3 Dec 2023 12:07:39 +0100 Subject: [PATCH 004/130] Start with version 5 (#211) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move to PHP v8.1 * Move to psalm level 1 * Remove dependency on steverhoades/oauth2-openid-connect-server * Move ClaimTranslatorExtractor to Utils * Move ConfigurationService to src * Rename ConfigurationService to ModuleConfig * Move to module config constants * Normalize codebase * First Symfony route (OP configuration) * Update tests * Set SSP requirement to 2.1 * Update upgrade log --------- Co-authored-by: Marko Ivančić --- .github/workflows/test.yaml | 12 +- CHANGELOG.md | 88 ------- CONFORMANCE_TEST.md | 23 +- FAQ.md | 12 +- README.md | 29 +-- UPGRADE.md | 26 +- composer.json | 31 ++- config-templates/module_oidc.php | 179 +++++++------- docker/Dockerfile | 3 +- docker/nginx-certs/README.md | 3 +- docker/ssp/authsources.php | 4 +- docker/ssp/config-override.php | 3 + docker/ssp/module_oidc.php | 142 ++--------- hooks/hook_cron.php | 43 +++- hooks/hook_federationpage.php | 21 +- hooks/hook_frontpage.php | 23 +- phpcs.xml | 15 +- phpunit.xml | 21 +- psalm.xml | 60 ++--- public/admin-clients/delete.php | 6 +- public/admin-clients/edit.php | 6 +- public/admin-clients/index.php | 6 +- public/admin-clients/new.php | 6 +- public/admin-clients/reset.php | 6 +- public/admin-clients/show.php | 6 +- public/authorize.php | 6 +- public/clients/delete.php | 6 +- public/clients/edit.php | 6 +- public/clients/index.php | 6 +- public/clients/new.php | 6 +- public/clients/reset.php | 6 +- public/clients/show.php | 6 +- public/install.php | 6 +- public/jwks.php | 6 +- public/logout.php | 2 + public/openid-configuration.php | 6 +- public/token.php | 6 +- public/userinfo.php | 6 +- rector.php | 35 +++ routing/routes/routes.yml | 7 +- routing/services/services.yml | 65 +++-- ...ntroller.php => AccessTokenController.php} | 20 +- ...roller.php => AuthorizationController.php} | 69 +++--- .../CreateController.php} | 85 +++---- .../DeleteController.php} | 41 ++-- .../EditController.php} | 85 +++---- .../IndexController.php} | 38 ++- .../ResetSecretController.php} | 31 +-- .../ShowController.php} | 34 ++- .../ConfigurationDiscoveryController.php | 32 +++ ...Controller.php => InstallerController.php} | 47 ++-- ...tJwksController.php => JwksController.php} | 15 +- src/Controller/LogoutController.php | 58 ++--- ...ConnectDiscoverConfigurationController.php | 38 --- ...AuthenticatedGetClientFromRequestTrait.php | 24 +- .../Traits/GetClientFromRequestTrait.php | 18 +- ...oController.php => UserInfoController.php} | 73 ++---- .../AccessTokenEntity.php | 87 ++++--- src/Entities/AuthCodeEntity.php | 99 ++++++++ src/Entities/ClaimSetEntity.php | 31 +++ src/{Entity => Entities}/ClientEntity.php | 101 ++++++-- .../Interfaces/AccessTokenEntityInterface.php | 4 +- .../Interfaces/AuthCodeEntityInterface.php | 7 +- .../Interfaces/ClaimSetEntityInterface.php | 16 ++ src/Entities/Interfaces/ClaimSetInterface.php | 17 ++ .../Interfaces/ClientEntityInterface.php | 11 +- .../EntityStringRepresentationInterface.php | 5 +- .../Interfaces/MementoInterface.php | 10 +- .../RefreshTokenEntityInterface.php | 4 +- src/Entities/Interfaces/ScopeInterface.php | 17 ++ ...TokenAssociatableWithAuthCodeInterface.php | 4 +- .../Interfaces/TokenRevokableInterface.php | 4 +- .../RefreshTokenEntity.php | 30 ++- src/{Entity => Entities}/ScopeEntity.php | 28 ++- .../Traits/AssociateWithAuthCodeTrait.php | 9 +- .../Traits/OidcAuthCodeTrait.php | 11 +- .../Traits/RevokeTokenTrait.php | 12 +- src/{Entity => Entities}/UserEntity.php | 59 +++-- src/Entity/AuthCodeEntity.php | 74 ------ src/Factories/AuthSimpleFactory.php | 16 +- src/Factories/AuthorizationServerFactory.php | 112 ++------- .../ClaimTranslatorExtractorFactory.php | 50 ++-- src/Factories/CryptKeyFactory.php | 36 ++- src/Factories/FormFactory.php | 25 +- src/Factories/Grant/AuthCodeGrantFactory.php | 62 ++--- src/Factories/Grant/ImplicitGrantFactory.php | 36 +-- .../Grant/OAuth2ImplicitGrantFactory.php | 22 +- .../Grant/RefreshTokenGrantFactory.php | 20 +- src/Factories/IdTokenResponseFactory.php | 43 +--- src/Factories/ResourceServerFactory.php | 27 +-- src/Factories/TemplateFactory.php | 13 +- src/{Form => Forms}/ClientForm.php | 118 ++++----- .../Controls/CsrfProtection.php | 24 +- ...figurationService.php => ModuleConfig.php} | 131 ++++++++-- .../AbstractDatabaseRepository.php | 33 +-- src/Repositories/AccessTokenRepository.php | 53 +++-- src/Repositories/AllowedOriginRepository.php | 5 +- src/Repositories/AuthCodeRepository.php | 36 ++- src/Repositories/ClientRepository.php | 107 +++++---- .../CodeChallengeVerifiersRepository.php | 14 +- .../AccessTokenRepositoryInterface.php | 5 +- .../AuthCodeRepositoryInterface.php | 6 +- .../Interfaces/IdentityProviderInterface.php | 20 ++ .../RefreshTokenRepositoryInterface.php | 9 +- src/Repositories/RefreshTokenRepository.php | 31 ++- src/Repositories/ScopeRepository.php | 45 ++-- .../Traits/RevokeTokenByAuthCodeIdTrait.php | 2 + src/Repositories/UserRepository.php | 37 +-- .../RelyingPartyAssociationInterface.php | 2 + .../Associations/RelyingPartyAssociation.php | 28 +-- src/Server/AuthorizationServer.php | 18 +- src/Server/Exceptions/OidcServerException.php | 38 +-- src/Server/Grants/AuthCodeGrant.php | 223 +++++++++++++----- src/Server/Grants/ImplicitGrant.php | 43 +++- ...lidatableWithCheckerResultBagInterface.php | 6 +- .../OidcCapableGrantTypeInterface.php | 2 + .../PkceEnabledGrantTypeInterface.php | 2 + src/Server/Grants/OAuth2ImplicitGrant.php | 70 +++++- src/Server/Grants/RefreshTokenGrant.php | 82 ++++++- .../Grants/Traits/IssueAccessTokenTrait.php | 23 +- .../BackChannelLogoutHandler.php | 26 +- .../RequestTypes/AuthorizationRequest.php | 13 +- src/Server/RequestTypes/LogoutRequest.php | 60 ++--- src/Server/ResponseTypes/IdTokenResponse.php | 56 +++-- .../Interfaces/AcrResponseTypeInterface.php | 2 + .../AuthTimeResponseTypeInterface.php | 2 + .../Interfaces/NonceResponseTypeInterface.php | 2 + .../SessionIdResponseTypeInterface.php | 2 + .../Validators/BearerTokenValidator.php | 67 ++++-- src/Services/AuthContextService.php | 58 ++--- src/Services/AuthProcService.php | 48 ++-- src/Services/AuthenticationService.php | 98 ++++---- src/Services/Container.php | 90 ++++--- src/Services/DatabaseLegacyOAuth2Import.php | 21 +- src/Services/DatabaseMigration.php | 97 ++++---- src/Services/IdTokenBuilder.php | 61 +++-- src/Services/JsonWebKeySetService.php | 23 +- src/Services/JsonWebTokenBuilderService.php | 25 +- src/Services/LoggerService.php | 58 ++--- src/Services/LogoutTokenBuilder.php | 10 +- ...adataService.php => OpMetadataService.php} | 30 +-- src/Services/RoutingService.php | 43 +++- src/Services/SessionMessagesService.php | 18 +- src/Services/SessionService.php | 61 +++-- src/Store/SessionLogoutTicketStoreBuilder.php | 29 --- .../SessionLogoutTicketStoreInterface.php | 11 - .../Session/LogoutTicketStoreBuilder.php | 31 +++ .../Session/LogoutTicketStoreDb.php} | 11 +- .../Session/LogoutTicketStoreInterface.php | 17 ++ src/Utils/Arr.php | 7 +- .../Interfaces/RequestRuleInterface.php | 6 +- .../Checker/Interfaces/ResultBagInterface.php | 12 +- .../Checker/Interfaces/ResultInterface.php | 7 +- src/Utils/Checker/RequestRulesManager.php | 23 +- src/Utils/Checker/Result.php | 23 +- src/Utils/Checker/ResultBag.php | 15 +- src/Utils/Checker/Rules/AbstractRule.php | 20 +- src/Utils/Checker/Rules/AcrValuesRule.php | 12 +- .../Checker/Rules/AddClaimsToIdTokenRule.php | 4 +- src/Utils/Checker/Rules/ClientIdRule.php | 10 +- .../Checker/Rules/CodeChallengeMethodRule.php | 12 +- src/Utils/Checker/Rules/CodeChallengeRule.php | 3 + src/Utils/Checker/Rules/IdTokenHintRule.php | 19 +- src/Utils/Checker/Rules/MaxAgeRule.php | 18 +- .../Rules/PostLogoutRedirectUriRule.php | 10 +- src/Utils/Checker/Rules/PromptRule.php | 20 +- src/Utils/Checker/Rules/RedirectUriRule.php | 15 +- .../Checker/Rules/RequestParameterRule.php | 7 +- .../Checker/Rules/RequestedClaimsRule.php | 22 +- src/Utils/Checker/Rules/RequiredNonceRule.php | 2 + .../Checker/Rules/RequiredOpenIdScopeRule.php | 9 +- src/Utils/Checker/Rules/ResponseTypeRule.php | 2 + .../Checker/Rules/ScopeOfflineAccessRule.php | 14 +- src/Utils/Checker/Rules/ScopeRule.php | 17 +- src/Utils/Checker/Rules/StateRule.php | 2 + src/Utils/Checker/Rules/UiLocalesRule.php | 2 + src/{ => Utils}/ClaimTranslatorExtractor.php | 188 ++++++++++++--- src/Utils/FingerprintGenerator.php | 16 +- src/Utils/ScopeHelper.php | 4 +- src/Utils/TimestampGenerator.php | 15 +- src/Utils/UniqueIdentifierGenerator.php | 4 +- templates/clients/_form.twig | 10 +- templates/clients/delete.twig | 8 +- templates/clients/index.twig | 23 +- templates/clients/show.twig | 8 +- templates/install.twig | 4 +- tests/Form/ClientFormTest.php | 17 -- tests/Services/ConfigurationServiceTest.php | 48 ---- .../SessionLogoutTicketStoreBuilderTest.php | 32 --- tests/bootstrap.php | 2 + tests/config/module_oidc.php | 196 ++------------- .../Controller/AccessTokenControllerTest.php} | 36 +-- .../AuthorizationControllerTest.php} | 115 +++++---- .../Client/CreateControllerTest.php} | 90 +++---- .../Client/DeleteControllerTest.php} | 86 +++---- .../Controller/Client/EditControllerTest.php} | 125 +++++----- .../Client/IndexControllerTest.php} | 61 ++--- .../Client/ResetSecretControllerTest.php} | 72 +++--- .../Controller/Client/ShowControllerTest.php} | 79 ++++--- .../ConfigurationDiscoveryControllerTest.php} | 38 +-- .../Controller/InstallerControllerTest.php} | 70 +++--- .../Controller/JwksControllerTest.php} | 28 +-- .../Controller/LogoutControllerTest.php | 115 +++------ .../Controller/UserInfoControllerTest.php} | 98 ++++---- .../Entities}/AccessTokenEntityTest.php | 41 +++- .../Entities}/AuthCodeEntityTest.php | 44 +++- .../Entities}/ClientEntityTest.php | 36 ++- .../Entities}/RefreshTokenEntityTest.php | 34 ++- .../Entities}/ScopeEntityTest.php | 8 +- .../Entities}/UserEntityTest.php | 26 +- .../Factories/AuthSimpleFactoryTest.php | 4 +- .../ClaimTranslatorExtractorFactoryTest.php | 37 +-- tests/src/Forms/ClientFormTest.php | 19 ++ tests/src/ModuleConfigTest.php | 57 +++++ .../AccessTokenRepositoryTest.php | 66 ++++-- .../AllowedOriginRepositoryTest.php | 28 +-- .../Repositories/AuthCodeRepositoryTest.php | 62 +++-- .../Repositories/ClientRepositoryTest.php | 91 ++++++- .../RefreshTokenRepositoryTest.php | 80 +++++-- .../Repositories/ScopeRepositoryTest.php | 26 +- .../Repositories/UserRepositoryTest.php | 36 ++- .../RelyingPartyAssociationTest.php | 2 + .../Server/AuthorizationServerTest.php | 4 +- .../Server/Grants/AuthCodeGrantTest.php | 45 ++-- .../Server/Grants/ImplicitGrantTest.php | 4 +- .../Server/Grants/OAuth2ImplicitGrantTest.php | 4 +- .../BackChannelLogoutHandlerTest.php | 20 +- .../RequestTypes/AuthorizationRequestTest.php | 4 +- .../Server/RequestTypes/LogoutRequestTest.php | 9 +- .../ResponseTypes/IdTokenResponseTest.php | 89 +++---- .../Validators/BearerTokenValidatorTest.php | 124 ++++------ .../Services/AuthContextServiceTest.php | 74 ++++-- .../Services/AuthProcServiceTest.php | 38 ++- .../Services/AuthenticationServiceTest.php | 100 +++++--- .../{ => src}/Services/IdTokenBuilderTest.php | 4 +- .../Services/JsonWebKeySetServiceTest.php | 24 +- .../JsonWebTokenBuilderServiceTest.php | 46 ++-- .../Services/LogoutTokenBuilderTest.php | 43 ++-- .../Services/OpMetadataServiceTest.php} | 42 +++- .../Services/SessionMessagesServiceTest.php | 16 +- .../{ => src}/Services/SessionServiceTest.php | 4 +- .../Session/LogoutTicketStoreBuilderTest.php | 34 +++ .../Session/LogoutTicketStoreDbTest.php} | 18 +- .../Utils/Checker/RequestRulesManagerTest.php | 41 ++-- .../{ => src}/Utils/Checker/ResultBagTest.php | 13 +- tests/{ => src}/Utils/Checker/ResultTest.php | 10 +- .../Utils/Checker/Rules/AcrValuesRuleTest.php | 15 +- .../Rules/AddClaimsToIdTokenRuleTest.php | 21 +- .../Utils/Checker/Rules/ClientIdRuleTest.php | 35 +-- .../Rules/CodeChallengeMethodRuleTest.php | 21 +- .../Checker/Rules/CodeChallengeRuleTest.php | 23 +- .../Checker/Rules/IdTokenHintRuleTest.php | 64 ++--- .../Rules/PostLogoutRedirectUriRuleTest.php | 63 ++--- .../Checker/Rules/RedirectUriRuleTest.php | 37 +-- .../Checker/Rules/RequestedClaimsRuleTest.php | 33 +-- .../Checker/Rules/RequiredNonceRuleTest.php | 21 +- .../Rules/RequiredOpenIdScopeRuleTest.php | 23 +- .../Checker/Rules/ResponseTypeRuleTest.php | 15 +- .../Rules/ScopeOfflineAccessRuleTest.php | 99 ++++---- .../Utils/Checker/Rules/ScopeRuleTest.php | 20 +- .../Utils/Checker/Rules/StateRuleTest.php | 15 +- .../Utils/Checker/Rules/UiLocalesRuleTest.php | 17 +- .../Utils}/ClaimTranslatorExtractorTest.php | 25 +- tests/{ => src}/Utils/ScopeHelperTest.php | 32 +-- .../Utils/UniqueIdentifierGeneratorTest.php | 2 + 265 files changed, 4971 insertions(+), 4066 deletions(-) delete mode 100644 CHANGELOG.md create mode 100644 rector.php rename src/Controller/{OAuth2AccessTokenController.php => AccessTokenController.php} (67%) rename src/Controller/{OAuth2AuthorizationController.php => AuthorizationController.php} (72%) rename src/Controller/{ClientCreateController.php => Client/CreateController.php} (53%) rename src/Controller/{ClientDeleteController.php => Client/DeleteController.php} (74%) rename src/Controller/{ClientEditController.php => Client/EditController.php} (62%) rename src/Controller/{ClientIndexController.php => Client/IndexController.php} (68%) rename src/Controller/{ClientResetSecretController.php => Client/ResetSecretController.php} (84%) rename src/Controller/{ClientShowController.php => Client/ShowController.php} (70%) create mode 100644 src/Controller/ConfigurationDiscoveryController.php rename src/Controller/{OpenIdConnectInstallerController.php => InstallerController.php} (64%) rename src/Controller/{OpenIdConnectJwksController.php => JwksController.php} (66%) delete mode 100644 src/Controller/OpenIdConnectDiscoverConfigurationController.php rename src/Controller/{OpenIdConnectUserInfoController.php => UserInfoController.php} (70%) rename src/{Entity => Entities}/AccessTokenEntity.php (64%) create mode 100644 src/Entities/AuthCodeEntity.php create mode 100644 src/Entities/ClaimSetEntity.php rename src/{Entity => Entities}/ClientEntity.php (61%) rename src/{Entity => Entities}/Interfaces/AccessTokenEntityInterface.php (74%) rename src/{Entity => Entities}/Interfaces/AuthCodeEntityInterface.php (76%) create mode 100644 src/Entities/Interfaces/ClaimSetEntityInterface.php create mode 100644 src/Entities/Interfaces/ClaimSetInterface.php rename src/{Entity => Entities}/Interfaces/ClientEntityInterface.php (86%) rename src/{Entity => Entities}/Interfaces/EntityStringRepresentationInterface.php (67%) rename src/{Entity => Entities}/Interfaces/MementoInterface.php (77%) rename src/{Entity => Entities}/Interfaces/RefreshTokenEntityInterface.php (74%) create mode 100644 src/Entities/Interfaces/ScopeInterface.php rename src/{Entity => Entities}/Interfaces/TokenAssociatableWithAuthCodeInterface.php (79%) rename src/{Entity => Entities}/Interfaces/TokenRevokableInterface.php (74%) rename src/{Entity => Entities}/RefreshTokenEntity.php (60%) rename src/{Entity => Entities}/ScopeEntity.php (76%) rename src/{Entity => Entities}/Traits/AssociateWithAuthCodeTrait.php (66%) rename src/{Entity => Entities}/Traits/OidcAuthCodeTrait.php (66%) rename src/{Entity => Entities}/Traits/RevokeTokenTrait.php (82%) rename src/{Entity => Entities}/UserEntity.php (63%) delete mode 100644 src/Entity/AuthCodeEntity.php rename src/{Form => Forms}/ClientForm.php (65%) rename src/{Form => Forms}/Controls/CsrfProtection.php (71%) rename src/{Services/ConfigurationService.php => ModuleConfig.php} (55%) create mode 100644 src/Repositories/Interfaces/IdentityProviderInterface.php rename src/Services/{OidcOpenIdProviderMetadataService.php => OpMetadataService.php} (60%) delete mode 100644 src/Store/SessionLogoutTicketStoreBuilder.php delete mode 100644 src/Store/SessionLogoutTicketStoreInterface.php create mode 100644 src/Stores/Session/LogoutTicketStoreBuilder.php rename src/{Store/SessionLogoutTicketStoreDb.php => Stores/Session/LogoutTicketStoreDb.php} (90%) create mode 100644 src/Stores/Session/LogoutTicketStoreInterface.php rename src/{ => Utils}/ClaimTranslatorExtractor.php (52%) delete mode 100644 tests/Form/ClientFormTest.php delete mode 100644 tests/Services/ConfigurationServiceTest.php delete mode 100644 tests/Store/SessionLogoutTicketStoreBuilderTest.php rename tests/{Controller/OAuth2AccessTokenControllerTest.php => src/Controller/AccessTokenControllerTest.php} (57%) rename tests/{Controller/OAuth2AuthorizationControllerTest.php => src/Controller/AuthorizationControllerTest.php} (84%) rename tests/{Controller/ClientCreateControllerTest.php => src/Controller/Client/CreateControllerTest.php} (79%) rename tests/{Controller/ClientDeleteControllerTest.php => src/Controller/Client/DeleteControllerTest.php} (77%) rename tests/{Controller/ClientEditControllerTest.php => src/Controller/Client/EditControllerTest.php} (85%) rename tests/{Controller/ClientIndexControllerTest.php => src/Controller/Client/IndexControllerTest.php} (62%) rename tests/{Controller/ClientResetSecretControllerTest.php => src/Controller/Client/ResetSecretControllerTest.php} (82%) rename tests/{Controller/ClientShowControllerTest.php => src/Controller/Client/ShowControllerTest.php} (71%) rename tests/{Controller/OpenIdConnectDiscoverConfigurationControllerTest.php => src/Controller/ConfigurationDiscoveryControllerTest.php} (56%) rename tests/{Controller/OpenIdConnectInstallerControllerTest.php => src/Controller/InstallerControllerTest.php} (80%) rename tests/{Controller/OpenIdConnectJwksControllerTest.php => src/Controller/JwksControllerTest.php} (61%) rename tests/{ => src}/Controller/LogoutControllerTest.php (81%) rename tests/{Controller/OpenIdConnectUserInfoControllerTest.php => src/Controller/UserInfoControllerTest.php} (84%) rename tests/{Entity => src/Entities}/AccessTokenEntityTest.php (77%) rename tests/{Entity => src/Entities}/AuthCodeEntityTest.php (70%) rename tests/{Entity => src/Entities}/ClientEntityTest.php (84%) rename tests/{Entity => src/Entities}/RefreshTokenEntityTest.php (64%) rename tests/{Entity => src/Entities}/ScopeEntityTest.php (88%) rename tests/{Entity => src/Entities}/UserEntityTest.php (75%) rename tests/{ => src}/Factories/AuthSimpleFactoryTest.php (81%) rename tests/{ => src}/Factories/ClaimTranslatorExtractorFactoryTest.php (80%) create mode 100644 tests/src/Forms/ClientFormTest.php create mode 100644 tests/src/ModuleConfigTest.php rename tests/{ => src}/Repositories/AccessTokenRepositoryTest.php (67%) rename tests/{ => src}/Repositories/AllowedOriginRepositoryTest.php (79%) rename tests/{ => src}/Repositories/AuthCodeRepositoryTest.php (69%) rename tests/{ => src}/Repositories/ClientRepositoryTest.php (83%) rename tests/{ => src}/Repositories/RefreshTokenRepositoryTest.php (60%) rename tests/{ => src}/Repositories/ScopeRepositoryTest.php (80%) rename tests/{ => src}/Repositories/UserRepositoryTest.php (79%) rename tests/{ => src}/Server/Associations/RelyingPartyAssociationTest.php (98%) rename tests/{ => src}/Server/AuthorizationServerTest.php (79%) rename tests/{ => src}/Server/Grants/AuthCodeGrantTest.php (65%) rename tests/{ => src}/Server/Grants/ImplicitGrantTest.php (81%) rename tests/{ => src}/Server/Grants/OAuth2ImplicitGrantTest.php (82%) rename tests/{ => src}/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php (83%) rename tests/{ => src}/Server/RequestTypes/AuthorizationRequestTest.php (83%) rename tests/{ => src}/Server/RequestTypes/LogoutRequestTest.php (92%) rename tests/{ => src}/Server/ResponseTypes/IdTokenResponseTest.php (80%) rename tests/{ => src}/Server/Validators/BearerTokenValidatorTest.php (80%) rename tests/{ => src}/Services/AuthContextServiceTest.php (72%) rename tests/{ => src}/Services/AuthProcServiceTest.php (61%) rename tests/{ => src}/Services/AuthenticationServiceTest.php (77%) rename tests/{ => src}/Services/IdTokenBuilderTest.php (81%) rename tests/{ => src}/Services/JsonWebKeySetServiceTest.php (85%) rename tests/{ => src}/Services/JsonWebTokenBuilderServiceTest.php (65%) rename tests/{ => src}/Services/LogoutTokenBuilderTest.php (69%) rename tests/{Services/OidcOpenIdProviderMetadataServiceTest.php => src/Services/OpMetadataServiceTest.php} (67%) rename tests/{ => src}/Services/SessionMessagesServiceTest.php (82%) rename tests/{ => src}/Services/SessionServiceTest.php (81%) create mode 100644 tests/src/Stores/Session/LogoutTicketStoreBuilderTest.php rename tests/{Store/SessionLogoutTicketStoreDbTest.php => src/Stores/Session/LogoutTicketStoreDbTest.php} (81%) rename tests/{ => src}/Utils/Checker/RequestRulesManagerTest.php (79%) rename tests/{ => src}/Utils/Checker/ResultBagTest.php (87%) rename tests/{ => src}/Utils/Checker/ResultTest.php (90%) rename tests/{ => src}/Utils/Checker/Rules/AcrValuesRuleTest.php (90%) rename tests/{ => src}/Utils/Checker/Rules/AddClaimsToIdTokenRuleTest.php (85%) rename tests/{ => src}/Utils/Checker/Rules/ClientIdRuleTest.php (71%) rename tests/{ => src}/Utils/Checker/Rules/CodeChallengeMethodRuleTest.php (90%) rename tests/{ => src}/Utils/Checker/Rules/CodeChallengeRuleTest.php (90%) rename tests/{ => src}/Utils/Checker/Rules/IdTokenHintRuleTest.php (71%) rename tests/{ => src}/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php (80%) rename tests/{ => src}/Utils/Checker/Rules/RedirectUriRuleTest.php (69%) rename tests/{ => src}/Utils/Checker/Rules/RequestedClaimsRuleTest.php (78%) rename tests/{ => src}/Utils/Checker/Rules/RequiredNonceRuleTest.php (89%) rename tests/{ => src}/Utils/Checker/Rules/RequiredOpenIdScopeRuleTest.php (89%) rename tests/{ => src}/Utils/Checker/Rules/ResponseTypeRuleTest.php (87%) rename tests/{ => src}/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php (72%) rename tests/{ => src}/Utils/Checker/Rules/ScopeRuleTest.php (91%) rename tests/{ => src}/Utils/Checker/Rules/StateRuleTest.php (92%) rename tests/{ => src}/Utils/Checker/Rules/UiLocalesRuleTest.php (83%) rename tests/{ => src/Utils}/ClaimTranslatorExtractorTest.php (93%) rename tests/{ => src}/Utils/ScopeHelperTest.php (60%) rename tests/{ => src}/Utils/UniqueIdentifierGeneratorTest.php (95%) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f5b6b2a4..c1c02764 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ["8.0", "8.1", "8.2"] + php-versions: ["8.1", "8.2"] 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.0' }} + if: ${{ matrix.php-versions != '8.1' }} 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.0' }} + if: ${{ matrix.php-versions == '8.1' }} uses: actions/upload-artifact@v1 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.0" + php-version: "8.1" 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.0" + php-version: "8.1" 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.0" + php-version: "8.1" tools: composer:v2 extensions: mbstring, xml diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 6c6e95ce..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,88 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## [2.0.4] -### Fixed -- Attempt fix for 'pull access denied for symfonycorp/cli' by @pradtke in #188 -- Add Access-Control-Allow-Origin header to responses, if not already present by @cicnavi in #190 - - -## [2.0.3] -### Fixed -- Use InMemory::empty by @pkoenig10 in #186 - -## [2.0.2] - 2022-07-22 -### Fixed -- Correct readme typo for module_oidc.php template path by @dgoosens in #168 -- Allow overriding cert+key name/location by @pradtke in #167 -- Fix access token timestamps, add issuer by @cicnavi in #174 -- Fix PK constraint name for allowed origin table - make it unique by @cicnavi in #173 -- Set restart url for authorize commands by @pradtke in #180 -- Fix admin-clients link by @Pyrex-FWI in #177 -- Logout tokens should have typ header with value 'logout+jwt' by @IlanaRadinsky in #185 -- Fail actions on code quality issues by @pradtke in #175 - -## [2.0.1] -### Fixed -- Make lib/Store/* available for Symfony DI. -- Fix ClientEntity postLogoutRedirectUri json_decode when 'post_logout_redirect_uri' is not defined - -## [2.0.0-rc.1] - 2021-10-08 -### Added -- Implicit flow support -- Back-channel logout -- RP initiated logout -- Support for 'sid' claim in ID and logout token -- Support for claim types -- Allow users with specific entitlements to add clients -- Support for ACR -- Support for requesting individual claims -- Support for allowed CORS origins for public clients -- Support for 'at_hash' claim in ID token -- Support for 'max_age' parameter -- List of supported grant types in OP configuration document -- List of supported auth methods for token endpoint in OP configuration document -- Support for 'prompt' parameter, for example using 'prompt=login' to require authentication -even if user has active SSO session -- Works with SSP new UI templating enabled -- Pagination for client list -- Support for basic authentication processing filters, for example for f-ticks logging, attribute -manipulation or similar, definable in oidc_config.php -- Support for 'nonce' claim in ID token -- Config options to add prefix to private scope claims and to enable multi-valued claims -### Changed -- Basic flow is now conformant -- Admin client configuration path has moved -- 'token_endpoint' renamed form '.../access_token.php' to '.../token.php' -- Requires php > 7.4 -- Auth. source is now optional when defining clients. If auth. source is not set for particular -client, a default one from the configuration will be used during authn. -### Fixed -- When authorization code is reused corresponding tokens are now immediately revoked -- Returning or displaying proper error messages is now more in line to specification -- Expired access tokens are now only deleted if corresponding refresh tokens are also expired -- JWT header parameter 'kid' is now generated dynamically based on public certificate fingerprint - -## [1.0.0-rc.2] - 2020-05-17 -### Added -- Second release candidate -- Updated league/oauth2-server to version 8.1 -### Changed -- Removed pkce config option -- New field _is_confidential_ in client (disabled for previous clients) -- Update database schema - -## [1.0.0-rc.1] - 2018-11-13 -### Added -- First release candidate -### Changed -- BC: Config file (`module_oidc.php`) has changed. Predefined scopes must be removed: openid, profile, mail, address, phone. - - -## [1.0.0-alpha.1] - 2018-04-11 -### Added -- First pre-release diff --git a/CONFORMANCE_TEST.md b/CONFORMANCE_TEST.md index 3e91c8dc..0f2fbfe5 100644 --- a/CONFORMANCE_TEST.md +++ b/CONFORMANCE_TEST.md @@ -20,7 +20,7 @@ MAVEN_CACHE=./m2 docker-compose -f builder-compose.yml run builder docker-compose up ``` -This will startup the Java conformance app and a MongoDB server. You'll need to configure a test. +This will start up the Java conformance app and a MongoDB server. You'll need to configure a test. Visit https://localhost:8443/ and "Create a new plan". The Test Plan should be "OpenID Connect Core: Basic Certification Profile Authorization server test" @@ -33,20 +33,21 @@ You'll need to get your OIDC SSP image running next ## Run SSP -You'll need to run SSP with OIDC on the same docker network as the compliance tests so they are able to communicate. +You'll need to run SSP with OIDC on the same docker network as the compliance tests, so they are able to communicate. See "Docker Compose" section of the main README. ## Run Conformance Tests -The conformance tests are interactive to make you authenticate. Some of the tests require you to clear cookies to confirm -certain test scenarios, while others require you to have session cookies to test the RP signaling to the OP that the user -should reauthenticate. The tests may also redirect you to https://localhost.emobix.co.uk:8443/ which will resolve to -the conformance Java container. You'll need to accept any SSL connection warnings. +The conformance tests are interactive to make you authenticate. Some of the tests require you to clear cookies to +confirm certain test scenarios, while others require you to have session cookies to test the RP signaling to the +OP that the user should reauthenticate. The tests may also redirect you to https://localhost.emobix.co.uk:8443/ +which will resolve to the conformance Java container. You'll need to accept any SSL connection warnings. ## Run automated tests -Eventually these test can have [the browser portion automated](https://gitlab.com/openid/conformance-suite/-/wikis/Design/BrowserControl) +Eventually these test can have +[the browser portion automated](https://gitlab.com/openid/conformance-suite/-/wikis/Design/BrowserControl) though the Conformance tests authors recommend getting them all to pass first. To run basic profile test, launch this command in console inside `simplesamlphp-module-oidc` directory: @@ -96,13 +97,13 @@ In this situation your OIDC OP must be accessible to the public internet. ## Deploy SSP OIDC Image The docker image created in the README.md is designed to be used for running the conformance tests. -It contains an sqlite database pre-populated with data that can be used for these tests. +It contains a sqlite database pre-populated with data that can be used for these tests. Build and run the image somewhere. ## Register and Create Conformance Tests Visit https://openid.net/certification/instructions/ -You can use the `json` deployment configurations under `conformance-tests` to configure your cloud instances. Update your -`discoveryUrl` to reflect the location you deployed SSP. You may also need to adjust `alias` since that is used in all -client redirect URIs and may conflict with existing test suites. +You can use the `json` deployment configurations under `conformance-tests` to configure your cloud instances. Update +your `discoveryUrl` to reflect the location you deployed SSP. You may also need to adjust `alias` since that is used +in all client redirect URIs and may conflict with existing test suites. diff --git a/FAQ.md b/FAQ.md index 02a955a7..fcf288c5 100644 --- a/FAQ.md +++ b/FAQ.md @@ -1,12 +1,12 @@ # Set JSON type for claims You can set the type of claim by prefixing the name with `int:`, `bool:` or `string:`. If no prefix is set then `string` -is assumed. In the rare event that your custom claim name starts with a prefix (example: `int:mycustomclaim`) you can add an one of -the type prefixes (example: `string:int:mycustomclaim`) to force the module to release a claim with the original prefix in it - (example: claim `int:mycustomclaim` of type `string`) +is assumed. In the rare event that your custom claim name starts with a prefix (example: `int:mycustomclaim`) you can +add one of the type prefixes (example: `string:int:mycustomclaim`) to force the module to release a claim with the +original prefix in it (example: claim `int:mycustomclaim` of type `string`) # Release photo -The OIDC `picture` claim is a url, while the `jpegPhoto` ldap attribute is often a b64 string. To use `jpegPhoto` you can -try using an authproc filter to turn it into a data url by adding `data:image/jpeg;base64,` prefix. The support for data urls -amongst OIDC client is unknown. \ No newline at end of file +The OIDC `picture` claim is an URL, while the `jpegPhoto` LDAP attribute is often a b64 string. To use `jpegPhoto` you +can try using an authproc filter to turn it into a data url by adding `data:image/jpeg;base64,` prefix. The support +for data URLs amongst OIDC client is unknown. \ No newline at end of file diff --git a/README.md b/README.md index 1692c5ec..b122b509 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,15 @@ Currently supported flows are: | OIDC module | SimpleSAMLphp | PHP | Note | |:------------|:--------------|:------:|-----------------------------| -| v4.\* | v2.0.\* | \>=8.0 | Recommended | -| v3.\* | v2.0.0 | \>=7.4 | Abandoned from August 2023. | +| v5.\* | v2.1.\* | \>=8.1 | Recommended | +| v4.\* | v2.0.\* | \>=8.0 | | +| v3.\* | v2.0.\* | \>=7.4 | Abandoned from August 2023. | | v2.\* | v1.19.\* | \>=7.4 | | +### Upgrading? + +If you are upgrading from a previous version, checkout the [upgrade guide](UPGRADE.md). + ## Installation Installation can be as easy as executing: @@ -95,10 +100,6 @@ If you use a passphrase, make sure to also configure it in the `module_oidc.php` In order to purge expired tokens, this module requires [cron module](https://simplesamlphp.org/docs/stable/cron:cron) to be enabled and configured. -## Upgrading? - -If you are upgrading from a previous version, checkout the [upgrade guide](UPGRADE.md). - ## Additional considerations ### Private scopes @@ -109,7 +110,7 @@ However, you can add your own private scopes in the `module_oidc.php` config fil [ + \SimpleSAML\Module\oidc\ModuleConfig::OPTION_AUTH_CUSTOM_SCOPES => [ 'private' => [ 'description' => 'private scope', 'claim_name_prefix' => '', // Optional prefix for claim names @@ -131,7 +132,7 @@ You can change or extend this table in the `module_oidc.php` config file, for ex [ + \SimpleSAML\Module\oidc\ModuleConfig::OPTION_AUTH_SAML_TO_OIDC_TRANSLATE_TABLE => [ // Overwrite default translation 'sub' => [ 'uid', // added @@ -185,7 +186,7 @@ documentation](https://simplesamlphp.org/docs/stable/simplesamlphp-authproc). [ + \SimpleSAML\Module\oidc\ModuleConfig::OPTION_AUTH_PROCESSING_FILTERS => [ 50 => [ 'class' => 'core:AttributeAdd', 'groups' => ['users', 'members'], @@ -206,8 +207,8 @@ eduPersonEntitlements from the `client` permission array. A permission can be disabled by commenting it out. -```bash - 'permissions' => [ +```php + \SimpleSAML\Module\oidc\ModuleConfig::OPTION_ADMIN_UI_PERMISSIONS => [ // Attribute to inspect to determine user's permissions 'attribute' => 'eduPersonEntitlement', // Which entitlements allow for registering, editing, delete a client. OIDC clients are owned by the creator @@ -242,9 +243,9 @@ form. Here are some sample configurations: ### With current git branch. -To explore the module using docker run the below command. This will run an SSP image, with the current oidc module mounted -in the container, along with some configuration files. Any code changes you make to your git checkout are "live" in -the container, allowing you to test and iterate different things. +To explore the module using docker run the below command. This will run an SSP image, with the current oidc module +mounted in the container, along with some configuration files. Any code changes you make to your git checkout are +"live" in the container, allowing you to test and iterate different things. ``` GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) diff --git a/UPGRADE.md b/UPGRADE.md index ebc7c234..13b10812 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,7 +1,31 @@ +# Version 4 to 5 + +## Major impact changes +- PHP version requirement was bumped to v8.1 + +## Medium impact changes +- Module config options in file 'module_oidc.php' are now using constants for config keys. The values for constants are +taken from the previous version of the module, so theoretically you don't have to rewrite your current config file, +although it is recommended to do so. + +## Low impact changes +- Removed the 'kid' config option which was not utilized in the codebase (from v2 of the module, the 'kid' value is the +fingerprint of the certificate). + +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: + +- psalm error level set to 1, which needed a fair amount of code adjustments +- refactored to strict typing whenever possible (psalm can now infer types for >99% of the codebase) +- refactored to PHP v8.* (up to PHP v8.1) code styling whenever possible, like using constructor property promotion, +match expressions... +- removed dependency on steverhoades/oauth2-openid-connect-server (low maintenance) + # Version 3 to 4 - PHP version requirement was bumped to v8.0 to enable updating important dependant packages like 'league/oauth2-server' which has already moved to PHPv8 between their minor releases. -- SimpleSAMLphp version fixed to v2.0.* +- SimpleSAMLphp version requirement fixed to v2.0.* # Version 2 to 3 - Module code was refactored to make it compatible with SimpleSAMLphp v2 diff --git a/composer.json b/composer.json index 000c2345..14530243 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ } ], "require": { - "php": "^8.0", + "php": "^8.1", "ext-curl": "*", "ext-json": "*", "ext-openssl": "*", @@ -28,22 +28,21 @@ "lcobucci/jwt": "^4.1", "league/oauth2-server": "^8.5.3", "nette/forms": "^3", - "psr/container": "^1.0", - "psr/log": "^1.1", - "simplesamlphp/composer-module-installer": "^1.2", + "psr/container": "^2.0", + "psr/log": "^3", + "simplesamlphp/composer-module-installer": "^1.3", "spomky-labs/base64url": "^2.0", - "steverhoades/oauth2-openid-connect-server": "^2.0", - "web-token/jwt-framework": "^2.1" + "symfony/expression-language": "^6.3", + "web-token/jwt-framework": "^3" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3", - "phpunit/php-code-coverage": "^9.0.0", - "phpunit/phpcov": "^8.2.0", - "phpunit/phpunit": "^9.0.0", - "simplesamlphp/simplesamlphp": "2.0.*", - "simplesamlphp/simplesamlphp-test-framework": "^1.2.1", - "squizlabs/php_codesniffer": "^3.7", - "vimeo/psalm": "^5.8" + "phpunit/phpunit": "^10", + "rector/rector": "^0.18.3", + "simplesamlphp/simplesamlphp": "2.1.*", + "simplesamlphp/simplesamlphp-test-framework": "^1.5", + "squizlabs/php_codesniffer": "^3", + "vimeo/psalm": "^5" }, "config": { "preferred-install": { @@ -52,7 +51,8 @@ "sort-packages": true, "allow-plugins": { "simplesamlphp/composer-module-installer": true - } + }, + "cache-dir": "build/composer" }, "autoload": { "psr-4": { @@ -61,12 +61,11 @@ }, "autoload-dev": { "psr-4": { - "SimpleSAML\\Test\\Module\\oidc\\": "tests/" + "SimpleSAML\\Test\\Module\\oidc\\": "tests/src/" } }, "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" } }, "scripts": { diff --git a/config-templates/module_oidc.php b/config-templates/module_oidc.php index 2452edbe..5b052025 100644 --- a/config-templates/module_oidc.php +++ b/config-templates/module_oidc.php @@ -1,5 +1,7 @@ 20, - - // The private key passphrase (optional) - // 'pass_phrase' => 'secret', - // The cert and key for signing the ID token. Default names are oidc_module.key and oidc_module.crt - // 'privatekey' => 'oidc_module.key', - // 'certificate' => 'oidc_module.crt', - - // Tokens TTL - 'authCodeDuration' => 'PT10M', // 10 minutes - 'refreshTokenDuration' => 'P1M', // 1 month - 'accessTokenDuration' => 'PT1H', // 1 hour, + /** + * PKI (public / private key) related options. + */ + // The private key passphrase (optional). + //ModuleConfig::OPTION_PKI_PRIVATE_KEY_PASSPHRASE => 'secret', + // The certificate and private key filenames for ID token signature handling, 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, - // Tag to run storage cleanup script using the cron module... - 'cron_tag' => 'hourly', + /** + * Token related options. + */ + // Authorization code and tokens TTL (validity duration), with given examples. For duration format info, check + // https://www.php.net/manual/en/dateinterval.construct.php + ModuleConfig::OPTION_TOKEN_AUTHORIZATION_CODE_TTL => 'PT10M', // 10 minutes + ModuleConfig::OPTION_TOKEN_REFRESH_TOKEN_TTL => 'P1M', // 1 month + ModuleConfig::OPTION_TOKEN_ACCESS_TOKEN_TTL => 'PT1H', // 1 hour, - // Set token signer + // Token signer, with given default. // See Lcobucci\JWT\Signer algorithms in https://github.com/lcobucci/jwt/tree/master/src/Signer - 'signer' => \Lcobucci\JWT\Signer\Rsa\Sha256::class, - // 'signer' => \Lcobucci\JWT\Signer\Hmac\Sha256::class, - // 'signer' => \Lcobucci\JWT\Signer\Ecdsa\Sha256::class, - - // The key id to use in the header. Default is a finger print of the public key - // 'kid' => 'abcd', - - - // this is the default auth source used for authentication if the auth source - // is not specified on particular client - 'auth' => 'default-sp', - - // useridattr is the attribute-name that contains the userid as returned from idp. By default, this attribute - // will be dynamically added to the 'sub' claim in the attribute-to-claim translation table (you will probably - // want to use this attribute as the 'sub' claim since it designates unique identifier for the user). - 'useridattr' => 'uid', + 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, /** - * Permissions let the module expose functionality to specific users. - * In the below configuration, a user's eduPersonEntitlement attribute is examined. If the user - * tries to do something that requires the 'client' permission (such as registering their own client) - * then they will need one of the eduPersonEntitlements from the `client` permission array. - * - * A permission can be disable by commenting it out. + * Authentication related options. */ - 'permissions' => [ - // Attribute to inspect to determine user's permissions - 'attribute' => 'eduPersonEntitlement', - // Which entitlements allow for registering, editing, delete a client. OIDC clients are owned by the creator - 'client' => ['urn:example:oidc:manage:client'], - ], + // The default authentication source to be used for authentication if the auth source is not specified on + // particular client. + ModuleConfig::OPTION_AUTH_SOURCE => 'default-sp', - // Settings regarding Authentication Processing Filters. - // Note: OIDC authN state array will not contain all of the keys which are available during SAML authN, - // like Service Provider metadata, etc. - // - // At the moment, the following SAML authN data will be available during OIDC authN in the sate array: - // - ['Attributes'], ['Authority'], ['AuthnInstant'], ['Expire'] - // Source and destination will have entity IDs corresponding to the OP issuer ID and Client ID respectively. - // - ['Source']['entityid'] - contains OpenId Provider issuer ID - // - ['Destination']['entityid'] - contains Relying Party (OIDC Client) ID - // In addition to that, the following OIDC related data will be available in the state array: - // - ['Oidc']['OpenIdProviderMetadata'] - contains information otherwise available from the OIDC configuration URL. - // - ['Oidc']['RelyingPartyMetadata'] - contains information about the OIDC client making the authN request. - // - ['Oidc']['AuthorizationRequestParameters'] - contains relevant authorization request query parameters. - // - // List of authproc filters which will run for every OIDC authN. Add filters as described in docs for SAML authproc - // @see https://simplesamlphp.org/docs/stable/simplesamlphp-authproc - 'authproc.oidc' => [ - // Add authproc filters here - ], + // The attribute name that contains the user identifier returned from IdP. By default, this attribute will be + // dynamically added to the 'sub' claim in the attribute-to-claim translation table (you will probably want + // to use this attribute as the 'sub' claim since it designates unique identifier for the user). + ModuleConfig::OPTION_AUTH_USER_IDENTIFIER_ATTRIBUTE => 'uid', - // Optional custom scopes. You can create as many scopes as you want and assign claims to them. - 'scopes' => [ -// 'private' => [ // The key represents the scope name. -// 'description' => 'private scope', -// 'claim_name_prefix' => '', // Prefix to apply for all claim names from this scope -// 'are_multiple_claim_values_allowed' => false, // Are claims for this scope allowed to have multiple values -// 'claims' => ['national_document_id'] // Claims from the translation table which this scope will contain -// ], - ], - 'translate' => [ + // The default translate table from SAML attributes to OIDC claims. + ModuleConfig::OPTION_AUTH_SAML_TO_OIDC_TRANSLATE_TABLE => [ /* - * This is the default translate table from SAML to OIDC. - * You can change here the behaviour or add more translation to your - * private attributes scopes - * * The basic format is * * 'claimName' => [ @@ -149,7 +109,7 @@ // 'description', // ], // 'picture' => [ -// // Empty. Previously 'jpegPhoto' however spec calls for a url to photo, not an actual photo. +// // Empty. Previously 'jpegPhoto' however spec calls for a URL to photo, not an actual photo. // ], // 'website' => [ // // Empty @@ -177,7 +137,7 @@ // 'type' => 'bool', // 'attributes' => [], // ], -// // address is a json object. Set the 'formatted' sub claim to postalAddress +// // address is a json object. Set the 'formatted' sub-claim to postalAddress // 'address' => [ // 'type' => 'json', // 'claims' => [ @@ -201,6 +161,16 @@ // ], ], + // Optional custom scopes. You can create as many scopes as you want and assign claims to them. + ModuleConfig::OPTION_AUTH_CUSTOM_SCOPES => [ +// 'private' => [ // The key represents the scope name. +// 'description' => 'private scope', +// 'claim_name_prefix' => '', // Prefix to apply for all claim names from this scope +// 'are_multiple_claim_values_allowed' => false, // Are claims for this scope allowed to have multiple values +// 'claims' => ['national_document_id'] // Claims from the translation table which this scope will contain +// ], + ], + // Optional list of the Authentication Context Class References that this OP supports. // If populated, this list will be available in OP discovery document (OP Metadata) as 'acr_values_supported'. // @see https://datatracker.ietf.org/doc/html/rfc6711 @@ -208,7 +178,7 @@ // @see https://openid.net/specs/openid-connect-core-1_0.html#IDToken (acr claim) // @see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest (acr_values parameter) // Syntax: string[] (array of strings) - 'acrValuesSupported' => [ + ModuleConfig::OPTION_AUTH_ACR_VALUES_SUPPORTED => [ // 'https://refeds.org/assurance/profile/espresso', // 'https://refeds.org/assurance/profile/cappuccino', // 'https://refeds.org/profile/mfa', @@ -226,7 +196,7 @@ // If this OP supports ACRs, indicate which usable auth source supports which ACRs. // Order of ACRs is important, more important ones being first. // Syntax: array (array with auth source as key and value being array of ACR values as strings) - 'authSourcesToAcrValuesMap' => [ + ModuleConfig::OPTION_AUTH_SOURCES_TO_ACR_VALUES_MAP => [ // 'example-userpass' => ['1', '0'], // 'default-sp' => ['http://id.incommon.org/assurance/bronze', '2', '1', '0'], // 'strongly-assured-authsource' => [ @@ -248,6 +218,49 @@ // set to that ACR for cookie authentication. // For example, OIDC Core Spec notes that authentication using a long-lived browser cookie is one example where // the use of "level 0" is appropriate: -// 'forcedAcrValueForCookieAuthentication' => '0', - 'forcedAcrValueForCookieAuthentication' => null, +// ModuleConfig::OPTION_AUTH_FORCED_ACR_VALUE_FOR_COOKIE_AUTHENTICATION => '0', + ModuleConfig::OPTION_AUTH_FORCED_ACR_VALUE_FOR_COOKIE_AUTHENTICATION => null, + + // Settings regarding Authentication Processing Filters. + // Note: OIDC authN state array will not contain all the keys which are available during SAML authN, + // like Service Provider metadata, etc. + // + // At the moment, the following SAML authN data will be available during OIDC authN in the sate array: + // - ['Attributes'], ['Authority'], ['AuthnInstant'], ['Expire'] + // Source and destination will have entity IDs corresponding to the OP issuer ID and Client ID respectively. + // - ['Source']['entityid'] - contains OpenId Provider issuer ID + // - ['Destination']['entityid'] - contains Relying Party (OIDC Client) ID + // In addition to that, the following OIDC related data will be available in the state array: + // - ['Oidc']['OpenIdProviderMetadata'] - contains information otherwise available from the OIDC configuration URL. + // - ['Oidc']['RelyingPartyMetadata'] - contains information about the OIDC client making the authN request. + // - ['Oidc']['AuthorizationRequestParameters'] - contains relevant authorization request query parameters. + // + // List of authproc filters which will run for every OIDC authN. Add filters as described in docs for SAML authproc + // @see https://simplesamlphp.org/docs/stable/simplesamlphp-authproc + ModuleConfig::OPTION_AUTH_PROCESSING_FILTERS => [ + // Add authproc filters here + ], + + /** + * Cron related options. + */ + // Cron tag used to run storage cleanup script using the cron module. + ModuleConfig::OPTION_CRON_TAG => 'hourly', + + /** + * Admin backend UI related options. + */ + // Permissions which let the module expose functionality to specific users. In the below configuration, a user's + // eduPersonEntitlement attribute is examined. If the user tries to do something that requires the 'client' + // permission (such as registering their own client), then they will need one of the eduPersonEntitlements + // from the `client` permission array. A permission can be disabled by commenting it out. + ModuleConfig::OPTION_ADMIN_UI_PERMISSIONS => [ + // Attribute to inspect to determine user's permissions + 'attribute' => 'eduPersonEntitlement', + // Which entitlements allow for registering, editing, delete a client. OIDC clients are owned by the creator + 'client' => ['urn:example:oidc:manage:client'], + ], + + // Pagination options. + ModuleConfig::OPTION_ADMIN_UI_PAGINATION_ITEMS_PER_PAGE => 20, ]; diff --git a/docker/Dockerfile b/docker/Dockerfile index d33aa180..18946657 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,5 @@ -FROM cirrusid/simplesamlphp:v2.0.0 +#FROM cirrusid/simplesamlphp:v2.0.0 +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/docker/nginx-certs/README.md b/docker/nginx-certs/README.md index 17e67af3..31efd918 100644 --- a/docker/nginx-certs/README.md +++ b/docker/nginx-certs/README.md @@ -1,5 +1,4 @@ -Every 90 days these certificates expire. -The upstream project/container will refresh its certs occassionaly and we +Every 90 days these certificates expire. The upstream project/container will refresh its certs occasionally, and we can sync them here. ```bash diff --git a/docker/ssp/authsources.php b/docker/ssp/authsources.php index 68e29348..7e84dc87 100644 --- a/docker/ssp/authsources.php +++ b/docker/ssp/authsources.php @@ -1,8 +1,10 @@ array( 'core:AdminPassword', ), diff --git a/docker/ssp/config-override.php b/docker/ssp/config-override.php index d7dae4ea..c0e92ac2 100644 --- a/docker/ssp/config-override.php +++ b/docker/ssp/config-override.php @@ -1,4 +1,7 @@ 'secret', - - // Tokens TTL - 'authCodeDuration' => 'PT10M', // 10 minutes - 'refreshTokenDuration' => 'P1M', // 1 month - 'accessTokenDuration' => 'PT1H', // 1 hour, +use SimpleSAML\Module\oidc\ModuleConfig; - // Tag to run storage cleanup script using the cron module... - 'cron_tag' => 'hourly', +$config = [ + ModuleConfig::OPTION_TOKEN_AUTHORIZATION_CODE_TTL => 'PT10M', + ModuleConfig::OPTION_TOKEN_REFRESH_TOKEN_TTL => 'P1M', + ModuleConfig::OPTION_TOKEN_ACCESS_TOKEN_TTL => 'PT1H', - // Set token signer - // See Lcobucci\JWT\Signer algorithms in https://github.com/lcobucci/jwt/tree/master/src/Signer - 'signer' => \Lcobucci\JWT\Signer\Rsa\Sha256::class, - // 'signer' => \Lcobucci\JWT\Signer\Hmac\Sha256::class, - // 'signer' => \Lcobucci\JWT\Signer\Ecdsa\Sha256::class, + ModuleConfig::OPTION_TOKEN_SIGNER => \Lcobucci\JWT\Signer\Rsa\Sha256::class, - // this is the default auth source used for authentication if the auth source - // is not specified on particular client - 'auth' => 'example-userpass', + ModuleConfig::OPTION_AUTH_SOURCE => 'example-userpass', - // useridattr is the attribute-name that contains the userid as returned from idp. By default, this attribute - // will be dynamically added to the 'sub' claim in the attribute-to-claim translation table (you will probably - // want to use this attribute as the 'sub' claim since it designates unique identifier for the user). - 'useridattr' => 'uid', - 'permissions' => [ - // Attribute to inspect to determine user's permissions + ModuleConfig::OPTION_AUTH_USER_IDENTIFIER_ATTRIBUTE => 'uid', + ModuleConfig::OPTION_ADMIN_UI_PERMISSIONS => [ 'attribute' => 'eduPersonEntitlement', - // Which entitlements allow for registering, editing, delete a client. OIDC clients are owned by the creator 'client' => ['urn:example:oidc:manage:client'], ], - // Settings regarding Authentication Processing Filters. - // Note: OIDC authN state array will not contain all of the keys which are available during SAML authN, - // like Service Provider metadata, etc. - // - // At the moment, the following SAML authN data will be available during OIDC authN in the sate array: - // - 'Attributes', 'Authority', 'AuthnInstant', 'Expire', 'IdPMetadata', 'Source' - // In addition to that, the following OIDC related data will be available in the state array: - // - 'OidcProviderMetadata' - contains information otherwise available from the OIDC configuration URL. - // - 'OidcRelyingPartyMetadata' - contains information about the OIDC client making the authN request. - // - 'OidcAuthorizationRequestParameters' - contains relevant authorization request query parameters. - // - // List of authproc filters which will run for every OIDC authN. Add filters as described in docs for SAML authproc - // @see https://simplesamlphp.org/docs/stable/simplesamlphp-authproc - 'authproc.oidc' => [ - // Add authproc filters here + ModuleConfig::OPTION_AUTH_PROCESSING_FILTERS => [ ], - // Optional custom scopes. You can create as many scopes as you want and assign claims to them. - 'scopes' => [ -// 'private' => [ // The key represents the scope name. -// 'description' => 'private scope', -// 'claim_name_prefix' => '', // Prefix to apply for all claim names from this scope -// 'are_multiple_claim_values_allowed' => false, // Are claims for this scope allowed to have multiple values -// 'claims' => ['national_document_id'] // Claims from the translation table which this scope will contain -// ], + ModuleConfig::OPTION_AUTH_CUSTOM_SCOPES => [ ], - 'translate' => [ - /* - * This is the default translate table from SAML to OIDC. - * You can change here the behaviour or add more translation to your - * private attributes scopes - * - * Note on 'sub' claim: by default, the list of attributes for 'sub' claim will also contain attribute defined - * in 'useridattr' setting. You will probably want to use this attribute as the 'sub' claim since it - * designates unique identifier for the user, However, override as necessary. - */ -// 'sub' => [ -// 'attribute-defined-in-useridattr', // will be dynamically added if the list for 'sub' claim is not set. -// 'eduPersonPrincipalName', -// 'eduPersonTargetedID', -// 'eduPersonUniqueId', -// ], -// 'name' => [ -// 'cn', -// 'displayName', -// ], -// 'family_name' => [ -// 'sn', -// ], -// 'given_name' => [ -// 'givenName', -// ], + ModuleConfig::OPTION_AUTH_SAML_TO_OIDC_TRANSLATE_TABLE => [ 'middle_name' => [ 'middle_name' ], -// 'nickname' => [ -// 'eduPersonNickname', -// ], -// 'preferred_username' => [ -// 'uid', -// ], -// 'profile' => [ -// 'labeledURI', -// 'description', -// ], 'picture' => [ 'jpegURL', ], @@ -125,16 +55,10 @@ 'zoneinfo' => [ 'zoneinfo' ], -// 'locale' => [ -// 'preferredLanguage', -// ], 'updated_at' => [ 'type' => 'int', 'updated_at' ], -// 'email' => [ -// 'mail', -// ], 'email_verified' => [ 'type' => 'bool', 'attributes' => ['email_verified'] @@ -150,50 +74,22 @@ 'country' => ['country'], ] ], - -// 'phone_number' => [ -// 'mobile', -// 'telephoneNumber', -// 'homePhone', -// ], 'phone_number_verified' => [ 'type' => 'bool', 'phone_number_verified' ], - /* - * Optional scopes attributes - */ -// 'national_document_id' => [ -// 'schacPersonalUniqueId', -// ], ], - // Optional list of the Authentication Context Class References that this OP supports. - // If populated, this list will be available in OP discovery document (OP Metadata) as 'acr_values_supported'. - // @see https://datatracker.ietf.org/doc/html/rfc6711 - // @see https://www.iana.org/assignments/loa-profiles/loa-profiles.xhtml - // @see https://openid.net/specs/openid-connect-core-1_0.html#IDToken (acr claim) - // @see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest (acr_values parameter) - // Syntax: string[] (array of strings) - 'acrValuesSupported' => [ + ModuleConfig::OPTION_AUTH_ACR_VALUES_SUPPORTED => [ '1', '0', ], - // If this OP supports ACRs, indicate which usable auth source supports which ACRs. - // Order of ACRs is important, more important ones being first. - // Syntax: array (array with auth source as key and value being array of ACR values as strings) - 'authSourcesToAcrValuesMap' => [ + ModuleConfig::OPTION_AUTH_SOURCES_TO_ACR_VALUES_MAP => [ 'example-userpass' => ['1', '0'], ], - // If this OP supports ACRs, indicate if authentication using cookie should be forced to specific ACR value. - // If this option is set to null, no specific ACR will be forced for cookie authentication and the resulting ACR - // will be one of the ACRs supported on used auth source during authentication, that is, session creation. - // If this option is set to specific ACR, with ACR value being one of the ACR value this OP supports, it will be - // set to that ACR for cookie authentication. - // For example, OIDC Core Spec notes that authentication using a long-lived browser cookie is one example where - // the use of "level 0" is appropriate: -// 'forcedAcrValueForCookieAuthentication' => '0', - 'forcedAcrValueForCookieAuthentication' => null, + ModuleConfig::OPTION_AUTH_FORCED_ACR_VALUE_FOR_COOKIE_AUTHENTICATION => null, + + ModuleConfig::OPTION_CRON_TAG => 'hourly', ]; diff --git a/hooks/hook_cron.php b/hooks/hook_cron.php index 35690d54..1ea969ae 100644 --- a/hooks/hook_cron.php +++ b/hooks/hook_cron.php @@ -1,5 +1,7 @@ config(); - if (null === $oidcConfig->getOptionalValue('cron_tag', null)) { + if (null === $oidcConfig->getOptionalValue(ModuleConfig::OPTION_CRON_TAG, null)) { return; } - if ($oidcConfig->getOptionalValue('cron_tag', null) !== $croninfo['tag']) { + if ($oidcConfig->getOptionalValue(ModuleConfig::OPTION_CRON_TAG, null) !== $croninfo['tag']) { return; } - $container = new \SimpleSAML\Module\oidc\Services\Container(); + $container = new Container(); try { + /** @var AccessTokenRepository $accessTokenRepository */ $accessTokenRepository = $container->get(AccessTokenRepository::class); $accessTokenRepository->removeExpired(); + /** @var AuthCodeRepository $authTokenRepository */ $authTokenRepository = $container->get(AuthCodeRepository::class); $authTokenRepository->removeExpired(); + /** @var RefreshTokenRepository $refreshTokenRepository */ $refreshTokenRepository = $container->get(RefreshTokenRepository::class); $refreshTokenRepository->removeExpired(); $croninfo['summary'][] = 'Module `oidc` clean up. Removed expired entries from storage.'; } catch (Exception $e) { $message = 'Module `oidc` clean up cron script failed: ' . $e->getMessage(); - \SimpleSAML\Logger::warning($message); + Logger::warning($message); $croninfo['summary'][] = $message; } } diff --git a/hooks/hook_federationpage.php b/hooks/hook_federationpage.php index b5abda50..dca1cca7 100644 --- a/hooks/hook_federationpage.php +++ b/hooks/hook_federationpage.php @@ -1,5 +1,7 @@ isUpdated()) { - $href = \SimpleSAML\Module::getModuleURL('oidc/install.php'); + if (! (new DatabaseMigration())->isUpdated()) { + $href = Module::getModuleURL('oidc/install.php'); $text = Translate::noop('OpenID Connect Installation'); } + if (!is_array($template->data['links'])) { + $template->data['links'] = []; + } + $template->data['links'][] = [ 'href' => $href, 'text' => $text, diff --git a/hooks/hook_frontpage.php b/hooks/hook_frontpage.php index 5a7f708e..7b642389 100644 --- a/hooks/hook_frontpage.php +++ b/hooks/hook_frontpage.php @@ -1,5 +1,7 @@ isUpdated(); + $isUpdated = (new DatabaseMigration())->isUpdated(); if (!$isUpdated) { $links['federation']['oidcregistry'] = [ - 'href' => \SimpleSAML\Module::getModuleURL('oidc/install.php'), + 'href' => Module::getModuleURL('oidc/install.php'), 'text' => [ 'en' => 'OpenID Connect Installation', 'es' => 'Instalación de OpenID Connect', @@ -43,7 +44,7 @@ function oidc_hook_frontpage(&$links) } $links['federation']['oidcregistry'] = [ - 'href' => \SimpleSAML\Module::getModuleURL('oidc/admin-clients/index.php'), + 'href' => Module::getModuleURL('oidc/admin-clients/index.php'), 'text' => [ 'en' => 'OpenID Connect Client Registry', 'es' => 'Registro de clientes OpenID Connect', diff --git a/phpcs.xml b/phpcs.xml index bbf97ec2..edcdc292 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -6,6 +6,9 @@ By default it is less stringent about long lines than other coding standards + + + config-templates hooks src @@ -17,12 +20,14 @@ public/assets/* - - - spec/* - - + + + + + + + diff --git a/phpunit.xml b/phpunit.xml index 5390b395..ac79ebb1 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,19 +1,11 @@ - - - ./src - + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd" + cacheDirectory="./build/phpunit-cache" +> + @@ -30,4 +22,9 @@ + + + ./src + + diff --git a/psalm.xml b/psalm.xml index d1867929..ddac7608 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,61 +1,49 @@ - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/public/admin-clients/delete.php b/public/admin-clients/delete.php index 9ef89c03..a1920f98 100644 --- a/public/admin-clients/delete.php +++ b/public/admin-clients/delete.php @@ -1,5 +1,7 @@ importNames(); + $rectorConfig->disableParallel(); + + $rectorConfig->bootstrapFiles([ + //__DIR__ . '/vendor/autoload.php', + ]); + + $rectorConfig->paths([ + // TODO mivanci also go trough commented out paths... + //__DIR__ . '/docker', + //__DIR__ . '/hooks', + //__DIR__ . '/public', + __DIR__ . '/src', + __DIR__ . '/tests', + ]); + + // register a single rule + $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); + $rectorConfig->rule(DeclareStrictTypesRector::class); + + // define sets of rules + $rectorConfig->sets([ + LevelSetList::UP_TO_PHP_81, + ]); +}; diff --git a/routing/routes/routes.yml b/routing/routes/routes.yml index 1b6661af..0abd5cb5 100644 --- a/routing/routes/routes.yml +++ b/routing/routes/routes.yml @@ -1,6 +1,9 @@ -# TODO sspv2 move to routes in SSP v2 #oidc-clients: # path: /clients # controller: SimpleSAML\Module\oidc\Services\RoutingService::call # defaults: -# controllerClassname: SimpleSAML\Module\oidc\Controller\ClientIndexController +# controllerClassname: SimpleSAML\Module\oidc\Controller\Client\IndexController + +openid-configuration: + path: openid-configuration + controller: SimpleSAML\Module\oidc\Controller\ConfigurationDiscoveryController \ No newline at end of file diff --git a/routing/services/services.yml b/routing/services/services.yml index ee3f0201..c9daf55d 100644 --- a/routing/services/services.yml +++ b/routing/services/services.yml @@ -1,27 +1,54 @@ services: - # default configuration for services in *this* file - _defaults: - public: false + # default configuration for services in *this* file + _defaults: + autowire: true + autoconfigure: true + public: false + bind: + #Psr\Log\LoggerInterface: '@SimpleSAML\Module\oidc\Services\LoggerService' + League\OAuth2\Server\Repositories\ClientRepositoryInterface: '@SimpleSAML\Module\oidc\Repositories\ClientRepository' + League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface: '@SimpleSAML\Module\oidc\Repositories\AccessTokenRepository' + League\OAuth2\Server\Repositories\ScopeRepositoryInterface: '@SimpleSAML\Module\oidc\Repositories\ScopeRepository' + League\OAuth2\Server\CryptKey|string $privateKey: '@oidc.key.private' + League\OAuth2\Server\CryptKey|string $publicKey: '@oidc.key.public' - SimpleSAML\Module\oidc\Services\: - resource: '../../src/Services/*' - exclude: '../../src/Services/{Container.php}' + SimpleSAML\Module\oidc\Services\: + resource: '../../src/Services/*' + exclude: '../../src/Services/{Container.php}' - SimpleSAML\Module\oidc\Repositories\: - resource: '../../src/Repositories/*' - exclude: '../../src/Repositories/{Interfaces}' + SimpleSAML\Module\oidc\Repositories\: + resource: '../../src/Repositories/*' + exclude: '../../src/Repositories/{Interfaces}' - SimpleSAML\Module\oidc\Factories\: - resource: '../../src/Factories/*' + SimpleSAML\Module\oidc\Factories\: + resource: '../../src/Factories/*' - SimpleSAML\Module\oidc\Store\: - resource: '../../src/Store/*' + SimpleSAML\Module\oidc\Stores\: + resource: '../../src/Stores/*' - SimpleSAML\Module\oidc\Server\AuthorizationServer: - class: SimpleSAML\Module\oidc\Server\AuthorizationServer + SimpleSAML\Module\oidc\ModuleConfig: ~ - League\OAuth2\Server\ResourceServer: - class: League\OAuth2\Server\ResourceServer + oidc.key.private: + class: League\OAuth2\Server\CryptKey + factory: ['@SimpleSAML\Module\oidc\Factories\CryptKeyFactory', 'buildPrivateKey'] - SimpleSAML\Module\oidc\ClaimTranslatorExtractor: - class: SimpleSAML\Module\oidc\ClaimTranslatorExtractor + oidc.key.public: + class: League\OAuth2\Server\CryptKey + factory: ['@SimpleSAML\Module\oidc\Factories\CryptKeyFactory', 'buildPublicKey'] + + SimpleSAML\Module\oidc\Factories\ResourceServerFactory: + arguments: + $publicKey: '@oidc.key.public' + + SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor: + arguments: + $userIdAttr: '@=service("SimpleSAML\\Module\\oidc\\ModuleConfig").getUserIdentifierAttribute()' + + SimpleSAML\Module\oidc\Server\AuthorizationServer: + arguments: + $encryptionKey: '@=service("SimpleSAML\\Module\\oidc\\ModuleConfig").getEncryptionKey()' + + # OAuth2 Server + League\OAuth2\Server\ResourceServer: + arguments: + $publicKey: '@oidc.key.public' diff --git a/src/Controller/OAuth2AccessTokenController.php b/src/Controller/AccessTokenController.php similarity index 67% rename from src/Controller/OAuth2AccessTokenController.php rename to src/Controller/AccessTokenController.php index 89432558..3988e80b 100644 --- a/src/Controller/OAuth2AccessTokenController.php +++ b/src/Controller/AccessTokenController.php @@ -1,5 +1,7 @@ authorizationServer = $authorizationServer; } - public function __invoke(ServerRequest $request): \Psr\Http\Message\ResponseInterface + /** + * @throws OAuthServerException + */ + public function __invoke(ServerRequest $request): ResponseInterface { return $this->authorizationServer->respondToAccessTokenRequest($request, new Response()); } diff --git a/src/Controller/OAuth2AuthorizationController.php b/src/Controller/AuthorizationController.php similarity index 72% rename from src/Controller/OAuth2AuthorizationController.php rename to src/Controller/AuthorizationController.php index beb69424..33799b56 100644 --- a/src/Controller/OAuth2AuthorizationController.php +++ b/src/Controller/AuthorizationController.php @@ -1,5 +1,7 @@ authenticationService = $authenticationService; - $this->authorizationServer = $authorizationServer; - $this->configurationService = $configurationService; - $this->loggerService = $loggerService; } /** @@ -61,7 +46,7 @@ public function __construct( * @throws Error\NotFound * @throws Error\Exception * @throws OAuthServerException - * @throws Exception + * @throws Exception|Throwable */ public function __invoke(ServerRequest $request): ResponseInterface { @@ -86,19 +71,17 @@ 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. - * @param AuthorizationRequest $authorizationRequest * @throws Exception */ - protected function validatePostAuthnAuthorizationRequest(AuthorizationRequest &$authorizationRequest) + protected function validatePostAuthnAuthorizationRequest(AuthorizationRequest $authorizationRequest): void { $this->validateAcr($authorizationRequest); } /** - * @param AuthorizationRequest $authorizationRequest * @throws Exception */ - protected function validateAcr(AuthorizationRequest &$authorizationRequest): void + protected function validateAcr(AuthorizationRequest $authorizationRequest): void { // If no ACRs requested, don't set ACR claim. if (($requestedAcrValues = $authorizationRequest->getRequestedAcrValues()) === null) { @@ -113,20 +96,30 @@ protected function validateAcr(AuthorizationRequest &$authorizationRequest): voi throw OidcServerException::serverError('isCookieBasedAuthn not set on authz request'); } - $availableAuthSourceAcrs = $this->configurationService->getAuthSourcesToAcrValuesMap()[$authSourceId] ?? []; - $forcedAcrForCookieAuthentication = $this->configurationService->getForcedAcrValueForCookieAuthentication(); + $authSourceToAcrValuesMap = $this->moduleConfig->getAuthSourcesToAcrValuesMap(); + + $availableAuthSourceAcrs = is_array($authSourceToAcrValuesMap[$authSourceId]) ? + $authSourceToAcrValuesMap[$authSourceId] : + []; + $forcedAcrForCookieAuthentication = $this->moduleConfig->getForcedAcrValueForCookieAuthentication(); if ($forcedAcrForCookieAuthentication !== null && $isCookieBasedAuthn) { $availableAuthSourceAcrs = [$forcedAcrForCookieAuthentication]; } - $isRequestedAcrEssential = $requestedAcrValues['essential'] ?? false; + $isRequestedAcrEssential = empty($requestedAcrValues['essential']) ? + false : + boolval($requestedAcrValues['essential']); + + $acrs = !empty($requestedAcrValues['values']) && is_array($requestedAcrValues['values']) ? + $requestedAcrValues['values'] : + []; - $matchedAcrs = array_intersect($availableAuthSourceAcrs, $requestedAcrValues['values']); + $matchedAcrs = array_intersect($availableAuthSourceAcrs, $acrs); // If we have matched ACRs, use the best (first) one (order is important). if (!empty($matchedAcrs)) { - $authorizationRequest->setAcr(current($matchedAcrs)); + $authorizationRequest->setAcr((string)current($matchedAcrs)); return; } @@ -137,7 +130,7 @@ protected function validateAcr(AuthorizationRequest &$authorizationRequest): voi // If the ACR is not essential, we should return current session ACR (if we have one available)... if (! empty($availableAuthSourceAcrs)) { - $authorizationRequest->setAcr(current($availableAuthSourceAcrs)); + $authorizationRequest->setAcr((string)current($availableAuthSourceAcrs)); return; } diff --git a/src/Controller/ClientCreateController.php b/src/Controller/Client/CreateController.php similarity index 53% rename from src/Controller/ClientCreateController.php rename to src/Controller/Client/CreateController.php index 41119051..126ada66 100644 --- a/src/Controller/ClientCreateController.php +++ b/src/Controller/Client/CreateController.php @@ -1,5 +1,7 @@ clientRepository = $clientRepository; - $this->allowedOriginRepository = $allowedOriginRepository; - $this->templateFactory = $templateFactory; - $this->formFactory = $formFactory; - $this->messages = $messages; - $this->authContextService = $authContextService; } /** * @return RedirectResponse|Template * @throws Exception + * @throws \Exception */ - public function __invoke(ServerRequest $request) + public function __invoke(): Template|RedirectResponse { /** @var ClientForm $form */ $form = $this->formFactory->build(ClientForm::class); @@ -78,23 +63,43 @@ public function __invoke(ServerRequest $request) $client['owner'] = $this->authContextService->getAuthUserId(); } + if ( + !is_string($client['name']) || + !is_string($client['description']) || + !is_array($client['redirect_uri']) || + !is_array($client['scopes']) || + !is_array($client['post_logout_redirect_uri']) || + !is_array($client['allowed_origin']) + ) { + throw OidcServerException::serverError('Invalid Client Entity data'); + } + + /** @var string[] $redirectUris */ + $redirectUris = $client['redirect_uri']; + /** @var string[] $scopes */ + $scopes = $client['scopes']; + /** @var string[] $postLogoutRedirectUris */ + $postLogoutRedirectUris = $client['post_logout_redirect_uri']; + /** @var string[] $allowedOrigins */ + $allowedOrigins = $client['allowed_origin']; + $this->clientRepository->add(ClientEntity::fromData( $client['id'], $client['secret'], $client['name'], $client['description'], - $client['redirect_uri'], - $client['scopes'], - $client['is_enabled'], - $client['is_confidential'], - $client['auth_source'], - $client['owner'] ?? null, - $client['post_logout_redirect_uri'], - $client['backchannel_logout_uri'] + $redirectUris, + $scopes, + (bool)$client['is_enabled'], + (bool)$client['is_confidential'], + empty($client['auth_source']) ? null : (string)$client['auth_source'], + empty($client['owner']) ? null : (string)$client['owner'], + $postLogoutRedirectUris, + empty($client['backchannel_logout_uri']) ? null : (string)$client['backchannel_logout_uri'] )); // Also persist allowed origins for this client. - $this->allowedOriginRepository->set($client['id'], $client['allowed_origin']); + $this->allowedOriginRepository->set($client['id'], $allowedOrigins); $this->messages->addMessage('{oidc:client:added}'); diff --git a/src/Controller/ClientDeleteController.php b/src/Controller/Client/DeleteController.php similarity index 74% rename from src/Controller/ClientDeleteController.php rename to src/Controller/Client/DeleteController.php index 73fa1b4b..c48112fd 100644 --- a/src/Controller/ClientDeleteController.php +++ b/src/Controller/Client/DeleteController.php @@ -1,5 +1,7 @@ clientRepository = $clientRepository; - $this->templateFactory = $templateFactory; - $this->messages = $messages; $this->authContextService = $authContextService; } /** - * @return \Laminas\Diactoros\Response\RedirectResponse|\SimpleSAML\XHTML\Template + * @throws ConfigurationError|BadRequest|NotFound|Exception|OidcServerException|JsonException + * @throws \Exception */ - public function __invoke(ServerRequest $request) + public function __invoke(ServerRequest $request): Template|RedirectResponse { $client = $this->getClientFromRequest($request); $body = $request->getParsedBody(); - $clientSecret = $body['secret'] ?? null; + $clientSecret = empty($body['secret']) ? null : (string)$body['secret']; $authedUser = $this->authContextService->isSspAdmin() ? null : $this->authContextService->getAuthUserId(); if ('POST' === mb_strtoupper($request->getMethod())) { if (!$clientSecret) { diff --git a/src/Controller/ClientEditController.php b/src/Controller/Client/EditController.php similarity index 62% rename from src/Controller/ClientEditController.php rename to src/Controller/Client/EditController.php index 47800daf..2e06240d 100644 --- a/src/Controller/ClientEditController.php +++ b/src/Controller/Client/EditController.php @@ -1,5 +1,7 @@ configurationService = $configurationService; $this->clientRepository = $clientRepository; - $this->allowedOriginRepository = $allowedOriginRepository; - $this->templateFactory = $templateFactory; - $this->formFactory = $formFactory; - $this->messages = $messages; $this->authContextService = $authContextService; } /** - * @return RedirectResponse|Template - * @throws Exception + * @throws BadRequest|Exception|NotFound|\Exception */ - public function __invoke(ServerRequest $request) + public function __invoke(ServerRequest $request): Template|RedirectResponse { - $client = $this->getClientFromRequest($request); $clientAllowedOrigins = $this->allowedOriginRepository->get($client->getIdentifier()); @@ -76,7 +62,6 @@ public function __invoke(ServerRequest $request) $form = $this->formFactory->build(ClientForm::class); $formAction = $request->withQueryParams(['client_id' => $client->getIdentifier()])->getRequestTarget(); $form->setAction($formAction); - $clientData = $client->toArray(); $clientData['allowed_origin'] = $clientAllowedOrigins; $form->setDefaults($clientData); @@ -85,23 +70,43 @@ public function __invoke(ServerRequest $request) if ($form->isSuccess()) { $data = $form->getValues(); + if ( + !is_string($data['name']) || + !is_string($data['description']) || + !is_array($data['redirect_uri']) || + !is_array($data['scopes']) || + !is_array($data['post_logout_redirect_uri']) || + !is_array($data['allowed_origin']) + ) { + throw OidcServerException::serverError('Invalid Client Entity data'); + } + + /** @var string[] $redirectUris */ + $redirectUris = $data['redirect_uri']; + /** @var string[] $scopes */ + $scopes = $data['scopes']; + /** @var string[] $postLogoutRedirectUris */ + $postLogoutRedirectUris = $data['post_logout_redirect_uri']; + /** @var string[] $allowedOrigins */ + $allowedOrigins = $data['allowed_origin']; + $this->clientRepository->update(ClientEntity::fromData( $client->getIdentifier(), $client->getSecret(), $data['name'], $data['description'], - $data['redirect_uri'], - $data['scopes'], + $redirectUris, + $scopes, (bool) $data['is_enabled'], (bool) $data['is_confidential'], - $data['auth_source'], + empty($data['auth_source']) ? null : (string)$data['auth_source'], $client->getOwner(), - $data['post_logout_redirect_uri'], - $data['backchannel_logout_uri'] + $postLogoutRedirectUris, + empty($data['backchannel_logout_uri']) ? null : (string)$data['backchannel_logout_uri'] ), $authedUser); // Also persist allowed origins for this client. - $this->allowedOriginRepository->set($client->getIdentifier(), $data['allowed_origin']); + $this->allowedOriginRepository->set($client->getIdentifier(), $allowedOrigins); $this->messages->addMessage('{oidc:client:updated}'); diff --git a/src/Controller/ClientIndexController.php b/src/Controller/Client/IndexController.php similarity index 68% rename from src/Controller/ClientIndexController.php rename to src/Controller/Client/IndexController.php index 6f64405a..84f5b8f2 100644 --- a/src/Controller/ClientIndexController.php +++ b/src/Controller/Client/IndexController.php @@ -1,5 +1,7 @@ clientRepository = $clientRepository; - $this->templateFactory = $templateFactory; - $this->authContextService = $authContextService; } - public function __invoke(ServerRequest $request): \SimpleSAML\XHTML\Template + /** + * @throws Exception + * @throws \Exception + */ + public function __invoke(ServerRequest $request): Template { $queryParams = $request->getQueryParams(); diff --git a/src/Controller/ClientResetSecretController.php b/src/Controller/Client/ResetSecretController.php similarity index 84% rename from src/Controller/ClientResetSecretController.php rename to src/Controller/Client/ResetSecretController.php index 306b06f0..172ce0f8 100644 --- a/src/Controller/ClientResetSecretController.php +++ b/src/Controller/Client/ResetSecretController.php @@ -1,5 +1,7 @@ clientRepository = $clientRepository; - $this->messages = $messages; $this->authContextService = $authContextService; } + /** + * @throws BadRequest + * @throws NotFound + * @throws Exception + * @throws \Exception + */ public function __invoke(ServerRequest $request): RedirectResponse { $client = $this->getClientFromRequest($request); $body = $request->getParsedBody(); - $clientSecret = $body['secret'] ?? null; + $clientSecret = empty($body['secret']) ? null : (string)$body['secret']; if ('POST' === mb_strtoupper($request->getMethod())) { if (!$clientSecret) { diff --git a/src/Controller/ClientShowController.php b/src/Controller/Client/ShowController.php similarity index 70% rename from src/Controller/ClientShowController.php rename to src/Controller/Client/ShowController.php index 2dc5cc8b..bb06e86e 100644 --- a/src/Controller/ClientShowController.php +++ b/src/Controller/Client/ShowController.php @@ -1,5 +1,7 @@ clientRepository = $clientRepository; - $this->allowedOriginRepository = $allowedOriginRepository; - $this->templateFactory = $templateFactory; $this->authContextService = $authContextService; } - public function __invoke(ServerRequest $request): \SimpleSAML\XHTML\Template + /** + * @throws BadRequest|Exception|NotFound|OidcServerException|JsonException + */ + public function __invoke(ServerRequest $request): Template { $client = $this->getClientFromRequest($request); $allowedOrigins = $this->allowedOriginRepository->get($client->getIdentifier()); diff --git a/src/Controller/ConfigurationDiscoveryController.php b/src/Controller/ConfigurationDiscoveryController.php new file mode 100644 index 00000000..08f3fea1 --- /dev/null +++ b/src/Controller/ConfigurationDiscoveryController.php @@ -0,0 +1,32 @@ +opMetadataService->getMetadata()); + } +} diff --git a/src/Controller/OpenIdConnectInstallerController.php b/src/Controller/InstallerController.php similarity index 64% rename from src/Controller/OpenIdConnectInstallerController.php rename to src/Controller/InstallerController.php index 6f776e72..63e5b591 100644 --- a/src/Controller/OpenIdConnectInstallerController.php +++ b/src/Controller/InstallerController.php @@ -1,5 +1,7 @@ templateFactory = $templateFactory; - $this->messages = $messages; - $this->databaseMigration = $databaseMigration; - $this->databaseLegacyOAuth2Import = $databaseLegacyOAuth2Import; } /** - * @return \Laminas\Diactoros\Response\RedirectResponse|\SimpleSAML\XHTML\Template + * @throws Exception */ - public function __invoke(ServerRequest $request) + public function __invoke(ServerRequest $request): Template|RedirectResponse { if ($this->databaseMigration->isUpdated()) { return new RedirectResponse((new HTTP())->addURLParameters('admin-clients/index.php', [])); } - $oauth2Enabled = \in_array('oauth2', Module::getModules(), true); + $oauth2Enabled = in_array('oauth2', Module::getModules(), true); $parsedBody = $request->getParsedBody(); if ('POST' === $request->getMethod() && ($parsedBody['migrate'] ?? false)) { diff --git a/src/Controller/OpenIdConnectJwksController.php b/src/Controller/JwksController.php similarity index 66% rename from src/Controller/OpenIdConnectJwksController.php rename to src/Controller/JwksController.php index 4cd13e32..ed951baa 100644 --- a/src/Controller/OpenIdConnectJwksController.php +++ b/src/Controller/JwksController.php @@ -1,5 +1,7 @@ jsonWebKeySetService = $jsonWebKeySetService; } - public function __invoke(ServerRequest $request): JsonResponse + public function __invoke(): JsonResponse { return new JsonResponse([ 'keys' => array_values($this->jsonWebKeySetService->keys()), diff --git a/src/Controller/LogoutController.php b/src/Controller/LogoutController.php index 105cedba..7e000cec 100644 --- a/src/Controller/LogoutController.php +++ b/src/Controller/LogoutController.php @@ -1,19 +1,21 @@ authorizationServer = $authorizationServer; - $this->authenticationService = $authenticationService; - $this->sessionService = $sessionService; - $this->sessionLogoutTicketStoreBuilder = $sessionLogoutTicketStoreBuilder; - $this->loggerService = $loggerService; - $this->templateFactory = $templateFactory; } /** @@ -75,11 +58,13 @@ public function __invoke(ServerRequest $request): Response // If id_token_hint was provided, resolve session ID $idTokenHint = $logoutRequest->getIdTokenHint(); if ($idTokenHint !== null) { - $sidClaim = $idTokenHint->claims()->get('sid'); + $sidClaim = empty($idTokenHint->claims()->get('sid')) ? + null : + (string)$idTokenHint->claims()->get('sid'); } // Check if RP is requesting logout for session that previously existed (not this current session). - // Claim 'sid' from 'id_token_hint' logout parameter indicates for which session should logout be + // Claim 'sid' from 'id_token_hint' logout parameter indicates for which session should log out be // performed (sid is session ID used when ID token was issued during authn). If the requested // sid is different from the current session ID, try to find the requested session. if ( @@ -143,12 +128,14 @@ public static function logoutHandler(): void // Check for session logout tickets. If there are any, it means that the logout was initiated using OIDC RP // initiated flow for specific session (not current one). - $sessionLogoutTicketStore = SessionLogoutTicketStoreBuilder::getStaticInstance(); + $sessionLogoutTicketStore = LogoutTicketStoreBuilder::getStaticInstance(); $sessionLogoutTickets = $sessionLogoutTicketStore->getAll(); if (! empty($sessionLogoutTickets)) { + // TODO low mivanci This could brake since interface does not mandate type. Move to strong typing. + /** @var array $sessionLogoutTicket */ foreach ($sessionLogoutTickets as $sessionLogoutTicket) { - $sid = $sessionLogoutTicket['sid']; + $sid = (string)$sessionLogoutTicket['sid']; if ($sid === $session->getSessionId()) { continue; } @@ -173,17 +160,22 @@ public static function logoutHandler(): void } } - $sessionLogoutTicketStore->deleteMultiple(array_map(fn($slt) => $slt['sid'], $sessionLogoutTickets)); + $sessionLogoutTicketStore->deleteMultiple( + array_map(fn(array $slt): string => (string)$slt['sid'], $sessionLogoutTickets) + ); } (new BackChannelLogoutHandler())->handle($relyingPartyAssociations); } + /** + * @throws ConfigurationError + */ protected function resolveResponse(LogoutRequest $logoutRequest, bool $wasLogoutActionCalled): Response { if (($postLogoutRedirectUri = $logoutRequest->getPostLogoutRedirectUri()) !== null) { if ($logoutRequest->getState() !== null) { - $postLogoutRedirectUri .= (strstr($postLogoutRedirectUri, '?') === false) ? '?' : '&'; + $postLogoutRedirectUri .= (!str_contains($postLogoutRedirectUri, '?')) ? '?' : '&'; $postLogoutRedirectUri .= http_build_query(['state' => $logoutRequest->getState()]); } diff --git a/src/Controller/OpenIdConnectDiscoverConfigurationController.php b/src/Controller/OpenIdConnectDiscoverConfigurationController.php deleted file mode 100644 index 405cdba3..00000000 --- a/src/Controller/OpenIdConnectDiscoverConfigurationController.php +++ /dev/null @@ -1,38 +0,0 @@ -oidcOpenIdProviderMetadataService = $oidcOpenIdProviderMetadataService; - } - - public function __invoke(ServerRequest $serverRequest): JsonResponse - { - return new JsonResponse($this->oidcOpenIdProviderMetadataService->getMetadata()); - } -} diff --git a/src/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php b/src/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php index 8f9927e5..9ca2dacf 100644 --- a/src/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php +++ b/src/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php @@ -1,5 +1,7 @@ getQueryParams(); - $clientId = $params['client_id'] ?? null; + $clientId = empty($params['client_id']) ? null : (string)$params['client_id']; - if (!$clientId) { + if (!is_string($clientId)) { throw new BadRequest('Client id is missing.'); } $authedUser = null; diff --git a/src/Controller/Traits/GetClientFromRequestTrait.php b/src/Controller/Traits/GetClientFromRequestTrait.php index 0e58de04..f375cc36 100644 --- a/src/Controller/Traits/GetClientFromRequestTrait.php +++ b/src/Controller/Traits/GetClientFromRequestTrait.php @@ -1,5 +1,7 @@ getQueryParams(); - $clientId = $params['client_id'] ?? null; + $clientId = empty($params['client_id']) ? null : (string)$params['client_id']; - if (!$clientId) { + if (!is_string($clientId)) { throw new BadRequest('Client id is missing.'); } diff --git a/src/Controller/OpenIdConnectUserInfoController.php b/src/Controller/UserInfoController.php similarity index 70% rename from src/Controller/OpenIdConnectUserInfoController.php rename to src/Controller/UserInfoController.php index 0c657045..8c948617 100644 --- a/src/Controller/OpenIdConnectUserInfoController.php +++ b/src/Controller/UserInfoController.php @@ -1,5 +1,7 @@ resourceServer = $resourceServer; - $this->accessTokenRepository = $accessTokenRepository; - $this->userRepository = $userRepository; - $this->allowedOriginRepository = $allowedOriginRepository; - $this->claimTranslatorExtractor = $claimTranslatorExtractor; } + /** + * @throws UserNotFound + * @throws OidcServerException + * @throws OAuthServerException + */ public function __invoke(ServerRequest $request): Response { // Check if this is actually a CORS preflight request... @@ -79,7 +55,9 @@ public function __invoke(ServerRequest $request): Response $authorization = $this->resourceServer->validateAuthenticatedRequest($request); + /** @var string $tokenId */ $tokenId = $authorization->getAttribute('oauth_access_token_id'); + /** @var string[] $scopes */ $scopes = $authorization->getAttribute('oauth_scopes'); $accessToken = $this->accessTokenRepository->findById($tokenId); @@ -100,18 +78,15 @@ public function __invoke(ServerRequest $request): Response } /** - * @param AccessTokenEntity $accessToken - * + * @throws OidcServerException * @throws UserNotFound - * - * @return UserEntity */ - private function getUser(AccessTokenEntity $accessToken) + 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 UserNotFound("User $userIdentifier not found"); } return $user; @@ -120,8 +95,6 @@ private function getUser(AccessTokenEntity $accessToken) /** * Handle CORS 'preflight' requests by checking if 'origin' is registered as allowed to make HTTP CORS requests, * typically initiated in browser by JavaScript clients. - * @param ServerRequest $request - * @return Response * @throws OidcServerException */ protected function handleCors(ServerRequest $request): Response diff --git a/src/Entity/AccessTokenEntity.php b/src/Entities/AccessTokenEntity.php similarity index 64% rename from src/Entity/AccessTokenEntity.php rename to src/Entities/AccessTokenEntity.php index 9859e3a2..0a1c4d86 100644 --- a/src/Entity/AccessTokenEntity.php +++ b/src/Entities/AccessTokenEntity.php @@ -1,5 +1,7 @@ ScopeEntity::fromData($scope), $stateScopes); $accessToken->identifier = $state['id']; $accessToken->scopes = $scopes; - $accessToken->expiryDateTime = \DateTimeImmutable::createFromMutable( + $accessToken->expiryDateTime = DateTimeImmutable::createFromMutable( TimestampGenerator::utc($state['expires_at']) ); - $accessToken->userIdentifier = $state['user_id']; + $accessToken->userIdentifier = empty($state['user_id']) ? null : (string)$state['user_id']; $accessToken->client = $state['client']; $accessToken->isRevoked = (bool) $state['is_revoked']; - $accessToken->authCodeId = $state['auth_code_id']; - $accessToken->requestedClaims = json_decode($state['requested_claims'] ?? '[]', true); + $accessToken->authCodeId = empty($state['auth_code_id']) ? null : (string)$state['auth_code_id']; + + $stateRequestedClaims = json_decode( + empty($state['requested_claims']) ? '[]' : (string)$state['requested_claims'], + true, + 512, + JSON_THROW_ON_ERROR + ); + if (!is_array($stateRequestedClaims)) { + throw OidcServerException::serverError('Invalid Access Token Entity state: requested claims'); + } + $accessToken->requestedClaims = $stateRequestedClaims; + return $accessToken; } @@ -116,9 +147,6 @@ public function getRequestedClaims(): array return $this->requestedClaims; } - /** - * @param array $requestedClaims - */ public function setRequestedClaims(array $requestedClaims): void { $this->requestedClaims = $requestedClaims; @@ -126,18 +154,20 @@ public function setRequestedClaims(array $requestedClaims): void /** * {@inheritdoc} + * @throws JsonException + * @throws JsonException */ public function getState(): array { return [ 'id' => $this->getIdentifier(), - 'scopes' => json_encode($this->scopes), + 'scopes' => json_encode($this->scopes, JSON_THROW_ON_ERROR), 'expires_at' => $this->getExpiryDateTime()->format('Y-m-d H:i:s'), 'user_id' => $this->getUserIdentifier(), 'client_id' => $this->getClient()->getIdentifier(), 'is_revoked' => (int) $this->isRevoked(), 'auth_code_id' => $this->getAuthCodeId(), - 'requested_claims' => json_encode($this->requestedClaims) + 'requested_claims' => json_encode($this->requestedClaims, JSON_THROW_ON_ERROR) ]; } @@ -146,14 +176,14 @@ public function getState(): array * @return string * @throws OAuthServerException */ - public function __toString() + public function __toString(): string { return $this->stringRepresentation = $this->convertToJWT()->toString(); } /** * Get string representation of access token at the moment of casting it to string. - * @return string|null String representation or null if it was not casted to string yet. + * @return string|null String representation or null if it was not cast to string yet. */ public function toString(): ?string { @@ -166,6 +196,7 @@ public function toString(): ?string * * @return Token * @throws OAuthServerException + * @throws Exception */ protected function convertToJWT(): Token { @@ -173,7 +204,7 @@ protected function convertToJWT(): Token $jwtBuilder = $jwtBuilderService->getDefaultJwtTokenBuilder() ->permittedFor($this->getClient()->getIdentifier()) - ->identifiedBy($this->getIdentifier()) + ->identifiedBy((string)$this->getIdentifier()) ->issuedAt(new DateTimeImmutable()) ->canOnlyBeUsedAfter(new DateTimeImmutable()) ->expiresAt($this->getExpiryDateTime()) diff --git a/src/Entities/AuthCodeEntity.php b/src/Entities/AuthCodeEntity.php new file mode 100644 index 00000000..ac1feac4 --- /dev/null +++ b/src/Entities/AuthCodeEntity.php @@ -0,0 +1,99 @@ + ScopeEntity::fromData($scope), + $stateScopes + ); + + $authCode->identifier = $state['id']; + $authCode->scopes = $scopes; + $authCode->expiryDateTime = DateTimeImmutable::createFromMutable( + TimestampGenerator::utc($state['expires_at']) + ); + $authCode->userIdentifier = empty($state['user_id']) ? null : (string)$state['user_id']; + $authCode->client = $state['client']; + $authCode->isRevoked = (bool) $state['is_revoked']; + $authCode->redirectUri = empty($state['redirect_uri']) ? null : (string)$state['redirect_uri']; + $authCode->nonce = empty($state['nonce']) ? null : (string)$state['nonce']; + + return $authCode; + } + + /** + * @throws JsonException + */ + public function getState(): array + { + return [ + 'id' => $this->getIdentifier(), + 'scopes' => json_encode($this->scopes, JSON_THROW_ON_ERROR), + 'expires_at' => $this->getExpiryDateTime()->format('Y-m-d H:i:s'), + 'user_id' => $this->getUserIdentifier(), + 'client_id' => $this->client->getIdentifier(), + 'is_revoked' => (int) $this->isRevoked(), + 'redirect_uri' => $this->getRedirectUri(), + 'nonce' => $this->getNonce(), + ]; + } +} diff --git a/src/Entities/ClaimSetEntity.php b/src/Entities/ClaimSetEntity.php new file mode 100644 index 00000000..a384db6e --- /dev/null +++ b/src/Entities/ClaimSetEntity.php @@ -0,0 +1,31 @@ + + * @copyright (c) 2018 Steve Rhoades + * @license http://opensource.org/licenses/MIT MIT + */ +class ClaimSetEntity implements ClaimSetEntityInterface +{ + public function __construct(protected string $scope, protected array $claims) + { + } + + public function getScope(): string + { + return $this->scope; + } + + public function getClaims(): array + { + return $this->claims; + } +} diff --git a/src/Entity/ClientEntity.php b/src/Entities/ClientEntity.php similarity index 61% rename from src/Entity/ClientEntity.php rename to src/Entities/ClientEntity.php index 721a3489..0189d6b4 100644 --- a/src/Entity/ClientEntity.php +++ b/src/Entities/ClientEntity.php @@ -1,5 +1,7 @@ isEnabled = true; } + /** + * @param string $id + * @param string $secret + * @param string $name + * @param string $description + * @param string[] $redirectUri + * @param string[] $scopes + * @param bool $isEnabled + * @param bool $isConfidential + * @param string|null $authSource + * @param string|null $owner + * @param string[] $postLogoutRedirectUri + * @param string|null $backChannelLogoutUri + * @return ClientEntityInterface + */ public static function fromData( string $id, string $secret, @@ -70,43 +94,76 @@ public static function fromData( $client->secret = $secret; $client->name = $name; $client->description = $description; - $client->authSource = $authSource; + $client->authSource = empty($authSource) ? null : $authSource; $client->redirectUri = $redirectUri; $client->scopes = $scopes; $client->isEnabled = $isEnabled; $client->isConfidential = $isConfidential; - $client->owner = $owner; + $client->owner = empty($owner) ? null : $owner; $client->postLogoutRedirectUri = $postLogoutRedirectUri; - $client->backChannelLogoutUri = $backChannelLogoutUri; + $client->backChannelLogoutUri = empty($backChannelLogoutUri) ? null : $backChannelLogoutUri; return $client; } /** - * {@inheritdoc} + * @throws JsonException + * @throws OidcServerException */ public static function fromState(array $state): self { $client = new self(); + if ( + !is_string($state['id']) || + !is_string($state['secret']) || + !is_string($state['name']) || + !is_string($state['redirect_uri']) || + !is_string($state['scopes']) + ) { + throw OidcServerException::serverError('Invalid Client Entity state'); + } + $client->identifier = $state['id']; $client->secret = $state['secret']; $client->name = $state['name']; - $client->description = $state['description']; - $client->authSource = $state['auth_source']; - $client->redirectUri = json_decode($state['redirect_uri'], true); - $client->scopes = json_decode($state['scopes'], true); + $client->description = (string)($state['description'] ?? ''); + $client->authSource = empty($state['auth_source']) ? null : (string)$state['auth_source']; + + /** @var string[] $redirectUris */ + $redirectUris = json_decode($state['redirect_uri'], true, 512, JSON_THROW_ON_ERROR); + $client->redirectUri = $redirectUris; + + /** @var string[] $scopes */ + $scopes = json_decode($state['scopes'], true, 512, JSON_THROW_ON_ERROR); + $client->scopes = $scopes; + $client->isEnabled = (bool) $state['is_enabled']; $client->isConfidential = (bool) ($state['is_confidential'] ?? false); - $client->owner = $state['owner'] ?? null; - $client->postLogoutRedirectUri = json_decode($state['post_logout_redirect_uri'] ?? "[]", true); - $client->backChannelLogoutUri = $state['backchannel_logout_uri'] ?? null; + $client->owner = empty($state['owner']) ? null : (string)$state['owner']; + + /** @var string[] $postLogoutRedirectUris */ + $postLogoutRedirectUris = json_decode( + (string)($state['post_logout_redirect_uri'] ?? "[]"), + true, + 512, + JSON_THROW_ON_ERROR + ); + $client->postLogoutRedirectUri = $postLogoutRedirectUris; + + + $client->backChannelLogoutUri = empty($state['backchannel_logout_uri']) ? + null : + (string)$state['backchannel_logout_uri']; return $client; } /** * {@inheritdoc} + * @throws JsonException + * @throws JsonException + * @throws JsonException */ public function getState(): array { @@ -116,12 +173,12 @@ public function getState(): array 'name' => $this->getName(), 'description' => $this->getDescription(), 'auth_source' => $this->getAuthSourceId(), - 'redirect_uri' => json_encode($this->getRedirectUri()), - 'scopes' => json_encode($this->getScopes()), + 'redirect_uri' => json_encode($this->getRedirectUri(), JSON_THROW_ON_ERROR), + 'scopes' => json_encode($this->getScopes(), JSON_THROW_ON_ERROR), 'is_enabled' => (int) $this->isEnabled(), 'is_confidential' => (int) $this->isConfidential(), 'owner' => $this->getOwner(), - 'post_logout_redirect_uri' => json_encode($this->getPostLogoutRedirectUri()), + 'post_logout_redirect_uri' => json_encode($this->getPostLogoutRedirectUri(), JSON_THROW_ON_ERROR), 'backchannel_logout_uri' => $this->getBackChannelLogoutUri(), ]; diff --git a/src/Entity/Interfaces/AccessTokenEntityInterface.php b/src/Entities/Interfaces/AccessTokenEntityInterface.php similarity index 74% rename from src/Entity/Interfaces/AccessTokenEntityInterface.php rename to src/Entities/Interfaces/AccessTokenEntityInterface.php index d75195e9..960ea44b 100644 --- a/src/Entity/Interfaces/AccessTokenEntityInterface.php +++ b/src/Entities/Interfaces/AccessTokenEntityInterface.php @@ -1,6 +1,8 @@ + * @copyright (c) 2018 Steve Rhoades + * @license http://opensource.org/licenses/MIT MIT + */ +interface ClaimSetEntityInterface extends ClaimSetInterface, ScopeInterface +{ +} diff --git a/src/Entities/Interfaces/ClaimSetInterface.php b/src/Entities/Interfaces/ClaimSetInterface.php new file mode 100644 index 00000000..342f1f34 --- /dev/null +++ b/src/Entities/Interfaces/ClaimSetInterface.php @@ -0,0 +1,17 @@ + + * @copyright (c) 2018 Steve Rhoades + * @license http://opensource.org/licenses/MIT MIT + */ +interface ClaimSetInterface +{ + public function getClaims(): array; +} diff --git a/src/Entity/Interfaces/ClientEntityInterface.php b/src/Entities/Interfaces/ClientEntityInterface.php similarity index 86% rename from src/Entity/Interfaces/ClientEntityInterface.php rename to src/Entities/Interfaces/ClientEntityInterface.php index 89c4581a..3402905d 100644 --- a/src/Entity/Interfaces/ClientEntityInterface.php +++ b/src/Entities/Interfaces/ClientEntityInterface.php @@ -1,11 +1,17 @@ + * @copyright (c) 2018 Steve Rhoades + * @license http://opensource.org/licenses/MIT MIT + */ +interface ScopeInterface +{ + public function getScope(): string; +} diff --git a/src/Entity/Interfaces/TokenAssociatableWithAuthCodeInterface.php b/src/Entities/Interfaces/TokenAssociatableWithAuthCodeInterface.php similarity index 79% rename from src/Entity/Interfaces/TokenAssociatableWithAuthCodeInterface.php rename to src/Entities/Interfaces/TokenAssociatableWithAuthCodeInterface.php index 9f544e1f..29306133 100644 --- a/src/Entity/Interfaces/TokenAssociatableWithAuthCodeInterface.php +++ b/src/Entities/Interfaces/TokenAssociatableWithAuthCodeInterface.php @@ -1,6 +1,8 @@ identifier = $state['id']; - $refreshToken->expiryDateTime = \DateTimeImmutable::createFromMutable( + $refreshToken->expiryDateTime = DateTimeImmutable::createFromMutable( TimestampGenerator::utc($state['expires_at']) ); $refreshToken->accessToken = $state['access_token']; $refreshToken->isRevoked = (bool) $state['is_revoked']; - $refreshToken->authCodeId = $state['auth_code_id']; + $refreshToken->authCodeId = empty($state['auth_code_id']) ? null : (string)$state['auth_code_id']; return $refreshToken; } diff --git a/src/Entity/ScopeEntity.php b/src/Entities/ScopeEntity.php similarity index 76% rename from src/Entity/ScopeEntity.php rename to src/Entities/ScopeEntity.php index 84a061b6..d9ec406d 100644 --- a/src/Entity/ScopeEntity.php +++ b/src/Entities/ScopeEntity.php @@ -1,5 +1,7 @@ */ - private $attributes; + private array $claims; - /** - * Constructor. - */ private function __construct() { } /** - * @param array $attributes + * @param array $claims */ public static function fromData( string $identifier, string $description = null, string $icon = null, - array $attributes = [] + array $claims = [] ): self { $scope = new self(); $scope->identifier = $identifier; $scope->description = $description; $scope->icon = $icon; - $scope->attributes = $attributes; + $scope->claims = $claims; return $scope; } @@ -75,13 +77,13 @@ public function getDescription(): ?string /** * @return array */ - public function getAttributes(): array + public function getClaims(): array { - return $this->attributes; + return $this->claims; } public function jsonSerialize(): string { - return $this->getIdentifier(); + return (string) $this->getIdentifier(); } } diff --git a/src/Entity/Traits/AssociateWithAuthCodeTrait.php b/src/Entities/Traits/AssociateWithAuthCodeTrait.php similarity index 66% rename from src/Entity/Traits/AssociateWithAuthCodeTrait.php rename to src/Entities/Traits/AssociateWithAuthCodeTrait.php index ece8373c..bb481822 100644 --- a/src/Entity/Traits/AssociateWithAuthCodeTrait.php +++ b/src/Entities/Traits/AssociateWithAuthCodeTrait.php @@ -1,13 +1,12 @@ nonce; } - /** - * @inheritDoc - */ - public function setNonce($nonce): void + public function setNonce(string $nonce): void { $this->nonce = $nonce; } diff --git a/src/Entity/Traits/RevokeTokenTrait.php b/src/Entities/Traits/RevokeTokenTrait.php similarity index 82% rename from src/Entity/Traits/RevokeTokenTrait.php rename to src/Entities/Traits/RevokeTokenTrait.php index fabe2b53..2834a02f 100644 --- a/src/Entity/Traits/RevokeTokenTrait.php +++ b/src/Entities/Traits/RevokeTokenTrait.php @@ -1,5 +1,7 @@ identifier = $state['id']; - $user->claims = json_decode($state['claims'], true, 512, JSON_INVALID_UTF8_SUBSTITUTE); + $claims = json_decode($state['claims'], true, 512, JSON_INVALID_UTF8_SUBSTITUTE); + + if (!is_array($claims)) { + throw OidcServerException::serverError('Invalid user entity data'); + } + $user->claims = $claims; $user->updatedAt = TimestampGenerator::utc($state['updated_at']); $user->createdAt = TimestampGenerator::utc($state['created_at']); @@ -98,6 +122,9 @@ public function getClaims(): array return $this->claims; } + /** + * @throws Exception + */ public function setClaims(array $claims): self { $this->claims = $claims; @@ -106,12 +133,12 @@ public function setClaims(array $claims): self return $this; } - public function getUpdatedAt(): \DateTime + public function getUpdatedAt(): DateTime { return $this->updatedAt; } - public function getCreatedAt(): \DateTime + public function getCreatedAt(): DateTime { return $this->createdAt; } diff --git a/src/Entity/AuthCodeEntity.php b/src/Entity/AuthCodeEntity.php deleted file mode 100644 index b18536e6..00000000 --- a/src/Entity/AuthCodeEntity.php +++ /dev/null @@ -1,74 +0,0 @@ -identifier = $state['id']; - $authCode->scopes = $scopes; - $authCode->expiryDateTime = \DateTimeImmutable::createFromMutable( - TimestampGenerator::utc($state['expires_at']) - ); - $authCode->userIdentifier = $state['user_id']; - $authCode->client = $state['client']; - $authCode->isRevoked = (bool) $state['is_revoked']; - $authCode->redirectUri = $state['redirect_uri']; - $authCode->nonce = $state['nonce']; - - - return $authCode; - } - - public function getState(): array - { - return [ - 'id' => $this->getIdentifier(), - 'scopes' => json_encode($this->scopes), - 'expires_at' => $this->getExpiryDateTime()->format('Y-m-d H:i:s'), - 'user_id' => $this->getUserIdentifier(), - 'client_id' => $this->client->getIdentifier(), - 'is_revoked' => (int) $this->isRevoked(), - 'redirect_uri' => $this->getRedirectUri(), - 'nonce' => $this->getNonce(), - ]; - } -} diff --git a/src/Factories/AuthSimpleFactory.php b/src/Factories/AuthSimpleFactory.php index 3f9d723f..86b1ed6e 100644 --- a/src/Factories/AuthSimpleFactory.php +++ b/src/Factories/AuthSimpleFactory.php @@ -1,5 +1,7 @@ clientRepository = $clientRepository; - $this->configurationService = $configurationService; } /** @@ -58,8 +56,6 @@ 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. * - * @param ClientEntityInterface $client - * @return string * @throws Exception */ public function resolveAuthSourceId(ClientEntityInterface $client): string @@ -72,6 +68,6 @@ public function resolveAuthSourceId(ClientEntityInterface $client): string */ public function getDefaultAuthSourceId(): string { - return $this->configurationService->getOpenIDConnectConfiguration()->getString('auth'); + return $this->moduleConfig->config()->getString(ModuleConfig::OPTION_AUTH_SOURCE); } } diff --git a/src/Factories/AuthorizationServerFactory.php b/src/Factories/AuthorizationServerFactory.php index 76b43c62..957e9fc8 100644 --- a/src/Factories/AuthorizationServerFactory.php +++ b/src/Factories/AuthorizationServerFactory.php @@ -1,5 +1,7 @@ clientRepository = $clientRepository; - $this->accessTokenRepository = $accessTokenRepository; - $this->scopeRepository = $scopeRepository; - $this->authCodeGrant = $authCodeGrant; - $this->oAuth2ImplicitGrant = $oAuth2ImplicitGrant; - $this->implicitGrant = $implicitGrant; - $this->refreshTokenGrant = $refreshTokenGrant; - $this->accessTokenDuration = $accessTokenDuration; - $this->idTokenResponse = $idTokenResponse; - $this->requestRulesManager = $requestRulesManager; - $this->privateKey = $privateKey; - $this->encryptionKey = $encryptionKey; } public function build(): AuthorizationServer diff --git a/src/Factories/ClaimTranslatorExtractorFactory.php b/src/Factories/ClaimTranslatorExtractorFactory.php index af96b7c0..762bf29f 100644 --- a/src/Factories/ClaimTranslatorExtractorFactory.php +++ b/src/Factories/ClaimTranslatorExtractorFactory.php @@ -1,5 +1,7 @@ configurationService = $configurationService; + public function __construct(private readonly ModuleConfig $moduleConfig) + { } /** - * @throws InvalidArgumentException * @throws Exception */ public function build(): ClaimTranslatorExtractor { - $translatorTable = $this->configurationService->getOpenIDConnectConfiguration() - ->getOptionalArray('translate', []); + $translatorTable = $this->moduleConfig->config() + ->getOptionalArray(ModuleConfig::OPTION_AUTH_SAML_TO_OIDC_TRANSLATE_TABLE, []); - $privateScopes = $this->configurationService->getOpenIDPrivateScopes(); + $privateScopes = $this->moduleConfig->getOpenIDPrivateScopes(); $claimSet = []; $allowedMultipleValueClaims = []; + /** + * @var string $scopeName + * @var array $scopeConfig + */ foreach ($privateScopes as $scopeName => $scopeConfig) { - $claims = $scopeConfig['claims'] ?? []; + $claims = is_array($scopeConfig['claims']) ? $scopeConfig['claims'] : []; if ($this->isScopeClaimNamePrefixSet($scopeConfig)) { - $prefix = $scopeConfig[self::CONFIG_KEY_CLAIM_NAME_PREFIX]; + $prefix = (string)($scopeConfig[self::CONFIG_KEY_CLAIM_NAME_PREFIX] ?? ''); $translatorTable = $this->applyPrefixToTranslatorTableKeys($translatorTable, $claims, $prefix); $claims = $this->applyPrefixToClaimNames($claims, $prefix); @@ -65,7 +65,7 @@ public function build(): ClaimTranslatorExtractor } } - $userIdAttr = $this->configurationService->getOpenIDConnectConfiguration()->getString('useridattr'); + $userIdAttr = $this->moduleConfig->getUserIdentifierAttribute(); return new ClaimTranslatorExtractor($userIdAttr, $claimSet, $translatorTable, $allowedMultipleValueClaims); } @@ -80,6 +80,10 @@ public function build(): ClaimTranslatorExtractor */ protected function applyPrefixToTranslatorTableKeys(array $translatorTable, array $claims, string $prefix): array { + /** + * @var string $claimKey + * @var array $mapping + */ foreach ($translatorTable as $claimKey => $mapping) { if (in_array($claimKey, $claims)) { $prefixedClaimKey = $prefix . $claimKey; @@ -98,7 +102,7 @@ protected function applyPrefixToTranslatorTableKeys(array $translatorTable, arra */ protected function applyPrefixToClaimNames(array $claims, string $prefix): array { - array_walk($claims, function (&$value, $key, $prefix) { + array_walk($claims, function (string &$value, mixed $key, string $prefix) { $value = $prefix . $value; }, $prefix); @@ -107,24 +111,20 @@ protected function applyPrefixToClaimNames(array $claims, string $prefix): array /** * Check if the scope has a claim name prefix set - * @param array $scopeConfig - * @return bool */ protected function isScopeClaimNamePrefixSet(array $scopeConfig): bool { return isset($scopeConfig[self::CONFIG_KEY_CLAIM_NAME_PREFIX]) && - is_string($scopeConfig[self::CONFIG_KEY_CLAIM_NAME_PREFIX]) && - !empty($scopeConfig[self::CONFIG_KEY_CLAIM_NAME_PREFIX]); + is_string($scopeConfig[self::CONFIG_KEY_CLAIM_NAME_PREFIX]) && + !empty($scopeConfig[self::CONFIG_KEY_CLAIM_NAME_PREFIX]); } /** * Check if the scope allows claims to have multiple values. - * @param array $scopeConfig - * @return bool */ protected function doesScopeAllowMultipleClaimValues(array $scopeConfig): bool { return isset($scopeConfig[self::CONFIG_KEY_MULTIPLE_CLAIM_VALUES_ALLOWED]) && - boolval($scopeConfig[self::CONFIG_KEY_MULTIPLE_CLAIM_VALUES_ALLOWED]); + $scopeConfig[self::CONFIG_KEY_MULTIPLE_CLAIM_VALUES_ALLOWED]; } } diff --git a/src/Factories/CryptKeyFactory.php b/src/Factories/CryptKeyFactory.php index bc63a3ed..9ed903ac 100644 --- a/src/Factories/CryptKeyFactory.php +++ b/src/Factories/CryptKeyFactory.php @@ -1,41 +1,35 @@ publicKeyPath = $publicKeyPath; - $this->privateKeyPath = $privateKeyPath; - $this->passPhrase = $passPhrase; } + /** + * @throws \Exception + */ public function buildPrivateKey(): CryptKey { - return new CryptKey($this->privateKeyPath, $this->passPhrase); + return new CryptKey( + $this->moduleConfig->getPrivateKeyPath(), + $this->moduleConfig->getPrivateKeyPassPhrase() + ); } + /** + * @throws \Exception + */ public function buildPublicKey(): CryptKey { - return new CryptKey($this->publicKeyPath); + return new CryptKey($this->moduleConfig->getCertPath()); } } diff --git a/src/Factories/FormFactory.php b/src/Factories/FormFactory.php index 95de80b9..9e3fc04f 100644 --- a/src/Factories/FormFactory.php +++ b/src/Factories/FormFactory.php @@ -1,5 +1,7 @@ configurationService = $configurationService; } /** - * @param string $classname Form classname + * @param class-string $classname Form classname * * @throws \Exception * * @return mixed */ - public function build(string $classname) + public function build(string $classname): mixed { - if (!class_exists($classname) && ($classname instanceof Form)) { - throw new Exception("Invalid form: {$classname}"); + if (!is_a($classname, Form::class, true)) { + throw new Exception("Invalid form: $classname"); } - /** @psalm-suppress InvalidStringClass */ - return new $classname($this->configurationService); + /** @psalm-suppress UnsafeInstantiation */ + return new $classname($this->moduleConfig); } } diff --git a/src/Factories/Grant/AuthCodeGrantFactory.php b/src/Factories/Grant/AuthCodeGrantFactory.php index 2aa2566b..a3ab82d5 100644 --- a/src/Factories/Grant/AuthCodeGrantFactory.php +++ b/src/Factories/Grant/AuthCodeGrantFactory.php @@ -1,5 +1,7 @@ authCodeRepository = $authCodeRepository; - $this->accessTokenRepository = $accessTokenRepository; - $this->refreshTokenRepository = $refreshTokenRepository; - $this->refreshTokenDuration = $refreshTokenDuration; - $this->authCodeDuration = $authCodeDuration; - $this->requestRulesManager = $requestRulesManager; - $this->configurationService = $configurationService; } + /** + * @throws Exception + */ public function build(): AuthCodeGrant { $authCodeGrant = new AuthCodeGrant( @@ -79,8 +46,7 @@ public function build(): AuthCodeGrant $this->accessTokenRepository, $this->refreshTokenRepository, $this->authCodeDuration, - $this->requestRulesManager, - $this->configurationService + $this->requestRulesManager ); $authCodeGrant->setRefreshTokenTTL($this->refreshTokenDuration); diff --git a/src/Factories/Grant/ImplicitGrantFactory.php b/src/Factories/Grant/ImplicitGrantFactory.php index 5cc07314..6648239d 100644 --- a/src/Factories/Grant/ImplicitGrantFactory.php +++ b/src/Factories/Grant/ImplicitGrantFactory.php @@ -1,5 +1,7 @@ idTokenBuilder = $idTokenBuilder; - $this->accessTokenDuration = $accessTokenDuration; - $this->requestRulesManager = $requestRulesManager; - $this->accessTokenDuration = $accessTokenDuration; - $this->accessTokenRepository = $accessTokenRepository; } public function build(): ImplicitGrant diff --git a/src/Factories/Grant/OAuth2ImplicitGrantFactory.php b/src/Factories/Grant/OAuth2ImplicitGrantFactory.php index 46e2027f..f22fb53c 100644 --- a/src/Factories/Grant/OAuth2ImplicitGrantFactory.php +++ b/src/Factories/Grant/OAuth2ImplicitGrantFactory.php @@ -1,5 +1,7 @@ accessTokenDuration = $accessTokenDuration; - $this->requestRulesManager = $requestRulesManager; + public function __construct( + private readonly DateInterval $accessTokenDuration, + private readonly RequestRulesManager $requestRulesManager + ) { } public function build(): OAuth2ImplicitGrant diff --git a/src/Factories/Grant/RefreshTokenGrantFactory.php b/src/Factories/Grant/RefreshTokenGrantFactory.php index 820eb34b..5bfce3bd 100644 --- a/src/Factories/Grant/RefreshTokenGrantFactory.php +++ b/src/Factories/Grant/RefreshTokenGrantFactory.php @@ -1,5 +1,7 @@ refreshTokenRepository = $refreshTokenRepository; - $this->refreshTokenDuration = $refreshTokenDuration; } public function build(): RefreshTokenGrant diff --git a/src/Factories/IdTokenResponseFactory.php b/src/Factories/IdTokenResponseFactory.php index 834dc308..4c71b0bf 100644 --- a/src/Factories/IdTokenResponseFactory.php +++ b/src/Factories/IdTokenResponseFactory.php @@ -1,5 +1,7 @@ userRepository = $userRepository; - $this->configurationService = $configurationService; - $this->idTokenBuilder = $idTokenBuilder; - $this->privateKey = $privateKey; - $this->encryptionKey = $encryptionKey; } public function build(): IdTokenResponse { $idTokenResponse = new IdTokenResponse( $this->userRepository, - $this->configurationService, - $this->idTokenBuilder + $this->idTokenBuilder, + $this->privateKey ); - $idTokenResponse->setPrivateKey($this->privateKey); $idTokenResponse->setEncryptionKey($this->encryptionKey); return $idTokenResponse; diff --git a/src/Factories/ResourceServerFactory.php b/src/Factories/ResourceServerFactory.php index 3481e322..909d2e25 100644 --- a/src/Factories/ResourceServerFactory.php +++ b/src/Factories/ResourceServerFactory.php @@ -1,5 +1,7 @@ accessTokenRepository = $accessTokenRepository; - $this->publicKey = $publicKey; - $this->authorizationValidator = $authorizationValidator; } public function build(): ResourceServer diff --git a/src/Factories/TemplateFactory.php b/src/Factories/TemplateFactory.php index 1084f1d9..3eda36f1 100644 --- a/src/Factories/TemplateFactory.php +++ b/src/Factories/TemplateFactory.php @@ -1,5 +1,7 @@ toArray(); + // TODO mivanci check if this is really necessary anymore $config['usenewui'] = true; $this->configuration = new Configuration($config, 'oidc'); } + /** + * @throws ConfigurationError + */ public function render(string $templateName, array $data = []): Template { $template = new Template($this->configuration, $templateName); diff --git a/src/Form/ClientForm.php b/src/Forms/ClientForm.php similarity index 65% rename from src/Form/ClientForm.php rename to src/Forms/ClientForm.php index 532498cb..5387e316 100644 --- a/src/Form/ClientForm.php +++ b/src/Forms/ClientForm.php @@ -1,5 +1,7 @@ configurationService = $configurationService; - $this->buildForm(); } @@ -59,9 +59,10 @@ public function validateRedirectUri(Form $form): void { /** @var array $values */ $values = $form->getValues(self::TYPE_ARRAY); - + /** @var string[] $redirectUris */ + $redirectUris = $values['redirect_uri'] ?? []; $this->validateByMatchingRegex( - $values['redirect_uri'] ?? [], + $redirectUris, self::REGEX_URI, 'Invalid URI: ' ); @@ -71,9 +72,10 @@ public function validateAllowedOrigin(Form $form): void { /** @var array $values */ $values = $form->getValues(self::TYPE_ARRAY); - + /** @var string[] $allowedOrigins */ + $allowedOrigins = $values['allowed_origin'] ?? []; $this->validateByMatchingRegex( - $values['allowed_origin'] ?? [], + $allowedOrigins, self::REGEX_ALLOWED_ORIGIN_URL, 'Invalid allowed origin: ' ); @@ -83,9 +85,10 @@ public function validatePostLogoutRedirectUri(Form $form): void { /** @var array $values */ $values = $form->getValues(self::TYPE_ARRAY); - + /** @var string[] $postLogoutRedirectUris */ + $postLogoutRedirectUris = $values['post_logout_redirect_uri'] ?? []; $this->validateByMatchingRegex( - $values['post_logout_redirect_uri'] ?? [], + $postLogoutRedirectUris, self::REGEX_URI, 'Invalid post-logout redirect URI: ' ); @@ -93,7 +96,9 @@ public function validatePostLogoutRedirectUri(Form $form): void public function validateBackChannelLogoutUri(Form $form): void { - if (($bclUri = $form->getValues()['backchannel_logout_uri'] ?? null) !== null) { + /** @var ?string $bclUri */ + $bclUri = $form->getValues()['backchannel_logout_uri'] ?? null; + if ($bclUri !== null) { $this->validateByMatchingRegex( [$bclUri], self::REGEX_HTTP_URI, @@ -103,10 +108,8 @@ public function validateBackChannelLogoutUri(Form $form): void } /** - * @param array $values + * @param string[] $values * @param non-empty-string $regex - * @param string $messagePrefix - * @return void */ protected function validateByMatchingRegex( array $values, @@ -126,22 +129,24 @@ public function getValues($returnType = null, ?array $controls = null): array $values = parent::getValues(self::TYPE_ARRAY); // Sanitize redirect_uri and allowed_origin - $values['redirect_uri'] = $this->convertTextToArrayWithLinesAsValues($values['redirect_uri']); + $values['redirect_uri'] = $this->convertTextToArrayWithLinesAsValues((string)$values['redirect_uri']); if (! $values['is_confidential'] && isset($values['allowed_origin'])) { - $values['allowed_origin'] = $this->convertTextToArrayWithLinesAsValues($values['allowed_origin']); + $values['allowed_origin'] = $this->convertTextToArrayWithLinesAsValues((string)$values['allowed_origin']); } else { $values['allowed_origin'] = []; } $values['post_logout_redirect_uri'] = - $this->convertTextToArrayWithLinesAsValues($values['post_logout_redirect_uri']); + $this->convertTextToArrayWithLinesAsValues((string)$values['post_logout_redirect_uri']); - $bclUri = trim($values['backchannel_logout_uri']); + $bclUri = trim((string)$values['backchannel_logout_uri']); $values['backchannel_logout_uri'] = empty($bclUri) ? null : $bclUri; + $scopes = is_array($values['scopes']) ? $values['scopes'] : []; + // openid scope is mandatory $values['scopes'] = array_unique( array_merge( - $values['scopes'], + $scopes, ['openid'] ) ); @@ -149,44 +154,53 @@ public function getValues($returnType = null, ?array $controls = null): array return $values; } - public function setDefaults($data, bool $erase = false) + /** + * @throws Exception + */ + public function setDefaults($data, bool $erase = false): ClientForm { if (! is_array($data)) { - if ($data instanceof \Traversable) { + if ($data instanceof Traversable) { $data = iterator_to_array($data); } else { $data = (array) $data; } } - $data['redirect_uri'] = implode("\n", $data['redirect_uri']); + /** @var string[] $redirectUris */ + $redirectUris = is_array($data['redirect_uri']) ? $data['redirect_uri'] : []; + $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'])) { - $data['allowed_origin'] = implode("\n", $data['allowed_origin']); + /** @var string[] $allowedOrigins */ + $allowedOrigins = is_array($data['allowed_origin']) ? $data['allowed_origin'] : []; + $data['allowed_origin'] = implode("\n", $allowedOrigins); } else { $data['allowed_origin'] = ''; } - $data['post_logout_redirect_uri'] = implode("\n", $data['post_logout_redirect_uri']); + /** @var string[] $postLogoutRedirectUris */ + $postLogoutRedirectUris = is_array($data['post_logout_redirect_uri']) ? $data['post_logout_redirect_uri'] : []; + $data['post_logout_redirect_uri'] = implode("\n", $postLogoutRedirectUris); - $data['scopes'] = array_intersect($data['scopes'], array_keys($this->getScopes())); + $scopes = is_array($data['scopes']) ? $data['scopes'] : []; + $data['scopes'] = array_intersect($scopes, array_keys($this->getScopes())); return parent::setDefaults($data, $erase); } + /** + * @throws Exception + */ protected function buildForm(): void { $this->getElementPrototype()->addAttributes(['class' => 'ui form']); - /** @psalm-suppress InvalidPropertyAssignmentValue According to docs this is fine. */ - $this->onValidate[] = [$this, 'validateRedirectUri']; - /** @psalm-suppress InvalidPropertyAssignmentValue According to docs this is fine. */ - $this->onValidate[] = [$this, 'validateAllowedOrigin']; - /** @psalm-suppress InvalidPropertyAssignmentValue According to docs this is fine. */ - $this->onValidate[] = [$this, 'validatePostLogoutRedirectUri']; - /** @psalm-suppress InvalidPropertyAssignmentValue According to docs this is fine. */ - $this->onValidate[] = [$this, 'validateBackChannelLogoutUri']; + $this->onValidate[] = $this->validateRedirectUri(...); + $this->onValidate[] = $this->validateAllowedOrigin(...); + $this->onValidate[] = $this->validatePostLogoutRedirectUri(...); + $this->onValidate[] = $this->validateBackChannelLogoutUri(...); $this->setMethod('POST'); $this->addComponent(new CsrfProtection('{oidc:client:csrf_error}'), Form::PROTECTOR_ID); @@ -223,29 +237,25 @@ protected function buildForm(): void $this->addText('backchannel_logout_uri', '{oidc:client:backchannel_logout_uri}'); } + /** + * @throws Exception + */ protected function getScopes(): array { - return array_map(function ($item) { - return $item['description']; - }, $this->configurationService->getOpenIDScopes()); + return array_map( + fn(array $item): mixed => $item['description'], + $this->moduleConfig->getOpenIDScopes() + ); } /** - * @param string $text * @return string[] */ protected function convertTextToArrayWithLinesAsValues(string $text): array { return array_filter( preg_split("/[\t\r\n]+/", $text), - /** - * @param string $line - * - * @return bool - */ - function (string $line) { - return !empty(trim($line)); - } + fn(string $line): bool => !empty(trim($line)) ); } } diff --git a/src/Form/Controls/CsrfProtection.php b/src/Forms/Controls/CsrfProtection.php similarity index 71% rename from src/Form/Controls/CsrfProtection.php rename to src/Forms/Controls/CsrfProtection.php index 24e8054e..7d9afb28 100644 --- a/src/Form/Controls/CsrfProtection.php +++ b/src/Forms/Controls/CsrfProtection.php @@ -1,5 +1,7 @@ setOmitted() @@ -44,9 +55,12 @@ public function __construct($errorMessage) $this->sspSession = Session::getSessionFromRequest(); } + /** + * @throws Exception + */ public function getToken(): string { - $token = $this->sspSession->getData('form_csrf', 'token'); + $token = (string)$this->sspSession->getData('form_csrf', 'token'); if (!$token) { $token = Random::generate(); diff --git a/src/Services/ConfigurationService.php b/src/ModuleConfig.php similarity index 55% rename from src/Services/ConfigurationService.php rename to src/ModuleConfig.php index 15ae9a74..8b5cee2f 100644 --- a/src/Services/ConfigurationService.php +++ b/src/ModuleConfig.php @@ -1,5 +1,7 @@ [ 'description' => 'openid', ], @@ -49,40 +78,59 @@ class ConfigurationService ]; /** - * @throws ConfigurationError + * @var Configuration Module configuration instance created form module config file. */ - public function __construct() - { - $this->validateConfiguration(); + private readonly Configuration $moduleConfig; + /** + * @var Configuration SimpleSAMLphp configuration instance. + */ + private readonly Configuration $sspConfig; + + /** + * @throws Exception + */ + 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. + ) { + $this->moduleConfig = Configuration::loadFromArray( + array_merge(Configuration::getConfig($fileName)->toArray(), $overrides) + ); + + $this->sspConfig = Configuration::getInstance(); + + $this->validate(); } /** * @throws Exception */ - public function getSimpleSAMLConfiguration(): Configuration + public function sspConfig(): Configuration { - return Configuration::getInstance(); + return $this->sspConfig; } /** * @throws Exception */ - public function getOpenIDConnectConfiguration(): Configuration + public function config(): Configuration { - return Configuration::getConfig('module_oidc.php'); + return $this->moduleConfig; } public function getSimpleSAMLSelfURLHost(): string { + // TODO mivanci Create bridge to SSP utility classes return (new HTTP())->getSelfURLHost(); } public function getOpenIdConnectModuleURL(string $path = null): string { + // TODO mivanci Create bridge to SSP utility classes $base = Module::getModuleURL('oidc'); if ($path) { - $base .= "/{$path}"; + $base .= "/$path"; } return $base; @@ -101,7 +149,7 @@ public function getOpenIDScopes(): array */ public function getOpenIDPrivateScopes(): array { - return $this->getOpenIDConnectConfiguration()->getOptionalArray('scopes', []); + return $this->config()->getOptionalArray(self::OPTION_AUTH_CUSTOM_SCOPES, []); } /** @@ -110,7 +158,7 @@ public function getOpenIDPrivateScopes(): array * * @throws ConfigurationError */ - private function validateConfiguration() + private function validate(): void { $privateScopes = $this->getOpenIDPrivateScopes(); array_walk( @@ -120,10 +168,16 @@ private function validateConfiguration() */ function (array $scope, string $name): void { if (in_array($name, array_keys(self::$standardClaims), true)) { - throw new ConfigurationError('Can not overwrite protected scope: ' . $name, 'oidc_config.php'); + throw new ConfigurationError( + 'Can not overwrite protected scope: ' . $name, + self::DEFAULT_FILE_NAME + ); } if (!array_key_exists('description', $scope)) { - throw new ConfigurationError('Scope [' . $name . '] description not defined', 'module_oidc.php'); + throw new ConfigurationError( + 'Scope [' . $name . '] description not defined', + self::DEFAULT_FILE_NAME + ); } } ); @@ -147,6 +201,7 @@ function (array $scope, string $name): void { 'values containing supported ACRs for each auth source key.'); } + /** @psalm-suppress MixedAssignment */ foreach ($acrValues as $acrValue) { if (! is_string($acrValue)) { throw new ConfigurationError('Config option authSourcesToAcrValuesMap should have array ' . @@ -177,7 +232,10 @@ function (array $scope, string $name): void { public function getSigner(): Signer { /** @psalm-var class-string $signerClassname */ - $signerClassname = $this->getOpenIDConnectConfiguration()->getOptionalString('signer', Sha256::class); + $signerClassname = $this->config()->getOptionalString( + self::OPTION_TOKEN_SIGNER, + Sha256::class + ); $class = new ReflectionClass($signerClassname); $signer = $class->newInstance(); @@ -192,23 +250,37 @@ public function getSigner(): Signer /** * Return the path to the public certificate * @return string The file system path + * @throws Exception */ public function getCertPath(): string { - $certName = $this->getOpenIDConnectConfiguration()->getOptionalString('certificate', 'oidc_module.crt'); + $certName = $this->config()->getOptionalString( + self::OPTION_PKI_CERTIFICATE_FILENAME, + self::DEFAULT_PKI_CERTIFICATE_FILENAME + ); return (new Config())->getCertPath($certName); } /** * Get the path to the private key - * @return string + * @throws Exception */ public function getPrivateKeyPath(): string { - $keyName = $this->getOpenIDConnectConfiguration()->getOptionalString('privatekey', 'oidc_module.key'); + $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(); + } + /** * Get the path to the private key * @return ?string @@ -216,7 +288,7 @@ public function getPrivateKeyPath(): string */ public function getPrivateKeyPassPhrase(): ?string { - return $this->getOpenIDConnectConfiguration()->getOptionalString('pass_phrase', null); + return $this->config()->getOptionalString(self::OPTION_PKI_PRIVATE_KEY_PASSPHRASE, null); } /** @@ -227,7 +299,7 @@ public function getPrivateKeyPassPhrase(): ?string */ public function getAuthProcFilters(): array { - return $this->getOpenIDConnectConfiguration()->getOptionalArray('authproc.oidc', []); + return $this->config()->getOptionalArray(self::OPTION_AUTH_PROCESSING_FILTERS, []); } /** @@ -238,7 +310,7 @@ public function getAuthProcFilters(): array */ public function getAcrValuesSupported(): array { - return array_values($this->getOpenIDConnectConfiguration()->getOptionalArray('acrValuesSupported', [])); + return array_values($this->config()->getOptionalArray(self::OPTION_AUTH_ACR_VALUES_SUPPORTED, [])); } /** @@ -249,7 +321,7 @@ public function getAcrValuesSupported(): array */ public function getAuthSourcesToAcrValuesMap(): array { - return $this->getOpenIDConnectConfiguration()->getOptionalArray('authSourcesToAcrValuesMap', []); + return $this->config()->getOptionalArray(self::OPTION_AUTH_SOURCES_TO_ACR_VALUES_MAP, []); } /** @@ -258,8 +330,9 @@ public function getAuthSourcesToAcrValuesMap(): array */ public function getForcedAcrValueForCookieAuthentication(): ?string { - $value = $this->getOpenIDConnectConfiguration() - ->getOptionalValue('forcedAcrValueForCookieAuthentication', null); + /** @psalm-suppress MixedAssignment */ + $value = $this->config() + ->getOptionalValue(self::OPTION_AUTH_FORCED_ACR_VALUE_FOR_COOKIE_AUTHENTICATION, null); if (is_null($value)) { return null; @@ -267,4 +340,12 @@ public function getForcedAcrValueForCookieAuthentication(): ?string return (string) $value; } + + /** + * @throws Exception + */ + public function getUserIdentifierAttribute(): string + { + return $this->config()->getString(ModuleConfig::OPTION_AUTH_USER_IDENTIFIER_ATTRIBUTE); + } } diff --git a/src/Repositories/AbstractDatabaseRepository.php b/src/Repositories/AbstractDatabaseRepository.php index 5e9744d6..1c62ad82 100644 --- a/src/Repositories/AbstractDatabaseRepository.php +++ b/src/Repositories/AbstractDatabaseRepository.php @@ -1,5 +1,7 @@ config = Configuration::getOptionalConfig('module_oidc.php'); + $this->config = $this->moduleConfig->config(); + // TODO mivanci move to Doctrine DBAL stores $this->database = Database::getInstance(); - $this->configurationService = $configurationService; } - /** - * @return string|null - */ - abstract public function getTableName(); + abstract public function getTableName(): ?string; } diff --git a/src/Repositories/AccessTokenRepository.php b/src/Repositories/AccessTokenRepository.php index 7115b266..2aef8f19 100644 --- a/src/Repositories/AccessTokenRepository.php +++ b/src/Repositories/AccessTokenRepository.php @@ -1,5 +1,7 @@ database->applyPrefix(self::TABLE_NAME); @@ -47,13 +50,21 @@ public function getNewToken( string $authCodeId = null, array $requestedClaims = null ): AccessTokenEntityInterface { + if (!is_null($userIdentifier)) { + $userIdentifier = (string)$userIdentifier; + } + if (empty($userIdentifier)) { + $userIdentifier = null; + } return AccessTokenEntity::fromData($clientEntity, $scopes, $userIdentifier, $authCodeId, $requestedClaims); } /** * {@inheritdoc} + * @throws Error + * @throws JsonException */ - public function persistNewAccessToken(OAuth2AccessTokenEntityInterface $accessTokenEntity) + public function persistNewAccessToken(OAuth2AccessTokenEntityInterface $accessTokenEntity): void { if (!$accessTokenEntity instanceof AccessTokenEntity) { throw new Error('Invalid AccessTokenEntity'); @@ -73,6 +84,8 @@ public function persistNewAccessToken(OAuth2AccessTokenEntityInterface $accessTo /** * Find Access Token by id. + * @throws Exception + * @throws OidcServerException */ public function findById(string $tokenId): ?AccessTokenEntity { @@ -83,26 +96,29 @@ public function findById(string $tokenId): ?AccessTokenEntity ] ); - if (!$rows = $stmt->fetchAll()) { + if (empty($rows = $stmt->fetchAll())) { return null; } + /** @var array $data */ $data = current($rows); - $clientRepository = new ClientRepository($this->configurationService); - $data['client'] = $clientRepository->findById($data['client_id']); + $clientRepository = new ClientRepository($this->moduleConfig); + $data['client'] = $clientRepository->findById((string)$data['client_id']); return AccessTokenEntity::fromState($data); } /** * {@inheritdoc} + * @throws JsonException + * @throws OidcServerException */ - public function revokeAccessToken($tokenId) + public function revokeAccessToken($tokenId): void { $accessToken = $this->findById($tokenId); if (!$accessToken instanceof AccessTokenEntity) { - throw new \RuntimeException("AccessToken not found: {$tokenId}"); + throw new RuntimeException("AccessToken not found: $tokenId"); } $accessToken->revoke(); @@ -111,13 +127,14 @@ public function revokeAccessToken($tokenId) /** * {@inheritdoc} + * @throws OidcServerException */ - public function isAccessTokenRevoked($tokenId) + public function isAccessTokenRevoked($tokenId): bool { $accessToken = $this->findById($tokenId); if (!$accessToken) { - throw new \RuntimeException("AccessToken not found: {$tokenId}"); + throw new RuntimeException("AccessToken not found: $tokenId"); } return $accessToken->isRevoked(); @@ -125,6 +142,7 @@ public function isAccessTokenRevoked($tokenId) /** * Removes expired access tokens. + * @throws Exception */ public function removeExpired(): void { @@ -133,10 +151,10 @@ public function removeExpired(): void // Delete expired access tokens, but only if the corresponding refresh token is also expired. $this->database->write( - "DELETE FROM {$accessTokenTableName} WHERE expires_at < :now AND + "DELETE FROM $accessTokenTableName WHERE expires_at < :now AND NOT EXISTS ( SELECT 1 FROM {$refreshTokenTableName} - WHERE {$accessTokenTableName}.id = {$refreshTokenTableName}.access_token_id AND expires_at > :now + WHERE $accessTokenTableName.id = $refreshTokenTableName.access_token_id AND expires_at > :now )", [ 'now' => TimestampGenerator::utc()->format('Y-m-d H:i:s'), @@ -144,6 +162,9 @@ public function removeExpired(): void ); } + /** + * @throws JsonException + */ private function update(AccessTokenEntity $accessTokenEntity): void { $stmt = sprintf( diff --git a/src/Repositories/AllowedOriginRepository.php b/src/Repositories/AllowedOriginRepository.php index 214781a5..961dd513 100644 --- a/src/Repositories/AllowedOriginRepository.php +++ b/src/Repositories/AllowedOriginRepository.php @@ -1,12 +1,14 @@ fetchAll()) { + if (empty($rows = $stmt->fetchAll())) { return null; } + /** @var array $data */ $data = current($rows); - $clientRepository = new ClientRepository($this->configurationService); - $data['client'] = $clientRepository->findById($data['client_id']); + $clientRepository = new ClientRepository($this->moduleConfig); + $data['client'] = $clientRepository->findById((string)$data['client_id']); return AuthCodeEntity::fromState($data); } /** * {@inheritdoc} + * @throws JsonException + * @throws Exception */ - public function revokeAuthCode($codeId) + public function revokeAuthCode($codeId): void { $authCode = $this->findById($codeId); if (!$authCode instanceof AuthCodeEntity) { - throw new \RuntimeException("AuthCode not found: {$codeId}"); + throw new RuntimeException("AuthCode not found: $codeId"); } $authCode->revoke(); @@ -99,13 +109,14 @@ public function revokeAuthCode($codeId) /** * {@inheritdoc} + * @throws Exception */ public function isAuthCodeRevoked($codeId): bool { $authCode = $this->findById($codeId); if (!$authCode instanceof AuthCodeEntity) { - throw new \RuntimeException("AuthCode not found: {$codeId}"); + throw new RuntimeException("AuthCode not found: $codeId"); } return $authCode->isRevoked(); @@ -113,6 +124,7 @@ public function isAuthCodeRevoked($codeId): bool /** * Removes expired auth codes. + * @throws Exception */ public function removeExpired(): void { @@ -125,9 +137,9 @@ public function removeExpired(): void } /** - * @return void + * @throws JsonException */ - private function update(AuthCodeEntity $authCodeEntity) + private function update(AuthCodeEntity $authCodeEntity): void { $stmt = sprintf( <<database->applyPrefix(self::TABLE_NAME); @@ -35,6 +36,7 @@ public function getTableName(): string /** * {@inheritdoc} * @throws OAuthServerException + * @throws JsonException */ public function getClientEntity($clientIdentifier) { @@ -54,6 +56,7 @@ public function getClientEntity($clientIdentifier) /** * @inheritDoc * @throws OAuthServerException + * @throws JsonException */ public function validateClient($clientIdentifier, $clientSecret, $grantType): bool { @@ -71,26 +74,36 @@ public function validateClient($clientIdentifier, $clientSecret, $grantType): bo } /** - * @param string $clientIdentifier - * @param ?string $owner restrict lookup to this owner - * @return ClientEntityInterface|null + * @throws OidcServerException + * @throws JsonException */ public function findById(string $clientIdentifier, ?string $owner = null): ?ClientEntityInterface { - list($query, $params) = $this->addOwnerWhereClause( + /** + * @var string $query + * @var array $params + */ + [$query, $params] = $this->addOwnerWhereClause( "SELECT * FROM {$this->getTableName()} WHERE id = :id", [ 'id' => $clientIdentifier, ], $owner ); + $stmt = $this->database->read($query, $params); - if (!$rows = $stmt->fetchAll()) { + if (empty($rows = $stmt->fetchAll())) { + return null; + } + + $row = current($rows); + + if (!is_array($row)) { return null; } - return ClientEntity::fromState(current($rows)); + return ClientEntity::fromState($row); } private function addOwnerWhereClause(string $query, array $params, ?string $owner = null): array @@ -108,10 +121,15 @@ private function addOwnerWhereClause(string $query, array $params, ?string $owne /** * @return ClientEntityInterface[] + * @throws OidcServerException|JsonException */ public function findAll(?string $owner = null): array { - list($query, $params) = $this->addOwnerWhereClause( + /** + * @var string $query + * @var array $params + */ + [$query, $params] = $this->addOwnerWhereClause( "SELECT * FROM {$this->getTableName()}", [], $owner @@ -123,6 +141,7 @@ public function findAll(?string $owner = null): array $clients = []; + /** @var array $state */ foreach ($stmt->fetchAll() as $state) { $clients[] = ClientEntity::fromState($state); } @@ -131,9 +150,6 @@ public function findAll(?string $owner = null): array } /** - * @param int $page - * @param string $query - * @param string|null $owner * @return array{numPages: int, currentPage: int, items: ClientEntityInterface[]} * @throws Exception */ @@ -145,19 +161,22 @@ public function findPaginated(int $page = 1, string $query = '', ?string $owner $numPages = $this->calculateNumOfPages($total, $limit); $page = $this->calculateCurrentPage($page, $numPages); $offset = $this->calculateOffset($page, $limit); - list($sqlQuery, $params) = $this->addOwnerWhereClause( + + /** + * @var string $sqlQuery + * @var array $params + */ + [$sqlQuery, $params] = $this->addOwnerWhereClause( "SELECT * FROM {$this->getTableName()} WHERE name LIKE :name", ['name' => '%' . $query . '%'], $owner ); $stmt = $this->database->read( - $sqlQuery . " ORDER BY name ASC LIMIT {$limit} OFFSET {$offset}", + $sqlQuery . " ORDER BY name ASC LIMIT $limit OFFSET $offset", $params ); - $clients = array_map(function ($state) { - return ClientEntity::fromState($state); - }, $stmt->fetchAll()); + $clients = array_map(fn(array $state) => ClientEntity::fromState($state), $stmt->fetchAll()); return [ 'numPages' => $numPages, @@ -210,7 +229,11 @@ public function add(ClientEntityInterface $client): void public function delete(ClientEntityInterface $client, ?string $owner = null): void { - list($sqlQuery, $params) = $this->addOwnerWhereClause( + /** + * @var string $sqlQuery + * @var array $params + */ + [$sqlQuery, $params] = $this->addOwnerWhereClause( "DELETE FROM {$this->getTableName()} WHERE id = :id", [ 'id' => $client->getIdentifier(), @@ -241,7 +264,12 @@ public function update(ClientEntityInterface $client, ?string $owner = null): vo , $this->getTableName() ); - list($sqlQuery, $params) = $this->addOwnerWhereClause( + + /** + * @var string $sqlQuery + * @var array $params + */ + [$sqlQuery, $params] = $this->addOwnerWhereClause( $stmt, $client->getState(), $owner @@ -254,7 +282,11 @@ public function update(ClientEntityInterface $client, ?string $owner = null): vo private function count(string $query, ?string $owner): int { - list($sqlQuery, $params) = $this->addOwnerWhereClause( + /** + * @var string $sqlQuery + * @var array $params + */ + [$sqlQuery, $params] = $this->addOwnerWhereClause( "SELECT COUNT(id) FROM {$this->getTableName()} WHERE name LIKE :name", ['name' => '%' . $query . '%'], $owner @@ -265,7 +297,7 @@ private function count(string $query, ?string $owner): int ); $stmt->execute(); - return (int) $stmt->fetchColumn(0); + return (int) $stmt->fetchColumn(); } /** @@ -273,26 +305,17 @@ private function count(string $query, ?string $owner): int */ private function getItemsPerPage(): int { - return $this->config->getOptionalIntegerRange('items_per_page', 1, 100, 20); + return $this->config + ->getOptionalIntegerRange(ModuleConfig::OPTION_ADMIN_UI_PAGINATION_ITEMS_PER_PAGE, 1, 100, 20); } - /** - * @param int $total - * @param int $limit - * @return int - */ private function calculateNumOfPages(int $total, int $limit): int { $numPages = (int)ceil($total / $limit); - return $numPages < 1 ? 1 : $numPages; + return max($numPages, 1); } - /** - * @param int $page - * @param int $numPages - * @return int - */ private function calculateCurrentPage(int $page, int $numPages): int { if ($page > $numPages) { @@ -306,13 +329,7 @@ private function calculateCurrentPage(int $page, int $numPages): int return $page; } - - /** - * @param int $page - * @param int $limit - * @return float|int - */ - private function calculateOffset(int $page, int $limit) + private function calculateOffset(int $page, int $limit): float|int { return ($page - 1) * $limit; } diff --git a/src/Repositories/CodeChallengeVerifiersRepository.php b/src/Repositories/CodeChallengeVerifiersRepository.php index fb759fbe..2f65be3d 100644 --- a/src/Repositories/CodeChallengeVerifiersRepository.php +++ b/src/Repositories/CodeChallengeVerifiersRepository.php @@ -1,21 +1,26 @@ codeChallengeVerifiers[$s256Verifier->getMethod()] = $s256Verifier; } @@ -33,7 +38,6 @@ public function getAll(): array } /** - * @param string $method * @return CodeChallengeVerifierInterface|null Verifier for the method or null if not supported. */ public function get(string $method): ?CodeChallengeVerifierInterface @@ -41,10 +45,6 @@ public function get(string $method): ?CodeChallengeVerifierInterface return $this->codeChallengeVerifiers[$method] ?? null; } - /** - * @param string $method - * @return bool - */ public function has(string $method): bool { return isset($this->codeChallengeVerifiers[$method]); diff --git a/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php b/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php index c699dc47..76bff94e 100644 --- a/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php +++ b/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php @@ -1,17 +1,18 @@ + * @copyright (c) 2018 Steve Rhoades + * @license http://opensource.org/licenses/MIT MIT + */ +interface IdentityProviderInterface extends RepositoryInterface +{ + public function getUserEntityByIdentifier(string $identifier): ?UserEntityInterface; +} diff --git a/src/Repositories/Interfaces/RefreshTokenRepositoryInterface.php b/src/Repositories/Interfaces/RefreshTokenRepositoryInterface.php index 18bf488f..cf30eb48 100644 --- a/src/Repositories/Interfaces/RefreshTokenRepositoryInterface.php +++ b/src/Repositories/Interfaces/RefreshTokenRepositoryInterface.php @@ -1,22 +1,21 @@ fetchAll()) { + if (empty($rows = $stmt->fetchAll())) { return null; } + /** @var array $data */ $data = current($rows); - $accessTokenRepository = new AccessTokenRepository($this->configurationService); - $data['access_token'] = ($accessTokenRepository)->findById($data['access_token_id']); + $accessTokenRepository = new AccessTokenRepository($this->moduleConfig); + $data['access_token'] = $accessTokenRepository->findById((string)$data['access_token_id']); return RefreshTokenEntity::fromState($data); } /** * {@inheritdoc} + * @throws OidcServerException */ - public function revokeRefreshToken($tokenId) + public function revokeRefreshToken($tokenId): void { $refreshToken = $this->findById($tokenId); if (!$refreshToken) { - throw new \RuntimeException("RefreshToken not found: {$tokenId}"); + throw new RuntimeException("RefreshToken not found: $tokenId"); } $refreshToken->revoke(); @@ -106,13 +115,14 @@ public function revokeRefreshToken($tokenId) /** * {@inheritdoc} + * @throws OidcServerException */ - public function isRefreshTokenRevoked($tokenId) + public function isRefreshTokenRevoked($tokenId): bool { $refreshToken = $this->findById($tokenId); if (!$refreshToken) { - throw new \RuntimeException("RefreshToken not found: {$tokenId}"); + throw new RuntimeException("RefreshToken not found: $tokenId"); } return $refreshToken->isRevoked(); @@ -120,6 +130,7 @@ public function isRefreshTokenRevoked($tokenId) /** * Removes expired refresh tokens. + * @throws Exception */ public function removeExpired(): void { diff --git a/src/Repositories/ScopeRepository.php b/src/Repositories/ScopeRepository.php index 25c342db..b3a2defc 100644 --- a/src/Repositories/ScopeRepository.php +++ b/src/Repositories/ScopeRepository.php @@ -1,5 +1,7 @@ configurationService->getOpenIDScopes(); + $scopes = $this->moduleConfig->getOpenIDScopes(); - if (false === \array_key_exists($identifier, $scopes)) { + if (false === array_key_exists($identifier, $scopes)) { return null; } + /** @var array $scope */ $scope = $scopes[$identifier]; + /** @var ?string $description */ $description = $scope['description'] ?? null; + /** @var ?string $icon */ $icon = $scope['icon'] ?? null; - $attributes = $scope['attributes'] ?? []; + /** @var string[] $claims */ + $claims = $scope['claims'] ?? []; - $scope = ScopeEntity::fromData( + return ScopeEntity::fromData( $identifier, $description, $icon, - $attributes + $claims ); - - return $scope; } /** @@ -65,13 +69,14 @@ public function finalizeScopes( $grantType, OAuth2ClientEntityInterface $clientEntity, $userIdentifier = null - ) { + ): array { if (!$clientEntity instanceof ClientEntity) { return []; } - return array_filter($scopes, function (ScopeEntityInterface $scope) use ($clientEntity) { - return \in_array($scope->getIdentifier(), $clientEntity->getScopes(), true); - }); + return array_filter( + $scopes, + fn(ScopeEntityInterface $scope) => in_array($scope->getIdentifier(), $clientEntity->getScopes(), true) + ); } } diff --git a/src/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php b/src/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php index 96b9c51b..4e2e1aa4 100644 --- a/src/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php +++ b/src/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php @@ -1,5 +1,7 @@ database->read( "SELECT * FROM {$this->getTableName()} WHERE id = :id", @@ -42,23 +48,30 @@ public function getUserEntityByIdentifier($identifier) ] ); - if (!$rows = $stmt->fetchAll()) { + if (empty($rows = $stmt->fetchAll())) { + return null; + } + + $row = current($rows); + + if (!is_array($row)) { return null; } - return UserEntity::fromState(current($rows)); + return UserEntity::fromState($row); } /** * {@inheritdoc} + * @throws Exception */ public function getUserEntityByUserCredentials( $username, $password, $grantType, OAuth2ClientEntityInterface $clientEntity - ) { - throw new \Exception('Not supported'); + ): ?UserEntityInterface { + throw new Exception('Not supported'); } public function add(UserEntity $userEntity): void @@ -73,9 +86,6 @@ public function add(UserEntity $userEntity): void ); } - /** - * @param \SimpleSAML\Module\oidc\Entity\UserEntity $userEntity - */ public function delete(UserEntity $user): void { $this->database->write( @@ -86,9 +96,6 @@ public function delete(UserEntity $user): void ); } - /** - * @param \SimpleSAML\Module\oidc\Entity\UserEntity $userEntity - */ public function update(UserEntity $user): void { $stmt = sprintf( diff --git a/src/Server/Associations/Interfaces/RelyingPartyAssociationInterface.php b/src/Server/Associations/Interfaces/RelyingPartyAssociationInterface.php index cd02a8e7..8baf4eb1 100644 --- a/src/Server/Associations/Interfaces/RelyingPartyAssociationInterface.php +++ b/src/Server/Associations/Interfaces/RelyingPartyAssociationInterface.php @@ -1,5 +1,7 @@ clientId = $clientId; - $this->userId = $userId; - $this->sessionId = $sessionId; - $this->backChannelLogoutUri = $backChannelLogoutUri; } public function getClientId(): string diff --git a/src/Server/AuthorizationServer.php b/src/Server/AuthorizationServer.php index 3601e613..e1481ced 100644 --- a/src/Server/AuthorizationServer.php +++ b/src/Server/AuthorizationServer.php @@ -1,9 +1,13 @@ getOrFail(StateRule::class)->getValue(); + /** @var string $redirectUri */ $redirectUri = $resultBag->getOrFail(RedirectUriRule::class)->getValue(); foreach ($this->enabledGrantTypes as $grantType) { diff --git a/src/Server/Exceptions/OidcServerException.php b/src/Server/Exceptions/OidcServerException.php index 0aac337d..732a26b0 100644 --- a/src/Server/Exceptions/OidcServerException.php +++ b/src/Server/Exceptions/OidcServerException.php @@ -1,37 +1,44 @@ payload = $payload; } @@ -304,7 +311,7 @@ public function setState(string $state = null): void } /** - * Generate a HTTP response. + * Generate an HTTP response. * * @param ResponseInterface $response * @param bool $useFragment True if errors should be in the URI fragment instead of query string. Note @@ -318,6 +325,7 @@ public function generateHttpResponse( $useFragment = false, $jsonOptions = 0 ): ResponseInterface { + /** @var array $headers */ $headers = $this->getHttpHeaders(); $payload = $this->getPayload(); @@ -329,16 +337,16 @@ public function generateHttpResponse( $paramSeparator = '#'; } - $this->redirectUri .= (\strstr($this->redirectUri, $paramSeparator) === false) ? $paramSeparator : '&'; + $this->redirectUri .= (!str_contains($this->redirectUri, $paramSeparator)) ? $paramSeparator : '&'; - return $response->withStatus(302)->withHeader('Location', $this->redirectUri . \http_build_query($payload)); + return $response->withStatus(302)->withHeader('Location', $this->redirectUri . http_build_query($payload)); } foreach ($headers as $header => $content) { $response = $response->withHeader($header, $content); } - $responseBody = \json_encode($payload, $jsonOptions) ?: 'JSON encoding of payload failed'; + $responseBody = json_encode($payload, $jsonOptions) ?: 'JSON encoding of payload failed'; $response->getBody()->write($responseBody); diff --git a/src/Server/Grants/AuthCodeGrant.php b/src/Server/Grants/AuthCodeGrant.php index b8e6a185..9af558d1 100644 --- a/src/Server/Grants/AuthCodeGrant.php +++ b/src/Server/Grants/AuthCodeGrant.php @@ -1,30 +1,39 @@ setRefreshTokenRepository($refreshTokenRepository); $this->authCodeTTL = $authCodeTTL; - $this->requestRulesManager = $requestRulesManager; - $this->configurationService = $configurationService; if (in_array('sha256', hash_algos(), true)) { $s256Verifier = new S256Verifier(); @@ -109,34 +171,28 @@ public function __construct( $this->codeChallengeVerifiers[$plainVerifier->getMethod()] = $plainVerifier; } - /** - * @param OAuth2ClientEntityInterface $client - * @return bool - */ protected function shouldCheckPkce(OAuth2ClientEntityInterface $client): bool { - return $this->requireCodeChallengeForPublicClients && - ! $client->isConfidential(); + return $this->requireCodeChallengeForPublicClients && !$client->isConfidential(); } /** * Check if the authorization request is OIDC candidate (can respond with ID token). - * - * @param OAuth2AuthorizationRequest $authorizationRequest - * @return bool */ public function isOidcCandidate( OAuth2AuthorizationRequest $authorizationRequest ): bool { // Check if the scopes contain 'oidc' scope - return (bool) Arr::find($authorizationRequest->getScopes(), function (ScopeEntityInterface $scope) { - return $scope->getIdentifier() === 'openid'; - }); + return (bool) Arr::find( + $authorizationRequest->getScopes(), + fn(ScopeEntityInterface $scope) => $scope->getIdentifier() === 'openid' + ); } /** * @inheritDoc * @throws OAuthServerException + * @throws JsonException */ public function completeAuthorizationRequest( OAuth2AuthorizationRequest $authorizationRequest @@ -151,22 +207,21 @@ public function completeAuthorizationRequest( /** * This is reimplementation of OAuth2 completeAuthorizationRequest method with addition of nonce handling. * - * @param AuthorizationRequest $authorizationRequest - * @return RedirectResponse * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException + * @throws JsonException */ public function completeOidcAuthorizationRequest( AuthorizationRequest $authorizationRequest ): RedirectResponse { $user = $authorizationRequest->getUser(); - if ($user instanceof UserEntityInterface === false) { - throw new LogicException('An instance of UserEntityInterface should be set on the ' . + if ($user instanceof UserEntity === false) { + throw new LogicException('An instance of UserEntity should be set on the ' . 'AuthorizationRequest'); } $finalRedirectUri = $authorizationRequest->getRedirectUri() - ?? $this->getClientRedirectUri($authorizationRequest); + ?? $this->getClientRedirectUri($authorizationRequest); if ($authorizationRequest->isAuthorizationApproved() !== true) { // The user denied the client, redirect them back with an error @@ -204,12 +259,7 @@ public function completeOidcAuthorizationRequest( 'session_id' => $authorizationRequest->getSessionId(), ]; - $jsonPayload = json_encode($payload); - - if ($jsonPayload === false) { - throw new LogicException('An error was encountered when JSON encoding the authorization ' . - 'request response'); - } + $jsonPayload = json_encode($payload, JSON_THROW_ON_ERROR); $response = new RedirectResponse(); $response->setRedirectUri( @@ -226,13 +276,7 @@ public function completeOidcAuthorizationRequest( } /** - * @param DateInterval $authCodeTTL - * @param OAuth2ClientEntityInterface $client - * @param string $userIdentifier - * @param string $redirectUri - * @param array $scopes - * @param string|null $nonce - * @return AuthCodeEntityInterface + * @param ScopeEntityInterface[] $scopes * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException */ @@ -244,10 +288,18 @@ protected function issueOidcAuthCode( array $scopes = [], string $nonce = null ): AuthCodeEntityInterface { - $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; + 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)) { + throw OidcServerException::serverError('Unexpected auth code entity type.'); + } + $authCode->setExpiryDateTime((new DateTimeImmutable())->add($authCodeTTL)); $authCode->setClient($client); $authCode->setUserIdentifier($userIdentifier); @@ -298,37 +350,44 @@ protected function getClientRedirectUri(OAuth2AuthorizationRequest $authorizatio * Reimplementation respondToAccessTokenRequest because of nonce feature. * * @param ServerRequestInterface $request - * @param ResponseTypeInterface $responseType - * @param DateInterval $accessTokenTTL - * - * @throws OAuthServerException + * @param ResponseTypeInterface $responseType + * @param DateInterval $accessTokenTTL * * @return ResponseTypeInterface * * TODO refactor to request checkers + * @throws OAuthServerException + * @throws JsonException + * @throws JsonException + * @throws JsonException + * */ public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, DateInterval $accessTokenTTL ): ResponseTypeInterface { - list($clientId) = $this->getClientCredentials($request); + [$clientId] = $this->getClientCredentials($request); - $client = $this->getClientEntityOrFail($clientId, $request); + $client = $this->getClientEntityOrFail((string)$clientId, $request); // Only validate the client if it is confidential if ($client->isConfidential()) { $this->validateClient($request); } - $encryptedAuthCode = $this->getRequestParameter('code', $request, null); + $encryptedAuthCode = $this->getRequestParameter('code', $request); if ($encryptedAuthCode === null) { throw OAuthServerException::invalidRequest('code'); } try { - $authCodePayload = json_decode($this->decrypt($encryptedAuthCode)); + /** + * @noinspection PhpUndefinedClassInspection + * @psalm-var AuthCodePayloadObject $authCodePayload + */ + $authCodePayload = json_decode($this->decrypt($encryptedAuthCode), null, 512, JSON_THROW_ON_ERROR); $this->validateAuthorizationCode($authCodePayload, $client, $request); @@ -342,7 +401,7 @@ public function respondToAccessTokenRequest( throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code', $e); } - $codeVerifier = $this->getRequestParameter('code_verifier', $request, null); + $codeVerifier = $this->getRequestParameter('code_verifier', $request); // If a code challenge isn't present but a code verifier is, reject the request to block PKCE downgrade attack if ($this->shouldCheckPkce($client) && empty($authCodePayload->code_challenge) && $codeVerifier !== null) { @@ -383,16 +442,17 @@ public function respondToAccessTokenRequest( throw OAuthServerException::serverError( sprintf( 'Unsupported code challenge method `%s`', - $authCodePayload->code_challenge_method + ($authCodePayload->code_challenge_method ?? '') ) ); } } } + /** @var array $claims */ $claims = property_exists($authCodePayload, 'claims') ? - json_decode(json_encode($authCodePayload->claims), true) - : null; + json_decode(json_encode($authCodePayload->claims, JSON_THROW_ON_ERROR), true, 512, JSON_THROW_ON_ERROR) + : null; // Issue and persist new access token $accessToken = $this->issueAccessToken( @@ -426,7 +486,7 @@ public function respondToAccessTokenRequest( if ( $responseType instanceof AcrResponseTypeInterface && property_exists($authCodePayload, 'acr') && - $authCodePayload->acr !== null + ! empty($authCodePayload->acr) ) { $responseType->setAcr($authCodePayload->acr); } @@ -434,7 +494,7 @@ public function respondToAccessTokenRequest( if ( $responseType instanceof SessionIdResponseTypeInterface && property_exists($authCodePayload, 'session_id') && - $authCodePayload->session_id !== null + ! empty($authCodePayload->session_id) ) { $responseType->setSessionId($authCodePayload->session_id); } @@ -449,6 +509,9 @@ public function respondToAccessTokenRequest( $responseType->setRefreshToken($refreshToken); } } + if (! is_a($this->authCodeRepository, AuthCodeRepositoryInterface::class)) { + throw OidcServerException::serverError('Unexpected auth code repository entity type.'); + } // Revoke used auth code $this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id); @@ -459,20 +522,38 @@ public function respondToAccessTokenRequest( /** * Reimplementation because of private parent access * - * @param stdClass $authCodePayload + * @param object $authCodePayload * @param OAuth2ClientEntityInterface $client * @param ServerRequestInterface $request * @throws OAuthServerException + * @throws OidcServerException */ protected function validateAuthorizationCode( - stdClass $authCodePayload, + object $authCodePayload, OAuth2ClientEntityInterface $client, ServerRequestInterface $request ): void { + /** + * @noinspection PhpUndefinedClassInspection + * @psalm-var AuthCodePayloadObject $authCodePayload + */ + if (!property_exists($authCodePayload, 'auth_code_id')) { throw OAuthServerException::invalidRequest('code', 'Authorization code malformed'); } + if (! is_a($this->authCodeRepository, AuthCodeRepositoryInterface::class)) { + throw OidcServerException::serverError('Unexpected auth code repository entity type.'); + } + + if (! is_a($this->accessTokenRepository, AccessTokenRepositoryInterface::class)) { + throw OidcServerException::serverError('Unexpected access token repository entity type.'); + } + + if (! is_a($this->refreshTokenRepository, RefreshTokenRepositoryInterface::class)) { + throw OidcServerException::serverError('Unexpected refresh token repository entity type.'); + } + if (time() > $authCodePayload->expire_time) { throw OAuthServerException::invalidGrant('Authorization code has expired'); } @@ -489,13 +570,16 @@ protected function validateAuthorizationCode( } // The redirect URI is required in this request - $redirectUri = $this->getRequestParameter('redirect_uri', $request, null); + $redirectUri = $this->getRequestParameter('redirect_uri', $request); if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) { throw OAuthServerException::invalidRequest('redirect_uri'); } if ($authCodePayload->redirect_uri !== $redirectUri) { - throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI'); + throw OAuthServerException::invalidRequest( + 'redirect_uri', + 'Invalid redirect URI or not the same as in authorization request' + ); } } @@ -517,7 +601,7 @@ public function validateAuthorizationRequestWithCheckerResultBag( ScopeOfflineAccessRule::class, ]; - // Since we have already validated redirect_uri and we have state, make it available for other checkers. + // Since we have already validated redirect_uri, and we have state, make it available for other checkers. $this->requestRulesManager->predefineResultBag($resultBag); /** @var string $redirectUri */ @@ -539,7 +623,7 @@ public function validateAuthorizationRequestWithCheckerResultBag( $resultBag = $this->requestRulesManager->check($request, $rulesToExecute); - /** @var array $scopes */ + /** @var ScopeEntityInterface[] $scopes */ $scopes = $resultBag->getOrFail(ScopeRule::class)->getValue(); $oAuth2AuthorizationRequest = new OAuth2AuthorizationRequest(); @@ -556,6 +640,7 @@ public function validateAuthorizationRequestWithCheckerResultBag( if ($shouldCheckPkce) { /** @var string $codeChallenge */ $codeChallenge = $resultBag->getOrFail(CodeChallengeRule::class)->getValue(); + /** @var string $codeChallengeMethod */ $codeChallengeMethod = $resultBag->getOrFail(CodeChallengeMethodRule::class)->getValue(); $oAuth2AuthorizationRequest->setCodeChallenge($codeChallenge); @@ -581,7 +666,11 @@ public function validateAuthorizationRequestWithCheckerResultBag( $requestClaims = $resultBag->get(RequestedClaimsRule::class); if (null !== $requestClaims) { - $authorizationRequest->setClaims($requestClaims->getValue()); + /** @var ?array $requestClaimValues */ + $requestClaimValues = $requestClaims->getValue(); + if (is_array($requestClaimValues)) { + $authorizationRequest->setClaims($requestClaimValues); + } } /** @var array|null $acrValues */ @@ -602,6 +691,10 @@ protected function issueRefreshToken( OAuth2AccessTokenEntityInterface $accessToken, string $authCodeId = null ): ?RefreshTokenEntityInterface { + if (! is_a($this->refreshTokenRepository, RefreshTokenRepositoryInterface::class)) { + throw OidcServerException::serverError('Unexpected refresh token repository entity type.'); + } + $refreshToken = $this->refreshTokenRepository->getNewRefreshToken(); if ($refreshToken === null) { diff --git a/src/Server/Grants/ImplicitGrant.php b/src/Server/Grants/ImplicitGrant.php index 3b39e17d..f19f6a24 100644 --- a/src/Server/Grants/ImplicitGrant.php +++ b/src/Server/Grants/ImplicitGrant.php @@ -1,9 +1,12 @@ accessTokenRepository = $accessTokenRepository; - $this->idTokenBuilder = $idTokenBuilder; } /** @@ -56,14 +63,18 @@ public function __construct( public function canRespondToAuthorizationRequest(ServerRequestInterface $request): bool { $queryParams = $request->getQueryParams(); - if (!isset($queryParams['response_type']) || !isset($queryParams['client_id'])) { + if ( + !isset($queryParams['response_type']) || + !is_string($queryParams['response_type']) || + !isset($queryParams['client_id']) + ) { return false; } $responseType = explode(" ", $queryParams['response_type']); return in_array('id_token', $responseType, true) && - ! in_array('code', $responseType, true); // ...avoid triggering hybrid flow + ! in_array('code', $responseType, true); // ...avoid triggering hybrid flow } /** @@ -93,7 +104,7 @@ public function validateAuthorizationRequestWithCheckerResultBag( ResultBagInterface $resultBag ): OAuth2AuthorizationRequest { $oAuth2AuthorizationRequest = - parent::validateAuthorizationRequestWithCheckerResultBag($request, $resultBag); + parent::validateAuthorizationRequestWithCheckerResultBag($request, $resultBag); $rulesToExecute = [ RequestParameterRule::class, @@ -114,7 +125,7 @@ public function validateAuthorizationRequestWithCheckerResultBag( $authorizationRequest = AuthorizationRequest::fromOAuth2AuthorizationRequest($oAuth2AuthorizationRequest); // nonce existence is validated using a rule, so we can get it from there. - $authorizationRequest->setNonce($resultBag->getOrFail(RequiredNonceRule::class)->getValue()); + $authorizationRequest->setNonce((string)$resultBag->getOrFail(RequiredNonceRule::class)->getValue()); $maxAge = $resultBag->get(MaxAgeRule::class); if (null !== $maxAge) { @@ -123,8 +134,13 @@ public function validateAuthorizationRequestWithCheckerResultBag( $requestClaims = $resultBag->get(RequestedClaimsRule::class); if (null !== $requestClaims) { - $authorizationRequest->setClaims($requestClaims->getValue()); + /** @var ?array $requestClaimValues */ + $requestClaimValues = $requestClaims->getValue(); + if (is_array($requestClaimValues)) { + $authorizationRequest->setClaims($requestClaimValues); + } } + /** @var bool $addClaimsToIdToken */ $addClaimsToIdToken = ($resultBag->getOrFail(AddClaimsToIdTokenRule::class))->getValue(); $authorizationRequest->setAddClaimsToIdToken($addClaimsToIdToken); @@ -143,12 +159,13 @@ public function validateAuthorizationRequestWithCheckerResultBag( * @throws UniqueTokenIdentifierConstraintViolationException * @throws OAuthServerException * @throws OidcServerException + * @throws Exception */ private function completeOidcAuthorizationRequest(AuthorizationRequest $authorizationRequest): ResponseTypeInterface { $user = $authorizationRequest->getUser(); - if ($user instanceof UserEntityInterface === false) { + if ($user instanceof UserEntity === false) { throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); } diff --git a/src/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php b/src/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php index e0794c1f..4a3d5259 100644 --- a/src/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php +++ b/src/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php @@ -1,5 +1,7 @@ requestRulesManager->predefineResultBag($resultBag); /** @var string $redirectUri */ @@ -73,7 +135,7 @@ public function validateAuthorizationRequestWithCheckerResultBag( $resultBag = $this->requestRulesManager->check($request, $rulesToExecute); - /** @var array $scopes */ + /** @var 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 568f1afc..686a9194 100644 --- a/src/Server/Grants/RefreshTokenGrant.php +++ b/src/Server/Grants/RefreshTokenGrant.php @@ -1,19 +1,77 @@ getRequestParameter('refresh_token', $request); - if (\is_null($encryptedRefreshToken)) { + if (is_null($encryptedRefreshToken)) { throw OidcServerException::invalidGrant('Failed to verify `refresh_token`'); } @@ -24,18 +82,26 @@ protected function validateOldRefreshToken(ServerRequestInterface $request, $cli throw OidcServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e); } - $refreshTokenData = \json_decode($refreshToken, true); + $refreshTokenData = json_decode($refreshToken, true, 512, JSON_THROW_ON_ERROR); + + if (! is_array($refreshTokenData)) { + throw OidcServerException::invalidRefreshToken('Refresh token has unexpected type'); + } if ($refreshTokenData['client_id'] !== $clientId) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_CLIENT_FAILED, $request)); - throw OidcServerException::invalidRefreshToken('Token is not linked to client'); + throw OidcServerException::invalidRefreshToken('Refresh token is not linked to client'); } - if ($refreshTokenData['expire_time'] < \time()) { - throw OidcServerException::invalidRefreshToken('Token has expired'); + if ($refreshTokenData['expire_time'] < time()) { + throw OidcServerException::invalidRefreshToken('Refresh token has expired'); } - if ($this->refreshTokenRepository->isRefreshTokenRevoked($refreshTokenData['refresh_token_id']) === true) { - throw OidcServerException::invalidRefreshToken('Token has been revoked'); + if ( + $this->refreshTokenRepository->isRefreshTokenRevoked( + (string)$refreshTokenData['refresh_token_id'] + ) === true + ) { + throw OidcServerException::invalidRefreshToken('Refresh token has been revoked'); } return $refreshTokenData; diff --git a/src/Server/Grants/Traits/IssueAccessTokenTrait.php b/src/Server/Grants/Traits/IssueAccessTokenTrait.php index 3f057be1..2a4f11b9 100644 --- a/src/Server/Grants/Traits/IssueAccessTokenTrait.php +++ b/src/Server/Grants/Traits/IssueAccessTokenTrait.php @@ -1,5 +1,7 @@ accessTokenRepository, AccessTokenRepositoryInterface::class)) { + throw OidcServerException::serverError( + 'Access token repository does not implement ' . AccessTokenRepositoryInterface::class + ); + } + $accessToken = $this->accessTokenRepository->getNewToken( $client, $scopes, @@ -81,7 +89,6 @@ protected function issueAccessToken( * Generate a new unique identifier. * * @param int $length - * * @throws OAuthServerException * * @return string diff --git a/src/Server/LogoutHandlers/BackChannelLogoutHandler.php b/src/Server/LogoutHandlers/BackChannelLogoutHandler.php index d3989312..1b0dda42 100644 --- a/src/Server/LogoutHandlers/BackChannelLogoutHandler.php +++ b/src/Server/LogoutHandlers/BackChannelLogoutHandler.php @@ -1,5 +1,7 @@ logoutTokenBuilder = $logoutTokenBuilder ?? new LogoutTokenBuilder(); - $this->loggerService = $loggerService ?? new LoggerService(); } /** - * @param array $relyingPartyAssociations + * @param array $relyingPartyAssociations * @param HandlerStack|null $handlerStack For easier testing * @throws OAuthServerException */ @@ -42,16 +38,16 @@ public function handle(array $relyingPartyAssociations, HandlerStack $handlerSta $pool = new Pool($client, $this->logoutRequestsGenerator($relyingPartyAssociations), [ 'concurrency' => 5, - 'fulfilled' => function (Response $response, $index) { + 'fulfilled' => function (Response $response, mixed $index) { // this is delivered each successful response $successMessage = "Backhannel Logout (index $index) - success, status: {$response->getStatusCode()} " . "{$response->getReasonPhrase()}"; $this->loggerService->notice($successMessage); }, - 'rejected' => function (GuzzleException $reason, $index) { + 'rejected' => function (GuzzleException $reason, mixed $index) { // this is delivered each failed request $errorMessage = "Backhannel Logout (index $index) - error, reason: {$reason->getCode()} " . - "{$reason->getMessage()}, exception type: " . get_class($reason); + "{$reason->getMessage()}, exception type: " . $reason::class; $this->loggerService->error($errorMessage); }, ]); @@ -64,7 +60,7 @@ public function handle(array $relyingPartyAssociations, HandlerStack $handlerSta } /** - * @param array $relyingPartyAssociations + * @param array $relyingPartyAssociations * @return Generator * @throws OAuthServerException */ @@ -74,7 +70,7 @@ protected function logoutRequestsGenerator(array $relyingPartyAssociations): Gen foreach ($relyingPartyAssociations as $association) { if ($association->getBackChannelLogoutUri() !== null) { $logMessage = "Backhannel Logout (index $index) - preparing request to: " . - $association->getBackChannelLogoutUri(); + ($association->getBackChannelLogoutUri() ?? ''); $this->loggerService->notice($logMessage); $index++; diff --git a/src/Server/RequestTypes/AuthorizationRequest.php b/src/Server/RequestTypes/AuthorizationRequest.php index 7c5f5a38..0b99bc3a 100644 --- a/src/Server/RequestTypes/AuthorizationRequest.php +++ b/src/Server/RequestTypes/AuthorizationRequest.php @@ -1,5 +1,7 @@ nonce; } - /** - * @param string $nonce - */ public function setNonce(string $nonce): void { $this->nonce = $nonce; @@ -119,17 +118,11 @@ public function getAddClaimsToIdToken(): bool return $this->addClaimsToIdToken; } - /** - * @param bool $addClaimsToIdToken - */ public function setAddClaimsToIdToken(bool $addClaimsToIdToken): void { $this->addClaimsToIdToken = $addClaimsToIdToken; } - /** - * @param string $responseType - */ public function setResponseType(string $responseType): void { $this->responseType = $responseType; diff --git a/src/Server/RequestTypes/LogoutRequest.php b/src/Server/RequestTypes/LogoutRequest.php index 12e4db0b..c3170497 100644 --- a/src/Server/RequestTypes/LogoutRequest.php +++ b/src/Server/RequestTypes/LogoutRequest.php @@ -1,48 +1,38 @@ idTokenHint = $idTokenHint; - $this->postLogoutRedirectUri = $postLogoutRedirectUri; - $this->state = $state; - $this->uiLocales = $uiLocales; } public function getIdTokenHint(): ?UnencryptedToken diff --git a/src/Server/ResponseTypes/IdTokenResponse.php b/src/Server/ResponseTypes/IdTokenResponse.php index e98ab5d7..e6aa60c2 100644 --- a/src/Server/ResponseTypes/IdTokenResponse.php +++ b/src/Server/ResponseTypes/IdTokenResponse.php @@ -1,5 +1,7 @@ identityProvider = $identityProvider; - $this->idTokenBuilder = $idTokenBuilder; - $this->configurationService = $configurationService; + $this->privateKey = $privateKey; } /** @@ -82,8 +93,17 @@ protected function getExtraParams(AccessTokenEntityInterface $accessToken): arra throw new RuntimeException('AccessToken must be ' . AccessTokenEntity::class); } - /** @var UserEntityInterface $userEntity */ - $userEntity = $this->identityProvider->getUserEntityByIdentifier($accessToken->getUserIdentifier()); + $userIdentifier = $accessToken->getUserIdentifier(); + + if (empty($userIdentifier)) { + throw OidcServerException::accessDenied('No user identifier present in AccessToken.'); + } + + $userEntity = $this->identityProvider->getUserEntityByIdentifier((string)$userIdentifier); + + if (empty($userEntity)) { + throw OidcServerException::accessDenied('No user available for provided user identifier.'); + } $token = $this->idTokenBuilder->build( $userEntity, diff --git a/src/Server/ResponseTypes/Interfaces/AcrResponseTypeInterface.php b/src/Server/ResponseTypes/Interfaces/AcrResponseTypeInterface.php index 8e010c1e..98cddc65 100644 --- a/src/Server/ResponseTypes/Interfaces/AcrResponseTypeInterface.php +++ b/src/Server/ResponseTypes/Interfaces/AcrResponseTypeInterface.php @@ -1,5 +1,7 @@ accessTokenRepository = $accessTokenRepository; + $this->setPublicKey($publicKey); } /** * Set the public key * * @param CryptKey $key + * @throws Exception */ - public function setPublicKey(CryptKey $key) + public function setPublicKey(CryptKey $key): void { $this->publicKey = $key; @@ -58,17 +76,18 @@ public function setPublicKey(CryptKey $key) /** * Initialise the JWT configuration. + * @throws Exception */ - protected function initJwtConfiguration() + protected function initJwtConfiguration(): void { $this->jwtConfiguration = Configuration::forSymmetricSigner( new Sha256(), - InMemory::empty() + InMemory::plainText('empty', 'empty') ); /** @psalm-suppress ArgumentTypeCoercion */ $this->jwtConfiguration->setValidationConstraints( - new StrictValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))), + new StrictValidAt(new SystemClock(new DateTimeZone(date_default_timezone_get()))), new SignedWith( new Sha256(), InMemory::plainText($this->publicKey->getKeyContents(), $this->publicKey->getPassPhrase() ?? '') @@ -86,16 +105,17 @@ public function validateAuthorization(ServerRequestInterface $request): ServerRe if ($request->hasHeader('authorization')) { $header = $request->getHeader('authorization'); - $jwt = \trim((string) \preg_replace('/^\s*Bearer\s/', '', $header[0])); + $jwt = trim((string) preg_replace('/^\s*Bearer\s/', '', $header[0])); } elseif ( strcasecmp($request->getMethod(), 'POST') === 0 && is_array($parsedBody = $request->getParsedBody()) && - isset($parsedBody['access_token']) + isset($parsedBody['access_token']) && + is_string($parsedBody['access_token']) ) { $jwt = $parsedBody['access_token']; } - if ($jwt === null) { + if (!is_string($jwt)) { throw OidcServerException::accessDenied('Missing Authorization header or access_token request body param.'); } @@ -111,14 +131,14 @@ public function validateAuthorization(ServerRequestInterface $request): ServerRe // Attempt to validate the JWT $constraints = $this->jwtConfiguration->validationConstraints(); $this->jwtConfiguration->validator()->assert($token, ...$constraints); - } catch (RequiredConstraintsViolated $exception) { + } catch (RequiredConstraintsViolated) { throw OidcServerException::accessDenied('Access token could not be verified'); } $claims = $token->claims(); - if (is_null($jti = $claims->get('jti')) || empty($jti)) { - throw OidcServerException::accessDenied('Access token malformed (jti missing)'); + if (is_null($jti = $claims->get('jti')) || empty($jti) || !is_string($jti)) { + throw OidcServerException::accessDenied('Access token malformed (jti missing or unexpected type)'); } // Check if token has been revoked @@ -140,9 +160,22 @@ public function validateAuthorization(ServerRequestInterface $request): ServerRe * @param mixed $aud * * @return array|string + * @throws OidcServerException */ - protected function convertSingleRecordAudToString($aud) + protected function convertSingleRecordAudToString(mixed $aud): array|string { - return \is_array($aud) && \count($aud) === 1 ? $aud[0] : $aud; + if (is_string($aud)) { + return $aud; + } + + if (is_array($aud) && !empty($aud)) { + if (count($aud) === 1) { + return (string)$aud[0]; + } else { + return $aud; + } + } + + throw OidcServerException::accessDenied('Unexpected sub claim value.'); } } diff --git a/src/Services/AuthContextService.php b/src/Services/AuthContextService.php index e2a9e74a..b230b2a5 100644 --- a/src/Services/AuthContextService.php +++ b/src/Services/AuthContextService.php @@ -1,8 +1,13 @@ configurationService = $configurationService; - $this->authSimpleFactory = $authSimpleFactory; + public function __construct( + private readonly ModuleConfig $moduleConfig, + private readonly AuthSimpleFactory $authSimpleFactory + ) { } public function isSspAdmin(): bool { + // TODO mivanci make bridge to SSP utility classes (search for SSP namespace through the codebase) return (new Auth())->isAdmin(); } + /** + * @throws Exception + * @throws \Exception + */ public function getAuthUserId(): string { $simple = $this->authenticate(); - $userIdAttr = $this->configurationService->getOpenIDConnectConfiguration()->getString('useridattr'); - return (new Attributes())->getExpectedAttribute($simple->getAttributes(), $userIdAttr); + $userIdAttr = $this->moduleConfig->getUserIdentifierAttribute(); + return (string)(new Attributes())->getExpectedAttribute($simple->getAttributes(), $userIdAttr); } /** @@ -56,21 +54,22 @@ public function getAuthUserId(): string * @param string $neededPermission The permissions needed * @throws \Exception thrown if permissions are not enabled or user is missing the needed entitlements */ - public function requirePermission(string $neededPermission) + public function requirePermission(string $neededPermission): void { $auth = $this->authenticate(); - $permissions = $this->configurationService - ->getOpenIDConnectConfiguration() - ->getOptionalConfigItem('permissions', null); - /** @psalm-suppress DocblockTypeContradiction */ + $permissions = $this->moduleConfig + ->config() + ->getOptionalConfigItem(ModuleConfig::OPTION_ADMIN_UI_PERMISSIONS, null); + if (is_null($permissions) || !$permissions->hasValue('attribute')) { - throw new \RuntimeException('Permissions not enabled'); + throw new RuntimeException('Permissions not enabled'); } if (!$permissions->hasValue($neededPermission)) { - throw new \RuntimeException('No permission defined for ' . $neededPermission); + throw new RuntimeException('No permission defined for ' . $neededPermission); } $attributeName = $permissions->getString('attribute'); + /** @var string[] $entitlements */ $entitlements = $auth->getAttributes()[$attributeName] ?? []; $neededEntitlements = $permissions->getArrayizeString($neededPermission); foreach ($entitlements as $entitlement) { @@ -78,9 +77,12 @@ public function requirePermission(string $neededPermission) return; } } - throw new \RuntimeException('Missing entitlement for ' . $neededPermission); + throw new RuntimeException('Missing entitlement for ' . $neededPermission); } + /** + * @throws \Exception + */ private function authenticate(): Simple { $simple = $this->authSimpleFactory->getDefaultAuthSource(); diff --git a/src/Services/AuthProcService.php b/src/Services/AuthProcService.php index d7930ee9..a9e68cb8 100644 --- a/src/Services/AuthProcService.php +++ b/src/Services/AuthProcService.php @@ -1,43 +1,40 @@ configurationService = $configurationService; $this->loadFilters(); } /** * Load filters defined in configuration. - * @throws \Exception + * @throws Exception */ private function loadFilters(): void { - $oidcAuthProcFilters = $this->configurationService->getAuthProcFilters(); + $oidcAuthProcFilters = $this->moduleConfig->getAuthProcFilters(); $this->filters = $this->parseFilterList($oidcAuthProcFilters); } @@ -46,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 array Array of ProcessingFilter objects. + * @throws Exception */ private function parseFilterList(array $filterSrc): array { @@ -59,25 +56,35 @@ private function parseFilterList(array $filterSrc): array } if (!is_array($filterConfig)) { - throw new \Exception('Invalid authentication processing filter configuration: ' . + throw new Exception('Invalid authentication processing filter configuration: ' . 'One of the filters wasn\'t a string or an array.'); } if (!array_key_exists('class', $filterConfig)) { - throw new \Exception('Authentication processing filter without name given.'); + throw new Exception('Authentication processing filter without name given.'); + } + + if (!is_string($filterConfig['class'])) { + throw new Exception('Invalid class value for authentication processing filter configuration.'); } $className = Module::resolveClass( $filterConfig['class'], 'Auth\Process', - '\SimpleSAML\Auth\ProcessingFilter' + '\\' . ProcessingFilter::class ); + if (!is_a($className, ProcessingFilter::class, true)) { + throw new Exception( + 'Authentication processing filter class configuration is not ProcessingFilter instance.' + ); + } + $filterConfig['%priority'] = $priority; unset($filterConfig['class']); /** - * @psalm-suppress InvalidStringClass + * @psalm-suppress UnsafeInstantiation */ $parsedFilters[] = new $className($filterConfig, null); } @@ -87,9 +94,6 @@ private function parseFilterList(array $filterSrc): array /** * Process given state array. - * - * @param array $state - * @return array */ public function processState(array $state): array { diff --git a/src/Services/AuthenticationService.php b/src/Services/AuthenticationService.php index 93bd484f..1fcb006b 100644 --- a/src/Services/AuthenticationService.php +++ b/src/Services/AuthenticationService.php @@ -1,5 +1,7 @@ userRepository = $userRepository; - $this->authSimpleFactory = $authSimpleFactory; - $this->authProcService = $authProcService; $this->clientRepository = $clientRepository; - $this->oidcOpenIdProviderMetadataService = $oidcOpenIdProviderMetadataService; - $this->sessionService = $sessionService; - $this->claimTranslatorExtractor = $claimTranslatorExtractor; - $this->userIdAttr = $userIdAttr; + $this->userIdAttr = $moduleConfig->getUserIdentifierAttribute(); } /** - * @param ServerRequestInterface $request - * @param array $loginParams - * @param bool $forceAuthn - * @return UserEntity * @throws Error\Exception * @throws Error\AuthSource * @throws Error\BadRequest @@ -112,16 +95,21 @@ public function getAuthenticateUser( $state = $this->prepareStateArray($authSimple, $oidcClient, $request); $state = $this->authProcService->processState($state); + + if (!isset($state['Attributes']) || !is_array($state['Attributes'])) { + throw new Error\Exception('State array does not contain any attributes.'); + } + $claims = $state['Attributes']; - if (!array_key_exists($this->userIdAttr, $claims)) { + if (!array_key_exists($this->userIdAttr, $claims) || !is_array($claims[$this->userIdAttr])) { $attr = implode(', ', array_keys($claims)); throw new Error\Exception( 'Attribute `useridattr` doesn\'t exists in claims. Available attributes are: ' . $attr ); } - $userId = $claims[$this->userIdAttr][0]; + $userId = (string)$claims[$this->userIdAttr][0]; $user = $this->userRepository->getUserEntityByIdentifier($userId); if (!$user) { @@ -137,12 +125,6 @@ public function getAuthenticateUser( return $user; } - /** - * @param Simple $authSimple - * @param ClientEntityInterface $client - * @param ServerRequestInterface $request - * @return array - */ private function prepareStateArray( Simple $authSimple, ClientEntityInterface $client, @@ -151,17 +133,23 @@ private function prepareStateArray( $state = $authSimple->getAuthDataArray(); $state['Oidc'] = [ - 'OpenIdProviderMetadata' => $this->oidcOpenIdProviderMetadataService->getMetadata(), - 'RelyingPartyMetadata' => array_filter($client->toArray(), function (string $key) { - return $key !== 'secret'; - }, ARRAY_FILTER_USE_KEY), - 'AuthorizationRequestParameters' => array_filter($request->getQueryParams(), function (string $key) { - $relevantAuthzParams = ['response_type', 'client_id', 'redirect_uri', 'scope', 'code_challenge_method']; - return in_array($key, $relevantAuthzParams); - }, ARRAY_FILTER_USE_KEY), + 'OpenIdProviderMetadata' => $this->opMetadataService->getMetadata(), + 'RelyingPartyMetadata' => array_filter( + $client->toArray(), + fn(/** @param array-key $key */ $key) => $key !== 'secret', + ARRAY_FILTER_USE_KEY + ), + 'AuthorizationRequestParameters' => array_filter( + $request->getQueryParams(), + function (/** @param array-key $key */ $key) { + $authzParams = ['response_type', 'client_id', 'redirect_uri', 'scope', 'code_challenge_method']; + return in_array($key, $authzParams); + }, + ARRAY_FILTER_USE_KEY + ), ]; - // Source and destination entity IDs, useful for eg. F-ticks logging... + // Source and destination entity IDs, useful for e.g. F-ticks logging... $state['Source'] = ['entityid' => $state['Oidc']['OpenIdProviderMetadata']['issuer']]; $state['Destination'] = ['entityid' => $state['Oidc']['RelyingPartyMetadata']['id']]; @@ -187,8 +175,6 @@ public function getSessionId(): ?string /** * Store Relying Party Association to the current session. - * @param ClientEntityInterface $oidcClient - * @param UserEntity $user * @throws Exception */ protected function addRelyingPartyAssociation(ClientEntityInterface $oidcClient, UserEntity $user): void @@ -199,7 +185,7 @@ protected function addRelyingPartyAssociation(ClientEntityInterface $oidcClient, $this->sessionService->addRelyingPartyAssociation( new RelyingPartyAssociation( $oidcClient->getIdentifier(), - $claims['sub'] ?? $user->getIdentifier(), + (string)($claims['sub'] ?? $user->getIdentifier()), $this->getSessionId(), $oidcClient->getBackChannelLogoutUri() ) diff --git a/src/Services/Container.php b/src/Services/Container.php index 7e2fccaf..215642a0 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -1,5 +1,7 @@ services[ConfigurationService::class] = $configurationService; + $moduleConfig = new ModuleConfig(); + $this->services[ModuleConfig::class] = $moduleConfig; $loggerService = new LoggerService(); $this->services[LoggerService::class] = $loggerService; - $clientRepository = new ClientRepository($configurationService); + $clientRepository = new ClientRepository($moduleConfig); $this->services[ClientRepository::class] = $clientRepository; - $userRepository = new UserRepository($configurationService); + $userRepository = new UserRepository($moduleConfig); $this->services[UserRepository::class] = $userRepository; - $authCodeRepository = new AuthCodeRepository($configurationService); + $authCodeRepository = new AuthCodeRepository($moduleConfig); $this->services[AuthCodeRepository::class] = $authCodeRepository; - $refreshTokenRepository = new RefreshTokenRepository($configurationService); + $refreshTokenRepository = new RefreshTokenRepository($moduleConfig); $this->services[RefreshTokenRepository::class] = $refreshTokenRepository; - $accessTokenRepository = new AccessTokenRepository($configurationService); + $accessTokenRepository = new AccessTokenRepository($moduleConfig); $this->services[AccessTokenRepository::class] = $accessTokenRepository; - $scopeRepository = new ScopeRepository($configurationService); + $scopeRepository = new ScopeRepository($moduleConfig); $this->services[ScopeRepository::class] = $scopeRepository; - $allowedOriginRepository = new AllowedOriginRepository($configurationService); + $allowedOriginRepository = new AllowedOriginRepository($moduleConfig); $this->services[AllowedOriginRepository::class] = $allowedOriginRepository; $database = Database::getInstance(); @@ -123,16 +125,16 @@ public function __construct() $databaseLegacyOAuth2Import = new DatabaseLegacyOAuth2Import($clientRepository); $this->services[DatabaseLegacyOAuth2Import::class] = $databaseLegacyOAuth2Import; - $authSimpleFactory = new AuthSimpleFactory($clientRepository, $configurationService); + $authSimpleFactory = new AuthSimpleFactory($clientRepository, $moduleConfig); $this->services[AuthSimpleFactory::class] = $authSimpleFactory; - $authContextService = new AuthContextService($configurationService, $authSimpleFactory); + $authContextService = new AuthContextService($moduleConfig, $authSimpleFactory); $this->services[AuthContextService::class] = $authContextService; - $formFactory = new FormFactory($configurationService); + $formFactory = new FormFactory($moduleConfig); $this->services[FormFactory::class] = $formFactory; - $jsonWebKeySetService = new JsonWebKeySetService($configurationService); + $jsonWebKeySetService = new JsonWebKeySetService($moduleConfig); $this->services[JsonWebKeySetService::class] = $jsonWebKeySetService; $session = Session::getSessionFromRequest(); @@ -147,17 +149,17 @@ public function __construct() $templateFactory = new TemplateFactory($simpleSAMLConfiguration); $this->services[TemplateFactory::class] = $templateFactory; - $authProcService = new AuthProcService($configurationService); + $authProcService = new AuthProcService($moduleConfig); $this->services[AuthProcService::class] = $authProcService; - $oidcOpenIdProviderMetadataService = new OidcOpenIdProviderMetadataService($configurationService); - $this->services[OidcOpenIdProviderMetadataService::class] = $oidcOpenIdProviderMetadataService; + $opMetadataService = new OpMetadataService($moduleConfig); + $this->services[OpMetadataService::class] = $opMetadataService; $metadataStorageHandler = MetaDataStorageHandler::getMetadataHandler(); $this->services[MetaDataStorageHandler::class] = $metadataStorageHandler; $claimTranslatorExtractor = (new ClaimTranslatorExtractorFactory( - $configurationService + $moduleConfig ))->build(); $this->services[ClaimTranslatorExtractor::class] = $claimTranslatorExtractor; @@ -166,25 +168,17 @@ public function __construct() $authSimpleFactory, $authProcService, $clientRepository, - $oidcOpenIdProviderMetadataService, + $opMetadataService, $sessionService, $claimTranslatorExtractor, - $oidcModuleConfiguration->getOptionalString('useridattr', 'uid') + $moduleConfig, ); $this->services[AuthenticationService::class] = $authenticationService; $codeChallengeVerifiersRepository = new CodeChallengeVerifiersRepository(); $this->services[CodeChallengeVerifiersRepository::class] = $codeChallengeVerifiersRepository; - $publicKeyPath = $configurationService->getCertPath(); - $privateKeyPath = $configurationService->getPrivateKeyPath(); - $passPhrase = $configurationService->getPrivateKeyPassPhrase(); - - $cryptKeyFactory = new CryptKeyFactory( - $publicKeyPath, - $privateKeyPath, - $passPhrase - ); + $cryptKeyFactory = new CryptKeyFactory($moduleConfig); $requestRules = [ new StateRule(), @@ -201,29 +195,29 @@ public function __construct() new AddClaimsToIdTokenRule(), new RequiredNonceRule(), new ResponseTypeRule(), - new IdTokenHintRule($configurationService, $cryptKeyFactory), + new IdTokenHintRule($moduleConfig, $cryptKeyFactory), new PostLogoutRedirectUriRule($clientRepository), new UiLocalesRule(), new AcrValuesRule(), - new ScopeOfflineAccessRule($configurationService), + new ScopeOfflineAccessRule(), ]; $requestRuleManager = new RequestRulesManager($requestRules, $loggerService); $this->services[RequestRulesManager::class] = $requestRuleManager; $accessTokenDuration = new DateInterval( - $configurationService->getOpenIDConnectConfiguration()->getString('accessTokenDuration') + $moduleConfig->config()->getString(ModuleConfig::OPTION_TOKEN_ACCESS_TOKEN_TTL) ); $authCodeDuration = new DateInterval( - $configurationService->getOpenIDConnectConfiguration()->getString('authCodeDuration') + $moduleConfig->config()->getString(ModuleConfig::OPTION_TOKEN_AUTHORIZATION_CODE_TTL) ); $refreshTokenDuration = new DateInterval( - $configurationService->getOpenIDConnectConfiguration()->getString('refreshTokenDuration') + $moduleConfig->config()->getString(ModuleConfig::OPTION_TOKEN_REFRESH_TOKEN_TTL) ); $publicKey = $cryptKeyFactory->buildPublicKey(); $privateKey = $cryptKeyFactory->buildPrivateKey(); $encryptionKey = (new Config())->getSecretSalt(); - $jsonWebTokenBuilderService = new JsonWebTokenBuilderService($configurationService); + $jsonWebTokenBuilderService = new JsonWebTokenBuilderService($moduleConfig); $this->services[JsonWebTokenBuilderService::class] = $jsonWebTokenBuilderService; $idTokenBuilder = new IdTokenBuilder($jsonWebTokenBuilderService, $claimTranslatorExtractor); @@ -232,15 +226,14 @@ public function __construct() $logoutTokenBuilder = new LogoutTokenBuilder($jsonWebTokenBuilderService); $this->services[LogoutTokenBuilder::class] = $logoutTokenBuilder; - $sessionLogoutTicketStoreDb = new SessionLogoutTicketStoreDb($database); - $this->services[SessionLogoutTicketStoreDb::class] = $sessionLogoutTicketStoreDb; + $sessionLogoutTicketStoreDb = new LogoutTicketStoreDb($database); + $this->services[LogoutTicketStoreDb::class] = $sessionLogoutTicketStoreDb; - $sessionLogoutTicketStoreBuilder = new SessionLogoutTicketStoreBuilder($sessionLogoutTicketStoreDb); - $this->services[SessionLogoutTicketStoreBuilder::class] = $sessionLogoutTicketStoreBuilder; + $sessionLogoutTicketStoreBuilder = new LogoutTicketStoreBuilder($sessionLogoutTicketStoreDb); + $this->services[LogoutTicketStoreBuilder::class] = $sessionLogoutTicketStoreBuilder; $idTokenResponseFactory = new IdTokenResponseFactory( $userRepository, - $this->services[ConfigurationService::class], $this->services[IdTokenBuilder::class], $privateKey, $encryptionKey @@ -254,7 +247,6 @@ public function __construct() $refreshTokenDuration, $authCodeDuration, $requestRuleManager, - $configurationService ); $this->services[AuthCodeGrant::class] = $authCodeGrantFactory->build(); @@ -291,7 +283,7 @@ public function __construct() ); $this->services[AuthorizationServer::class] = $authorizationServerFactory->build(); - $bearerTokenValidator = new BearerTokenValidator($accessTokenRepository); + $bearerTokenValidator = new BearerTokenValidator($accessTokenRepository, $publicKey); $this->services[BearerTokenValidator::class] = $bearerTokenValidator; $resourceServerFactory = new ResourceServerFactory( @@ -305,7 +297,7 @@ public function __construct() /** * @inheritdoc */ - public function get($id) + public function get(string $id): mixed { if (false === $this->has($id)) { throw new class ($id) extends Exception implements NotFoundExceptionInterface { @@ -322,7 +314,7 @@ public function __construct(string $id) /** * @inheritdoc */ - public function has($id): bool + public function has(string $id): bool { return array_key_exists($id, $this->services); } diff --git a/src/Services/DatabaseLegacyOAuth2Import.php b/src/Services/DatabaseLegacyOAuth2Import.php index b14852ea..260478b6 100644 --- a/src/Services/DatabaseLegacyOAuth2Import.php +++ b/src/Services/DatabaseLegacyOAuth2Import.php @@ -1,5 +1,7 @@ clientRepository = $clientRepository; } /** - * @psalm-suppress UndefinedClass UndefinedMethod - * - * @return void + * @psalm-suppress UndefinedClass, MixedAssignment, MixedArrayAccess, MixedArgument + * @throws OidcServerException|JsonException */ - public function import() + public function import(): void { if (!class_exists('\SimpleSAML\Modules\OAuth2\Repositories\ClientRepository')) { return; diff --git a/src/Services/DatabaseMigration.php b/src/Services/DatabaseMigration.php index d6a2853a..f3d5da2a 100644 --- a/src/Services/DatabaseMigration.php +++ b/src/Services/DatabaseMigration.php @@ -1,5 +1,7 @@ versionsTableName(); $this->database->write( - "CREATE TABLE IF NOT EXISTS {$versionsTablename} (version VARCHAR(191) PRIMARY KEY NOT NULL)" + "CREATE TABLE IF NOT EXISTS $versionsTablename (version VARCHAR(191) PRIMARY KEY NOT NULL)" ); return $this->database - ->read("SELECT version FROM ${versionsTablename}") + ->read("SELECT version FROM $versionsTablename") ->fetchAll(PDO::FETCH_COLUMN, 0); } @@ -66,57 +68,57 @@ public function migrate(): void if (!in_array('20180305180300', $versions, true)) { $this->version20180305180300(); - $this->database->write("INSERT INTO ${versionsTablename} (version) VALUES ('20180305180300')"); + $this->database->write("INSERT INTO $versionsTablename (version) VALUES ('20180305180300')"); } if (!in_array('20180425203400', $versions, true)) { $this->version20180425203400(); - $this->database->write("INSERT INTO ${versionsTablename} (version) VALUES ('20180425203400')"); + $this->database->write("INSERT INTO $versionsTablename (version) VALUES ('20180425203400')"); } if (!in_array('20200517071100', $versions, true)) { $this->version20200517071100(); - $this->database->write("INSERT INTO ${versionsTablename} (version) VALUES ('20200517071100')"); + $this->database->write("INSERT INTO $versionsTablename (version) VALUES ('20200517071100')"); } if (!in_array('20200901163000', $versions, true)) { $this->version20200901163000(); - $this->database->write("INSERT INTO ${versionsTablename} (version) VALUES ('20200901163000')"); + $this->database->write("INSERT INTO $versionsTablename (version) VALUES ('20200901163000')"); } if (!in_array('20210714113000', $versions, true)) { $this->version20210714113000(); - $this->database->write("INSERT INTO ${versionsTablename} (version) VALUES ('20210714113000')"); + $this->database->write("INSERT INTO $versionsTablename (version) VALUES ('20210714113000')"); } if (!in_array('20210823141300', $versions, true)) { $this->version20210823141300(); - $this->database->write("INSERT INTO ${versionsTablename} (version) VALUES ('20210823141300')"); + $this->database->write("INSERT INTO $versionsTablename (version) VALUES ('20210823141300')"); } if (!in_array('20210827111300', $versions, true)) { $this->version20210827111300(); - $this->database->write("INSERT INTO ${versionsTablename} (version) VALUES ('20210827111300')"); + $this->database->write("INSERT INTO $versionsTablename (version) VALUES ('20210827111300')"); } if (!in_array('20210902113500', $versions, true)) { $this->version20210902113500(); - $this->database->write("INSERT INTO ${versionsTablename} (version) VALUES ('20210902113500')"); + $this->database->write("INSERT INTO $versionsTablename (version) VALUES ('20210902113500')"); } if (!in_array('20210908143500', $versions, true)) { $this->version20210908143500(); - $this->database->write("INSERT INTO ${versionsTablename} (version) VALUES ('20210908143500')"); + $this->database->write("INSERT INTO $versionsTablename (version) VALUES ('20210908143500')"); } if (!in_array('20210916153400', $versions, true)) { $this->version20210916153400(); - $this->database->write("INSERT INTO ${versionsTablename} (version) VALUES ('20210916153400')"); + $this->database->write("INSERT INTO $versionsTablename (version) VALUES ('20210916153400')"); } if (!in_array('20210916173400', $versions, true)) { $this->version20210916173400(); - $this->database->write("INSERT INTO ${versionsTablename} (version) VALUES ('20210916173400')"); + $this->database->write("INSERT INTO $versionsTablename (version) VALUES ('20210916173400')"); } } @@ -128,11 +130,11 @@ private function versionsTableName(): string /** * @return void */ - private function version20180305180300() + private function version20180305180300(): void { $userTablename = $this->database->applyPrefix(UserRepository::TABLE_NAME); $this->database->write(<<< EOT - CREATE TABLE ${userTablename} ( + CREATE TABLE $userTablename ( id VARCHAR(191) PRIMARY KEY NOT NULL, claims TEXT, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -143,7 +145,7 @@ private function version20180305180300() $clientTableName = $this->database->applyPrefix(ClientRepository::TABLE_NAME); $this->database->write(<<< EOT - CREATE TABLE ${clientTableName} ( + CREATE TABLE $clientTableName ( id VARCHAR(191) PRIMARY KEY NOT NULL, secret VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, @@ -159,17 +161,17 @@ private function version20180305180300() $fkAccessTokenUser = $this->generateIdentifierName([$accessTokenTableName, 'user_id'], 'fk'); $fkAccessTokenClient = $this->generateIdentifierName([$accessTokenTableName, 'client_id'], 'fk'); $this->database->write(<<< EOT - CREATE TABLE ${accessTokenTableName} ( + CREATE TABLE $accessTokenTableName ( id VARCHAR(191) PRIMARY KEY NOT NULL, scopes TEXT, expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, user_id VARCHAR(191) NOT NULL, client_id VARCHAR(191) NOT NULL, is_revoked BOOLEAN NOT NULL DEFAULT false, - CONSTRAINT {$fkAccessTokenUser} FOREIGN KEY (user_id) - REFERENCES ${userTablename} (id) ON DELETE CASCADE, - CONSTRAINT {$fkAccessTokenClient} FOREIGN KEY (client_id) - REFERENCES ${clientTableName} (id) ON DELETE CASCADE + CONSTRAINT $fkAccessTokenUser FOREIGN KEY (user_id) + REFERENCES $userTablename (id) ON DELETE CASCADE, + CONSTRAINT $fkAccessTokenClient FOREIGN KEY (client_id) + REFERENCES $clientTableName (id) ON DELETE CASCADE ) EOT ); @@ -177,13 +179,13 @@ private function version20180305180300() $refreshTokenTableName = $this->database->applyPrefix(RefreshTokenRepository::TABLE_NAME); $fkRefreshTokenAccessToken = $this->generateIdentifierName([$refreshTokenTableName, 'access_token_id'], 'fk'); $this->database->write(<<< EOT - CREATE TABLE ${refreshTokenTableName} ( + CREATE TABLE $refreshTokenTableName ( id VARCHAR(191) PRIMARY KEY NOT NULL, expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, access_token_id VARCHAR(191) NOT NULL, is_revoked BOOLEAN NOT NULL DEFAULT false, - CONSTRAINT {$fkRefreshTokenAccessToken} FOREIGN KEY (access_token_id) - REFERENCES ${accessTokenTableName} (id) ON DELETE CASCADE + CONSTRAINT $fkRefreshTokenAccessToken FOREIGN KEY (access_token_id) + REFERENCES $accessTokenTableName (id) ON DELETE CASCADE ) EOT ); @@ -192,7 +194,7 @@ private function version20180305180300() $fkAuthCodeUser = $this->generateIdentifierName([$authCodeTableName, 'user_id'], 'fk'); $fkAuthCodeClient = $this->generateIdentifierName([$authCodeTableName, 'client_id'], 'fk'); $this->database->write(<<< EOT - CREATE TABLE ${authCodeTableName} ( + CREATE TABLE $authCodeTableName ( id VARCHAR(191) PRIMARY KEY NOT NULL, scopes TEXT, expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -200,10 +202,10 @@ private function version20180305180300() client_id VARCHAR(191) NOT NULL, is_revoked BOOLEAN NOT NULL DEFAULT false, redirect_uri TEXT NOT NULL, - CONSTRAINT {$fkAuthCodeUser} FOREIGN KEY (user_id) - REFERENCES ${userTablename} (id) ON DELETE CASCADE, - CONSTRAINT {$fkAuthCodeClient} FOREIGN KEY (client_id) - REFERENCES ${clientTableName} (id) ON DELETE CASCADE + CONSTRAINT $fkAuthCodeUser FOREIGN KEY (user_id) + REFERENCES $userTablename (id) ON DELETE CASCADE, + CONSTRAINT $fkAuthCodeClient FOREIGN KEY (client_id) + REFERENCES $clientTableName (id) ON DELETE CASCADE ) EOT ); @@ -212,7 +214,7 @@ private function version20180305180300() /** * @return void */ - private function version20180425203400() + private function version20180425203400(): void { $clientTableName = $this->database->applyPrefix(ClientRepository::TABLE_NAME); $this->database->write(<<< EOT @@ -222,7 +224,7 @@ private function version20180425203400() ); } - private function version20200517071100() + private function version20200517071100(): void { $clientTableName = $this->database->applyPrefix(ClientRepository::TABLE_NAME); $this->database->write(<<< EOT @@ -255,7 +257,7 @@ private function version20210902113500(): void /** * Add auth_code_id column to access token and refresh token tables */ - protected function version20210714113000() + protected function version20210714113000(): void { $tableName = $this->database->applyPrefix(AccessTokenRepository::TABLE_NAME); $this->database->write(<<< EOT @@ -275,7 +277,7 @@ protected function version20210714113000() /** * Add requested claims to authorization token */ - protected function version20210823141300() + protected function version20210823141300(): void { $tableName = $this->database->applyPrefix(AccessTokenRepository::TABLE_NAME); $this->database->write(<<< EOT @@ -296,12 +298,12 @@ protected function version20210827111300(): void $fkAllowedOriginClient = $this->generateIdentifierName([$allowedOriginTableName, 'client_id'], 'fk'); $this->database->write(<<< EOT - CREATE TABLE ${allowedOriginTableName} ( + CREATE TABLE $allowedOriginTableName ( client_id VARCHAR(191) NOT NULL, origin VARCHAR(191) NOT NULL, - CONSTRAINT {$pkAllowedOriginClient} PRIMARY KEY (client_id, origin), - CONSTRAINT {$fkAllowedOriginClient} FOREIGN KEY (client_id) - REFERENCES ${clientTableName} (id) ON DELETE CASCADE + CONSTRAINT $pkAllowedOriginClient PRIMARY KEY (client_id, origin), + CONSTRAINT $fkAllowedOriginClient FOREIGN KEY (client_id) + REFERENCES $clientTableName (id) ON DELETE CASCADE ) EOT ); @@ -323,7 +325,7 @@ protected function version20210908143500(): void /** * Add backchannel_logout_uri to client */ - protected function version20210916153400() + protected function version20210916153400(): void { $clientTableName = $this->database->applyPrefix(ClientRepository::TABLE_NAME); $this->database->write(<<< EOT @@ -336,12 +338,12 @@ protected function version20210916153400() /** * Add logout_ticket table */ - protected function version20210916173400() + protected function version20210916173400(): void { - $tableName = $this->database->applyPrefix(SessionLogoutTicketStoreDb::TABLE_NAME); + $tableName = $this->database->applyPrefix(LogoutTicketStoreDb::TABLE_NAME); $this->database->write( <<< EOT - CREATE TABLE ${tableName} ( + CREATE TABLE $tableName ( sid VARCHAR(191) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) @@ -349,12 +351,13 @@ protected function version20210916173400() ); } + /** + * @param string[] $columnNames + */ private function generateIdentifierName(array $columnNames, string $prefix = '', int $maxSize = 30): string { - $hash = implode('', array_map(function ($column) { - return dechex(crc32($column)); - }, $columnNames)); + $hash = implode('', array_map(fn($column) => dechex(crc32($column)), $columnNames)); - return mb_strtoupper(mb_substr("{$prefix}_{$hash}", 0, $maxSize)); + return mb_strtoupper(mb_substr("{$prefix}_$hash", 0, $maxSize)); } } diff --git a/src/Services/IdTokenBuilder.php b/src/Services/IdTokenBuilder.php index 6e68873d..ad03b38c 100644 --- a/src/Services/IdTokenBuilder.php +++ b/src/Services/IdTokenBuilder.php @@ -1,5 +1,7 @@ jsonWebTokenBuilderService = $jsonWebTokenBuilderService; - $this->claimExtractor = $claimExtractor; } /** @@ -85,29 +82,42 @@ public function build( ); $claims = array_merge($additionalClaims, $claims); - + /** + * @var string $claimName + * @var mixed $claimValue + */ foreach ($claims as $claimName => $claimValue) { switch ($claimName) { case RegisteredClaims::AUDIENCE: - $builder->permittedFor($claimValue); + if (is_array($claimValue)) { + /** @psalm-suppress MixedAssignment */ + foreach ($claimValue as $aud) { + $builder->permittedFor((string)$aud); + } + } else { + $builder->permittedFor((string)$claimValue); + } break; case RegisteredClaims::EXPIRATION_TIME: - $builder->expiresAt(new DateTimeImmutable('@' . $claimValue)); + /** @noinspection PhpUnnecessaryStringCastInspection */ + $builder->expiresAt(new DateTimeImmutable('@' . (string)$claimValue)); break; case RegisteredClaims::ID: - $builder->identifiedBy($claimValue); + $builder->identifiedBy((string)$claimValue); break; case RegisteredClaims::ISSUED_AT: - $builder->issuedAt(new DateTimeImmutable('@' . $claimValue)); + /** @noinspection PhpUnnecessaryStringCastInspection */ + $builder->issuedAt(new DateTimeImmutable('@' . (string)$claimValue)); break; case RegisteredClaims::ISSUER: - $builder->issuedBy($claimValue); + $builder->issuedBy((string)$claimValue); break; case RegisteredClaims::NOT_BEFORE: - $builder->canOnlyBeUsedAfter(new DateTimeImmutable('@' . $claimValue)); + /** @noinspection PhpUnnecessaryStringCastInspection */ + $builder->canOnlyBeUsedAfter(new DateTimeImmutable('@' . (string)$claimValue)); break; case RegisteredClaims::SUBJECT: - $builder->relatedTo($claimValue); + $builder->relatedTo((string)$claimValue); break; default: if ($addClaimsFromScopes || array_key_exists($claimName, $additionalClaims)) { @@ -119,6 +129,9 @@ public function build( return $this->jsonWebTokenBuilderService->getSignedJwtTokenFromBuilder($builder); } + /** + * @throws OAuthServerException + */ protected function getBuilder( AccessTokenEntityInterface $accessToken, UserEntityInterface $userEntity @@ -129,13 +142,11 @@ protected function getBuilder( ->identifiedBy($accessToken->getIdentifier()) ->canOnlyBeUsedAfter(new DateTimeImmutable('now')) ->expiresAt($accessToken->getExpiryDateTime()) - ->relatedTo($userEntity->getIdentifier()); + ->relatedTo((string)$userEntity->getIdentifier()); } /** - * @param AccessTokenEntityInterface $accessToken * @param string $jwsAlgorithm JWS Algorithm designation (like RS256, RS384...) - * @return string */ protected function generateAccessTokenHash(AccessTokenEntityInterface $accessToken, string $jwsAlgorithm): string { @@ -152,8 +163,8 @@ protected function generateAccessTokenHash(AccessTokenEntityInterface $accessTok EntityStringRepresentationInterface::class); } - // Try to use toString() so that it uses the string representation if it was already casted to string, - // otherwise, use the casted version. + // Try to use toString() so that it uses the string representation if it was already cast to string, + // otherwise, use the cast version. $accessTokenString = $accessToken->toString() ?? (string) $accessToken; $hashAlgorithm = 'sha' . $jwsAlgorithmBitLength; diff --git a/src/Services/JsonWebKeySetService.php b/src/Services/JsonWebKeySetService.php index 450766b7..74d24158 100644 --- a/src/Services/JsonWebKeySetService.php +++ b/src/Services/JsonWebKeySetService.php @@ -1,5 +1,7 @@ getCertPath(); + $publicKeyPath = $moduleConfig->getCertPath(); if (!file_exists($publicKeyPath)) { - throw new Exception("OpenId Connect certification file does not exists: {$publicKeyPath}."); + throw new Exception("OpenId Connect certification file does not exists: $publicKeyPath."); } $kid = FingerprintGenerator::forFile($publicKeyPath); @@ -50,9 +49,9 @@ public function __construct(ConfigurationService $configurationService) } /** - * @return \Jose\Component\Core\JWK[] + * @return JWK[] */ - public function keys() + public function keys(): array { return $this->jwkSet->all(); } diff --git a/src/Services/JsonWebTokenBuilderService.php b/src/Services/JsonWebTokenBuilderService.php index 3a57297f..fff9e447 100644 --- a/src/Services/JsonWebTokenBuilderService.php +++ b/src/Services/JsonWebTokenBuilderService.php @@ -1,5 +1,7 @@ configurationService = $configurationService ?? new ConfigurationService(); - $this->jwtConfig = Configuration::forAsymmetricSigner( - $this->configurationService->getSigner(), + $this->moduleConfig->getSigner(), InMemory::file( - $this->configurationService->getPrivateKeyPath(), - $this->configurationService->getPrivateKeyPassPhrase() ?? '' + $this->moduleConfig->getPrivateKeyPath(), + $this->moduleConfig->getPrivateKeyPassPhrase() ?? '' ), - InMemory::empty() + InMemory::plainText('empty', 'empty') ); } @@ -46,14 +46,17 @@ public function getDefaultJwtTokenBuilder(): Builder { // Ignore microseconds when handling dates. return $this->jwtConfig->builder(ChainedFormatter::withUnixTimestampDates()) - ->issuedBy($this->configurationService->getSimpleSAMLSelfURLHost()) + ->issuedBy($this->moduleConfig->getSimpleSAMLSelfURLHost()) ->issuedAt(new DateTimeImmutable('now')) ->identifiedBy(UniqueIdentifierGenerator::hitMe()); } + /** + * @throws Exception + */ public function getSignedJwtTokenFromBuilder(Builder $builder): UnencryptedToken { - $kid = FingerprintGenerator::forFile($this->configurationService->getCertPath()); + $kid = FingerprintGenerator::forFile($this->moduleConfig->getCertPath()); return $builder->withHeader('kid', $kid) ->getToken( @@ -67,6 +70,6 @@ public function getSignedJwtTokenFromBuilder(Builder $builder): UnencryptedToken */ public function getSigner(): Signer { - return $this->configurationService->getSigner(); + return $this->moduleConfig->getSigner(); } } diff --git a/src/Services/LoggerService.php b/src/Services/LoggerService.php index faebeaa3..7045d6eb 100644 --- a/src/Services/LoggerService.php +++ b/src/Services/LoggerService.php @@ -1,40 +1,43 @@ alert($message, $context); - break; - case LogLevel::CRITICAL: - $this->critical($message, $context); - break; - case LogLevel::DEBUG: - $this->debug($message, $context); - break; - case LogLevel::EMERGENCY: - $this->emergency($message, $context); - break; - case LogLevel::ERROR: - $this->error($message, $context); - break; - case LogLevel::INFO: - $this->info($message, $context); - break; - case LogLevel::NOTICE: - $this->notice($message, $context); - break; - case LogLevel::WARNING: - $this->warning($message, $context); - break; - default: - throw new InvalidArgumentException("Unrecognized log level '$level''"); - } + match ($level) { + LogLevel::ALERT => $this->alert($message, $context), + LogLevel::CRITICAL => $this->critical($message, $context), + LogLevel::DEBUG => $this->debug($message, $context), + LogLevel::EMERGENCY => $this->emergency($message, $context), + LogLevel::ERROR => $this->error($message, $context), + LogLevel::INFO => $this->info($message, $context), + LogLevel::NOTICE => $this->notice($message, $context), + LogLevel::WARNING => $this->warning($message, $context), + default => throw new InvalidArgumentException("Unrecognized log level '$level''"), + }; } public static function getInstance(): self diff --git a/src/Services/LogoutTokenBuilder.php b/src/Services/LogoutTokenBuilder.php index 4f522251..23c9746a 100644 --- a/src/Services/LogoutTokenBuilder.php +++ b/src/Services/LogoutTokenBuilder.php @@ -1,23 +1,23 @@ jsonWebTokenBuilderService = $jsonWebTokenBuilderService ?? new JsonWebTokenBuilderService(); } /** - * @throws OAuthServerException + * @throws OAuthServerException|Exception */ public function forRelyingPartyAssociation(RelyingPartyAssociationInterface $relyingPartyAssociation): string { diff --git a/src/Services/OidcOpenIdProviderMetadataService.php b/src/Services/OpMetadataService.php similarity index 60% rename from src/Services/OidcOpenIdProviderMetadataService.php rename to src/Services/OpMetadataService.php index 63642383..d9d393b1 100644 --- a/src/Services/OidcOpenIdProviderMetadataService.php +++ b/src/Services/OpMetadataService.php @@ -1,28 +1,28 @@ configurationService = $configurationService; $this->initMetadata(); } @@ -30,17 +30,17 @@ public function __construct( * Initialize metadata array. * @throws Exception */ - public function initMetadata(): void + private function initMetadata(): void { $this->metadata = []; - $this->metadata['issuer'] = $this->configurationService->getSimpleSAMLSelfURLHost(); + $this->metadata['issuer'] = $this->moduleConfig->getSimpleSAMLSelfURLHost(); $this->metadata['authorization_endpoint'] = - $this->configurationService->getOpenIdConnectModuleURL('authorize.php'); - $this->metadata['token_endpoint'] = $this->configurationService->getOpenIdConnectModuleURL('token.php'); - $this->metadata['userinfo_endpoint'] = $this->configurationService->getOpenIdConnectModuleURL('userinfo.php'); - $this->metadata['end_session_endpoint'] = $this->configurationService->getOpenIdConnectModuleURL('logout.php'); - $this->metadata['jwks_uri'] = $this->configurationService->getOpenIdConnectModuleURL('jwks.php'); - $this->metadata['scopes_supported'] = array_keys($this->configurationService->getOpenIDScopes()); + $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->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']; @@ -49,7 +49,7 @@ public function initMetadata(): void $this->metadata['request_parameter_supported'] = false; $this->metadata['grant_types_supported'] = ['authorization_code', 'refresh_token']; $this->metadata['claims_parameter_supported'] = true; - if (!(empty($acrValuesSupported = $this->configurationService->getAcrValuesSupported()))) { + if (!(empty($acrValuesSupported = $this->moduleConfig->getAcrValuesSupported()))) { $this->metadata['acr_values_supported'] = $acrValuesSupported; } $this->metadata['backchannel_logout_supported'] = true; diff --git a/src/Services/RoutingService.php b/src/Services/RoutingService.php index 4727214d..571c8936 100644 --- a/src/Services/RoutingService.php +++ b/src/Services/RoutingService.php @@ -1,5 +1,7 @@ requireAdmin(); } @@ -51,10 +60,14 @@ public static function call(string $controllerClassname, bool $authenticated = t } /** + * @throws BadRequest + * @throws ContainerExceptionInterface * @throws Exception + * @throws NotFoundExceptionInterface + * @throws ReflectionException * @throws \Exception */ - public static function callWithPermission(string $controllerClassname, string $permission) + public static function callWithPermission(string $controllerClassname, string $permission): void { $container = new Container(); /** @var AuthContextService $authContext */ @@ -66,9 +79,13 @@ public static function callWithPermission(string $controllerClassname, string $p /** * @throws BadRequest * @throws Exception + * @throws ReflectionException + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface * @throws \Exception + * @psalm-suppress MixedMethodCall, MixedAssignment */ - private static function callController($container, string $controllerClassname): void + private static function callController(ContainerInterface $container, string $controllerClassname): void { /** @var callable $controller */ $controller = self::getController($controllerClassname, $container); @@ -105,17 +122,20 @@ private static function callController($container, string $controllerClassname): return; } - throw new Exception('Response type not supported: ' . get_class($response)); + throw new Exception('Response type not supported: ' . $response::class); } /** - * @throws ReflectionException * @throws BadRequest + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws ReflectionException + * @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 BadRequest("Controller does not exist: $controllerClassname"); } $controllerReflectionClass = new ReflectionClass($controllerClassname); @@ -144,12 +164,13 @@ protected static function getController(string $controllerClassname, ContainerIn /** * @return void */ - protected static function enableJsonExceptionResponse() + protected static function enableJsonExceptionResponse(): void { set_exception_handler(function (Throwable $t) { if ($t instanceof Error) { // Showing SSP Error will also use SSP logger to log it. - return $t->show(); + $t->show(); + return; } elseif ($t instanceof OAuthServerException) { $response = $t->generateHttpResponse(new Response()); } else { diff --git a/src/Services/SessionMessagesService.php b/src/Services/SessionMessagesService.php index 039554ca..799825a8 100644 --- a/src/Services/SessionMessagesService.php +++ b/src/Services/SessionMessagesService.php @@ -1,5 +1,7 @@ session = $session; } /** - * @return void + * @throws Exception */ - public function addMessage(string $value) + public function addMessage(string $value): void { $this->session->setData('message', uniqid(), $value); } @@ -37,11 +36,12 @@ public function addMessage(string $value) /** * @return array */ - public function getMessages() + public function getMessages(): array { + /** @var array $messages */ $messages = $this->session->getDataOfType('message'); - foreach ($messages as $key => $message) { + foreach (array_keys($messages) as $key) { $this->session->deleteData('message', $key); } diff --git a/src/Services/SessionService.php b/src/Services/SessionService.php index fea55204..6cdb52ec 100644 --- a/src/Services/SessionService.php +++ b/src/Services/SessionService.php @@ -1,5 +1,7 @@ session = $session; } public function getCurrentSession(): Session @@ -50,10 +50,17 @@ public function setIsCookieBasedAuthn(bool $isCookieBasedAuthn): void public function getIsCookieBasedAuthn(): ?bool { - return $this->session->getData( + /** @var ?bool $isCookieBasedAuthn */ + $isCookieBasedAuthn = $this->session->getData( self::SESSION_DATA_TYPE, self::SESSION_DATA_ID_IS_COOKIE_BASED_AUTHN ); + + if (is_bool($isCookieBasedAuthn)) { + return $isCookieBasedAuthn; + } + + return null; } /** @@ -61,7 +68,12 @@ public function getIsCookieBasedAuthn(): ?bool */ public function addRelyingPartyAssociation(RelyingPartyAssociationInterface $association): void { - $associationId = hash('sha256', $association->getClientId() . $association->getSessionId()); + $sessionId = $association->getSessionId(); + if (empty($sessionId)) { + return; + } + + $associationId = hash('sha256', $association->getClientId() . $sessionId); $associations = $this->getRelyingPartyAssociations(); if (! array_key_exists($associationId, $associations)) { @@ -81,9 +93,22 @@ public function getRelyingPartyAssociations(): array return self::getRelyingPartyAssociationsForSession($this->session); } + /** + * @return array + */ public static function getRelyingPartyAssociationsForSession(Session $session): array { - return $session->getData(self::SESSION_DATA_TYPE, self::SESSION_DATA_ID_RP_ASSOCIATIONS) ?? []; + $relyingPartyAssociations = $session->getData(self::SESSION_DATA_TYPE, self::SESSION_DATA_ID_RP_ASSOCIATIONS); + + if (!is_array($relyingPartyAssociations)) { + return []; + } + + // Make sure we only have RelyingPartyAssociations here... + return array_filter( + $relyingPartyAssociations, + fn($value) => $value instanceof RelyingPartyAssociationInterface + ); } /** @@ -138,7 +163,6 @@ public function registerLogoutHandler(string $authSourceId, string $className, s /** * Set indication if logout was initiated using OIDC protocol. - * @param bool $isOidcInitiatedLogout * @throws Exception */ public function setIsOidcInitiatedLogout(bool $isOidcInitiatedLogout): void @@ -151,19 +175,8 @@ public function setIsOidcInitiatedLogout(bool $isOidcInitiatedLogout): void ); } - /** - * Get indication if logout was initiated using OIDC protocol. - * @return bool - */ - public function getIsOidcInitiatedLogout(): bool - { - return self::getIsOidcInitiatedLogoutForSession($this->session); - } - /** * Helper method to get indication if logout was initiated using OIDC protocol for given session. - * @param Session $session - * @return bool */ public static function getIsOidcInitiatedLogoutForSession(Session $session): bool { diff --git a/src/Store/SessionLogoutTicketStoreBuilder.php b/src/Store/SessionLogoutTicketStoreBuilder.php deleted file mode 100644 index 53e90d18..00000000 --- a/src/Store/SessionLogoutTicketStoreBuilder.php +++ /dev/null @@ -1,29 +0,0 @@ -database = $database ?? Database::getInstance(); - $this->ttl = $ttl >= 0 ? $ttl : 0; + $this->ttl = max($ttl, 0); } public function add(string $sid): void @@ -52,6 +54,7 @@ public function delete(string $sid): void } /** + * @inheritDoc * @throws Exception */ public function deleteMultiple(array $sids): void diff --git a/src/Stores/Session/LogoutTicketStoreInterface.php b/src/Stores/Session/LogoutTicketStoreInterface.php new file mode 100644 index 00000000..a961ad06 --- /dev/null +++ b/src/Stores/Session/LogoutTicketStoreInterface.php @@ -0,0 +1,17 @@ +add($rule); } $this->resultBag = new ResultBag(); - $this->loggerService = $loggerService ?? new LoggerService(); } public function add(RequestRuleInterface $rule): void @@ -49,12 +48,10 @@ public function add(RequestRuleInterface $rule): void } /** - * @param ServerRequestInterface $request - * @param array $ruleKeysToExecute + * @param class-string[] $ruleKeysToExecute * @param bool $useFragmentInHttpErrorResponses Indicate that in case of HTTP error responses, params should be * returned in URI fragment instead of query. - * @param array $allowedServerRequestMethods Indicate allowed HTTP methods used for request - * @return ResultBagInterface + * @param string[] $allowedServerRequestMethods Indicate allowed HTTP methods used for request * @throws OidcServerException */ public function check( @@ -86,8 +83,7 @@ public function check( } /** - * Predefine (add) the existing result so it can be used by other checkers during check. - * @param ResultInterface $result + * Predefine (add) the existing result, so it can be used by other checkers during check. */ public function predefineResult(ResultInterface $result): void { @@ -96,7 +92,6 @@ public function predefineResult(ResultInterface $result): void /** * Predefine existing ResultBag so that it can be used by other checkers during check. - * @param ResultBagInterface $resultBag */ public function predefineResultBag(ResultBagInterface $resultBag): void { @@ -105,10 +100,8 @@ public function predefineResultBag(ResultBagInterface $resultBag): void /** * Set data which will be available in each check, using key value pair - * @param string $key - * @param mixed $value */ - public function setData(string $key, $value): void + public function setData(string $key, mixed $value): void { $this->data[$key] = $value; } diff --git a/src/Utils/Checker/Result.php b/src/Utils/Checker/Result.php index 74d14d2c..dfb8a776 100644 --- a/src/Utils/Checker/Result.php +++ b/src/Utils/Checker/Result.php @@ -1,30 +1,19 @@ key = $key; - $this->value = $value; } public function getKey(): string @@ -32,12 +21,12 @@ public function getKey(): string return $this->key; } - public function getValue() + public function getValue(): mixed { return $this->value; } - public function setValue($value): void + public function setValue(mixed $value): void { $this->value = $value; } diff --git a/src/Utils/Checker/ResultBag.php b/src/Utils/Checker/ResultBag.php index 3227f069..6d506cf9 100644 --- a/src/Utils/Checker/ResultBag.php +++ b/src/Utils/Checker/ResultBag.php @@ -1,16 +1,21 @@ results[$key])) { - return $this->results[$key]; - } - - return null; + return $this->results[$key] ?? null; } /** @@ -42,7 +43,7 @@ public function getOrFail(string $key): ResultInterface $result = $this->get($key); if ($result === null) { - throw new \LogicException(\sprintf('Checker error: expected existing result, but none found (%s)', $key)); + throw new LogicException(sprintf('Checker error: expected existing result, but none found (%s)', $key)); } return $result; diff --git a/src/Utils/Checker/Rules/AbstractRule.php b/src/Utils/Checker/Rules/AbstractRule.php index 3c9bb356..bd7727cf 100644 --- a/src/Utils/Checker/Rules/AbstractRule.php +++ b/src/Utils/Checker/Rules/AbstractRule.php @@ -1,13 +1,12 @@ getQueryParams()[$paramKey] ?? null; + $param = isset($request->getQueryParams()[$paramKey]) ? + (string)$request->getQueryParams()[$paramKey] : null; + break; case 'POST': if (is_array($parsedBody = $request->getParsedBody())) { - return $parsedBody[$paramKey] ?? null; + $param = isset($parsedBody[$paramKey]) ? (string)$parsedBody[$paramKey] : null; } - // break; // ... falls through to default + break; default: $loggerService->warning( sprintf( @@ -57,6 +63,6 @@ protected function getParamFromRequestBasedOnAllowedMethods( ); } - return null; + return $param; } } diff --git a/src/Utils/Checker/Rules/AcrValuesRule.php b/src/Utils/Checker/Rules/AcrValuesRule.php index d29f7d9b..ffc6ed1d 100644 --- a/src/Utils/Checker/Rules/AcrValuesRule.php +++ b/src/Utils/Checker/Rules/AcrValuesRule.php @@ -1,5 +1,7 @@ getValue()['id_token']['acr'] ?? []; - $acrValues['essential'] = $requestedAcrClaim['essential'] ?? false; + $acrValues['essential'] = (bool)($requestedAcrClaim['essential'] ?? false); $acrValues['values'] = array_merge( isset($requestedAcrClaim['value']) ? [$requestedAcrClaim['value']] : [], - $requestedAcrClaim['values'] ?? [] + isset($requestedAcrClaim['values']) && is_array($requestedAcrClaim['values']) ? + $requestedAcrClaim['values'] : [] ); } // Check for acr_values request parameter - if (($acrValuesParam = $request->getQueryParams()['acr_values'] ?? null) !== null) { + /** @var ?string $acrValuesParam */ + $acrValuesParam = $request->getQueryParams()['acr_values'] ?? null; + if ($acrValuesParam !== null) { $acrValues['values'] = array_merge($acrValues['values'], explode(' ', $acrValuesParam)); } diff --git a/src/Utils/Checker/Rules/AddClaimsToIdTokenRule.php b/src/Utils/Checker/Rules/AddClaimsToIdTokenRule.php index 7f8620c7..60bff15b 100644 --- a/src/Utils/Checker/Rules/AddClaimsToIdTokenRule.php +++ b/src/Utils/Checker/Rules/AddClaimsToIdTokenRule.php @@ -1,5 +1,7 @@ getOrFail(ResponseTypeRule::class)->getValue(); return new Result($this->getKey(), $responseType === "id_token"); diff --git a/src/Utils/Checker/Rules/ClientIdRule.php b/src/Utils/Checker/Rules/ClientIdRule.php index 27a0b9b3..88d475a9 100644 --- a/src/Utils/Checker/Rules/ClientIdRule.php +++ b/src/Utils/Checker/Rules/ClientIdRule.php @@ -1,10 +1,12 @@ clientRepository = $clientRepository; } /** @@ -31,6 +30,7 @@ public function checkRule( bool $useFragmentInHttpErrorResponses = false, array $allowedServerRequestMethods = ['GET'] ): ?ResultInterface { + /** @var ?string $clientId */ $clientId = $request->getQueryParams()['client_id'] ?? $request->getServerParams()['PHP_AUTH_USER'] ?? null; if ($clientId === null) { diff --git a/src/Utils/Checker/Rules/CodeChallengeMethodRule.php b/src/Utils/Checker/Rules/CodeChallengeMethodRule.php index b47f448b..31e975a0 100644 --- a/src/Utils/Checker/Rules/CodeChallengeMethodRule.php +++ b/src/Utils/Checker/Rules/CodeChallengeMethodRule.php @@ -1,5 +1,7 @@ codeChallengeVerifiersRepository = $codeChallengeVerifiersRepository; } /** @@ -37,6 +36,7 @@ public function checkRule( /** @var string|null $state */ $state = $currentResultBag->getOrFail(StateRule::class)->getValue(); + /** @var string $codeChallengeMethod */ $codeChallengeMethod = $request->getQueryParams()['code_challenge_method'] ?? 'plain'; $codeChallengeVerifiers = $this->codeChallengeVerifiersRepository->getAll(); @@ -44,9 +44,7 @@ public function checkRule( throw OidcServerException::invalidRequest( 'code_challenge_method', 'Code challenge method must be one of ' . implode(', ', array_map( - function ($method) { - return '`' . $method . '`'; - }, + fn($method) => '`' . $method . '`', array_keys($codeChallengeVerifiers) )), null, diff --git a/src/Utils/Checker/Rules/CodeChallengeRule.php b/src/Utils/Checker/Rules/CodeChallengeRule.php index 0c0d53ef..369695a1 100644 --- a/src/Utils/Checker/Rules/CodeChallengeRule.php +++ b/src/Utils/Checker/Rules/CodeChallengeRule.php @@ -1,5 +1,7 @@ getOrFail(StateRule::class)->getValue(); + /** @var ?string $codeChallenge */ $codeChallenge = $request->getQueryParams()['code_challenge'] ?? null; if ($codeChallenge === null) { diff --git a/src/Utils/Checker/Rules/IdTokenHintRule.php b/src/Utils/Checker/Rules/IdTokenHintRule.php index 8e9bb631..e0f65ab2 100644 --- a/src/Utils/Checker/Rules/IdTokenHintRule.php +++ b/src/Utils/Checker/Rules/IdTokenHintRule.php @@ -1,5 +1,7 @@ configurationService = $configurationService; - $this->cryptKeyFactory = $cryptKeyFactory; } /** @@ -60,7 +57,7 @@ public function checkRule( $publicKey = $this->cryptKeyFactory->buildPublicKey(); /** @psalm-suppress ArgumentTypeCoercion */ $jwtConfig = Configuration::forAsymmetricSigner( - $this->configurationService->getSigner(), + $this->moduleConfig->getSigner(), InMemory::plainText($privateKey->getKeyContents(), $privateKey->getPassPhrase() ?? ''), InMemory::plainText($publicKey->getKeyContents()) ); @@ -72,12 +69,12 @@ public function checkRule( /** @psalm-suppress ArgumentTypeCoercion */ $jwtConfig->validator()->assert( $idTokenHint, - new IssuedBy($this->configurationService->getSimpleSAMLSelfURLHost()), + new IssuedBy($this->moduleConfig->getSimpleSAMLSelfURLHost()), // 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->configurationService->getSigner(), + $this->moduleConfig->getSigner(), InMemory::plainText($publicKey->getKeyContents()) ) ); diff --git a/src/Utils/Checker/Rules/MaxAgeRule.php b/src/Utils/Checker/Rules/MaxAgeRule.php index 76a8c45d..e1ef9769 100644 --- a/src/Utils/Checker/Rules/MaxAgeRule.php +++ b/src/Utils/Checker/Rules/MaxAgeRule.php @@ -1,9 +1,11 @@ authSimpleFactory = $authSimpleFactory; - $this->authenticationService = $authenticationService; } /** @@ -58,6 +54,8 @@ public function checkRule( /** @var string $redirectUri */ $redirectUri = $currentResultBag->getOrFail(RedirectUriRule::class)->getValue(); + /** @var ?string $state */ + $state = $queryParams['state'] ?? null; if (false === filter_var($queryParams['max_age'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) { throw OidcServerException::invalidRequest( @@ -65,7 +63,7 @@ public function checkRule( 'max_age must be a valid integer', null, $redirectUri, - $queryParams['state'] ?? null, + $state, $useFragmentInHttpErrorResponses ); } diff --git a/src/Utils/Checker/Rules/PostLogoutRedirectUriRule.php b/src/Utils/Checker/Rules/PostLogoutRedirectUriRule.php index 29b78615..8b8fc5eb 100644 --- a/src/Utils/Checker/Rules/PostLogoutRedirectUriRule.php +++ b/src/Utils/Checker/Rules/PostLogoutRedirectUriRule.php @@ -1,5 +1,7 @@ clientRepository = $clientRepository; } /** @@ -59,9 +58,10 @@ public function checkRule( $claims = $idTokenHint->claims()->all(); - if (! isset($claims['aud']) || empty($claims['aud'])) { + if (empty($claims['aud'])) { throw OidcServerException::invalidRequest('id_token_hint', 'aud claim not present', null, null, $state); } + /** @var string[] $auds */ $auds = is_array($claims['aud']) ? $claims['aud'] : [$claims['aud']]; $isPostLogoutRedirectUriRegistered = false; diff --git a/src/Utils/Checker/Rules/PromptRule.php b/src/Utils/Checker/Rules/PromptRule.php index db115032..feb57b87 100644 --- a/src/Utils/Checker/Rules/PromptRule.php +++ b/src/Utils/Checker/Rules/PromptRule.php @@ -1,10 +1,12 @@ authSimpleFactory = $authSimpleFactory; - $this->authenticationService = $authenticationService; } /** @@ -56,19 +52,21 @@ public function checkRule( return null; } - $prompt = explode(" ", $queryParams['prompt']); + $prompt = explode(" ", (string)$queryParams['prompt']); if (count($prompt) > 1 && in_array('none', $prompt, true)) { throw OAuthServerException::invalidRequest('prompt', 'Invalid prompt parameter'); } /** @var string $redirectUri */ $redirectUri = $currentResultBag->getOrFail(RedirectUriRule::class)->getValue(); + /** @var ?string $state */ + $state = $queryParams['state'] ?? null; if (in_array('none', $prompt, true) && !$authSimple->isAuthenticated()) { throw OidcServerException::loginRequired( null, $redirectUri, null, - $queryParams['state'] ?? null, + $state, $useFragmentInHttpErrorResponses ); } diff --git a/src/Utils/Checker/Rules/RedirectUriRule.php b/src/Utils/Checker/Rules/RedirectUriRule.php index eb411c51..bfe5e926 100644 --- a/src/Utils/Checker/Rules/RedirectUriRule.php +++ b/src/Utils/Checker/Rules/RedirectUriRule.php @@ -1,10 +1,12 @@ getRedirectUri()) && - (strcmp($client->getRedirectUri(), $redirectUri) !== 0) - ) { + $clientRedirectUri = $client->getRedirectUri(); + if (is_string($clientRedirectUri) && (strcmp($clientRedirectUri, $redirectUri) !== 0)) { throw OidcServerException::invalidClient($request); } elseif ( - is_array($client->getRedirectUri()) && - in_array($redirectUri, $client->getRedirectUri(), true) === false + is_array($clientRedirectUri) && + in_array($redirectUri, $clientRedirectUri, true) === false ) { throw OidcServerException::invalidRequest('redirect_uri'); } diff --git a/src/Utils/Checker/Rules/RequestParameterRule.php b/src/Utils/Checker/Rules/RequestParameterRule.php index 48ce9def..8debca13 100644 --- a/src/Utils/Checker/Rules/RequestParameterRule.php +++ b/src/Utils/Checker/Rules/RequestParameterRule.php @@ -1,5 +1,7 @@ getOrFail(RedirectUriRule::class)->getValue(); - $state = $currentResultBag->get(StateRule::class); + /** @var ?string $stateValue */ + $stateValue = ($currentResultBag->get(StateRule::class))?->getValue(); throw OidcServerException::requestNotSupported( 'request object not supported', $redirectUri, null, - $state ? $state->getValue() : null, + $stateValue, $useFragmentInHttpErrorResponses ); } diff --git a/src/Utils/Checker/Rules/RequestedClaimsRule.php b/src/Utils/Checker/Rules/RequestedClaimsRule.php index 597868bb..2f92616a 100644 --- a/src/Utils/Checker/Rules/RequestedClaimsRule.php +++ b/src/Utils/Checker/Rules/RequestedClaimsRule.php @@ -1,24 +1,22 @@ claimExtractor = $claimExtractor; } @@ -33,11 +31,13 @@ public function checkRule( bool $useFragmentInHttpErrorResponses = false, array $allowedServerRequestMethods = ['GET'] ): ?ResultInterface { + /** @var ?string $claimsParam */ $claimsParam = $request->getQueryParams()['claims'] ?? null; if ($claimsParam === null) { return null; } - $claims = json_decode($claimsParam, true); + /** @var ?array $claims */ + $claims = json_decode($claimsParam, true, 512, JSON_THROW_ON_ERROR); if (is_null($claims)) { return null; } @@ -60,7 +60,7 @@ public function checkRule( return new Result($this->getKey(), $claims); } - private function filterUnauthorizedClaims(array &$requestClaims, string $key, array $authorized) + private function filterUnauthorizedClaims(array &$requestClaims, string $key, array $authorized): void { if (!array_key_exists($key, $requestClaims)) { return; @@ -72,9 +72,7 @@ private function filterUnauthorizedClaims(array &$requestClaims, string $key, ar } $requestClaims[$key] = array_filter( $requested, - function ($key) use ($authorized) { - return in_array($key, $authorized); - }, + fn($key) => in_array($key, $authorized), ARRAY_FILTER_USE_KEY ); } diff --git a/src/Utils/Checker/Rules/RequiredNonceRule.php b/src/Utils/Checker/Rules/RequiredNonceRule.php index 6765c574..24f8f4ed 100644 --- a/src/Utils/Checker/Rules/RequiredNonceRule.php +++ b/src/Utils/Checker/Rules/RequiredNonceRule.php @@ -1,5 +1,7 @@ getOrFail(ScopeRule::class)->getValue(); - $isOpenIdScopePresent = (bool) array_filter($validScopes, function ($scopeEntity) { - return $scopeEntity->getIdentifier() === 'openid'; - }); + $isOpenIdScopePresent = (bool) array_filter( + $validScopes, + fn($scopeEntity) => $scopeEntity->getIdentifier() === 'openid' + ); if (! $isOpenIdScopePresent) { throw OidcServerException::invalidRequest( diff --git a/src/Utils/Checker/Rules/ResponseTypeRule.php b/src/Utils/Checker/Rules/ResponseTypeRule.php index f0c7211b..bdaabe0a 100644 --- a/src/Utils/Checker/Rules/ResponseTypeRule.php +++ b/src/Utils/Checker/Rules/ResponseTypeRule.php @@ -1,5 +1,7 @@ configurationService = $configurationService; - } - /** * @inheritDoc + * @throws Throwable */ public function checkRule( ServerRequestInterface $request, diff --git a/src/Utils/Checker/Rules/ScopeRule.php b/src/Utils/Checker/Rules/ScopeRule.php index 18c77230..f9960d72 100644 --- a/src/Utils/Checker/Rules/ScopeRule.php +++ b/src/Utils/Checker/Rules/ScopeRule.php @@ -1,5 +1,7 @@ scopeRepository = $scopeRepository; } /** @@ -43,7 +42,7 @@ public function checkRule( $scopeDelimiterString = $data['scope_delimiter_string'] ?? ' '; $scopes = $this->convertScopesQueryStringToArray( - $request->getQueryParams()['scope'] ?? $defaultScope, + (string)($request->getQueryParams()['scope'] ?? $defaultScope), $scopeDelimiterString ); @@ -65,9 +64,7 @@ public function checkRule( /** * Converts a scopes query string to an array to easily iterate for validation. * - * @param string $scopes - * @param string $scopeDelimiterString - * @return array + * @return string[] * @throws OidcServerException */ protected function convertScopesQueryStringToArray(string $scopes, string $scopeDelimiterString): array @@ -76,8 +73,6 @@ protected function convertScopesQueryStringToArray(string $scopes, string $scope throw OidcServerException::serverError('Scope delimiter string can not be empty.'); } - return array_filter(explode($scopeDelimiterString, trim($scopes)), function ($scope) { - return !empty($scope); - }); + return array_filter(explode($scopeDelimiterString, trim($scopes)), fn($scope) => !empty($scope)); } } diff --git a/src/Utils/Checker/Rules/StateRule.php b/src/Utils/Checker/Rules/StateRule.php index fa4b125a..b2647c1d 100644 --- a/src/Utils/Checker/Rules/StateRule.php +++ b/src/Utils/Checker/Rules/StateRule.php @@ -1,5 +1,7 @@ + * @copyright (c) 2018 Steve Rhoades + * @license http://opensource.org/licenses/MIT MIT */ -namespace SimpleSAML\Module\oidc; +namespace SimpleSAML\Module\oidc\Utils; use Lcobucci\JWT\Token\RegisteredClaims; -use OpenIDConnectServer\ClaimExtractor; -use OpenIDConnectServer\Entities\ClaimSetEntity; -use OpenIDConnectServer\Exception\InvalidArgumentException; +use League\OAuth2\Server\Entities\ScopeEntityInterface; use RuntimeException; +use SimpleSAML\Module\oidc\Entities\ClaimSetEntity; +use SimpleSAML\Module\oidc\Entities\Interfaces\ClaimSetEntityInterface; +use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; -class ClaimTranslatorExtractor extends ClaimExtractor +class ClaimTranslatorExtractor { - /** @var array */ - protected $translationTable = [ + /** @var array */ + protected array $claimSets = []; + + /** @var string[] */ + protected array $protectedClaims = ['openid', 'profile', 'email', 'address', 'phone']; + + protected array $translationTable = [ 'sub' => [ 'eduPersonPrincipalName', 'eduPersonTargetedID', @@ -53,7 +67,7 @@ class ClaimTranslatorExtractor extends ClaimExtractor 'description', ], 'picture' => [ - // Empty 'jpegPhoto', Previously 'jpegPhoto' however spec calls for a url to photo, not an actual photo. + // Empty 'jpegPhoto', Previously 'jpegPhoto' however spec calls for an url to photo, not an actual photo. ], 'website' => [ // Empty @@ -99,7 +113,7 @@ class ClaimTranslatorExtractor extends ClaimExtractor /** * From JSON Web Token Claims registry: https://www.iana.org/assignments/jwt/jwt.xhtml */ - public const REGISTERED_CLAIMS = [ + final public const REGISTERED_CLAIMS = [ ...RegisteredClaims::ALL, 'azp', 'nonce', @@ -111,65 +125,132 @@ class ClaimTranslatorExtractor extends ClaimExtractor 'sub_jwk', ]; - /** - * Claims for which it is allowed to have multiple values. - */ - protected array $allowedMultiValueClaims; - /** * ClaimTranslatorExtractor constructor. * - * @param string $userIdAttr * @param ClaimSetEntity[] $claimSets - * @param array $translationTable - * @param array $allowedMultipleValueClaims - * @throws InvalidArgumentException + * @throws OidcServerException */ public function __construct( string $userIdAttr, array $claimSets = [], array $translationTable = [], - array $allowedMultipleValueClaims = [] + protected array $allowedMultiValueClaims = [] ) { // By default, add the userIdAttribute as one of the attribute for 'sub' claim. + /** @psalm-suppress MixedArgument */ array_unshift($this->translationTable['sub'], $userIdAttr); $this->translationTable = array_merge($this->translationTable, $translationTable); - $this->allowedMultiValueClaims = $allowedMultipleValueClaims; - - $this->protectedClaims[] = 'openid'; $this->addClaimSet(new ClaimSetEntity('openid', [ 'sub', ])); - parent::__construct($claimSets); + // Add Default OpenID Connect Claims + // @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims + $this->addClaimSet( + new ClaimSetEntity('profile', [ + 'name', + 'family_name', + 'given_name', + 'middle_name', + 'nickname', + 'preferred_username', + 'profile', + 'picture', + 'website', + 'gender', + 'birthdate', + 'zoneinfo', + 'locale', + 'updated_at' + ]) + ); + $this->addClaimSet( + new ClaimSetEntity('email', [ + 'email', + 'email_verified' + ]) + ); + $this->addClaimSet( + new ClaimSetEntity('address', [ + 'address' + ]) + ); + $this->addClaimSet( + new ClaimSetEntity('phone', [ + 'phone_number', + 'phone_number_verified' + ]) + ); + + foreach ($claimSets as $claimSet) { + $this->addClaimSet($claimSet); + } } /** - * @param array $translationTable - * @param array $samlAttributes - * @return array + * @throws OidcServerException */ + public function addClaimSet(ClaimSetEntityInterface $claimSet): self + { + $scope = $claimSet->getScope(); + + if (in_array($scope, $this->protectedClaims) && !empty($this->claimSets[$scope])) { + throw OidcServerException::serverError( + sprintf("%s is a protected scope and is pre-defined by the OpenID Connect specification.", $scope) + ); + } + + $this->claimSets[$scope] = $claimSet; + + return $this; + } + + public function getClaimSet(string $scope): ?ClaimSetEntityInterface + { + if (!$this->hasClaimSet($scope)) { + return null; + } + + return $this->claimSets[$scope]; + } + + public function hasClaimSet(string $scope): bool + { + return array_key_exists($scope, $this->claimSets); + } + private function translateSamlAttributesToClaims(array $translationTable, array $samlAttributes): array { $claims = []; + /** + * @var string $claim + * @var array $mappingConfig + */ foreach ($translationTable as $claim => $mappingConfig) { - $type = $mappingConfig['type'] ?? 'string'; + $type = (string)($mappingConfig['type'] ?? 'string'); unset($mappingConfig['type']); if ($type === 'json') { - $subClaims = $this->translateSamlAttributesToClaims($mappingConfig['claims'], $samlAttributes); + $mappingConfigClaims = is_array($mappingConfig['claims']) ? $mappingConfig['claims'] : []; + $subClaims = $this->translateSamlAttributesToClaims($mappingConfigClaims, $samlAttributes); $claims[$claim] = $subClaims; continue; } // Look for attributes in the attribute key, if not set then assume to legacy style configuration - $attributes = $mappingConfig['attributes'] ?? $mappingConfig; + $attributes = isset($mappingConfig['attributes']) && is_array($mappingConfig['attributes']) ? + $mappingConfig['attributes'] : + $mappingConfig; + /** @var string $samlMatch */ foreach ($attributes as $samlMatch) { if (array_key_exists($samlMatch, $samlAttributes)) { + /** @psalm-suppress MixedAssignment, MixedArgument */ $values = in_array($claim, $this->allowedMultiValueClaims) ? - $samlAttributes[$samlMatch] : - current($samlAttributes[$samlMatch]); + $samlAttributes[$samlMatch] : + current($samlAttributes[$samlMatch]); + /** @psalm-suppress MixedAssignment */ $claims[$claim] = $this->convertType($type, $values); break; } @@ -178,11 +259,13 @@ private function translateSamlAttributesToClaims(array $translationTable, array return $claims; } - private function convertType(string $type, $attributes) + private function convertType(string $type, mixed $attributes): mixed { if (is_array($attributes)) { $values = []; + /** @psalm-suppress MixedAssignment */ foreach ($attributes as $attribute) { + /** @psalm-suppress MixedAssignment */ $values[] = $this->convertType($type, $attribute); } return $values; @@ -200,21 +283,52 @@ private function convertType(string $type, $attributes) return $attributes; } + /** + * @param array $scopes + */ public function extract(array $scopes, array $claims): array { - $translatedClaims = $this->translateSamlAttributesToClaims($this->translationTable, $claims); + $claims = $this->translateSamlAttributesToClaims($this->translationTable, $claims); + + $claimData = []; + $keys = array_keys($claims); + + foreach ($scopes as $scope) { + $scopeName = ($scope instanceof ScopeEntityInterface) ? $scope->getIdentifier() : $scope; + + $claimSet = $this->getClaimSet($scopeName); + if (null === $claimSet) { + continue; + } + + $intersected = array_intersect($claimSet->getClaims(), $keys); + + if (empty($intersected)) { + continue; + } + + $data = array_filter( + $claims, + fn($key) => in_array($key, $intersected), + ARRAY_FILTER_USE_KEY + ); + + $claimData = array_merge($claimData, $data); + } - return parent::extract($scopes, $translatedClaims); + return $claimData; } public function extractAdditionalIdTokenClaims(?array $claimsRequest, array $claims): array { + /** @var array $idTokenClaims */ $idTokenClaims = $claimsRequest['id_token'] ?? []; return $this->extractAdditionalClaims($idTokenClaims, $claims); } public function extractAdditionalUserInfoClaims(?array $claimsRequest, array $claims): array { + /** @var array $userInfoClaims */ $userInfoClaims = $claimsRequest['userinfo'] ?? []; return $this->extractAdditionalClaims($userInfoClaims, $claims); } @@ -223,8 +337,6 @@ public function extractAdditionalUserInfoClaims(?array $claimsRequest, array $cl * Add any individually requested claims * @link https://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests * @param array $requestedClaims keys are requested claims, value is array of additional info on the request - * @param array $claims - * @return array */ private function extractAdditionalClaims(array $requestedClaims, array $claims): array { @@ -235,9 +347,7 @@ private function extractAdditionalClaims(array $requestedClaims, array $claims): return array_filter( $translatedClaims, - function ($key) use ($requestedClaims) { - return array_key_exists($key, $requestedClaims); - }, + fn(/** @param array-key $key */ $key) => array_key_exists($key, $requestedClaims), ARRAY_FILTER_USE_KEY ); } diff --git a/src/Utils/FingerprintGenerator.php b/src/Utils/FingerprintGenerator.php index cba68db0..a69093ac 100644 --- a/src/Utils/FingerprintGenerator.php +++ b/src/Utils/FingerprintGenerator.php @@ -1,7 +1,11 @@
- +
@@ -38,7 +41,10 @@
- + {{ '{oidc:client:is_confidential_help}'|trans }} diff --git a/templates/clients/delete.twig b/templates/clients/delete.twig index e0916615..05622129 100644 --- a/templates/clients/delete.twig +++ b/templates/clients/delete.twig @@ -21,12 +21,12 @@
- - + +
- -
diff --git a/templates/clients/index.twig b/templates/clients/index.twig index 810a44a5..f3b5cb6d 100644 --- a/templates/clients/index.twig +++ b/templates/clients/index.twig @@ -50,9 +50,15 @@
- - - + + + + + + + + +
@@ -70,13 +76,18 @@ diff --git a/templates/clients/show.twig b/templates/clients/show.twig index 9b6cc2b7..74878cf2 100644 --- a/templates/clients/show.twig +++ b/templates/clients/show.twig @@ -38,14 +38,18 @@ {{ '{oidc:client:identifier}'|trans }} {{ client.identifier }} - + {{ '{oidc:client:secret}'|trans }} {{ client.secret }} - + diff --git a/templates/install.twig b/templates/install.twig index ea272a5c..4f1cc44b 100644 --- a/templates/install.twig +++ b/templates/install.twig @@ -16,8 +16,8 @@ {% if oauth2_enabled %}
- - + +
{% endif %} diff --git a/tests/Form/ClientFormTest.php b/tests/Form/ClientFormTest.php deleted file mode 100644 index 419a632d..00000000 --- a/tests/Form/ClientFormTest.php +++ /dev/null @@ -1,17 +0,0 @@ -markTestIncomplete(); - } -} diff --git a/tests/Services/ConfigurationServiceTest.php b/tests/Services/ConfigurationServiceTest.php deleted file mode 100644 index 01adf0b1..00000000 --- a/tests/Services/ConfigurationServiceTest.php +++ /dev/null @@ -1,48 +0,0 @@ - $certDir - ] - ) - ); - Configuration::setPreLoadedConfig( - Configuration::loadFromArray([]), - 'module_oidc.php' - ); - // Test default cert and pem - $service = new ConfigurationService(); - $this->assertEquals($certDir . 'oidc_module.crt', $service->getCertPath()); - $this->assertEquals($certDir . 'oidc_module.key', $service->getPrivateKeyPath()); - - // Set customized - Configuration::setPreLoadedConfig( - Configuration::loadFromArray( - [ - 'privatekey' => 'myPrivateKey.key', - 'certificate' => 'myCertificate.crt', - ] - ), - 'module_oidc.php' - ); - $service = new ConfigurationService(); - $this->assertEquals($certDir . 'myCertificate.crt', $service->getCertPath()); - $this->assertEquals($certDir . 'myPrivateKey.key', $service->getPrivateKeyPath()); - } -} diff --git a/tests/Store/SessionLogoutTicketStoreBuilderTest.php b/tests/Store/SessionLogoutTicketStoreBuilderTest.php deleted file mode 100644 index 0eb62732..00000000 --- a/tests/Store/SessionLogoutTicketStoreBuilderTest.php +++ /dev/null @@ -1,32 +0,0 @@ - 'sqlite::memory:', - 'database.username' => null, - 'database.password' => null, - 'database.prefix' => 'phpunit_', - 'database.persistent' => true, - 'database.secondaries' => [], - ]; - - Configuration::loadFromArray($config, '', 'simplesaml'); - $builder = new SessionLogoutTicketStoreBuilder(); - - $this->assertInstanceOf(SessionLogoutTicketStoreInterface::class, $builder->getInstance()); - $this->assertInstanceOf(SessionLogoutTicketStoreInterface::class, $builder::getStaticInstance()); - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 50e480f9..2a9a7344 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,5 +1,7 @@ 'secret', - - // Tokens TTL - 'authCodeDuration' => 'PT10M', // 10 minutes - 'refreshTokenDuration' => 'P1M', // 1 month - 'accessTokenDuration' => 'PT1H', // 1 hour, + ModuleConfig::OPTION_TOKEN_AUTHORIZATION_CODE_TTL => 'PT10M', + ModuleConfig::OPTION_TOKEN_REFRESH_TOKEN_TTL => 'P1M', + ModuleConfig::OPTION_TOKEN_ACCESS_TOKEN_TTL => 'PT1H', - // Tag to run storage cleanup script using the cron module... - 'cron_tag' => 'hourly', + ModuleConfig::OPTION_CRON_TAG => 'hourly', - // Set token signer - // See Lcobucci\JWT\Signer algorithms in https://github.com/lcobucci/jwt/tree/master/src/Signer - 'signer' => \Lcobucci\JWT\Signer\Rsa\Sha256::class, - // 'signer' => \Lcobucci\JWT\Signer\Hmac\Sha256::class, - // 'signer' => \Lcobucci\JWT\Signer\Ecdsa\Sha256::class, + ModuleConfig::OPTION_TOKEN_SIGNER => Sha256::class, - // this is the default auth source used for authentication if the auth source - // is not specified on particular client - 'auth' => 'default-sp', + ModuleConfig::OPTION_AUTH_SOURCE => 'default-sp', - // useridattr is the attribute-name that contains the userid as returned from idp. By default, this attribute - // will be dynamically added to the 'sub' claim in the attribute-to-claim translation table (you will probably - // want to use this attribute as the 'sub' claim since it designates unique identifier for the user). - 'useridattr' => 'uid', + ModuleConfig::OPTION_AUTH_USER_IDENTIFIER_ATTRIBUTE => 'uid', - // Optional custom scopes. You can create as many scopes as you want and assign claims to them. - 'scopes' => [ -// 'private' => [ // The key represents the scope name. -// 'description' => 'private scope', -// 'claim_name_prefix' => '', // Prefix to apply for all claim names from this scope -// 'are_multiple_claim_values_allowed' => false, // Are claims for this scope allowed to have multiple values -// 'claims' => ['national_document_id'] // Claims from the translation table which this scope will contain -// ], + ModuleConfig::OPTION_AUTH_CUSTOM_SCOPES => [ ], - 'translate' => [ - /* - * This is the default translate table from SAML to OIDC. - * You can change here the behaviour or add more translation to your - * private attributes scopes - * - * The basic format is - * - * 'claimName' => [ - * 'type' => 'string|int|bool|json', - * // For non JSON types - * 'attributes' => ['samlAttribute1', 'samlAttribute2'] - * // For JSON types - * 'claims => [ - * 'subclaim' => [ 'type' => 'string', 'attributes' => ['saml1']] - * ] - * ] - * - * For convenience the default type is "string" so type does not need to be defined. - * If "attributes" is not set, then it is assumed that the rest of the values are saml - * attribute names. - * - * Note on 'sub' claim: by default, the list of attributes for 'sub' claim will also contain attribute defined - * in 'useridattr' setting. You will probably want to use this attribute as the 'sub' claim since it - * designates unique identifier for the user, However, override as necessary. - */ -// 'sub' => [ -// 'attribute-defined-in-useridattr', // will be dynamically added if the list for 'sub' claim is not set. -// 'eduPersonPrincipalName', -// 'eduPersonTargetedID', -// 'eduPersonUniqueId', -// ], -// 'name' => [ -// 'cn', -// 'displayName', -// ], -// 'family_name' => [ -// 'sn', -// ], -// 'given_name' => [ -// 'givenName', -// ], -// 'middle_name' => [ -// // Empty -// ], -// 'nickname' => [ -// 'eduPersonNickname', -// ], -// 'preferred_username' => [ -// 'uid', -// ], -// 'profile' => [ -// 'labeledURI', -// 'description', -// ], -// 'picture' => [ -// // Empty. Previously 'jpegPhoto' however spec calls for a url to photo, not an actual photo. -// ], -// 'website' => [ -// // Empty -// ], -// 'gender' => [ -// // Empty -// ], -// 'birthdate' => [ -// // Empty -// ], -// 'zoneinfo' => [ -// // Empty -// ], -// 'locale' => [ -// 'preferredLanguage', -// ], -// 'updated_at' => [ -// 'type' => 'int', -// 'attributes' => [], -// ], -// 'email' => [ -// 'mail', -// ], -// 'email_verified' => [ -// 'type' => 'bool', -// 'attributes' => [], -// ], -// // address is a json object. Set the 'formatted' sub claim to postalAddress -// 'address' => [ -// 'type' => 'json', -// 'claims' => [ -// 'formatted' => ['postalAddress'], -// ] -// ], -// 'phone_number' => [ -// 'mobile', -// 'telephoneNumber', -// 'homePhone', -// ], -// 'phone_number_verified' => [ -// 'type' => 'bool', -// 'attributes' => [], -// ], - /* - * Optional scopes attributes - */ -// 'national_document_id' => [ -// 'schacPersonalUniqueId', -// ], + ModuleConfig::OPTION_AUTH_SAML_TO_OIDC_TRANSLATE_TABLE => [ ], - // Optional list of the Authentication Context Class References that this OP supports. - // If populated, this list will be available in OP discovery document (OP Metadata) as 'acr_values_supported'. - // @see https://datatracker.ietf.org/doc/html/rfc6711 - // @see https://www.iana.org/assignments/loa-profiles/loa-profiles.xhtml - // @see https://openid.net/specs/openid-connect-core-1_0.html#IDToken (acr claim) - // @see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest (acr_values parameter) - // Syntax: string[] (array of strings) - 'acrValuesSupported' => [ -// 'https://refeds.org/assurance/profile/espresso', -// 'https://refeds.org/assurance/profile/cappuccino', -// 'https://refeds.org/profile/mfa', -// 'https://refeds.org/profile/sfa', -// 'urn:mace:incommon:iap:silver', -// 'urn:mace:incommon:iap:bronze', -// '4', -// '3', -// '2', -// '1', -// '0', -// '...', + ModuleConfig::OPTION_AUTH_ACR_VALUES_SUPPORTED => [ ], - // If this OP supports ACRs, indicate which usable auth source supports which ACRs. - // Order of ACRs is important, more important ones being first. - // Syntax: array (array with auth source as key and value being array of ACR values as strings) - 'authSourcesToAcrValuesMap' => [ -// 'example-userpass' => ['1', '0'], -// 'default-sp' => ['http://id.incommon.org/assurance/bronze', '2', '1', '0'], -// 'strongly-assured-authsource' => [ -// 'https://refeds.org/assurance/profile/espresso', -// 'https://refeds.org/profile/mfa', -// 'https://refeds.org/assurance/profile/cappuccino', -// 'https://refeds.org/profile/sfa', -// '3', -// '2', -// '1', -// '0', -// ], + ModuleConfig::OPTION_AUTH_SOURCES_TO_ACR_VALUES_MAP => [ ], - // If this OP supports ACRs, indicate if authentication using cookie should be forced to specific ACR value. - // If this option is set to null, no specific ACR will be forced for cookie authentication and the resulting ACR - // will be one of the ACRs supported on used auth source during authentication, that is, session creation. - // If this option is set to specific ACR, with ACR value being one of the ACR value this OP supports, it will be - // set to that ACR for cookie authentication. - // For example, OIDC Core Spec notes that authentication using a long-lived browser cookie is one example where - // the use of "level 0" is appropriate: -// 'forcedAcrValueForCookieAuthentication' => '0', - 'forcedAcrValueForCookieAuthentication' => null, + ModuleConfig::OPTION_AUTH_FORCED_ACR_VALUE_FOR_COOKIE_AUTHENTICATION => null, ]; diff --git a/tests/Controller/OAuth2AccessTokenControllerTest.php b/tests/src/Controller/AccessTokenControllerTest.php similarity index 57% rename from tests/Controller/OAuth2AccessTokenControllerTest.php rename to tests/src/Controller/AccessTokenControllerTest.php index 9f9a3908..d171add5 100644 --- a/tests/Controller/OAuth2AccessTokenControllerTest.php +++ b/tests/src/Controller/AccessTokenControllerTest.php @@ -1,31 +1,30 @@ authorizationServerMock = $this->createMock(AuthorizationServer::class); @@ -37,11 +36,14 @@ protected function setUp(): void public function testItIsInitializable(): void { $this->assertInstanceOf( - OAuth2AccessTokenController::class, - new OAuth2AccessTokenController($this->authorizationServerMock) + AccessTokenController::class, + new AccessTokenController($this->authorizationServerMock) ); } + /** + * @throws OAuthServerException + */ public function testItRespondsToAccessTokenRequest(): void { $this->authorizationServerMock @@ -52,7 +54,7 @@ public function testItRespondsToAccessTokenRequest(): void $this->assertSame( $this->responseMock, - (new OAuth2AccessTokenController($this->authorizationServerMock))->__invoke($this->serverRequestMock) + (new AccessTokenController($this->authorizationServerMock))->__invoke($this->serverRequestMock) ); } } diff --git a/tests/Controller/OAuth2AuthorizationControllerTest.php b/tests/src/Controller/AuthorizationControllerTest.php similarity index 84% rename from tests/Controller/OAuth2AuthorizationControllerTest.php rename to tests/src/Controller/AuthorizationControllerTest.php index 73ff698d..bfb3580e 100644 --- a/tests/Controller/OAuth2AuthorizationControllerTest.php +++ b/tests/src/Controller/AuthorizationControllerTest.php @@ -1,58 +1,40 @@ ['1', '0'], 'essential' => false]; + /** + * @throws Exception + */ public function setUp(): void { $this->authenticationServiceStub = $this->createStub(AuthenticationService::class); $this->authorizationServerStub = $this->createStub(AuthorizationServer::class); - $this->configurationServiceStub = $this->createStub(ConfigurationService::class); + $this->moduleConfigStub = $this->createStub(ModuleConfig::class); $this->loggerServiceMock = $this->createMock(LoggerService::class); $this->authorizationRequestMock = $this->createMock(AuthorizationRequest::class); @@ -79,6 +64,7 @@ public function setUp(): void * @throws Error\NotFound * @throws Error\Exception * @throws OAuthServerException + * @throws Throwable */ public function testReturnsResponseWhenInvoked(): void { @@ -91,10 +77,10 @@ public function testReturnsResponseWhenInvoked(): void $this->authenticationServiceStub->method('getAuthenticateUser')->willReturn($this->userEntityStub); - $controller = new OAuth2AuthorizationController( + $controller = new AuthorizationController( $this->authenticationServiceStub, $this->authorizationServerStub, - $this->configurationServiceStub, + $this->moduleConfigStub, $this->loggerServiceMock ); @@ -107,6 +93,7 @@ public function testReturnsResponseWhenInvoked(): void * @throws Error\NotFound * @throws Error\Exception * @throws OAuthServerException + * @throws Throwable */ public function testValidateAcrThrowsIfAuthSourceIdNotSetInAuthorizationRequest(): void { @@ -120,10 +107,10 @@ public function testValidateAcrThrowsIfAuthSourceIdNotSetInAuthorizationRequest( $this->expectException(OidcServerException::class); - (new OAuth2AuthorizationController( + (new AuthorizationController( $this->authenticationServiceStub, $this->authorizationServerStub, - $this->configurationServiceStub, + $this->moduleConfigStub, $this->loggerServiceMock ))($this->serverRequestStub); } @@ -134,6 +121,7 @@ public function testValidateAcrThrowsIfAuthSourceIdNotSetInAuthorizationRequest( * @throws Error\NotFound * @throws Error\Exception * @throws OAuthServerException + * @throws Throwable */ public function testValidateAcrThrowsIfCookieBasedAuthnNotSetInAuthorizationRequest(): void { @@ -149,10 +137,10 @@ public function testValidateAcrThrowsIfCookieBasedAuthnNotSetInAuthorizationRequ $this->expectException(OidcServerException::class); - (new OAuth2AuthorizationController( + (new AuthorizationController( $this->authenticationServiceStub, $this->authorizationServerStub, - $this->configurationServiceStub, + $this->moduleConfigStub, $this->loggerServiceMock ))($this->serverRequestStub); } @@ -163,6 +151,7 @@ public function testValidateAcrThrowsIfCookieBasedAuthnNotSetInAuthorizationRequ * @throws Error\NotFound * @throws Error\Exception * @throws OAuthServerException + * @throws Throwable */ public function testValidateAcrSetsForcedAcrForCookieAuthentication(): void { @@ -173,10 +162,10 @@ public function testValidateAcrSetsForcedAcrForCookieAuthentication(): void $this->authorizationRequestMock->method('getAuthSourceId')->willReturn(self::$sampleAuthSourceId); $this->authorizationRequestMock->method('getIsCookieBasedAuthn')->willReturn(true); - $this->configurationServiceStub + $this->moduleConfigStub ->method('getAuthSourcesToAcrValuesMap') ->willReturn(self::$sampleAuthSourcesToAcrValuesMap); - $this->configurationServiceStub->method('getForcedAcrValueForCookieAuthentication')->willReturn('0'); + $this->moduleConfigStub->method('getForcedAcrValueForCookieAuthentication')->willReturn('0'); $this->authorizationServerStub ->method('validateAuthorizationRequest') @@ -187,10 +176,10 @@ public function testValidateAcrSetsForcedAcrForCookieAuthentication(): void $this->authorizationRequestMock->expects($this->once())->method('setAcr')->with('0'); - (new OAuth2AuthorizationController( + (new AuthorizationController( $this->authenticationServiceStub, $this->authorizationServerStub, - $this->configurationServiceStub, + $this->moduleConfigStub, $this->loggerServiceMock ))($this->serverRequestStub); } @@ -201,6 +190,7 @@ public function testValidateAcrSetsForcedAcrForCookieAuthentication(): void * @throws Error\NotFound * @throws Error\Exception * @throws OAuthServerException + * @throws Throwable */ public function testValidateAcrThrowsIfNoMatchedAcrForEssentialAcrs(): void { @@ -212,7 +202,7 @@ public function testValidateAcrThrowsIfNoMatchedAcrForEssentialAcrs(): void $this->authorizationRequestMock->method('getAuthSourceId')->willReturn(self::$sampleAuthSourceId); $this->authorizationRequestMock->method('getIsCookieBasedAuthn')->willReturn(false); - $this->configurationServiceStub + $this->moduleConfigStub ->method('getAuthSourcesToAcrValuesMap') ->willReturn(self::$sampleAuthSourcesToAcrValuesMap); @@ -225,10 +215,10 @@ public function testValidateAcrThrowsIfNoMatchedAcrForEssentialAcrs(): void $this->expectException(OidcServerException::class); - (new OAuth2AuthorizationController( + (new AuthorizationController( $this->authenticationServiceStub, $this->authorizationServerStub, - $this->configurationServiceStub, + $this->moduleConfigStub, $this->loggerServiceMock ))($this->serverRequestStub); } @@ -239,6 +229,7 @@ public function testValidateAcrThrowsIfNoMatchedAcrForEssentialAcrs(): void * @throws Error\NotFound * @throws Error\Exception * @throws OAuthServerException + * @throws Throwable */ public function testValidateAcrSetsFirstMatchedAcr(): void { @@ -249,7 +240,7 @@ public function testValidateAcrSetsFirstMatchedAcr(): void $this->authorizationRequestMock->method('getAuthSourceId')->willReturn(self::$sampleAuthSourceId); $this->authorizationRequestMock->method('getIsCookieBasedAuthn')->willReturn(false); - $this->configurationServiceStub + $this->moduleConfigStub ->method('getAuthSourcesToAcrValuesMap') ->willReturn(self::$sampleAuthSourcesToAcrValuesMap); @@ -262,10 +253,10 @@ public function testValidateAcrSetsFirstMatchedAcr(): void $this->authorizationRequestMock->expects($this->once())->method('setAcr')->with('1'); - (new OAuth2AuthorizationController( + (new AuthorizationController( $this->authenticationServiceStub, $this->authorizationServerStub, - $this->configurationServiceStub, + $this->moduleConfigStub, $this->loggerServiceMock ))($this->serverRequestStub); } @@ -276,6 +267,7 @@ public function testValidateAcrSetsFirstMatchedAcr(): void * @throws Error\NotFound * @throws Error\Exception * @throws OAuthServerException + * @throws Throwable */ public function testValidateAcrSetsCurrentSessionAcrIfNoMatchedAcr(): void { @@ -287,7 +279,7 @@ public function testValidateAcrSetsCurrentSessionAcrIfNoMatchedAcr(): void $this->authorizationRequestMock->method('getAuthSourceId')->willReturn(self::$sampleAuthSourceId); $this->authorizationRequestMock->method('getIsCookieBasedAuthn')->willReturn(false); - $this->configurationServiceStub + $this->moduleConfigStub ->method('getAuthSourcesToAcrValuesMap') ->willReturn(self::$sampleAuthSourcesToAcrValuesMap); @@ -300,10 +292,10 @@ public function testValidateAcrSetsCurrentSessionAcrIfNoMatchedAcr(): void $this->authorizationRequestMock->expects($this->once())->method('setAcr')->with('1'); - (new OAuth2AuthorizationController( + (new AuthorizationController( $this->authenticationServiceStub, $this->authorizationServerStub, - $this->configurationServiceStub, + $this->moduleConfigStub, $this->loggerServiceMock ))($this->serverRequestStub); } @@ -314,6 +306,7 @@ public function testValidateAcrSetsCurrentSessionAcrIfNoMatchedAcr(): void * @throws Error\NotFound * @throws Error\Exception * @throws OAuthServerException + * @throws Throwable */ public function testValidateAcrLogsWarningIfNoAcrsConfigured(): void { @@ -325,7 +318,7 @@ public function testValidateAcrLogsWarningIfNoAcrsConfigured(): void $this->authorizationRequestMock->method('getIsCookieBasedAuthn')->willReturn(false); $authSourcesToAcrValuesMap = [self::$sampleAuthSourceId => []]; - $this->configurationServiceStub + $this->moduleConfigStub ->method('getAuthSourcesToAcrValuesMap') ->willReturn($authSourcesToAcrValuesMap); @@ -339,10 +332,10 @@ public function testValidateAcrLogsWarningIfNoAcrsConfigured(): void $this->authorizationRequestMock->expects($this->once())->method('setAcr'); $this->loggerServiceMock->expects($this->once())->method('warning'); - (new OAuth2AuthorizationController( + (new AuthorizationController( $this->authenticationServiceStub, $this->authorizationServerStub, - $this->configurationServiceStub, + $this->moduleConfigStub, $this->loggerServiceMock ))($this->serverRequestStub); } diff --git a/tests/Controller/ClientCreateControllerTest.php b/tests/src/Controller/Client/CreateControllerTest.php similarity index 79% rename from tests/Controller/ClientCreateControllerTest.php rename to tests/src/Controller/Client/CreateControllerTest.php index e18d0fe6..9dcf9c4b 100644 --- a/tests/Controller/ClientCreateControllerTest.php +++ b/tests/src/Controller/Client/CreateControllerTest.php @@ -1,15 +1,20 @@ clientRepositoryMock = $this->createMock(ClientRepository::class); @@ -75,12 +56,12 @@ protected function setUp(): void public function testCanInstantiate(): void { $controller = $this->getStubbedInstance(); - $this->assertInstanceOf(ClientCreateController::class, $controller); + $this->assertInstanceOf(CreateController::class, $controller); } - protected function getStubbedInstance(): ClientCreateController + protected function getStubbedInstance(): CreateController { - return new ClientCreateController( + return new \SimpleSAML\Module\oidc\Controller\Client\CreateController( $this->clientRepositoryMock, $this->allowedOriginRepositoryMock, $this->templateFactoryMock, @@ -90,6 +71,9 @@ protected function getStubbedInstance(): ClientCreateController ); } + /** + * @throws Exception + */ public function testCanShowNewClientForm(): void { $this->clientFormMock @@ -119,9 +103,12 @@ public function testCanShowNewClientForm(): void ->willReturn($this->clientFormMock); $controller = $this->getStubbedInstance(); - $this->assertSame($this->templateStub, $controller->__invoke($this->serverRequestStub)); + $this->assertSame($this->templateStub, $controller->__invoke()); } + /** + * @throws Exception + */ public function testCanCreateNewClientFromFormData(): void { $this->clientFormMock @@ -170,9 +157,12 @@ public function testCanCreateNewClientFromFormData(): void ->with('{oidc:client:added}'); $controller = $this->getStubbedInstance(); - $this->assertInstanceOf(RedirectResponse::class, $controller->__invoke($this->serverRequestStub)); + $this->assertInstanceOf(RedirectResponse::class, $controller->__invoke()); } + /** + * @throws Exception + */ public function testCanSetOwnerInNewClient(): void { $this->authContextServiceMock->expects($this->once())->method('isSspAdmin')->willReturn(false); @@ -211,10 +201,8 @@ public function testCanSetOwnerInNewClient(): void ->willReturn($this->clientFormMock); $this->clientRepositoryMock->expects($this->once())->method('add') - ->with($this->callback(function ($client) { - return is_callable([$client, 'getOwner']) && - $client->getOwner() == 'ownerUsername'; - })); + ->with($this->callback(fn($client) => is_callable([$client, 'getOwner']) && + $client->getOwner() == 'ownerUsername')); $this->sessionMessageServiceMock ->expects($this->once()) @@ -222,6 +210,6 @@ public function testCanSetOwnerInNewClient(): void ->with('{oidc:client:added}'); $controller = $this->getStubbedInstance(); - $this->assertInstanceOf(RedirectResponse::class, $controller->__invoke($this->serverRequestStub)); + $this->assertInstanceOf(RedirectResponse::class, $controller->__invoke()); } } diff --git a/tests/Controller/ClientDeleteControllerTest.php b/tests/src/Controller/Client/DeleteControllerTest.php similarity index 77% rename from tests/Controller/ClientDeleteControllerTest.php rename to tests/src/Controller/Client/DeleteControllerTest.php index 28d04465..b10ab8ec 100644 --- a/tests/Controller/ClientDeleteControllerTest.php +++ b/tests/src/Controller/Client/DeleteControllerTest.php @@ -1,58 +1,46 @@ clientRepositoryMock = $this->createMock(ClientRepository::class); @@ -66,9 +54,9 @@ protected function setUp(): void $this->templateStub = $this->createStub(Template::class); } - protected function getStubbedInstance(): ClientDeleteController + protected function getStubbedInstance(): \SimpleSAML\Module\oidc\Controller\Client\DeleteController { - return new ClientDeleteController( + return new DeleteController( $this->clientRepositoryMock, $this->templateFactoryMock, $this->sessionMessageServiceMock, @@ -79,9 +67,12 @@ protected function getStubbedInstance(): ClientDeleteController public function testCanInstantiate(): void { $controller = $this->getStubbedInstance(); - $this->assertInstanceOf(ClientDeleteController::class, $controller); + $this->assertInstanceOf(\SimpleSAML\Module\oidc\Controller\Client\DeleteController::class, $controller); } + /** + * @throws ConfigurationError|BadRequest|NotFound|\SimpleSAML\Error\Exception|OidcServerException|JsonException + */ public function testItAsksConfirmationBeforeDeletingClient(): void { $this->serverRequestMock->expects($this->once())->method('getQueryParams') @@ -99,6 +90,9 @@ public function testItAsksConfirmationBeforeDeletingClient(): void $this->assertInstanceOf(Template::class, $controller->__invoke($this->serverRequestMock)); } + /** + * @throws ConfigurationError|BadRequest|NotFound|\SimpleSAML\Error\Exception|OidcServerException|JsonException + */ public function testThrowsIfIdNotFoundInDeleteAction(): void { $this->serverRequestMock->expects($this->once())->method('getQueryParams')->willReturn([]); @@ -108,6 +102,9 @@ public function testThrowsIfIdNotFoundInDeleteAction(): void ($this->getStubbedInstance())->__invoke($this->serverRequestMock); } + /** + * @throws ConfigurationError|BadRequest|NotFound|\SimpleSAML\Error\Exception|OidcServerException|JsonException + */ public function testThrowsIfSecretNotFoundInDeleteAction(): void { $this->serverRequestMock->expects($this->once())->method('getQueryParams') @@ -122,6 +119,9 @@ public function testThrowsIfSecretNotFoundInDeleteAction(): void ($this->getStubbedInstance())->__invoke($this->serverRequestMock); } + /** + * @throws ConfigurationError|BadRequest|NotFound|\SimpleSAML\Error\Exception|OidcServerException|JsonException + */ public function testThrowsIfSecretIsInvalidInDeleteAction(): void { $this->serverRequestMock->expects($this->once())->method('getQueryParams') @@ -138,6 +138,9 @@ public function testThrowsIfSecretIsInvalidInDeleteAction(): void ($this->getStubbedInstance())->__invoke($this->serverRequestMock); } + /** + * @throws ConfigurationError|BadRequest|NotFound|\SimpleSAML\Error\Exception|OidcServerException|JsonException + */ public function testItDeletesClient(): void { $this->serverRequestMock->expects($this->once())->method('getQueryParams') @@ -159,6 +162,9 @@ public function testItDeletesClient(): void ); } + /** + * @throws ConfigurationError|BadRequest|NotFound|\SimpleSAML\Error\Exception|OidcServerException|JsonException + */ public function testItDeletesClientWithOwner(): void { $this->authContextServiceMock->expects($this->exactly(2))->method('isSspAdmin')->willReturn(false); diff --git a/tests/Controller/ClientEditControllerTest.php b/tests/src/Controller/Client/EditControllerTest.php similarity index 85% rename from tests/Controller/ClientEditControllerTest.php rename to tests/src/Controller/Client/EditControllerTest.php index 8848dd58..df6bff3c 100644 --- a/tests/Controller/ClientEditControllerTest.php +++ b/tests/src/Controller/Client/EditControllerTest.php @@ -1,82 +1,54 @@ configurationServiceMock = $this->createMock(ConfigurationService::class); + $this->moduleConfigMock = $this->createMock(ModuleConfig::class); $this->clientRepositoryMock = $this->createMock(ClientRepository::class); $this->allowedOriginRepositoryMock = $this->createMock(AllowedOriginRepository::class); $this->templateFactoryMock = $this->createMock(TemplateFactory::class); @@ -90,16 +62,15 @@ protected function setUp(): void $this->templateStub = $this->createStub(Template::class); $this->clientFormMock = $this->createMock(ClientForm::class); - $this->configurationServiceMock->method('getOpenIdConnectModuleURL')->willReturn('url'); + $this->moduleConfigMock->method('getOpenIdConnectModuleURL')->willReturn('url'); $this->uriStub->method('getPath')->willReturn('/'); $this->serverRequestMock->method('getUri')->willReturn($this->uriStub); $this->serverRequestMock->method('withQueryParams')->willReturn($this->serverRequestMock); } - protected function getStubbedInstance(): ClientEditController + protected function getStubbedInstance(): EditController { - return new ClientEditController( - $this->configurationServiceMock, + return new EditController( $this->clientRepositoryMock, $this->allowedOriginRepositoryMock, $this->templateFactoryMock, @@ -111,9 +82,17 @@ protected function getStubbedInstance(): ClientEditController public function testItIsInitializable(): void { - $this->assertInstanceOf(ClientEditController::class, $this->getStubbedInstance()); + $this->assertInstanceOf( + EditController::class, + $this->getStubbedInstance() + ); } + /** + * @throws BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws NotFound + */ public function testItShowsEditClientForm(): void { $this->authContextServiceMock->method('isSspAdmin')->willReturn(true); @@ -160,6 +139,11 @@ public function testItShowsEditClientForm(): void ); } + /** + * @throws BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws NotFound + */ public function testItUpdatesClientFromEditClientFormData(): void { $this->authContextServiceMock->method('isSspAdmin')->willReturn(true); @@ -227,7 +211,6 @@ public function testItUpdatesClientFromEditClientFormData(): void false, 'auth_source', 'existingOwner', - [] ), null ); @@ -242,6 +225,11 @@ public function testItUpdatesClientFromEditClientFormData(): void ); } + /** + * @throws BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws NotFound + */ public function testItSendsOwnerArgToRepoOnUpdate(): void { $this->authContextServiceMock->expects($this->atLeastOnce())->method('isSspAdmin')->willReturn(false); @@ -309,8 +297,6 @@ public function testItSendsOwnerArgToRepoOnUpdate(): void false, 'auth_source', 'existingOwner', - [], - null ), 'authedUserId' ); @@ -327,6 +313,10 @@ public function testItSendsOwnerArgToRepoOnUpdate(): void ); } + /** + * @throws \SimpleSAML\Error\Exception + * @throws NotFound + */ public function testThrowsIdNotFoundExceptionInEditAction(): void { $this->serverRequestMock->expects($this->once())->method('getQueryParams')->willReturn([]); @@ -336,13 +326,18 @@ public function testThrowsIdNotFoundExceptionInEditAction(): void ($this->getStubbedInstance())->__invoke($this->serverRequestMock); } + /** + * @throws BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws 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->expectException(Exception::class); ($this->getStubbedInstance())->__invoke($this->serverRequestMock); } diff --git a/tests/Controller/ClientIndexControllerTest.php b/tests/src/Controller/Client/IndexControllerTest.php similarity index 62% rename from tests/Controller/ClientIndexControllerTest.php rename to tests/src/Controller/Client/IndexControllerTest.php index 3385e907..addfa32c 100644 --- a/tests/Controller/ClientIndexControllerTest.php +++ b/tests/src/Controller/Client/IndexControllerTest.php @@ -1,52 +1,36 @@ clientRepositoryMock = $this->createMock(ClientRepository::class); @@ -63,9 +47,9 @@ protected function setUp(): void $this->serverRequestMock->method('getQueryParams')->willReturn(['page' => 1]); } - protected function getStubbedInstance(): ClientIndexController + protected function getStubbedInstance(): IndexController { - return new ClientIndexController( + return new IndexController( $this->clientRepositoryMock, $this->templateFactoryMock, $this->authContextServiceMock @@ -74,9 +58,12 @@ protected function getStubbedInstance(): ClientIndexController public function testItIsInitializable(): void { - $this->assertInstanceOf(ClientIndexController::class, $this->getStubbedInstance()); + $this->assertInstanceOf(IndexController::class, $this->getStubbedInstance()); } + /** + * @throws \SimpleSAML\Error\Exception + */ public function testItShowsClientIndex(): void { $this->clientRepositoryMock->expects($this->once())->method('findPaginated') diff --git a/tests/Controller/ClientResetSecretControllerTest.php b/tests/src/Controller/Client/ResetSecretControllerTest.php similarity index 82% rename from tests/Controller/ClientResetSecretControllerTest.php rename to tests/src/Controller/Client/ResetSecretControllerTest.php index 3eff31a1..f83d6414 100644 --- a/tests/Controller/ClientResetSecretControllerTest.php +++ b/tests/src/Controller/Client/ResetSecretControllerTest.php @@ -1,46 +1,38 @@ clientRepositoryMock = $this->createMock(ClientRepository::class); @@ -58,9 +50,9 @@ public static function setUpBeforeClass(): void $_SERVER['REQUEST_URI'] = ''; } - protected function prepareStubbedInstance(): ClientResetSecretController + protected function prepareStubbedInstance(): \SimpleSAML\Module\oidc\Controller\Client\ResetSecretController { - return new ClientResetSecretController( + return new ResetSecretController( $this->clientRepositoryMock, $this->sessionMessagesServiceMock, $this->authContextServiceMock @@ -70,11 +62,15 @@ protected function prepareStubbedInstance(): ClientResetSecretController public function testCanInstantiate(): void { $this->assertInstanceOf( - ClientResetSecretController::class, + \SimpleSAML\Module\oidc\Controller\Client\ResetSecretController::class, $this->prepareStubbedInstance() ); } + /** + * @throws Exception + * @throws NotFound + */ public function testItThrowsIdNotFoundExceptionInResetSecretAction(): void { $this->serverRequestMock->method('getQueryParams')->willReturn([]); @@ -82,6 +78,10 @@ public function testItThrowsIdNotFoundExceptionInResetSecretAction(): void $this->prepareStubbedInstance()->__invoke($this->serverRequestMock); } + /** + * @throws BadRequest + * @throws Exception + */ public function testItThrowsClientNotFoundExceptionInResetSecretAction(): void { $this->serverRequestMock->method('getQueryParams')->willReturn(['client_id' => 'clientid']); @@ -95,6 +95,10 @@ public function testItThrowsClientNotFoundExceptionInResetSecretAction(): void $this->prepareStubbedInstance()->__invoke($this->serverRequestMock); } + /** + * @throws Exception + * @throws NotFound + */ public function testThrowsSecretNotFoundExceptionInResetSecretAction(): void { $this->serverRequestMock @@ -112,6 +116,10 @@ public function testThrowsSecretNotFoundExceptionInResetSecretAction(): void $this->prepareStubbedInstance()->__invoke($this->serverRequestMock); } + /** + * @throws Exception + * @throws NotFound + */ public function testThrowsSecretInvalidExceptionInResetSecretAction(): void { $this->serverRequestMock @@ -135,6 +143,11 @@ public function testThrowsSecretInvalidExceptionInResetSecretAction(): void $this->prepareStubbedInstance()->__invoke($this->serverRequestMock); } + /** + * @throws BadRequest + * @throws Exception + * @throws NotFound + */ public function testItResetSecretsClient(): void { $this->serverRequestMock @@ -169,6 +182,11 @@ public function testItResetSecretsClient(): void $this->prepareStubbedInstance()->__invoke($this->serverRequestMock); } + /** + * @throws BadRequest + * @throws Exception + * @throws NotFound + */ public function testItSendBackToShowClientIfNotPostMethodInResetAction(): void { $this->serverRequestMock diff --git a/tests/Controller/ClientShowControllerTest.php b/tests/src/Controller/Client/ShowControllerTest.php similarity index 71% rename from tests/Controller/ClientShowControllerTest.php rename to tests/src/Controller/Client/ShowControllerTest.php index c0bef3cd..f3dc4a11 100644 --- a/tests/Controller/ClientShowControllerTest.php +++ b/tests/src/Controller/Client/ShowControllerTest.php @@ -1,55 +1,43 @@ clientRepositoryMock = $this->createMock(ClientRepository::class); @@ -69,9 +57,9 @@ public static function setUpBeforeClass(): void $_SERVER['REQUEST_URI'] = ''; } - protected function getStubbedInstance(): ClientShowController + protected function getStubbedInstance(): ShowController { - return new ClientShowController( + return new ShowController( $this->clientRepositoryMock, $this->allowedOriginRepositoryMock, $this->templateFactoryMock, @@ -81,9 +69,18 @@ protected function getStubbedInstance(): ClientShowController public function testItIsInitializable(): void { - $this->assertInstanceOf(ClientShowController::class, $this->getStubbedInstance()); + $this->assertInstanceOf( + ShowController::class, + $this->getStubbedInstance() + ); } + /** + * @throws BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws NotFound + * @throws OidcServerException|JsonException + */ public function testItShowsClientDescription(): void { $this->serverRequestMock @@ -117,6 +114,11 @@ public function testItShowsClientDescription(): void ); } + /** + * @throws \SimpleSAML\Error\Exception + * @throws NotFound + * @throws OidcServerException|JsonException + */ public function testItThrowsIdNotFoundException(): void { $this->serverRequestMock->expects($this->once())->method('getQueryParams')->willReturn([]); @@ -125,6 +127,11 @@ public function testItThrowsIdNotFoundException(): void $this->getStubbedInstance()->__invoke($this->serverRequestMock); } + /** + * @throws BadRequest + * @throws \SimpleSAML\Error\Exception + * @throws OidcServerException|JsonException + */ public function testItThrowsClientNotFoundException(): void { $this->serverRequestMock diff --git a/tests/Controller/OpenIdConnectDiscoverConfigurationControllerTest.php b/tests/src/Controller/ConfigurationDiscoveryControllerTest.php similarity index 56% rename from tests/Controller/OpenIdConnectDiscoverConfigurationControllerTest.php rename to tests/src/Controller/ConfigurationDiscoveryControllerTest.php index 37945b4b..75bbd30c 100644 --- a/tests/Controller/OpenIdConnectDiscoverConfigurationControllerTest.php +++ b/tests/src/Controller/ConfigurationDiscoveryControllerTest.php @@ -1,18 +1,22 @@ 'http://localhost', 'authorization_endpoint' => 'http://localhost/authorize.php', 'token_endpoint' => 'http://localhost/token.php', @@ -25,18 +29,16 @@ class OpenIdConnectDiscoverConfigurationControllerTest extends TestCase 'code_challenge_methods_supported' => ['plain', 'S256'], 'end_session_endpoint' => 'http://localhost/logout.php', ]; + + protected MockObject $oidcOpenIdProviderMetadataServiceMock; + protected MockObject $serverRequestMock; + /** - * \PHPUnit\Framework\MockObject\MockObject - */ - protected $oidcOpenIdProviderMetadataServiceMock; - /** - * \PHPUnit\Framework\MockObject\MockObject + * @throws Exception */ - protected $serverRequestMock; - protected function setUp(): void { - $this->oidcOpenIdProviderMetadataServiceMock = $this->createMock(OidcOpenIdProviderMetadataService::class); + $this->oidcOpenIdProviderMetadataServiceMock = $this->createMock(OpMetadataService::class); $this->oidcOpenIdProviderMetadataServiceMock->method('getMetadata')->willReturn(self::OIDC_OP_METADATA); $this->serverRequestMock = $this->createMock(ServerRequest::class); @@ -45,20 +47,20 @@ protected function setUp(): void public function testItIsInitializable(): void { $this->assertInstanceOf( - OpenIdConnectDiscoverConfigurationController::class, - new OpenIdConnectDiscoverConfigurationController($this->oidcOpenIdProviderMetadataServiceMock) + ConfigurationDiscoveryController::class, + new ConfigurationDiscoveryController($this->oidcOpenIdProviderMetadataServiceMock) ); } - protected function getStubbedInstance(): OpenIdConnectDiscoverConfigurationController + protected function getStubbedInstance(): ConfigurationDiscoveryController { - return new OpenIdConnectDiscoverConfigurationController($this->oidcOpenIdProviderMetadataServiceMock); + return new ConfigurationDiscoveryController($this->oidcOpenIdProviderMetadataServiceMock); } public function testItReturnsOpenIdConnectConfiguration(): void { $this->assertSame( - $this->getStubbedInstance()->__invoke($this->serverRequestMock)->getPayload(), + json_decode($this->getStubbedInstance()->__invoke()->getContent(), true), self::OIDC_OP_METADATA ); } diff --git a/tests/Controller/OpenIdConnectInstallerControllerTest.php b/tests/src/Controller/InstallerControllerTest.php similarity index 80% rename from tests/Controller/OpenIdConnectInstallerControllerTest.php rename to tests/src/Controller/InstallerControllerTest.php index d9925c5d..0f8015a2 100644 --- a/tests/Controller/OpenIdConnectInstallerControllerTest.php +++ b/tests/src/Controller/InstallerControllerTest.php @@ -1,10 +1,14 @@ templateFactoryMock = $this->createMock(TemplateFactory::class); @@ -53,9 +42,9 @@ protected function setUp(): void $this->templateMock = $this->createMock(Template::class); } - protected function createStubbedInstance(): OpenIdConnectInstallerController + protected function createStubbedInstance(): InstallerController { - return new OpenIdConnectInstallerController( + return new InstallerController( $this->templateFactoryMock, $this->sessionMessagesService, $this->databaseMigrationMock, @@ -66,11 +55,14 @@ protected function createStubbedInstance(): OpenIdConnectInstallerController public function testItIsInitializable(): void { $this->assertInstanceOf( - OpenIdConnectInstallerController::class, + InstallerController::class, $this->createStubbedInstance() ); } + /** + * @throws \Exception + */ public function testItReturnsToMainPageIfAlreadyUpdated(): void { $this->databaseMigrationMock @@ -84,6 +76,9 @@ public function testItReturnsToMainPageIfAlreadyUpdated(): void ); } + /** + * @throws \Exception + */ public function testItShowsInformationPage(): void { $this->serverRequestMock->expects($this->once())->method('getParsedBody'); @@ -100,6 +95,9 @@ public function testItShowsInformationPage(): void ); } + /** + * @throws \Exception + */ public function testItRequiresConfirmationBeforeInstallSchema(): void { $this->serverRequestMock->expects($this->once())->method('getParsedBody'); @@ -117,6 +115,9 @@ public function testItRequiresConfirmationBeforeInstallSchema(): void ); } + /** + * @throws \Exception + */ public function testItCreatesSchema(): void { $this->serverRequestMock->expects($this->once())->method('getParsedBody')->willReturn(['migrate' => true,]); @@ -131,6 +132,9 @@ public function testItCreatesSchema(): void ); } + /** + * @throws \Exception + */ public function testItImportsDataFromOauth2Module(): void { $this->serverRequestMock @@ -143,9 +147,11 @@ public function testItImportsDataFromOauth2Module(): void $this->sessionMessagesService ->expects($this->atLeast(2)) ->method('addMessage') - ->with($this->callback(function ($message) { - return in_array($message, ['{oidc:install:finished}', '{oidc:import:finished}']); - })); + ->with( + $this->callback( + fn($message) => in_array($message, ['{oidc:install:finished}', '{oidc:import:finished}']) + ) + ); $this->assertInstanceOf( RedirectResponse::class, diff --git a/tests/Controller/OpenIdConnectJwksControllerTest.php b/tests/src/Controller/JwksControllerTest.php similarity index 61% rename from tests/Controller/OpenIdConnectJwksControllerTest.php rename to tests/src/Controller/JwksControllerTest.php index fe5bb672..efffe479 100644 --- a/tests/Controller/OpenIdConnectJwksControllerTest.php +++ b/tests/src/Controller/JwksControllerTest.php @@ -1,26 +1,27 @@ jsonWebKeySetServiceMock = $this->createMock(JsonWebKeySetService::class); @@ -30,8 +31,8 @@ protected function setUp(): void public function testItIsInitializable(): void { $this->assertInstanceOf( - OpenIdConnectJwksController::class, - new OpenIdConnectJwksController($this->jsonWebKeySetServiceMock) + JwksController::class, + new JwksController($this->jsonWebKeySetServiceMock) ); } @@ -52,8 +53,7 @@ public function testItReturnsJsonKeys(): void $this->assertSame( ['keys' => $keys], - (new OpenIdConnectJwksController($this->jsonWebKeySetServiceMock)) - ->__invoke($this->serverRequestMock) + (new JwksController($this->jsonWebKeySetServiceMock))->__invoke() ->getPayload() ); } diff --git a/tests/Controller/LogoutControllerTest.php b/tests/src/Controller/LogoutControllerTest.php similarity index 81% rename from tests/Controller/LogoutControllerTest.php rename to tests/src/Controller/LogoutControllerTest.php index f7050215..d15780b8 100644 --- a/tests/Controller/LogoutControllerTest.php +++ b/tests/src/Controller/LogoutControllerTest.php @@ -1,26 +1,29 @@ authorizationServerStub = $this->createStub(AuthorizationServer::class); - $this->authenticationServiceStub = $this->createStub(AuthenticationService::class); $this->sessionServiceStub = $this->createStub(SessionService::class); - $this->sessionLogoutTicketStoreBuilderStub = $this->createStub(SessionLogoutTicketStoreBuilder::class); + $this->sessionLogoutTicketStoreBuilderStub = $this->createStub(LogoutTicketStoreBuilder::class); $this->serverRequestStub = $this->createStub(ServerRequest::class); $this->currentSessionMock = $this->createMock(Session::class); $this->sessionMock = $this->createMock(Session::class); @@ -102,7 +62,7 @@ public function setUp(): void $this->idTokenHintStub = $this->createStub(UnencryptedToken::class); $this->dataSet = new DataSet(['sid' => '123'], ''); $this->loggerServiceMock = $this->createMock(LoggerService::class); - $this->sessionLogoutTicketStoreDbStub = $this->createStub(SessionLogoutTicketStoreDb::class); + $this->sessionLogoutTicketStoreDbStub = $this->createStub(LogoutTicketStoreDb::class); $this->templateFactoryStub = $this->createStub(TemplateFactory::class); } @@ -112,7 +72,6 @@ public function testConstruct(): void LogoutController::class, new LogoutController( $this->authorizationServerStub, - $this->authenticationServiceStub, $this->sessionServiceStub, $this->sessionLogoutTicketStoreBuilderStub, $this->loggerServiceMock, @@ -132,7 +91,6 @@ public function testInvokeThrowsForInvalidLogoutRequest(): void $logoutController = new LogoutController( $this->authorizationServerStub, - $this->authenticationServiceStub, $this->sessionServiceStub, $this->sessionLogoutTicketStoreBuilderStub, $this->loggerServiceMock, @@ -164,14 +122,10 @@ public function testCallLogoutForSessionIdInIdTokenHint(): void $this->sessionMock->expects($this->exactly(2)) ->method('doLogout') - ->withConsecutive( - ['authId1'], - ['authId2'] - ); + ->with($this->callback(fn($authId) => in_array($authId, ['authId1', 'authId2']))); (new LogoutController( $this->authorizationServerStub, - $this->authenticationServiceStub, $this->sessionServiceStub, $this->sessionLogoutTicketStoreBuilderStub, $this->loggerServiceMock, @@ -202,7 +156,6 @@ public function testLogsIfSessionFromIdTokenHintNotFound(): void (new LogoutController( $this->authorizationServerStub, - $this->authenticationServiceStub, $this->sessionServiceStub, $this->sessionLogoutTicketStoreBuilderStub, $this->loggerServiceMock, @@ -221,16 +174,12 @@ public function testLogoutCalledOnCurrentSession(): void $this->currentSessionMock->expects($this->exactly(2)) ->method('doLogout') - ->withConsecutive( - ['authId1'], - ['authId2'] - ); + ->with($this->callback(fn($authId) => in_array($authId, ['authId1', 'authId2']))); $this->sessionServiceStub->method('getCurrentSession')->willReturn($this->currentSessionMock); (new LogoutController( $this->authorizationServerStub, - $this->authenticationServiceStub, $this->sessionServiceStub, $this->sessionLogoutTicketStoreBuilderStub, $this->loggerServiceMock, @@ -253,7 +202,6 @@ public function testReturnsRedirectResponseIfPostLogoutRedirectUriIsSet(): void $logoutController = new LogoutController( $this->authorizationServerStub, - $this->authenticationServiceStub, $this->sessionServiceStub, $this->sessionLogoutTicketStoreBuilderStub, $this->loggerServiceMock, @@ -275,7 +223,6 @@ public function testReturnsResponse(): void $logoutController = new LogoutController( $this->authorizationServerStub, - $this->authenticationServiceStub, $this->sessionServiceStub, $this->sessionLogoutTicketStoreBuilderStub, $this->loggerServiceMock, @@ -285,7 +232,7 @@ public function testReturnsResponse(): void $this->assertInstanceOf(Response::class, $logoutController->__invoke($this->serverRequestStub)); } - public function testLogoutHandler(): void + public function testLogoutHandler(): never { $this->markTestIncomplete(); } diff --git a/tests/Controller/OpenIdConnectUserInfoControllerTest.php b/tests/src/Controller/UserInfoControllerTest.php similarity index 84% rename from tests/Controller/OpenIdConnectUserInfoControllerTest.php rename to tests/src/Controller/UserInfoControllerTest.php index 7e3094c6..afc5e9ba 100644 --- a/tests/Controller/OpenIdConnectUserInfoControllerTest.php +++ b/tests/src/Controller/UserInfoControllerTest.php @@ -1,63 +1,44 @@ resourceServerMock = $this->createMock(ResourceServer::class); @@ -72,9 +53,9 @@ protected function setUp(): void $this->userEntityMock = $this->createMock(UserEntity::class); } - protected function prepareMockedInstance(): OpenIdConnectUserInfoController + protected function prepareMockedInstance(): UserInfoController { - return new OpenIdConnectUserInfoController( + return new UserInfoController( $this->resourceServerMock, $this->accessTokenRepositoryMock, $this->userRepositoryMock, @@ -86,11 +67,16 @@ protected function prepareMockedInstance(): OpenIdConnectUserInfoController public function testItIsInitializable(): void { $this->assertInstanceOf( - OpenIdConnectUserInfoController::class, + UserInfoController::class, $this->prepareMockedInstance() ); } + /** + * @throws UserNotFound + * @throws OidcServerException + * @throws OAuthServerException + */ public function testItReturnsUserClaims(): void { $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('GET'); @@ -145,13 +131,16 @@ public function testItReturnsUserClaims(): void ->with([], ['mail' => ['userid@localhost.localdomain']]) ->willReturn([]); - /** @psalm-suppress UndefinedMethod */ $this->assertSame( ['email' => 'userid@localhost.localdomain'], $this->prepareMockedInstance()->__invoke($this->serverRequestMock)->getPayload() ); } + /** + * @throws OidcServerException + * @throws OAuthServerException + */ public function testItThrowsIfAccessTokenNotFound(): void { $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('GET'); @@ -183,6 +172,10 @@ public function testItThrowsIfAccessTokenNotFound(): void $this->prepareMockedInstance()->__invoke($this->serverRequestMock); } + /** + * @throws OidcServerException + * @throws OAuthServerException + */ public function testItThrowsIfUserNotFound(): void { $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('GET'); @@ -223,6 +216,11 @@ public function testItThrowsIfUserNotFound(): void $this->prepareMockedInstance()->__invoke($this->serverRequestMock); } + /** + * @throws UserNotFound + * @throws OidcServerException + * @throws OAuthServerException + */ public function testItHandlesCorsRequest(): void { $origin = 'https://example.org'; @@ -241,6 +239,10 @@ public function testItHandlesCorsRequest(): void ); } + /** + * @throws UserNotFound + * @throws OAuthServerException + */ public function testItThrowsIfCorsOriginNotAllowed(): void { $origin = 'https://example.org'; @@ -252,6 +254,10 @@ public function testItThrowsIfCorsOriginNotAllowed(): void $this->prepareMockedInstance()->__invoke($this->serverRequestMock); } + /** + * @throws UserNotFound + * @throws OAuthServerException + */ public function testItThrowsIfOriginHeaderNotAvailable(): void { $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('OPTIONS'); diff --git a/tests/Entity/AccessTokenEntityTest.php b/tests/src/Entities/AccessTokenEntityTest.php similarity index 77% rename from tests/Entity/AccessTokenEntityTest.php rename to tests/src/Entities/AccessTokenEntityTest.php index f5e95250..342923fe 100644 --- a/tests/Entity/AccessTokenEntityTest.php +++ b/tests/src/Entities/AccessTokenEntityTest.php @@ -1,16 +1,20 @@ dirname(__DIR__, 2) . '/docker/ssp/', + 'certdir' => dirname(__DIR__, 3) . '/docker/ssp/', ]; Configuration::loadFromArray($config, '', 'simplesaml'); @@ -74,16 +83,20 @@ protected function setUp(): void $this->state = [ 'id' => $this->id, - 'scopes' => json_encode($this->scopes), + 'scopes' => json_encode($this->scopes, JSON_THROW_ON_ERROR), 'expires_at' => $this->expiresAt, 'user_id' => $this->userId, 'client' => $this->clientEntityStub, 'is_revoked' => $this->isRevoked, 'auth_code_id' => $this->authCodeId, - 'requested_claims' => json_encode($this->requestedClaims) + 'requested_claims' => json_encode($this->requestedClaims, JSON_THROW_ON_ERROR) ]; } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testCanCreateInstanceFromState(): void { $this->assertInstanceOf(AccessTokenEntity::class, AccessTokenEntity::fromState($this->state)); @@ -103,17 +116,25 @@ public function testCanCreateInstanceFromData(): void ); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testHasProperState(): void { $accessTokenEntity = AccessTokenEntity::fromState($this->state); $accessTokenEntityState = $accessTokenEntity->getState(); $this->assertSame($this->id, $accessTokenEntityState['id']); - $this->assertSame(json_encode($this->scopes), $accessTokenEntityState['scopes']); + $this->assertSame(json_encode($this->scopes, JSON_THROW_ON_ERROR), $accessTokenEntityState['scopes']); $this->assertSame($this->requestedClaims, $accessTokenEntity->getRequestedClaims()); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testHasImmutableStringRepresentation(): void { $accessTokenEntity = AccessTokenEntity::fromState($this->state); diff --git a/tests/Entity/AuthCodeEntityTest.php b/tests/src/Entities/AuthCodeEntityTest.php similarity index 70% rename from tests/Entity/AuthCodeEntityTest.php rename to tests/src/Entities/AuthCodeEntityTest.php index 5da4bcf3..f49642ca 100644 --- a/tests/Entity/AuthCodeEntityTest.php +++ b/tests/src/Entities/AuthCodeEntityTest.php @@ -1,22 +1,28 @@ clientEntityMock = $this->createMock(ClientEntity::class); @@ -33,12 +39,20 @@ protected function setUp(): void ]; } + /** + * @throws OidcServerException + * @throws JsonException + */ protected function prepareMockedInstance(array $state = null): AuthCodeEntity { - $state = $state ?? $this->state; + $state ??= $this->state; return AuthCodeEntity::fromState($state); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testItIsInitializable(): void { $this->assertInstanceOf( @@ -47,6 +61,10 @@ public function testItIsInitializable(): void ); } + /** + * @throws JsonException + * @throws OidcServerException + */ public function testCanGetState(): void { $this->assertSame( @@ -64,6 +82,10 @@ public function testCanGetState(): void ); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testCanSetNonce(): void { $authCodeEntity = $this->prepareMockedInstance(); @@ -72,6 +94,10 @@ public function testCanSetNonce(): void $this->assertSame('new_nonce', $authCodeEntity->getNonce()); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testCanBeRevoked(): void { $authCodeEntity = $this->prepareMockedInstance(); diff --git a/tests/Entity/ClientEntityTest.php b/tests/src/Entities/ClientEntityTest.php similarity index 84% rename from tests/Entity/ClientEntityTest.php rename to tests/src/Entities/ClientEntityTest.php index f30ad9bb..e2125713 100644 --- a/tests/Entity/ClientEntityTest.php +++ b/tests/src/Entities/ClientEntityTest.php @@ -1,12 +1,16 @@ state; + $state ??= $this->state; return ClientEntity::fromState($state); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testItIsInitializable(): void { $this->assertInstanceOf( @@ -48,6 +60,10 @@ public function testItIsInitializable(): void ); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testCanGetProperties(): void { $clientEntity = $this->prepareMockedInstance(); @@ -71,6 +87,10 @@ public function testCanGetProperties(): void $this->assertSame('https://localhost/back', $clientEntity->getBackChannelLogoutUri()); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testCanChangeSecret(): void { $clientEntity = $this->prepareMockedInstance(); @@ -79,6 +99,10 @@ public function testCanChangeSecret(): void $this->assertSame($clientEntity->getSecret(), 'new_secret'); } + /** + * @throws JsonException + * @throws OidcServerException + */ public function testCanGetState(): void { $this->assertSame( @@ -100,6 +124,10 @@ public function testCanGetState(): void ); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testCanExportAsArray(): void { $this->assertSame( diff --git a/tests/Entity/RefreshTokenEntityTest.php b/tests/src/Entities/RefreshTokenEntityTest.php similarity index 64% rename from tests/Entity/RefreshTokenEntityTest.php rename to tests/src/Entities/RefreshTokenEntityTest.php index 402c87f8..26265730 100644 --- a/tests/Entity/RefreshTokenEntityTest.php +++ b/tests/src/Entities/RefreshTokenEntityTest.php @@ -1,23 +1,28 @@ accessTokenEntityMock = $this->createMock(AccessTokenEntity::class); @@ -32,12 +37,18 @@ protected function setUp(): void ]; } + /** + * @throws OidcServerException + */ protected function prepareMockedInstance(array $state = null): RefreshTokenEntityInterface { - $state = $state ?? $this->state; + $state ??= $this->state; return RefreshTokenEntity::fromState($state); } + /** + * @throws OidcServerException + */ public function testItIsInitializable(): void { $this->assertInstanceOf( @@ -46,6 +57,9 @@ public function testItIsInitializable(): void ); } + /** + * @throws OidcServerException + */ public function testCanGetState(): void { $this->assertSame( diff --git a/tests/Entity/ScopeEntityTest.php b/tests/src/Entities/ScopeEntityTest.php similarity index 88% rename from tests/Entity/ScopeEntityTest.php rename to tests/src/Entities/ScopeEntityTest.php index 50a7ecb5..a3b6fa19 100644 --- a/tests/Entity/ScopeEntityTest.php +++ b/tests/src/Entities/ScopeEntityTest.php @@ -1,8 +1,10 @@ assertSame('id', $scopeEntity->getIdentifier()); $this->assertSame('description', $scopeEntity->getDescription()); $this->assertSame('icon', $scopeEntity->getIcon()); - $this->assertSame(['attrid' => 'attrval'], $scopeEntity->getAttributes()); + $this->assertSame(['attrid' => 'attrval'], $scopeEntity->getClaims()); $this->assertSame('id', $scopeEntity->jsonSerialize()); } } diff --git a/tests/Entity/UserEntityTest.php b/tests/src/Entities/UserEntityTest.php similarity index 75% rename from tests/Entity/UserEntityTest.php rename to tests/src/Entities/UserEntityTest.php index fa9e3f3b..4c7a81bb 100644 --- a/tests/Entity/UserEntityTest.php +++ b/tests/src/Entities/UserEntityTest.php @@ -1,12 +1,16 @@ state; + $state ??= $this->state; return UserEntity::fromState($state); } + /** + * @throws OidcServerException + * @throws Exception + */ public function testItIsInitializable(): void { $this->assertInstanceOf( @@ -41,6 +52,10 @@ public function testItIsInitializable(): void ); } + /** + * @throws OidcServerException + * @throws Exception + */ public function testCanGetProperties(): void { $userEntity = $this->prepareMockedInstance(); @@ -53,6 +68,9 @@ public function testCanGetProperties(): void $this->assertSame($userEntity->getClaims(), ['claim']); } + /** + * @throws OidcServerException + */ public function testCanGetState(): void { $this->assertSame( diff --git a/tests/Factories/AuthSimpleFactoryTest.php b/tests/src/Factories/AuthSimpleFactoryTest.php similarity index 81% rename from tests/Factories/AuthSimpleFactoryTest.php rename to tests/src/Factories/AuthSimpleFactoryTest.php index 0e340031..2463ee09 100644 --- a/tests/Factories/AuthSimpleFactoryTest.php +++ b/tests/src/Factories/AuthSimpleFactoryTest.php @@ -1,5 +1,7 @@ markTestIncomplete(); } diff --git a/tests/Factories/ClaimTranslatorExtractorFactoryTest.php b/tests/src/Factories/ClaimTranslatorExtractorFactoryTest.php similarity index 80% rename from tests/Factories/ClaimTranslatorExtractorFactoryTest.php rename to tests/src/Factories/ClaimTranslatorExtractorFactoryTest.php index 04d13186..b77f4060 100644 --- a/tests/Factories/ClaimTranslatorExtractorFactoryTest.php +++ b/tests/src/Factories/ClaimTranslatorExtractorFactoryTest.php @@ -1,33 +1,37 @@ configurationServiceMock = $this->createMock(ConfigurationService::class); - $this->configurationServiceMock - ->method('getOpenIDConnectConfiguration') + $this->moduleConfigMock = $this->createMock(ModuleConfig::class); + $this->moduleConfigMock + ->method('config') ->willReturn( Configuration::loadFromArray( [ - 'useridattr' => 'uid', - 'translate' => [ + ModuleConfig::OPTION_AUTH_USER_IDENTIFIER_ATTRIBUTE => 'uid', + ModuleConfig::OPTION_AUTH_SAML_TO_OIDC_TRANSLATE_TABLE => [ 'testClaim' => ['attribute'], 'intClaim' => [ 'type' => 'int', @@ -42,7 +46,7 @@ protected function setUp(): void ] ) ); - $this->configurationServiceMock + $this->moduleConfigMock ->method('getOpenIDPrivateScopes') ->willReturn( [ @@ -63,7 +67,7 @@ protected function setUp(): void protected function prepareMockedInstance(): ClaimTranslatorExtractorFactory { - return new ClaimTranslatorExtractorFactory($this->configurationServiceMock); + return new ClaimTranslatorExtractorFactory($this->moduleConfigMock); } public function testCanCreateInstance(): void @@ -74,6 +78,9 @@ public function testCanCreateInstance(): void ); } + /** + * @throws \Exception + */ public function testCanBuildClaimTranslatorExtractor(): void { $this->assertInstanceOf( @@ -82,11 +89,13 @@ public function testCanBuildClaimTranslatorExtractor(): void ); } + /** + * @throws \Exception + */ public function testExtractor(): void { $claimTranslatorExtractor = $this->prepareMockedInstance()->build(); - /** @psalm-suppress PossiblyNullReference */ $this->assertSame( $claimTranslatorExtractor->getClaimSet('customScope2')->getClaims(), ['myprefix_testClaim2', 'myprefix_boolClaim'] diff --git a/tests/src/Forms/ClientFormTest.php b/tests/src/Forms/ClientFormTest.php new file mode 100644 index 00000000..244ca520 --- /dev/null +++ b/tests/src/Forms/ClientFormTest.php @@ -0,0 +1,19 @@ +markTestIncomplete(); + } +} diff --git a/tests/src/ModuleConfigTest.php b/tests/src/ModuleConfigTest.php new file mode 100644 index 00000000..f1a45c08 --- /dev/null +++ b/tests/src/ModuleConfigTest.php @@ -0,0 +1,57 @@ + $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() + ); + + // 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 + ); + $moduleConfig = new ModuleConfig(); + $this->assertEquals($certDir . 'myCertificate.crt', $moduleConfig->getCertPath()); + $this->assertEquals($certDir . 'myPrivateKey.key', $moduleConfig->getPrivateKeyPath()); + } +} diff --git a/tests/Repositories/AccessTokenRepositoryTest.php b/tests/src/Repositories/AccessTokenRepositoryTest.php similarity index 67% rename from tests/Repositories/AccessTokenRepositoryTest.php rename to tests/src/Repositories/AccessTokenRepositoryTest.php index de6b213a..4a7327cf 100644 --- a/tests/Repositories/AccessTokenRepositoryTest.php +++ b/tests/src/Repositories/AccessTokenRepositoryTest.php @@ -1,5 +1,7 @@ migrate(); - $configurationService = new ConfigurationService(); + $moduleConfig = new ModuleConfig(); $client = ClientRepositoryTest::getClient(self::CLIENT_ID); - (new ClientRepository($configurationService))->add($client); + (new ClientRepository($moduleConfig))->add($client); $user = UserEntity::fromData(self::USER_ID); - (new UserRepository($configurationService))->add($user); + (new UserRepository($moduleConfig))->add($user); - self::$repository = new AccessTokenRepository($configurationService); + self::$repository = new AccessTokenRepository($moduleConfig); } public function testGetTableName(): void @@ -68,6 +75,13 @@ public function testGetTableName(): void $this->assertSame('phpunit_oidc_access_token', self::$repository->getTableName()); } + /** + * @throws UniqueTokenIdentifierConstraintViolationException + * @throws Error + * @throws OidcServerException + * @throws JsonException + * @throws Exception + */ public function testAddAndFound(): void { $scopes = [ @@ -80,7 +94,7 @@ public function testAddAndFound(): void self::USER_ID ); $accessToken->setIdentifier(self::ACCESS_TOKEN_ID); - $accessToken->setExpiryDateTime(\DateTimeImmutable::createFromMutable( + $accessToken->setExpiryDateTime(DateTimeImmutable::createFromMutable( TimestampGenerator::utc('yesterday') )); @@ -91,6 +105,9 @@ public function testAddAndFound(): void $this->assertEquals($accessToken, $foundAccessToken); } + /** + * @throws OidcServerException + */ public function testAddAndNotFound(): void { $notFoundAccessToken = self::$repository->findById('notoken'); @@ -98,6 +115,10 @@ public function testAddAndNotFound(): void $this->assertNull($notFoundAccessToken); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testRevokeToken(): void { self::$repository->revokeAccessToken(self::ACCESS_TOKEN_ID); @@ -106,20 +127,31 @@ public function testRevokeToken(): void $this->assertTrue($isRevoked); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testErrorRevokeInvalidToken(): void { - $this->expectException(\RuntimeException::class); + $this->expectException(Exception::class); self::$repository->revokeAccessToken('notoken'); } + /** + * @throws OidcServerException + */ public function testErrorCheckIsRevokedInvalidToken(): void { - $this->expectException(\RuntimeException::class); + $this->expectException(Exception::class); self::$repository->isAccessTokenRevoked('notoken'); } + /** + * @throws OidcServerException + * @throws Exception + */ public function testRemoveExpired(): void { self::$repository->removeExpired(); diff --git a/tests/Repositories/AllowedOriginRepositoryTest.php b/tests/src/Repositories/AllowedOriginRepositoryTest.php similarity index 79% rename from tests/Repositories/AllowedOriginRepositoryTest.php rename to tests/src/Repositories/AllowedOriginRepositoryTest.php index 00a17019..2fedf975 100644 --- a/tests/Repositories/AllowedOriginRepositoryTest.php +++ b/tests/src/Repositories/AllowedOriginRepositoryTest.php @@ -1,12 +1,15 @@ migrate(); - self::$configurationService = new ConfigurationService(); + self::$moduleConfig = new ModuleConfig(); $client = ClientRepositoryTest::getClient(self::CLIENT_ID); - (new ClientRepository(self::$configurationService))->add($client); + (new ClientRepository(self::$moduleConfig))->add($client); - self::$repository = new AllowedOriginRepository(self::$configurationService); + self::$repository = new AllowedOriginRepository(self::$moduleConfig); } public function tearDown(): void diff --git a/tests/Repositories/AuthCodeRepositoryTest.php b/tests/src/Repositories/AuthCodeRepositoryTest.php similarity index 69% rename from tests/Repositories/AuthCodeRepositoryTest.php rename to tests/src/Repositories/AuthCodeRepositoryTest.php index b190a55d..5efa3e51 100644 --- a/tests/Repositories/AuthCodeRepositoryTest.php +++ b/tests/src/Repositories/AuthCodeRepositoryTest.php @@ -1,5 +1,7 @@ migrate(); - $configurationService = new ConfigurationService(); + $moduleConfig = new ModuleConfig(); $client = ClientRepositoryTest::getClient(self::CLIENT_ID); - (new ClientRepository($configurationService))->add($client); + (new ClientRepository($moduleConfig))->add($client); $user = UserEntity::fromData(self::USER_ID); - (new UserRepository($configurationService))->add($user); + (new UserRepository($moduleConfig))->add($user); - self::$repository = new AuthCodeRepository($configurationService); + self::$repository = new AuthCodeRepository($moduleConfig); } public function testGetTableName(): void @@ -69,6 +75,13 @@ public function testGetTableName(): void $this->assertSame('phpunit_oidc_auth_code', self::$repository->getTableName()); } + /** + * @throws UniqueTokenIdentifierConstraintViolationException + * @throws Error + * @throws JsonException + * @throws Exception + * @throws Exception + */ public function testAddAndFound(): void { $scopes = [ @@ -80,7 +93,7 @@ public function testAddAndFound(): void $authCode->setIdentifier(self::AUTH_CODE_ID); $authCode->setClient(ClientRepositoryTest::getClient(self::CLIENT_ID)); $authCode->setUserIdentifier(self::USER_ID); - $authCode->setExpiryDateTime(\DateTimeImmutable::createFromMutable(TimestampGenerator::utc('yesterday'))); + $authCode->setExpiryDateTime(DateTimeImmutable::createFromMutable(TimestampGenerator::utc('yesterday'))); $authCode->setRedirectUri(self::REDIRECT_URI); foreach ($scopes as $scope) { $authCode->addScope($scope); @@ -93,6 +106,9 @@ public function testAddAndFound(): void $this->assertEquals($authCode, $foundAuthCode); } + /** + * @throws Exception + */ public function testAddAndNotFound(): void { $notFoundAuthCode = self::$repository->findById('nocode'); @@ -100,6 +116,10 @@ public function testAddAndNotFound(): void $this->assertNull($notFoundAuthCode); } + /** + * @throws JsonException + * @throws Exception + */ public function testRevokeCode(): void { self::$repository->revokeAuthCode(self::AUTH_CODE_ID); @@ -108,20 +128,26 @@ public function testRevokeCode(): void $this->assertTrue($isRevoked); } + /** + * @throws JsonException + */ public function testErrorRevokeInvalidAuthCode(): void { - $this->expectException(\RuntimeException::class); + $this->expectException(Exception::class); self::$repository->revokeAuthCode('nocode'); } public function testErrorCheckIsRevokedInvalidAuthCode(): void { - $this->expectException(\RuntimeException::class); + $this->expectException(Exception::class); self::$repository->isAuthCodeRevoked('nocode'); } + /** + * @throws Exception + */ public function testRemoveExpired(): void { self::$repository->removeExpired(); diff --git a/tests/Repositories/ClientRepositoryTest.php b/tests/src/Repositories/ClientRepositoryTest.php similarity index 83% rename from tests/Repositories/ClientRepositoryTest.php rename to tests/src/Repositories/ClientRepositoryTest.php index 95d15541..b8b7ec3c 100644 --- a/tests/Repositories/ClientRepositoryTest.php +++ b/tests/src/Repositories/ClientRepositoryTest.php @@ -1,5 +1,7 @@ migrate(); - $configurationService = new ConfigurationService(); + $moduleConfig = new ModuleConfig(); - self::$repository = new ClientRepository($configurationService); + self::$repository = new ClientRepository($moduleConfig); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function tearDown(): void { $clients = self::$repository->findAll(); @@ -66,6 +74,10 @@ public function testGetTableName(): void $this->assertSame('phpunit_oidc_client', self::$repository->getTableName()); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testAddAndFound(): void { $client = self::getClient('clientid'); @@ -75,6 +87,10 @@ public function testAddAndFound(): void $this->assertEquals($client, $foundClient); } + /** + * @throws OAuthServerException + * @throws JsonException + */ public function testGetClientEntity(): void { $client = self::getClient('clientid'); @@ -84,6 +100,9 @@ public function testGetClientEntity(): void $this->assertNotNull($client); } + /** + * @throws JsonException + */ public function testGetDisabledClientEntity(): void { $this->expectException(OAuthServerException::class); @@ -91,9 +110,13 @@ public function testGetDisabledClientEntity(): void $client = self::getClient('clientid', false); self::$repository->add($client); - $client = self::$repository->getClientEntity('clientid'); + self::$repository->getClientEntity('clientid'); } + /** + * @throws OAuthServerException + * @throws JsonException + */ public function testNotFoundClient(): void { $client = self::$repository->getClientEntity('unknownid'); @@ -101,6 +124,10 @@ public function testNotFoundClient(): void $this->assertNull($client); } + /** + * @throws OAuthServerException + * @throws JsonException + */ public function testValidateConfidentialClient(): void { $client = self::getClient('clientid', true, true); @@ -110,15 +137,23 @@ public function testValidateConfidentialClient(): void $this->assertTrue($validate); } + /** + * @throws OAuthServerException + * @throws JsonException + */ public function testValidatePublicClient(): void { - $client = self::getClient('clientid', true); + $client = self::getClient('clientid'); self::$repository->add($client); $validate = self::$repository->validateClient('clientid', null, null); $this->assertTrue($validate); } + /** + * @throws OAuthServerException + * @throws JsonException + */ public function testNotValidateConfidentialClientWithWrongSecret() { $client = self::getClient('clientid', true, true); @@ -128,12 +163,20 @@ public function testNotValidateConfidentialClientWithWrongSecret() $this->assertFalse($validate); } + /** + * @throws OAuthServerException + * @throws JsonException + */ public function testNotValidateWhenClientDoesNotExists() { $validate = self::$repository->validateClient('clientid', 'wrongclientsecret', null); $this->assertFalse($validate); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testFindAll(): void { $client = self::getClient('clientid'); @@ -144,13 +187,16 @@ public function testFindAll(): void $this->assertInstanceOf(ClientEntity::class, current($clients)); } + /** + * @throws Exception + */ public function testFindPaginated(): void { array_map(function ($i) { self::$repository->add(self::getClient('clientid' . $i)); }, range(1, 21)); - $clientPageOne = self::$repository->findPaginated(1); + $clientPageOne = self::$repository->findPaginated(); self::assertCount(20, $clientPageOne['items']); self::assertEquals(2, $clientPageOne['numPages']); self::assertEquals(1, $clientPageOne['currentPage']); @@ -160,6 +206,9 @@ public function testFindPaginated(): void self::assertEquals(2, $clientPageTwo['currentPage']); } + /** + * @throws Exception + */ public function testFindPageInRange(): void { array_map(function ($i) { @@ -172,6 +221,9 @@ public function testFindPageInRange(): void self::assertEquals(2, $clientPageOne['currentPage']); } + /** + * @throws Exception + */ public function testFindPaginationWithEmptyList() { $clientPageOne = self::$repository->findPaginated(0); @@ -180,6 +232,10 @@ public function testFindPaginationWithEmptyList() self::assertCount(0, $clientPageOne['items']); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testUpdate(): void { $client = self::getClient('clientid'); @@ -203,19 +259,28 @@ public function testUpdate(): void $this->assertEquals($client, $foundClient); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testDelete(): void { $client = self::getClient('clientid'); self::$repository->add($client); $client = self::$repository->findById('clientid'); - /** @psalm-suppress PossiblyNullArgument */ self::$repository->delete($client); $foundClient = self::$repository->findById('clientid'); $this->assertNull($foundClient); } + /** + * @throws JsonException + * @throws OidcServerException + * @throws Exception + * @throws Exception + */ public function testCrudWithOwner(): void { $owner = 'homer@example.com'; diff --git a/tests/Repositories/RefreshTokenRepositoryTest.php b/tests/src/Repositories/RefreshTokenRepositoryTest.php similarity index 60% rename from tests/Repositories/RefreshTokenRepositoryTest.php rename to tests/src/Repositories/RefreshTokenRepositoryTest.php index c884e21e..08ae8eca 100644 --- a/tests/Repositories/RefreshTokenRepositoryTest.php +++ b/tests/src/Repositories/RefreshTokenRepositoryTest.php @@ -1,5 +1,7 @@ migrate(); - $configurationService = new ConfigurationService(); + $moduleConfig = new ModuleConfig(); $client = ClientRepositoryTest::getClient(self::CLIENT_ID); - (new ClientRepository($configurationService))->add($client); + (new ClientRepository($moduleConfig))->add($client); $user = UserEntity::fromData(self::USER_ID); - (new UserRepository($configurationService))->add($user); + (new UserRepository($moduleConfig))->add($user); $accessToken = AccessTokenEntity::fromData($client, [], self::USER_ID); $accessToken->setIdentifier(self::ACCESS_TOKEN_ID); - $accessToken->setExpiryDateTime(new \DateTimeImmutable('yesterday')); - (new AccessTokenRepository($configurationService))->persistNewAccessToken($accessToken); + $accessToken->setExpiryDateTime(new DateTimeImmutable('yesterday')); + (new AccessTokenRepository($moduleConfig))->persistNewAccessToken($accessToken); - self::$repository = new RefreshTokenRepository($configurationService); + self::$repository = new RefreshTokenRepository($moduleConfig); } public function testGetTableName(): void @@ -75,15 +87,21 @@ public function testGetTableName(): void $this->assertSame('phpunit_oidc_refresh_token', self::$repository->getTableName()); } + /** + * @throws UniqueTokenIdentifierConstraintViolationException + * @throws OidcServerException + * @throws OAuthServerException + * @throws Exception + * @throws Exception + */ public function testAddAndFound(): void { - $configurationService = new ConfigurationService(); - $accessToken = (new AccessTokenRepository($configurationService))->findById(self::ACCESS_TOKEN_ID); + $moduleConfig = new ModuleConfig(); + $accessToken = (new AccessTokenRepository($moduleConfig))->findById(self::ACCESS_TOKEN_ID); $refreshToken = self::$repository->getNewRefreshToken(); $refreshToken->setIdentifier(self::REFRESH_TOKEN_ID); - $refreshToken->setExpiryDateTime(\DateTimeImmutable::createFromMutable(TimestampGenerator::utc('yesterday'))); - /** @psalm-suppress PossiblyNullArgument */ + $refreshToken->setExpiryDateTime(DateTimeImmutable::createFromMutable(TimestampGenerator::utc('yesterday'))); $refreshToken->setAccessToken($accessToken); self::$repository->persistNewRefreshToken($refreshToken); @@ -93,6 +111,9 @@ public function testAddAndFound(): void $this->assertEquals($refreshToken, $foundRefreshToken); } + /** + * @throws OidcServerException + */ public function testAddAndNotFound(): void { $notFoundRefreshToken = self::$repository->findById('notoken'); @@ -100,6 +121,9 @@ public function testAddAndNotFound(): void $this->assertNull($notFoundRefreshToken); } + /** + * @throws OidcServerException + */ public function testRevokeToken(): void { self::$repository->revokeRefreshToken(self::REFRESH_TOKEN_ID); @@ -108,20 +132,30 @@ public function testRevokeToken(): void $this->assertTrue($isRevoked); } + /** + * @throws OidcServerException + */ public function testErrorRevokeInvalidToken(): void { - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); self::$repository->revokeRefreshToken('notoken'); } + /** + * @throws OidcServerException + */ public function testErrorCheckIsRevokedInvalidToken(): void { - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); self::$repository->isRefreshTokenRevoked('notoken'); } + /** + * @throws OidcServerException + * @throws Exception + */ public function testRemoveExpired(): void { self::$repository->removeExpired(); diff --git a/tests/Repositories/ScopeRepositoryTest.php b/tests/src/Repositories/ScopeRepositoryTest.php similarity index 80% rename from tests/Repositories/ScopeRepositoryTest.php rename to tests/src/Repositories/ScopeRepositoryTest.php index d3728269..822e7348 100644 --- a/tests/Repositories/ScopeRepositoryTest.php +++ b/tests/src/Repositories/ScopeRepositoryTest.php @@ -1,5 +1,7 @@ migrate(); } + /** + * @throws Exception + */ public function testGetScopeEntityByIdentifier(): void { - $scopeRepository = new ScopeRepository(new ConfigurationService()); + $scopeRepository = new ScopeRepository(new ModuleConfig()); $scope = $scopeRepository->getScopeEntityByIdentifier('openid'); @@ -57,16 +61,22 @@ public function testGetScopeEntityByIdentifier(): void $this->assertEquals($expected, $scope); } + /** + * @throws Exception + */ public function testGetUnknownScope(): void { - $scopeRepository = new ScopeRepository(new ConfigurationService()); + $scopeRepository = new ScopeRepository(new ModuleConfig()); $this->assertNull($scopeRepository->getScopeEntityByIdentifier('none')); } + /** + * @throws Exception + */ public function testFinalizeScopes(): void { - $scopeRepository = new ScopeRepository(new ConfigurationService()); + $scopeRepository = new ScopeRepository(new ModuleConfig()); $scopes = [ ScopeEntity::fromData('openid'), ScopeEntity::fromData('basic'), diff --git a/tests/Repositories/UserRepositoryTest.php b/tests/src/Repositories/UserRepositoryTest.php similarity index 79% rename from tests/Repositories/UserRepositoryTest.php rename to tests/src/Repositories/UserRepositoryTest.php index d8c0cd9f..53c590ea 100644 --- a/tests/Repositories/UserRepositoryTest.php +++ b/tests/src/Repositories/UserRepositoryTest.php @@ -1,5 +1,7 @@ migrate(); - $configurationService = new ConfigurationService(); + $moduleConfig = new ModuleConfig(); - self::$repository = new UserRepository($configurationService); + self::$repository = new UserRepository($moduleConfig); } public function testGetTableName(): void @@ -55,16 +58,22 @@ public function testGetTableName(): void $this->assertSame('phpunit_oidc_user', self::$repository->getTableName()); } + /** + * @throws OidcServerException + * @throws Exception + */ public function testAddAndFound(): void { self::$repository->add(UserEntity::fromData('uniqueid')); $user = self::$repository->getUserEntityByIdentifier('uniqueid'); $this->assertNotNull($user); - /** @psalm-suppress PossiblyNullReference */ $this->assertSame($user->getIdentifier(), 'uniqueid'); } + /** + * @throws OidcServerException + */ public function testNotFound(): void { $user = self::$repository->getUserEntityByIdentifier('unknownid'); @@ -72,10 +81,13 @@ public function testNotFound(): void $this->assertNull($user); } + /** + * @throws OidcServerException + * @throws Exception + */ public function testUpdate(): void { $user = self::$repository->getUserEntityByIdentifier('uniqueid'); - /** @psalm-suppress PossiblyNullReference */ $user->setClaims(['uid' => ['johndoe']]); self::$repository->update($user); @@ -83,10 +95,12 @@ public function testUpdate(): void $this->assertNotSame($user, $user2); } + /** + * @throws OidcServerException + */ public function testDelete(): void { $user = self::$repository->getUserEntityByIdentifier('uniqueid'); - /** @psalm-suppress PossiblyNullArgument */ self::$repository->delete($user); $user = self::$repository->getUserEntityByIdentifier('uniqueid'); diff --git a/tests/Server/Associations/RelyingPartyAssociationTest.php b/tests/src/Server/Associations/RelyingPartyAssociationTest.php similarity index 98% rename from tests/Server/Associations/RelyingPartyAssociationTest.php rename to tests/src/Server/Associations/RelyingPartyAssociationTest.php index 041ed6f4..57d673d8 100644 --- a/tests/Server/Associations/RelyingPartyAssociationTest.php +++ b/tests/src/Server/Associations/RelyingPartyAssociationTest.php @@ -1,5 +1,7 @@ markTestIncomplete(); } diff --git a/tests/Server/Grants/AuthCodeGrantTest.php b/tests/src/Server/Grants/AuthCodeGrantTest.php similarity index 65% rename from tests/Server/Grants/AuthCodeGrantTest.php rename to tests/src/Server/Grants/AuthCodeGrantTest.php index a309c619..2b185b54 100644 --- a/tests/Server/Grants/AuthCodeGrantTest.php +++ b/tests/src/Server/Grants/AuthCodeGrantTest.php @@ -1,13 +1,18 @@ authCodeRepositoryStub = $this->createStub(AuthCodeRepositoryInterface::class); $this->accessTokenRepositoryStub = $this->createStub(AccessTokenRepositoryInterface::class); $this->refreshTokenRepositoryStub = $this->createStub(RefreshTokenRepositoryInterface::class); - $this->authCodeTtl = new \DateInterval('PT1M'); + $this->authCodeTtl = new DateInterval('PT1M'); $this->requestRulesManagerStub = $this->createStub(RequestRulesManager::class); - $this->configurationServiceStub = $this->createStub(ConfigurationService::class); + $this->moduleConfigStub = $this->createStub(ModuleConfig::class); } + /** + * @throws \Exception + */ public function testCanCreateInstance(): void { $this->assertInstanceOf( @@ -57,7 +53,6 @@ public function testCanCreateInstance(): void $this->refreshTokenRepositoryStub, $this->authCodeTtl, $this->requestRulesManagerStub, - $this->configurationServiceStub ) ); } diff --git a/tests/Server/Grants/ImplicitGrantTest.php b/tests/src/Server/Grants/ImplicitGrantTest.php similarity index 81% rename from tests/Server/Grants/ImplicitGrantTest.php rename to tests/src/Server/Grants/ImplicitGrantTest.php index 224fa39e..ccd35463 100644 --- a/tests/Server/Grants/ImplicitGrantTest.php +++ b/tests/src/Server/Grants/ImplicitGrantTest.php @@ -1,5 +1,7 @@ markTestIncomplete(); } diff --git a/tests/Server/Grants/OAuth2ImplicitGrantTest.php b/tests/src/Server/Grants/OAuth2ImplicitGrantTest.php similarity index 82% rename from tests/Server/Grants/OAuth2ImplicitGrantTest.php rename to tests/src/Server/Grants/OAuth2ImplicitGrantTest.php index 8b1132ab..4c6df02e 100644 --- a/tests/Server/Grants/OAuth2ImplicitGrantTest.php +++ b/tests/src/Server/Grants/OAuth2ImplicitGrantTest.php @@ -1,5 +1,7 @@ markTestIncomplete(); } diff --git a/tests/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php b/tests/src/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php similarity index 83% rename from tests/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php rename to tests/src/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php index d544329a..deb9ad52 100644 --- a/tests/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php +++ b/tests/src/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php @@ -1,7 +1,12 @@ logoutTokenBuilderMock = $this->createMock(LogoutTokenBuilder::class); @@ -35,6 +43,9 @@ public function setUp(): void $this->sampleRelyingPartyAssociation[] = $this->getSampleRelyingPartyAssociation(); } + /** + * @throws OAuthServerException + */ public function testLogsErrorForInvalidUri(): void { $this->loggerServiceMock @@ -47,6 +58,9 @@ public function testLogsErrorForInvalidUri(): void $handler->handle($this->sampleRelyingPartyAssociation); } + /** + * @throws OAuthServerException + */ public function testLogsNoticeForSuccessfulResponse(): void { $mockHandler = new MockHandler([ diff --git a/tests/Server/RequestTypes/AuthorizationRequestTest.php b/tests/src/Server/RequestTypes/AuthorizationRequestTest.php similarity index 83% rename from tests/Server/RequestTypes/AuthorizationRequestTest.php rename to tests/src/Server/RequestTypes/AuthorizationRequestTest.php index 8be452d7..2ec0e99b 100644 --- a/tests/Server/RequestTypes/AuthorizationRequestTest.php +++ b/tests/src/Server/RequestTypes/AuthorizationRequestTest.php @@ -1,5 +1,7 @@ markTestIncomplete(); } diff --git a/tests/Server/RequestTypes/LogoutRequestTest.php b/tests/src/Server/RequestTypes/LogoutRequestTest.php similarity index 92% rename from tests/Server/RequestTypes/LogoutRequestTest.php rename to tests/src/Server/RequestTypes/LogoutRequestTest.php index bc4cb883..319383f6 100644 --- a/tests/Server/RequestTypes/LogoutRequestTest.php +++ b/tests/src/Server/RequestTypes/LogoutRequestTest.php @@ -1,8 +1,12 @@ idTokenHintStub = $this->createStub(UnencryptedToken::class); diff --git a/tests/Server/ResponseTypes/IdTokenResponseTest.php b/tests/src/Server/ResponseTypes/IdTokenResponseTest.php similarity index 80% rename from tests/Server/ResponseTypes/IdTokenResponseTest.php rename to tests/src/Server/ResponseTypes/IdTokenResponseTest.php index 9c9b3ef9..09fc0a3b 100644 --- a/tests/Server/ResponseTypes/IdTokenResponseTest.php +++ b/tests/src/Server/ResponseTypes/IdTokenResponseTest.php @@ -1,7 +1,10 @@ certFolder = dirname(__DIR__, 3) . '/docker/ssp/'; + $this->certFolder = dirname(__DIR__, 4) . '/docker/ssp/'; $this->userEntity = UserEntity::fromData(self::SUBJECT, [ 'cn' => ['Homer Simpson'], 'mail' => ['myEmail@example.com'] @@ -66,7 +75,7 @@ protected function setUp(): void ScopeEntity::fromData('openid'), ScopeEntity::fromData('email'), ]; - $this->expiration = (new \DateTimeImmutable())->setTimestamp(time() + 3600); + $this->expiration = (new DateTimeImmutable())->setTimestamp(time() + 3600); $this->clientEntityMock = $this->createMock(ClientEntity::class); $this->clientEntityMock->method('getIdentifier')->willReturn(self::CLIENT_ID); @@ -84,24 +93,24 @@ protected function setUp(): void ->with(self::SUBJECT) ->willReturn($this->userEntity); - $this->configurationServiceMock = $this->createMock(ConfigurationService::class); - $this->configurationServiceMock->method('getSigner')->willReturn(new Sha256()); - $this->configurationServiceMock->method('getSimpleSAMLSelfURLHost')->willReturn(self::ISSUER); - $this->configurationServiceMock->method('getCertPath') + $this->moduleConfigMock = $this->createMock(ModuleConfig::class); + $this->moduleConfigMock->method('getSigner')->willReturn(new Sha256()); + $this->moduleConfigMock->method('getSimpleSAMLSelfURLHost')->willReturn(self::ISSUER); + $this->moduleConfigMock->method('getCertPath') ->willReturn($this->certFolder . '/oidc_module.crt'); - $this->configurationServiceMock->method('getPrivateKeyPath') + $this->moduleConfigMock->method('getPrivateKeyPath') ->willReturn($this->certFolder . '/oidc_module.key'); - $this->configurationServiceMock + $this->moduleConfigMock ->expects($this->atLeast(1)) ->method('getPrivateKeyPassPhrase'); $this->sspConfigurationMock = $this->createMock(Configuration::class); - $this->configurationServiceMock->method('getOpenIDConnectConfiguration') + $this->moduleConfigMock->method('config') ->willReturn($this->sspConfigurationMock); $this->privateKey = new CryptKey($this->certFolder . '/oidc_module.key', null, false); $this->idTokenBuilder = new IdTokenBuilder( - new JsonWebTokenBuilderService($this->configurationServiceMock), + new JsonWebTokenBuilderService($this->moduleConfigMock), new ClaimTranslatorExtractor(self::USER_ID_ATTR) ); } @@ -110,11 +119,10 @@ protected function prepareMockedInstance(): IdTokenResponse { $idTokenResponse = new IdTokenResponse( $this->identityProviderMock, - $this->configurationServiceMock, - $this->idTokenBuilder + $this->idTokenBuilder, + $this->privateKey, ); - $idTokenResponse->setPrivateKey($this->privateKey); $idTokenResponse->setNonce(null); $idTokenResponse->setAuthTime(null); $idTokenResponse->setAcr(null); @@ -131,6 +139,9 @@ public function testItIsInitializable(): void ); } + /** + * @throws Exception + */ public function testItCanGenerateResponse(): void { $this->accessTokenEntityMock->method('getRequestedClaims')->willReturn([]); @@ -144,6 +155,9 @@ public function testItCanGenerateResponse(): void $this->assertTrue($this->shouldHaveValidIdToken($body)); } + /** + * @throws Exception + */ public function testItCanGenerateResponseWithIndividualRequestedClaims(): void { $idTokenResponse = $this->prepareMockedInstance(); @@ -214,7 +228,6 @@ protected function shouldHaveValidIdToken(string $body, $expectedClaims = []): b /** @var Plain $token */ $token = (new Parser(new JoseEncoder()))->parse($result['id_token']); - /** @psalm-suppress ArgumentTypeCoercion */ $validator->assert( $token, new IdentifiedBy(self::TOKEN_ID), @@ -234,12 +247,8 @@ protected function shouldHaveValidIdToken(string $body, $expectedClaims = []): b ); } $expectedClaimsKeys = array_keys($expectedClaims); - $expectedClaimsKeys = array_merge( - ['iss', 'iat', 'jti', 'aud', 'nbf', 'exp', 'sub', 'at_hash'], - $expectedClaimsKeys - ); + $expectedClaimsKeys = ['iss', 'iat', 'jti', 'aud', 'nbf', 'exp', 'sub', 'at_hash', ...$expectedClaimsKeys]; $claims = array_keys($token->claims()->all()); - /** @psalm-suppress DocblockTypeContradiction */ if ($claims !== $expectedClaimsKeys) { throw new Exception( 'missing expected claim. Got ' . var_export($claims, true) @@ -259,7 +268,7 @@ protected function shouldHaveValidIdToken(string $body, $expectedClaims = []): b $dateWithNoMicroseconds = ['nbf', 'exp', 'iat']; foreach ($dateWithNoMicroseconds as $key) { /** - * @var DateTimeImmutable + * @var DateTimeImmutable $val */ $val = $token->claims()->get($key); //Get format representing microseconds diff --git a/tests/Server/Validators/BearerTokenValidatorTest.php b/tests/src/Server/Validators/BearerTokenValidatorTest.php similarity index 80% rename from tests/Server/Validators/BearerTokenValidatorTest.php rename to tests/src/Server/Validators/BearerTokenValidatorTest.php index 66a28d07..63a3ac0d 100644 --- a/tests/Server/Validators/BearerTokenValidatorTest.php +++ b/tests/src/Server/Validators/BearerTokenValidatorTest.php @@ -1,21 +1,27 @@ accessTokenRepositoryStub = $this->createStub(AccessTokenRepository::class); $this->serverRequest = new ServerRequest(); - $this->bearerTokenValidator = new BearerTokenValidator($this->accessTokenRepositoryStub); - $this->bearerTokenValidator->setPublicKey(self::$publicCryptKey); + $this->bearerTokenValidator = new BearerTokenValidator($this->accessTokenRepositoryStub, self::$publicCryptKey); } + /** + * @throws OidcServerException + * @throws JsonException + */ public static function setUpBeforeClass(): void { // To make lib/SimpleSAML/Utils/HTTP::getSelfURL() work... @@ -126,8 +88,8 @@ public static function setUpBeforeClass(): void file_put_contents(self::$publicKeyPath, self::$publicKey); file_put_contents(self::$privateKeyPath, self::$privateKey); - \chmod(self::$publicKeyPath, 0600); - \chmod(self::$privateKeyPath, 0600); + chmod(self::$publicKeyPath, 0600); + chmod(self::$privateKeyPath, 0600); self::$publicCryptKey = new CryptKey(self::$publicKeyPath); self::$privateCryptKey = new CryptKey(self::$privateKeyPath); @@ -174,6 +136,9 @@ public function testValidatorThrowsForNonExistentAccessToken() $this->bearerTokenValidator->validateAuthorization($this->serverRequest); } + /** + * @throws OidcServerException + */ public function testValidatesForAuthorizationHeader() { $serverRequest = $this->serverRequest->withAddedHeader('Authorization', 'Bearer ' . self::$accessToken); @@ -186,6 +151,9 @@ public function testValidatesForAuthorizationHeader() ); } + /** + * @throws OidcServerException + */ public function testValidatesForPostBodyParam() { $bodyArray = ['access_token' => self::$accessToken]; @@ -214,6 +182,10 @@ public function testThrowsForUnparsableAccessToken() $this->bearerTokenValidator->validateAuthorization($serverRequest); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testThrowsForExpiredAccessToken() { $accessTokenState = self::$accessTokenState; @@ -231,13 +203,17 @@ public function testThrowsForExpiredAccessToken() $this->bearerTokenValidator->validateAuthorization($serverRequest); } + /** + * @throws OidcServerException|\Exception + */ public function testThrowsForRevokedAccessToken() { - /** @psalm-suppress UndefinedInterfaceMethod */ $this->accessTokenRepositoryStub->method('isAccessTokenRevoked')->willReturn(true); - $bearerTokenValidator = new BearerTokenValidator($this->accessTokenRepositoryStub); - $bearerTokenValidator->setPublicKey(self::$publicCryptKey); + $bearerTokenValidator = new BearerTokenValidator( + $this->accessTokenRepositoryStub, + self::$publicCryptKey, + ); $serverRequest = $this->serverRequest->withAddedHeader('Authorization', 'Bearer ' . self::$accessToken); @@ -246,6 +222,10 @@ public function testThrowsForRevokedAccessToken() $bearerTokenValidator->validateAuthorization($serverRequest); } + /** + * @throws OidcServerException + * @throws JsonException + */ public function testThrowsForEmptyAccessTokenJti() { $accessTokenState = self::$accessTokenState; diff --git a/tests/Services/AuthContextServiceTest.php b/tests/src/Services/AuthContextServiceTest.php similarity index 72% rename from tests/Services/AuthContextServiceTest.php rename to tests/src/Services/AuthContextServiceTest.php index 48f3b37c..6d181ed3 100644 --- a/tests/Services/AuthContextServiceTest.php +++ b/tests/src/Services/AuthContextServiceTest.php @@ -1,30 +1,37 @@ ['myUsername'], 'someEntitlement' => ['val1', 'val2', 'val3'] ]; protected Configuration $permissions; - protected \PHPUnit\Framework\MockObject\MockObject $oidcConfigurationMock; - protected \PHPUnit\Framework\MockObject\MockObject $configurationServiceMock; - protected \PHPUnit\Framework\MockObject\MockObject $authSimpleService; - protected \PHPUnit\Framework\MockObject\MockObject $authSimpleFactory; - + protected MockObject $oidcConfigurationMock; + protected MockObject $moduleConfigMock; + protected MockObject $authSimpleService; + protected MockObject $authSimpleFactory; + + /** + * @throws \PHPUnit\Framework\MockObject\Exception + */ protected function setUp(): void { $this->permissions = Configuration::loadFromArray( @@ -38,9 +45,8 @@ protected function setUp(): void $this->oidcConfigurationMock = $this->createMock(Configuration::class); - $this->configurationServiceMock = $this->createMock(ConfigurationService::class); - $this->configurationServiceMock->method('getOpenIDConnectConfiguration') - ->willReturn($this->oidcConfigurationMock); + $this->moduleConfigMock = $this->createMock(ModuleConfig::class); + $this->moduleConfigMock->method('config')->willReturn($this->oidcConfigurationMock); $this->authSimpleService = $this->createMock(Simple::class); @@ -51,7 +57,7 @@ protected function setUp(): void protected function prepareMockedInstance(): AuthContextService { return new AuthContextService( - $this->configurationServiceMock, + $this->moduleConfigMock, $this->authSimpleFactory ); } @@ -64,11 +70,12 @@ public function testItIsInitializable(): void ); } + /** + * @throws Exception + */ public function testItReturnsUsername(): void { - $this->oidcConfigurationMock->method('getString') - ->with('useridattr') - ->willReturn('idAttribute'); + $this->moduleConfigMock->method('getUserIdentifierAttribute')->willReturn('idAttribute'); $this->authSimpleService->method('getAttributes')->willReturn(self::AUTHORIZED_USER); $this->assertSame( @@ -80,10 +87,10 @@ public function testItReturnsUsername(): void public function testItThrowsWhenNoUsername(): void { $this->oidcConfigurationMock->method('getOptionalConfigItem') - ->with('permissions', null) + ->with(ModuleConfig::OPTION_ADMIN_UI_PERMISSIONS, null) ->willReturn($this->permissions); $this->oidcConfigurationMock->method('getString') - ->with('useridattr') + ->with(ModuleConfig::OPTION_AUTH_USER_IDENTIFIER_ATTRIBUTE) ->willReturn('attributeNotSet'); $this->authSimpleService->method('getAttributes')->willReturn(self::AUTHORIZED_USER); @@ -91,10 +98,13 @@ public function testItThrowsWhenNoUsername(): void $this->prepareMockedInstance()->getAuthUserId(); } + /** + * @throws \Exception + */ public function testPermissionsOk(): void { $this->oidcConfigurationMock->method('getOptionalConfigItem') - ->with('permissions', null) + ->with(ModuleConfig::OPTION_ADMIN_UI_PERMISSIONS, null) ->willReturn($this->permissions); $this->authSimpleService->method('getAttributes')->willReturn(self::AUTHORIZED_USER); @@ -102,19 +112,25 @@ public function testPermissionsOk(): void $this->expectNotToPerformAssertions(); } + /** + * @throws \Exception + */ public function testItThrowsIfNotAuthorizedForPermission(): void { $this->oidcConfigurationMock->method('getOptionalConfigItem') - ->with('permissions', null) + ->with(ModuleConfig::OPTION_ADMIN_UI_PERMISSIONS, null) ->willReturn($this->permissions); - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); $this->prepareMockedInstance()->requirePermission('no-match'); } + /** + * @throws \Exception + */ public function testItThrowsForWrongEntitlements(): void { $this->oidcConfigurationMock->method('getOptionalConfigItem') - ->with('permissions', null) + ->with(ModuleConfig::OPTION_ADMIN_UI_PERMISSIONS, null) ->willReturn($this->permissions); $this->authSimpleService->method('getAttributes') ->willReturn( @@ -124,14 +140,17 @@ public function testItThrowsForWrongEntitlements(): void ] ); - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); $this->prepareMockedInstance()->requirePermission('client'); } + /** + * @throws \Exception + */ public function testItThrowsForNotHavingEntitlementAttribute(): void { $this->oidcConfigurationMock->method('getOptionalConfigItem') - ->with('permissions', null) + ->with(ModuleConfig::OPTION_ADMIN_UI_PERMISSIONS, null) ->willReturn($this->permissions); $this->authSimpleService->method('getAttributes') ->willReturn( @@ -140,17 +159,20 @@ public function testItThrowsForNotHavingEntitlementAttribute(): void ] ); - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); $this->prepareMockedInstance()->requirePermission('client'); } + /** + * @throws \Exception + */ public function testThrowsForNotHavingEnabledPermissions(): void { $this->oidcConfigurationMock->method('getOptionalConfigItem') - ->with('permissions', null) + ->with(ModuleConfig::OPTION_ADMIN_UI_PERMISSIONS, null) ->willReturn(Configuration::loadFromArray([])); - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); $this->prepareMockedInstance()->requirePermission('client'); } } diff --git a/tests/Services/AuthProcServiceTest.php b/tests/src/Services/AuthProcServiceTest.php similarity index 61% rename from tests/Services/AuthProcServiceTest.php rename to tests/src/Services/AuthProcServiceTest.php index 68401a66..72884c83 100644 --- a/tests/Services/AuthProcServiceTest.php +++ b/tests/src/Services/AuthProcServiceTest.php @@ -1,28 +1,42 @@ configurationServiceMock = $this->createMock(ConfigurationService::class); + $this->moduleConfigMock = $this->createMock(ModuleConfig::class); } + /** + * @throws \Exception + */ public function prepareMockedInstance(): AuthProcService { - return new AuthProcService($this->configurationServiceMock); + return new AuthProcService($this->moduleConfigMock); } + /** + * @throws \Exception + */ public function testItIsInitializable(): void { $this->assertInstanceOf( @@ -31,25 +45,31 @@ public function testItIsInitializable(): void ); } + /** + * @throws \Exception + */ public function testItLoadsConfiguredFilters(): void { - $this->configurationServiceMock->method('getAuthProcFilters') - ->willReturn(['\SimpleSAML\Module\core\Auth\Process\AttributeAdd',]); + $this->moduleConfigMock->method('getAuthProcFilters') + ->willReturn(['\\' . AttributeAdd::class,]); $authProcService = $this->prepareMockedInstance(); $this->assertIsArray($authProcService->getLoadedFilters()); $this->assertCount(1, $authProcService->getLoadedFilters()); } + /** + * @throws \Exception + */ public function testItExecutesConfiguredFilters(): void { $sampleFilters = [ 50 => [ - 'class' => '\SimpleSAML\Module\core\Auth\Process\AttributeAdd', + 'class' => '\\' . AttributeAdd::class, 'newKey' => ['newValue'] ], ]; - $this->configurationServiceMock->method('getAuthProcFilters')->willReturn($sampleFilters); + $this->moduleConfigMock->method('getAuthProcFilters')->willReturn($sampleFilters); $state = ['Attributes' => ['existingKey' => ['existingValue']]]; diff --git a/tests/Services/AuthenticationServiceTest.php b/tests/src/Services/AuthenticationServiceTest.php similarity index 77% rename from tests/Services/AuthenticationServiceTest.php rename to tests/src/Services/AuthenticationServiceTest.php index 9daa031e..674e2c96 100644 --- a/tests/Services/AuthenticationServiceTest.php +++ b/tests/src/Services/AuthenticationServiceTest.php @@ -1,25 +1,30 @@ 'https://idp.example.org']; - public const USER_ENTITY_ATTRIBUTES = [ + final public const AUTH_SOURCE = 'auth_source'; + final public const USER_ID_ATTR = 'uid'; + final public const USERNAME = 'username'; + final public const OIDC_OP_METADATA = ['issuer' => 'https://idp.example.org']; + final public const USER_ENTITY_ATTRIBUTES = [ self::USER_ID_ATTR => [self::USERNAME], 'eduPersonTargetedId' => [self::USERNAME], ]; - public const AUTH_DATA = ['Attributes' => self::USER_ENTITY_ATTRIBUTES]; - public const CLIENT_ENTITY = ['id' => 'clientid', 'redirect_uri' => 'https://rp.example.org']; - public const AUTHZ_REQUEST_PARAMS = ['client_id' => 'clientid', 'redirect_uri' => 'https://rp.example.org']; - public const STATE = [ + final public const AUTH_DATA = ['Attributes' => self::USER_ENTITY_ATTRIBUTES]; + final public const CLIENT_ENTITY = ['id' => 'clientid', 'redirect_uri' => 'https://rp.example.org']; + final public const AUTHZ_REQUEST_PARAMS = ['client_id' => 'clientid', 'redirect_uri' => 'https://rp.example.org']; + final public const STATE = [ 'Attributes' => self::AUTH_DATA['Attributes'], 'Oidc' => [ 'OpenIdProviderMetadata' => self::OIDC_OP_METADATA, @@ -47,23 +52,26 @@ class AuthenticationServiceTest extends TestCase ], ]; - public static $uri = 'https://some-server/authorize.php?abc=efg'; - - protected \PHPUnit\Framework\MockObject\MockObject $claimTranslatorExtractorMock; - protected \PHPUnit\Framework\MockObject\MockObject $serverRequestMock; - protected \PHPUnit\Framework\MockObject\MockObject $clientEntityMock; - protected \PHPUnit\Framework\MockObject\MockObject $userRepositoryMock; - protected \PHPUnit\Framework\MockObject\MockObject $authSimpleFactoryMock; - protected \PHPUnit\Framework\MockObject\MockObject $authSimpleMock; - protected \PHPUnit\Framework\MockObject\MockObject $authProcServiceMock; - protected \PHPUnit\Framework\MockObject\MockObject $clientRepositoryMock; - protected \PHPUnit\Framework\MockObject\MockObject $configurationServiceMock; - protected \PHPUnit\Framework\MockObject\MockObject $oidcOpenIdProviderMetadataServiceMock; - protected \PHPUnit\Framework\MockObject\MockObject $sessionServiceMock; - protected \PHPUnit\Framework\MockObject\MockObject $authSourceMock; - protected \PHPUnit\Framework\MockObject\MockObject $sessionMock; - protected \PHPUnit\Framework\MockObject\MockObject $userEntityMock; - + public static string $uri = 'https://some-server/authorize.php?abc=efg'; + + protected MockObject $claimTranslatorExtractorMock; + protected MockObject $serverRequestMock; + protected MockObject $clientEntityMock; + protected MockObject $userRepositoryMock; + protected MockObject $authSimpleFactoryMock; + protected MockObject $authSimpleMock; + protected MockObject $authProcServiceMock; + protected MockObject $clientRepositoryMock; + protected MockObject $moduleConfigMock; + protected MockObject $oidcOpenIdProviderMetadataServiceMock; + protected MockObject $sessionServiceMock; + protected MockObject $authSourceMock; + protected MockObject $sessionMock; + protected MockObject $userEntityMock; + + /** + * @throws \PHPUnit\Framework\MockObject\Exception + */ protected function setUp(): void { $this->serverRequestMock = $this->createMock(ServerRequest::class); @@ -73,8 +81,8 @@ protected function setUp(): void $this->authSimpleMock = $this->createMock(Simple::class); $this->authProcServiceMock = $this->createMock(AuthProcService::class); $this->clientRepositoryMock = $this->createMock(ClientRepository::class); - $this->configurationServiceMock = $this->createMock(ConfigurationService::class); - $this->oidcOpenIdProviderMetadataServiceMock = $this->createMock(OidcOpenIdProviderMetadataService::class); + $this->moduleConfigMock = $this->createMock(ModuleConfig::class); + $this->oidcOpenIdProviderMetadataServiceMock = $this->createMock(OpMetadataService::class); $this->sessionServiceMock = $this->createMock(SessionService::class); $this->claimTranslatorExtractorMock = $this->createMock(ClaimTranslatorExtractor::class); $this->authSourceMock = $this->createMock(Source::class); @@ -96,7 +104,8 @@ protected function setUp(): void $this->oidcOpenIdProviderMetadataServiceMock->method('getMetadata')->willReturn(self::OIDC_OP_METADATA); - $this->configurationServiceMock->method('getAuthProcFilters')->willReturn([]); + $this->moduleConfigMock->method('getAuthProcFilters')->willReturn([]); + $this->moduleConfigMock->method('getUserIdentifierAttribute')->willReturn(self::USER_ID_ATTR); $this->sessionServiceMock->method('getCurrentSession')->willReturn($this->sessionMock); } @@ -111,7 +120,7 @@ public function prepareMockedInstance(): AuthenticationService $this->oidcOpenIdProviderMetadataServiceMock, $this->sessionServiceMock, $this->claimTranslatorExtractorMock, - self::USER_ID_ATTR + $this->moduleConfigMock ); } @@ -123,6 +132,12 @@ public function testItIsInitializable(): void ); } + /** + * @throws AuthSource + * @throws BadRequest + * @throws NotFound + * @throws Exception + */ public function testItCreatesNewUser(): void { $clientId = 'client123'; @@ -159,6 +174,12 @@ public function testItCreatesNewUser(): void ); } + /** + * @throws AuthSource + * @throws BadRequest + * @throws NotFound + * @throws Exception + */ public function testItReturnsAnUser(): void { $clientId = 'client123'; @@ -197,6 +218,11 @@ public function testItReturnsAnUser(): void ); } + /** + * @throws AuthSource + * @throws BadRequest + * @throws NotFound + */ public function testItThrowsIfClaimsNotExist(): void { $this->authSourceMock->method('getAuthId')->willReturn('theAuthId'); diff --git a/tests/Services/IdTokenBuilderTest.php b/tests/src/Services/IdTokenBuilderTest.php similarity index 81% rename from tests/Services/IdTokenBuilderTest.php rename to tests/src/Services/IdTokenBuilderTest.php index f62b09eb..38e5ac28 100644 --- a/tests/Services/IdTokenBuilderTest.php +++ b/tests/src/Services/IdTokenBuilderTest.php @@ -1,5 +1,7 @@ markTestIncomplete(); } diff --git a/tests/Services/JsonWebKeySetServiceTest.php b/tests/src/Services/JsonWebKeySetServiceTest.php similarity index 85% rename from tests/Services/JsonWebKeySetServiceTest.php rename to tests/src/Services/JsonWebKeySetServiceTest.php index 492c86a9..5a7b3ec0 100644 --- a/tests/Services/JsonWebKeySetServiceTest.php +++ b/tests/src/Services/JsonWebKeySetServiceTest.php @@ -1,5 +1,7 @@ assertEquals($JWKSet->all(), $jsonWebKeySetService->keys()); } + /** + * @throws \SimpleSAML\Error\Exception + */ public function testCertificationFileNotFound(): void { - $this->expectException(\Exception::class); + $this->expectException(Exception::class); $this->expectExceptionMessageMatches('/OpenId Connect certification file does not exists/'); $config = [ @@ -98,6 +102,6 @@ public function testCertificationFileNotFound(): void ]; Configuration::loadFromArray($config, '', 'simplesaml'); - new JsonWebKeySetService(new ConfigurationService()); + new JsonWebKeySetService(new ModuleConfig()); } } diff --git a/tests/Services/JsonWebTokenBuilderServiceTest.php b/tests/src/Services/JsonWebTokenBuilderServiceTest.php similarity index 65% rename from tests/Services/JsonWebTokenBuilderServiceTest.php rename to tests/src/Services/JsonWebTokenBuilderServiceTest.php index b1c940e1..a9413f2b 100644 --- a/tests/Services/JsonWebTokenBuilderServiceTest.php +++ b/tests/src/Services/JsonWebTokenBuilderServiceTest.php @@ -1,7 +1,10 @@ configurationServiceStub = $this->createStub(ConfigurationService::class); - $this->configurationServiceStub->method('getSigner')->willReturn(self::$signerSha256); - $this->configurationServiceStub->method('getPrivateKeyPath')->willReturn(self::$privateKeyPath); - $this->configurationServiceStub->method('getCertPath')->willReturn(self::$publicKeyPath); - $this->configurationServiceStub->method('getSimpleSAMLSelfURLHost')->willReturn(self::$selfUrlHost); + $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); } /** @@ -54,7 +60,7 @@ public function setUp(): void */ public function testCanCreateBuilderInstance(): void { - $builderService = new JsonWebTokenBuilderService($this->configurationServiceStub); + $builderService = new JsonWebTokenBuilderService($this->moduleConfigStub); $this->assertInstanceOf( Builder::class, @@ -69,7 +75,7 @@ public function testCanCreateBuilderInstance(): void */ public function testCanGenerateSignedJwtToken(): void { - $builderService = new JsonWebTokenBuilderService($this->configurationServiceStub); + $builderService = new JsonWebTokenBuilderService($this->moduleConfigStub); $tokenBuilder = $builderService->getDefaultJwtTokenBuilder(); $unencryptedToken = $builderService->getSignedJwtTokenFromBuilder($tokenBuilder); @@ -81,12 +87,12 @@ public function testCanGenerateSignedJwtToken(): void $token = $unencryptedToken->toString(); $jwtConfig = Configuration::forAsymmetricSigner( - $this->configurationServiceStub->getSigner(), + $this->moduleConfigStub->getSigner(), InMemory::file( - $this->configurationServiceStub->getPrivateKeyPath(), - $this->configurationServiceStub->getPrivateKeyPassPhrase() ?? '' + $this->moduleConfigStub->getPrivateKeyPath(), + $this->moduleConfigStub->getPrivateKeyPassPhrase() ?? '' ), - InMemory::file($this->configurationServiceStub->getCertPath()) + InMemory::file($this->moduleConfigStub->getCertPath()) ); $parsedToken = $jwtConfig->parser()->parse($token); @@ -96,8 +102,8 @@ public function testCanGenerateSignedJwtToken(): void $parsedToken, new IssuedBy(self::$selfUrlHost), new SignedWith( - $this->configurationServiceStub->getSigner(), - InMemory::file($this->configurationServiceStub->getCertPath()) + $this->moduleConfigStub->getSigner(), + InMemory::file($this->moduleConfigStub->getCertPath()) ) ) ); @@ -110,7 +116,7 @@ public function testCanReturnCurrentSigner(): void { $this->assertSame( self::$signerSha256, - (new JsonWebTokenBuilderService($this->configurationServiceStub))->getSigner() + (new JsonWebTokenBuilderService($this->moduleConfigStub))->getSigner() ); } } diff --git a/tests/Services/LogoutTokenBuilderTest.php b/tests/src/Services/LogoutTokenBuilderTest.php similarity index 69% rename from tests/Services/LogoutTokenBuilderTest.php rename to tests/src/Services/LogoutTokenBuilderTest.php index 4b6f1cd7..01ed7fea 100644 --- a/tests/Services/LogoutTokenBuilderTest.php +++ b/tests/src/Services/LogoutTokenBuilderTest.php @@ -1,7 +1,10 @@ configurationServiceStub = $this->createStub(ConfigurationService::class); - $this->configurationServiceStub->method('getSigner')->willReturn(self::$signerSha256); - $this->configurationServiceStub->method('getPrivateKeyPath')->willReturn(self::$privateKeyPath); - $this->configurationServiceStub->method('getCertPath')->willReturn(self::$publicKeyPath); - $this->configurationServiceStub->method('getSimpleSAMLSelfURLHost')->willReturn(self::$selfUrlHost); + $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->relyingPartyAssociationStub = $this->createStub(RelyingPartyAssociationInterface::class); $this->relyingPartyAssociationStub->method('getClientId')->willReturn(self::$clientId); @@ -70,7 +75,7 @@ public function setUp(): void ->method('getBackChannelLogoutUri') ->willReturn(self::$backChannelLogoutUri); - $this->jsonWebTokenBuilderService = new JsonWebTokenBuilderService($this->configurationServiceStub); + $this->jsonWebTokenBuilderService = new JsonWebTokenBuilderService($this->moduleConfigStub); } /** @@ -85,12 +90,12 @@ public function testCanGenerateSignedTokenForRelyingPartyAssociation(): void // Check token validity $jwtConfig = Configuration::forAsymmetricSigner( - $this->configurationServiceStub->getSigner(), + $this->moduleConfigStub->getSigner(), InMemory::file( - $this->configurationServiceStub->getPrivateKeyPath(), - $this->configurationServiceStub->getPrivateKeyPassPhrase() ?? '' + $this->moduleConfigStub->getPrivateKeyPath(), + $this->moduleConfigStub->getPrivateKeyPassPhrase() ?? '' ), - InMemory::file($this->configurationServiceStub->getCertPath()) + InMemory::file($this->moduleConfigStub->getCertPath()) ); $parsedToken = $jwtConfig->parser()->parse($token); @@ -102,8 +107,8 @@ public function testCanGenerateSignedTokenForRelyingPartyAssociation(): void new PermittedFor(self::$clientId), new RelatedTo(self::$userId), new SignedWith( - $this->configurationServiceStub->getSigner(), - InMemory::file($this->configurationServiceStub->getCertPath()) + $this->moduleConfigStub->getSigner(), + InMemory::file($this->moduleConfigStub->getCertPath()) ) ) ); diff --git a/tests/Services/OidcOpenIdProviderMetadataServiceTest.php b/tests/src/Services/OpMetadataServiceTest.php similarity index 67% rename from tests/Services/OidcOpenIdProviderMetadataServiceTest.php rename to tests/src/Services/OpMetadataServiceTest.php index 5888ad98..c8a445b9 100644 --- a/tests/Services/OidcOpenIdProviderMetadataServiceTest.php +++ b/tests/src/Services/OpMetadataServiceTest.php @@ -1,27 +1,34 @@ configurationServiceMock = $this->createMock(ConfigurationService::class); + $this->moduleConfigMock = $this->createMock(ModuleConfig::class); - $this->configurationServiceMock->expects($this->once())->method('getOpenIDScopes') + $this->moduleConfigMock->expects($this->once())->method('getOpenIDScopes') ->willReturn(['openid' => 'openid']); - $this->configurationServiceMock->expects($this->once())->method('getSimpleSAMLSelfURLHost') + $this->moduleConfigMock->expects($this->once())->method('getSimpleSAMLSelfURLHost') ->willReturn('http://localhost'); - $this->configurationServiceMock->method('getOpenIdConnectModuleURL') + $this->moduleConfigMock->method('getOpenIdConnectModuleURL') ->willReturnCallback(function ($path) { $paths = [ 'authorize.php' => 'http://localhost/authorize.php', @@ -33,22 +40,31 @@ public function setUp(): void return $paths[$path] ?? null; }); - $this->configurationServiceMock->method('getAcrValuesSupported')->willReturn(['1']); + $this->moduleConfigMock->method('getAcrValuesSupported')->willReturn(['1']); } - protected function prepareMockedInstance(): OidcOpenIdProviderMetadataService + /** + * @throws \Exception + */ + protected function prepareMockedInstance(): OpMetadataService { - return new OidcOpenIdProviderMetadataService($this->configurationServiceMock); + return new OpMetadataService($this->moduleConfigMock); } + /** + * @throws \Exception + */ public function testItIsInitializable(): void { $this->assertInstanceOf( - OidcOpenIdProviderMetadataService::class, + OpMetadataService::class, $this->prepareMockedInstance() ); } + /** + * @throws \Exception + */ public function testItReturnsExpectedMetadata(): void { $this->assertSame( diff --git a/tests/Services/SessionMessagesServiceTest.php b/tests/src/Services/SessionMessagesServiceTest.php similarity index 82% rename from tests/Services/SessionMessagesServiceTest.php rename to tests/src/Services/SessionMessagesServiceTest.php index df9ce288..0ba4120e 100644 --- a/tests/Services/SessionMessagesServiceTest.php +++ b/tests/src/Services/SessionMessagesServiceTest.php @@ -1,7 +1,11 @@ sessionMock = $this->createMock(Session::class); @@ -31,6 +38,9 @@ public function testItIsInitializable(): void ); } + /** + * @throws \Exception + */ public function testItAddsMessage(): void { $this->sessionMock->expects($this->once()) @@ -54,9 +64,7 @@ public function testItGetsMessages(): void $this->sessionMock->expects($this->exactly(2)) ->method('deleteData') - ->with($this->callback(function ($id) { - return ! in_array($id, ['msg1', 'msg2']); - })); + ->with($this->callback(fn($id) => ! in_array($id, ['msg1', 'msg2']))); $this->prepareMockedInstance()->getMessages(); } diff --git a/tests/Services/SessionServiceTest.php b/tests/src/Services/SessionServiceTest.php similarity index 81% rename from tests/Services/SessionServiceTest.php rename to tests/src/Services/SessionServiceTest.php index 0d132b0e..b56e6510 100644 --- a/tests/Services/SessionServiceTest.php +++ b/tests/src/Services/SessionServiceTest.php @@ -1,5 +1,7 @@ markTestIncomplete(); } diff --git a/tests/src/Stores/Session/LogoutTicketStoreBuilderTest.php b/tests/src/Stores/Session/LogoutTicketStoreBuilderTest.php new file mode 100644 index 00000000..32e71202 --- /dev/null +++ b/tests/src/Stores/Session/LogoutTicketStoreBuilderTest.php @@ -0,0 +1,34 @@ + 'sqlite::memory:', + 'database.username' => null, + 'database.password' => null, + 'database.prefix' => 'phpunit_', + 'database.persistent' => true, + 'database.secondaries' => [], + ]; + + Configuration::loadFromArray($config, '', 'simplesaml'); + $builder = new LogoutTicketStoreBuilder(); + + $this->assertInstanceOf(LogoutTicketStoreInterface::class, $builder->getInstance()); + $this->assertInstanceOf(LogoutTicketStoreInterface::class, $builder::getStaticInstance()); + } +} diff --git a/tests/Store/SessionLogoutTicketStoreDbTest.php b/tests/src/Stores/Session/LogoutTicketStoreDbTest.php similarity index 81% rename from tests/Store/SessionLogoutTicketStoreDbTest.php rename to tests/src/Stores/Session/LogoutTicketStoreDbTest.php index fb113c8c..ddfc9537 100644 --- a/tests/Store/SessionLogoutTicketStoreDbTest.php +++ b/tests/src/Stores/Session/LogoutTicketStoreDbTest.php @@ -1,17 +1,19 @@ add($sid); $allSids = $store->getAll(); @@ -54,7 +56,7 @@ public function testCanDeleteMultipleTickets(): void $sid2 = 'sid2'; $sid3 = 'sid3'; - $store = new SessionLogoutTicketStoreDb(); + $store = new LogoutTicketStoreDb(); $store->add($sid1); $store->add($sid2); $store->add($sid3); @@ -79,7 +81,7 @@ public function testCanDeleteMultipleTickets(): void */ public function testCanDeleteExpiredTickets(): void { - $store = new SessionLogoutTicketStoreDb(null, 0); + $store = new LogoutTicketStoreDb(null, 0); $sid = 'sid123'; $store->add($sid); $this->assertEmpty($store->getAll()); diff --git a/tests/Utils/Checker/RequestRulesManagerTest.php b/tests/src/Utils/Checker/RequestRulesManagerTest.php similarity index 79% rename from tests/Utils/Checker/RequestRulesManagerTest.php rename to tests/src/Utils/Checker/RequestRulesManagerTest.php index a06544a3..a17cde5d 100644 --- a/tests/Utils/Checker/RequestRulesManagerTest.php +++ b/tests/src/Utils/Checker/RequestRulesManagerTest.php @@ -1,8 +1,12 @@ result = $this->createStub(ResultInterface::class); - $this->result->method('getKey')->willReturn($this->key); - $this->result->method('getValue')->willReturn($this->value); + $this->resultStub = $this->createStub(ResultInterface::class); + $this->resultStub->method('getKey')->willReturn($this->key); + $this->resultStub->method('getValue')->willReturn($this->value); - $this->rule = $this->createStub(RequestRuleInterface::class); - $this->rule->method('getKey')->willReturn($this->key); - $this->rule->method('checkRule')->willReturn($this->result); + $this->ruleStub = $this->createStub(RequestRuleInterface::class); + $this->ruleStub->method('getKey')->willReturn($this->key); + $this->ruleStub->method('checkRule')->willReturn($this->resultStub); $this->request = $this->createStub(ServerRequestInterface::class); @@ -47,6 +54,9 @@ public function testConstructWithoutRules(): RequestRulesManager return $requestRulesManager; } + /** + * @throws Exception + */ public function testConstructWithRules(): void { $rules = [$this->createStub(RequestRuleInterface::class)]; @@ -56,13 +66,11 @@ public function testConstructWithRules(): void /** * @depends testConstructWithoutRules * - * @param RequestRulesManager $requestRulesManager - * @return void * @throws OidcServerException */ public function testAddAndCheck(RequestRulesManager $requestRulesManager): void { - $requestRulesManager->add($this->rule); + $requestRulesManager->add($this->ruleStub); $resultBag = $requestRulesManager->check($this->request, [$this->key]); $this->assertInstanceOf(ResultBagInterface::class, $resultBag); @@ -73,8 +81,6 @@ public function testAddAndCheck(RequestRulesManager $requestRulesManager): void /** * @depends testConstructWithoutRules * - * @param RequestRulesManager $requestRulesManager - * @return void * @throws OidcServerException */ public function testCheckWithNonExistingRuleKeyThrows(RequestRulesManager $requestRulesManager): void @@ -86,13 +92,11 @@ public function testCheckWithNonExistingRuleKeyThrows(RequestRulesManager $reque /** * @depends testConstructWithoutRules * - * @param RequestRulesManager $requestRulesManager - * @return void * @throws OidcServerException */ public function testPredefineResult(RequestRulesManager $requestRulesManager): void { - $requestRulesManager->predefineResult($this->result); + $requestRulesManager->predefineResult($this->resultStub); $resultBag = $requestRulesManager->check($this->request, []); $this->assertInstanceOf(ResultBagInterface::class, $resultBag); @@ -102,9 +106,8 @@ public function testPredefineResult(RequestRulesManager $requestRulesManager): v /** * @depends testConstructWithoutRules * - * @param RequestRulesManager $requestRulesManager - * @return void * @throws OidcServerException + * @throws Exception */ public function testSetData(RequestRulesManager $requestRulesManager): void { diff --git a/tests/Utils/Checker/ResultBagTest.php b/tests/src/Utils/Checker/ResultBagTest.php similarity index 87% rename from tests/Utils/Checker/ResultBagTest.php rename to tests/src/Utils/Checker/ResultBagTest.php index 694e33ea..10399e85 100644 --- a/tests/Utils/Checker/ResultBagTest.php +++ b/tests/src/Utils/Checker/ResultBagTest.php @@ -1,7 +1,10 @@ resultBag->add($this->result); $this->assertSame($this->result, $this->resultBag->getOrFail($this->key)); - $this->expectException(\LogicException::class); + $this->expectException(LogicException::class); $this->resultBag->getOrFail('non-existent'); } diff --git a/tests/Utils/Checker/ResultTest.php b/tests/src/Utils/Checker/ResultTest.php similarity index 90% rename from tests/Utils/Checker/ResultTest.php rename to tests/src/Utils/Checker/ResultTest.php index 76fdfba1..80c67cb3 100644 --- a/tests/Utils/Checker/ResultTest.php +++ b/tests/src/Utils/Checker/ResultTest.php @@ -1,5 +1,7 @@ requestStub = $this->createStub(ServerRequestInterface::class); diff --git a/tests/Utils/Checker/Rules/AddClaimsToIdTokenRuleTest.php b/tests/src/Utils/Checker/Rules/AddClaimsToIdTokenRuleTest.php similarity index 85% rename from tests/Utils/Checker/Rules/AddClaimsToIdTokenRuleTest.php rename to tests/src/Utils/Checker/Rules/AddClaimsToIdTokenRuleTest.php index 925e8dd4..54abab16 100644 --- a/tests/Utils/Checker/Rules/AddClaimsToIdTokenRuleTest.php +++ b/tests/src/Utils/Checker/Rules/AddClaimsToIdTokenRuleTest.php @@ -1,7 +1,11 @@ 'client123', @@ -42,8 +46,11 @@ class AddClaimsToIdTokenRuleTest extends TestCase private ResultBag $resultBag; - private $loggerServiceStub; + private Stub $loggerServiceStub; + /** + * @throws Exception + */ protected function setUp(): void { $this->requestStub = $this->createStub(ServerRequestInterface::class); @@ -54,6 +61,7 @@ protected function setUp(): void /** * @dataProvider validResponseTypeProvider + * @throws Throwable */ public function testAddClaimsToIdTokenRuleTest($responseType) { @@ -61,11 +69,11 @@ public function testAddClaimsToIdTokenRuleTest($responseType) $this->resultBag->add(new Result(ResponseTypeRule::class, $responseType)); $result = $rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? - new Result(AddClaimsToIdTokenRule::class, null); + new Result(AddClaimsToIdTokenRule::class, null); $this->assertTrue($result->getValue()); } - public function validResponseTypeProvider(): array + public static function validResponseTypeProvider(): array { return [ ['id_token'], @@ -74,6 +82,7 @@ public function validResponseTypeProvider(): array /** * @dataProvider invalidResponseTypeProvider + * @throws Throwable */ public function testDoNotAddClaimsToIdTokenRuleTest($responseType) { @@ -81,12 +90,12 @@ public function testDoNotAddClaimsToIdTokenRuleTest($responseType) $this->resultBag->add(new Result(ResponseTypeRule::class, $responseType)); $result = $rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? - new Result(AddClaimsToIdTokenRule::class, null); + new Result(AddClaimsToIdTokenRule::class, null); $this->assertFalse($result->getValue()); } - public function invalidResponseTypeProvider(): array + public static function invalidResponseTypeProvider(): array { return [ ['code'], diff --git a/tests/Utils/Checker/Rules/ClientIdRuleTest.php b/tests/src/Utils/Checker/Rules/ClientIdRuleTest.php similarity index 71% rename from tests/Utils/Checker/Rules/ClientIdRuleTest.php rename to tests/src/Utils/Checker/Rules/ClientIdRuleTest.php index cb9d5bbd..9cc47332 100644 --- a/tests/Utils/Checker/Rules/ClientIdRuleTest.php +++ b/tests/src/Utils/Checker/Rules/ClientIdRuleTest.php @@ -1,11 +1,15 @@ clientRepository = $this->createStub(ClientRepositoryInterface::class); + $this->clientRepositoryStub = $this->createStub(ClientRepositoryInterface::class); $this->requestStub = $this->createStub(ServerRequestInterface::class); $this->resultBagStub = $this->createStub(ResultBagInterface::class); $this->loggerServiceStub = $this->createStub(LoggerService::class); @@ -32,48 +39,46 @@ protected function setUp(): void public function testConstruct(): void { - $this->assertInstanceOf(ClientIdRule::class, new ClientIdRule($this->clientRepository)); + $this->assertInstanceOf(ClientIdRule::class, new ClientIdRule($this->clientRepositoryStub)); } public function testCheckRuleEmptyClientIdThrows(): void { $this->requestStub->method('getQueryParams')->willReturn([]); $this->expectException(OidcServerException::class); - (new ClientIdRule($this->clientRepository))->checkRule( + (new ClientIdRule($this->clientRepositoryStub))->checkRule( $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, - [] ); } public function testCheckRuleInvalidClientThrows(): void { $this->requestStub->method('getQueryParams')->willReturn(['client_id' => '123']); - $this->clientRepository->method('getClientEntity')->willReturn('invalid'); + $this->clientRepositoryStub->method('getClientEntity')->willReturn('invalid'); $this->expectException(OidcServerException::class); - (new ClientIdRule($this->clientRepository))->checkRule( + (new ClientIdRule($this->clientRepositoryStub))->checkRule( $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, - [] ); } /** * @throws OidcServerException + * @throws Exception */ public function testCheckRuleForValidClientId(): void { $this->requestStub->method('getQueryParams')->willReturn(['client_id' => '123']); $client = $this->createStub(ClientEntityInterface::class); - $this->clientRepository->method('getClientEntity')->willReturn($client); + $this->clientRepositoryStub->method('getClientEntity')->willReturn($client); - $result = (new ClientIdRule($this->clientRepository))->checkRule( + $result = (new ClientIdRule($this->clientRepositoryStub))->checkRule( $this->requestStub, $this->resultBagStub, $this->loggerServiceStub, - [] ); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertInstanceOf(ClientEntityInterface::class, $result->getValue()); diff --git a/tests/Utils/Checker/Rules/CodeChallengeMethodRuleTest.php b/tests/src/Utils/Checker/Rules/CodeChallengeMethodRuleTest.php similarity index 90% rename from tests/Utils/Checker/Rules/CodeChallengeMethodRuleTest.php rename to tests/src/Utils/Checker/Rules/CodeChallengeMethodRuleTest.php index 4a07f959..c1059028 100644 --- a/tests/Utils/Checker/Rules/CodeChallengeMethodRuleTest.php +++ b/tests/src/Utils/Checker/Rules/CodeChallengeMethodRuleTest.php @@ -1,8 +1,12 @@ rule = new CodeChallengeMethodRule(new CodeChallengeVerifiersRepository()); @@ -47,7 +54,7 @@ public function testCheckRuleRedirectUriDependency(): void { $resultBag = new ResultBag(); $this->expectException(LogicException::class); - $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []); + $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -59,7 +66,7 @@ public function testCheckRuleStateDependency(): void $resultBag = new ResultBag(); $resultBag->add($this->redirectUriResult); $this->expectException(LogicException::class); - $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []); + $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -70,7 +77,7 @@ public function testCheckRuleWithInvalidCodeChallengeMethodThrows(): void $resultBag = $this->prepareValidResultBag(); $this->requestStub->method('getQueryParams')->willReturn(['code_challenge_method' => 'invalid']); $this->expectException(OidcServerException::class); - $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []); + $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -81,7 +88,7 @@ public function testCheckRuleForValidCodeChallengeMethod(): void { $resultBag = $this->prepareValidResultBag(); $this->requestStub->method('getQueryParams')->willReturn(['code_challenge_method' => 'plain']); - $result = $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []); + $result = $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertSame('plain', $result->getValue()); diff --git a/tests/Utils/Checker/Rules/CodeChallengeRuleTest.php b/tests/src/Utils/Checker/Rules/CodeChallengeRuleTest.php similarity index 90% rename from tests/Utils/Checker/Rules/CodeChallengeRuleTest.php rename to tests/src/Utils/Checker/Rules/CodeChallengeRuleTest.php index abbc6532..4ac0249a 100644 --- a/tests/Utils/Checker/Rules/CodeChallengeRuleTest.php +++ b/tests/src/Utils/Checker/Rules/CodeChallengeRuleTest.php @@ -1,8 +1,12 @@ rule = new CodeChallengeRule(); @@ -48,7 +55,7 @@ public function testCheckRuleRedirectUriDependency(): void { $resultBag = new ResultBag(); $this->expectException(LogicException::class); - $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []); + $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -60,7 +67,7 @@ public function testCheckRuleStateDependency(): void $resultBag = new ResultBag(); $resultBag->add($this->redirectUriResult); $this->expectException(LogicException::class); - $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []); + $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -71,7 +78,7 @@ public function testCheckRuleNoCodeChallengeThrows(): void $resultBag = $this->prepareValidResultBag(); $this->requestStub->method('getQueryParams')->willReturn([]); $this->expectException(OidcServerException::class); - $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []); + $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -82,7 +89,7 @@ public function testCheckRuleInvalidCodeChallengeThrows(): void $resultBag = $this->prepareValidResultBag(); $this->requestStub->method('getQueryParams')->willReturn(['code_challenge' => 'too-short']); $this->expectException(OidcServerException::class); - $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []); + $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -93,7 +100,7 @@ public function testCheckRuleForValidCodeChallenge(): void { $resultBag = $this->prepareValidResultBag(); $this->requestStub->method('getQueryParams')->willReturn(['code_challenge' => $this->codeChallenge]); - $result = $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []); + $result = $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertSame($this->codeChallenge, $result->getValue()); diff --git a/tests/Utils/Checker/Rules/IdTokenHintRuleTest.php b/tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php similarity index 71% rename from tests/Utils/Checker/Rules/IdTokenHintRuleTest.php rename to tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php index a5b8ada7..d71dbc87 100644 --- a/tests/Utils/Checker/Rules/IdTokenHintRuleTest.php +++ b/tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php @@ -1,5 +1,7 @@ resultBagStub = $this->createStub(ResultBagInterface::class); - $this->configurationServiceStub = $this->createStub(ConfigurationService::class); - $this->configurationServiceStub->method('getSigner')->willReturn(new Sha256()); - $this->configurationServiceStub->method('getSimpleSAMLSelfURLHost')->willReturn(self::$issuer); + $this->moduleConfigStub = $this->createStub(ModuleConfig::class); + $this->moduleConfigStub->method('getSigner')->willReturn(new Sha256()); + $this->moduleConfigStub->method('getSimpleSAMLSelfURLHost')->willReturn(self::$issuer); $this->cryptKeyFactoryStub = $this->createStub(CryptKeyFactory::class); $this->cryptKeyFactoryStub->method('buildPrivateKey')->willReturn(self::$privateKey); $this->cryptKeyFactoryStub->method('buildPublicKey')->willReturn(self::$publicKey); - /** @psalm-suppress ArgumentTypeCoercion */ $this->jwtConfig = Configuration::forAsymmetricSigner( - $this->configurationServiceStub->getSigner(), + $this->moduleConfigStub->getSigner(), InMemory::plainText(self::$privateKey->getKeyContents()), InMemory::plainText(self::$publicKey->getKeyContents()) ); @@ -79,7 +83,7 @@ protected function setUp(): void public function testConstruct(): void { $this->assertInstanceOf(IdTokenHintRule::class, new IdTokenHintRule( - $this->configurationServiceStub, + $this->moduleConfigStub, $this->cryptKeyFactoryStub )); } @@ -90,7 +94,7 @@ public function testConstruct(): void */ public function testCheckRuleIsNullWhenParamNotSet(): void { - $rule = new IdTokenHintRule($this->configurationServiceStub, $this->cryptKeyFactoryStub); + $rule = new IdTokenHintRule($this->moduleConfigStub, $this->cryptKeyFactoryStub); $this->requestStub->method('getMethod')->willReturn(''); $result = $rule->checkRule( $this->requestStub, @@ -108,7 +112,7 @@ public function testCheckRuleThrowsForMalformedIdToken(): void { $this->requestStub->method('getMethod')->willReturn('GET'); $this->requestStub->method('getQueryParams')->willReturn(['id_token_hint' => 'malformed']); - $rule = new IdTokenHintRule($this->configurationServiceStub, $this->cryptKeyFactoryStub); + $rule = new IdTokenHintRule($this->moduleConfigStub, $this->cryptKeyFactoryStub); $this->expectException(Throwable::class); $rule->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); } @@ -120,14 +124,14 @@ public function testCheckRuleThrowsForIdTokenWithInvalidSignature(): void { $this->requestStub->method('getMethod')->willReturn('GET'); $invalidSignatureJwt = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUub3JnIiwic3ViIjo' . - 'iMTIzNDU2Nzg5MCIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMn0.JGJ_KSiXiRsgVc5nYFTSqbaeeM3UA5DGnOTaz3' . - 'UqbyHM0ogO3rq_-8FwLRzGk-7942U6rQ1ARziLsYYsUtH7yaUTWi6xSvh_mJQuF8hl_X3OghJWeXWms42OjAkHXtB-H7LQ_bEQNV' . - 'nF8XYLsq06MoHeHxAnDkVpVcZyDrmhauAqV1PTWsC9FMMKaxfoVsIbeQ0-PV_gAgzSK5-T0bliXPUdWFjvPXJ775jqqy4ZyNJYh' . - '1_rZ1WyOrJu7AHkT9R4FNQNCw40BRzfI3S_OYBNirKAh5G0sctNwEEaJL_a3lEwKYSC-d_sZ6WBvFP8B138b7T6nPzI71tvfXE' . - 'Ru7Q7rA'; + 'iMTIzNDU2Nzg5MCIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMn0.JGJ_KSiXiRsgVc5nYFTSqbaeeM3UA5DGnOTaz3' . + 'UqbyHM0ogO3rq_-8FwLRzGk-7942U6rQ1ARziLsYYsUtH7yaUTWi6xSvh_mJQuF8hl_X3OghJWeXWms42OjAkHXtB-H7LQ_bEQNV' . + 'nF8XYLsq06MoHeHxAnDkVpVcZyDrmhauAqV1PTWsC9FMMKaxfoVsIbeQ0-PV_gAgzSK5-T0bliXPUdWFjvPXJ775jqqy4ZyNJYh' . + '1_rZ1WyOrJu7AHkT9R4FNQNCw40BRzfI3S_OYBNirKAh5G0sctNwEEaJL_a3lEwKYSC-d_sZ6WBvFP8B138b7T6nPzI71tvfXE' . + 'Ru7Q7rA'; $this->requestStub->method('getQueryParams')->willReturn(['id_token_hint' => $invalidSignatureJwt]); - $rule = new IdTokenHintRule($this->configurationServiceStub, $this->cryptKeyFactoryStub); + $rule = new IdTokenHintRule($this->moduleConfigStub, $this->cryptKeyFactoryStub); $this->expectException(Throwable::class); $rule->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); } @@ -140,14 +144,13 @@ public function testCheckRuleThrowsForIdTokenWithInvalidIssuer(): void { $this->requestStub->method('getMethod')->willReturn('GET'); - /** @psalm-suppress ArgumentTypeCoercion */ $invalidIssuerJwt = $this->jwtConfig->builder()->issuedBy('invalid')->getToken( - $this->configurationServiceStub->getSigner(), + $this->moduleConfigStub->getSigner(), InMemory::plainText(self::$privateKey->getKeyContents()) )->toString(); $this->requestStub->method('getQueryParams')->willReturn(['id_token_hint' => $invalidIssuerJwt]); - $rule = new IdTokenHintRule($this->configurationServiceStub, $this->cryptKeyFactoryStub); + $rule = new IdTokenHintRule($this->moduleConfigStub, $this->cryptKeyFactoryStub); $this->expectException(Throwable::class); $rule->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub); } @@ -161,16 +164,15 @@ public function testCheckRulePassesForValidIdToken(): void { $this->requestStub->method('getMethod')->willReturn('GET'); - /** @psalm-suppress ArgumentTypeCoercion */ $idToken = $this->jwtConfig->builder()->issuedBy(self::$issuer)->getToken( - $this->configurationServiceStub->getSigner(), + $this->moduleConfigStub->getSigner(), InMemory::plainText(self::$privateKey->getKeyContents()) )->toString(); $this->requestStub->method('getQueryParams')->willReturn(['id_token_hint' => $idToken]); - $rule = new IdTokenHintRule($this->configurationServiceStub, $this->cryptKeyFactoryStub); + $rule = new IdTokenHintRule($this->moduleConfigStub, $this->cryptKeyFactoryStub); $result = $rule->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? - new Result(IdTokenHintRule::class); + new Result(IdTokenHintRule::class); $this->assertInstanceOf(UnencryptedToken::class, $result->getValue()); } diff --git a/tests/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php b/tests/src/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php similarity index 80% rename from tests/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php rename to tests/src/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php index d73557ac..145390e0 100644 --- a/tests/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php +++ b/tests/src/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php @@ -1,13 +1,18 @@ clientRepository = $this->createStub(ClientRepository::class); + $this->clientRepositoryStub = $this->createStub(ClientRepository::class); $this->requestStub = $this->createStub(ServerRequestInterface::class); $this->requestStub->method('getMethod')->willReturn('GET'); $this->resultBagStub = $this->createStub(ResultBagInterface::class); $this->clientStub = $this->createStub(ClientEntityInterface::class); - /** @psalm-suppress ArgumentTypeCoercion */ $this->jwtConfig = Configuration::forAsymmetricSigner( new Sha256(), InMemory::plainText(self::$privateKey->getKeyContents()), @@ -74,9 +81,9 @@ protected function setUp(): void */ public function testCheckRuleReturnsNullIfNoParamSet(): void { - $result = (new PostLogoutRedirectUriRule($this->clientRepository)) + $result = (new PostLogoutRedirectUriRule($this->clientRepositoryStub)) ->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? - (new Result(PostLogoutRedirectUriRule::class)); + (new Result(PostLogoutRedirectUriRule::class)); $this->assertNull($result->getValue()); } @@ -91,9 +98,9 @@ public function testCheckRuleThrowsWhenIdTokenHintNotAvailable(): void $this->expectException(Throwable::class); - (new PostLogoutRedirectUriRule($this->clientRepository)) + (new PostLogoutRedirectUriRule($this->clientRepositoryStub)) ->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? - (new Result(PostLogoutRedirectUriRule::class)); + (new Result(PostLogoutRedirectUriRule::class)); } /** @@ -104,7 +111,6 @@ public function testCheckRuleThrowsWhenAudClaimNotValid(): void $this->requestStub->method('getQueryParams') ->willReturn(['post_logout_redirect_uri' => self::$postLogoutRedirectUri]); - /** @psalm-suppress ArgumentTypeCoercion */ $jwt = $this->jwtConfig->builder()->issuedBy(self::$issuer) ->getToken( new Sha256(), @@ -119,9 +125,9 @@ public function testCheckRuleThrowsWhenAudClaimNotValid(): void $this->expectException(Throwable::class); - (new PostLogoutRedirectUriRule($this->clientRepository)) + (new PostLogoutRedirectUriRule($this->clientRepositoryStub)) ->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? - (new Result(PostLogoutRedirectUriRule::class)); + (new Result(PostLogoutRedirectUriRule::class)); } /** @@ -132,7 +138,6 @@ public function testCheckRuleThrowsWhenClientNotFound(): void $this->requestStub->method('getQueryParams') ->willReturn(['post_logout_redirect_uri' => self::$postLogoutRedirectUri]); - /** @psalm-suppress ArgumentTypeCoercion */ $jwt = $this->jwtConfig->builder() ->issuedBy(self::$issuer) ->permittedFor('invalid-client-id') @@ -141,7 +146,7 @@ public function testCheckRuleThrowsWhenClientNotFound(): void InMemory::plainText(self::$privateKey->getKeyContents()) ); - $this->clientRepository->method('findById')->willReturn(null); + $this->clientRepositoryStub->method('findById')->willReturn(null); $this->resultBagStub->method('getOrFail')->willReturnOnConsecutiveCalls( new Result(StateRule::class), @@ -150,9 +155,9 @@ public function testCheckRuleThrowsWhenClientNotFound(): void $this->expectException(Throwable::class); - (new PostLogoutRedirectUriRule($this->clientRepository)) + (new PostLogoutRedirectUriRule($this->clientRepositoryStub)) ->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? - (new Result(PostLogoutRedirectUriRule::class)); + (new Result(PostLogoutRedirectUriRule::class)); } /** @@ -163,7 +168,6 @@ public function testCheckRuleThrowsWhenPostLogoutRegisteredUriNotRegistered(): v $this->requestStub->method('getQueryParams') ->willReturn(['post_logout_redirect_uri' => self::$postLogoutRedirectUri]); - /** @psalm-suppress ArgumentTypeCoercion */ $jwt = $this->jwtConfig->builder() ->issuedBy(self::$issuer) ->permittedFor('client-id') @@ -176,7 +180,7 @@ public function testCheckRuleThrowsWhenPostLogoutRegisteredUriNotRegistered(): v 'https://some-other-uri' ]); - $this->clientRepository->method('findById')->willReturn($this->clientStub); + $this->clientRepositoryStub->method('findById')->willReturn($this->clientStub); $this->resultBagStub->method('getOrFail')->willReturnOnConsecutiveCalls( new Result(StateRule::class), @@ -185,9 +189,9 @@ public function testCheckRuleThrowsWhenPostLogoutRegisteredUriNotRegistered(): v $this->expectException(Throwable::class); - (new PostLogoutRedirectUriRule($this->clientRepository)) + (new PostLogoutRedirectUriRule($this->clientRepositoryStub)) ->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? - (new Result(PostLogoutRedirectUriRule::class)); + (new Result(PostLogoutRedirectUriRule::class)); } /** @@ -199,7 +203,6 @@ public function testCheckRuleReturnsForRegisteredPostLogoutRedirectUri(): void $this->requestStub->method('getQueryParams') ->willReturn(['post_logout_redirect_uri' => self::$postLogoutRedirectUri]); - /** @psalm-suppress ArgumentTypeCoercion */ $jwt = $this->jwtConfig->builder() ->issuedBy(self::$issuer) ->permittedFor('client-id') @@ -212,16 +215,16 @@ public function testCheckRuleReturnsForRegisteredPostLogoutRedirectUri(): void self::$postLogoutRedirectUri ]); - $this->clientRepository->method('findById')->willReturn($this->clientStub); + $this->clientRepositoryStub->method('findById')->willReturn($this->clientStub); $this->resultBagStub->method('getOrFail')->willReturnOnConsecutiveCalls( new Result(StateRule::class), new Result(IdTokenHintRule::class, $jwt) ); - $result = (new PostLogoutRedirectUriRule($this->clientRepository)) + $result = (new PostLogoutRedirectUriRule($this->clientRepositoryStub)) ->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? - (new Result(PostLogoutRedirectUriRule::class)); + (new Result(PostLogoutRedirectUriRule::class)); $this->assertEquals(self::$postLogoutRedirectUri, $result->getValue()); } diff --git a/tests/Utils/Checker/Rules/RedirectUriRuleTest.php b/tests/src/Utils/Checker/Rules/RedirectUriRuleTest.php similarity index 69% rename from tests/Utils/Checker/Rules/RedirectUriRuleTest.php rename to tests/src/Utils/Checker/Rules/RedirectUriRuleTest.php index 68de3f23..34a94190 100644 --- a/tests/Utils/Checker/Rules/RedirectUriRuleTest.php +++ b/tests/src/Utils/Checker/Rules/RedirectUriRuleTest.php @@ -1,11 +1,15 @@ rule = new RedirectUriRule(); $this->resultBag = new ResultBag(); $this->clientStub = $this->createStub(ClientEntityInterface::class); - $this->request = $this->createStub(ServerRequestInterface::class); + $this->requestStub = $this->createStub(ServerRequestInterface::class); $this->loggerServiceStub = $this->createStub(LoggerService::class); } @@ -44,7 +51,7 @@ protected function setUp(): void public function testCheckRuleClientIdDependancy(): void { $this->expectException(LogicException::class); - $this->rule->checkRule($this->request, $this->resultBag, $this->loggerServiceStub, []); + $this->rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); } /** @@ -55,7 +62,7 @@ public function testCheckRuleWithInvalidClientDependancy(): void { $this->resultBag->add(new Result(ClientIdRule::class, 'invalid')); $this->expectException(LogicException::class); - $this->rule->checkRule($this->request, $this->resultBag, $this->loggerServiceStub, []); + $this->rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); } /** @@ -63,11 +70,11 @@ public function testCheckRuleWithInvalidClientDependancy(): void */ public function testCheckRuleRedirectUriNotSetThrows(): void { - $this->request->method('getQueryParams')->willReturn([]); + $this->requestStub->method('getQueryParams')->willReturn([]); $resultBag = $this->prepareValidResultBag(); $this->expectException(OidcServerException::class); - $this->rule->checkRule($this->request, $resultBag, $this->loggerServiceStub, []); + $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -75,11 +82,11 @@ public function testCheckRuleRedirectUriNotSetThrows(): void */ public function testCheckRuleDifferentClientRedirectUriThrows(): void { - $this->request->method('getQueryParams')->willReturn(['redirect_uri' => 'invalid']); + $this->requestStub->method('getQueryParams')->willReturn(['redirect_uri' => 'invalid']); $resultBag = $this->prepareValidResultBag(); $this->expectException(OidcServerException::class); - $this->rule->checkRule($this->request, $resultBag, $this->loggerServiceStub, []); + $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -87,13 +94,13 @@ public function testCheckRuleDifferentClientRedirectUriThrows(): void */ public function testCheckRuleDifferentClientRedirectUriArrayThrows(): void { - $this->request->method('getQueryParams')->willReturn(['redirect_uri' => 'invalid']); + $this->requestStub->method('getQueryParams')->willReturn(['redirect_uri' => 'invalid']); $this->clientStub->method('getRedirectUri')->willReturn([$this->redirectUri]); $this->resultBag->add(new Result(ClientIdRule::class, $this->clientStub)); $this->expectException(OidcServerException::class); - $this->rule->checkRule($this->request, $this->resultBag, $this->loggerServiceStub, []); + $this->rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); } /** @@ -102,10 +109,10 @@ public function testCheckRuleDifferentClientRedirectUriArrayThrows(): void */ public function testCheckRuleWithValidRedirectUri(): void { - $this->request->method('getQueryParams')->willReturn(['redirect_uri' => $this->redirectUri]); + $this->requestStub->method('getQueryParams')->willReturn(['redirect_uri' => $this->redirectUri]); $resultBag = $this->prepareValidResultBag(); - $result = $this->rule->checkRule($this->request, $resultBag, $this->loggerServiceStub, []); + $result = $this->rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertSame($this->redirectUri, $result->getValue()); diff --git a/tests/Utils/Checker/Rules/RequestedClaimsRuleTest.php b/tests/src/Utils/Checker/Rules/RequestedClaimsRuleTest.php similarity index 78% rename from tests/Utils/Checker/Rules/RequestedClaimsRuleTest.php rename to tests/src/Utils/Checker/Rules/RequestedClaimsRuleTest.php index 0724b1b0..7b2218b3 100644 --- a/tests/Utils/Checker/Rules/RequestedClaimsRuleTest.php +++ b/tests/src/Utils/Checker/Rules/RequestedClaimsRuleTest.php @@ -1,17 +1,20 @@ resultBag = new ResultBag(); $this->clientStub = $this->createStub(ClientEntityInterface::class); - $this->request = $this->createStub(ServerRequestInterface::class); + $this->requestStub = $this->createStub(ServerRequestInterface::class); $this->clientStub->method('getScopes')->willReturn(['openid', 'profile', 'email']); $this->resultBag->add(new Result(ClientIdRule::class, $this->clientStub)); $this->loggerServiceStub = $this->createStub(LoggerService::class); } /** - * @throws InvalidArgumentException * @throws Throwable */ public function testNoRequestedClaims(): void { $rule = new RequestedClaimsRule(new ClaimTranslatorExtractor(self::$userIdAttr)); - $result = $rule->checkRule($this->request, $this->resultBag, $this->loggerServiceStub, []); + $result = $rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); $this->assertNull($result); } /** - * @throws InvalidArgumentException * @throws Throwable */ public function testWithClaims(): void @@ -75,20 +79,19 @@ public function testWithClaims(): void // Add some claims the client is not authorized for $requestedClaims['userinfo']['someClaim'] = null; $requestedClaims['id_token']['secret_password'] = null; - $this->request->method('getQueryParams')->willReturn([ + $this->requestStub->method('getQueryParams')->willReturn([ 'claims' => json_encode($requestedClaims), 'client_id' => 'abc' ]); $rule = new RequestedClaimsRule(new ClaimTranslatorExtractor(self::$userIdAttr)); - $result = $rule->checkRule($this->request, $this->resultBag, $this->loggerServiceStub, []); + $result = $rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); $this->assertNotNull($result); $this->assertEquals($expectedClaims, $result->getValue()); } /** - * @throws InvalidArgumentException * @throws Throwable */ public function testOnlyWithNonStandardClaimRequest(): void @@ -99,13 +102,13 @@ public function testOnlyWithNonStandardClaimRequest(): void ] ]; $requestedClaims = $expectedClaims; - $this->request->method('getQueryParams')->willReturn([ + $this->requestStub->method('getQueryParams')->willReturn([ 'claims' => json_encode($requestedClaims), 'client_id' => 'abc' ]); $rule = new RequestedClaimsRule(new ClaimTranslatorExtractor(self::$userIdAttr)); - $result = $rule->checkRule($this->request, $this->resultBag, $this->loggerServiceStub, []); + $result = $rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); $this->assertNotNull($result); $this->assertEquals($expectedClaims, $result->getValue()); } diff --git a/tests/Utils/Checker/Rules/RequiredNonceRuleTest.php b/tests/src/Utils/Checker/Rules/RequiredNonceRuleTest.php similarity index 89% rename from tests/Utils/Checker/Rules/RequiredNonceRuleTest.php rename to tests/src/Utils/Checker/Rules/RequiredNonceRuleTest.php index b077422c..8b117832 100644 --- a/tests/Utils/Checker/Rules/RequiredNonceRuleTest.php +++ b/tests/src/Utils/Checker/Rules/RequiredNonceRuleTest.php @@ -1,8 +1,12 @@ 'nonce123', 'state' => 'state123', ]; - protected $loggerServiceStub; + protected Stub $loggerServiceStub; + /** + * @throws Exception + */ protected function setUp(): void { $this->redirectUriResult = new Result(RedirectUriRule::class, 'https://some-uri.org'); @@ -54,7 +61,7 @@ public function testCheckRuleRedirectUriDependency(): void $rule = new RequiredNonceRule(); $resultBag = new ResultBag(); $this->expectException(LogicException::class); - $rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []); + $rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -67,7 +74,7 @@ public function testCheckRuleStateDependency(): void $resultBag = new ResultBag(); $resultBag->add($this->redirectUriResult); $this->expectException(LogicException::class); - $rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []); + $rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -80,8 +87,8 @@ public function testCheckRulePassesWhenNonceIsPresent() $this->requestStub->method('getQueryParams')->willReturn($this->requestQueryParams); - $result = $rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub, []) ?? - new Result(RequiredNonceRule::class, null); + $result = $rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? + new Result(RequiredNonceRule::class, null); $this->assertEquals($this->requestQueryParams['nonce'], $result->getValue()); } @@ -100,6 +107,6 @@ public function testCheckRuleThrowsWhenNonceIsNotPresent() $this->expectException(OidcServerException::class); - $rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub, []); + $rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); } } diff --git a/tests/Utils/Checker/Rules/RequiredOpenIdScopeRuleTest.php b/tests/src/Utils/Checker/Rules/RequiredOpenIdScopeRuleTest.php similarity index 89% rename from tests/Utils/Checker/Rules/RequiredOpenIdScopeRuleTest.php rename to tests/src/Utils/Checker/Rules/RequiredOpenIdScopeRuleTest.php index 7497a89f..22a56b26 100644 --- a/tests/Utils/Checker/Rules/RequiredOpenIdScopeRuleTest.php +++ b/tests/src/Utils/Checker/Rules/RequiredOpenIdScopeRuleTest.php @@ -1,11 +1,15 @@ redirectUriResult = new Result(RedirectUriRule::class, 'https://some-uri.org'); @@ -53,7 +60,7 @@ public function testCheckRuleRedirectUriDependency(): void $rule = new RequiredOpenIdScopeRule(); $resultBag = new ResultBag(); $this->expectException(LogicException::class); - $rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []); + $rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -66,7 +73,7 @@ public function testCheckRuleStateDependency(): void $resultBag = new ResultBag(); $resultBag->add($this->redirectUriResult); $this->expectException(LogicException::class); - $rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []); + $rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } /** @@ -81,8 +88,8 @@ public function testCheckRulePassesWhenOpenIdScopeIsPresent() $resultBag->add($this->stateResult); $resultBag->add($this->scopeResult); - $result = $rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []) ?? - new Result(RequiredOpenIdScopeRule::class, null); + $result = $rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub) ?? + new Result(RequiredOpenIdScopeRule::class, null); $this->assertTrue($result->getValue()); } @@ -103,6 +110,6 @@ public function testCheckRuleThrowsWhenOpenIdScopeIsNotPresent() $this->expectException(OidcServerException::class); - $rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub, []); + $rule->checkRule($this->requestStub, $resultBag, $this->loggerServiceStub); } } diff --git a/tests/Utils/Checker/Rules/ResponseTypeRuleTest.php b/tests/src/Utils/Checker/Rules/ResponseTypeRuleTest.php similarity index 87% rename from tests/Utils/Checker/Rules/ResponseTypeRuleTest.php rename to tests/src/Utils/Checker/Rules/ResponseTypeRuleTest.php index 21456c11..9838ac13 100644 --- a/tests/Utils/Checker/Rules/ResponseTypeRuleTest.php +++ b/tests/src/Utils/Checker/Rules/ResponseTypeRuleTest.php @@ -1,7 +1,11 @@ 'client123', @@ -41,8 +45,11 @@ class ResponseTypeRuleTest extends TestCase */ private ResultBag $resultBag; - protected $loggerServiceStub; + protected Stub $loggerServiceStub; + /** + * @throws Exception + */ protected function setUp(): void { $this->requestStub = $this->createStub(ServerRequestInterface::class); @@ -62,11 +69,11 @@ public function testResponseTypeRuleTest($responseType) $this->requestParams['response_type'] = $responseType; $this->requestStub->method('getQueryParams')->willReturn($this->requestParams); $result = $rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub) ?? - new Result(ResponseTypeRule::class, null); + new Result(ResponseTypeRule::class, null); $this->assertSame($responseType, $result->getValue()); } - public function validResponseTypeProvider(): array + public static function validResponseTypeProvider(): array { return [ ['id_token'], diff --git a/tests/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php b/tests/src/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php similarity index 72% rename from tests/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php rename to tests/src/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php index 266c0539..cf56ff28 100644 --- a/tests/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php +++ b/tests/src/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php @@ -1,74 +1,46 @@ serverRequestStub = $this->createStub(ServerRequestInterface::class); @@ -90,7 +62,7 @@ protected function setUp(): void $this->clientResultStub = $this->createStub(ResultInterface::class); $this->validScopesResultStub = $this->createStub(ResultInterface::class); - $this->configurationServiceStub = $this->createStub(ConfigurationService::class); + $this->moduleConfigStub = $this->createStub(ModuleConfig::class); $this->openIdConfigurationStub = $this->createStub(Configuration::class); } @@ -98,10 +70,14 @@ public function testCanCreateInstance(): void { $this->assertInstanceOf( ScopeOfflineAccessRule::class, - new ScopeOfflineAccessRule($this->configurationServiceStub) + new ScopeOfflineAccessRule() ); } + /** + * @throws Throwable + * @throws OidcServerException + */ public function testReturnsFalseWhenOfflineAccessScopeNotPresent(): void { $this->clientStub->method('getScopes')->willReturn(['openid']); @@ -118,16 +94,19 @@ public function testReturnsFalseWhenOfflineAccessScopeNotPresent(): void ); $this->openIdConfigurationStub->method('getBoolean')->willReturn(false); - $this->configurationServiceStub->method('getOpenIDConnectConfiguration') + $this->moduleConfigStub->method('config') ->willReturn($this->openIdConfigurationStub); - $result = (new ScopeOfflineAccessRule($this->configurationServiceStub)) + $result = (new ScopeOfflineAccessRule()) ->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); $this->assertNotNull($result); $this->assertFalse($result->getValue()); } + /** + * @throws Throwable + */ public function testThrowsWhenClientDoesntHaveOfflineAccessScopeRegistered(): void { $this->clientStub->method('getScopes')->willReturn(['openid']); @@ -145,15 +124,19 @@ public function testThrowsWhenClientDoesntHaveOfflineAccessScopeRegistered(): vo ); $this->openIdConfigurationStub->method('getBoolean')->willReturn(false); - $this->configurationServiceStub->method('getOpenIDConnectConfiguration') + $this->moduleConfigStub->method('config') ->willReturn($this->openIdConfigurationStub); $this->expectException(OidcServerException::class); - (new ScopeOfflineAccessRule($this->configurationServiceStub)) + (new ScopeOfflineAccessRule()) ->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); } + /** + * @throws Throwable + * @throws OidcServerException + */ public function testReturnsTrueWhenClientDoesHaveOfflineAccessScopeRegistered(): void { $this->clientStub->method('getScopes')->willReturn(['openid', 'offline_access']); @@ -171,10 +154,10 @@ public function testReturnsTrueWhenClientDoesHaveOfflineAccessScopeRegistered(): ); $this->openIdConfigurationStub->method('getBoolean')->willReturn(false); - $this->configurationServiceStub->method('getOpenIDConnectConfiguration') + $this->moduleConfigStub->method('config') ->willReturn($this->openIdConfigurationStub); - $result = (new ScopeOfflineAccessRule($this->configurationServiceStub)) + $result = (new ScopeOfflineAccessRule()) ->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); $this->assertNotNull($result); diff --git a/tests/Utils/Checker/Rules/ScopeRuleTest.php b/tests/src/Utils/Checker/Rules/ScopeRuleTest.php similarity index 91% rename from tests/Utils/Checker/Rules/ScopeRuleTest.php rename to tests/src/Utils/Checker/Rules/ScopeRuleTest.php index 72015a19..134a74ec 100644 --- a/tests/Utils/Checker/Rules/ScopeRuleTest.php +++ b/tests/src/Utils/Checker/Rules/ScopeRuleTest.php @@ -1,12 +1,17 @@ '', 'scope_delimiter_string' => ' ', @@ -36,10 +41,13 @@ class ScopeRuleTest extends TestCase protected Result $redirectUriResult; protected Result $stateResult; - protected $requestStub; + protected Stub $requestStub; - protected $loggerServiceStub; + protected Stub $loggerServiceStub; + /** + * @throws Exception + */ protected function setUp(): void { $this->scopeRepositoryStub = $this->createStub(ScopeRepositoryInterface::class); @@ -137,7 +145,7 @@ protected function prepareValidResultBag(): ResultBag return $resultBag; } - protected function prepareValidScopeRepositoryStub() + protected function prepareValidScopeRepositoryStub(): InvocationStubber { return $this->scopeRepositoryStub ->method('getScopeEntityByIdentifier') diff --git a/tests/Utils/Checker/Rules/StateRuleTest.php b/tests/src/Utils/Checker/Rules/StateRuleTest.php similarity index 92% rename from tests/Utils/Checker/Rules/StateRuleTest.php rename to tests/src/Utils/Checker/Rules/StateRuleTest.php index e9f3a3e8..dff253b8 100644 --- a/tests/Utils/Checker/Rules/StateRuleTest.php +++ b/tests/src/Utils/Checker/Rules/StateRuleTest.php @@ -1,7 +1,11 @@ loggerServiceStub = $this->createStub(LoggerService::class); @@ -31,6 +38,7 @@ public function testGetKey(): void /** * @throws OidcServerException + * @throws Exception */ public function testCheckRuleGetMethod(): void { @@ -52,6 +60,7 @@ public function testCheckRuleGetMethod(): void /** * @throws OidcServerException + * @throws Exception */ public function testCheckRulePostMethod(): void { @@ -73,6 +82,7 @@ public function testCheckRulePostMethod(): void /** * @throws OidcServerException + * @throws Exception */ public function testCheckRuleReturnsNullWhenMethodNotAllowed(): void { @@ -85,7 +95,7 @@ public function testCheckRuleReturnsNullWhenMethodNotAllowed(): void $resultBag = new ResultBag(); $rule = new StateRule(); - $result = $rule->checkRule($request, $resultBag, $this->loggerServiceStub, [], false, ['GET']); + $result = $rule->checkRule($request, $resultBag, $this->loggerServiceStub); $this->assertInstanceOf(ResultInterface::class, $result); $this->assertNull($result->getValue()); @@ -93,6 +103,7 @@ public function testCheckRuleReturnsNullWhenMethodNotAllowed(): void /** * @throws OidcServerException + * @throws Exception */ public function testCheckRuleReturnsNullWhenMethodNotSupported(): void { diff --git a/tests/Utils/Checker/Rules/UiLocalesRuleTest.php b/tests/src/Utils/Checker/Rules/UiLocalesRuleTest.php similarity index 83% rename from tests/Utils/Checker/Rules/UiLocalesRuleTest.php rename to tests/src/Utils/Checker/Rules/UiLocalesRuleTest.php index 3c3a7743..9a5fe1da 100644 --- a/tests/Utils/Checker/Rules/UiLocalesRuleTest.php +++ b/tests/src/Utils/Checker/Rules/UiLocalesRuleTest.php @@ -1,7 +1,11 @@ requestStub = $this->createStub(ServerRequestInterface::class); @@ -37,7 +44,7 @@ public function testCheckRuleReturnsResultWhenParamSet() $result = (new UiLocalesRule()) ->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? - new Result(UiLocalesRule::class); + new Result(UiLocalesRule::class); $this->assertEquals('en', $result->getValue()); } @@ -51,7 +58,7 @@ public function testCheckRuleReturnsNullWhenParamNotSet() $result = (new UiLocalesRule()) ->checkRule($this->requestStub, $this->resultBagStub, $this->loggerServiceStub) ?? - new Result(UiLocalesRule::class); + new Result(UiLocalesRule::class); $this->assertNull($result->getValue()); } diff --git a/tests/ClaimTranslatorExtractorTest.php b/tests/src/Utils/ClaimTranslatorExtractorTest.php similarity index 93% rename from tests/ClaimTranslatorExtractorTest.php rename to tests/src/Utils/ClaimTranslatorExtractorTest.php index 0e472d7b..536d7317 100644 --- a/tests/ClaimTranslatorExtractorTest.php +++ b/tests/src/Utils/ClaimTranslatorExtractorTest.php @@ -1,22 +1,25 @@ scopeEntityOpenId = $this->createStub(ScopeEntityInterface::class); - $this->scopeEntityOpenId->method('getIdentifier')->willReturn('openid'); - $this->scopeEntityProfile = $this->createStub(ScopeEntityInterface::class); - $this->scopeEntityProfile->method('getIdentifier')->willReturn('profile'); + $this->scopeEntityOpenIdStub = $this->createStub(ScopeEntityInterface::class); + $this->scopeEntityOpenIdStub->method('getIdentifier')->willReturn('openid'); + $this->scopeEntityProfileStub = $this->createStub(ScopeEntityInterface::class); + $this->scopeEntityProfileStub->method('getIdentifier')->willReturn('profile'); $this->scopeEntitiesArray = [ - $this->scopeEntityOpenId, - $this->scopeEntityProfile + $this->scopeEntityOpenIdStub, + $this->scopeEntityProfileStub ]; } + /** + * @throws OidcServerException + */ public function testCanCheckScopeExistence(): void { $this->assertTrue(ScopeHelper::scopeExists($this->scopeEntitiesArray, 'openid')); diff --git a/tests/Utils/UniqueIdentifierGeneratorTest.php b/tests/src/Utils/UniqueIdentifierGeneratorTest.php similarity index 95% rename from tests/Utils/UniqueIdentifierGeneratorTest.php rename to tests/src/Utils/UniqueIdentifierGeneratorTest.php index a6a87fa4..17c78b98 100644 --- a/tests/Utils/UniqueIdentifierGeneratorTest.php +++ b/tests/src/Utils/UniqueIdentifierGeneratorTest.php @@ -1,5 +1,7 @@ Date: Sun, 3 Dec 2023 12:26:36 +0100 Subject: [PATCH 005/130] 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 fe6f6489ba78c25a9163947300921926ec5b6415 Mon Sep 17 00:00:00 2001 From: Al F Date: Thu, 7 Dec 2023 14:07:54 -0500 Subject: [PATCH 006/130] Fix CsrfProtection callable and move it to array callback format (#213) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix CsrfProtection callable and move it to array callback format --------- Co-authored-by: Marko Ivančić --- src/Forms/Controls/CsrfProtection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Forms/Controls/CsrfProtection.php b/src/Forms/Controls/CsrfProtection.php index 7d9afb28..f7b81dd6 100644 --- a/src/Forms/Controls/CsrfProtection.php +++ b/src/Forms/Controls/CsrfProtection.php @@ -24,7 +24,7 @@ class CsrfProtection extends BaseCsrfProtection { - final public const PROTECTION = 'SimpleSAML\Module\oidc\Form\Controls\CsrfProtection::validateCsrf'; + final public const PROTECTION = [\SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection::class, 'validateCsrf']; protected Session $sspSession; From c42edce16dfddced05d29cb0e191f8f3d8c13c2b Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Fri, 9 Feb 2024 13:58:40 +0100 Subject: [PATCH 007/130] Introduce installation script (#214) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introduce installation script to support automatic deployments * Update readme --------- Co-authored-by: Marko Ivančić --- README.md | 4 +++ bin/install.php | 39 ++++++++++++++++++++++++++ composer.json | 3 +- psalm.xml | 3 ++ src/Forms/ClientForm.php | 14 ++++++--- src/Forms/Controls/CsrfProtection.php | 3 +- src/Utils/ClaimTranslatorExtractor.php | 2 +- 7 files changed, 61 insertions(+), 7 deletions(-) create mode 100755 bin/install.php diff --git a/README.md b/README.md index b122b509..f09c0c59 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,10 @@ The module comes with some default SQL migrations which set up needed tables in open the _Federation_ tab from your _SimpleSAMLphp_ installation and select the option _OpenID Connect Installation_ inside the _Tools_ section. Once there, all you need to do is press the _Install_ button and the schema will be created. +Alternatively, in case of automatic / scripted deployments, you can run the 'install.php' script from the command line: + + php modules/oidc/bin/install.php + ### Relying Party (RP) Administration The module lets you manage (create, read, update and delete) approved RPs from the module user interface itself. diff --git a/bin/install.php b/bin/install.php new file mode 100755 index 00000000..6028c7ec --- /dev/null +++ b/bin/install.php @@ -0,0 +1,39 @@ +#!/usr/bin/env php +isUpdated()) { + echo 'Database is up to date, skipping.' . PHP_EOL; + return 0; + } + + echo 'Running database migrations.' . PHP_EOL; + + $databaseMigration->migrate(); + + echo 'Done running migrations.'; + return 0; +} catch (Throwable $exception) { + echo 'There was an error while trying run database migrations: ' . $exception->getMessage(); + return 1; +} diff --git a/composer.json b/composer.json index 14530243..39f8b63b 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,8 @@ }, "sort-packages": true, "allow-plugins": { - "simplesamlphp/composer-module-installer": true + "simplesamlphp/composer-module-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true }, "cache-dir": "build/composer" }, diff --git a/psalm.xml b/psalm.xml index ddac7608..0eb366f7 100644 --- a/psalm.xml +++ b/psalm.xml @@ -44,6 +44,9 @@ + + + diff --git a/src/Forms/ClientForm.php b/src/Forms/ClientForm.php index 5387e316..9b43be51 100644 --- a/src/Forms/ClientForm.php +++ b/src/Forms/ClientForm.php @@ -17,12 +17,16 @@ namespace SimpleSAML\Module\oidc\Forms; use Exception; +use Nette\Forms\Container; use Nette\Forms\Form; use SimpleSAML\Auth\Source; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection; use Traversable; +/** + * @psalm-suppress PropertyNotSetInConstructor Raised for $httpRequest which is marked as internal, so won't handle. + */ class ClientForm extends Form { protected const TYPE_ARRAY = 'array'; @@ -123,7 +127,7 @@ protected function validateByMatchingRegex( } } - public function getValues($returnType = null, ?array $controls = null): array + public function getValues(string|object|bool|null $returnType = null, ?array $controls = null): array { /** @var array $values */ $values = parent::getValues(self::TYPE_ARRAY); @@ -157,7 +161,7 @@ public function getValues($returnType = null, ?array $controls = null): array /** * @throws Exception */ - public function setDefaults($data, bool $erase = false): ClientForm + public function setDefaults(object|array $data, bool $erase = false): static { if (! is_array($data)) { if ($data instanceof Traversable) { @@ -187,7 +191,9 @@ public function setDefaults($data, bool $erase = false): ClientForm $scopes = is_array($data['scopes']) ? $data['scopes'] : []; $data['scopes'] = array_intersect($scopes, array_keys($this->getScopes())); - return parent::setDefaults($data, $erase); + parent::setDefaults($data, $erase); + + return $this; } /** @@ -203,7 +209,7 @@ protected function buildForm(): void $this->onValidate[] = $this->validateBackChannelLogoutUri(...); $this->setMethod('POST'); - $this->addComponent(new CsrfProtection('{oidc:client:csrf_error}'), Form::PROTECTOR_ID); + $this->addComponent(new CsrfProtection('{oidc:client:csrf_error}'), 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 f7b81dd6..1935b4a9 100644 --- a/src/Forms/Controls/CsrfProtection.php +++ b/src/Forms/Controls/CsrfProtection.php @@ -21,6 +21,7 @@ use Nette\InvalidStateException; use Nette\Utils\Random; use SimpleSAML\Session; +use Stringable; class CsrfProtection extends BaseCsrfProtection { @@ -32,7 +33,7 @@ class CsrfProtection extends BaseCsrfProtection /** * @throws Exception */ - public function __construct(object|string $errorMessage) + public function __construct(string|Stringable|null $errorMessage) { // 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. diff --git a/src/Utils/ClaimTranslatorExtractor.php b/src/Utils/ClaimTranslatorExtractor.php index fee1ebfb..e52fc404 100644 --- a/src/Utils/ClaimTranslatorExtractor.php +++ b/src/Utils/ClaimTranslatorExtractor.php @@ -197,7 +197,7 @@ public function addClaimSet(ClaimSetEntityInterface $claimSet): self { $scope = $claimSet->getScope(); - if (in_array($scope, $this->protectedClaims) && !empty($this->claimSets[$scope])) { + if (in_array($scope, $this->protectedClaims) && isset($this->claimSets[$scope])) { throw OidcServerException::serverError( sprintf("%s is a protected scope and is pre-defined by the OpenID Connect specification.", $scope) ); From b21e9421cd3eb8050acdf9dd53cfb459230b0d91 Mon Sep 17 00:00:00 2001 From: mheder Date: Thu, 29 Feb 2024 13:40:55 +0100 Subject: [PATCH 008/130] Update README.md (#215) extending the documentation with the enabling step --- README.md | 61 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index f09c0c59..1110a478 100644 --- a/README.md +++ b/README.md @@ -54,28 +54,6 @@ you have at least the following parameters set: 'database.username' => 'user', 'database.password' => 'password', -### Run database migrations - -The module comes with some default SQL migrations which set up needed tables in the configured database. To run them, -open the _Federation_ tab from your _SimpleSAMLphp_ installation and select the option _OpenID Connect Installation_ -inside the _Tools_ section. Once there, all you need to do is press the _Install_ button and the schema will be created. - -Alternatively, in case of automatic / scripted deployments, you can run the 'install.php' script from the command line: - - php modules/oidc/bin/install.php - -### 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 open the _Federation_ tab from your _SimpleSAMLphp_ installation -and select the option _OpenID Connect Client Registry_ inside the _Tools_ section. - -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. - -Client ID and secret will be generated, and can be seen after the client creation by clicking on the 'show' button. - ### Create RSA key pair During the authentication flow, generated ID Token and Access Token will be in a form of signed JSON Web token (JWS). @@ -99,6 +77,43 @@ or use your passphrase if provided on private key generation: If you use a passphrase, make sure to also configure it in the `module_oidc.php` config file. +### Enabling the module + +At this point we can enable the module by adding `'oidc' => true` to the list of enabled modules in the main simplesamlphp configuration file, `config/config.php`. + + 'module.enable' => [ + 'exampleauth' => false, + 'core' => true, + 'admin' => true, + 'saml' => true, + // enable oidc module + 'oidc' => true + ], + +This is required the enable the module on the _Federation_ tab in the admin web interface, which can be used in the next two steps to finalize the installation. + +### Run database migrations + +The module comes with some default SQL migrations which set up needed tables in the configured database. To run them, +open the _Federation_ tab from your _SimpleSAMLphp_ installation and select the option _OpenID Connect Installation_ +inside the _Tools_ section. Once there, all you need to do is press the _Install_ button and the schema will be created. + +Alternatively, in case of automatic / scripted deployments, you can run the 'install.php' script from the command line: + + php modules/oidc/bin/install.php + +### 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 open the _Federation_ tab from your _SimpleSAMLphp_ installation +and select the option _OpenID Connect Client Registry_ inside the _Tools_ section. + +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. + +Client ID and secret will be generated, and can be seen after the client creation by clicking on the 'show' button. + ### Cron hook In order to purge expired tokens, this module requires [cron module](https://simplesamlphp.org/docs/stable/cron:cron) @@ -332,4 +347,4 @@ See [CONFORMANCE_TEST.md](CONFORMANCE_TEST.md) ## Have more questions? -Check the [FAQ](FAQ.md). \ No newline at end of file +Check the [FAQ](FAQ.md). From 14c484074e289bdd0758c6199f1f8781d6ff750b Mon Sep 17 00:00:00 2001 From: mheder Date: Thu, 29 Feb 2024 13:49:58 +0100 Subject: [PATCH 009/130] Update README.md (#216) helping the deployer learn the endpoints --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 1110a478..c24c49a3 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,14 @@ Client ID and secret will be generated, and can be seen after the client creatio In order to purge expired tokens, this module requires [cron module](https://simplesamlphp.org/docs/stable/cron:cron) 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/openid-configuration.php` + +This endpoint can be used to set up a `.well-known` URL (see below). + ## Additional considerations ### Private scopes 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 010/130] 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 011/130] 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 012/130] 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 6d32cc29e16ca38d0d208794ab3193b710d110b6 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 22 May 2024 16:55:02 +0200 Subject: [PATCH 013/130] Add SSP-specific sniffs --- phpcs.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/phpcs.xml b/phpcs.xml index edcdc292..6c188982 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -22,7 +22,14 @@ - + + + + + + + + From a9270b5bb1780b9d140188621d24a21af7db1ef0 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 22 May 2024 23:48:31 +0200 Subject: [PATCH 014/130] Fix trailing commas --- src/Controller/AuthorizationController.php | 4 +- src/Controller/Client/CreateController.php | 4 +- src/Controller/Client/DeleteController.php | 2 +- src/Controller/Client/EditController.php | 8 +- src/Controller/Client/IndexController.php | 2 +- .../Client/ResetSecretController.php | 4 +- src/Controller/Client/ShowController.php | 4 +- src/Controller/InstallerController.php | 2 +- src/Controller/LogoutController.php | 18 ++--- src/Controller/UserInfoController.php | 4 +- src/Entities/AccessTokenEntity.php | 8 +- src/Entities/AuthCodeEntity.php | 4 +- src/Entities/ClientEntity.php | 4 +- .../Interfaces/ClientEntityInterface.php | 2 +- src/Entities/RefreshTokenEntity.php | 2 +- src/Entities/ScopeEntity.php | 2 +- src/Factories/AuthSimpleFactory.php | 2 +- src/Factories/AuthorizationServerFactory.php | 12 +-- src/Factories/CryptKeyFactory.php | 4 +- src/Factories/Grant/AuthCodeGrantFactory.php | 4 +- src/Factories/Grant/ImplicitGrantFactory.php | 4 +- .../Grant/OAuth2ImplicitGrantFactory.php | 2 +- .../Grant/RefreshTokenGrantFactory.php | 2 +- src/Factories/IdTokenResponseFactory.php | 4 +- src/Factories/ResourceServerFactory.php | 4 +- src/Forms/ClientForm.php | 18 ++--- src/ModuleConfig.php | 16 ++-- src/Repositories/AccessTokenRepository.php | 14 ++-- src/Repositories/AllowedOriginRepository.php | 6 +- src/Repositories/AuthCodeRepository.php | 16 ++-- src/Repositories/ClientRepository.php | 30 ++++---- .../AccessTokenRepositoryInterface.php | 2 +- src/Repositories/RefreshTokenRepository.php | 12 +-- src/Repositories/ScopeRepository.php | 6 +- .../Traits/RevokeTokenByAuthCodeIdTrait.php | 4 +- src/Repositories/UserRepository.php | 14 ++-- .../Associations/RelyingPartyAssociation.php | 2 +- src/Server/AuthorizationServer.php | 6 +- src/Server/Exceptions/OidcServerException.php | 16 ++-- src/Server/Grants/AuthCodeGrant.php | 44 +++++------ src/Server/Grants/ImplicitGrant.php | 20 ++--- ...lidatableWithCheckerResultBagInterface.php | 2 +- src/Server/Grants/OAuth2ImplicitGrant.php | 4 +- src/Server/Grants/RefreshTokenGrant.php | 2 +- .../Grants/Traits/IssueAccessTokenTrait.php | 6 +- .../BackChannelLogoutHandler.php | 6 +- .../RequestTypes/AuthorizationRequest.php | 2 +- src/Server/RequestTypes/LogoutRequest.php | 2 +- src/Server/ResponseTypes/IdTokenResponse.php | 4 +- .../Validators/BearerTokenValidator.php | 8 +- src/Services/AuthContextService.php | 2 +- src/Services/AuthProcService.php | 6 +- src/Services/AuthenticationService.php | 18 ++--- src/Services/Container.php | 18 ++--- src/Services/DatabaseLegacyOAuth2Import.php | 2 +- src/Services/DatabaseMigration.php | 71 +++++++++-------- src/Services/IdTokenBuilder.php | 20 ++--- src/Services/JsonWebTokenBuilderService.php | 8 +- src/Services/LogoutTokenBuilder.php | 2 +- src/Services/OpMetadataService.php | 2 +- src/Services/RoutingService.php | 2 +- src/Services/SessionService.php | 14 ++-- src/Stores/Session/LogoutTicketStoreDb.php | 8 +- .../Interfaces/RequestRuleInterface.php | 2 +- src/Utils/Checker/RequestRulesManager.php | 4 +- src/Utils/Checker/Rules/AbstractRule.php | 10 +-- src/Utils/Checker/Rules/AcrValuesRule.php | 4 +- .../Checker/Rules/AddClaimsToIdTokenRule.php | 2 +- src/Utils/Checker/Rules/ClientIdRule.php | 2 +- .../Checker/Rules/CodeChallengeMethodRule.php | 6 +- src/Utils/Checker/Rules/CodeChallengeRule.php | 6 +- src/Utils/Checker/Rules/IdTokenHintRule.php | 12 +-- src/Utils/Checker/Rules/MaxAgeRule.php | 6 +- .../Rules/PostLogoutRedirectUriRule.php | 6 +- src/Utils/Checker/Rules/PromptRule.php | 6 +- src/Utils/Checker/Rules/RedirectUriRule.php | 2 +- .../Checker/Rules/RequestParameterRule.php | 4 +- .../Checker/Rules/RequestedClaimsRule.php | 4 +- src/Utils/Checker/Rules/RequiredNonceRule.php | 4 +- .../Checker/Rules/RequiredOpenIdScopeRule.php | 6 +- src/Utils/Checker/Rules/ResponseTypeRule.php | 2 +- .../Checker/Rules/ScopeOfflineAccessRule.php | 4 +- src/Utils/Checker/Rules/ScopeRule.php | 4 +- src/Utils/Checker/Rules/StateRule.php | 4 +- src/Utils/Checker/Rules/UiLocalesRule.php | 4 +- src/Utils/ClaimTranslatorExtractor.php | 32 ++++---- .../Controller/AccessTokenControllerTest.php | 4 +- .../AuthorizationControllerTest.php | 16 ++-- .../Client/CreateControllerTest.php | 6 +- .../Client/DeleteControllerTest.php | 6 +- .../Controller/Client/EditControllerTest.php | 20 ++--- .../Controller/Client/IndexControllerTest.php | 8 +- .../Client/ResetSecretControllerTest.php | 8 +- .../Controller/Client/ShowControllerTest.php | 10 +-- .../ConfigurationDiscoveryControllerTest.php | 4 +- .../Controller/InstallerControllerTest.php | 18 ++--- tests/src/Controller/JwksControllerTest.php | 5 +- tests/src/Controller/LogoutControllerTest.php | 16 ++-- .../src/Controller/UserInfoControllerTest.php | 14 ++-- tests/src/Entities/AccessTokenEntityTest.php | 6 +- tests/src/Entities/AuthCodeEntityTest.php | 6 +- tests/src/Entities/ClientEntityTest.php | 8 +- tests/src/Entities/RefreshTokenEntityTest.php | 4 +- tests/src/Entities/ScopeEntityTest.php | 4 +- tests/src/Entities/UserEntityTest.php | 6 +- .../ClaimTranslatorExtractorFactoryTest.php | 24 +++--- tests/src/ModuleConfigTest.php | 14 ++-- .../AccessTokenRepositoryTest.php | 4 +- .../src/Repositories/ClientRepositoryTest.php | 6 +- .../src/Repositories/ScopeRepositoryTest.php | 2 +- .../RelyingPartyAssociationTest.php | 2 +- tests/src/Server/Grants/AuthCodeGrantTest.php | 2 +- .../BackChannelLogoutHandlerTest.php | 4 +- .../Server/RequestTypes/LogoutRequestTest.php | 2 +- .../ResponseTypes/IdTokenResponseTest.php | 30 ++++---- .../Validators/BearerTokenValidatorTest.php | 8 +- tests/src/Services/AuthContextServiceTest.php | 16 ++-- tests/src/Services/AuthProcServiceTest.php | 4 +- .../Services/AuthenticationServiceTest.php | 10 +-- .../src/Services/JsonWebKeySetServiceTest.php | 2 +- .../JsonWebTokenBuilderServiceTest.php | 14 ++-- tests/src/Services/LogoutTokenBuilderTest.php | 10 +-- tests/src/Services/OpMetadataServiceTest.php | 4 +- .../Services/SessionMessagesServiceTest.php | 4 +- .../Utils/Checker/RequestRulesManagerTest.php | 2 +- .../Utils/Checker/Rules/AcrValuesRuleTest.php | 6 +- .../Checker/Rules/IdTokenHintRuleTest.php | 10 +-- .../Rules/PostLogoutRedirectUriRuleTest.php | 26 +++---- .../Checker/Rules/RequestedClaimsRuleTest.php | 26 +++---- .../Rules/ScopeOfflineAccessRuleTest.php | 8 +- .../src/Utils/Checker/Rules/ScopeRuleTest.php | 12 +-- .../Utils/ClaimTranslatorExtractorTest.php | 76 +++++++++---------- tests/src/Utils/ScopeHelperTest.php | 2 +- 133 files changed, 590 insertions(+), 592 deletions(-) diff --git a/src/Controller/AuthorizationController.php b/src/Controller/AuthorizationController.php index 33799b56..f6592b6a 100644 --- a/src/Controller/AuthorizationController.php +++ b/src/Controller/AuthorizationController.php @@ -36,7 +36,7 @@ public function __construct( private readonly AuthenticationService $authenticationService, private readonly AuthorizationServer $authorizationServer, private readonly ModuleConfig $moduleConfig, - private readonly LoggerService $loggerService + private readonly LoggerService $loggerService, ) { } @@ -139,7 +139,7 @@ protected function validateAcr(AuthorizationRequest $authorizationRequest): void $message = sprintf( 'No ACRs configured for current auth source, whilst specification mandates one. ' . 'Falling back to generic ACR (%s).', - $genericAcr + $genericAcr, ); $this->loggerService->warning($message); $authorizationRequest->setAcr($genericAcr); diff --git a/src/Controller/Client/CreateController.php b/src/Controller/Client/CreateController.php index 126ada66..852227a3 100644 --- a/src/Controller/Client/CreateController.php +++ b/src/Controller/Client/CreateController.php @@ -39,7 +39,7 @@ public function __construct( private readonly TemplateFactory $templateFactory, private readonly FormFactory $formFactory, private readonly SessionMessagesService $messages, - private readonly AuthContextService $authContextService + private readonly AuthContextService $authContextService, ) { } @@ -95,7 +95,7 @@ public function __invoke(): Template|RedirectResponse empty($client['auth_source']) ? null : (string)$client['auth_source'], empty($client['owner']) ? null : (string)$client['owner'], $postLogoutRedirectUris, - empty($client['backchannel_logout_uri']) ? null : (string)$client['backchannel_logout_uri'] + empty($client['backchannel_logout_uri']) ? null : (string)$client['backchannel_logout_uri'], )); // Also persist allowed origins for this client. diff --git a/src/Controller/Client/DeleteController.php b/src/Controller/Client/DeleteController.php index c48112fd..b656af73 100644 --- a/src/Controller/Client/DeleteController.php +++ b/src/Controller/Client/DeleteController.php @@ -40,7 +40,7 @@ public function __construct( ClientRepository $clientRepository, private readonly TemplateFactory $templateFactory, private readonly SessionMessagesService $messages, - AuthContextService $authContextService + AuthContextService $authContextService, ) { $this->clientRepository = $clientRepository; $this->authContextService = $authContextService; diff --git a/src/Controller/Client/EditController.php b/src/Controller/Client/EditController.php index 2e06240d..da10c1b5 100644 --- a/src/Controller/Client/EditController.php +++ b/src/Controller/Client/EditController.php @@ -44,7 +44,7 @@ public function __construct( private readonly TemplateFactory $templateFactory, private readonly FormFactory $formFactory, private readonly SessionMessagesService $messages, - AuthContextService $authContextService + AuthContextService $authContextService, ) { $this->clientRepository = $clientRepository; $this->authContextService = $authContextService; @@ -102,7 +102,7 @@ public function __invoke(ServerRequest $request): Template|RedirectResponse empty($data['auth_source']) ? null : (string)$data['auth_source'], $client->getOwner(), $postLogoutRedirectUris, - empty($data['backchannel_logout_uri']) ? null : (string)$data['backchannel_logout_uri'] + empty($data['backchannel_logout_uri']) ? null : (string)$data['backchannel_logout_uri'], ), $authedUser); // Also persist allowed origins for this client. @@ -111,7 +111,7 @@ public function __invoke(ServerRequest $request): Template|RedirectResponse $this->messages->addMessage('{oidc:client:updated}'); return new RedirectResponse( - (new HTTP())->addURLParameters('show.php', ['client_id' => $client->getIdentifier()]) + (new HTTP())->addURLParameters('show.php', ['client_id' => $client->getIdentifier()]), ); } @@ -119,7 +119,7 @@ public function __invoke(ServerRequest $request): Template|RedirectResponse 'form' => $form, 'regexUri' => ClientForm::REGEX_URI, 'regexAllowedOriginUrl' => ClientForm::REGEX_ALLOWED_ORIGIN_URL, - 'regexHttpUri' => ClientForm::REGEX_HTTP_URI + 'regexHttpUri' => ClientForm::REGEX_HTTP_URI, ]); } } diff --git a/src/Controller/Client/IndexController.php b/src/Controller/Client/IndexController.php index 84f5b8f2..8bed980a 100644 --- a/src/Controller/Client/IndexController.php +++ b/src/Controller/Client/IndexController.php @@ -28,7 +28,7 @@ class IndexController public function __construct( private readonly ClientRepository $clientRepository, private readonly TemplateFactory $templateFactory, - private readonly AuthContextService $authContextService + private readonly AuthContextService $authContextService, ) { } diff --git a/src/Controller/Client/ResetSecretController.php b/src/Controller/Client/ResetSecretController.php index 172ce0f8..3c6c5a9b 100644 --- a/src/Controller/Client/ResetSecretController.php +++ b/src/Controller/Client/ResetSecretController.php @@ -35,7 +35,7 @@ class ResetSecretController public function __construct( ClientRepository $clientRepository, private readonly SessionMessagesService $messages, - AuthContextService $authContextService + AuthContextService $authContextService, ) { $this->clientRepository = $clientRepository; $this->authContextService = $authContextService; @@ -69,7 +69,7 @@ public function __invoke(ServerRequest $request): RedirectResponse } return new RedirectResponse( - (new HTTP())->addURLParameters('show.php', ['client_id' => $client->getIdentifier()]) + (new HTTP())->addURLParameters('show.php', ['client_id' => $client->getIdentifier()]), ); } } diff --git a/src/Controller/Client/ShowController.php b/src/Controller/Client/ShowController.php index bb06e86e..3a6b743b 100644 --- a/src/Controller/Client/ShowController.php +++ b/src/Controller/Client/ShowController.php @@ -36,7 +36,7 @@ public function __construct( ClientRepository $clientRepository, private readonly AllowedOriginRepository $allowedOriginRepository, private readonly TemplateFactory $templateFactory, - AuthContextService $authContextService + AuthContextService $authContextService, ) { $this->clientRepository = $clientRepository; $this->authContextService = $authContextService; @@ -52,7 +52,7 @@ public function __invoke(ServerRequest $request): Template return $this->templateFactory->render('oidc:clients/show.twig', [ 'client' => $client, - 'allowedOrigins' => $allowedOrigins + 'allowedOrigins' => $allowedOrigins, ]); } } diff --git a/src/Controller/InstallerController.php b/src/Controller/InstallerController.php index 63e5b591..5ae32b20 100644 --- a/src/Controller/InstallerController.php +++ b/src/Controller/InstallerController.php @@ -34,7 +34,7 @@ public function __construct( private readonly TemplateFactory $templateFactory, private readonly SessionMessagesService $messages, private readonly DatabaseMigration $databaseMigration, - private readonly DatabaseLegacyOAuth2Import $databaseLegacyOAuth2Import + private readonly DatabaseLegacyOAuth2Import $databaseLegacyOAuth2Import, ) { } diff --git a/src/Controller/LogoutController.php b/src/Controller/LogoutController.php index 7e000cec..87040110 100644 --- a/src/Controller/LogoutController.php +++ b/src/Controller/LogoutController.php @@ -28,7 +28,7 @@ public function __construct( protected SessionService $sessionService, protected LogoutTicketStoreBuilder $sessionLogoutTicketStoreBuilder, protected LoggerService $loggerService, - protected TemplateFactory $templateFactory + protected TemplateFactory $templateFactory, ) { } @@ -87,7 +87,7 @@ public function __invoke(ServerRequest $request): Response } } catch (Throwable $exception) { $this->loggerService->warning( - sprintf('Logout: could not get session with ID %s, error: %s', $sidClaim, $exception->getMessage()) + sprintf('Logout: could not get session with ID %s, error: %s', $sidClaim, $exception->getMessage()), ); } } @@ -131,8 +131,8 @@ public static function logoutHandler(): void $sessionLogoutTicketStore = LogoutTicketStoreBuilder::getStaticInstance(); $sessionLogoutTickets = $sessionLogoutTicketStore->getAll(); - if (! empty($sessionLogoutTickets)) { - // TODO low mivanci This could brake since interface does not mandate type. Move to strong typing. + if (!empty($sessionLogoutTickets)) { + // TODO 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']; @@ -144,7 +144,7 @@ public static function logoutHandler(): void if (($sessionLogoutTicketSession = Session::getSession($sid)) !== null) { $relyingPartyAssociations = array_merge( $relyingPartyAssociations, - SessionService::getRelyingPartyAssociationsForSession($sessionLogoutTicketSession) + SessionService::getRelyingPartyAssociationsForSession($sessionLogoutTicketSession), ); SessionService::clearRelyingPartyAssociationsForSession($sessionLogoutTicketSession); @@ -154,14 +154,14 @@ public static function logoutHandler(): void sprintf( 'Session Ticket Logout: could not get session with ID %s, error: %s', $sid, - $exception->getMessage() - ) + $exception->getMessage(), + ), ); } } $sessionLogoutTicketStore->deleteMultiple( - array_map(fn(array $slt): string => (string)$slt['sid'], $sessionLogoutTickets) + array_map(fn(array $slt): string => (string)$slt['sid'], $sessionLogoutTickets), ); } @@ -183,7 +183,7 @@ protected function resolveResponse(LogoutRequest $logoutRequest, bool $wasLogout } return $this->templateFactory->render('oidc:/logout.twig', [ - 'wasLogoutActionCalled' => $wasLogoutActionCalled + 'wasLogoutActionCalled' => $wasLogoutActionCalled, ]); } } diff --git a/src/Controller/UserInfoController.php b/src/Controller/UserInfoController.php index 8c948617..e1f50517 100644 --- a/src/Controller/UserInfoController.php +++ b/src/Controller/UserInfoController.php @@ -37,7 +37,7 @@ public function __construct( private readonly AccessTokenRepository $accessTokenRepository, private readonly UserRepository $userRepository, private readonly AllowedOriginRepository $allowedOriginRepository, - private readonly ClaimTranslatorExtractor $claimTranslatorExtractor + private readonly ClaimTranslatorExtractor $claimTranslatorExtractor, ) { } @@ -70,7 +70,7 @@ public function __invoke(ServerRequest $request): Response $requestedClaims = $accessToken->getRequestedClaims(); $additionalClaims = $this->claimTranslatorExtractor->extractAdditionalUserInfoClaims( $requestedClaims, - $user->getClaims() + $user->getClaims(), ); $claims = array_merge($additionalClaims, $claims); diff --git a/src/Entities/AccessTokenEntity.php b/src/Entities/AccessTokenEntity.php index 0a1c4d86..117c3427 100644 --- a/src/Entities/AccessTokenEntity.php +++ b/src/Entities/AccessTokenEntity.php @@ -76,7 +76,7 @@ public static function fromData( array $scopes, int|string $userIdentifier = null, string $authCodeId = null, - array $requestedClaims = null + array $requestedClaims = null, ): self { $accessToken = new self(); @@ -118,7 +118,7 @@ public static function fromState(array $state): self $accessToken->identifier = $state['id']; $accessToken->scopes = $scopes; $accessToken->expiryDateTime = DateTimeImmutable::createFromMutable( - TimestampGenerator::utc($state['expires_at']) + TimestampGenerator::utc($state['expires_at']), ); $accessToken->userIdentifier = empty($state['user_id']) ? null : (string)$state['user_id']; $accessToken->client = $state['client']; @@ -129,7 +129,7 @@ public static function fromState(array $state): self empty($state['requested_claims']) ? '[]' : (string)$state['requested_claims'], true, 512, - JSON_THROW_ON_ERROR + JSON_THROW_ON_ERROR, ); if (!is_array($stateRequestedClaims)) { throw OidcServerException::serverError('Invalid Access Token Entity state: requested claims'); @@ -167,7 +167,7 @@ public function getState(): array 'client_id' => $this->getClient()->getIdentifier(), 'is_revoked' => (int) $this->isRevoked(), 'auth_code_id' => $this->getAuthCodeId(), - 'requested_claims' => json_encode($this->requestedClaims, JSON_THROW_ON_ERROR) + 'requested_claims' => json_encode($this->requestedClaims, JSON_THROW_ON_ERROR), ]; } diff --git a/src/Entities/AuthCodeEntity.php b/src/Entities/AuthCodeEntity.php index ac1feac4..9cca532a 100644 --- a/src/Entities/AuthCodeEntity.php +++ b/src/Entities/AuthCodeEntity.php @@ -63,13 +63,13 @@ public static function fromState(array $state): self * @return ScopeEntity */ fn(string $scope) => ScopeEntity::fromData($scope), - $stateScopes + $stateScopes, ); $authCode->identifier = $state['id']; $authCode->scopes = $scopes; $authCode->expiryDateTime = DateTimeImmutable::createFromMutable( - TimestampGenerator::utc($state['expires_at']) + TimestampGenerator::utc($state['expires_at']), ); $authCode->userIdentifier = empty($state['user_id']) ? null : (string)$state['user_id']; $authCode->client = $state['client']; diff --git a/src/Entities/ClientEntity.php b/src/Entities/ClientEntity.php index 0189d6b4..3838a9af 100644 --- a/src/Entities/ClientEntity.php +++ b/src/Entities/ClientEntity.php @@ -86,7 +86,7 @@ public static function fromData( ?string $authSource = null, ?string $owner = null, array $postLogoutRedirectUri = [], - ?string $backChannelLogoutUri = null + ?string $backChannelLogoutUri = null, ): ClientEntityInterface { $client = new self(); @@ -147,7 +147,7 @@ public static function fromState(array $state): self (string)($state['post_logout_redirect_uri'] ?? "[]"), true, 512, - JSON_THROW_ON_ERROR + JSON_THROW_ON_ERROR, ); $client->postLogoutRedirectUri = $postLogoutRedirectUris; diff --git a/src/Entities/Interfaces/ClientEntityInterface.php b/src/Entities/Interfaces/ClientEntityInterface.php index 3402905d..6c57a44b 100644 --- a/src/Entities/Interfaces/ClientEntityInterface.php +++ b/src/Entities/Interfaces/ClientEntityInterface.php @@ -22,7 +22,7 @@ public static function fromData( bool $isEnabled, bool $isConfidential = false, ?string $authSource = null, - ?string $owner = null + ?string $owner = null, ): self; public function toArray(): array; diff --git a/src/Entities/RefreshTokenEntity.php b/src/Entities/RefreshTokenEntity.php index e33e1697..ca111f2e 100644 --- a/src/Entities/RefreshTokenEntity.php +++ b/src/Entities/RefreshTokenEntity.php @@ -52,7 +52,7 @@ public static function fromState(array $state): RefreshTokenEntityInterface $refreshToken->identifier = $state['id']; $refreshToken->expiryDateTime = DateTimeImmutable::createFromMutable( - TimestampGenerator::utc($state['expires_at']) + TimestampGenerator::utc($state['expires_at']), ); $refreshToken->accessToken = $state['access_token']; $refreshToken->isRevoked = (bool) $state['is_revoked']; diff --git a/src/Entities/ScopeEntity.php b/src/Entities/ScopeEntity.php index d9ec406d..3511e3bb 100644 --- a/src/Entities/ScopeEntity.php +++ b/src/Entities/ScopeEntity.php @@ -52,7 +52,7 @@ public static function fromData( string $identifier, string $description = null, string $icon = null, - array $claims = [] + array $claims = [], ): self { $scope = new self(); diff --git a/src/Factories/AuthSimpleFactory.php b/src/Factories/AuthSimpleFactory.php index 86b1ed6e..5b339e19 100644 --- a/src/Factories/AuthSimpleFactory.php +++ b/src/Factories/AuthSimpleFactory.php @@ -28,7 +28,7 @@ class AuthSimpleFactory public function __construct( ClientRepository $clientRepository, - private readonly ModuleConfig $moduleConfig + private readonly ModuleConfig $moduleConfig, ) { $this->clientRepository = $clientRepository; } diff --git a/src/Factories/AuthorizationServerFactory.php b/src/Factories/AuthorizationServerFactory.php index 957e9fc8..4f4762e2 100644 --- a/src/Factories/AuthorizationServerFactory.php +++ b/src/Factories/AuthorizationServerFactory.php @@ -42,7 +42,7 @@ public function __construct( private readonly IdTokenResponse $idTokenResponse, private readonly RequestRulesManager $requestRulesManager, private readonly CryptKey $privateKey, - private readonly string $encryptionKey + private readonly string $encryptionKey, ) { } @@ -55,27 +55,27 @@ public function build(): AuthorizationServer $this->privateKey, $this->encryptionKey, $this->idTokenResponse, - $this->requestRulesManager + $this->requestRulesManager, ); $authorizationServer->enableGrantType( $this->authCodeGrant, - $this->accessTokenDuration + $this->accessTokenDuration, ); $authorizationServer->enableGrantType( $this->oAuth2ImplicitGrant, - $this->accessTokenDuration + $this->accessTokenDuration, ); $authorizationServer->enableGrantType( $this->implicitGrant, - $this->accessTokenDuration + $this->accessTokenDuration, ); $authorizationServer->enableGrantType( $this->refreshTokenGrant, - $this->accessTokenDuration + $this->accessTokenDuration, ); return $authorizationServer; diff --git a/src/Factories/CryptKeyFactory.php b/src/Factories/CryptKeyFactory.php index 9ed903ac..6fd38f71 100644 --- a/src/Factories/CryptKeyFactory.php +++ b/src/Factories/CryptKeyFactory.php @@ -10,7 +10,7 @@ class CryptKeyFactory { public function __construct( - private readonly ModuleConfig $moduleConfig + private readonly ModuleConfig $moduleConfig, ) { } @@ -21,7 +21,7 @@ public function buildPrivateKey(): CryptKey { return new CryptKey( $this->moduleConfig->getPrivateKeyPath(), - $this->moduleConfig->getPrivateKeyPassPhrase() + $this->moduleConfig->getPrivateKeyPassPhrase(), ); } diff --git a/src/Factories/Grant/AuthCodeGrantFactory.php b/src/Factories/Grant/AuthCodeGrantFactory.php index a3ab82d5..ccb0d087 100644 --- a/src/Factories/Grant/AuthCodeGrantFactory.php +++ b/src/Factories/Grant/AuthCodeGrantFactory.php @@ -32,7 +32,7 @@ public function __construct( private readonly RefreshTokenRepository $refreshTokenRepository, private readonly DateInterval $refreshTokenDuration, private readonly DateInterval $authCodeDuration, - private readonly RequestRulesManager $requestRulesManager + private readonly RequestRulesManager $requestRulesManager, ) { } @@ -46,7 +46,7 @@ public function build(): AuthCodeGrant $this->accessTokenRepository, $this->refreshTokenRepository, $this->authCodeDuration, - $this->requestRulesManager + $this->requestRulesManager, ); $authCodeGrant->setRefreshTokenTTL($this->refreshTokenDuration); diff --git a/src/Factories/Grant/ImplicitGrantFactory.php b/src/Factories/Grant/ImplicitGrantFactory.php index 6648239d..c1c2b7cb 100644 --- a/src/Factories/Grant/ImplicitGrantFactory.php +++ b/src/Factories/Grant/ImplicitGrantFactory.php @@ -27,7 +27,7 @@ public function __construct( private readonly IdTokenBuilder $idTokenBuilder, private readonly DateInterval $accessTokenDuration, private readonly RequestRulesManager $requestRulesManager, - private readonly AccessTokenRepository $accessTokenRepository + private readonly AccessTokenRepository $accessTokenRepository, ) { } @@ -38,7 +38,7 @@ public function build(): ImplicitGrant $this->accessTokenDuration, $this->accessTokenRepository, '#', - $this->requestRulesManager + $this->requestRulesManager, ); } } diff --git a/src/Factories/Grant/OAuth2ImplicitGrantFactory.php b/src/Factories/Grant/OAuth2ImplicitGrantFactory.php index f22fb53c..8480103e 100644 --- a/src/Factories/Grant/OAuth2ImplicitGrantFactory.php +++ b/src/Factories/Grant/OAuth2ImplicitGrantFactory.php @@ -23,7 +23,7 @@ class OAuth2ImplicitGrantFactory { public function __construct( private readonly DateInterval $accessTokenDuration, - private readonly RequestRulesManager $requestRulesManager + private readonly RequestRulesManager $requestRulesManager, ) { } diff --git a/src/Factories/Grant/RefreshTokenGrantFactory.php b/src/Factories/Grant/RefreshTokenGrantFactory.php index 5bfce3bd..babf98e7 100644 --- a/src/Factories/Grant/RefreshTokenGrantFactory.php +++ b/src/Factories/Grant/RefreshTokenGrantFactory.php @@ -23,7 +23,7 @@ class RefreshTokenGrantFactory { public function __construct( private readonly RefreshTokenRepository $refreshTokenRepository, - private readonly DateInterval $refreshTokenDuration + private readonly DateInterval $refreshTokenDuration, ) { } diff --git a/src/Factories/IdTokenResponseFactory.php b/src/Factories/IdTokenResponseFactory.php index 4c71b0bf..2310fb1d 100644 --- a/src/Factories/IdTokenResponseFactory.php +++ b/src/Factories/IdTokenResponseFactory.php @@ -27,7 +27,7 @@ public function __construct( private readonly UserRepository $userRepository, private readonly IdTokenBuilder $idTokenBuilder, private readonly CryptKey $privateKey, - private readonly string $encryptionKey + private readonly string $encryptionKey, ) { } @@ -36,7 +36,7 @@ public function build(): IdTokenResponse $idTokenResponse = new IdTokenResponse( $this->userRepository, $this->idTokenBuilder, - $this->privateKey + $this->privateKey, ); $idTokenResponse->setEncryptionKey($this->encryptionKey); diff --git a/src/Factories/ResourceServerFactory.php b/src/Factories/ResourceServerFactory.php index 909d2e25..9798b570 100644 --- a/src/Factories/ResourceServerFactory.php +++ b/src/Factories/ResourceServerFactory.php @@ -25,7 +25,7 @@ class ResourceServerFactory public function __construct( private readonly AccessTokenRepository $accessTokenRepository, private readonly CryptKey $publicKey, - private readonly AuthorizationValidatorInterface $authorizationValidator + private readonly AuthorizationValidatorInterface $authorizationValidator, ) { } @@ -34,7 +34,7 @@ public function build(): ResourceServer return new ResourceServer( $this->accessTokenRepository, $this->publicKey, - $this->authorizationValidator + $this->authorizationValidator, ); } } diff --git a/src/Forms/ClientForm.php b/src/Forms/ClientForm.php index 9b43be51..4c2462e9 100644 --- a/src/Forms/ClientForm.php +++ b/src/Forms/ClientForm.php @@ -68,7 +68,7 @@ public function validateRedirectUri(Form $form): void $this->validateByMatchingRegex( $redirectUris, self::REGEX_URI, - 'Invalid URI: ' + 'Invalid URI: ', ); } @@ -81,7 +81,7 @@ public function validateAllowedOrigin(Form $form): void $this->validateByMatchingRegex( $allowedOrigins, self::REGEX_ALLOWED_ORIGIN_URL, - 'Invalid allowed origin: ' + 'Invalid allowed origin: ', ); } @@ -94,7 +94,7 @@ public function validatePostLogoutRedirectUri(Form $form): void $this->validateByMatchingRegex( $postLogoutRedirectUris, self::REGEX_URI, - 'Invalid post-logout redirect URI: ' + 'Invalid post-logout redirect URI: ', ); } @@ -106,7 +106,7 @@ public function validateBackChannelLogoutUri(Form $form): void $this->validateByMatchingRegex( [$bclUri], self::REGEX_HTTP_URI, - 'Invalid back-channel logout URI: ' + 'Invalid back-channel logout URI: ', ); } } @@ -118,7 +118,7 @@ public function validateBackChannelLogoutUri(Form $form): void protected function validateByMatchingRegex( array $values, string $regex, - string $messagePrefix = 'Invalid value: ' + string $messagePrefix = 'Invalid value: ', ): void { foreach ($values as $value) { if (!preg_match($regex, $value)) { @@ -151,8 +151,8 @@ public function getValues(string|object|bool|null $returnType = null, ?array $co $values['scopes'] = array_unique( array_merge( $scopes, - ['openid'] - ) + ['openid'], + ), ); return $values; @@ -250,7 +250,7 @@ protected function getScopes(): array { return array_map( fn(array $item): mixed => $item['description'], - $this->moduleConfig->getOpenIDScopes() + $this->moduleConfig->getOpenIDScopes(), ); } @@ -261,7 +261,7 @@ protected function convertTextToArrayWithLinesAsValues(string $text): array { return array_filter( preg_split("/[\t\r\n]+/", $text), - fn(string $line): bool => !empty(trim($line)) + fn(string $line): bool => !empty(trim($line)), ); } } diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php index 8b5cee2f..85ccd475 100644 --- a/src/ModuleConfig.php +++ b/src/ModuleConfig.php @@ -91,10 +91,10 @@ 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. + array $overrides = [], // Primarily used for easy (unit) testing overrides. ) { $this->moduleConfig = Configuration::loadFromArray( - array_merge(Configuration::getConfig($fileName)->toArray(), $overrides) + array_merge(Configuration::getConfig($fileName)->toArray(), $overrides), ); $this->sspConfig = Configuration::getInstance(); @@ -170,16 +170,16 @@ function (array $scope, string $name): void { if (in_array($name, array_keys(self::$standardClaims), true)) { throw new ConfigurationError( 'Can not overwrite protected scope: ' . $name, - self::DEFAULT_FILE_NAME + self::DEFAULT_FILE_NAME, ); } if (!array_key_exists('description', $scope)) { throw new ConfigurationError( 'Scope [' . $name . '] description not defined', - self::DEFAULT_FILE_NAME + self::DEFAULT_FILE_NAME, ); } - } + }, ); $acrValuesSupported = $this->getAcrValuesSupported(); @@ -234,7 +234,7 @@ public function getSigner(): Signer /** @psalm-var class-string $signerClassname */ $signerClassname = $this->config()->getOptionalString( self::OPTION_TOKEN_SIGNER, - Sha256::class + Sha256::class, ); $class = new ReflectionClass($signerClassname); @@ -256,7 +256,7 @@ public function getCertPath(): string { $certName = $this->config()->getOptionalString( self::OPTION_PKI_CERTIFICATE_FILENAME, - self::DEFAULT_PKI_CERTIFICATE_FILENAME + self::DEFAULT_PKI_CERTIFICATE_FILENAME, ); return (new Config())->getCertPath($certName); } @@ -269,7 +269,7 @@ public function getPrivateKeyPath(): string { $keyName = $this->config()->getOptionalString( self::OPTION_PKI_PRIVATE_KEY_FILENAME, - self::DEFAULT_PKI_PRIVATE_KEY_FILENAME + self::DEFAULT_PKI_PRIVATE_KEY_FILENAME, ); // TODO mivanci move to bridge classes to SSP utils return (new Config())->getCertPath($keyName); diff --git a/src/Repositories/AccessTokenRepository.php b/src/Repositories/AccessTokenRepository.php index 2aef8f19..7070d585 100644 --- a/src/Repositories/AccessTokenRepository.php +++ b/src/Repositories/AccessTokenRepository.php @@ -48,7 +48,7 @@ public function getNewToken( array $scopes, $userIdentifier = null, string $authCodeId = null, - array $requestedClaims = null + array $requestedClaims = null, ): AccessTokenEntityInterface { if (!is_null($userIdentifier)) { $userIdentifier = (string)$userIdentifier; @@ -73,12 +73,12 @@ public function persistNewAccessToken(OAuth2AccessTokenEntityInterface $accessTo $stmt = sprintf( "INSERT INTO %s (id, scopes, expires_at, user_id, client_id, is_revoked, auth_code_id, requested_claims) " . "VALUES (:id, :scopes, :expires_at, :user_id, :client_id, :is_revoked, :auth_code_id, :requested_claims)", - $this->getTableName() + $this->getTableName(), ); $this->database->write( $stmt, - $accessTokenEntity->getState() + $accessTokenEntity->getState(), ); } @@ -93,7 +93,7 @@ public function findById(string $tokenId): ?AccessTokenEntity "SELECT * FROM {$this->getTableName()} WHERE id = :id", [ 'id' => $tokenId, - ] + ], ); if (empty($rows = $stmt->fetchAll())) { @@ -158,7 +158,7 @@ public function removeExpired(): void )", [ 'now' => TimestampGenerator::utc()->format('Y-m-d H:i:s'), - ] + ], ); } @@ -171,12 +171,12 @@ private function update(AccessTokenEntity $accessTokenEntity): void "UPDATE %s SET scopes = :scopes, expires_at = :expires_at, user_id = :user_id, " . "client_id = :client_id, is_revoked = :is_revoked, auth_code_id = :auth_code_id, " . "requested_claims = :requested_claims WHERE id = :id", - $this->getTableName() + $this->getTableName(), ); $this->database->write( $stmt, - $accessTokenEntity->getState() + $accessTokenEntity->getState(), ); } } diff --git a/src/Repositories/AllowedOriginRepository.php b/src/Repositories/AllowedOriginRepository.php index 961dd513..4dcc2628 100644 --- a/src/Repositories/AllowedOriginRepository.php +++ b/src/Repositories/AllowedOriginRepository.php @@ -49,7 +49,7 @@ public function delete(string $clientId): void { $this->database->write( "DELETE FROM {$this->getTableName()} WHERE client_id = :client_id", - ['client_id' => $clientId] + ['client_id' => $clientId], ); } @@ -57,7 +57,7 @@ public function get(string $clientId): array { $stmt = $this->database->read( "SELECT origin FROM {$this->getTableName()} WHERE client_id = :client_id", - ['client_id' => $clientId] + ['client_id' => $clientId], ); return $stmt->fetchAll(PDO::FETCH_COLUMN, 0); @@ -67,7 +67,7 @@ public function has(string $origin): bool { $stmt = $this->database->read( "SELECT origin FROM {$this->getTableName()} WHERE origin = :origin LIMIT 1", - ['origin' => $origin] + ['origin' => $origin], ); return (bool) count($stmt->fetchAll(PDO::FETCH_COLUMN, 0)); diff --git a/src/Repositories/AuthCodeRepository.php b/src/Repositories/AuthCodeRepository.php index 0fc71c2b..7e286db6 100644 --- a/src/Repositories/AuthCodeRepository.php +++ b/src/Repositories/AuthCodeRepository.php @@ -56,12 +56,12 @@ public function persistNewAuthCode(OAuth2AuthCodeEntityInterface $authCodeEntity $stmt = sprintf( "INSERT INTO %s (id, scopes, expires_at, user_id, client_id, is_revoked, redirect_uri, nonce) " . "VALUES (:id, :scopes, :expires_at, :user_id, :client_id, :is_revoked, :redirect_uri, :nonce)", - $this->getTableName() + $this->getTableName(), ); $this->database->write( $stmt, - $authCodeEntity->getState() + $authCodeEntity->getState(), ); } @@ -75,7 +75,7 @@ public function findById(string $codeId): ?AuthCodeEntityInterface "SELECT * FROM {$this->getTableName()} WHERE id = :id", [ 'id' => $codeId, - ] + ], ); if (empty($rows = $stmt->fetchAll())) { @@ -132,7 +132,7 @@ public function removeExpired(): void "DELETE FROM {$this->getTableName()} WHERE expires_at < :now", [ 'now' => TimestampGenerator::utc()->format('Y-m-d H:i:s'), - ] + ], ); } @@ -143,8 +143,8 @@ private function update(AuthCodeEntity $authCodeEntity): void { $stmt = sprintf( <<getTableName() + $this->getTableName(), ); $this->database->write( $stmt, - $authCodeEntity->getState() + $authCodeEntity->getState(), ); } } diff --git a/src/Repositories/ClientRepository.php b/src/Repositories/ClientRepository.php index 5ce1ce59..d76ae5ef 100644 --- a/src/Repositories/ClientRepository.php +++ b/src/Repositories/ClientRepository.php @@ -88,7 +88,7 @@ public function findById(string $clientIdentifier, ?string $owner = null): ?Clie [ 'id' => $clientIdentifier, ], - $owner + $owner, ); $stmt = $this->database->read($query, $params); @@ -132,11 +132,11 @@ public function findAll(?string $owner = null): array [$query, $params] = $this->addOwnerWhereClause( "SELECT * FROM {$this->getTableName()}", [], - $owner + $owner, ); $stmt = $this->database->read( "$query ORDER BY name ASC", - $params + $params, ); $clients = []; @@ -169,11 +169,11 @@ public function findPaginated(int $page = 1, string $query = '', ?string $owner [$sqlQuery, $params] = $this->addOwnerWhereClause( "SELECT * FROM {$this->getTableName()} WHERE name LIKE :name", ['name' => '%' . $query . '%'], - $owner + $owner, ); $stmt = $this->database->read( $sqlQuery . " ORDER BY name ASC LIMIT $limit OFFSET $offset", - $params + $params, ); $clients = array_map(fn(array $state) => ClientEntity::fromState($state), $stmt->fetchAll()); @@ -181,7 +181,7 @@ public function findPaginated(int $page = 1, string $query = '', ?string $owner return [ 'numPages' => $numPages, 'currentPage' => $page, - 'items' => $clients + 'items' => $clients, ]; } @@ -219,11 +219,11 @@ public function add(ClientEntityInterface $client): void ) EOS , - $this->getTableName() + $this->getTableName(), ); $this->database->write( $stmt, - $client->getState() + $client->getState(), ); } @@ -238,7 +238,7 @@ public function delete(ClientEntityInterface $client, ?string $owner = null): vo [ 'id' => $client->getIdentifier(), ], - $owner + $owner, ); $this->database->write($sqlQuery, $params); } @@ -247,7 +247,7 @@ public function update(ClientEntityInterface $client, ?string $owner = null): vo { $stmt = sprintf( <<getTableName() + $this->getTableName(), ); /** @@ -272,11 +272,11 @@ public function update(ClientEntityInterface $client, ?string $owner = null): vo [$sqlQuery, $params] = $this->addOwnerWhereClause( $stmt, $client->getState(), - $owner + $owner, ); $this->database->write( $sqlQuery, - $params + $params, ); } @@ -289,11 +289,11 @@ private function count(string $query, ?string $owner): int [$sqlQuery, $params] = $this->addOwnerWhereClause( "SELECT COUNT(id) FROM {$this->getTableName()} WHERE name LIKE :name", ['name' => '%' . $query . '%'], - $owner + $owner, ); $stmt = $this->database->read( $sqlQuery, - $params + $params, ); $stmt->execute(); diff --git a/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php b/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php index 76bff94e..d51f145e 100644 --- a/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php +++ b/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php @@ -31,6 +31,6 @@ public function getNewToken( array $scopes, $userIdentifier = null, string $authCodeId = null, - array $requestedClaims = null + array $requestedClaims = null, ): AccessTokenEntityInterface; } diff --git a/src/Repositories/RefreshTokenRepository.php b/src/Repositories/RefreshTokenRepository.php index da26e701..3a7adde4 100644 --- a/src/Repositories/RefreshTokenRepository.php +++ b/src/Repositories/RefreshTokenRepository.php @@ -62,12 +62,12 @@ public function persistNewRefreshToken(OAuth2RefreshTokenEntityInterface $refres $stmt = sprintf( "INSERT INTO %s (id, expires_at, access_token_id, is_revoked, auth_code_id) " . "VALUES (:id, :expires_at, :access_token_id, :is_revoked, :auth_code_id)", - $this->getTableName() + $this->getTableName(), ); $this->database->write( $stmt, - $refreshTokenEntity->getState() + $refreshTokenEntity->getState(), ); } @@ -82,7 +82,7 @@ public function findById(string $tokenId): ?RefreshTokenEntityInterface "SELECT * FROM {$this->getTableName()} WHERE id = :id", [ 'id' => $tokenId, - ] + ], ); if (empty($rows = $stmt->fetchAll())) { @@ -138,7 +138,7 @@ public function removeExpired(): void "DELETE FROM {$this->getTableName()} WHERE expires_at < :now", [ 'now' => TimestampGenerator::utc()->format('Y-m-d H:i:s'), - ] + ], ); } @@ -147,12 +147,12 @@ private function update(RefreshTokenEntityInterface $refreshTokenEntity): void $stmt = sprintf( "UPDATE %s SET expires_at = :expires_at, access_token_id = :access_token_id, is_revoked = :is_revoked, " . "auth_code_id = :auth_code_id WHERE id = :id", - $this->getTableName() + $this->getTableName(), ); $this->database->write( $stmt, - $refreshTokenEntity->getState() + $refreshTokenEntity->getState(), ); } } diff --git a/src/Repositories/ScopeRepository.php b/src/Repositories/ScopeRepository.php index b3a2defc..8c71f5a3 100644 --- a/src/Repositories/ScopeRepository.php +++ b/src/Repositories/ScopeRepository.php @@ -57,7 +57,7 @@ public function getScopeEntityByIdentifier($identifier): ScopeEntity|ScopeEntity $identifier, $description, $icon, - $claims + $claims, ); } @@ -68,7 +68,7 @@ public function finalizeScopes( array $scopes, $grantType, OAuth2ClientEntityInterface $clientEntity, - $userIdentifier = null + $userIdentifier = null, ): array { if (!$clientEntity instanceof ClientEntity) { return []; @@ -76,7 +76,7 @@ public function finalizeScopes( return array_filter( $scopes, - fn(ScopeEntityInterface $scope) => in_array($scope->getIdentifier(), $clientEntity->getScopes(), true) + fn(ScopeEntityInterface $scope) => in_array($scope->getIdentifier(), $clientEntity->getScopes(), true), ); } } diff --git a/src/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php b/src/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php index 4e2e1aa4..75f44093 100644 --- a/src/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php +++ b/src/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php @@ -10,12 +10,12 @@ public function revokeByAuthCodeId(string $authCodeId): void { $stmt = sprintf( "UPDATE %s SET is_revoked = 1 WHERE auth_code_id = :auth_code_id", - $this->getTableName() + $this->getTableName(), ); $this->database->write( $stmt, - ['auth_code_id' => $authCodeId] + ['auth_code_id' => $authCodeId], ); } } diff --git a/src/Repositories/UserRepository.php b/src/Repositories/UserRepository.php index cbc24aa9..f90b68b6 100644 --- a/src/Repositories/UserRepository.php +++ b/src/Repositories/UserRepository.php @@ -45,7 +45,7 @@ public function getUserEntityByIdentifier(string $identifier): ?UserEntity "SELECT * FROM {$this->getTableName()} WHERE id = :id", [ 'id' => $identifier, - ] + ], ); if (empty($rows = $stmt->fetchAll())) { @@ -69,7 +69,7 @@ public function getUserEntityByUserCredentials( $username, $password, $grantType, - OAuth2ClientEntityInterface $clientEntity + OAuth2ClientEntityInterface $clientEntity, ): ?UserEntityInterface { throw new Exception('Not supported'); } @@ -78,11 +78,11 @@ public function add(UserEntity $userEntity): void { $stmt = sprintf( "INSERT INTO %s (id, claims, updated_at, created_at) VALUES (:id, :claims, :updated_at, :created_at)", - $this->getTableName() + $this->getTableName(), ); $this->database->write( $stmt, - $userEntity->getState() + $userEntity->getState(), ); } @@ -92,7 +92,7 @@ public function delete(UserEntity $user): void "DELETE FROM {$this->getTableName()} WHERE id = :id", [ 'id' => $user->getIdentifier(), - ] + ], ); } @@ -100,12 +100,12 @@ public function update(UserEntity $user): void { $stmt = sprintf( "UPDATE %s SET claims = :claims, updated_at = :updated_at, created_at = :created_at WHERE id = :id", - $this->getTableName() + $this->getTableName(), ); $this->database->write( $stmt, - $user->getState() + $user->getState(), ); } } diff --git a/src/Server/Associations/RelyingPartyAssociation.php b/src/Server/Associations/RelyingPartyAssociation.php index cedca608..cebbffff 100644 --- a/src/Server/Associations/RelyingPartyAssociation.php +++ b/src/Server/Associations/RelyingPartyAssociation.php @@ -15,7 +15,7 @@ public function __construct( /** * Registered back-channel logout URI for the client. */ - protected ?string $backChannelLogoutUri = null + protected ?string $backChannelLogoutUri = null, ) { } diff --git a/src/Server/AuthorizationServer.php b/src/Server/AuthorizationServer.php index e1481ced..448ee6c2 100644 --- a/src/Server/AuthorizationServer.php +++ b/src/Server/AuthorizationServer.php @@ -50,7 +50,7 @@ public function __construct( CryptKey|string $privateKey, Key|string $encryptionKey, ResponseTypeInterface $responseType = null, - RequestRulesManager $requestRulesManager = null + RequestRulesManager $requestRulesManager = null, ) { parent::__construct( $clientRepository, @@ -58,7 +58,7 @@ public function __construct( $scopeRepository, $privateKey, $encryptionKey, - $responseType + $responseType, ); $this->clientRepository = $clientRepository; @@ -78,7 +78,7 @@ public function validateAuthorizationRequest(ServerRequestInterface $request): O $rulesToExecute = [ StateRule::class, ClientIdRule::class, - RedirectUriRule::class + RedirectUriRule::class, ]; try { diff --git a/src/Server/Exceptions/OidcServerException.php b/src/Server/Exceptions/OidcServerException.php index 732a26b0..90f5e53b 100644 --- a/src/Server/Exceptions/OidcServerException.php +++ b/src/Server/Exceptions/OidcServerException.php @@ -60,7 +60,7 @@ public function __construct( string $hint = null, string $redirectUri = null, Throwable $previous = null, - string $state = null + string $state = null, ) { parent::__construct($message, $code, $errorType, $httpStatusCode, $hint, $redirectUri, $previous); @@ -95,7 +95,7 @@ public function __construct( public static function unsupportedResponseType( string $redirectUri = null, string $state = null, - bool $useFragment = false + bool $useFragment = false, ): OidcServerException { $errorMessage = 'The response type is not supported by the authorization server.'; $hint = 'Check that all required parameters have been provided'; @@ -118,7 +118,7 @@ public static function invalidScope( $scope, $redirectUri = null, string $state = null, - bool $useFragment = false + bool $useFragment = false, ): OidcServerException { // OAuthServerException correctly implements this error, however, it misses state parameter. $e = parent::invalidScope($scope, $redirectUri); @@ -145,7 +145,7 @@ public static function invalidRequest( Throwable $previous = null, string $redirectUri = null, string $state = null, - bool $useFragment = false + bool $useFragment = false, ): OidcServerException { $e = parent::invalidRequest($parameter, $hint, $previous); // OAuthServerException misses the ability to set redirectUri for invalid requests, as well as state. @@ -169,7 +169,7 @@ public static function accessDenied( $redirectUri = null, Throwable $previous = null, string $state = null, - bool $useFragment = false + bool $useFragment = false, ): OidcServerException { $e = parent::accessDenied($hint, $redirectUri, $previous); $e->setState($state); @@ -194,7 +194,7 @@ public static function loginRequired( string $redirectUri = null, Throwable $previous = null, string $state = null, - bool $useFragment = false + bool $useFragment = false, ): OidcServerException { $errorMessage = "End-User is not already authenticated."; @@ -220,7 +220,7 @@ public static function requestNotSupported( string $redirectUri = null, Throwable $previous = null, string $state = null, - bool $useFragment = false + bool $useFragment = false, ): OidcServerException { $errorMessage = "Request object not supported."; @@ -323,7 +323,7 @@ public function setState(string $state = null): void public function generateHttpResponse( ResponseInterface $response, $useFragment = false, - $jsonOptions = 0 + $jsonOptions = 0, ): ResponseInterface { /** @var array $headers */ $headers = $this->getHttpHeaders(); diff --git a/src/Server/Grants/AuthCodeGrant.php b/src/Server/Grants/AuthCodeGrant.php index 9af558d1..b76791f9 100644 --- a/src/Server/Grants/AuthCodeGrant.php +++ b/src/Server/Grants/AuthCodeGrant.php @@ -152,7 +152,7 @@ public function __construct( AccessTokenRepositoryInterface $accessTokenRepository, RefreshTokenRepositoryInterface $refreshTokenRepository, DateInterval $authCodeTTL, - protected RequestRulesManager $requestRulesManager + protected RequestRulesManager $requestRulesManager, ) { parent::__construct($authCodeRepository, $refreshTokenRepository, $authCodeTTL); @@ -180,12 +180,12 @@ protected function shouldCheckPkce(OAuth2ClientEntityInterface $client): bool * Check if the authorization request is OIDC candidate (can respond with ID token). */ public function isOidcCandidate( - OAuth2AuthorizationRequest $authorizationRequest + OAuth2AuthorizationRequest $authorizationRequest, ): bool { // Check if the scopes contain 'oidc' scope return (bool) Arr::find( $authorizationRequest->getScopes(), - fn(ScopeEntityInterface $scope) => $scope->getIdentifier() === 'openid' + fn(ScopeEntityInterface $scope) => $scope->getIdentifier() === 'openid', ); } @@ -195,7 +195,7 @@ public function isOidcCandidate( * @throws JsonException */ public function completeAuthorizationRequest( - OAuth2AuthorizationRequest $authorizationRequest + OAuth2AuthorizationRequest $authorizationRequest, ): ResponseTypeInterface { if ($authorizationRequest instanceof AuthorizationRequest) { return $this->completeOidcAuthorizationRequest($authorizationRequest); @@ -212,7 +212,7 @@ public function completeAuthorizationRequest( * @throws JsonException */ public function completeOidcAuthorizationRequest( - AuthorizationRequest $authorizationRequest + AuthorizationRequest $authorizationRequest, ): RedirectResponse { $user = $authorizationRequest->getUser(); if ($user instanceof UserEntity === false) { @@ -229,7 +229,7 @@ public function completeOidcAuthorizationRequest( 'The user denied the request', $finalRedirectUri, null, - $authorizationRequest->getState() + $authorizationRequest->getState(), ); } @@ -240,7 +240,7 @@ public function completeOidcAuthorizationRequest( $user->getIdentifier(), $finalRedirectUri, $authorizationRequest->getScopes(), - $authorizationRequest->getNonce() + $authorizationRequest->getNonce(), ); $payload = [ @@ -268,8 +268,8 @@ public function completeOidcAuthorizationRequest( [ 'code' => $this->encrypt($jsonPayload), 'state' => $authorizationRequest->getState(), - ] - ) + ], + ), ); return $response; @@ -286,7 +286,7 @@ protected function issueOidcAuthCode( string $userIdentifier, string $redirectUri, array $scopes = [], - string $nonce = null + string $nonce = null, ): AuthCodeEntityInterface { $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; @@ -365,7 +365,7 @@ protected function getClientRedirectUri(OAuth2AuthorizationRequest $authorizatio public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, - DateInterval $accessTokenTTL + DateInterval $accessTokenTTL, ): ResponseTypeInterface { [$clientId] = $this->getClientCredentials($request); @@ -395,7 +395,7 @@ public function respondToAccessTokenRequest( $this->validateScopes($authCodePayload->scopes), $this->getIdentifier(), $client, - $authCodePayload->user_id + $authCodePayload->user_id, ); } catch (LogicException $e) { throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code', $e); @@ -407,7 +407,7 @@ public function respondToAccessTokenRequest( if ($this->shouldCheckPkce($client) && empty($authCodePayload->code_challenge) && $codeVerifier !== null) { throw OAuthServerException::invalidRequest( 'code_challenge', - 'code_verifier received when no code_challenge is present' + 'code_verifier received when no code_challenge is present', ); } @@ -422,7 +422,7 @@ public function respondToAccessTokenRequest( if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeVerifier) !== 1) { throw OAuthServerException::invalidRequest( 'code_verifier', - 'Code Verifier must follow the specifications of RFC-7636.' + 'Code Verifier must follow the specifications of RFC-7636.', ); } @@ -433,7 +433,7 @@ public function respondToAccessTokenRequest( if ( $codeChallengeVerifier->verifyCodeChallenge( $codeVerifier, - $authCodePayload->code_challenge + $authCodePayload->code_challenge, ) === false ) { throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.'); @@ -442,8 +442,8 @@ public function respondToAccessTokenRequest( throw OAuthServerException::serverError( sprintf( 'Unsupported code challenge method `%s`', - ($authCodePayload->code_challenge_method ?? '') - ) + ($authCodePayload->code_challenge_method ?? ''), + ), ); } } @@ -461,7 +461,7 @@ public function respondToAccessTokenRequest( $authCodePayload->user_id, $scopes, $authCodePayload->auth_code_id, - $claims + $claims, ); $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); $responseType->setAccessToken($accessToken); @@ -531,7 +531,7 @@ public function respondToAccessTokenRequest( protected function validateAuthorizationCode( object $authCodePayload, OAuth2ClientEntityInterface $client, - ServerRequestInterface $request + ServerRequestInterface $request, ): void { /** * @noinspection PhpUndefinedClassInspection @@ -578,7 +578,7 @@ protected function validateAuthorizationCode( if ($authCodePayload->redirect_uri !== $redirectUri) { throw OAuthServerException::invalidRequest( 'redirect_uri', - 'Invalid redirect URI or not the same as in authorization request' + 'Invalid redirect URI or not the same as in authorization request', ); } } @@ -589,7 +589,7 @@ protected function validateAuthorizationCode( */ public function validateAuthorizationRequestWithCheckerResultBag( ServerRequestInterface $request, - ResultBagInterface $resultBag + ResultBagInterface $resultBag, ): OAuth2AuthorizationRequest { $rulesToExecute = [ RequestParameterRule::class, @@ -689,7 +689,7 @@ public function validateAuthorizationRequestWithCheckerResultBag( */ protected function issueRefreshToken( OAuth2AccessTokenEntityInterface $accessToken, - string $authCodeId = null + string $authCodeId = null, ): ?RefreshTokenEntityInterface { if (! is_a($this->refreshTokenRepository, RefreshTokenRepositoryInterface::class)) { throw OidcServerException::serverError('Unexpected refresh token repository entity type.'); diff --git a/src/Server/Grants/ImplicitGrant.php b/src/Server/Grants/ImplicitGrant.php index f19f6a24..e773a7e6 100644 --- a/src/Server/Grants/ImplicitGrant.php +++ b/src/Server/Grants/ImplicitGrant.php @@ -51,7 +51,7 @@ public function __construct( DateInterval $accessTokenTTL, AccessTokenRepositoryInterface $accessTokenRepository, string $queryDelimiter = '#', - RequestRulesManager $requestRulesManager = null + RequestRulesManager $requestRulesManager = null, ) { parent::__construct($accessTokenTTL, $queryDelimiter, $requestRulesManager); $this->accessTokenRepository = $accessTokenRepository; @@ -86,7 +86,7 @@ public function canRespondToAuthorizationRequest(ServerRequestInterface $request * @throws UniqueTokenIdentifierConstraintViolationException */ public function completeAuthorizationRequest( - OAuth2AuthorizationRequest $authorizationRequest + OAuth2AuthorizationRequest $authorizationRequest, ): ResponseTypeInterface { if ($authorizationRequest instanceof AuthorizationRequest) { return $this->completeOidcAuthorizationRequest($authorizationRequest); @@ -101,7 +101,7 @@ public function completeAuthorizationRequest( */ public function validateAuthorizationRequestWithCheckerResultBag( ServerRequestInterface $request, - ResultBagInterface $resultBag + ResultBagInterface $resultBag, ): OAuth2AuthorizationRequest { $oAuth2AuthorizationRequest = parent::validateAuthorizationRequestWithCheckerResultBag($request, $resultBag); @@ -115,7 +115,7 @@ public function validateAuthorizationRequestWithCheckerResultBag( AddClaimsToIdTokenRule::class, RequiredNonceRule::class, RequestedClaimsRule::class, - AcrValuesRule::class + AcrValuesRule::class, ]; $this->requestRulesManager->predefineResultBag($resultBag); @@ -177,7 +177,7 @@ private function completeOidcAuthorizationRequest(AuthorizationRequest $authoriz $redirectUrl, null, $authorizationRequest->getState(), - $this->shouldUseFragment() + $this->shouldUseFragment(), ); } @@ -186,7 +186,7 @@ private function completeOidcAuthorizationRequest(AuthorizationRequest $authoriz $authorizationRequest->getScopes(), $this->getIdentifier(), $authorizationRequest->getClient(), - $user->getIdentifier() + $user->getIdentifier(), ); $responseParams = [ @@ -199,7 +199,7 @@ private function completeOidcAuthorizationRequest(AuthorizationRequest $authoriz $user->getIdentifier(), $finalizedScopes, null, - $authorizationRequest->getClaims() + $authorizationRequest->getClaims(), ); if ($accessToken instanceof EntityStringRepresentationInterface === false) { @@ -226,7 +226,7 @@ private function completeOidcAuthorizationRequest(AuthorizationRequest $authoriz $authorizationRequest->getNonce(), $authorizationRequest->getAuthTime(), $authorizationRequest->getAcr(), - $authorizationRequest->getSessionId() + $authorizationRequest->getSessionId(), ); $responseParams['id_token'] = $idToken->toString(); @@ -237,8 +237,8 @@ private function completeOidcAuthorizationRequest(AuthorizationRequest $authoriz $this->makeRedirectUri( $redirectUrl, $responseParams, - $this->queryDelimiter - ) + $this->queryDelimiter, + ), ); return $response; diff --git a/src/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php b/src/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php index 4a3d5259..77a678a2 100644 --- a/src/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php +++ b/src/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php @@ -17,6 +17,6 @@ interface AuthorizationValidatableWithCheckerResultBagInterface */ public function validateAuthorizationRequestWithCheckerResultBag( ServerRequestInterface $request, - ResultBagInterface $resultBag + ResultBagInterface $resultBag, ): OAuth2AuthorizationRequest; } diff --git a/src/Server/Grants/OAuth2ImplicitGrant.php b/src/Server/Grants/OAuth2ImplicitGrant.php index 0d89b9c3..5e040ade 100644 --- a/src/Server/Grants/OAuth2ImplicitGrant.php +++ b/src/Server/Grants/OAuth2ImplicitGrant.php @@ -94,7 +94,7 @@ class OAuth2ImplicitGrant extends ImplicitGrant implements AuthorizationValidata public function __construct( DateInterval $accessTokenTTL, string $queryDelimiter = '#', - RequestRulesManager $requestRulesManager = null + RequestRulesManager $requestRulesManager = null, ) { parent::__construct($accessTokenTTL, $queryDelimiter); @@ -113,7 +113,7 @@ public function __construct( */ public function validateAuthorizationRequestWithCheckerResultBag( ServerRequestInterface $request, - ResultBagInterface $resultBag + ResultBagInterface $resultBag, ): OAuth2AuthorizationRequest { $rulesToExecute = [ ScopeRule::class, diff --git a/src/Server/Grants/RefreshTokenGrant.php b/src/Server/Grants/RefreshTokenGrant.php index 686a9194..3f2dc717 100644 --- a/src/Server/Grants/RefreshTokenGrant.php +++ b/src/Server/Grants/RefreshTokenGrant.php @@ -98,7 +98,7 @@ protected function validateOldRefreshToken(ServerRequestInterface $request, $cli if ( $this->refreshTokenRepository->isRefreshTokenRevoked( - (string)$refreshTokenData['refresh_token_id'] + (string)$refreshTokenData['refresh_token_id'], ) === true ) { throw OidcServerException::invalidRefreshToken('Refresh token has been revoked'); diff --git a/src/Server/Grants/Traits/IssueAccessTokenTrait.php b/src/Server/Grants/Traits/IssueAccessTokenTrait.php index 2a4f11b9..9abfaaf5 100644 --- a/src/Server/Grants/Traits/IssueAccessTokenTrait.php +++ b/src/Server/Grants/Traits/IssueAccessTokenTrait.php @@ -49,14 +49,14 @@ protected function issueAccessToken( $userIdentifier = null, array $scopes = [], string $authCodeId = null, - array $requestedClaims = null + array $requestedClaims = null, ): AccessTokenEntityInterface { $maxGenerationAttempts = AbstractGrant::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; /** Since we are using our own repository interface, check for proper type. */ if (! is_a($this->accessTokenRepository, AccessTokenRepositoryInterface::class)) { throw OidcServerException::serverError( - 'Access token repository does not implement ' . AccessTokenRepositoryInterface::class + 'Access token repository does not implement ' . AccessTokenRepositoryInterface::class, ); } @@ -65,7 +65,7 @@ protected function issueAccessToken( $scopes, $userIdentifier, $authCodeId, - $requestedClaims + $requestedClaims, ); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add($accessTokenTTL)); $accessToken->setPrivateKey($this->privateKey); diff --git a/src/Server/LogoutHandlers/BackChannelLogoutHandler.php b/src/Server/LogoutHandlers/BackChannelLogoutHandler.php index 1b0dda42..d119740b 100644 --- a/src/Server/LogoutHandlers/BackChannelLogoutHandler.php +++ b/src/Server/LogoutHandlers/BackChannelLogoutHandler.php @@ -21,7 +21,7 @@ class BackChannelLogoutHandler { public function __construct( protected LogoutTokenBuilder $logoutTokenBuilder = new LogoutTokenBuilder(), - protected LoggerService $loggerService = new LoggerService() + protected LoggerService $loggerService = new LoggerService(), ) { } @@ -75,7 +75,7 @@ protected function logoutRequestsGenerator(array $relyingPartyAssociations): Gen $index++; $query = http_build_query( - ['logout_token' => $this->logoutTokenBuilder->forRelyingPartyAssociation($association)] + ['logout_token' => $this->logoutTokenBuilder->forRelyingPartyAssociation($association)], ); /** @psalm-suppress PossiblyNullArgument We have checked for nulls... */ @@ -83,7 +83,7 @@ protected function logoutRequestsGenerator(array $relyingPartyAssociations): Gen 'POST', $association->getBackChannelLogoutUri(), ['Content-Type' => 'application/x-www-form-urlencoded'], - $query + $query, ); } } diff --git a/src/Server/RequestTypes/AuthorizationRequest.php b/src/Server/RequestTypes/AuthorizationRequest.php index 0b99bc3a..c4c664e7 100644 --- a/src/Server/RequestTypes/AuthorizationRequest.php +++ b/src/Server/RequestTypes/AuthorizationRequest.php @@ -45,7 +45,7 @@ class AuthorizationRequest extends OAuth2AuthorizationRequest protected ?string $sessionId = null; public static function fromOAuth2AuthorizationRequest( - OAuth2AuthorizationRequest $oAuth2authorizationRequest + OAuth2AuthorizationRequest $oAuth2authorizationRequest, ): AuthorizationRequest { $authorizationRequest = new self(); diff --git a/src/Server/RequestTypes/LogoutRequest.php b/src/Server/RequestTypes/LogoutRequest.php index c3170497..ab825bd1 100644 --- a/src/Server/RequestTypes/LogoutRequest.php +++ b/src/Server/RequestTypes/LogoutRequest.php @@ -31,7 +31,7 @@ public function __construct( * End-User's preferred languages and scripts for the user interface, represented as a space-separated list of * BCP47 [RFC5646] language tag values, ordered by preference. */ - protected ?string $uiLocales = null + protected ?string $uiLocales = null, ) { } diff --git a/src/Server/ResponseTypes/IdTokenResponse.php b/src/Server/ResponseTypes/IdTokenResponse.php index e6aa60c2..022e703b 100644 --- a/src/Server/ResponseTypes/IdTokenResponse.php +++ b/src/Server/ResponseTypes/IdTokenResponse.php @@ -73,7 +73,7 @@ class IdTokenResponse extends BearerTokenResponse implements public function __construct( private readonly IdentityProviderInterface $identityProvider, protected IdTokenBuilder $idTokenBuilder, - CryptKey $privateKey + CryptKey $privateKey, ) { $this->privateKey = $privateKey; } @@ -113,7 +113,7 @@ protected function getExtraParams(AccessTokenEntityInterface $accessToken): arra $this->getNonce(), $this->getAuthTime(), $this->getAcr(), - $this->getSessionId() + $this->getSessionId(), ); return [ diff --git a/src/Server/Validators/BearerTokenValidator.php b/src/Server/Validators/BearerTokenValidator.php index ede61f87..c8ff8d86 100644 --- a/src/Server/Validators/BearerTokenValidator.php +++ b/src/Server/Validators/BearerTokenValidator.php @@ -54,7 +54,7 @@ class BearerTokenValidator extends OAuth2BearerTokenValidator public function __construct( AccessTokenRepositoryInterface $accessTokenRepository, CryptKey $publicKey, - DateInterval $jwtValidAtDateLeeway = null + DateInterval $jwtValidAtDateLeeway = null, ) { parent::__construct($accessTokenRepository, $jwtValidAtDateLeeway); $this->accessTokenRepository = $accessTokenRepository; @@ -82,7 +82,7 @@ protected function initJwtConfiguration(): void { $this->jwtConfiguration = Configuration::forSymmetricSigner( new Sha256(), - InMemory::plainText('empty', 'empty') + InMemory::plainText('empty', 'empty'), ); /** @psalm-suppress ArgumentTypeCoercion */ @@ -90,8 +90,8 @@ protected function initJwtConfiguration(): void new StrictValidAt(new SystemClock(new DateTimeZone(date_default_timezone_get()))), new SignedWith( new Sha256(), - InMemory::plainText($this->publicKey->getKeyContents(), $this->publicKey->getPassPhrase() ?? '') - ) + InMemory::plainText($this->publicKey->getKeyContents(), $this->publicKey->getPassPhrase() ?? ''), + ), ); } diff --git a/src/Services/AuthContextService.php b/src/Services/AuthContextService.php index b230b2a5..cf3a75db 100644 --- a/src/Services/AuthContextService.php +++ b/src/Services/AuthContextService.php @@ -28,7 +28,7 @@ class AuthContextService */ public function __construct( private readonly ModuleConfig $moduleConfig, - private readonly AuthSimpleFactory $authSimpleFactory + private readonly AuthSimpleFactory $authSimpleFactory, ) { } diff --git a/src/Services/AuthProcService.php b/src/Services/AuthProcService.php index a9e68cb8..b3cd8e17 100644 --- a/src/Services/AuthProcService.php +++ b/src/Services/AuthProcService.php @@ -23,7 +23,7 @@ class AuthProcService * @see \SimpleSAML\Auth\ProcessingChain for original implementation */ public function __construct( - private readonly ModuleConfig $moduleConfig + private readonly ModuleConfig $moduleConfig, ) { $this->loadFilters(); } @@ -71,12 +71,12 @@ private function parseFilterList(array $filterSrc): array $className = Module::resolveClass( $filterConfig['class'], 'Auth\Process', - '\\' . ProcessingFilter::class + '\\' . ProcessingFilter::class, ); if (!is_a($className, ProcessingFilter::class, true)) { throw new Exception( - 'Authentication processing filter class configuration is not ProcessingFilter instance.' + 'Authentication processing filter class configuration is not ProcessingFilter instance.', ); } diff --git a/src/Services/AuthenticationService.php b/src/Services/AuthenticationService.php index 1fcb006b..5e579117 100644 --- a/src/Services/AuthenticationService.php +++ b/src/Services/AuthenticationService.php @@ -53,7 +53,7 @@ public function __construct( private readonly OpMetadataService $opMetadataService, private readonly SessionService $sessionService, private readonly ClaimTranslatorExtractor $claimTranslatorExtractor, - ModuleConfig $moduleConfig + ModuleConfig $moduleConfig, ) { $this->clientRepository = $clientRepository; $this->userIdAttr = $moduleConfig->getUserIdentifierAttribute(); @@ -69,7 +69,7 @@ public function __construct( public function getAuthenticateUser( ServerRequestInterface $request, array $loginParams = [], - bool $forceAuthn = false + bool $forceAuthn = false, ): UserEntity { $oidcClient = $this->getClientFromRequest($request); $authSimple = $this->authSimpleFactory->build($oidcClient); @@ -87,7 +87,7 @@ public function getAuthenticateUser( $this->sessionService->registerLogoutHandler( $this->authSourceId, LogoutController::class, - 'logoutHandler' + 'logoutHandler', ); } else { $this->sessionService->setIsCookieBasedAuthn(true); @@ -105,7 +105,7 @@ public function getAuthenticateUser( if (!array_key_exists($this->userIdAttr, $claims) || !is_array($claims[$this->userIdAttr])) { $attr = implode(', ', array_keys($claims)); throw new Error\Exception( - 'Attribute `useridattr` doesn\'t exists in claims. Available attributes are: ' . $attr + 'Attribute `useridattr` doesn\'t exists in claims. Available attributes are: ' . $attr, ); } @@ -128,7 +128,7 @@ public function getAuthenticateUser( private function prepareStateArray( Simple $authSimple, ClientEntityInterface $client, - ServerRequestInterface $request + ServerRequestInterface $request, ): array { $state = $authSimple->getAuthDataArray(); @@ -137,7 +137,7 @@ private function prepareStateArray( 'RelyingPartyMetadata' => array_filter( $client->toArray(), fn(/** @param array-key $key */ $key) => $key !== 'secret', - ARRAY_FILTER_USE_KEY + ARRAY_FILTER_USE_KEY, ), 'AuthorizationRequestParameters' => array_filter( $request->getQueryParams(), @@ -145,7 +145,7 @@ function (/** @param array-key $key */ $key) { $authzParams = ['response_type', 'client_id', 'redirect_uri', 'scope', 'code_challenge_method']; return in_array($key, $authzParams); }, - ARRAY_FILTER_USE_KEY + ARRAY_FILTER_USE_KEY, ), ]; @@ -187,8 +187,8 @@ protected function addRelyingPartyAssociation(ClientEntityInterface $oidcClient, $oidcClient->getIdentifier(), (string)($claims['sub'] ?? $user->getIdentifier()), $this->getSessionId(), - $oidcClient->getBackChannelLogoutUri() - ) + $oidcClient->getBackChannelLogoutUri(), + ), ); } } diff --git a/src/Services/Container.php b/src/Services/Container.php index 215642a0..88ef2622 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -159,7 +159,7 @@ public function __construct() $this->services[MetaDataStorageHandler::class] = $metadataStorageHandler; $claimTranslatorExtractor = (new ClaimTranslatorExtractorFactory( - $moduleConfig + $moduleConfig, ))->build(); $this->services[ClaimTranslatorExtractor::class] = $claimTranslatorExtractor; @@ -205,13 +205,13 @@ public function __construct() $this->services[RequestRulesManager::class] = $requestRuleManager; $accessTokenDuration = new DateInterval( - $moduleConfig->config()->getString(ModuleConfig::OPTION_TOKEN_ACCESS_TOKEN_TTL) + $moduleConfig->config()->getString(ModuleConfig::OPTION_TOKEN_ACCESS_TOKEN_TTL), ); $authCodeDuration = new DateInterval( - $moduleConfig->config()->getString(ModuleConfig::OPTION_TOKEN_AUTHORIZATION_CODE_TTL) + $moduleConfig->config()->getString(ModuleConfig::OPTION_TOKEN_AUTHORIZATION_CODE_TTL), ); $refreshTokenDuration = new DateInterval( - $moduleConfig->config()->getString(ModuleConfig::OPTION_TOKEN_REFRESH_TOKEN_TTL) + $moduleConfig->config()->getString(ModuleConfig::OPTION_TOKEN_REFRESH_TOKEN_TTL), ); $publicKey = $cryptKeyFactory->buildPublicKey(); $privateKey = $cryptKeyFactory->buildPrivateKey(); @@ -236,7 +236,7 @@ public function __construct() $userRepository, $this->services[IdTokenBuilder::class], $privateKey, - $encryptionKey + $encryptionKey, ); $this->services[IdTokenResponse::class] = $idTokenResponseFactory->build(); @@ -257,13 +257,13 @@ public function __construct() $this->services[IdTokenBuilder::class], $accessTokenDuration, $requestRuleManager, - $accessTokenRepository + $accessTokenRepository, ); $this->services[ImplicitGrant::class] = $implicitGrantFactory->build(); $refreshTokenGrantFactory = new RefreshTokenGrantFactory( $refreshTokenRepository, - $refreshTokenDuration + $refreshTokenDuration, ); $this->services[RefreshTokenGrant::class] = $refreshTokenGrantFactory->build(); @@ -279,7 +279,7 @@ public function __construct() $this->services[IdTokenResponse::class], $requestRuleManager, $privateKey, - $encryptionKey + $encryptionKey, ); $this->services[AuthorizationServer::class] = $authorizationServerFactory->build(); @@ -289,7 +289,7 @@ public function __construct() $resourceServerFactory = new ResourceServerFactory( $accessTokenRepository, $publicKey, - $bearerTokenValidator + $bearerTokenValidator, ); $this->services[ResourceServer::class] = $resourceServerFactory->build(); } diff --git a/src/Services/DatabaseLegacyOAuth2Import.php b/src/Services/DatabaseLegacyOAuth2Import.php index 260478b6..3501f590 100644 --- a/src/Services/DatabaseLegacyOAuth2Import.php +++ b/src/Services/DatabaseLegacyOAuth2Import.php @@ -57,7 +57,7 @@ public function import(): void $client['scopes'], true, false, - $client['auth_source'] + $client['auth_source'], )); } } diff --git a/src/Services/DatabaseMigration.php b/src/Services/DatabaseMigration.php index f3d5da2a..990f7b2b 100644 --- a/src/Services/DatabaseMigration.php +++ b/src/Services/DatabaseMigration.php @@ -53,7 +53,7 @@ public function versions(): array { $versionsTablename = $this->versionsTableName(); $this->database->write( - "CREATE TABLE IF NOT EXISTS $versionsTablename (version VARCHAR(191) PRIMARY KEY NOT NULL)" + "CREATE TABLE IF NOT EXISTS $versionsTablename (version VARCHAR(191) PRIMARY KEY NOT NULL)", ); return $this->database @@ -141,7 +141,7 @@ private function version20180305180300(): void created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) EOT - ); + ,); $clientTableName = $this->database->applyPrefix(ClientRepository::TABLE_NAME); $this->database->write(<<< EOT @@ -155,7 +155,7 @@ private function version20180305180300(): void scopes TEXT NOT NULL ) EOT - ); + ,); $accessTokenTableName = $this->database->applyPrefix(AccessTokenRepository::TABLE_NAME); $fkAccessTokenUser = $this->generateIdentifierName([$accessTokenTableName, 'user_id'], 'fk'); @@ -165,22 +165,22 @@ private function version20180305180300(): void id VARCHAR(191) PRIMARY KEY NOT NULL, scopes TEXT, expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - user_id VARCHAR(191) NOT NULL, + user_id VARCHAR(191) NOT NULL, client_id VARCHAR(191) NOT NULL, is_revoked BOOLEAN NOT NULL DEFAULT false, - CONSTRAINT $fkAccessTokenUser FOREIGN KEY (user_id) - REFERENCES $userTablename (id) ON DELETE CASCADE, - CONSTRAINT $fkAccessTokenClient FOREIGN KEY (client_id) - REFERENCES $clientTableName (id) ON DELETE CASCADE + CONSTRAINT $fkAccessTokenUser FOREIGN KEY (user_id) + REFERENCES $userTablename (id) ON DELETE CASCADE, + CONSTRAINT $fkAccessTokenClient FOREIGN KEY (client_id) + REFERENCES $clientTableName (id) ON DELETE CASCADE ) EOT - ); + ,); $refreshTokenTableName = $this->database->applyPrefix(RefreshTokenRepository::TABLE_NAME); $fkRefreshTokenAccessToken = $this->generateIdentifierName([$refreshTokenTableName, 'access_token_id'], 'fk'); $this->database->write(<<< EOT CREATE TABLE $refreshTokenTableName ( - id VARCHAR(191) PRIMARY KEY NOT NULL, + id VARCHAR(191) PRIMARY KEY NOT NULL, expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, access_token_id VARCHAR(191) NOT NULL, is_revoked BOOLEAN NOT NULL DEFAULT false, @@ -188,7 +188,7 @@ private function version20180305180300(): void REFERENCES $accessTokenTableName (id) ON DELETE CASCADE ) EOT - ); + ,); $authCodeTableName = $this->database->applyPrefix(AuthCodeRepository::TABLE_NAME); $fkAuthCodeUser = $this->generateIdentifierName([$authCodeTableName, 'user_id'], 'fk'); @@ -198,17 +198,17 @@ private function version20180305180300(): void id VARCHAR(191) PRIMARY KEY NOT NULL, scopes TEXT, expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - user_id VARCHAR(191) NOT NULL, + user_id VARCHAR(191) NOT NULL, client_id VARCHAR(191) NOT NULL, is_revoked BOOLEAN NOT NULL DEFAULT false, redirect_uri TEXT NOT NULL, CONSTRAINT $fkAuthCodeUser FOREIGN KEY (user_id) - REFERENCES $userTablename (id) ON DELETE CASCADE, + REFERENCES $userTablename (id) ON DELETE CASCADE, CONSTRAINT $fkAuthCodeClient FOREIGN KEY (client_id) - REFERENCES $clientTableName (id) ON DELETE CASCADE + REFERENCES $clientTableName (id) ON DELETE CASCADE ) EOT - ); + ,); } /** @@ -221,7 +221,7 @@ private function version20180425203400(): void ALTER TABLE ${clientTableName} ADD is_enabled BOOLEAN NOT NULL DEFAULT true EOT - ); + ,); } private function version20200517071100(): void @@ -229,9 +229,9 @@ private function version20200517071100(): void $clientTableName = $this->database->applyPrefix(ClientRepository::TABLE_NAME); $this->database->write(<<< EOT ALTER TABLE ${clientTableName} - ADD is_confidential BOOLEAN NOT NULL DEFAULT false + ADD is_confidential BOOLEAN NOT NULL DEFAULT false EOT - ); + ,); } private function version20200901163000(): void @@ -239,9 +239,9 @@ private function version20200901163000(): void $clientTableName = $this->database->applyPrefix(AuthCodeRepository::TABLE_NAME); $this->database->write(<<< EOT ALTER TABLE ${clientTableName} - ADD nonce TEXT NULL + ADD nonce TEXT NULL EOT - ); + ,); } private function version20210902113500(): void @@ -249,9 +249,9 @@ private function version20210902113500(): void $clientTableName = $this->database->applyPrefix(ClientRepository::TABLE_NAME); $this->database->write(<<< EOT ALTER TABLE ${clientTableName} - ADD owner VARCHAR(191) NULL + ADD owner VARCHAR(191) NULL EOT - ); + ,); } /** @@ -262,16 +262,16 @@ protected function version20210714113000(): void $tableName = $this->database->applyPrefix(AccessTokenRepository::TABLE_NAME); $this->database->write(<<< EOT ALTER TABLE ${tableName} - ADD auth_code_id VARCHAR(191) NULL + ADD auth_code_id VARCHAR(191) NULL EOT - ); + ,); $tableName = $this->database->applyPrefix(RefreshTokenRepository::TABLE_NAME); $this->database->write(<<< EOT ALTER TABLE ${tableName} - ADD auth_code_id VARCHAR(191) NULL + ADD auth_code_id VARCHAR(191) NULL EOT - ); + ,); } /** @@ -282,9 +282,9 @@ protected function version20210823141300(): void $tableName = $this->database->applyPrefix(AccessTokenRepository::TABLE_NAME); $this->database->write(<<< EOT ALTER TABLE ${tableName} - ADD requested_claims TEXT NULL + ADD requested_claims TEXT NULL EOT - ); + ,); } /** @@ -306,7 +306,7 @@ protected function version20210827111300(): void REFERENCES $clientTableName (id) ON DELETE CASCADE ) EOT - ); + ,); } /** @@ -317,9 +317,9 @@ protected function version20210908143500(): void $clientTableName = $this->database->applyPrefix(ClientRepository::TABLE_NAME); $this->database->write(<<< EOT ALTER TABLE ${clientTableName} - ADD post_logout_redirect_uri TEXT NULL + ADD post_logout_redirect_uri TEXT NULL EOT - ); + ,); } /** @@ -330,9 +330,9 @@ protected function version20210916153400(): void $clientTableName = $this->database->applyPrefix(ClientRepository::TABLE_NAME); $this->database->write(<<< EOT ALTER TABLE ${clientTableName} - ADD backchannel_logout_uri TEXT NULL + ADD backchannel_logout_uri TEXT NULL EOT - ); + ,); } /** @@ -341,14 +341,13 @@ protected function version20210916153400(): void protected function version20210916173400(): void { $tableName = $this->database->applyPrefix(LogoutTicketStoreDb::TABLE_NAME); - $this->database->write( - <<< EOT + $this->database->write(<<< EOT CREATE TABLE $tableName ( sid VARCHAR(191) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) EOT - ); + ,); } /** diff --git a/src/Services/IdTokenBuilder.php b/src/Services/IdTokenBuilder.php index ad03b38c..1ae4e7ae 100644 --- a/src/Services/IdTokenBuilder.php +++ b/src/Services/IdTokenBuilder.php @@ -23,7 +23,7 @@ class IdTokenBuilder { public function __construct( private readonly JsonWebTokenBuilderService $jsonWebTokenBuilderService, - private readonly ClaimTranslatorExtractor $claimExtractor + private readonly ClaimTranslatorExtractor $claimExtractor, ) { } @@ -38,7 +38,7 @@ public function build( ?string $nonce, ?int $authTime, ?string $acr, - ?string $sessionId + ?string $sessionId, ): UnencryptedToken { if (false === is_a($userEntity, ClaimSetInterface::class)) { throw new RuntimeException('UserEntity must implement ClaimSetInterface'); @@ -60,8 +60,8 @@ public function build( 'at_hash', $this->generateAccessTokenHash( $accessToken, - $this->jsonWebTokenBuilderService->getSigner()->algorithmId() - ) + $this->jsonWebTokenBuilderService->getSigner()->algorithmId(), + ), ); } @@ -78,7 +78,7 @@ public function build( $requestedClaims = $accessToken->getRequestedClaims(); $additionalClaims = $this->claimExtractor->extractAdditionalIdTokenClaims( $requestedClaims, - $userEntity->getClaims() + $userEntity->getClaims(), ); $claims = array_merge($additionalClaims, $claims); @@ -134,7 +134,7 @@ public function build( */ protected function getBuilder( AccessTokenEntityInterface $accessToken, - UserEntityInterface $userEntity + UserEntityInterface $userEntity, ): Builder { return $this->jsonWebTokenBuilderService ->getDefaultJwtTokenBuilder() @@ -154,7 +154,7 @@ protected function generateAccessTokenHash(AccessTokenEntityInterface $accessTok $jwsAlgorithmBitLength = (int) substr($jwsAlgorithm, 2); - if (! in_array($jwsAlgorithmBitLength, $validBitLengths, true)) { + if (!in_array($jwsAlgorithmBitLength, $validBitLengths, true)) { throw new RuntimeException(sprintf('JWS algorithm not supported (%s)', $jwsAlgorithm)); } @@ -176,11 +176,11 @@ protected function generateAccessTokenHash(AccessTokenEntityInterface $accessTok hash( $hashAlgorithm, $accessTokenString, - true + true, ), 0, - $hashByteLength - ) + $hashByteLength, + ), ); } } diff --git a/src/Services/JsonWebTokenBuilderService.php b/src/Services/JsonWebTokenBuilderService.php index fff9e447..8d9c7ad7 100644 --- a/src/Services/JsonWebTokenBuilderService.php +++ b/src/Services/JsonWebTokenBuilderService.php @@ -27,15 +27,15 @@ class JsonWebTokenBuilderService * @throws Exception */ public function __construct( - protected ModuleConfig $moduleConfig = new ModuleConfig() + protected ModuleConfig $moduleConfig = new ModuleConfig(), ) { $this->jwtConfig = Configuration::forAsymmetricSigner( $this->moduleConfig->getSigner(), InMemory::file( $this->moduleConfig->getPrivateKeyPath(), - $this->moduleConfig->getPrivateKeyPassPhrase() ?? '' + $this->moduleConfig->getPrivateKeyPassPhrase() ?? '', ), - InMemory::plainText('empty', 'empty') + InMemory::plainText('empty', 'empty'), ); } @@ -61,7 +61,7 @@ public function getSignedJwtTokenFromBuilder(Builder $builder): UnencryptedToken return $builder->withHeader('kid', $kid) ->getToken( $this->jwtConfig->signer(), - $this->jwtConfig->signingKey() + $this->jwtConfig->signingKey(), ); } diff --git a/src/Services/LogoutTokenBuilder.php b/src/Services/LogoutTokenBuilder.php index 23c9746a..bcf3c112 100644 --- a/src/Services/LogoutTokenBuilder.php +++ b/src/Services/LogoutTokenBuilder.php @@ -12,7 +12,7 @@ class LogoutTokenBuilder { public function __construct( - protected JsonWebTokenBuilderService $jsonWebTokenBuilderService = new JsonWebTokenBuilderService() + protected JsonWebTokenBuilderService $jsonWebTokenBuilderService = new JsonWebTokenBuilderService(), ) { } diff --git a/src/Services/OpMetadataService.php b/src/Services/OpMetadataService.php index d9d393b1..7756e000 100644 --- a/src/Services/OpMetadataService.php +++ b/src/Services/OpMetadataService.php @@ -21,7 +21,7 @@ class OpMetadataService * @throws Exception */ public function __construct( - private readonly ModuleConfig $moduleConfig + private readonly ModuleConfig $moduleConfig, ) { $this->initMetadata(); } diff --git a/src/Services/RoutingService.php b/src/Services/RoutingService.php index 571c8936..b3e73412 100644 --- a/src/Services/RoutingService.php +++ b/src/Services/RoutingService.php @@ -47,7 +47,7 @@ class RoutingService public static function call( string $controllerClassname, bool $authenticated = true, - bool $jsonResponse = false + bool $jsonResponse = false, ): void { if ($authenticated) { (new Auth())->requireAdmin(); diff --git a/src/Services/SessionService.php b/src/Services/SessionService.php index 6cdb52ec..00204021 100644 --- a/src/Services/SessionService.php +++ b/src/Services/SessionService.php @@ -44,7 +44,7 @@ public function setIsCookieBasedAuthn(bool $isCookieBasedAuthn): void self::SESSION_DATA_TYPE, self::SESSION_DATA_ID_IS_COOKIE_BASED_AUTHN, $isCookieBasedAuthn, - Session::DATA_TIMEOUT_SESSION_END + Session::DATA_TIMEOUT_SESSION_END, ); } @@ -53,7 +53,7 @@ public function getIsCookieBasedAuthn(): ?bool /** @var ?bool $isCookieBasedAuthn */ $isCookieBasedAuthn = $this->session->getData( self::SESSION_DATA_TYPE, - self::SESSION_DATA_ID_IS_COOKIE_BASED_AUTHN + self::SESSION_DATA_ID_IS_COOKIE_BASED_AUTHN, ); if (is_bool($isCookieBasedAuthn)) { @@ -84,7 +84,7 @@ public function addRelyingPartyAssociation(RelyingPartyAssociationInterface $ass self::SESSION_DATA_TYPE, self::SESSION_DATA_ID_RP_ASSOCIATIONS, $associations, - Session::DATA_TIMEOUT_SESSION_END + Session::DATA_TIMEOUT_SESSION_END, ); } @@ -107,7 +107,7 @@ public static function getRelyingPartyAssociationsForSession(Session $session): // Make sure we only have RelyingPartyAssociations here... return array_filter( $relyingPartyAssociations, - fn($value) => $value instanceof RelyingPartyAssociationInterface + fn($value) => $value instanceof RelyingPartyAssociationInterface, ); } @@ -128,7 +128,7 @@ public static function clearRelyingPartyAssociationsForSession(Session $session) self::SESSION_DATA_TYPE, self::SESSION_DATA_ID_RP_ASSOCIATIONS, [], - Session::DATA_TIMEOUT_SESSION_END + Session::DATA_TIMEOUT_SESSION_END, ); } @@ -141,7 +141,7 @@ public function setIsAuthnPerformedInPreviousRequest(bool $isAuthnPerformedInPre self::SESSION_DATA_TYPE, self::SESSION_DATA_ID_IS_AUTHN_PERFORMED_IN_PREVIOUS_REQUEST, $isAuthnPerformedInPreviousRequest, - Session::DATA_TIMEOUT_SESSION_END + Session::DATA_TIMEOUT_SESSION_END, ); } @@ -171,7 +171,7 @@ public function setIsOidcInitiatedLogout(bool $isOidcInitiatedLogout): void self::SESSION_DATA_TYPE, self::SESSION_DATA_ID_IS_OIDC_INITIATED_LOGOUT, $isOidcInitiatedLogout, - Session::DATA_TIMEOUT_SESSION_END + Session::DATA_TIMEOUT_SESSION_END, ); } diff --git a/src/Stores/Session/LogoutTicketStoreDb.php b/src/Stores/Session/LogoutTicketStoreDb.php index e81c8395..f0b39a2d 100644 --- a/src/Stores/Session/LogoutTicketStoreDb.php +++ b/src/Stores/Session/LogoutTicketStoreDb.php @@ -31,12 +31,12 @@ public function add(string $sid): void { $stmt = sprintf( "INSERT INTO %s (sid) VALUES (:sid)", - $this->getTableName() + $this->getTableName(), ); $this->database->write( $stmt, - ['sid' => $sid] + ['sid' => $sid], ); } @@ -49,7 +49,7 @@ public function delete(string $sid): void $this->database->write( "DELETE FROM {$this->getTableName()} WHERE sid = :sid", - ['sid' => $sid,] + ['sid' => $sid], ); } @@ -101,7 +101,7 @@ protected function deleteExpired(): void 'expiration' => TimestampGenerator::utc() ->sub(new DateInterval('PT' . $this->ttl . 'S')) ->format('Y-m-d H:i:s'), - ] + ], ); } /** diff --git a/src/Utils/Checker/Interfaces/RequestRuleInterface.php b/src/Utils/Checker/Interfaces/RequestRuleInterface.php index a7c00365..fb33abfc 100644 --- a/src/Utils/Checker/Interfaces/RequestRuleInterface.php +++ b/src/Utils/Checker/Interfaces/RequestRuleInterface.php @@ -32,6 +32,6 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface; } diff --git a/src/Utils/Checker/RequestRulesManager.php b/src/Utils/Checker/RequestRulesManager.php index 4b5b3f8d..c57ca15a 100644 --- a/src/Utils/Checker/RequestRulesManager.php +++ b/src/Utils/Checker/RequestRulesManager.php @@ -58,7 +58,7 @@ public function check( ServerRequestInterface $request, array $ruleKeysToExecute, bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ResultBagInterface { foreach ($ruleKeysToExecute as $ruleKey) { if (! isset($this->rules[$ruleKey])) { @@ -71,7 +71,7 @@ public function check( $this->loggerService, $this->data, $useFragmentInHttpErrorResponses, - $allowedServerRequestMethods + $allowedServerRequestMethods, ); if ($result !== null) { diff --git a/src/Utils/Checker/Rules/AbstractRule.php b/src/Utils/Checker/Rules/AbstractRule.php index bd7727cf..b417ec9b 100644 --- a/src/Utils/Checker/Rules/AbstractRule.php +++ b/src/Utils/Checker/Rules/AbstractRule.php @@ -25,7 +25,7 @@ protected function getParamFromRequestBasedOnAllowedMethods( string $paramKey, ServerRequestInterface $request, LoggerService $loggerService, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?string { // Make sure the case is compatible... $allowedServerRequestMethods = array_map('strtoupper', $allowedServerRequestMethods); @@ -36,8 +36,8 @@ protected function getParamFromRequestBasedOnAllowedMethods( sprintf( 'Method %s not allowed for intended request. Allowed methods were %s.', $requestMethod, - implode(', ', $allowedServerRequestMethods) - ) + implode(', ', $allowedServerRequestMethods), + ), ); return null; } @@ -58,8 +58,8 @@ protected function getParamFromRequestBasedOnAllowedMethods( $loggerService->warning( sprintf( 'Request method %s is not supported.', - $requestMethod - ) + $requestMethod, + ), ); } diff --git a/src/Utils/Checker/Rules/AcrValuesRule.php b/src/Utils/Checker/Rules/AcrValuesRule.php index ffc6ed1d..b70823f6 100644 --- a/src/Utils/Checker/Rules/AcrValuesRule.php +++ b/src/Utils/Checker/Rules/AcrValuesRule.php @@ -21,7 +21,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { $acrValues = [ 'essential' => false, @@ -49,7 +49,7 @@ public function checkRule( $acrValues['values'] = array_merge( isset($requestedAcrClaim['value']) ? [$requestedAcrClaim['value']] : [], isset($requestedAcrClaim['values']) && is_array($requestedAcrClaim['values']) ? - $requestedAcrClaim['values'] : [] + $requestedAcrClaim['values'] : [], ); } diff --git a/src/Utils/Checker/Rules/AddClaimsToIdTokenRule.php b/src/Utils/Checker/Rules/AddClaimsToIdTokenRule.php index 60bff15b..c6e01cf5 100644 --- a/src/Utils/Checker/Rules/AddClaimsToIdTokenRule.php +++ b/src/Utils/Checker/Rules/AddClaimsToIdTokenRule.php @@ -23,7 +23,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { /** @var string $responseType */ $responseType = $currentResultBag->getOrFail(ResponseTypeRule::class)->getValue(); diff --git a/src/Utils/Checker/Rules/ClientIdRule.php b/src/Utils/Checker/Rules/ClientIdRule.php index 88d475a9..45ba58ee 100644 --- a/src/Utils/Checker/Rules/ClientIdRule.php +++ b/src/Utils/Checker/Rules/ClientIdRule.php @@ -28,7 +28,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { /** @var ?string $clientId */ $clientId = $request->getQueryParams()['client_id'] ?? $request->getServerParams()['PHP_AUTH_USER'] ?? null; diff --git a/src/Utils/Checker/Rules/CodeChallengeMethodRule.php b/src/Utils/Checker/Rules/CodeChallengeMethodRule.php index 31e975a0..04528d6c 100644 --- a/src/Utils/Checker/Rules/CodeChallengeMethodRule.php +++ b/src/Utils/Checker/Rules/CodeChallengeMethodRule.php @@ -29,7 +29,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { /** @var string $redirectUri */ $redirectUri = $currentResultBag->getOrFail(RedirectUriRule::class)->getValue(); @@ -45,12 +45,12 @@ public function checkRule( 'code_challenge_method', 'Code challenge method must be one of ' . implode(', ', array_map( fn($method) => '`' . $method . '`', - array_keys($codeChallengeVerifiers) + array_keys($codeChallengeVerifiers), )), null, $redirectUri, $state, - $useFragmentInHttpErrorResponses + $useFragmentInHttpErrorResponses, ); } diff --git a/src/Utils/Checker/Rules/CodeChallengeRule.php b/src/Utils/Checker/Rules/CodeChallengeRule.php index 369695a1..1c6bb8e2 100644 --- a/src/Utils/Checker/Rules/CodeChallengeRule.php +++ b/src/Utils/Checker/Rules/CodeChallengeRule.php @@ -24,7 +24,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { /** @var string $redirectUri */ $redirectUri = $currentResultBag->getOrFail(RedirectUriRule::class)->getValue(); @@ -41,7 +41,7 @@ public function checkRule( null, $redirectUri, $state, - $useFragmentInHttpErrorResponses + $useFragmentInHttpErrorResponses, ); } @@ -54,7 +54,7 @@ public function checkRule( null, $redirectUri, $state, - $useFragmentInHttpErrorResponses + $useFragmentInHttpErrorResponses, ); } diff --git a/src/Utils/Checker/Rules/IdTokenHintRule.php b/src/Utils/Checker/Rules/IdTokenHintRule.php index e0f65ab2..0faaf43b 100644 --- a/src/Utils/Checker/Rules/IdTokenHintRule.php +++ b/src/Utils/Checker/Rules/IdTokenHintRule.php @@ -23,7 +23,7 @@ class IdTokenHintRule extends AbstractRule { public function __construct( protected ModuleConfig $moduleConfig, - protected CryptKeyFactory $cryptKeyFactory + protected CryptKeyFactory $cryptKeyFactory, ) { } @@ -37,7 +37,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { /** @var string|null $state */ $state = $currentResultBag->getOrFail(StateRule::class)->getValue(); @@ -46,7 +46,7 @@ public function checkRule( 'id_token_hint', $request, $loggerService, - $allowedServerRequestMethods + $allowedServerRequestMethods, ); if ($idTokenHintParam === null) { @@ -59,7 +59,7 @@ public function checkRule( $jwtConfig = Configuration::forAsymmetricSigner( $this->moduleConfig->getSigner(), InMemory::plainText($privateKey->getKeyContents(), $privateKey->getPassPhrase() ?? ''), - InMemory::plainText($publicKey->getKeyContents()) + InMemory::plainText($publicKey->getKeyContents()), ); try { @@ -75,8 +75,8 @@ public function checkRule( // signed with previous key... new SignedWith( $this->moduleConfig->getSigner(), - InMemory::plainText($publicKey->getKeyContents()) - ) + InMemory::plainText($publicKey->getKeyContents()), + ), ); } catch (Throwable $exception) { throw OidcServerException::invalidRequest('id_token_hint', $exception->getMessage(), null, null, $state); diff --git a/src/Utils/Checker/Rules/MaxAgeRule.php b/src/Utils/Checker/Rules/MaxAgeRule.php index e1ef9769..8461fb91 100644 --- a/src/Utils/Checker/Rules/MaxAgeRule.php +++ b/src/Utils/Checker/Rules/MaxAgeRule.php @@ -21,7 +21,7 @@ class MaxAgeRule extends AbstractRule { public function __construct( private readonly AuthSimpleFactory $authSimpleFactory, - private readonly AuthenticationService $authenticationService + private readonly AuthenticationService $authenticationService, ) { } @@ -39,7 +39,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { $queryParams = $request->getQueryParams(); @@ -64,7 +64,7 @@ public function checkRule( null, $redirectUri, $state, - $useFragmentInHttpErrorResponses + $useFragmentInHttpErrorResponses, ); } diff --git a/src/Utils/Checker/Rules/PostLogoutRedirectUriRule.php b/src/Utils/Checker/Rules/PostLogoutRedirectUriRule.php index 8b8fc5eb..0f55d7c7 100644 --- a/src/Utils/Checker/Rules/PostLogoutRedirectUriRule.php +++ b/src/Utils/Checker/Rules/PostLogoutRedirectUriRule.php @@ -30,7 +30,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { /** @var string|null $state */ $state = $currentResultBag->getOrFail(StateRule::class)->getValue(); @@ -42,7 +42,7 @@ public function checkRule( 'post_logout_redirect_uri', $request, $loggerService, - $allowedServerRequestMethods + $allowedServerRequestMethods, ); $result = new Result($this->getKey(), $postLogoutRedirectUri); @@ -82,7 +82,7 @@ public function checkRule( 'post_logout_redirect_uri not registered', null, null, - $state + $state, ); } diff --git a/src/Utils/Checker/Rules/PromptRule.php b/src/Utils/Checker/Rules/PromptRule.php index feb57b87..fae74633 100644 --- a/src/Utils/Checker/Rules/PromptRule.php +++ b/src/Utils/Checker/Rules/PromptRule.php @@ -21,7 +21,7 @@ class PromptRule extends AbstractRule { public function __construct( private readonly AuthSimpleFactory $authSimpleFactory, - private readonly AuthenticationService $authenticationService + private readonly AuthenticationService $authenticationService, ) { } @@ -40,7 +40,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { /** @var ClientEntityInterface $client */ $client = $currentResultBag->getOrFail(ClientIdRule::class)->getValue(); @@ -67,7 +67,7 @@ public function checkRule( $redirectUri, null, $state, - $useFragmentInHttpErrorResponses + $useFragmentInHttpErrorResponses, ); } diff --git a/src/Utils/Checker/Rules/RedirectUriRule.php b/src/Utils/Checker/Rules/RedirectUriRule.php index bfe5e926..2d1ba31d 100644 --- a/src/Utils/Checker/Rules/RedirectUriRule.php +++ b/src/Utils/Checker/Rules/RedirectUriRule.php @@ -26,7 +26,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { $client = $currentResultBag->getOrFail(ClientIdRule::class)->getValue(); if (! $client instanceof ClientEntityInterface) { diff --git a/src/Utils/Checker/Rules/RequestParameterRule.php b/src/Utils/Checker/Rules/RequestParameterRule.php index 8debca13..4bd25160 100644 --- a/src/Utils/Checker/Rules/RequestParameterRule.php +++ b/src/Utils/Checker/Rules/RequestParameterRule.php @@ -23,7 +23,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { $queryParams = $request->getQueryParams(); if (!array_key_exists('request', $queryParams)) { @@ -40,7 +40,7 @@ public function checkRule( $redirectUri, null, $stateValue, - $useFragmentInHttpErrorResponses + $useFragmentInHttpErrorResponses, ); } } diff --git a/src/Utils/Checker/Rules/RequestedClaimsRule.php b/src/Utils/Checker/Rules/RequestedClaimsRule.php index 2f92616a..59dded79 100644 --- a/src/Utils/Checker/Rules/RequestedClaimsRule.php +++ b/src/Utils/Checker/Rules/RequestedClaimsRule.php @@ -29,7 +29,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { /** @var ?string $claimsParam */ $claimsParam = $request->getQueryParams()['claims'] ?? null; @@ -73,7 +73,7 @@ private function filterUnauthorizedClaims(array &$requestClaims, string $key, ar $requestClaims[$key] = array_filter( $requested, fn($key) => in_array($key, $authorized), - ARRAY_FILTER_USE_KEY + ARRAY_FILTER_USE_KEY, ); } } diff --git a/src/Utils/Checker/Rules/RequiredNonceRule.php b/src/Utils/Checker/Rules/RequiredNonceRule.php index 24f8f4ed..70b0d1d6 100644 --- a/src/Utils/Checker/Rules/RequiredNonceRule.php +++ b/src/Utils/Checker/Rules/RequiredNonceRule.php @@ -24,7 +24,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { /** @var string $redirectUri */ $redirectUri = $currentResultBag->getOrFail(RedirectUriRule::class)->getValue(); @@ -41,7 +41,7 @@ public function checkRule( null, $redirectUri, $state, - $useFragmentInHttpErrorResponses + $useFragmentInHttpErrorResponses, ); } diff --git a/src/Utils/Checker/Rules/RequiredOpenIdScopeRule.php b/src/Utils/Checker/Rules/RequiredOpenIdScopeRule.php index 46048c33..2bde9ada 100644 --- a/src/Utils/Checker/Rules/RequiredOpenIdScopeRule.php +++ b/src/Utils/Checker/Rules/RequiredOpenIdScopeRule.php @@ -25,7 +25,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { /** @var string $redirectUri */ $redirectUri = $currentResultBag->getOrFail(RedirectUriRule::class)->getValue(); @@ -36,7 +36,7 @@ public function checkRule( $isOpenIdScopePresent = (bool) array_filter( $validScopes, - fn($scopeEntity) => $scopeEntity->getIdentifier() === 'openid' + fn($scopeEntity) => $scopeEntity->getIdentifier() === 'openid', ); if (! $isOpenIdScopePresent) { @@ -46,7 +46,7 @@ public function checkRule( null, $redirectUri, $state, - $useFragmentInHttpErrorResponses + $useFragmentInHttpErrorResponses, ); } diff --git a/src/Utils/Checker/Rules/ResponseTypeRule.php b/src/Utils/Checker/Rules/ResponseTypeRule.php index bdaabe0a..2f620a5a 100644 --- a/src/Utils/Checker/Rules/ResponseTypeRule.php +++ b/src/Utils/Checker/Rules/ResponseTypeRule.php @@ -22,7 +22,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { $queryParams = $request->getQueryParams(); diff --git a/src/Utils/Checker/Rules/ScopeOfflineAccessRule.php b/src/Utils/Checker/Rules/ScopeOfflineAccessRule.php index 28a0e1d8..8ca17c33 100644 --- a/src/Utils/Checker/Rules/ScopeOfflineAccessRule.php +++ b/src/Utils/Checker/Rules/ScopeOfflineAccessRule.php @@ -27,7 +27,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { /** @var string $redirectUri */ $redirectUri = $currentResultBag->getOrFail(RedirectUriRule::class)->getValue(); @@ -51,7 +51,7 @@ public function checkRule( null, $redirectUri, $state, - $useFragmentInHttpErrorResponses + $useFragmentInHttpErrorResponses, ); } diff --git a/src/Utils/Checker/Rules/ScopeRule.php b/src/Utils/Checker/Rules/ScopeRule.php index f9960d72..42321499 100644 --- a/src/Utils/Checker/Rules/ScopeRule.php +++ b/src/Utils/Checker/Rules/ScopeRule.php @@ -30,7 +30,7 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { /** @var string $redirectUri */ $redirectUri = $currentResultBag->getOrFail(RedirectUriRule::class)->getValue(); @@ -43,7 +43,7 @@ public function checkRule( $scopes = $this->convertScopesQueryStringToArray( (string)($request->getQueryParams()['scope'] ?? $defaultScope), - $scopeDelimiterString + $scopeDelimiterString, ); $validScopes = []; diff --git a/src/Utils/Checker/Rules/StateRule.php b/src/Utils/Checker/Rules/StateRule.php index b2647c1d..c40b5d37 100644 --- a/src/Utils/Checker/Rules/StateRule.php +++ b/src/Utils/Checker/Rules/StateRule.php @@ -21,13 +21,13 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { $state = $this->getParamFromRequestBasedOnAllowedMethods( 'state', $request, $loggerService, - $allowedServerRequestMethods + $allowedServerRequestMethods, ); return new Result($this->getKey(), $state); diff --git a/src/Utils/Checker/Rules/UiLocalesRule.php b/src/Utils/Checker/Rules/UiLocalesRule.php index 978e4101..c744f045 100644 --- a/src/Utils/Checker/Rules/UiLocalesRule.php +++ b/src/Utils/Checker/Rules/UiLocalesRule.php @@ -21,13 +21,13 @@ public function checkRule( LoggerService $loggerService, array $data = [], bool $useFragmentInHttpErrorResponses = false, - array $allowedServerRequestMethods = ['GET'] + array $allowedServerRequestMethods = ['GET'], ): ?ResultInterface { return new Result($this->getKey(), $this->getParamFromRequestBasedOnAllowedMethods( 'ui_locales', $request, $loggerService, - $allowedServerRequestMethods + $allowedServerRequestMethods, )); } } diff --git a/src/Utils/ClaimTranslatorExtractor.php b/src/Utils/ClaimTranslatorExtractor.php index e52fc404..3ff32978 100644 --- a/src/Utils/ClaimTranslatorExtractor.php +++ b/src/Utils/ClaimTranslatorExtractor.php @@ -85,19 +85,19 @@ class ClaimTranslatorExtractor 'preferredLanguage', ], 'updated_at' => [ - 'type' => 'int' + 'type' => 'int', ], 'email' => [ 'mail', ], 'email_verified' => [ - 'type' => 'bool' + 'type' => 'bool', ], 'address' => [ 'type' => 'json', 'claims' => [ 'formatted' => ['postalAddress'], - ] + ], ], 'phone_number' => [ 'mobile', @@ -105,7 +105,7 @@ class ClaimTranslatorExtractor 'homePhone', ], 'phone_number_verified' => [ - 'type' => 'bool' + 'type' => 'bool', // Empty ], ]; @@ -135,7 +135,7 @@ public function __construct( string $userIdAttr, array $claimSets = [], array $translationTable = [], - protected array $allowedMultiValueClaims = [] + protected array $allowedMultiValueClaims = [], ) { // By default, add the userIdAttribute as one of the attribute for 'sub' claim. /** @psalm-suppress MixedArgument */ @@ -164,25 +164,25 @@ public function __construct( 'birthdate', 'zoneinfo', 'locale', - 'updated_at' - ]) + 'updated_at', + ]), ); $this->addClaimSet( new ClaimSetEntity('email', [ 'email', - 'email_verified' - ]) + 'email_verified', + ]), ); $this->addClaimSet( new ClaimSetEntity('address', [ - 'address' - ]) + 'address', + ]), ); $this->addClaimSet( new ClaimSetEntity('phone', [ 'phone_number', - 'phone_number_verified' - ]) + 'phone_number_verified', + ]), ); foreach ($claimSets as $claimSet) { @@ -199,7 +199,7 @@ public function addClaimSet(ClaimSetEntityInterface $claimSet): self if (in_array($scope, $this->protectedClaims) && isset($this->claimSets[$scope])) { throw OidcServerException::serverError( - sprintf("%s is a protected scope and is pre-defined by the OpenID Connect specification.", $scope) + sprintf("%s is a protected scope and is pre-defined by the OpenID Connect specification.", $scope), ); } @@ -310,7 +310,7 @@ public function extract(array $scopes, array $claims): array $data = array_filter( $claims, fn($key) => in_array($key, $intersected), - ARRAY_FILTER_USE_KEY + ARRAY_FILTER_USE_KEY, ); $claimData = array_merge($claimData, $data); @@ -348,7 +348,7 @@ private function extractAdditionalClaims(array $requestedClaims, array $claims): return array_filter( $translatedClaims, fn(/** @param array-key $key */ $key) => array_key_exists($key, $requestedClaims), - ARRAY_FILTER_USE_KEY + ARRAY_FILTER_USE_KEY, ); } } diff --git a/tests/src/Controller/AccessTokenControllerTest.php b/tests/src/Controller/AccessTokenControllerTest.php index d171add5..f207c300 100644 --- a/tests/src/Controller/AccessTokenControllerTest.php +++ b/tests/src/Controller/AccessTokenControllerTest.php @@ -37,7 +37,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( AccessTokenController::class, - new AccessTokenController($this->authorizationServerMock) + new AccessTokenController($this->authorizationServerMock), ); } @@ -54,7 +54,7 @@ public function testItRespondsToAccessTokenRequest(): void $this->assertSame( $this->responseMock, - (new AccessTokenController($this->authorizationServerMock))->__invoke($this->serverRequestMock) + (new AccessTokenController($this->authorizationServerMock))->__invoke($this->serverRequestMock), ); } } diff --git a/tests/src/Controller/AuthorizationControllerTest.php b/tests/src/Controller/AuthorizationControllerTest.php index bfb3580e..e007b042 100644 --- a/tests/src/Controller/AuthorizationControllerTest.php +++ b/tests/src/Controller/AuthorizationControllerTest.php @@ -81,7 +81,7 @@ public function testReturnsResponseWhenInvoked(): void $this->authenticationServiceStub, $this->authorizationServerStub, $this->moduleConfigStub, - $this->loggerServiceMock + $this->loggerServiceMock, ); $this->assertInstanceOf(ResponseInterface::class, $controller($this->serverRequestStub)); @@ -111,7 +111,7 @@ public function testValidateAcrThrowsIfAuthSourceIdNotSetInAuthorizationRequest( $this->authenticationServiceStub, $this->authorizationServerStub, $this->moduleConfigStub, - $this->loggerServiceMock + $this->loggerServiceMock, ))($this->serverRequestStub); } @@ -141,7 +141,7 @@ public function testValidateAcrThrowsIfCookieBasedAuthnNotSetInAuthorizationRequ $this->authenticationServiceStub, $this->authorizationServerStub, $this->moduleConfigStub, - $this->loggerServiceMock + $this->loggerServiceMock, ))($this->serverRequestStub); } @@ -180,7 +180,7 @@ public function testValidateAcrSetsForcedAcrForCookieAuthentication(): void $this->authenticationServiceStub, $this->authorizationServerStub, $this->moduleConfigStub, - $this->loggerServiceMock + $this->loggerServiceMock, ))($this->serverRequestStub); } @@ -219,7 +219,7 @@ public function testValidateAcrThrowsIfNoMatchedAcrForEssentialAcrs(): void $this->authenticationServiceStub, $this->authorizationServerStub, $this->moduleConfigStub, - $this->loggerServiceMock + $this->loggerServiceMock, ))($this->serverRequestStub); } @@ -257,7 +257,7 @@ public function testValidateAcrSetsFirstMatchedAcr(): void $this->authenticationServiceStub, $this->authorizationServerStub, $this->moduleConfigStub, - $this->loggerServiceMock + $this->loggerServiceMock, ))($this->serverRequestStub); } @@ -296,7 +296,7 @@ public function testValidateAcrSetsCurrentSessionAcrIfNoMatchedAcr(): void $this->authenticationServiceStub, $this->authorizationServerStub, $this->moduleConfigStub, - $this->loggerServiceMock + $this->loggerServiceMock, ))($this->serverRequestStub); } @@ -336,7 +336,7 @@ public function testValidateAcrLogsWarningIfNoAcrsConfigured(): void $this->authenticationServiceStub, $this->authorizationServerStub, $this->moduleConfigStub, - $this->loggerServiceMock + $this->loggerServiceMock, ))($this->serverRequestStub); } } diff --git a/tests/src/Controller/Client/CreateControllerTest.php b/tests/src/Controller/Client/CreateControllerTest.php index 9dcf9c4b..90043f64 100644 --- a/tests/src/Controller/Client/CreateControllerTest.php +++ b/tests/src/Controller/Client/CreateControllerTest.php @@ -67,7 +67,7 @@ protected function getStubbedInstance(): CreateController $this->templateFactoryMock, $this->formFactoryMock, $this->sessionMessageServiceMock, - $this->authContextServiceMock + $this->authContextServiceMock, ); } @@ -134,7 +134,7 @@ public function testCanCreateNewClientFromFormData(): void 'allowed_origin' => [], 'post_logout_redirect_uri' => [], 'backchannel_logout_uri' => null, - ] + ], ); $this->formFactoryMock @@ -192,7 +192,7 @@ public function testCanSetOwnerInNewClient(): void 'allowed_origin' => [], 'post_logout_redirect_uri' => [], 'backchannel_logout_uri' => null, - ] + ], ); $this->formFactoryMock diff --git a/tests/src/Controller/Client/DeleteControllerTest.php b/tests/src/Controller/Client/DeleteControllerTest.php index b10ab8ec..35f21a50 100644 --- a/tests/src/Controller/Client/DeleteControllerTest.php +++ b/tests/src/Controller/Client/DeleteControllerTest.php @@ -60,7 +60,7 @@ protected function getStubbedInstance(): \SimpleSAML\Module\oidc\Controller\Clie $this->clientRepositoryMock, $this->templateFactoryMock, $this->sessionMessageServiceMock, - $this->authContextServiceMock + $this->authContextServiceMock, ); } @@ -158,7 +158,7 @@ public function testItDeletesClient(): void $this->assertInstanceOf( RedirectResponse::class, - ($this->getStubbedInstance())->__invoke($this->serverRequestMock) + ($this->getStubbedInstance())->__invoke($this->serverRequestMock), ); } @@ -184,7 +184,7 @@ public function testItDeletesClientWithOwner(): void $this->assertInstanceOf( RedirectResponse::class, - ($this->getStubbedInstance())->__invoke($this->serverRequestMock) + ($this->getStubbedInstance())->__invoke($this->serverRequestMock), ); } } diff --git a/tests/src/Controller/Client/EditControllerTest.php b/tests/src/Controller/Client/EditControllerTest.php index df6bff3c..7b475d69 100644 --- a/tests/src/Controller/Client/EditControllerTest.php +++ b/tests/src/Controller/Client/EditControllerTest.php @@ -76,7 +76,7 @@ protected function getStubbedInstance(): EditController $this->templateFactoryMock, $this->formFactoryMock, $this->sessionMessageServiceMock, - $this->authContextServiceMock + $this->authContextServiceMock, ); } @@ -84,7 +84,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( EditController::class, - $this->getStubbedInstance() + $this->getStubbedInstance(), ); } @@ -130,12 +130,12 @@ public function testItShowsEditClientForm(): void 'regexUri' => ClientForm::REGEX_URI, 'regexAllowedOriginUrl' => ClientForm::REGEX_ALLOWED_ORIGIN_URL, 'regexHttpUri' => ClientForm::REGEX_HTTP_URI, - ] + ], )->willReturn($this->templateStub); $this->assertSame( ($this->getStubbedInstance())->__invoke($this->serverRequestMock), - $this->templateStub + $this->templateStub, ); } @@ -194,7 +194,7 @@ public function testItUpdatesClientFromEditClientFormData(): void 'allowed_origin' => [], 'post_logout_redirect_uri' => [], 'backchannel_logout_uri' => null, - ] + ], ); $this->formFactoryMock->expects($this->once())->method('build')->willReturn($this->clientFormMock); @@ -212,7 +212,7 @@ public function testItUpdatesClientFromEditClientFormData(): void 'auth_source', 'existingOwner', ), - null + null, ); $this->allowedOriginRepositoryMock->expects($this->once())->method('set')->with('clientid', []); @@ -221,7 +221,7 @@ public function testItUpdatesClientFromEditClientFormData(): void $this->assertInstanceOf( RedirectResponse::class, - ($this->getStubbedInstance())->__invoke($this->serverRequestMock) + ($this->getStubbedInstance())->__invoke($this->serverRequestMock), ); } @@ -280,7 +280,7 @@ public function testItSendsOwnerArgToRepoOnUpdate(): void 'allowed_origin' => [], 'post_logout_redirect_uri' => [], 'backchannel_logout_uri' => null, - ] + ], ); $this->formFactoryMock->expects($this->once())->method('build')->willReturn($this->clientFormMock); @@ -298,7 +298,7 @@ public function testItSendsOwnerArgToRepoOnUpdate(): void 'auth_source', 'existingOwner', ), - 'authedUserId' + 'authedUserId', ); $this->allowedOriginRepositoryMock->expects($this->once())->method('get')->with('clientid') @@ -309,7 +309,7 @@ public function testItSendsOwnerArgToRepoOnUpdate(): void $this->assertInstanceOf( RedirectResponse::class, - ($this->getStubbedInstance())->__invoke($this->serverRequestMock) + ($this->getStubbedInstance())->__invoke($this->serverRequestMock), ); } diff --git a/tests/src/Controller/Client/IndexControllerTest.php b/tests/src/Controller/Client/IndexControllerTest.php index addfa32c..320f7f14 100644 --- a/tests/src/Controller/Client/IndexControllerTest.php +++ b/tests/src/Controller/Client/IndexControllerTest.php @@ -52,7 +52,7 @@ protected function getStubbedInstance(): IndexController return new IndexController( $this->clientRepositoryMock, $this->templateFactoryMock, - $this->authContextServiceMock + $this->authContextServiceMock, ); } @@ -72,8 +72,8 @@ public function testItShowsClientIndex(): void [ 'items' => [], 'numPages' => 1, - 'currentPage' => 1 - ] + 'currentPage' => 1, + ], ); $this->templateFactoryMock->expects($this->once())->method('render')->with( @@ -83,7 +83,7 @@ public function testItShowsClientIndex(): void 'numPages' => 1, 'currentPage' => 1, 'query' => '', - ] + ], )->willReturn($this->templateStub); $this->assertSame($this->templateStub, ($this->getStubbedInstance())->__invoke($this->serverRequestMock)); diff --git a/tests/src/Controller/Client/ResetSecretControllerTest.php b/tests/src/Controller/Client/ResetSecretControllerTest.php index f83d6414..0e64b8ba 100644 --- a/tests/src/Controller/Client/ResetSecretControllerTest.php +++ b/tests/src/Controller/Client/ResetSecretControllerTest.php @@ -55,15 +55,15 @@ protected function prepareStubbedInstance(): \SimpleSAML\Module\oidc\Controller\ return new ResetSecretController( $this->clientRepositoryMock, $this->sessionMessagesServiceMock, - $this->authContextServiceMock + $this->authContextServiceMock, ); } public function testCanInstantiate(): void { $this->assertInstanceOf( - \SimpleSAML\Module\oidc\Controller\Client\ResetSecretController::class, - $this->prepareStubbedInstance() + ResetSecretController::class, + $this->prepareStubbedInstance(), ); } @@ -209,7 +209,7 @@ public function testItSendBackToShowClientIfNotPostMethodInResetAction(): void $this->assertInstanceOf( RedirectResponse::class, - $this->prepareStubbedInstance()->__invoke($this->serverRequestMock) + $this->prepareStubbedInstance()->__invoke($this->serverRequestMock), ); } } diff --git a/tests/src/Controller/Client/ShowControllerTest.php b/tests/src/Controller/Client/ShowControllerTest.php index f3dc4a11..2cee7d75 100644 --- a/tests/src/Controller/Client/ShowControllerTest.php +++ b/tests/src/Controller/Client/ShowControllerTest.php @@ -63,7 +63,7 @@ protected function getStubbedInstance(): ShowController $this->clientRepositoryMock, $this->allowedOriginRepositoryMock, $this->templateFactoryMock, - $this->authContextServiceMock + $this->authContextServiceMock, ); } @@ -71,7 +71,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( ShowController::class, - $this->getStubbedInstance() + $this->getStubbedInstance(), ); } @@ -104,13 +104,13 @@ public function testItShowsClientDescription(): void 'oidc:clients/show.twig', [ 'client' => $this->clientEntityMock, - 'allowedOrigins' => [] - ] + 'allowedOrigins' => [], + ], )->willReturn($this->templateMock); $this->assertSame( $this->templateMock, - $this->getStubbedInstance()->__invoke($this->serverRequestMock) + $this->getStubbedInstance()->__invoke($this->serverRequestMock), ); } diff --git a/tests/src/Controller/ConfigurationDiscoveryControllerTest.php b/tests/src/Controller/ConfigurationDiscoveryControllerTest.php index 75bbd30c..63f14776 100644 --- a/tests/src/Controller/ConfigurationDiscoveryControllerTest.php +++ b/tests/src/Controller/ConfigurationDiscoveryControllerTest.php @@ -48,7 +48,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( ConfigurationDiscoveryController::class, - new ConfigurationDiscoveryController($this->oidcOpenIdProviderMetadataServiceMock) + new ConfigurationDiscoveryController($this->oidcOpenIdProviderMetadataServiceMock), ); } @@ -61,7 +61,7 @@ public function testItReturnsOpenIdConnectConfiguration(): void { $this->assertSame( json_decode($this->getStubbedInstance()->__invoke()->getContent(), true), - self::OIDC_OP_METADATA + self::OIDC_OP_METADATA, ); } } diff --git a/tests/src/Controller/InstallerControllerTest.php b/tests/src/Controller/InstallerControllerTest.php index 0f8015a2..e3dcb3d5 100644 --- a/tests/src/Controller/InstallerControllerTest.php +++ b/tests/src/Controller/InstallerControllerTest.php @@ -48,7 +48,7 @@ protected function createStubbedInstance(): InstallerController $this->templateFactoryMock, $this->sessionMessagesService, $this->databaseMigrationMock, - $this->databaseLegacyOAuth2ImportMock + $this->databaseLegacyOAuth2ImportMock, ); } @@ -56,7 +56,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( InstallerController::class, - $this->createStubbedInstance() + $this->createStubbedInstance(), ); } @@ -72,7 +72,7 @@ public function testItReturnsToMainPageIfAlreadyUpdated(): void $this->assertInstanceOf( RedirectResponse::class, - $this->createStubbedInstance()->__invoke($this->serverRequestMock) + $this->createStubbedInstance()->__invoke($this->serverRequestMock), ); } @@ -91,7 +91,7 @@ public function testItShowsInformationPage(): void $this->assertSame( $this->templateMock, - $this->createStubbedInstance()->__invoke($this->serverRequestMock) + $this->createStubbedInstance()->__invoke($this->serverRequestMock), ); } @@ -111,7 +111,7 @@ public function testItRequiresConfirmationBeforeInstallSchema(): void $this->assertSame( $this->templateMock, - $this->createStubbedInstance()->__invoke($this->serverRequestMock) + $this->createStubbedInstance()->__invoke($this->serverRequestMock), ); } @@ -128,7 +128,7 @@ public function testItCreatesSchema(): void $this->assertInstanceOf( RedirectResponse::class, - $this->createStubbedInstance()->__invoke($this->serverRequestMock) + $this->createStubbedInstance()->__invoke($this->serverRequestMock), ); } @@ -149,13 +149,13 @@ public function testItImportsDataFromOauth2Module(): void ->method('addMessage') ->with( $this->callback( - fn($message) => in_array($message, ['{oidc:install:finished}', '{oidc:import:finished}']) - ) + fn($message) => in_array($message, ['{oidc:install:finished}', '{oidc:import:finished}']), + ), ); $this->assertInstanceOf( RedirectResponse::class, - $this->createStubbedInstance()->__invoke($this->serverRequestMock) + $this->createStubbedInstance()->__invoke($this->serverRequestMock), ); } } diff --git a/tests/src/Controller/JwksControllerTest.php b/tests/src/Controller/JwksControllerTest.php index efffe479..41943d9c 100644 --- a/tests/src/Controller/JwksControllerTest.php +++ b/tests/src/Controller/JwksControllerTest.php @@ -32,7 +32,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( JwksController::class, - new JwksController($this->jsonWebKeySetServiceMock) + new JwksController($this->jsonWebKeySetServiceMock), ); } @@ -53,8 +53,7 @@ public function testItReturnsJsonKeys(): void $this->assertSame( ['keys' => $keys], - (new JwksController($this->jsonWebKeySetServiceMock))->__invoke() - ->getPayload() + (new JwksController($this->jsonWebKeySetServiceMock))->__invoke()->getPayload(), ); } } diff --git a/tests/src/Controller/LogoutControllerTest.php b/tests/src/Controller/LogoutControllerTest.php index d15780b8..3c2658f8 100644 --- a/tests/src/Controller/LogoutControllerTest.php +++ b/tests/src/Controller/LogoutControllerTest.php @@ -75,8 +75,8 @@ public function testConstruct(): void $this->sessionServiceStub, $this->sessionLogoutTicketStoreBuilderStub, $this->loggerServiceMock, - $this->templateFactoryStub - ) + $this->templateFactoryStub, + ), ); } @@ -94,7 +94,7 @@ public function testInvokeThrowsForInvalidLogoutRequest(): void $this->sessionServiceStub, $this->sessionLogoutTicketStoreBuilderStub, $this->loggerServiceMock, - $this->templateFactoryStub + $this->templateFactoryStub, ); $this->expectException(BadRequest::class); @@ -129,7 +129,7 @@ public function testCallLogoutForSessionIdInIdTokenHint(): void $this->sessionServiceStub, $this->sessionLogoutTicketStoreBuilderStub, $this->loggerServiceMock, - $this->templateFactoryStub + $this->templateFactoryStub, ))->__invoke($this->serverRequestStub); } @@ -159,7 +159,7 @@ public function testLogsIfSessionFromIdTokenHintNotFound(): void $this->sessionServiceStub, $this->sessionLogoutTicketStoreBuilderStub, $this->loggerServiceMock, - $this->templateFactoryStub + $this->templateFactoryStub, ))->__invoke($this->serverRequestStub); } @@ -183,7 +183,7 @@ public function testLogoutCalledOnCurrentSession(): void $this->sessionServiceStub, $this->sessionLogoutTicketStoreBuilderStub, $this->loggerServiceMock, - $this->templateFactoryStub + $this->templateFactoryStub, ))->__invoke($this->serverRequestStub); } @@ -205,7 +205,7 @@ public function testReturnsRedirectResponseIfPostLogoutRedirectUriIsSet(): void $this->sessionServiceStub, $this->sessionLogoutTicketStoreBuilderStub, $this->loggerServiceMock, - $this->templateFactoryStub + $this->templateFactoryStub, ); $this->assertInstanceOf(RedirectResponse::class, $logoutController->__invoke($this->serverRequestStub)); @@ -226,7 +226,7 @@ public function testReturnsResponse(): void $this->sessionServiceStub, $this->sessionLogoutTicketStoreBuilderStub, $this->loggerServiceMock, - $this->templateFactoryStub + $this->templateFactoryStub, ); $this->assertInstanceOf(Response::class, $logoutController->__invoke($this->serverRequestStub)); diff --git a/tests/src/Controller/UserInfoControllerTest.php b/tests/src/Controller/UserInfoControllerTest.php index afc5e9ba..4a9022ff 100644 --- a/tests/src/Controller/UserInfoControllerTest.php +++ b/tests/src/Controller/UserInfoControllerTest.php @@ -60,7 +60,7 @@ protected function prepareMockedInstance(): UserInfoController $this->accessTokenRepositoryMock, $this->userRepositoryMock, $this->allowedOriginRepositoryMock, - $this->claimTranslatorExtractorMock + $this->claimTranslatorExtractorMock, ); } @@ -68,7 +68,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( UserInfoController::class, - $this->prepareMockedInstance() + $this->prepareMockedInstance(), ); } @@ -86,7 +86,7 @@ public function testItReturnsUserClaims(): void ->willReturnCallback(function ($argument) { $argumentValueMap = [ 'oauth_access_token_id' => 'tokenid', - 'oauth_scopes' => ['openid', 'email'] + 'oauth_scopes' => ['openid', 'email'], ]; if (array_key_exists($argument, $argumentValueMap)) { @@ -133,7 +133,7 @@ public function testItReturnsUserClaims(): void $this->assertSame( ['email' => 'userid@localhost.localdomain'], - $this->prepareMockedInstance()->__invoke($this->serverRequestMock)->getPayload() + $this->prepareMockedInstance()->__invoke($this->serverRequestMock)->getPayload(), ); } @@ -150,7 +150,7 @@ public function testItThrowsIfAccessTokenNotFound(): void ->willReturnCallback(function ($argument) { $argumentValueMap = [ 'oauth_access_token_id' => 'tokenid', - 'oauth_scopes' => ['openid', 'email'] + 'oauth_scopes' => ['openid', 'email'], ]; if (array_key_exists($argument, $argumentValueMap)) { @@ -185,7 +185,7 @@ public function testItThrowsIfUserNotFound(): void ->willReturnCallback(function ($argument) { $argumentValueMap = [ 'oauth_access_token_id' => 'tokenid', - 'oauth_scopes' => ['openid', 'email'] + 'oauth_scopes' => ['openid', 'email'], ]; if (array_key_exists($argument, $argumentValueMap)) { @@ -235,7 +235,7 @@ public function testItHandlesCorsRequest(): void 'Access-Control-Allow-Methods' => ['GET, POST, OPTIONS'], 'Access-Control-Allow-Headers' => ['Authorization'], 'Access-Control-Allow-Credentials' => ['true'], - ] + ], ); } diff --git a/tests/src/Entities/AccessTokenEntityTest.php b/tests/src/Entities/AccessTokenEntityTest.php index 342923fe..5d2d6897 100644 --- a/tests/src/Entities/AccessTokenEntityTest.php +++ b/tests/src/Entities/AccessTokenEntityTest.php @@ -89,7 +89,7 @@ protected function setUp(): void 'client' => $this->clientEntityStub, 'is_revoked' => $this->isRevoked, 'auth_code_id' => $this->authCodeId, - 'requested_claims' => json_encode($this->requestedClaims, JSON_THROW_ON_ERROR) + 'requested_claims' => json_encode($this->requestedClaims, JSON_THROW_ON_ERROR), ]; } @@ -111,8 +111,8 @@ public function testCanCreateInstanceFromData(): void $this->scopes, $this->userId, $this->authCodeId, - $this->requestedClaims - ) + $this->requestedClaims, + ), ); } diff --git a/tests/src/Entities/AuthCodeEntityTest.php b/tests/src/Entities/AuthCodeEntityTest.php index f49642ca..2acbaddb 100644 --- a/tests/src/Entities/AuthCodeEntityTest.php +++ b/tests/src/Entities/AuthCodeEntityTest.php @@ -35,7 +35,7 @@ protected function setUp(): void 'client' => $this->clientEntityMock, 'is_revoked' => false, 'redirect_uri' => 'https://localhost/redirect', - 'nonce' => 'nonce' + 'nonce' => 'nonce', ]; } @@ -57,7 +57,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( AuthCodeEntity::class, - $this->prepareMockedInstance() + $this->prepareMockedInstance(), ); } @@ -78,7 +78,7 @@ public function testCanGetState(): void 'is_revoked' => 0, 'redirect_uri' => 'https://localhost/redirect', 'nonce' => 'nonce', - ] + ], ); } diff --git a/tests/src/Entities/ClientEntityTest.php b/tests/src/Entities/ClientEntityTest.php index e2125713..920fa9d2 100644 --- a/tests/src/Entities/ClientEntityTest.php +++ b/tests/src/Entities/ClientEntityTest.php @@ -51,12 +51,12 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( ClientEntity::class, - $this->prepareMockedInstance() + $this->prepareMockedInstance(), ); $this->assertInstanceOf( ClientEntity::class, - ClientEntity::fromData('id', 'secret', 'name', 'description', ['redirectUri'], [], true) + ClientEntity::fromData('id', 'secret', 'name', 'description', ['redirectUri'], [], true), ); } @@ -120,7 +120,7 @@ public function testCanGetState(): void 'owner' => 'user@test.com', 'post_logout_redirect_uri' => json_encode([]), 'backchannel_logout_uri' => null, - ] + ], ); } @@ -145,7 +145,7 @@ public function testCanExportAsArray(): void 'owner' => 'user@test.com', 'post_logout_redirect_uri' => [], 'backchannel_logout_uri' => null, - ] + ], ); } } diff --git a/tests/src/Entities/RefreshTokenEntityTest.php b/tests/src/Entities/RefreshTokenEntityTest.php index 26265730..409a6c61 100644 --- a/tests/src/Entities/RefreshTokenEntityTest.php +++ b/tests/src/Entities/RefreshTokenEntityTest.php @@ -53,7 +53,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( RefreshTokenEntity::class, - $this->prepareMockedInstance() + $this->prepareMockedInstance(), ); } @@ -70,7 +70,7 @@ public function testCanGetState(): void 'access_token_id' => 'access_token_id', 'is_revoked' => 0, 'auth_code_id' => '123', - ] + ], ); } } diff --git a/tests/src/Entities/ScopeEntityTest.php b/tests/src/Entities/ScopeEntityTest.php index a3b6fa19..4aa2e66c 100644 --- a/tests/src/Entities/ScopeEntityTest.php +++ b/tests/src/Entities/ScopeEntityTest.php @@ -13,7 +13,7 @@ protected function prepareMockedInstance( string $id = 'id', string $description = 'description', string $icon = 'icon', - array $attributes = ['attrid' => 'attrval'] + array $attributes = ['attrid' => 'attrval'], ): ScopeEntity { return ScopeEntity::fromData($id, $description, $icon, $attributes); } @@ -22,7 +22,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( ScopeEntity::class, - $this->prepareMockedInstance() + $this->prepareMockedInstance(), ); } diff --git a/tests/src/Entities/UserEntityTest.php b/tests/src/Entities/UserEntityTest.php index 4c7a81bb..9c32252c 100644 --- a/tests/src/Entities/UserEntityTest.php +++ b/tests/src/Entities/UserEntityTest.php @@ -43,12 +43,12 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( UserEntity::class, - $this->prepareMockedInstance() + $this->prepareMockedInstance(), ); $this->assertInstanceOf( UserEntity::class, - UserEntity::fromData('id') + UserEntity::fromData('id'), ); } @@ -80,7 +80,7 @@ public function testCanGetState(): void 'claims' => json_encode([]), 'updated_at' => '1970-01-01 00:00:00', 'created_at' => '1970-01-01 00:00:00', - ] + ], ); } } diff --git a/tests/src/Factories/ClaimTranslatorExtractorFactoryTest.php b/tests/src/Factories/ClaimTranslatorExtractorFactoryTest.php index b77f4060..076ef7eb 100644 --- a/tests/src/Factories/ClaimTranslatorExtractorFactoryTest.php +++ b/tests/src/Factories/ClaimTranslatorExtractorFactoryTest.php @@ -35,16 +35,16 @@ protected function setUp(): void 'testClaim' => ['attribute'], 'intClaim' => [ 'type' => 'int', - 'intAttribute' + 'intAttribute', ], 'testClaim2' => ['attribute2'], 'boolClaim' => [ 'type' => 'bool', - 'attributes' => ['boolAttribute'] + 'attributes' => ['boolAttribute'], ], - ] - ] - ) + ], + ], + ), ); $this->moduleConfigMock ->method('getOpenIDPrivateScopes') @@ -61,7 +61,7 @@ protected function setUp(): void 'claims' => ['testClaim3', 'boolClaim'], 'are_multiple_claim_values_allowed' => true, ], - ] + ], ); } @@ -74,7 +74,7 @@ public function testCanCreateInstance(): void { $this->assertInstanceOf( ClaimTranslatorExtractorFactory::class, - $this->prepareMockedInstance() + $this->prepareMockedInstance(), ); } @@ -85,7 +85,7 @@ public function testCanBuildClaimTranslatorExtractor(): void { $this->assertInstanceOf( ClaimTranslatorExtractor::class, - $this->prepareMockedInstance()->build() + $this->prepareMockedInstance()->build(), ); } @@ -98,7 +98,7 @@ public function testExtractor(): void $this->assertSame( $claimTranslatorExtractor->getClaimSet('customScope2')->getClaims(), - ['myprefix_testClaim2', 'myprefix_boolClaim'] + ['myprefix_testClaim2', 'myprefix_boolClaim'], ); $claimData = $claimTranslatorExtractor->extract( @@ -108,8 +108,8 @@ public function testExtractor(): void 'attribute' => ['val1'], 'intAttribute' => ['56789'], 'boolAttribute' => ['yes'], - 'attribute2' => ['val2'] - ] + 'attribute2' => ['val2'], + ], ); $this->assertSame( @@ -120,7 +120,7 @@ public function testExtractor(): void 'intClaim' => 56789, 'myprefix_testClaim2' => "val2", 'myprefix_boolClaim' => true, - ] + ], ); } } diff --git a/tests/src/ModuleConfigTest.php b/tests/src/ModuleConfigTest.php index f1a45c08..28893b0c 100644 --- a/tests/src/ModuleConfigTest.php +++ b/tests/src/ModuleConfigTest.php @@ -24,20 +24,20 @@ public function testSigningKeyNameCanBeCustomized(): void Configuration::setPreLoadedConfig( Configuration::loadFromArray( [ - 'certdir' => $certDir - ] - ) + 'certdir' => $certDir, + ], + ), ); Configuration::setPreLoadedConfig( Configuration::loadFromArray([]), - ModuleConfig::DEFAULT_FILE_NAME + 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() + $moduleConfig->getPrivateKeyPath(), ); // Set customized @@ -46,9 +46,9 @@ public function testSigningKeyNameCanBeCustomized(): void [ ModuleConfig::OPTION_PKI_PRIVATE_KEY_FILENAME => 'myPrivateKey.key', ModuleConfig::OPTION_PKI_CERTIFICATE_FILENAME => 'myCertificate.crt', - ] + ], ), - ModuleConfig::DEFAULT_FILE_NAME + ModuleConfig::DEFAULT_FILE_NAME, ); $moduleConfig = new ModuleConfig(); $this->assertEquals($certDir . 'myCertificate.crt', $moduleConfig->getCertPath()); diff --git a/tests/src/Repositories/AccessTokenRepositoryTest.php b/tests/src/Repositories/AccessTokenRepositoryTest.php index 4a7327cf..50f0d87f 100644 --- a/tests/src/Repositories/AccessTokenRepositoryTest.php +++ b/tests/src/Repositories/AccessTokenRepositoryTest.php @@ -91,11 +91,11 @@ public function testAddAndFound(): void $accessToken = self::$repository->getNewToken( ClientRepositoryTest::getClient(self::CLIENT_ID), $scopes, - self::USER_ID + self::USER_ID, ); $accessToken->setIdentifier(self::ACCESS_TOKEN_ID); $accessToken->setExpiryDateTime(DateTimeImmutable::createFromMutable( - TimestampGenerator::utc('yesterday') + TimestampGenerator::utc('yesterday'), )); self::$repository->persistNewAccessToken($accessToken); diff --git a/tests/src/Repositories/ClientRepositoryTest.php b/tests/src/Repositories/ClientRepositoryTest.php index b8b7ec3c..2ab94c0d 100644 --- a/tests/src/Repositories/ClientRepositoryTest.php +++ b/tests/src/Repositories/ClientRepositoryTest.php @@ -250,7 +250,7 @@ public function testUpdate(): void ['openid'], true, false, - 'admin' + 'admin', ); self::$repository->update($client); @@ -332,7 +332,7 @@ public static function getClient( string $id, bool $enabled = true, bool $confidential = false, - ?string $owner = null + ?string $owner = null, ): ClientEntityInterface { return ClientEntity::fromData( $id, @@ -344,7 +344,7 @@ public static function getClient( $enabled, $confidential, 'admin', - $owner + $owner, ); } } diff --git a/tests/src/Repositories/ScopeRepositoryTest.php b/tests/src/Repositories/ScopeRepositoryTest.php index 822e7348..b558dcf7 100644 --- a/tests/src/Repositories/ScopeRepositoryTest.php +++ b/tests/src/Repositories/ScopeRepositoryTest.php @@ -55,7 +55,7 @@ public function testGetScopeEntityByIdentifier(): void $expected = ScopeEntity::fromData( 'openid', - 'openid' + 'openid', ); $this->assertEquals($expected, $scope); diff --git a/tests/src/Server/Associations/RelyingPartyAssociationTest.php b/tests/src/Server/Associations/RelyingPartyAssociationTest.php index 57d673d8..56e4f2a4 100644 --- a/tests/src/Server/Associations/RelyingPartyAssociationTest.php +++ b/tests/src/Server/Associations/RelyingPartyAssociationTest.php @@ -23,7 +23,7 @@ public function testConstruct(): void $this->clientId, $this->userId, $this->sessionId, - $this->backChannelLogoutUri + $this->backChannelLogoutUri, ); $this->assertEquals($this->clientId, $rpAssociation->getClientId()); diff --git a/tests/src/Server/Grants/AuthCodeGrantTest.php b/tests/src/Server/Grants/AuthCodeGrantTest.php index 2b185b54..3e1c4df4 100644 --- a/tests/src/Server/Grants/AuthCodeGrantTest.php +++ b/tests/src/Server/Grants/AuthCodeGrantTest.php @@ -53,7 +53,7 @@ public function testCanCreateInstance(): void $this->refreshTokenRepositoryStub, $this->authCodeTtl, $this->requestRulesManagerStub, - ) + ), ); } } diff --git a/tests/src/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php b/tests/src/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php index deb9ad52..6580bc88 100644 --- a/tests/src/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php +++ b/tests/src/Server/LogoutHandlers/BackChannelLogoutHandlerTest.php @@ -82,7 +82,7 @@ protected function getSampleRelyingPartyAssociation( ?string $clientId = null, ?string $userId = null, ?string $sessionId = null, - ?string $backChannelLogoutUri = null + ?string $backChannelLogoutUri = null, ): RelyingPartyAssociation { $id = substr((string) hrtime(true), -4); @@ -90,7 +90,7 @@ protected function getSampleRelyingPartyAssociation( $clientId ?? 'client' . $id, $userId ?? 'user' . $id, $sessionId ?? 'session' . $id, - $backChannelLogoutUri ?? 'https://example.org/logout/' . $id + $backChannelLogoutUri ?? 'https://example.org/logout/' . $id, ); } } diff --git a/tests/src/Server/RequestTypes/LogoutRequestTest.php b/tests/src/Server/RequestTypes/LogoutRequestTest.php index 319383f6..4ee65bc6 100644 --- a/tests/src/Server/RequestTypes/LogoutRequestTest.php +++ b/tests/src/Server/RequestTypes/LogoutRequestTest.php @@ -46,7 +46,7 @@ public function testConstructWithParams(): void $this->idTokenHintStub, self::$postLogoutRedirectUri, self::$state, - self::$uiLocales + self::$uiLocales, ); $this->assertInstanceOf(LogoutRequest::class, $logoutRequest); diff --git a/tests/src/Server/ResponseTypes/IdTokenResponseTest.php b/tests/src/Server/ResponseTypes/IdTokenResponseTest.php index 09fc0a3b..44597285 100644 --- a/tests/src/Server/ResponseTypes/IdTokenResponseTest.php +++ b/tests/src/Server/ResponseTypes/IdTokenResponseTest.php @@ -69,7 +69,7 @@ protected function setUp(): void $this->certFolder = dirname(__DIR__, 4) . '/docker/ssp/'; $this->userEntity = UserEntity::fromData(self::SUBJECT, [ 'cn' => ['Homer Simpson'], - 'mail' => ['myEmail@example.com'] + 'mail' => ['myEmail@example.com'], ]); $this->scopes = [ ScopeEntity::fromData('openid'), @@ -111,7 +111,7 @@ protected function setUp(): void $this->idTokenBuilder = new IdTokenBuilder( new JsonWebTokenBuilderService($this->moduleConfigMock), - new ClaimTranslatorExtractor(self::USER_ID_ATTR) + new ClaimTranslatorExtractor(self::USER_ID_ATTR), ); } @@ -135,7 +135,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( IdTokenResponse::class, - $this->prepareMockedInstance() + $this->prepareMockedInstance(), ); } @@ -168,17 +168,17 @@ public function testItCanGenerateResponseWithIndividualRequestedClaims(): void "id_token" => [ "name" => [ "essential" => true, - ] + ], ], "userinfo" => [ "email" => [ "essential" => true, - ] - ] - ] + ], + ], + ], ); $this->accessTokenEntityMock->method('getScopes')->willReturn( - [ScopeEntity::fromData('openid'),] + [ScopeEntity::fromData('openid')], ); $idTokenResponse->setAccessToken($this->accessTokenEntityMock); $response = $idTokenResponse->generateHttpResponse(new Response()); @@ -192,7 +192,7 @@ public function testNoExtraParamsForNonOidcRequest(): void { $this->accessTokenEntityMock->method('getRequestedClaims')->willReturn([]); $this->accessTokenEntityMock->method('getScopes')->willReturn( - [ScopeEntity::fromData('profile'),] + [ScopeEntity::fromData('profile')], ); $idTokenResponse = $this->prepareMockedInstance(); $idTokenResponse->setAccessToken($this->accessTokenEntityMock); @@ -219,7 +219,7 @@ protected function shouldHaveValidIdToken(string $body, $expectedClaims = []): b if ($responseKeys !== array_flip($expectedResponseFields)) { throw new Exception( 'missing expected keys. Got ' . var_export(array_keys($result), true) - . ' need ' . var_export($expectedResponseFields, true) + . ' need ' . var_export($expectedResponseFields, true), ); } @@ -237,13 +237,13 @@ protected function shouldHaveValidIdToken(string $body, $expectedClaims = []): b new StrictValidAt(SystemClock::fromUTC()), new SignedWith( new Sha256(), - InMemory::plainText(file_get_contents($this->certFolder . '/oidc_module.crt')) - ) + InMemory::plainText(file_get_contents($this->certFolder . '/oidc_module.crt')), + ), ); if ($token->headers()->get('kid') !== self::KEY_ID) { throw new Exception( - 'Wrong key id. Expected ' . self::KEY_ID . ' was ' . $token->headers()->get('kid') + 'Wrong key id. Expected ' . self::KEY_ID . ' was ' . $token->headers()->get('kid'), ); } $expectedClaimsKeys = array_keys($expectedClaims); @@ -252,7 +252,7 @@ protected function shouldHaveValidIdToken(string $body, $expectedClaims = []): b if ($claims !== $expectedClaimsKeys) { throw new Exception( 'missing expected claim. Got ' . var_export($claims, true) - . ' need ' . var_export($expectedClaimsKeys, true) + . ' need ' . var_export($expectedClaimsKeys, true), ); } foreach ($expectedClaims as $claim => $value) { @@ -260,7 +260,7 @@ protected function shouldHaveValidIdToken(string $body, $expectedClaims = []): b if ($value !== $valFromToken) { throw new Exception( 'Expected claim value ' . var_export($value, true) - . ' got ' . var_export($valFromToken, true) + . ' got ' . var_export($valFromToken, true), ); } } diff --git a/tests/src/Server/Validators/BearerTokenValidatorTest.php b/tests/src/Server/Validators/BearerTokenValidatorTest.php index 63a3ac0d..a9202272 100644 --- a/tests/src/Server/Validators/BearerTokenValidatorTest.php +++ b/tests/src/Server/Validators/BearerTokenValidatorTest.php @@ -101,7 +101,7 @@ public static function setUpBeforeClass(): void 'desc1', ['redirect-uri'], ['openid'], - true + true, ); self::$accessTokenState = [ @@ -111,7 +111,7 @@ public static function setUpBeforeClass(): void 'user_id' => 'user123', 'client' => self::$clientEntity, 'is_revoked' => false, - 'auth_code_id' => 'authCode123' + 'auth_code_id' => 'authCode123', ]; self::$accessTokenEntity = AccessTokenEntity::fromState(self::$accessTokenState); @@ -147,7 +147,7 @@ public function testValidatesForAuthorizationHeader() $this->assertSame( self::$accessTokenState['id'], - $validatedServerRequest->getAttribute('oauth_access_token_id') + $validatedServerRequest->getAttribute('oauth_access_token_id'), ); } @@ -169,7 +169,7 @@ public function testValidatesForPostBodyParam() $this->assertSame( self::$accessTokenState['id'], - $validatedServerRequest->getAttribute('oauth_access_token_id') + $validatedServerRequest->getAttribute('oauth_access_token_id'), ); } diff --git a/tests/src/Services/AuthContextServiceTest.php b/tests/src/Services/AuthContextServiceTest.php index 6d181ed3..b851b2bb 100644 --- a/tests/src/Services/AuthContextServiceTest.php +++ b/tests/src/Services/AuthContextServiceTest.php @@ -21,7 +21,7 @@ class AuthContextServiceTest extends TestCase { final public const AUTHORIZED_USER = [ 'idAttribute' => ['myUsername'], - 'someEntitlement' => ['val1', 'val2', 'val3'] + 'someEntitlement' => ['val1', 'val2', 'val3'], ]; protected Configuration $permissions; protected MockObject $oidcConfigurationMock; @@ -40,7 +40,7 @@ protected function setUp(): void 'attribute' => 'someEntitlement', // Entitlements allow for registering, editing, delete a client. OIDC clients are owned by the creator 'client' => ['val2'], - ] + ], ); $this->oidcConfigurationMock = $this->createMock(Configuration::class); @@ -58,7 +58,7 @@ protected function prepareMockedInstance(): AuthContextService { return new AuthContextService( $this->moduleConfigMock, - $this->authSimpleFactory + $this->authSimpleFactory, ); } @@ -66,7 +66,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( AuthContextService::class, - $this->prepareMockedInstance() + $this->prepareMockedInstance(), ); } @@ -80,7 +80,7 @@ public function testItReturnsUsername(): void $this->assertSame( $this->prepareMockedInstance()->getAuthUserId(), - 'myUsername' + 'myUsername', ); } @@ -136,8 +136,8 @@ public function testItThrowsForWrongEntitlements(): void ->willReturn( [ 'idAttribute' => ['myUsername'], - 'someEntitlement' => ['otherEntitlement'] - ] + 'someEntitlement' => ['otherEntitlement'], + ], ); $this->expectException(RuntimeException::class); @@ -156,7 +156,7 @@ public function testItThrowsForNotHavingEntitlementAttribute(): void ->willReturn( [ 'idAttribute' => ['myUsername'], - ] + ], ); $this->expectException(RuntimeException::class); diff --git a/tests/src/Services/AuthProcServiceTest.php b/tests/src/Services/AuthProcServiceTest.php index 72884c83..00e39aee 100644 --- a/tests/src/Services/AuthProcServiceTest.php +++ b/tests/src/Services/AuthProcServiceTest.php @@ -41,7 +41,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( AuthProcService::class, - $this->prepareMockedInstance() + $this->prepareMockedInstance(), ); } @@ -66,7 +66,7 @@ public function testItExecutesConfiguredFilters(): void $sampleFilters = [ 50 => [ 'class' => '\\' . AttributeAdd::class, - 'newKey' => ['newValue'] + 'newKey' => ['newValue'], ], ]; $this->moduleConfigMock->method('getAuthProcFilters')->willReturn($sampleFilters); diff --git a/tests/src/Services/AuthenticationServiceTest.php b/tests/src/Services/AuthenticationServiceTest.php index 674e2c96..4839d65c 100644 --- a/tests/src/Services/AuthenticationServiceTest.php +++ b/tests/src/Services/AuthenticationServiceTest.php @@ -120,7 +120,7 @@ public function prepareMockedInstance(): AuthenticationService $this->oidcOpenIdProviderMetadataServiceMock, $this->sessionServiceMock, $this->claimTranslatorExtractorMock, - $this->moduleConfigMock + $this->moduleConfigMock, ); } @@ -128,7 +128,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( AuthenticationService::class, - $this->prepareMockedInstance() + $this->prepareMockedInstance(), ); } @@ -166,11 +166,11 @@ public function testItCreatesNewUser(): void $this->assertSame( $userEntity->getIdentifier(), - self::USERNAME + self::USERNAME, ); $this->assertSame( $userEntity->getClaims(), - self::USER_ENTITY_ATTRIBUTES + self::USER_ENTITY_ATTRIBUTES, ); } @@ -214,7 +214,7 @@ public function testItReturnsAnUser(): void $this->assertSame( $this->prepareMockedInstance()->getAuthenticateUser($this->serverRequestMock), - $this->userEntityMock + $this->userEntityMock, ); } diff --git a/tests/src/Services/JsonWebKeySetServiceTest.php b/tests/src/Services/JsonWebKeySetServiceTest.php index 5a7b3ec0..d99a8164 100644 --- a/tests/src/Services/JsonWebKeySetServiceTest.php +++ b/tests/src/Services/JsonWebKeySetServiceTest.php @@ -51,7 +51,7 @@ public static function setUpBeforeClass(): void Configuration::setPreLoadedConfig( Configuration::loadFromArray([]), - ModuleConfig::DEFAULT_FILE_NAME + ModuleConfig::DEFAULT_FILE_NAME, ); } diff --git a/tests/src/Services/JsonWebTokenBuilderServiceTest.php b/tests/src/Services/JsonWebTokenBuilderServiceTest.php index a9413f2b..f2a5d366 100644 --- a/tests/src/Services/JsonWebTokenBuilderServiceTest.php +++ b/tests/src/Services/JsonWebTokenBuilderServiceTest.php @@ -64,7 +64,7 @@ public function testCanCreateBuilderInstance(): void $this->assertInstanceOf( Builder::class, - $builderService->getDefaultJwtTokenBuilder() + $builderService->getDefaultJwtTokenBuilder(), ); } @@ -90,9 +90,9 @@ public function testCanGenerateSignedJwtToken(): void $this->moduleConfigStub->getSigner(), InMemory::file( $this->moduleConfigStub->getPrivateKeyPath(), - $this->moduleConfigStub->getPrivateKeyPassPhrase() ?? '' + $this->moduleConfigStub->getPrivateKeyPassPhrase() ?? '', ), - InMemory::file($this->moduleConfigStub->getCertPath()) + InMemory::file($this->moduleConfigStub->getCertPath()), ); $parsedToken = $jwtConfig->parser()->parse($token); @@ -103,9 +103,9 @@ public function testCanGenerateSignedJwtToken(): void new IssuedBy(self::$selfUrlHost), new SignedWith( $this->moduleConfigStub->getSigner(), - InMemory::file($this->moduleConfigStub->getCertPath()) - ) - ) + InMemory::file($this->moduleConfigStub->getCertPath()), + ), + ), ); } @@ -116,7 +116,7 @@ public function testCanReturnCurrentSigner(): void { $this->assertSame( self::$signerSha256, - (new JsonWebTokenBuilderService($this->moduleConfigStub))->getSigner() + (new JsonWebTokenBuilderService($this->moduleConfigStub))->getSigner(), ); } } diff --git a/tests/src/Services/LogoutTokenBuilderTest.php b/tests/src/Services/LogoutTokenBuilderTest.php index 01ed7fea..51c1d468 100644 --- a/tests/src/Services/LogoutTokenBuilderTest.php +++ b/tests/src/Services/LogoutTokenBuilderTest.php @@ -93,9 +93,9 @@ public function testCanGenerateSignedTokenForRelyingPartyAssociation(): void $this->moduleConfigStub->getSigner(), InMemory::file( $this->moduleConfigStub->getPrivateKeyPath(), - $this->moduleConfigStub->getPrivateKeyPassPhrase() ?? '' + $this->moduleConfigStub->getPrivateKeyPassPhrase() ?? '', ), - InMemory::file($this->moduleConfigStub->getCertPath()) + InMemory::file($this->moduleConfigStub->getCertPath()), ); $parsedToken = $jwtConfig->parser()->parse($token); @@ -108,9 +108,9 @@ public function testCanGenerateSignedTokenForRelyingPartyAssociation(): void new RelatedTo(self::$userId), new SignedWith( $this->moduleConfigStub->getSigner(), - InMemory::file($this->moduleConfigStub->getCertPath()) - ) - ) + InMemory::file($this->moduleConfigStub->getCertPath()), + ), + ), ); $this->assertTrue($parsedToken->headers()->has('typ')); diff --git a/tests/src/Services/OpMetadataServiceTest.php b/tests/src/Services/OpMetadataServiceTest.php index c8a445b9..c94cf06c 100644 --- a/tests/src/Services/OpMetadataServiceTest.php +++ b/tests/src/Services/OpMetadataServiceTest.php @@ -58,7 +58,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( OpMetadataService::class, - $this->prepareMockedInstance() + $this->prepareMockedInstance(), ); } @@ -88,7 +88,7 @@ public function testItReturnsExpectedMetadata(): void 'backchannel_logout_supported' => true, 'backchannel_logout_session_supported' => true, ], - $this->prepareMockedInstance()->getMetadata() + $this->prepareMockedInstance()->getMetadata(), ); } } diff --git a/tests/src/Services/SessionMessagesServiceTest.php b/tests/src/Services/SessionMessagesServiceTest.php index 0ba4120e..b8113bb1 100644 --- a/tests/src/Services/SessionMessagesServiceTest.php +++ b/tests/src/Services/SessionMessagesServiceTest.php @@ -34,7 +34,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( SessionMessagesService::class, - $this->prepareMockedInstance() + $this->prepareMockedInstance(), ); } @@ -59,7 +59,7 @@ public function testItGetsMessages(): void [ 'msg1' => 'Message one.', 'msg2' => 'Message two.', - ] + ], ); $this->sessionMock->expects($this->exactly(2)) diff --git a/tests/src/Utils/Checker/RequestRulesManagerTest.php b/tests/src/Utils/Checker/RequestRulesManagerTest.php index a17cde5d..374bc6f2 100644 --- a/tests/src/Utils/Checker/RequestRulesManagerTest.php +++ b/tests/src/Utils/Checker/RequestRulesManagerTest.php @@ -121,7 +121,7 @@ public function testSetData(RequestRulesManager $requestRulesManager): void $this->identicalTo($this->request), $this->isInstanceOf(ResultBagInterface::class), $this->isInstanceOf(LoggerService::class), - $this->arrayHasKey($this->key) + $this->arrayHasKey($this->key), ); $requestRulesManager->add($ruleMock); diff --git a/tests/src/Utils/Checker/Rules/AcrValuesRuleTest.php b/tests/src/Utils/Checker/Rules/AcrValuesRuleTest.php index c7ddc8f8..2a05ec07 100644 --- a/tests/src/Utils/Checker/Rules/AcrValuesRuleTest.php +++ b/tests/src/Utils/Checker/Rules/AcrValuesRuleTest.php @@ -44,7 +44,7 @@ public function testNoAcrIsSetIfAcrValuesNotRequested(): void $result = (new AcrValuesRule())->checkRule( $this->requestStub, $this->resultBagStub, - $this->loggerServiceStub + $this->loggerServiceStub, ) ?? new Result(AcrValuesRule::class, null); $this->assertNull($result->getValue()); } @@ -61,7 +61,7 @@ public function testPopulatesAcrValuesFromClaimsParameter(): void $result = (new AcrValuesRule())->checkRule( $this->requestStub, $this->resultBagStub, - $this->loggerServiceStub + $this->loggerServiceStub, ) ?? new Result(AcrValuesRule::class, null); $this->assertSame(['1', '0'], $result->getValue()['values']); @@ -78,7 +78,7 @@ public function testPopulatesAcrValuesFromAcrValuesRequestParameter(): void $result = (new AcrValuesRule())->checkRule( $this->requestStub, $this->resultBagStub, - $this->loggerServiceStub + $this->loggerServiceStub, ) ?? new Result(AcrValuesRule::class, null); $this->assertSame(['1', '0'], $result->getValue()['values']); diff --git a/tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php b/tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php index d71dbc87..d0fc1289 100644 --- a/tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php +++ b/tests/src/Utils/Checker/Rules/IdTokenHintRuleTest.php @@ -74,7 +74,7 @@ protected function setUp(): void $this->jwtConfig = Configuration::forAsymmetricSigner( $this->moduleConfigStub->getSigner(), InMemory::plainText(self::$privateKey->getKeyContents()), - InMemory::plainText(self::$publicKey->getKeyContents()) + InMemory::plainText(self::$publicKey->getKeyContents()), ); $this->loggerServiceStub = $this->createStub(LoggerService::class); @@ -84,7 +84,7 @@ public function testConstruct(): void { $this->assertInstanceOf(IdTokenHintRule::class, new IdTokenHintRule( $this->moduleConfigStub, - $this->cryptKeyFactoryStub + $this->cryptKeyFactoryStub, )); } @@ -99,7 +99,7 @@ public function testCheckRuleIsNullWhenParamNotSet(): void $result = $rule->checkRule( $this->requestStub, $this->resultBagStub, - $this->loggerServiceStub + $this->loggerServiceStub, ) ?? new Result(IdTokenHintRule::class); $this->assertNull($result->getValue()); @@ -146,7 +146,7 @@ public function testCheckRuleThrowsForIdTokenWithInvalidIssuer(): void $invalidIssuerJwt = $this->jwtConfig->builder()->issuedBy('invalid')->getToken( $this->moduleConfigStub->getSigner(), - InMemory::plainText(self::$privateKey->getKeyContents()) + InMemory::plainText(self::$privateKey->getKeyContents()), )->toString(); $this->requestStub->method('getQueryParams')->willReturn(['id_token_hint' => $invalidIssuerJwt]); @@ -166,7 +166,7 @@ public function testCheckRulePassesForValidIdToken(): void $idToken = $this->jwtConfig->builder()->issuedBy(self::$issuer)->getToken( $this->moduleConfigStub->getSigner(), - InMemory::plainText(self::$privateKey->getKeyContents()) + InMemory::plainText(self::$privateKey->getKeyContents()), )->toString(); $this->requestStub->method('getQueryParams')->willReturn(['id_token_hint' => $idToken]); diff --git a/tests/src/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php b/tests/src/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php index 145390e0..a0591f47 100644 --- a/tests/src/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php +++ b/tests/src/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php @@ -69,7 +69,7 @@ protected function setUp(): void $this->jwtConfig = Configuration::forAsymmetricSigner( new Sha256(), InMemory::plainText(self::$privateKey->getKeyContents()), - InMemory::plainText(self::$publicKey->getKeyContents()) + InMemory::plainText(self::$publicKey->getKeyContents()), ); $this->loggerServiceStub = $this->createStub(LoggerService::class); @@ -114,13 +114,13 @@ public function testCheckRuleThrowsWhenAudClaimNotValid(): void $jwt = $this->jwtConfig->builder()->issuedBy(self::$issuer) ->getToken( new Sha256(), - InMemory::plainText(self::$privateKey->getKeyContents()) + InMemory::plainText(self::$privateKey->getKeyContents()), ); $this->resultBagStub->method('getOrFail')->willReturnOnConsecutiveCalls( new Result(StateRule::class), - new Result(IdTokenHintRule::class, $jwt) + new Result(IdTokenHintRule::class, $jwt), ); $this->expectException(Throwable::class); @@ -143,14 +143,14 @@ public function testCheckRuleThrowsWhenClientNotFound(): void ->permittedFor('invalid-client-id') ->getToken( new Sha256(), - InMemory::plainText(self::$privateKey->getKeyContents()) + InMemory::plainText(self::$privateKey->getKeyContents()), ); $this->clientRepositoryStub->method('findById')->willReturn(null); $this->resultBagStub->method('getOrFail')->willReturnOnConsecutiveCalls( new Result(StateRule::class), - new Result(IdTokenHintRule::class, $jwt) + new Result(IdTokenHintRule::class, $jwt), ); $this->expectException(Throwable::class); @@ -173,18 +173,18 @@ public function testCheckRuleThrowsWhenPostLogoutRegisteredUriNotRegistered(): v ->permittedFor('client-id') ->getToken( new Sha256(), - InMemory::plainText(self::$privateKey->getKeyContents()) + InMemory::plainText(self::$privateKey->getKeyContents()), ); $this->clientStub->method('getPostLogoutRedirectUri')->willReturn([ - 'https://some-other-uri' - ]); + 'https://some-other-uri', + ]); $this->clientRepositoryStub->method('findById')->willReturn($this->clientStub); $this->resultBagStub->method('getOrFail')->willReturnOnConsecutiveCalls( new Result(StateRule::class), - new Result(IdTokenHintRule::class, $jwt) + new Result(IdTokenHintRule::class, $jwt), ); $this->expectException(Throwable::class); @@ -208,18 +208,18 @@ public function testCheckRuleReturnsForRegisteredPostLogoutRedirectUri(): void ->permittedFor('client-id') ->getToken( new Sha256(), - InMemory::plainText(self::$privateKey->getKeyContents()) + InMemory::plainText(self::$privateKey->getKeyContents()), ); $this->clientStub->method('getPostLogoutRedirectUri')->willReturn([ - self::$postLogoutRedirectUri - ]); + self::$postLogoutRedirectUri, + ]); $this->clientRepositoryStub->method('findById')->willReturn($this->clientStub); $this->resultBagStub->method('getOrFail')->willReturnOnConsecutiveCalls( new Result(StateRule::class), - new Result(IdTokenHintRule::class, $jwt) + new Result(IdTokenHintRule::class, $jwt), ); $result = (new PostLogoutRedirectUriRule($this->clientRepositoryStub)) diff --git a/tests/src/Utils/Checker/Rules/RequestedClaimsRuleTest.php b/tests/src/Utils/Checker/Rules/RequestedClaimsRuleTest.php index 7b2218b3..681c8072 100644 --- a/tests/src/Utils/Checker/Rules/RequestedClaimsRuleTest.php +++ b/tests/src/Utils/Checker/Rules/RequestedClaimsRuleTest.php @@ -63,17 +63,17 @@ public function testWithClaims(): void "name" => null, "email" => [ "essential" => true, - "extras_stuff_not_in_spec" => "should be ignored" - ] + "extras_stuff_not_in_spec" => "should be ignored", + ], ], "id_token" => [ 'name' => [ - "essential" => true - ] + "essential" => true, + ], ], "additional_stuff" => [ - "should be ignored" - ] + "should be ignored", + ], ]; $requestedClaims = $expectedClaims; // Add some claims the client is not authorized for @@ -81,8 +81,8 @@ public function testWithClaims(): void $requestedClaims['id_token']['secret_password'] = null; $this->requestStub->method('getQueryParams')->willReturn([ 'claims' => json_encode($requestedClaims), - 'client_id' => 'abc' - ]); + 'client_id' => 'abc', + ]); $rule = new RequestedClaimsRule(new ClaimTranslatorExtractor(self::$userIdAttr)); $result = $rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); @@ -98,14 +98,14 @@ public function testOnlyWithNonStandardClaimRequest(): void { $expectedClaims = [ "additional_stuff" => [ - "should be ignored" - ] + "should be ignored", + ], ]; $requestedClaims = $expectedClaims; $this->requestStub->method('getQueryParams')->willReturn([ - 'claims' => json_encode($requestedClaims), - 'client_id' => 'abc' - ]); + 'claims' => json_encode($requestedClaims), + 'client_id' => 'abc', + ]); $rule = new RequestedClaimsRule(new ClaimTranslatorExtractor(self::$userIdAttr)); $result = $rule->checkRule($this->requestStub, $this->resultBag, $this->loggerServiceStub); diff --git a/tests/src/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php b/tests/src/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php index cf56ff28..b4a55ebe 100644 --- a/tests/src/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php +++ b/tests/src/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php @@ -70,7 +70,7 @@ public function testCanCreateInstance(): void { $this->assertInstanceOf( ScopeOfflineAccessRule::class, - new ScopeOfflineAccessRule() + new ScopeOfflineAccessRule(), ); } @@ -90,7 +90,7 @@ public function testReturnsFalseWhenOfflineAccessScopeNotPresent(): void $this->redirectUriResultStub, $this->stateResultStub, $this->clientResultStub, - $this->validScopesResultStub + $this->validScopesResultStub, ); $this->openIdConfigurationStub->method('getBoolean')->willReturn(false); @@ -120,7 +120,7 @@ public function testThrowsWhenClientDoesntHaveOfflineAccessScopeRegistered(): vo $this->redirectUriResultStub, $this->stateResultStub, $this->clientResultStub, - $this->validScopesResultStub + $this->validScopesResultStub, ); $this->openIdConfigurationStub->method('getBoolean')->willReturn(false); @@ -150,7 +150,7 @@ public function testReturnsTrueWhenClientDoesHaveOfflineAccessScopeRegistered(): $this->redirectUriResultStub, $this->stateResultStub, $this->clientResultStub, - $this->validScopesResultStub + $this->validScopesResultStub, ); $this->openIdConfigurationStub->method('getBoolean')->willReturn(false); diff --git a/tests/src/Utils/Checker/Rules/ScopeRuleTest.php b/tests/src/Utils/Checker/Rules/ScopeRuleTest.php index 134a74ec..25d5c699 100644 --- a/tests/src/Utils/Checker/Rules/ScopeRuleTest.php +++ b/tests/src/Utils/Checker/Rules/ScopeRuleTest.php @@ -105,8 +105,8 @@ public function testValidScopes(): void ->willReturn( $this->onConsecutiveCalls( $this->scopeEntities['openid'], - $this->scopeEntities['profile'] - ) + $this->scopeEntities['profile'], + ), ); $result = (new ScopeRule($this->scopeRepositoryStub)) @@ -128,8 +128,8 @@ public function testInvalidScopeThrows(): void ->method('getScopeEntityByIdentifier') ->willReturn( $this->onConsecutiveCalls( - 'invalid-scope-entity' - ) + 'invalid-scope-entity', + ), ); $this->expectException(OidcServerException::class); @@ -152,8 +152,8 @@ protected function prepareValidScopeRepositoryStub(): InvocationStubber ->willReturn( $this->onConsecutiveCalls( $this->scopeEntities['openid'], - $this->scopeEntities['profile'] - ) + $this->scopeEntities['profile'], + ), ); } } diff --git a/tests/src/Utils/ClaimTranslatorExtractorTest.php b/tests/src/Utils/ClaimTranslatorExtractorTest.php index 536d7317..1a2e9ab9 100644 --- a/tests/src/Utils/ClaimTranslatorExtractorTest.php +++ b/tests/src/Utils/ClaimTranslatorExtractorTest.php @@ -26,39 +26,39 @@ public function testTypeConversion(): void $claimSet = new ClaimSetEntity( 'typeConversion', [ - 'intClaim', - 'boolClaim1', - 'boolClaimYes', - 'boolClaimTrue', - 'boolClaimOther', - 'defaultClaim', - 'stringClaim', - 'jsonClaim', - //Test oid style claim names is not interpreted as a type of 'urn' - 'urn:oid:2.5.4.3' - ] + 'intClaim', + 'boolClaim1', + 'boolClaimYes', + 'boolClaimTrue', + 'boolClaimOther', + 'defaultClaim', + 'stringClaim', + 'jsonClaim', + // Test oid style claim names is not interpreted as a type of 'urn' + 'urn:oid:2.5.4.3', + ], ); $translate = [ 'intClaim' => [ 'type' => 'int', - 'attributes' => ['intAttribute'] + 'attributes' => ['intAttribute'], ], 'boolClaim1' => [ 'type' => 'bool', - 'attributes' => ['boolAttribute1'] + 'attributes' => ['boolAttribute1'], ], 'boolClaimYes' => [ 'type' => 'bool', - 'attributes' => ['boolAttributeYes'] + 'attributes' => ['boolAttributeYes'], ] , 'boolClaimTrue' => [ 'type' => 'bool', - 'attributes' => ['boolAttributeTrue'] + 'attributes' => ['boolAttributeTrue'], ], 'boolClaimOther' => [ 'type' => 'bool', - 'attributes' => ['boolAttributeOther'] + 'attributes' => ['boolAttributeOther'], ], 'defaultClaim' => ['stringAttribute'], 'stringClaim' => ['type' => 'string', 'attributes' => ['stringAttribute']], @@ -67,16 +67,16 @@ public function testTypeConversion(): void 'claims' => [ 'subIntClaim' => [ 'type' => 'int', - 'attributes' => ['intAttribute'] + 'attributes' => ['intAttribute'], ], 'subBoolClaim' => [ 'type' => 'bool', - 'attributes' => ['boolAttribute1'] + 'attributes' => ['boolAttribute1'], ], 'subStringClaim' => ['stringAttribute'], - ] + ], ], - 'urn:oid:2.5.4.3' => ['stringAttribute'] + 'urn:oid:2.5.4.3' => ['stringAttribute'], ]; $userAttributes = (new Attributes())->normalizeAttributesArray( [ @@ -86,12 +86,12 @@ public function testTypeConversion(): void 'boolAttributeTrue' => 'true', 'boolAttributeOther' => 'anythingElseIsFalse', 'stringAttribute' => 'someString', - ] + ], ); $claimTranslator = new ClaimTranslatorExtractor(self::$userIdAttr, [$claimSet], $translate); $releasedClaims = $claimTranslator->extract( ['typeConversion'], - $userAttributes + $userAttributes, ); $expectedClaims = [ 'intClaim' => 7890, @@ -106,7 +106,7 @@ public function testTypeConversion(): void 'subBoolClaim' => true, 'subStringClaim' => 'someString', ], - 'urn:oid:2.5.4.3' => 'someString' + 'urn:oid:2.5.4.3' => 'someString', ]; $this->assertSame($expectedClaims, $releasedClaims); @@ -121,13 +121,13 @@ public function testDefaultTypeConversion(): void // Address is the only non-string attribute with a default saml source $userAttributes = (new Attributes())->normalizeAttributesArray( [ - 'postalAddress' => 'myAddress' - ] + 'postalAddress' => 'myAddress', + ], ); $claimTranslator = new ClaimTranslatorExtractor(self::$userIdAttr); $releasedClaims = $claimTranslator->extract( ['address'], - $userAttributes + $userAttributes, ); $expectedClaims = [ 'address' => [ @@ -147,22 +147,22 @@ public function testStandardClaimTypesCanBeSet(): void $translate = [ 'updated_at' => [ 'type' => 'int', - 'last_updated' + 'last_updated', ], 'email_verified' => [ 'type' => 'bool', - 'is_email_verified' + 'is_email_verified', ], 'phone_number_verified' => [ 'type' => 'bool', - 'is_phone_number_verified' + 'is_phone_number_verified', ], 'address' => [ 'type' => 'json', 'claims' => [ 'country' => ['country'], 'postal_code' => ['postal'], - ] + ], ], ]; $userAttributes = (new Attributes())->normalizeAttributesArray( @@ -173,17 +173,17 @@ public function testStandardClaimTypesCanBeSet(): void 'last_updated' => '12341', 'is_email_verified' => 'yes', 'is_phone_number_verified' => 'no', - ] + ], ); $claimTranslator = new ClaimTranslatorExtractor(self::$userIdAttr, [], $translate); $releasedClaims = $claimTranslator->extract( ['address', 'profile', 'email', 'phone'], - $userAttributes + $userAttributes, ); $expectedClaims = [ 'address' => [ 'country' => 'CA', - 'postal_code' => '93105' + 'postal_code' => '93105', ], 'updated_at' => 12341, 'email_verified' => true, @@ -203,7 +203,7 @@ public function testInvalidTypeConversion(): void $translate = [ 'testClaim' => [ 'type' => 'int', - 'testClaim' + 'testClaim', ], ]; $userAttributes = (new Attributes())->normalizeAttributesArray(['testClaim' => '7890F',]); @@ -219,8 +219,8 @@ public function testExtractRequestClaimsUserInfo(): void $claimTranslator = new ClaimTranslatorExtractor(self::$userIdAttr); $requestClaims = [ "userinfo" => [ - "name" => ['essential' => true] - ] + "name" => ['essential' => true], + ], ]; $claims = $claimTranslator->extractAdditionalUserInfoClaims($requestClaims, ['cn' => ['bob']]); @@ -235,8 +235,8 @@ public function testExtractRequestClaimsIdToken(): void $claimTranslator = new ClaimTranslatorExtractor(self::$userIdAttr); $requestClaims = [ "id_token" => [ - "name" => ['essential' => true] - ] + "name" => ['essential' => true], + ], ]; $claims = $claimTranslator->extractAdditionalIdTokenClaims($requestClaims, ['displayName' => ['bob']]); diff --git a/tests/src/Utils/ScopeHelperTest.php b/tests/src/Utils/ScopeHelperTest.php index 3636f4cb..83990170 100644 --- a/tests/src/Utils/ScopeHelperTest.php +++ b/tests/src/Utils/ScopeHelperTest.php @@ -31,7 +31,7 @@ protected function setUp(): void $this->scopeEntityProfileStub->method('getIdentifier')->willReturn('profile'); $this->scopeEntitiesArray = [ $this->scopeEntityOpenIdStub, - $this->scopeEntityProfileStub + $this->scopeEntityProfileStub, ]; } From 91c431cde2233008f8ccdc96ff0845c3749e199b Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Thu, 23 May 2024 10:14:14 +0200 Subject: [PATCH 015/130] Fix deprecated use of --- src/Services/DatabaseMigration.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Services/DatabaseMigration.php b/src/Services/DatabaseMigration.php index 990f7b2b..ffde144c 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 ,); From 4f14e0a7e148eeaef80a8d8a7c93263ac9522cb7 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Thu, 23 May 2024 10:21:32 +0200 Subject: [PATCH 016/130] Upgrade actions --- .github/workflows/test.yaml | 48 ++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c1c02764..3c800cc2 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -35,16 +35,16 @@ jobs: git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get composer cache directory id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$GITHUB_ENV" - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: - path: ${{ steps.composer-cache.outputs.dir }} + path: $COMPOSER_CACHE key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- @@ -66,7 +66,7 @@ jobs: - name: Save coverage data if: ${{ matrix.php-versions == '8.1' }} - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: build-data path: ${{ github.workspace }}/build @@ -86,16 +86,16 @@ jobs: - name: Setup problem matchers for PHP run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get composer cache directory id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$GITHUB_ENV" - name: Cache composer dependencies - uses: actions/cache@v1 + uses: actions/cache@v4 with: - path: ${{ steps.composer-cache.outputs.dir }} + path: $COMPOSER_CACHE key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- @@ -103,13 +103,13 @@ jobs: run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Security check for locked dependencies - uses: symfonycorp/security-checker-action@v3 + uses: symfonycorp/security-checker-action@v5 - name: Update Composer dependencies run: composer update --no-progress --prefer-dist --optimize-autoloader - name: Security check for updated dependencies - uses: symfonycorp/security-checker-action@v3 + uses: symfonycorp/security-checker-action@v5 sanity-check: name: Sanity checks @@ -127,16 +127,16 @@ jobs: - name: Setup problem matchers for PHP run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get composer cache directory id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$GITHUB_ENV" - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: - path: ${{ steps.composer-cache.outputs.dir }} + path: $COMPOSER_CACHE key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- @@ -159,29 +159,33 @@ jobs: - name: Setup problem matchers for PHP run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get composer cache directory id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$GITHUB_ENV" - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: - path: ${{ steps.composer-cache.outputs.dir }} + path: $COMPOSER_CACHE key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install Composer dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: build-data path: ${{ github.workspace }}/build - name: Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true + verbose: true - name: PHP Code Sniffer if: always() @@ -197,7 +201,7 @@ jobs: SUITE_BASE_URL: https://localhost.emobix.co.uk:8443 VERSION: release-v4.1.45 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: path: main - name: Setup Python Dependencies 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 017/130] 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 018/130] 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 019/130] 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 020/130] 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 021/130] 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 022/130] 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 683f9d5273edbecabc6ff4c30c1fe4b2386cca2e Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 29 May 2024 18:38:21 +0200 Subject: [PATCH 023/130] Raise key-size from 2048 > 3072 Dutch government standards deem 2048 'okayish' and 3072+ 'good' .. Feel free to decline this PR, but I just thought it was time to raise the bar a bit. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c24c49a3..30c3a45e 100644 --- a/README.md +++ b/README.md @@ -61,11 +61,11 @@ Because of the signing part, you need to create a public/private RSA key pair. To generate the private key, you can run this command in the terminal: - openssl genrsa -out cert/oidc_module.key 2048 + openssl genrsa -out cert/oidc_module.key 3072 If you want to provide a passphrase for your private key, run this command instead: - openssl genrsa -passout pass:myPassPhrase -out cert/oidc_module.key 2048 + openssl genrsa -passout pass:myPassPhrase -out cert/oidc_module.key 3072 Now you need to extract the public key from the private key: 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 024/130] 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 058/130] 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 059/130] 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 060/130] 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 061/130] 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 062/130] 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 063/130] 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 064/130] 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 065/130] 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 066/130] 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 067/130] 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 068/130] 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 069/130] 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 c568af5b48fd00a3cbc2584e52135c7aa6088da2 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Fri, 31 Jan 2025 10:29:32 +0100 Subject: [PATCH 070/130] Make name of the identifier attribute set in config visible in the error message (#272) --- src/Services/AuthenticationService.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Services/AuthenticationService.php b/src/Services/AuthenticationService.php index 5e579117..04dbe5d8 100644 --- a/src/Services/AuthenticationService.php +++ b/src/Services/AuthenticationService.php @@ -105,7 +105,12 @@ public function getAuthenticateUser( if (!array_key_exists($this->userIdAttr, $claims) || !is_array($claims[$this->userIdAttr])) { $attr = implode(', ', array_keys($claims)); throw new Error\Exception( - 'Attribute `useridattr` doesn\'t exists in claims. Available attributes are: ' . $attr, + sprintf( + 'User identifier attribute `%s` does not exist in the user attribute state.' . + ' Available attributes are: %s.', + $this->userIdAttr, + $attr, + ), ); } From 4e6bdaa6ac941354a4c691dbe94b595e97be2aff Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Wed, 5 Feb 2025 10:33:50 +0100 Subject: [PATCH 071/130] 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 072/130] 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 073/130] 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 074/130] 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 075/130] 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 076/130] 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 077/130] 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 078/130] 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 079/130] 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 080/130] 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 081/130] 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 082/130] 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 083/130] 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 084/130] 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 085/130] 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 086/130] 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 087/130] 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 088/130] 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 089/130] 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 090/130] 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 091/130] 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 092/130] 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), + ); + } +} From ba2555842aaa28bd6788d5b5bebfb05dadd0c431 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 26 Feb 2025 11:11:06 +0100 Subject: [PATCH 093/130] Make host part of redirect_uri regex check optional (#293) * Fix redirect_uri regex * Add unit test for redirect_uri validation --------- Co-authored-by: Marko Ivancic --- src/Forms/ClientForm.php | 4 +++- tests/unit/src/Forms/ClientFormTest.php | 32 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/Forms/ClientForm.php b/src/Forms/ClientForm.php index 71844403..1b59c65d 100644 --- a/src/Forms/ClientForm.php +++ b/src/Forms/ClientForm.php @@ -34,8 +34,10 @@ class ClientForm extends Form /** * RFC3986. AppendixB. Parsing a URI Reference with a Regular Expression. + * From v6.*, the regex was modified to allow URI without host, to support adding entries like + * `openid-credential-offer://` */ - final public const REGEX_URI = '/^[^:]+:\/\/?[^\s\/$.?#].[^\s]*$/'; + final public const REGEX_URI = '/^[^:]+:\/\/?([^\s\/$.?#].[^\s]*)?$/'; /** * Must have http:// or https:// scheme, and at least one 'domain.top-level-domain' pair, or more subdomains. diff --git a/tests/unit/src/Forms/ClientFormTest.php b/tests/unit/src/Forms/ClientFormTest.php index e8f1f944..1134c0b3 100644 --- a/tests/unit/src/Forms/ClientFormTest.php +++ b/tests/unit/src/Forms/ClientFormTest.php @@ -171,4 +171,36 @@ public function testSetDefaultsUnsetsAuthSourceIfNotValid(): void $this->assertNull($sut->getValues()['auth_source']); } + + public static function redirectUriProvider(): array + { + return [ + ['https', false], + ['https:', false], + ['example', false], + ['example.com', false], + ['example.com/?foo=bar', false], + ['www.example.com/?foo=bar', false], + ['https://example', true], + ['https://example.com', true], + ['https://example.com/', true], + ['https://example.com/foo', true], + ['https://example.com/foo?bar=1', true], + + // To support OID4VCI + ['openid-credential-offer://', true], + ['foo://', true], + ['https://', true], + ]; + } + + #[DataProvider('redirectUriProvider')] + public function testCanValidateRedirectUri(string $url, bool $isValid): void + { + $sut = $this->sut(); + $sut->setValues(['redirect_uri' => $url]); + $sut->validateRedirectUri($sut); + + $this->assertEquals(!$isValid, $sut->hasErrors(), $url); + } } From 37cba1363276deacac9675ebcce99a5c65289301 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Mon, 10 Mar 2025 12:54:35 +0100 Subject: [PATCH 094/130] Move to trust_mark_id claim (#294) --- src/Controllers/Federation/EntityStatementController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Controllers/Federation/EntityStatementController.php b/src/Controllers/Federation/EntityStatementController.php index cf7cee86..44021bf7 100644 --- a/src/Controllers/Federation/EntityStatementController.php +++ b/src/Controllers/Federation/EntityStatementController.php @@ -136,12 +136,12 @@ public function configuration(): Response if ($trustMarkEntity->getSubject() !== $this->moduleConfig->getIssuer()) { throw OidcServerException::serverError(sprintf( 'Trust Mark %s is not intended for this entity.', - $trustMarkEntity->getIdentifier(), + $trustMarkEntity->getTrustMarkId(), )); } return [ - ClaimsEnum::Id->value => $trustMarkEntity->getIdentifier(), + ClaimsEnum::Id->value => $trustMarkEntity->getTrustMarkId(), ClaimsEnum::TrustMark->value => $token, ]; }, $trustMarkTokens); From 5198a51b286defcd83c3bb583792833c80b6ff31 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Thu, 13 Mar 2025 11:39:13 +0100 Subject: [PATCH 095/130] Start using trust_mark_id in federation entity configuration JWS (#295) --- src/Controllers/Federation/EntityStatementController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controllers/Federation/EntityStatementController.php b/src/Controllers/Federation/EntityStatementController.php index 44021bf7..d67e413e 100644 --- a/src/Controllers/Federation/EntityStatementController.php +++ b/src/Controllers/Federation/EntityStatementController.php @@ -141,7 +141,7 @@ public function configuration(): Response } return [ - ClaimsEnum::Id->value => $trustMarkEntity->getTrustMarkId(), + ClaimsEnum::TrustMarkId->value => $trustMarkEntity->getTrustMarkId(), ClaimsEnum::TrustMark->value => $token, ]; }, $trustMarkTokens); From e48921e0cd71518af5dc59374ce6e8bded31bb6e Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Thu, 13 Mar 2025 11:58:22 +0100 Subject: [PATCH 096/130] Bump cirrusid/simplesamlphp to v2.3.7 (#296) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 46543010..6bd877ea 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM cirrusid/simplesamlphp:v2.3.5 +FROM cirrusid/simplesamlphp:v2.3.7 #FROM cicnavi/simplesamlphp:dev RUN apt-get update && apt-get install -y sqlite3 From 1bab69dc2aca0961eadf96c42b098dc4f624627e Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Fri, 28 Mar 2025 16:28:38 +0100 Subject: [PATCH 097/130] Enable showing supported claims in OP discovery (#299) --- UPGRADE.md | 3 +- config/module_oidc.php.dist | 8 +++- src/ModuleConfig.php | 9 +++++ src/Services/Container.php | 12 +++--- src/Services/OpMetadataService.php | 7 ++++ src/Utils/ClaimTranslatorExtractor.php | 19 ++++++--- tests/unit/src/ModuleConfigTest.php | 11 ++++++ .../src/Services/OpMetadataServiceTest.php | 39 ++++++++++++++++--- .../Utils/ClaimTranslatorExtractorTest.php | 20 ++++++++++ 9 files changed, 110 insertions(+), 18 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 9cc95e34..0484b251 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -35,7 +35,8 @@ https://openid.net/specs/openid-connect-core-1_0.html#RequestObject https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication ## New configuration options - +- (from v6.1) Show `claims_supported` claim in OP Discovery endpoint - you can now choose to show supported claims, +as is recommended by OpenID Connect Discovery specification https://openid.net/specs/openid-connect-discovery-1_0.html. - (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) Protocol caching adapter and its arguments diff --git a/config/module_oidc.php.dist b/config/module_oidc.php.dist index caea6492..b1ca0118 100644 --- a/config/module_oidc.php.dist +++ b/config/module_oidc.php.dist @@ -78,7 +78,8 @@ $config = [ // to use this attribute as the 'sub' claim since it designates unique identifier for the user). ModuleConfig::OPTION_AUTH_USER_IDENTIFIER_ATTRIBUTE => 'uid', - // The default translate table from SAML attributes to OIDC claims. + // The default translate table from SAML attributes to OIDC claims. If you don't want to support specific default + // claim, set it to an empty array. ModuleConfig::OPTION_AUTH_SAML_TO_OIDC_TRANSLATE_TABLE => [ /* * The basic format is @@ -243,6 +244,11 @@ $config = [ // ModuleConfig::OPTION_AUTH_FORCED_ACR_VALUE_FOR_COOKIE_AUTHENTICATION => '0', ModuleConfig::OPTION_AUTH_FORCED_ACR_VALUE_FOR_COOKIE_AUTHENTICATION => null, + // Choose if OP discovery document will include 'claims_supported' claim, which is recommended per OpenID Connect + // Discovery specification https://openid.net/specs/openid-connect-discovery-1_0.html. The list will include all + // claims for which "SAML attribute to OIDC claim translation" has been defined above. + ModuleConfig::OPTION_PROTOCOL_DISCOVERY_SHOW_CLAIMS_SUPPORTED => false, + // Settings regarding Authentication Processing Filters. // Note: OIDC authN state array will not contain all the keys which are available during SAML authN, // like Service Provider metadata, etc. diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php index 1e50289c..8f89fcaa 100644 --- a/src/ModuleConfig.php +++ b/src/ModuleConfig.php @@ -81,6 +81,7 @@ class ModuleConfig 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_PROTOCOL_DISCOVERY_SHOW_CLAIMS_SUPPORTED = 'protocol_discover_show_claims_supported'; final public const OPTION_FEDERATION_PARTICIPATION_LIMIT_BY_TRUST_MARKS = 'federation_participation_limit_by_trust_marks'; @@ -504,6 +505,14 @@ public function getProtocolClientEntityCacheDuration(): DateInterval ); } + public function getProtocolDiscoveryShowClaimsSupported(): bool + { + return $this->config()->getOptionalBoolean( + self::OPTION_PROTOCOL_DISCOVERY_SHOW_CLAIMS_SUPPORTED, + false, + ); + } + /***************************************************************************************************************** * OpenID Federation related config. diff --git a/src/Services/Container.php b/src/Services/Container.php index c1d5bf4b..3570fbad 100644 --- a/src/Services/Container.php +++ b/src/Services/Container.php @@ -189,12 +189,6 @@ public function __construct() ); $this->services[TemplateFactory::class] = $templateFactory; - $opMetadataService = new OpMetadataService($moduleConfig); - $this->services[OpMetadataService::class] = $opMetadataService; - - $metadataStorageHandler = MetaDataStorageHandler::getMetadataHandler(); - $this->services[MetaDataStorageHandler::class] = $metadataStorageHandler; - $claimSetEntityFactory = new ClaimSetEntityFactory(); $this->services[ClaimSetEntityFactory::class] = $claimSetEntityFactory; @@ -204,6 +198,12 @@ public function __construct() ))->build(); $this->services[ClaimTranslatorExtractor::class] = $claimTranslatorExtractor; + $opMetadataService = new OpMetadataService($moduleConfig, $claimTranslatorExtractor); + $this->services[OpMetadataService::class] = $opMetadataService; + + $metadataStorageHandler = MetaDataStorageHandler::getMetadataHandler(); + $this->services[MetaDataStorageHandler::class] = $metadataStorageHandler; + $processingChainFactory = new ProcessingChainFactory($moduleConfig); $this->services[ProcessingChainFactory::class] = $processingChainFactory; diff --git a/src/Services/OpMetadataService.php b/src/Services/OpMetadataService.php index 73b3ece7..921724bb 100644 --- a/src/Services/OpMetadataService.php +++ b/src/Services/OpMetadataService.php @@ -6,6 +6,7 @@ use SimpleSAML\Module\oidc\Codebooks\RoutesEnum; use SimpleSAML\Module\oidc\ModuleConfig; +use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; use SimpleSAML\OpenID\Codebooks\ClaimsEnum; use SimpleSAML\OpenID\Codebooks\TokenEndpointAuthMethodsEnum; @@ -24,6 +25,7 @@ class OpMetadataService */ public function __construct( private readonly ModuleConfig $moduleConfig, + private readonly ClaimTranslatorExtractor $claimTranslatorExtractor, ) { $this->initMetadata(); } @@ -75,6 +77,11 @@ private function initMetadata(): void } $this->metadata[ClaimsEnum::BackChannelLogoutSupported->value] = true; $this->metadata[ClaimsEnum::BackChannelLogoutSessionSupported->value] = true; + + if ($this->moduleConfig->getProtocolDiscoveryShowClaimsSupported()) { + $claimsSupported = $this->claimTranslatorExtractor->getSupportedClaims(); + $this->metadata[ClaimsEnum::ClaimsSupported->value] = $claimsSupported; + } } /** diff --git a/src/Utils/ClaimTranslatorExtractor.php b/src/Utils/ClaimTranslatorExtractor.php index 83e7ba57..599a7b04 100644 --- a/src/Utils/ClaimTranslatorExtractor.php +++ b/src/Utils/ClaimTranslatorExtractor.php @@ -35,7 +35,7 @@ class ClaimTranslatorExtractor protected array $claimSets = []; /** @var string[] */ - protected array $protectedClaims = ['openid', 'profile', 'email', 'address', 'phone']; + protected array $protectedScopes = ['openid', 'profile', 'email', 'address', 'phone']; protected array $translationTable = [ 'sub' => [ @@ -85,13 +85,13 @@ class ClaimTranslatorExtractor 'preferredLanguage', ], 'updated_at' => [ - 'type' => 'int', +// 'type' => 'int', ], 'email' => [ 'mail', ], 'email_verified' => [ - 'type' => 'bool', +// 'type' => 'bool', ], 'address' => [ 'type' => 'json', @@ -105,7 +105,7 @@ class ClaimTranslatorExtractor 'homePhone', ], 'phone_number_verified' => [ - 'type' => 'bool', +// 'type' => 'bool', // Empty ], ]; @@ -198,7 +198,7 @@ public function addClaimSet(ClaimSetEntityInterface $claimSet): self { $scope = $claimSet->getScope(); - if (in_array($scope, $this->protectedClaims, true) && isset($this->claimSets[$scope])) { + if (in_array($scope, $this->protectedScopes, true) && isset($this->claimSets[$scope])) { throw OidcServerException::serverError( sprintf("%s is a protected scope and is pre-defined by the OpenID Connect specification.", $scope), ); @@ -352,4 +352,13 @@ private function extractAdditionalClaims(array $requestedClaims, array $claims): ARRAY_FILTER_USE_KEY, ); } + + /** + * Get supported claims for this OP. This will return all the claims for which the "SAML attribute to OIDC claim + * translation" has been defined in module config, meaning it is expected for OP to release those claims. + */ + public function getSupportedClaims(): array + { + return array_keys(array_filter($this->translationTable)); + } } diff --git a/tests/unit/src/ModuleConfigTest.php b/tests/unit/src/ModuleConfigTest.php index f13c2d41..22faea7d 100644 --- a/tests/unit/src/ModuleConfigTest.php +++ b/tests/unit/src/ModuleConfigTest.php @@ -372,4 +372,15 @@ public function testCanGetProtocolCacheConfiguration(): void $this->assertInstanceOf(DateInterval::class, $this->sut()->getProtocolUserEntityCacheDuration()); $this->assertInstanceOf(DateInterval::class, $this->sut()->getProtocolClientEntityCacheDuration()); } + + public function testCanGetProtocolDiscoveryShowClaimsSupported(): void + { + $this->assertFalse($this->sut()->getProtocolDiscoveryShowClaimsSupported()); + $this->assertTrue( + $this->sut( + null, + [ModuleConfig::OPTION_PROTOCOL_DISCOVERY_SHOW_CLAIMS_SUPPORTED => true], + )->getProtocolDiscoveryShowClaimsSupported(), + ); + } } diff --git a/tests/unit/src/Services/OpMetadataServiceTest.php b/tests/unit/src/Services/OpMetadataServiceTest.php index c56aa882..43ce4fa5 100644 --- a/tests/unit/src/Services/OpMetadataServiceTest.php +++ b/tests/unit/src/Services/OpMetadataServiceTest.php @@ -10,6 +10,8 @@ use SimpleSAML\Module\oidc\Codebooks\RoutesEnum; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Services\OpMetadataService; +use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor; +use SimpleSAML\OpenID\Codebooks\ClaimsEnum; /** * @covers \SimpleSAML\Module\oidc\Services\OpMetadataService @@ -17,6 +19,7 @@ class OpMetadataServiceTest extends TestCase { protected MockObject $moduleConfigMock; + protected MockObject $claimTranslatorExtractorMock; /** * @throws \Exception @@ -46,14 +49,24 @@ public function setUp(): void $signer = $this->createMock(Rsa::class); $signer->method('algorithmId')->willReturn('RS256'); $this->moduleConfigMock->method('getProtocolSigner')->willReturn($signer); + + $this->claimTranslatorExtractorMock = $this->createMock(ClaimTranslatorExtractor::class); } /** * @throws \Exception */ - protected function prepareMockedInstance(): OpMetadataService - { - return new OpMetadataService($this->moduleConfigMock); + protected function sut( + ?ModuleConfig $moduleConfig = null, + ?ClaimTranslatorExtractor $claimTranslatorExtractor = null, + ): OpMetadataService { + $moduleConfig = $moduleConfig ?? $this->moduleConfigMock; + $claimTranslatorExtractor = $claimTranslatorExtractor ?? $this->claimTranslatorExtractorMock; + + return new OpMetadataService( + $moduleConfig, + $claimTranslatorExtractor, + ); } /** @@ -63,7 +76,7 @@ public function testItIsInitializable(): void { $this->assertInstanceOf( OpMetadataService::class, - $this->prepareMockedInstance(), + $this->sut(), ); } @@ -102,7 +115,23 @@ public function testItReturnsExpectedMetadata(): void 'backchannel_logout_supported' => true, 'backchannel_logout_session_supported' => true, ], - $this->prepareMockedInstance()->getMetadata(), + $this->sut()->getMetadata(), + ); + } + + public function testCanShowClaimsSupportedClaim(): void + { + $this->moduleConfigMock->method('getProtocolDiscoveryShowClaimsSupported')->willReturn(true); + $this->claimTranslatorExtractorMock->method('getSupportedClaims')->willReturn(['sample']); + + $sut = $this->sut(); + $this->assertArrayHasKey( + ClaimsEnum::ClaimsSupported->value, + $sut->getMetadata(), + ); + + $this->assertTrue( + in_array('sample', $sut->getMetadata()[ClaimsEnum::ClaimsSupported->value], true), ); } } diff --git a/tests/unit/src/Utils/ClaimTranslatorExtractorTest.php b/tests/unit/src/Utils/ClaimTranslatorExtractorTest.php index 9204479b..f5d4cba5 100644 --- a/tests/unit/src/Utils/ClaimTranslatorExtractorTest.php +++ b/tests/unit/src/Utils/ClaimTranslatorExtractorTest.php @@ -272,4 +272,24 @@ public function testExtractRequestClaimsIdToken(): void $claims = $claimTranslator->extractAdditionalIdTokenClaims($requestClaims, ['displayName' => ['bob']]); $this->assertEquals(['name' => 'bob'], $claims); } + + public function testCanGetSupportedClaims(): void + { + $translate = [ + 'custom' => [ + 'type' => 'int', + 'custom_attr', + ], + ]; + + $this->assertTrue(in_array('custom', $this->mock([], $translate)->getSupportedClaims(), true)); + } + + public function testCanUnsetClaimWhichIsSupportedByDefault(): void + { + $this->assertTrue(in_array('nickname', $this->mock()->getSupportedClaims(), true)); + + $translate = ['nickname' => []]; + $this->assertFalse(in_array('nickname', $this->mock([], $translate)->getSupportedClaims(), true)); + } } From bc836b5103e0e1c8d3eb8e8a34ce08afb1892809 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Tue, 1 Apr 2025 11:32:35 +0200 Subject: [PATCH 098/130] Enable dynamic fetching of own Trust Marks (#300) * Add initial dynamic Trust Mark fetch capabilities * Add coverage --- UPGRADE.md | 1 + config/module_oidc.php.dist | 11 ++- src/Controllers/Admin/ConfigController.php | 19 ++++- .../Federation/EntityStatementController.php | 42 ++++++++++ src/ModuleConfig.php | 11 +++ templates/config/federation.twig | 2 +- .../Admin/ConfigControllerTest.php | 29 +++++++ .../EntityStatementControllerTest.php | 6 ++ tests/unit/src/ModuleConfigTest.php | 81 ++++++++++++++++++- 9 files changed, 197 insertions(+), 5 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 0484b251..e3443186 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -47,6 +47,7 @@ and optionally a port (as in all previous module versions). - 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 + - (from v6.1) own Trust Marks to dynamically fetch - signer algorithm - entity statement duration - organization name diff --git a/config/module_oidc.php.dist b/config/module_oidc.php.dist index b1ca0118..8c56930b 100644 --- a/config/module_oidc.php.dist +++ b/config/module_oidc.php.dist @@ -369,11 +369,20 @@ $config = [ ], // (optional) Federation Trust Mark tokens. An array of tokens (signed JWTs), each representing a Trust Mark - // issued to this entity. + // issued to this entity. This option is primarily intended for long-lasting or non-expiring tokens, so it + // is not necessary to dynamically fetch / refresh them. ModuleConfig::OPTION_FEDERATION_TRUST_MARK_TOKENS => [ // 'eyJ...GHg', ], + // (optional) Federation Trust Marks for dynamic fetching. An array of key-value pairs, where key is Trust Mark ID + // and value is Trust Mark Issuer ID, each representing a Trust Mark issued to this entity. Each Trust Mark ID + // in this array will be dynamically fetched from noted Trust Mark Issuer as necessary. If federation caching + // is enabled (recommended), fetched Trust Marks will also be cached until their expiry. + ModuleConfig::OPTION_FEDERATION_DYNAMIC_TRUST_MARKS => [ +// 'trust-mark-id' => 'trust-mark-issuer-id', + ], + // (optional) Federation participation limit by Trust Marks. This is an array with the following format: // [ // 'trust-anchor-id' => [ diff --git a/src/Controllers/Admin/ConfigController.php b/src/Controllers/Admin/ConfigController.php index b8bf55b6..85e35a6c 100644 --- a/src/Controllers/Admin/ConfigController.php +++ b/src/Controllers/Admin/ConfigController.php @@ -68,7 +68,7 @@ public function protocolSettings(): Response public function federationSettings(): Response { - $trustMarks = null; + $trustMarks = []; if (is_array($trustMarkTokens = $this->moduleConfig->getFederationTrustMarkTokens())) { $trustMarks = array_map( function (string $token): Federation\TrustMark { @@ -78,6 +78,23 @@ function (string $token): Federation\TrustMark { ); } + if (is_array($dynamicTrustMarks = $this->moduleConfig->getFederationDynamicTrustMarks())) { + /** + * @var non-empty-string $trustMarkId + * @var non-empty-string $trustMarkIssuerId + */ + foreach ($dynamicTrustMarks as $trustMarkId => $trustMarkIssuerId) { + $trustMarkIssuerConfigurationStatement = $this->federation->entityStatementFetcher() + ->fromCacheOrWellKnownEndpoint($trustMarkIssuerId); + + $trustMarks[] = $this->federation->trustMarkFetcher()->fromCacheOrFederationTrustMarkEndpoint( + $trustMarkId, + $this->moduleConfig->getIssuer(), + $trustMarkIssuerConfigurationStatement, + ); + } + } + return $this->templateFactory->build( 'oidc:config/federation.twig', [ diff --git a/src/Controllers/Federation/EntityStatementController.php b/src/Controllers/Federation/EntityStatementController.php index d67e413e..105d8100 100644 --- a/src/Controllers/Federation/EntityStatementController.php +++ b/src/Controllers/Federation/EntityStatementController.php @@ -11,6 +11,7 @@ use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\JsonWebKeySetService; use SimpleSAML\Module\oidc\Services\JsonWebTokenBuilderService; +use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Services\OpMetadataService; use SimpleSAML\Module\oidc\Utils\FederationCache; use SimpleSAML\Module\oidc\Utils\Routes; @@ -42,6 +43,7 @@ public function __construct( private readonly Helpers $helpers, private readonly Routes $routes, private readonly Federation $federation, + private readonly LoggerService $loggerService, private readonly ?FederationCache $federationCache, ) { if (!$this->moduleConfig->getFederationEnabled()) { @@ -126,6 +128,8 @@ public function configuration(): Response $builder = $builder->withClaim(ClaimsEnum::AuthorityHints->value, $authorityHints); } + $trustMarks = []; + if ( is_array($trustMarkTokens = $this->moduleConfig->getFederationTrustMarkTokens()) && (!empty($trustMarkTokens)) @@ -145,7 +149,45 @@ public function configuration(): Response ClaimsEnum::TrustMark->value => $token, ]; }, $trustMarkTokens); + } + + if ( + is_array($dynamicTrustMarks = $this->moduleConfig->getFederationDynamicTrustMarks()) && + (!empty($dynamicTrustMarks)) + ) { + /** + * @var non-empty-string $trustMarkId + * @var non-empty-string $trustMarkIssuerId + */ + foreach ($dynamicTrustMarks as $trustMarkId => $trustMarkIssuerId) { + try { + $trustMarkIssuerConfigurationStatement = $this->federation->entityStatementFetcher() + ->fromCacheOrWellKnownEndpoint($trustMarkIssuerId); + + $trustMarkEntity = $this->federation->trustMarkFetcher()->fromCacheOrFederationTrustMarkEndpoint( + $trustMarkId, + $this->moduleConfig->getIssuer(), + $trustMarkIssuerConfigurationStatement, + ); + + $trustMarks[] = [ + ClaimsEnum::TrustMarkId->value => $trustMarkId, + ClaimsEnum::TrustMark->value => $trustMarkEntity->getToken(), + ]; + } catch (\Throwable $exception) { + $this->loggerService->error( + 'Error fetching Trust Mark: ' . $exception->getMessage(), + [ + 'trustMarkId' => $trustMarkId, + 'subjectId' => $this->moduleConfig->getIssuer(), + 'trustMarkIssuerId' => $trustMarkIssuerId, + ], + ); + } + } + } + if (!empty($trustMarks)) { $builder = $builder->withClaim(ClaimsEnum::TrustMarks->value, $trustMarks); } diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php index 8f89fcaa..4a9fe3b8 100644 --- a/src/ModuleConfig.php +++ b/src/ModuleConfig.php @@ -76,6 +76,7 @@ class ModuleConfig final public const OPTION_FEDERATION_CACHE_MAX_DURATION_FOR_FETCHED = 'federation_cache_max_duration_for_fetched'; final public const OPTION_FEDERATION_TRUST_ANCHORS = 'federation_trust_anchors'; final public const OPTION_FEDERATION_TRUST_MARK_TOKENS = 'federation_trust_mark_tokens'; + final public const OPTION_FEDERATION_DYNAMIC_TRUST_MARKS = 'federation_dynamic_trust_mark_tokens'; final public const OPTION_FEDERATION_CACHE_DURATION_FOR_PRODUCED = 'federation_cache_duration_for_produced'; final public const OPTION_PROTOCOL_CACHE_ADAPTER = 'protocol_cache_adapter'; final public const OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS = 'protocol_cache_adapter_arguments'; @@ -632,6 +633,16 @@ public function getFederationTrustMarkTokens(): ?array return empty($trustMarks) ? null : $trustMarks; } + public function getFederationDynamicTrustMarks(): ?array + { + $dynamicTrustMarks = $this->config()->getOptionalArray( + self::OPTION_FEDERATION_DYNAMIC_TRUST_MARKS, + null, + ); + + return empty($dynamicTrustMarks) ? null : $dynamicTrustMarks; + } + public function getOrganizationName(): ?string { return $this->config()->getOptionalString( diff --git a/templates/config/federation.twig b/templates/config/federation.twig index 8bb739ee..4b05424c 100644 --- a/templates/config/federation.twig +++ b/templates/config/federation.twig @@ -93,7 +93,7 @@ {% if trustMarks|default is not empty %} {% for trustMark in trustMarks %}

    - - {{ trustMark.getPayload.id }} + - {{ trustMark.getPayload.trust_mark_id }} {{- trustMark.getPayload|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}} diff --git a/tests/unit/src/Controllers/Admin/ConfigControllerTest.php b/tests/unit/src/Controllers/Admin/ConfigControllerTest.php index b648fd57..07c4a8d6 100644 --- a/tests/unit/src/Controllers/Admin/ConfigControllerTest.php +++ b/tests/unit/src/Controllers/Admin/ConfigControllerTest.php @@ -28,6 +28,8 @@ class ConfigControllerTest extends TestCase protected MockObject $federationMock; protected MockObject $routesMock; protected MockObject $trustMarkFactoryMock; + protected MockObject $entityStatementFetcherMock; + protected MockObject $trustMarkFetcherMock; protected function setUp(): void { @@ -41,6 +43,12 @@ protected function setUp(): void $this->trustMarkFactoryMock = $this->createMock(TrustMarkFactory::class); $this->federationMock->method('trustMarkFactory')->willReturn($this->trustMarkFactoryMock); + + $this->entityStatementFetcherMock = $this->createMock(Federation\EntityStatementFetcher::class); + $this->federationMock->method('entityStatementFetcher')->willReturn($this->entityStatementFetcherMock); + + $this->trustMarkFetcherMock = $this->createMock(Federation\TrustMarkFetcher::class); + $this->federationMock->method('trustMarkFetcher')->willReturn($this->trustMarkFetcherMock); } public function sut( @@ -129,4 +137,25 @@ public function testCanIncludeTrustMarksInFederationSettings(): void $this->sut()->federationSettings(); } + + public function testCanIncludeDynamicTrustMarksInFederationSettings(): void + { + $this->moduleConfigMock->method('getIssuer')->willReturn('issuer-id'); + $this->moduleConfigMock->method('getFederationDynamicTrustMarks') + ->willReturn(['trust-mark-id' => 'trust-mark-issuer-id']); + + $this->entityStatementFetcherMock->expects($this->once())->method('fromCacheOrWellKnownEndpoint') + ->with('trust-mark-issuer-id'); + + $this->trustMarkFetcherMock->expects($this->once())->method('fromCacheOrFederationTrustMarkEndpoint') + ->with( + 'trust-mark-id', + 'issuer-id', + ); + + $this->templateFactoryMock->expects($this->once())->method('build') + ->with('oidc:config/federation.twig'); + + $this->sut()->federationSettings(); + } } diff --git a/tests/unit/src/Controllers/Federation/EntityStatementControllerTest.php b/tests/unit/src/Controllers/Federation/EntityStatementControllerTest.php index e2170067..56a5e589 100644 --- a/tests/unit/src/Controllers/Federation/EntityStatementControllerTest.php +++ b/tests/unit/src/Controllers/Federation/EntityStatementControllerTest.php @@ -14,6 +14,7 @@ use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\JsonWebKeySetService; use SimpleSAML\Module\oidc\Services\JsonWebTokenBuilderService; +use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Services\OpMetadataService; use SimpleSAML\Module\oidc\Utils\FederationCache; use SimpleSAML\Module\oidc\Utils\Routes; @@ -30,6 +31,7 @@ class EntityStatementControllerTest extends TestCase protected MockObject $helpersMock; protected MockObject $routesMock; protected MockObject $federationMock; + protected MockObject $loggerServiceMock; protected MockObject $federationCacheMock; protected function setUp(): void @@ -42,6 +44,7 @@ protected function setUp(): void $this->helpersMock = $this->createMock(Helpers::class); $this->routesMock = $this->createMock(Routes::class); $this->federationMock = $this->createMock(Federation::class); + $this->loggerServiceMock = $this->createMock(LoggerService::class); $this->federationCacheMock = $this->createMock(FederationCache::class); } @@ -54,6 +57,7 @@ protected function sut( ?Helpers $helpers = null, ?Routes $routes = null, ?Federation $federation = null, + ?LoggerService $loggerService = null, ?FederationCache $federationCache = null, ): EntityStatementController { $moduleConfig ??= $this->moduleConfigMock; @@ -64,6 +68,7 @@ protected function sut( $helpers ??= $this->helpersMock; $routes ??= $this->routesMock; $federation ??= $this->federationMock; + $loggerService ??= $this->loggerServiceMock; $federationCache ??= $this->federationCacheMock; return new EntityStatementController( @@ -75,6 +80,7 @@ protected function sut( $helpers, $routes, $federation, + $loggerService, $federationCache, ); } diff --git a/tests/unit/src/ModuleConfigTest.php b/tests/unit/src/ModuleConfigTest.php index 22faea7d..f2991676 100644 --- a/tests/unit/src/ModuleConfigTest.php +++ b/tests/unit/src/ModuleConfigTest.php @@ -378,9 +378,86 @@ public function testCanGetProtocolDiscoveryShowClaimsSupported(): void $this->assertFalse($this->sut()->getProtocolDiscoveryShowClaimsSupported()); $this->assertTrue( $this->sut( - null, - [ModuleConfig::OPTION_PROTOCOL_DISCOVERY_SHOW_CLAIMS_SUPPORTED => true], + overrides: [ModuleConfig::OPTION_PROTOCOL_DISCOVERY_SHOW_CLAIMS_SUPPORTED => true], )->getProtocolDiscoveryShowClaimsSupported(), ); } + + public function testCanGetProtocolNewCertPath(): void + { + $this->assertNull($this->sut()->getProtocolNewCertPath()); + + $sut = $this->sut( + overrides: [ModuleConfig::OPTION_PKI_NEW_CERTIFICATE_FILENAME => 'new-cert'], + ); + + $this->assertStringContainsString('new-cert', $sut->getProtocolNewCertPath()); + } + + public function testCanGetFederationNewCertPath(): void + { + $this->assertNull($this->sut()->getFederationNewCertPath()); + + $sut = $this->sut( + overrides: [ModuleConfig::OPTION_PKI_FEDERATION_NEW_CERTIFICATE_FILENAME => 'new-cert'], + ); + + $this->assertStringContainsString('new-cert', $sut->getFederationNewCertPath()); + } + + public function testCanGetFederationDynamicTrustMarks(): void + { + $this->assertNull($this->sut()->getFederationDynamicTrustMarks()); + + $sut = $this->sut( + overrides: [ + ModuleConfig::OPTION_FEDERATION_DYNAMIC_TRUST_MARKS => [ + 'trust-mark-id' => 'trust-mark-issuer-id', + ], + ], + ); + + $this->assertArrayHasKey( + 'trust-mark-id', + $sut->getFederationDynamicTrustMarks(), + ); + } + + public function testCanGetFederationParticipationLimitByTrustMarks(): void + { + $this->assertArrayHasKey( + 'https://ta.example.org/', + $this->sut()->getFederationParticipationLimitByTrustMarks(), + ); + } + + public function testCanGetTrustMarksNeededForFederationParticipationFor(): void + { + $neededTrustMarks = $this->sut()->getTrustMarksNeededForFederationParticipationFor('https://ta.example.org/'); + + $this->assertArrayHasKey('one_of', $neededTrustMarks); + $this->assertTrue(in_array('trust-mark-id', $neededTrustMarks['one_of'])); + } + + public function testGetTrustMarksNeededForFederationParticipationForThrowsOnInvalidConfigValue(): void + { + $sut = $this->sut( + overrides: [ + ModuleConfig::OPTION_FEDERATION_PARTICIPATION_LIMIT_BY_TRUST_MARKS => [ + 'https://ta.example.org/' => 'invalid', + ], + ], + ); + + $this->expectException(ConfigurationError::class); + + $sut->getTrustMarksNeededForFederationParticipationFor('https://ta.example.org/'); + } + + public function testCanGetIsFederationParticipationLimitedByTrustMarksFor(): void + { + $this->assertTrue( + $this->sut()->isFederationParticipationLimitedByTrustMarksFor('https://ta.example.org/'), + ); + } } From 8ae7d1e4cfbb28eaac13ef3b5ec02c07e2cc6472 Mon Sep 17 00:00:00 2001 From: Marko Ivancic Date: Wed, 2 Apr 2025 14:47:46 +0200 Subject: [PATCH 099/130] Enable federation list endpoint (#301) * Enable federation list endpoint --- README.md | 1 + UPGRADE.md | 1 + routing/routes/routes.php | 5 + src/Codebooks/RoutesEnum.php | 1 + .../Federation/EntityStatementController.php | 11 +- .../SubordinateListingsController.php | 67 +++++++++++ src/Repositories/ClientRepository.php | 70 ++++++++++- src/Utils/Routes.php | 5 + templates/clients/includes/form.twig | 4 +- .../SubordinateListingsControllerTest.php | 110 ++++++++++++++++++ .../src/Repositories/ClientRepositoryTest.php | 44 ++++++- 11 files changed, 305 insertions(+), 14 deletions(-) create mode 100644 src/Controllers/Federation/SubordinateListingsController.php create mode 100644 tests/unit/src/Controllers/Federation/SubordinateListingsControllerTest.php diff --git a/README.md b/README.md index 46550471..d4b573c5 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Currently, the following OIDF features are supported: * 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) +* subordinate listing endpoint OIDF support is implemented using the underlying [SimpleSAMLphp OpenID library](https://github.com/simplesamlphp/openid). diff --git a/UPGRADE.md b/UPGRADE.md index e3443186..a4efab84 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -15,6 +15,7 @@ be fetched by RPs, and do the switch between "old" and "new" key pair when you f - 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) + - (from v6.1) Subordinate listing endpoint - Clients can now be configured with new properties: - Entity Identifier - Supported OpenID Federation Registration Types diff --git a/routing/routes/routes.php b/routing/routes/routes.php index f05ac6ac..caad53c7 100644 --- a/routing/routes/routes.php +++ b/routing/routes/routes.php @@ -15,6 +15,7 @@ use SimpleSAML\Module\oidc\Controllers\ConfigurationDiscoveryController; use SimpleSAML\Module\oidc\Controllers\EndSessionController; use SimpleSAML\Module\oidc\Controllers\Federation\EntityStatementController; +use SimpleSAML\Module\oidc\Controllers\Federation\SubordinateListingsController; use SimpleSAML\Module\oidc\Controllers\JwksController; use SimpleSAML\Module\oidc\Controllers\UserInfoController; use SimpleSAML\OpenID\Codebooks\HttpMethodsEnum; @@ -96,4 +97,8 @@ $routes->add(RoutesEnum::FederationFetch->name, RoutesEnum::FederationFetch->value) ->controller([EntityStatementController::class, 'fetch']) ->methods([HttpMethodsEnum::GET->value]); + + $routes->add(RoutesEnum::FederationList->name, RoutesEnum::FederationList->value) + ->controller([SubordinateListingsController::class, 'list']) + ->methods([HttpMethodsEnum::GET->value]); }; diff --git a/src/Codebooks/RoutesEnum.php b/src/Codebooks/RoutesEnum.php index f81a98b8..6c17691a 100644 --- a/src/Codebooks/RoutesEnum.php +++ b/src/Codebooks/RoutesEnum.php @@ -46,4 +46,5 @@ enum RoutesEnum: string case FederationConfiguration = '.well-known/openid-federation'; case FederationFetch = 'federation/fetch'; + case FederationList = 'federation/list'; } diff --git a/src/Controllers/Federation/EntityStatementController.php b/src/Controllers/Federation/EntityStatementController.php index 105d8100..5f4494a7 100644 --- a/src/Controllers/Federation/EntityStatementController.php +++ b/src/Controllers/Federation/EntityStatementController.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Module\oidc\Controllers\Federation; -use SimpleSAML\Module\oidc\Codebooks\RoutesEnum; use SimpleSAML\Module\oidc\Helpers; use SimpleSAML\Module\oidc\ModuleConfig; use SimpleSAML\Module\oidc\Repositories\ClientRepository; @@ -95,11 +94,10 @@ public function configuration(): Response ClaimsEnum::HomepageUri->value => $this->moduleConfig->getHomepageUri(), ], )), - ClaimsEnum::FederationFetchEndpoint->value => - $this->moduleConfig->getModuleUrl(RoutesEnum::FederationFetch->value), + ClaimsEnum::FederationFetchEndpoint->value => $this->routes->urlFederationFetch(), + ClaimsEnum::FederationListEndpoint->value => $this->routes->urlFederationList(), // 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', //'federation_trust_mark_status_endpoint', //'federation_trust_mark_list_endpoint', @@ -211,7 +209,7 @@ public function configuration(): Response public function fetch(Request $request): Response { - $subject = $request->query->get(ClaimsEnum::Sub->value); + $subject = $request->query->getString(ClaimsEnum::Sub->value); if (empty($subject)) { return $this->routes->newJsonErrorResponse( @@ -222,7 +220,6 @@ public function fetch(Request $request): Response } /** @var non-empty-string $subject */ - $subject = (string)$subject; $cachedSubordinateStatement = $this->federationCache?->get( null, @@ -234,7 +231,7 @@ public function fetch(Request $request): Response return $this->prepareEntityStatementResponse((string)$cachedSubordinateStatement); } - $client = $this->clientRepository->findByEntityIdentifier($subject); + $client = $this->clientRepository->findFederatedByEntityIdentifier($subject); if (empty($client)) { return $this->routes->newJsonErrorResponse( ErrorsEnum::NotFound->value, diff --git a/src/Controllers/Federation/SubordinateListingsController.php b/src/Controllers/Federation/SubordinateListingsController.php new file mode 100644 index 00000000..f0eb5edc --- /dev/null +++ b/src/Controllers/Federation/SubordinateListingsController.php @@ -0,0 +1,67 @@ +moduleConfig->getFederationEnabled()) { + throw OidcServerException::forbidden('federation capabilities not enabled'); + } + } + + public function list(Request $request): Response + { + // If unsupported query parameter is provided, we have to respond with an error: "If the responder does not + // support this feature, it MUST use the HTTP status code 400 and the content type application/json, with + // the error code unsupported_parameter." + + // Currently, we don't support any of the mentioned params in the spec, so let's return error for any of them. + $unsupportedParams = [ + ParamsEnum::EntityType->value, + ParamsEnum::TrustMarked->value, + ParamsEnum::TrustMarkId->value, + ParamsEnum::Intermediate->value, + ]; + + $requestedParams = array_keys($request->query->all()); + + if (!empty($intersectedParams = array_intersect($unsupportedParams, $requestedParams))) { + return $this->routes->newJsonErrorResponse( + ErrorsEnum::UnsupportedParameter->value, + 'Unsupported parameter: ' . implode(', ', $intersectedParams), + 400, + ); + } + + $subordinateEntityIdList = array_filter(array_map( + function (ClientEntityInterface $clientEntity): ?string { + return $clientEntity->getEntityIdentifier(); + }, + $this->clientRepository->findAllFederated(), + )); + + return $this->routes->newJsonResponse( + $subordinateEntityIdList, + ); + } +} diff --git a/src/Repositories/ClientRepository.php b/src/Repositories/ClientRepository.php index 49654731..27bc952a 100644 --- a/src/Repositories/ClientRepository.php +++ b/src/Repositories/ClientRepository.php @@ -153,14 +153,10 @@ public function findByEntityIdentifier(string $entityIdentifier, ?string $owner <<getTableName()} WHERE - entity_identifier = :entity_identifier AND - is_enabled = :is_enabled AND - is_federated = :is_federated + entity_identifier = :entity_identifier EOS, [ 'entity_identifier' => $entityIdentifier, - 'is_enabled' => [true, PDO::PARAM_BOOL], - 'is_federated' => [true, PDO::PARAM_BOOL], ], $owner, ); @@ -190,6 +186,29 @@ public function findByEntityIdentifier(string $entityIdentifier, ?string $owner return $clientEntity; } + public function findFederatedByEntityIdentifier( + string $entityIdentifier, + ?string $owner = null, + ): ?ClientEntityInterface { + $clientEntity = $this->findByEntityIdentifier($entityIdentifier, $owner); + + if (is_null($clientEntity)) { + return null; + } + + if ( + is_null($clientEntity->getEntityIdentifier()) || + (! $clientEntity->isEnabled()) || + (! $clientEntity->isFederated()) || + (!is_array($clientEntity->getFederationJwks())) || + $clientEntity->isExpired() + ) { + return null; + } + + return $clientEntity; + } + private function addOwnerWhereClause(string $query, array $params, ?string $owner = null): array { if (isset($owner)) { @@ -234,6 +253,47 @@ public function findAll(?string $owner = null): array return $clients; } + /** + * @return \SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface[] + * @throws \JsonException + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + */ + public function findAllFederated(?string $owner = null): array + { + /** + * @var string $query + * @var array $params + */ + [$query, $params] = $this->addOwnerWhereClause( + <<getTableName()} + WHERE + entity_identifier IS NOT NULL AND + federation_jwks IS NOT NULL AND + is_enabled = :is_enabled AND + is_federated = :is_federated + EOS, + [ + 'is_enabled' => [true, PDO::PARAM_BOOL], + 'is_federated' => [true, PDO::PARAM_BOOL], + ], + $owner, + ); + $stmt = $this->database->read( + "$query ORDER BY name ASC", + $params, + ); + + $clients = []; + + /** @var array $state */ + foreach ($stmt->fetchAll() as $state) { + $clients[] = $this->clientEntityFactory->fromState($state); + } + + return $clients; + } + /** * @return array{ * numPages: int, diff --git a/src/Utils/Routes.php b/src/Utils/Routes.php index a9fea448..d9134231 100644 --- a/src/Utils/Routes.php +++ b/src/Utils/Routes.php @@ -193,4 +193,9 @@ public function urlFederationFetch(array $parameters = []): string { return $this->getModuleUrl(RoutesEnum::FederationFetch->value, $parameters); } + + public function urlFederationList(array $parameters = []): string + { + return $this->getModuleUrl(RoutesEnum::FederationList->value, $parameters); + } } diff --git a/templates/clients/includes/form.twig b/templates/clients/includes/form.twig index c5049b3d..cf085f33 100644 --- a/templates/clients/includes/form.twig +++ b/templates/clients/includes/form.twig @@ -143,7 +143,9 @@

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

    - + + {% trans %}In order for an entity to participate in federation contexts (for example, to be listed as subordinate to this OP), it must have an Entity Identifier and Federation JWKS set. {% endtrans %} +