From 50bea95307dec6364cfe9cb25e5dbe2fa2b3f634 Mon Sep 17 00:00:00 2001 From: Andrej Hudec Date: Tue, 12 Sep 2023 14:32:13 +0200 Subject: [PATCH] Add options to handle other oAuth2 servers, e.g. Google (#13) * Change cookie domain to accept empty value * Allow to have null in cookie domain * Configure scope delimiter * Allow to authenticate user by SSO email * Rename method `findOneByEmail` to `findOneBySsoEmail` * Accept opaque oAuth2 access tokens * Store access token to cache on first request --- src/Configuration/CookieConfiguration.php | 4 +- src/Configuration/OAuth2Configuration.php | 15 +- .../OAuth2AuthUserRepositoryInterface.php | 1 + .../AnzuSystemsAuthExtension.php | 3 + src/DependencyInjection/Configuration.php | 10 +- .../GrantAccessByOAuth2TokenProcess.php | 67 ++++++- src/HttpClient/OAuth2HttpClient.php | 61 ++++-- src/Model/AccessTokenDto.php | 56 ++++++ src/Model/Enum/JwtAlgorithm.php | 2 +- src/Model/OpaqueAccessTokenResponseDto.php | 40 ++++ .../AnzuSystemsAuthExtensionTest.php | 181 ++++++++++++++++++ 11 files changed, 410 insertions(+), 30 deletions(-) create mode 100644 src/Model/AccessTokenDto.php create mode 100644 src/Model/OpaqueAccessTokenResponseDto.php create mode 100644 tests/DependencyInjection/AnzuSystemsAuthExtensionTest.php diff --git a/src/Configuration/CookieConfiguration.php b/src/Configuration/CookieConfiguration.php index 2abb89b..4fc906a 100644 --- a/src/Configuration/CookieConfiguration.php +++ b/src/Configuration/CookieConfiguration.php @@ -7,7 +7,7 @@ final class CookieConfiguration { public function __construct( - private readonly string $domain, + private readonly ?string $domain, private readonly bool $secure, private readonly string $jwtPayloadCookieName, private readonly string $jwtSignatureCookieName, @@ -18,7 +18,7 @@ public function __construct( ) { } - public function getDomain(): string + public function getDomain(): ?string { return $this->domain; } diff --git a/src/Configuration/OAuth2Configuration.php b/src/Configuration/OAuth2Configuration.php index cd45808..784871d 100644 --- a/src/Configuration/OAuth2Configuration.php +++ b/src/Configuration/OAuth2Configuration.php @@ -22,6 +22,8 @@ public function __construct( private readonly string $ssoClientSecret, private readonly string $ssoPublicCert, private readonly array $ssoScopes, + private readonly string $ssoScopeDelimiter, + private readonly bool $considerAccessTokenAsJwt, private readonly CacheItemPoolInterface $accessTokenCachePool, ) { } @@ -36,8 +38,12 @@ public function getSsoAuthorizeUrl(): string return $this->ssoAuthorizeUrl; } - public function getSsoUserInfoUrl(string $userId): string + public function getSsoUserInfoUrl(?string $userId): string { + if (!$userId) { + return $this->ssoUserInfoUrl; + } + return str_replace(self::SSO_USER_ID_PLACEHOLDER_URL, $userId, $this->ssoUserInfoUrl); } @@ -57,7 +63,7 @@ public function getResolvedSsoAuthorizeUrl(string $state): string 'response_type' => 'code', 'state' => $state, 'redirect_uri' => $this->getSsoRedirectUrl(), - 'scope' => implode(',', $this->getSsoScopes()), + 'scope' => implode($this->ssoScopeDelimiter, $this->getSsoScopes()), ]) ); } @@ -94,4 +100,9 @@ public function getAccessTokenCachePool(): CacheItemPoolInterface { return $this->accessTokenCachePool; } + + public function isAccessTokenConsideredJwt(): bool + { + return $this->considerAccessTokenAsJwt; + } } diff --git a/src/Contracts/OAuth2AuthUserRepositoryInterface.php b/src/Contracts/OAuth2AuthUserRepositoryInterface.php index 9da952f..1195da0 100644 --- a/src/Contracts/OAuth2AuthUserRepositoryInterface.php +++ b/src/Contracts/OAuth2AuthUserRepositoryInterface.php @@ -7,4 +7,5 @@ interface OAuth2AuthUserRepositoryInterface { public function findOneBySsoUserId(string $ssoUserId): ?AnzuAuthUserInterface; + public function findOneBySsoEmail(string $email): ?AnzuAuthUserInterface; } diff --git a/src/DependencyInjection/AnzuSystemsAuthExtension.php b/src/DependencyInjection/AnzuSystemsAuthExtension.php index 31c8fc9..55d147c 100644 --- a/src/DependencyInjection/AnzuSystemsAuthExtension.php +++ b/src/DependencyInjection/AnzuSystemsAuthExtension.php @@ -100,6 +100,8 @@ public function load(array $configs, ContainerBuilder $container): void ->setArgument('$ssoClientSecret', $oauth2Section['client_secret']) ->setArgument('$ssoPublicCert', $oauth2Section['public_cert']) ->setArgument('$ssoScopes', $oauth2Section['scopes']) + ->setArgument('$ssoScopeDelimiter', $oauth2Section['scope_delimiter']) + ->setArgument('$considerAccessTokenAsJwt', $oauth2Section['consider_access_token_as_jwt']) ->setArgument('$accessTokenCachePool', new Reference($oauth2Section['access_token_cache'])) ; @@ -116,6 +118,7 @@ public function load(array $configs, ContainerBuilder $container): void ->register(GrantAccessByOAuth2TokenProcess::class) ->setAutowired(true) ->setAutoconfigured(true) + ->setArgument('$authMethod', $oauth2Section['auth_method']) ; $container diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 8eea8d2..0dd4757 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -5,6 +5,7 @@ namespace AnzuSystems\AuthBundle\DependencyInjection; use AnzuSystems\AuthBundle\Configuration\OAuth2Configuration; +use AnzuSystems\AuthBundle\Domain\Process\OAuth2\GrantAccessByOAuth2TokenProcess; use AnzuSystems\AuthBundle\Model\Enum\AuthType; use AnzuSystems\AuthBundle\Model\Enum\JwtAlgorithm; use AnzuSystems\AuthBundle\Model\SsoUserDto; @@ -37,8 +38,9 @@ private function addCookieSection(): NodeDefinition { return (new TreeBuilder('cookie'))->getRootNode() ->isRequired() + ->addDefaultsIfNotSet() ->children() - ->scalarNode('domain')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('domain')->defaultValue(null)->end() ->booleanNode('secure')->isRequired()->end() ->scalarNode('device_id_name')->defaultValue('anz_di')->end() ->arrayNode('jwt') @@ -148,7 +150,13 @@ private function addOAuth2AuthorizationSection(): NodeDefinition ->scalarNode('client_id')->defaultValue('')->end() ->scalarNode('client_secret')->defaultValue('')->end() ->scalarNode('public_cert')->defaultValue('')->end() + ->enumNode('scope_delimiter')->values([' ', ','])->defaultValue(',')->end() ->arrayNode('scopes')->scalarPrototype()->end()->end() + ->booleanNode('consider_access_token_as_jwt')->defaultTrue()->end() + ->enumNode('auth_method') + ->values([GrantAccessByOAuth2TokenProcess::AUTH_METHOD_SSO_ID, GrantAccessByOAuth2TokenProcess::AUTH_METHOD_SSO_EMAIL]) + ->defaultValue(GrantAccessByOAuth2TokenProcess::AUTH_METHOD_SSO_ID) + ->end() ->end() ; } diff --git a/src/Domain/Process/OAuth2/GrantAccessByOAuth2TokenProcess.php b/src/Domain/Process/OAuth2/GrantAccessByOAuth2TokenProcess.php index 4d043c5..8f827c9 100644 --- a/src/Domain/Process/OAuth2/GrantAccessByOAuth2TokenProcess.php +++ b/src/Domain/Process/OAuth2/GrantAccessByOAuth2TokenProcess.php @@ -4,15 +4,19 @@ namespace AnzuSystems\AuthBundle\Domain\Process\OAuth2; +use AnzuSystems\AuthBundle\Contracts\AnzuAuthUserInterface; use AnzuSystems\AuthBundle\Contracts\OAuth2AuthUserRepositoryInterface; use AnzuSystems\AuthBundle\Domain\Process\GrantAccessOnResponseProcess; use AnzuSystems\AuthBundle\Exception\InvalidJwtException; use AnzuSystems\AuthBundle\Exception\UnsuccessfulAccessTokenRequestException; +use AnzuSystems\AuthBundle\Exception\UnsuccessfulUserInfoRequestException; use AnzuSystems\AuthBundle\HttpClient\OAuth2HttpClient; +use AnzuSystems\AuthBundle\Model\AccessTokenDto; use AnzuSystems\AuthBundle\Model\Enum\UserOAuthLoginState; use AnzuSystems\AuthBundle\Util\HttpUtil; use AnzuSystems\CommonBundle\Log\Factory\LogContextFactory; use AnzuSystems\CommonBundle\Traits\SerializerAwareTrait; +use AnzuSystems\Contracts\Exception\AnzuException; use AnzuSystems\SerializerBundle\Exception\SerializerException; use Exception; use Lcobucci\JWT\Token\RegisteredClaims; @@ -26,41 +30,59 @@ final class GrantAccessByOAuth2TokenProcess { use SerializerAwareTrait; + public const AUTH_METHOD_SSO_ID = 'sso_id'; + public const AUTH_METHOD_SSO_EMAIL = 'sso_email'; + public function __construct( private readonly OAuth2HttpClient $OAuth2HttpClient, private readonly GrantAccessOnResponseProcess $grantAccessOnResponseProcess, private readonly ValidateOAuth2AccessTokenProcess $validateOAuth2AccessTokenProcess, - private readonly OAuth2AuthUserRepositoryInterface $OAuth2AuthUserRepository, + private readonly OAuth2AuthUserRepositoryInterface $oAuth2AuthUserRepository, private readonly HttpUtil $httpUtil, private readonly LoggerInterface $appLogger, private readonly LogContextFactory $contextFactory, + private readonly string $authMethod, ) { } /** * @throws SerializerException + * @throws AnzuException */ public function execute(Request $request): Response { $code = (string) $request->query->get('code'); try { - $ssoJwt = $this->OAuth2HttpClient->requestAccessTokenByAuthCode($code); + $accessTokenDto = $this->OAuth2HttpClient->requestAccessTokenByAuthCode($code); } catch (UnsuccessfulAccessTokenRequestException $exception) { $this->logException($request, $exception); return $this->createRedirectResponseForRequest($request, UserOAuthLoginState::FailureSsoCommunicationFailed); } + if ($accessTokenDto->getJwt()) { + // validate jwt + try { + $this->validateOAuth2AccessTokenProcess->execute($accessTokenDto->getJwt()); + } catch (InvalidJwtException $exception) { + $this->logException($request, $exception); + + return $this->createRedirectResponseForRequest($request, UserOAuthLoginState::FailureUnauthorized); + } + } + try { - $this->validateOAuth2AccessTokenProcess->execute($ssoJwt->getAccessToken()); - $ssoUserId = (string) $ssoJwt->getAccessToken()->claims()->get(RegisteredClaims::SUBJECT); - } catch (InvalidJwtException $exception) { + $authUser = $this->getAuthUser($accessTokenDto); + } catch (UnsuccessfulUserInfoRequestException | UnsuccessfulAccessTokenRequestException $exception) { $this->logException($request, $exception); - return $this->createRedirectResponseForRequest($request, UserOAuthLoginState::FailureUnauthorized); + return $this->createRedirectResponseForRequest( + $request, + UserOAuthLoginState::FailureSsoCommunicationFailed + ); } - $authUser = $this->OAuth2AuthUserRepository->findOneBySsoUserId($ssoUserId); + if (null === $authUser || false === $authUser->isEnabled()) { return $this->createRedirectResponseForRequest($request, UserOAuthLoginState::FailureUnauthorized); } @@ -92,4 +114,35 @@ private function createRedirectResponseForRequest(Request $request, UserOAuthLog return new RedirectResponse($redirectUrl); } + + /** + * @throws AnzuException + * @throws UnsuccessfulAccessTokenRequestException + * @throws UnsuccessfulUserInfoRequestException + */ + private function getAuthUser(AccessTokenDto $accessTokenDto): ?AnzuAuthUserInterface + { + if (self::AUTH_METHOD_SSO_EMAIL === $this->authMethod) { + // fetch user info + $ssoUser = $this->OAuth2HttpClient->getSsoUserInfo(); + + return $this->oAuth2AuthUserRepository->findOneBySsoEmail($ssoUser->getEmail()); + } + + if (self::AUTH_METHOD_SSO_ID === $this->authMethod) { + // prefer to use the jwt + if ($accessTokenDto->getJwt()) { + $ssoUserId = (string) $accessTokenDto->getJwt()->claims()->get(RegisteredClaims::SUBJECT); + + return $this->oAuth2AuthUserRepository->findOneBySsoUserId($ssoUserId); + } + + // otherwise fetch user info + $ssoUser = $this->OAuth2HttpClient->getSsoUserInfo(); + + return $this->oAuth2AuthUserRepository->findOneBySsoUserId($ssoUser->getId()); + } + + throw new AnzuException(sprintf('Unknown auth method "%s".', $this->authMethod)); + } } diff --git a/src/HttpClient/OAuth2HttpClient.php b/src/HttpClient/OAuth2HttpClient.php index 95c4cc5..6cc3604 100644 --- a/src/HttpClient/OAuth2HttpClient.php +++ b/src/HttpClient/OAuth2HttpClient.php @@ -7,12 +7,13 @@ use AnzuSystems\AuthBundle\Configuration\OAuth2Configuration; use AnzuSystems\AuthBundle\Exception\UnsuccessfulAccessTokenRequestException; use AnzuSystems\AuthBundle\Exception\UnsuccessfulUserInfoRequestException; +use AnzuSystems\AuthBundle\Model\AccessTokenDto; use AnzuSystems\AuthBundle\Model\AccessTokenResponseDto; +use AnzuSystems\AuthBundle\Model\OpaqueAccessTokenResponseDto; use AnzuSystems\AuthBundle\Model\SsoUserDto; -use AnzuSystems\CommonBundle\Log\Factory\LogContextFactory; use AnzuSystems\SerializerBundle\Exception\SerializerException; use AnzuSystems\SerializerBundle\Serializer; -use DateTimeInterface; +use Psr\Cache\CacheItemInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -31,29 +32,33 @@ public function __construct( /** * @throws UnsuccessfulAccessTokenRequestException */ - public function requestAccessTokenByAuthCode(string $code): AccessTokenResponseDto + public function requestAccessTokenByAuthCode(string $code): AccessTokenDto { - return $this->sendTokenRequest($this->configuration->getSsoAccessTokenUrl(), [ + $accessToken = $this->sendTokenRequest($this->configuration->getSsoAccessTokenUrl(), [ 'grant_type' => 'authorization_code', 'code' => $code, 'client_id' => $this->configuration->getSsoClientId(), 'client_secret' => $this->configuration->getSsoClientSecret(), 'redirect_uri' => $this->configuration->getSsoRedirectUrl(), ]); + + $this->storeAccessTokenToCache($this->getAccessTokenCacheItem(), $accessToken); + + return $accessToken; } /** * @throws UnsuccessfulAccessTokenRequestException * @throws UnsuccessfulUserInfoRequestException */ - public function getSsoUserInfo(string $id): SsoUserDto + public function getSsoUserInfo(?string $id = null): SsoUserDto { try { $response = $this->client->request( method: Request::METHOD_GET, url: $this->configuration->getSsoUserInfoUrl($id), options: [ - 'auth_bearer' => $this->requestAccessTokenForClientService()->getAccessToken()->toString(), + 'auth_bearer' => $this->requestAccessTokenForClientService()->getAccessToken(), ] ); @@ -70,11 +75,9 @@ public function getSsoUserInfo(string $id): SsoUserDto * * @noinspection PhpDocMissingThrowsInspection */ - private function requestAccessTokenForClientService(): AccessTokenResponseDto + private function requestAccessTokenForClientService(): AccessTokenDto { - $cachePool = $this->configuration->getAccessTokenCachePool(); - /** @noinspection PhpUnhandledExceptionInspection */ - $accessTokenCacheItem = $cachePool->getItem(self::CLIENT_SERVICE_ACCESS_TOKEN_CACHE_KEY); + $accessTokenCacheItem = $this->getAccessTokenCacheItem(); if ($accessTokenCacheItem->isHit()) { return $accessTokenCacheItem->get(); } @@ -84,11 +87,8 @@ private function requestAccessTokenForClientService(): AccessTokenResponseDto 'client_id' => $this->configuration->getSsoClientId(), 'client_secret' => $this->configuration->getSsoClientSecret(), ]); - /** @var DateTimeInterface $expiresAfter */ - $expiresAfter = $accessToken->getAccessToken()->claims()->get('exp'); - $accessTokenCacheItem->set($accessToken); - $accessTokenCacheItem->expiresAt($expiresAfter); - $cachePool->save($accessTokenCacheItem); + + $this->storeAccessTokenToCache($accessTokenCacheItem, $accessToken); return $accessToken; } @@ -96,16 +96,43 @@ private function requestAccessTokenForClientService(): AccessTokenResponseDto /** * @throws UnsuccessfulAccessTokenRequestException */ - private function sendTokenRequest(string $url, array $bodyParameters): AccessTokenResponseDto + private function sendTokenRequest(string $url, array $bodyParameters): AccessTokenDto { try { $response = $this->client->request(Request::METHOD_POST, $url, ['body' => $bodyParameters]); - return $this->serializer->deserialize($response->getContent(), AccessTokenResponseDto::class); + if ($this->configuration->isAccessTokenConsideredJwt()) { + return AccessTokenDto::createFromJwtAccessTokenResponse( + $this->serializer->deserialize($response->getContent(), AccessTokenResponseDto::class) + ); + } + + return AccessTokenDto::createFromOpaqueAccessTokenResponse( + $this->serializer->deserialize($response->getContent(), OpaqueAccessTokenResponseDto::class) + ); } catch (ExceptionInterface $exception) { throw UnsuccessfulAccessTokenRequestException::create('Token request failed!', $exception); } catch (SerializerException $exception) { throw UnsuccessfulAccessTokenRequestException::create('Invalid jwt token response!', $exception); } } + + private function getAccessTokenCacheItem(): CacheItemInterface + { + /** @noinspection PhpUnhandledExceptionInspection */ + return $this->configuration->getAccessTokenCachePool()->getItem( + self::CLIENT_SERVICE_ACCESS_TOKEN_CACHE_KEY + ); + } + + private function storeAccessTokenToCache( + CacheItemInterface $accessTokenCacheItem, + AccessTokenDto $accessToken + ): void { + $cachePool = $this->configuration->getAccessTokenCachePool(); + + $accessTokenCacheItem->set($accessToken); + $accessTokenCacheItem->expiresAt($accessToken->getExpiresAt()); + $cachePool->save($accessTokenCacheItem); + } } diff --git a/src/Model/AccessTokenDto.php b/src/Model/AccessTokenDto.php new file mode 100644 index 0000000..b2f171d --- /dev/null +++ b/src/Model/AccessTokenDto.php @@ -0,0 +1,56 @@ +accessToken = $accessToken; + $this->expiresAt = $expiresAt; + $this->jwt = $accessTokenJwt; + } + + + public function getJwt(): ?Plain + { + return $this->jwt; + } + + public function getAccessToken(): string + { + return $this->accessToken; + } + + public function getExpiresAt(): DateTimeInterface + { + return $this->expiresAt; + } + + public static function createFromJwtAccessTokenResponse(AccessTokenResponseDto $accessTokenResponseDto): self + { + $jwt = $accessTokenResponseDto->getAccessToken(); + /** @var DateTimeInterface $expiresAt */ + $expiresAt = $jwt->claims()->get('exp'); + + return new self($jwt->toString(), $expiresAt, $jwt); + } + + public static function createFromOpaqueAccessTokenResponse(OpaqueAccessTokenResponseDto $accessTokenResponseDto): self + { + $date = (new DateTimeImmutable())->add(new DateInterval('PT' . $accessTokenResponseDto->getExpiresIn() . 'S')); + + return new self($accessTokenResponseDto->getAccessToken(), $date); + } +} diff --git a/src/Model/Enum/JwtAlgorithm.php b/src/Model/Enum/JwtAlgorithm.php index bcde451..b52c173 100644 --- a/src/Model/Enum/JwtAlgorithm.php +++ b/src/Model/Enum/JwtAlgorithm.php @@ -20,7 +20,7 @@ enum JwtAlgorithm: string implements EnumInterface public function signer(): Signer\Ecdsa | Signer\Rsa\Sha256 { return match ($this) { - self::ES256 => Signer\Ecdsa\Sha256::create(), + self::ES256 => new Signer\Ecdsa\Sha256(), self::RS256 => new Signer\Rsa\Sha256(), }; } diff --git a/src/Model/OpaqueAccessTokenResponseDto.php b/src/Model/OpaqueAccessTokenResponseDto.php new file mode 100644 index 0000000..34d88e2 --- /dev/null +++ b/src/Model/OpaqueAccessTokenResponseDto.php @@ -0,0 +1,40 @@ +accessToken; + } + + public function setAccessToken(string $accessToken): self + { + $this->accessToken = $accessToken; + + return $this; + } + + public function getExpiresIn(): int + { + return $this->expiresIn; + } + + public function setExpiresIn(int $expiresIn): self + { + $this->expiresIn = $expiresIn; + + return $this; + } +} diff --git a/tests/DependencyInjection/AnzuSystemsAuthExtensionTest.php b/tests/DependencyInjection/AnzuSystemsAuthExtensionTest.php new file mode 100644 index 0000000..f6fc1ea --- /dev/null +++ b/tests/DependencyInjection/AnzuSystemsAuthExtensionTest.php @@ -0,0 +1,181 @@ +configuration = null; + } + + public function testEmptyConfiguration(): void + { + $this->configuration = new ContainerBuilder(); + $loader = new AnzuSystemsAuthExtension(); + $config = $this->getEmptyConfig(); + $loader->load([$config], $this->configuration); + + $this->assertParameter(null, 'anzu_systems.auth_bundle.cookie.domain'); + $this->assertParameter(true, 'anzu_systems.auth_bundle.cookie.secure'); + $this->assertParameter('anz_di', 'anzu_systems.auth_bundle.cookie.device_id_name'); + $this->assertParameter('anz_jp', 'anzu_systems.auth_bundle.cookie.jwt.payload_part_name'); + $this->assertParameter('anz_js', 'anzu_systems.auth_bundle.cookie.jwt.signature_part_name'); + $this->assertParameter('anz_js', 'anzu_systems.auth_bundle.cookie.jwt.signature_part_name'); + + $this->assertParameter('anz_rt', 'anzu_systems.auth_bundle.cookie.refresh_token.name'); + $this->assertParameter(31536000, 'anzu_systems.auth_bundle.cookie.refresh_token.lifetime'); + $this->assertParameter('anz_rte', 'anzu_systems.auth_bundle.cookie.refresh_token.existence_name'); + + $this->assertParameter('anz', 'anzu_systems.auth_bundle.jwt.audience'); + $this->assertParameter('ES256', 'anzu_systems.auth_bundle.jwt.algorithm'); + $this->assertParameter('foo_public_cert', 'anzu_systems.auth_bundle.jwt.public_cert'); + $this->assertParameter('foo_private_cert', 'anzu_systems.auth_bundle.jwt.private_cert'); + $this->assertParameter(3600, 'anzu_systems.auth_bundle.jwt.lifetime'); + + $this->assertNotHasDefinition(AuthenticationSuccessHandler::class); + $this->assertNotHasDefinition(AuthenticationFailureHandler::class); + $this->assertNotHasDefinition(GrantAccessOnResponseProcess::class); + $this->assertNotHasDefinition(RefreshTokenProcess::class); + $this->assertNotHasDefinition(LogoutListener::class); + $this->assertNotHasDefinition(OAuth2Configuration::class); + $this->assertNotHasDefinition(GrantAccessByOAuth2TokenProcess::class); + } + + public function testFullConfiguration(): void + { + $this->configuration = new ContainerBuilder(); + $loader = new AnzuSystemsAuthExtension(); + $config = $this->getFullConfig(); + $loader->load([$config], $this->configuration); + + $this->assertParameter('.example.com', 'anzu_systems.auth_bundle.cookie.domain'); + $this->assertParameter(true, 'anzu_systems.auth_bundle.cookie.secure'); + $this->assertParameter('anz_di', 'anzu_systems.auth_bundle.cookie.device_id_name'); + $this->assertParameter('anz_jp', 'anzu_systems.auth_bundle.cookie.jwt.payload_part_name'); + $this->assertParameter('anz_js', 'anzu_systems.auth_bundle.cookie.jwt.signature_part_name'); + $this->assertParameter('anz_js', 'anzu_systems.auth_bundle.cookie.jwt.signature_part_name'); + + $this->assertParameter('anz_rt', 'anzu_systems.auth_bundle.cookie.refresh_token.name'); + $this->assertParameter(31536000, 'anzu_systems.auth_bundle.cookie.refresh_token.lifetime'); + $this->assertParameter('anz_rte', 'anzu_systems.auth_bundle.cookie.refresh_token.existence_name'); + + $this->assertParameter('anz', 'anzu_systems.auth_bundle.jwt.audience'); + $this->assertParameter('ES256', 'anzu_systems.auth_bundle.jwt.algorithm'); + $this->assertParameter('foo_public_cert', 'anzu_systems.auth_bundle.jwt.public_cert'); + $this->assertParameter('foo_private_cert', 'anzu_systems.auth_bundle.jwt.private_cert'); + $this->assertParameter(3600, 'anzu_systems.auth_bundle.jwt.lifetime'); + + + $this->assertHasDefinition(AuthenticationSuccessHandler::class); + $this->assertHasDefinition(AuthenticationFailureHandler::class); + $this->assertHasDefinition(GrantAccessOnResponseProcess::class); + $this->assertHasDefinition(RefreshTokenProcess::class); + $this->assertHasDefinition(LogoutListener::class); + + $this->assertHasDefinition(OAuth2Configuration::class); + $oAuth2ConfigurationDefinition = $this->configuration->getDefinition(OAuth2Configuration::class); + $oAuth2ConfArguments = $oAuth2ConfigurationDefinition->getArguments(); + self::assertSame('https://example.com/access-token-url', $oAuth2ConfArguments['$ssoAccessTokenUrl']); + self::assertSame('https://example.com/authorize-url', $oAuth2ConfArguments['$ssoAuthorizeUrl']); + self::assertSame('https://example.com/redirect-url', $oAuth2ConfArguments['$ssoRedirectUrl']); + self::assertSame('https://example.com/user-info-url', $oAuth2ConfArguments['$ssoUserInfoUrl']); + self::assertSame('AnzuSystems\AuthBundle\Model\SsoUserDto', $oAuth2ConfArguments['$ssoUserInfoClass']); + self::assertSame('qux', $oAuth2ConfArguments['$ssoClientId']); + self::assertSame('bar-secret', $oAuth2ConfArguments['$ssoClientSecret']); + self::assertSame('qux-public-cert', $oAuth2ConfArguments['$ssoPublicCert']); + self::assertSame(['email', 'profile'], $oAuth2ConfArguments['$ssoScopes']); + self::assertSame(' ', $oAuth2ConfArguments['$ssoScopeDelimiter']); + self::assertFalse($oAuth2ConfArguments['$considerAccessTokenAsJwt']); + + $this->assertHasDefinition(GrantAccessByOAuth2TokenProcess::class); + $grantProcessDefinition = $this->configuration->getDefinition(GrantAccessByOAuth2TokenProcess::class); + $grantProcessArguments = $grantProcessDefinition->getArguments(); + self::assertSame('sso_email', $grantProcessArguments['$authMethod']); + } + + private function getEmptyConfig(): ?array + { + $yaml = <<parse($yaml); + } + + private function getFullConfig(): array + { + $yaml = <<parse($yaml); + } + + private function assertParameter($value, string $key): void + { + self::assertSame($value, $this->configuration->getParameter($key), sprintf('%s parameter is correct', $key)); + } + + private function assertHasDefinition(string $id): void + { + self::assertTrue(($this->configuration->hasDefinition($id) || $this->configuration->hasAlias($id))); + } + + private function assertNotHasDefinition(string $id): void + { + self::assertFalse(($this->configuration->hasDefinition($id) || $this->configuration->hasAlias($id))); + } +}