From 9ea42eabea6e44363a40f0520da33c1b2e7cd037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Wed, 17 Jun 2026 19:45:41 +0200 Subject: [PATCH] Remove old container and legacy URLs --- docs/6-oidc-upgrade.md | 24 + hooks/hook_cron.php | 38 +- public/authorize.php | 20 - public/jwks.php | 20 - public/logout.php | 20 - public/openid-configuration.php | 20 - public/token.php | 20 - public/userinfo.php | 20 - routing/services/services.yml | 5 +- src/Services/Container.php | 674 ------------------ src/Services/ExpiredEntriesCleaner.php | 39 + src/Services/RoutingService.php | 188 ----- .../Services/ExpiredEntriesCleanerTest.php | 77 ++ 13 files changed, 152 insertions(+), 1013 deletions(-) delete mode 100644 public/authorize.php delete mode 100644 public/jwks.php delete mode 100644 public/logout.php delete mode 100644 public/openid-configuration.php delete mode 100644 public/token.php delete mode 100644 public/userinfo.php delete mode 100644 src/Services/Container.php create mode 100644 src/Services/ExpiredEntriesCleaner.php delete mode 100644 src/Services/RoutingService.php create mode 100644 tests/unit/src/Services/ExpiredEntriesCleanerTest.php diff --git a/docs/6-oidc-upgrade.md b/docs/6-oidc-upgrade.md index 8525550b..722b75b9 100644 --- a/docs/6-oidc-upgrade.md +++ b/docs/6-oidc-upgrade.md @@ -153,6 +153,30 @@ statements about subordinates are removed, as the final specification explicitly states that leaf entities must not have those endpoints. This effectively means that this OP implementation can only be a leaf entity in the federation context, and not a federation operator or intermediary entity. +- The legacy OIDC endpoints served directly by PHP files in the module's +`public` folder are now removed (as announced in the version 5 to 6 upgrade +notes). These were the old routes still reachable at URLs ending in `.php`: + - `/module.php/oidc/authorize.php` + - `/module.php/oidc/token.php` + - `/module.php/oidc/userinfo.php` + - `/module.php/oidc/jwks.php` + - `/module.php/oidc/logout.php` + - `/module.php/oidc/openid-configuration.php` + + Use the Symfony-based routes instead, which have been the default since + version 6 and are the ones advertised in the OP Configuration + (`.well-known/openid-configuration`) endpoint: + - `/module.php/oidc/authorization` + - `/module.php/oidc/token` + - `/module.php/oidc/userinfo` + - `/module.php/oidc/jwks` + - `/module.php/oidc/end-session` + - `/module.php/oidc/.well-known/openid-configuration` + + Any relying party still calling the old `.php` URLs must be updated to the + new routes. Note that since version 6 the OP has been publishing the new + routes in its discovery metadata, so RPs that read the OP Configuration + dynamically need no change. Medium impact changes: diff --git a/hooks/hook_cron.php b/hooks/hook_cron.php index 133613fc..a823cf35 100644 --- a/hooks/hook_cron.php +++ b/hooks/hook_cron.php @@ -14,20 +14,14 @@ * file that was distributed with this source code. */ +use SimpleSAML\Kernel; use SimpleSAML\Logger; use SimpleSAML\Module\oidc\ModuleConfig; -use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository; -use SimpleSAML\Module\oidc\Repositories\AuthCodeRepository; -use SimpleSAML\Module\oidc\Repositories\IssuerStateRepository; -use SimpleSAML\Module\oidc\Repositories\PushedAuthorizationRequestRepository; -use SimpleSAML\Module\oidc\Repositories\RefreshTokenRepository; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; -use SimpleSAML\Module\oidc\Services\Container; +use SimpleSAML\Module\oidc\Services\ExpiredEntriesCleaner; /** * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException - * @throws \Psr\Container\ContainerExceptionInterface - * @throws \Psr\Container\NotFoundExceptionInterface * @throws \Exception */ function oidc_hook_cron(array &$croninfo): void @@ -51,31 +45,15 @@ function oidc_hook_cron(array &$croninfo): void return; } - $container = new Container(); - try { - /** @var \SimpleSAML\Module\oidc\Repositories\AccessTokenRepository $accessTokenRepository */ - $accessTokenRepository = $container->get(AccessTokenRepository::class); - $accessTokenRepository->removeExpired(); - - /** @var \SimpleSAML\Module\oidc\Repositories\AuthCodeRepository $authTokenRepository */ - $authTokenRepository = $container->get(AuthCodeRepository::class); - $authTokenRepository->removeExpired(); - - /** @var \SimpleSAML\Module\oidc\Repositories\RefreshTokenRepository $refreshTokenRepository */ - $refreshTokenRepository = $container->get(RefreshTokenRepository::class); - $refreshTokenRepository->removeExpired(); - - /** @var \SimpleSAML\Module\oidc\Repositories\IssuerStateRepository $issuerStateRepository */ - $issuerStateRepository = $container->get(IssuerStateRepository::class); - $issuerStateRepository->removeInvalid(); - - /** @var \SimpleSAML\Module\oidc\Repositories\PushedAuthorizationRequestRepository $parRepository */ - $parRepository = $container->get(PushedAuthorizationRequestRepository::class); - $parRepository->removeExpired(); + $kernel = new Kernel(ModuleConfig::MODULE_NAME); + $kernel->boot(); + /** @var \SimpleSAML\Module\oidc\Services\ExpiredEntriesCleaner $cleaner */ + $cleaner = $kernel->getContainer()->get(ExpiredEntriesCleaner::class); + $cleaner->clean(); $croninfo['summary'][] = 'Module `oidc` clean up. Removed expired entries from storage.'; - } catch (Exception $e) { + } catch (Throwable $e) { $message = 'Module `oidc` clean up cron script failed: ' . $e->getMessage(); Logger::warning($message); $croninfo['summary'][] = $message; diff --git a/public/authorize.php b/public/authorize.php deleted file mode 100644 index 45cf4b2f..00000000 --- a/public/authorize.php +++ /dev/null @@ -1,20 +0,0 @@ -services[ModuleConfig::class] = $moduleConfig; - - $loggerService = new LoggerService(); - $this->services[LoggerService::class] = $loggerService; - $authSimpleFactory = new AuthSimpleFactory($moduleConfig); - $this->services[AuthSimpleFactory::class] = $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; - - $helpers = new Helpers(); - $this->services[Helpers::class] = $helpers; - - $csrfProtection = new CsrfProtection( - Translate::noop('Your session has expired. Please return to the home page and try again.'), - $session, - ); - - $formFactory = new FormFactory( - $moduleConfig, - $csrfProtection, - $sspBridge, - $helpers, - ); - $this->services[FormFactory::class] = $formFactory; - - $sessionService = new SessionService($session); - $this->services[SessionService::class] = $sessionService; - - $sessionMessagesService = new SessionMessagesService($session); - $this->services[SessionMessagesService::class] = $sessionMessagesService; - - $oidcMenu = new Menu(); - $this->services[Menu::class] = $oidcMenu; - - $routes = new Routes( - $moduleConfig, - $sspBridge, - ); - $this->services[Routes::class] = $routes; - - $vciContextResolver = new VciContextResolver($moduleConfig, $routes); - $this->services[VciContextResolver::class] = $vciContextResolver; - - $templateFactory = new TemplateFactory( - $simpleSAMLConfiguration, - $moduleConfig, - $oidcMenu, - $sspBridge, - $sessionMessagesService, - $routes, - ); - $this->services[TemplateFactory::class] = $templateFactory; - - $claimSetEntityFactory = new ClaimSetEntityFactory(); - $this->services[ClaimSetEntityFactory::class] = $claimSetEntityFactory; - - $claimTranslatorExtractor = (new ClaimTranslatorExtractorFactory( - $moduleConfig, - $claimSetEntityFactory, - ))->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; - - $stateService = new StateService(); - $this->services[StateService::class] = $stateService; - - $core = new Core(); - $this->services[Core::class] = $core; - $classInstanceBuilder = new ClassInstanceBuilder(); - $this->services[ClassInstanceBuilder::class] = $classInstanceBuilder; - $cacheFactory = new CacheFactory($moduleConfig, $loggerService, $classInstanceBuilder); - $this->services[CacheFactory::class] = $cacheFactory; - $federationCache = $cacheFactory->forFederation(); - $this->services[FederationCache::class] = $federationCache; - $protocolCache = $cacheFactory->forProtocol(); - $this->services[ProtocolCache::class] = $protocolCache; - $federationFactory = new FederationFactory($moduleConfig, $loggerService, $federationCache); - $this->services[FederationFactory::class] = $federationFactory; - $federation = $federationFactory->build(); - $this->services[Federation::class] = $federation; - - $httpFoundationFactory = new HttpFoundationFactory(); - $this->services[HttpFoundationFactory::class] = $httpFoundationFactory; - - $serverRequestFactory = new ServerRequestFactory(); - $this->services[ServerRequestFactoryInterface::class] = $serverRequestFactory; - - $responseFactory = new ResponseFactory(); - $this->services[ResponseFactoryInterface::class] = $responseFactory; - - $streamFactory = new StreamFactory(); - $this->services[StreamFactoryInterface::class] = $streamFactory; - - $uploadedFileFactory = new UploadedFileFactory(); - $this->services[UploadedFileFactoryInterface::class] = $uploadedFileFactory; - - $psrHttpBridge = new PsrHttpBridge( - $httpFoundationFactory, - $serverRequestFactory, - $responseFactory, - $streamFactory, - $uploadedFileFactory, - ); - $this->services[PsrHttpBridge::class] = $psrHttpBridge; - - $clientEntityFactory = new ClientEntityFactory( - $sspBridge, - $helpers, - $moduleConfig, - ); - $this->services[ClientEntityFactory::class] = $clientEntityFactory; - - $database = Database::getInstance(); - $this->services[Database::class] = $database; - - $clientRepository = new ClientRepository( - $moduleConfig, - $database, - $protocolCache, - $clientEntityFactory, - ); - $this->services[ClientRepository::class] = $clientRepository; - - $pushedAuthorizationRequestEntityFactory = new PushedAuthorizationRequestEntityFactory( - $moduleConfig, - $helpers, - ); - $this->services[PushedAuthorizationRequestEntityFactory::class] = $pushedAuthorizationRequestEntityFactory; - - $pushedAuthorizationRequestRepository = new PushedAuthorizationRequestRepository( - $moduleConfig, - $database, - $protocolCache, - $pushedAuthorizationRequestEntityFactory, - $helpers, - ); - $this->services[PushedAuthorizationRequestRepository::class] = $pushedAuthorizationRequestRepository; - - $requestObject = (new RequestObjectFactory($moduleConfig, $loggerService))->build(); - $this->services[RequestObject::class] = $requestObject; - - $requestParamsResolver = new RequestParamsResolver( - $helpers, - $core, - $federation, - $psrHttpBridge, - $requestObject, - $moduleConfig, - $clientRepository, - $pushedAuthorizationRequestRepository, - $loggerService, - ); - $this->services[RequestParamsResolver::class] = $requestParamsResolver; - - $userEntityFactory = new UserEntityFactory($helpers); - $this->services[UserEntityFactory::class] = $userEntityFactory; - - $userRepository = new UserRepository( - $moduleConfig, - $database, - $protocolCache, - $helpers, - $userEntityFactory, - ); - $this->services[UserRepository::class] = $userRepository; - - $scopeEntityFactory = new ScopeEntityFactory(); - $this->services[ScopeEntityFactory::class] = $scopeEntityFactory; - - $authCodeEntityFactory = new AuthCodeEntityFactory( - $helpers, - $scopeEntityFactory, - ); - $this->services[AuthCodeEntityFactory::class] = $authCodeEntityFactory; - - $authCodeRepository = new AuthCodeRepository( - $moduleConfig, - $database, - $protocolCache, - $clientRepository, - $authCodeEntityFactory, - $helpers, - ); - $this->services[AuthCodeRepository::class] = $authCodeRepository; - - $cryptKeyFactory = new CryptKeyFactory($moduleConfig); - - $privateKey = $cryptKeyFactory->buildPrivateKey(); - - $jwsFactory = new JwsFactory($moduleConfig, $loggerService); - $this->services[JwsFactory::class] = $jwsFactory; - - $jws = $jwsFactory->build(); - $this->services[Jws::class] = $jws; - - - $accessTokenEntityFactory = new AccessTokenEntityFactory( - $helpers, - $scopeEntityFactory, - $jws, - $moduleConfig, - ); - $this->services[AccessTokenEntityFactory::class] = $accessTokenEntityFactory; - - $accessTokenRepository = new AccessTokenRepository( - $moduleConfig, - $database, - $protocolCache, - $clientRepository, - $accessTokenEntityFactory, - $helpers, - ); - $this->services[AccessTokenRepository::class] = $accessTokenRepository; - - $refreshTokenEntityFactory = new RefreshTokenEntityFactory($helpers); - $this->services[RefreshTokenEntityFactory::class] = $refreshTokenEntityFactory; - - $refreshTokenRepository = new RefreshTokenRepository( - $moduleConfig, - $database, - $protocolCache, - $accessTokenRepository, - $refreshTokenEntityFactory, - $helpers, - ); - $this->services[RefreshTokenRepository::class] = $refreshTokenRepository; - - $scopeRepository = new ScopeRepository($moduleConfig, $scopeEntityFactory); - $this->services[ScopeRepository::class] = $scopeRepository; - - $allowedOriginRepository = new AllowedOriginRepository( - $moduleConfig, - $database, - $protocolCache, - ); - $this->services[AllowedOriginRepository::class] = $allowedOriginRepository; - - $issuerStateEntityFactory = new IssuerStateEntityFactory( - $moduleConfig, - $helpers, - ); - $this->services[IssuerStateEntityFactory::class] = $issuerStateEntityFactory; - - $issuerStateRepository = new IssuerStateRepository( - $moduleConfig, - $database, - $protocolCache, - $issuerStateEntityFactory, - $helpers, - ); - $this->services[IssuerStateRepository::class] = $issuerStateRepository; - - $databaseMigration = new DatabaseMigration($database); - $this->services[DatabaseMigration::class] = $databaseMigration; - - $authenticationService = new AuthenticationService( - $userRepository, - $authSimpleFactory, - $clientRepository, - $opMetadataService, - $sessionService, - $claimTranslatorExtractor, - $moduleConfig, - $processingChainFactory, - $stateService, - $requestParamsResolver, - $userEntityFactory, - ); - $this->services[AuthenticationService::class] = $authenticationService; - - $codeChallengeVerifiersRepository = new CodeChallengeVerifiersRepository(); - $this->services[CodeChallengeVerifiersRepository::class] = $codeChallengeVerifiersRepository; - - $jwksFactory = new JwksFactory($moduleConfig, $loggerService, $federationCache); - $this->services[JwksFactory::class] = $jwksFactory; - - $jwks = $jwksFactory->build(); - $this->services[Jwks::class] = $jwks; - - $jwksResolver = new JwksResolver($jwks); - $this->services[JwksResolver::class] = $jwksResolver; - $federationParticipationValidator = new FederationParticipationValidator( - $moduleConfig, - $federation, - $loggerService, - ); - $this->services[FederationParticipationValidator::class] = $federationParticipationValidator; - - $authenticatedOAuth2ClientResolver = new AuthenticatedOAuth2ClientResolver( - clientRepository: $clientRepository, - requestParamsResolver: $requestParamsResolver, - loggerService: $loggerService, - psrHttpBridge: $psrHttpBridge, - jwksResolver: $jwksResolver, - moduleConfig: $moduleConfig, - helpers: $helpers, - protocolCache: $protocolCache, - ); - $this->services[AuthenticatedOAuth2ClientResolver::class] = $authenticatedOAuth2ClientResolver; - - $queryResponseMode = new QueryResponseMode(); - $fragmentResponseMode = new FragmentResponseMode(); - $formPostResponseMode = new FormPostResponseMode($templateFactory); - - $requestRules = [ - new StateRule($requestParamsResolver, $helpers), - new ClientRule( - $requestParamsResolver, - $helpers, - $clientRepository, - $moduleConfig, - $clientEntityFactory, - $federation, - $jwksResolver, - $federationParticipationValidator, - $loggerService, - $federationCache, - ), - new ClientRedirectUriRule($requestParamsResolver, $helpers, $moduleConfig), - new RequestObjectRule($requestParamsResolver, $helpers, $jwksResolver, $moduleConfig), - new RequestUriRule( - $requestParamsResolver, - $helpers, - $pushedAuthorizationRequestRepository, - $moduleConfig, - ), - new ResponseModeRule( - $requestParamsResolver, - $helpers, - $moduleConfig, - $queryResponseMode, - $fragmentResponseMode, - $formPostResponseMode, - ), - new PromptRule($requestParamsResolver, $helpers, $authSimpleFactory, $authenticationService, $sspBridge), - new MaxAgeRule($requestParamsResolver, $helpers, $authSimpleFactory, $authenticationService, $sspBridge), - 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, - $jwks, - $core, - ), - new PostLogoutRedirectUriRule($requestParamsResolver, $helpers, $clientRepository), - new UiLocalesRule($requestParamsResolver, $helpers), - new AcrValuesRule($requestParamsResolver, $helpers), - new ScopeOfflineAccessRule($requestParamsResolver, $helpers), - new ClientAuthenticationRule( - $requestParamsResolver, - $helpers, - $authenticatedOAuth2ClientResolver, - ), - new CodeVerifierRule($requestParamsResolver, $helpers), - ]; - $requestRuleManager = new RequestRulesManager($requestRules, $loggerService); - $this->services[RequestRulesManager::class] = $requestRuleManager; - - $idTokenBuilder = new IdTokenBuilder( - $claimTranslatorExtractor, - $core, - $moduleConfig, - ); - $this->services[IdTokenBuilder::class] = $idTokenBuilder; - - $logoutTokenBuilder = new LogoutTokenBuilder($moduleConfig, $loggerService); - $this->services[LogoutTokenBuilder::class] = $logoutTokenBuilder; - - $sessionLogoutTicketStoreDb = new LogoutTicketStoreDb($database); - $this->services[LogoutTicketStoreDb::class] = $sessionLogoutTicketStoreDb; - - $sessionLogoutTicketStoreBuilder = new LogoutTicketStoreBuilder($sessionLogoutTicketStoreDb); - $this->services[LogoutTicketStoreBuilder::class] = $sessionLogoutTicketStoreBuilder; - - $tokenResponseFactory = new TokenResponseFactory( - $moduleConfig, - $userRepository, - $this->services[IdTokenBuilder::class], - $privateKey, - $loggerService, - ); - $this->services[TokenResponse::class] = $tokenResponseFactory->build(); - - $this->services[Helpers::class] = $helpers; - - $refreshTokenIssuer = new RefreshTokenIssuer( - $helpers, - $refreshTokenRepository, - $refreshTokenEntityFactory, - $loggerService, - ); - $this->services[RefreshTokenIssuer::class] = $refreshTokenIssuer; - - $authCodeGrantFactory = new AuthCodeGrantFactory( - $moduleConfig, - $authCodeRepository, - $accessTokenRepository, - $refreshTokenRepository, - $requestRuleManager, - $requestParamsResolver, - $accessTokenEntityFactory, - $authCodeEntityFactory, - $refreshTokenIssuer, - $helpers, - $loggerService, - ); - $this->services[AuthCodeGrant::class] = $authCodeGrantFactory->build(); - - $implicitGrantFactory = new ImplicitGrantFactory( - $moduleConfig, - $this->services[IdTokenBuilder::class], - $requestRuleManager, - $accessTokenRepository, - $requestParamsResolver, - $accessTokenEntityFactory, - ); - $this->services[ImplicitGrant::class] = $implicitGrantFactory->build(); - - $refreshTokenGrantFactory = new RefreshTokenGrantFactory( - $moduleConfig, - $refreshTokenRepository, - $accessTokenEntityFactory, - $refreshTokenIssuer, - ); - $this->services[RefreshTokenGrant::class] = $refreshTokenGrantFactory->build(); - - $preAuthCodeGrantFactory = new PreAuthCodeGrantFactory( - $moduleConfig, - $authCodeRepository, - $accessTokenRepository, - $refreshTokenRepository, - $requestRuleManager, - $requestParamsResolver, - $accessTokenEntityFactory, - $authCodeEntityFactory, - $refreshTokenIssuer, - $helpers, - $loggerService, - ); - $this->services[PreAuthCodeGrant::class] = $preAuthCodeGrantFactory->build(); - - $authorizationServerFactory = new AuthorizationServerFactory( - $moduleConfig, - $clientRepository, - $accessTokenRepository, - $scopeRepository, - $this->services[AuthCodeGrant::class], - $this->services[ImplicitGrant::class], - $this->services[RefreshTokenGrant::class], - $this->services[TokenResponse::class], - $requestRuleManager, - $privateKey, - $this->services[PreAuthCodeGrant::class], - $loggerService, - ); - $this->services[AuthorizationServer::class] = $authorizationServerFactory->build(); - - $bearerTokenValidator = new BearerTokenValidator( - $accessTokenRepository, - $moduleConfig, - $jws, - $jwks, - $loggerService, - ); - $this->services[BearerTokenValidator::class] = $bearerTokenValidator; - - $resourceServer = new ResourceServer( - $bearerTokenValidator, - ); - $this->services[ResourceServer::class] = $resourceServer; - - - $errorResponder = new ErrorResponder($psrHttpBridge); - $this->services[ErrorResponder::class] = $errorResponder; - - $nonceService = new NonceService($jws, $moduleConfig, $loggerService); - $this->services[NonceService::class] = $nonceService; - } - - /** - * @inheritdoc - */ - public function get(string $id): mixed - { - if (false === $this->has($id)) { - throw new class ($id) extends Exception implements NotFoundExceptionInterface { - public function __construct(string $id) - { - parent::__construct("Service not found: $id."); - } - }; - } - - return $this->services[$id]; - } - - /** - * @inheritdoc - */ - public function has(string $id): bool - { - return array_key_exists($id, $this->services); - } -} diff --git a/src/Services/ExpiredEntriesCleaner.php b/src/Services/ExpiredEntriesCleaner.php new file mode 100644 index 00000000..9343c045 --- /dev/null +++ b/src/Services/ExpiredEntriesCleaner.php @@ -0,0 +1,39 @@ +accessTokenRepository->removeExpired(); + $this->authCodeRepository->removeExpired(); + $this->refreshTokenRepository->removeExpired(); + $this->issuerStateRepository->removeInvalid(); + $this->pushedAuthorizationRequestRepository->removeExpired(); + } +} diff --git a/src/Services/RoutingService.php b/src/Services/RoutingService.php deleted file mode 100644 index eeb83f3c..00000000 --- a/src/Services/RoutingService.php +++ /dev/null @@ -1,188 +0,0 @@ -requireAdmin(); - } - - if ($jsonResponse) { - self::enableJsonExceptionResponse(); - } - self::callController(new Container(), $controllerClassname); - } - - /** - * @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 - { - $container = new Container(); - /** @var AuthContextService $authContext */ - $authContext = $container->get(AuthContextService::class); - $authContext->requirePermission($permission); - self::callController($container, $controllerClassname); - } - - /** - * @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 - { - /** @var callable $controller */ - $controller = self::getController($controllerClassname, $container); - $serverRequest = ServerRequestFactory::fromGlobals(); - $response = $controller($serverRequest); - - # TODO sspv2 return Symfony\Component\HttpFoundation\Response (Template instance) in SSP v2 - if ($response instanceof SymfonyResponse) { - if ($response instanceof Template) { - $response->data['messages'] = $container->get(SessionMessagesService::class)->getMessages(); - } - - // If not already handled, allow CORS (for JS clients). - if (!$response->headers->has('Access-Control-Allow-Origin')) { - $response->headers->set('Access-Control-Allow-Origin', '*'); - } - - - $response->send(); - - return; - } - - if ($response instanceof ResponseInterface) { - // If not already handled, allow CORS (for JS clients). - if (!$response->hasHeader('Access-Control-Allow-Origin')) { - $response = $response->withHeader('Access-Control-Allow-Origin', '*'); - } - - $emitter = new SapiEmitter(); - - $emitter->emit($response); - - return; - } - - throw new Error\Exception('Response type not supported: ' . $response::class); - } - - /** - * @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 Error\BadRequest("Controller does not exist: $controllerClassname"); - } - $controllerReflectionClass = new ReflectionClass($controllerClassname); - - $arguments = []; - $constructor = $controllerReflectionClass->getConstructor(); - - if (null !== $constructor) { - foreach ($constructor->getParameters() as $parameter) { - $reflectionClass = $parameter->getClass(); - if (null === $reflectionClass) { - throw new RuntimeException('Parameter not found in container: ' . $parameter->getName()); - } - - $className = $reflectionClass->getName(); - if (false === $container->has($className)) { - throw new RuntimeException('Service not found in container: ' . $className); - } - - $arguments[] = $container->get($className); - } - } - - return $controllerReflectionClass->newInstanceArgs($arguments); - } - - /** - * @return void - */ - protected static function enableJsonExceptionResponse(): void - { - set_exception_handler(function (Throwable $t) { - if ($t instanceof Error\Error) { - // Showing SSP Error will also use SSP logger to log it. - $t->show(); - return; - } elseif ($t instanceof OAuthServerException) { - $response = $t->generateHttpResponse(new Response()); - } else { - $error = []; - $error['error'] = [ - 'code' => 500, - 'message' => $t->getMessage(), - ]; - $response = new JsonResponse($error, 500); - } - - // Log exception using SSP Exception logging feature. - (Error\Exception::fromException($t))->logError(); - - $emitter = new SapiEmitter(); - $emitter->emit($response); - }); - } -} diff --git a/tests/unit/src/Services/ExpiredEntriesCleanerTest.php b/tests/unit/src/Services/ExpiredEntriesCleanerTest.php new file mode 100644 index 00000000..60096674 --- /dev/null +++ b/tests/unit/src/Services/ExpiredEntriesCleanerTest.php @@ -0,0 +1,77 @@ +accessTokenRepositoryMock = $this->createMock(AccessTokenRepository::class); + $this->authCodeRepositoryMock = $this->createMock(AuthCodeRepository::class); + $this->refreshTokenRepositoryMock = $this->createMock(RefreshTokenRepository::class); + $this->issuerStateRepositoryMock = $this->createMock(IssuerStateRepository::class); + $this->pushedAuthorizationRequestRepositoryMock = $this->createMock( + PushedAuthorizationRequestRepository::class, + ); + } + + public function testItIsInitializable(): void + { + $cleaner = new ExpiredEntriesCleaner( + $this->accessTokenRepositoryMock, + $this->authCodeRepositoryMock, + $this->refreshTokenRepositoryMock, + $this->issuerStateRepositoryMock, + $this->pushedAuthorizationRequestRepositoryMock, + ); + + $this->assertInstanceOf(ExpiredEntriesCleaner::class, $cleaner); + } + + public function testCleanRemovesExpiredAndInvalidEntries(): void + { + $this->accessTokenRepositoryMock->expects($this->once()) + ->method('removeExpired'); + + $this->authCodeRepositoryMock->expects($this->once()) + ->method('removeExpired'); + + $this->refreshTokenRepositoryMock->expects($this->once()) + ->method('removeExpired'); + + $this->issuerStateRepositoryMock->expects($this->once()) + ->method('removeInvalid'); + + $this->pushedAuthorizationRequestRepositoryMock->expects($this->once()) + ->method('removeExpired'); + + $cleaner = new ExpiredEntriesCleaner( + $this->accessTokenRepositoryMock, + $this->authCodeRepositoryMock, + $this->refreshTokenRepositoryMock, + $this->issuerStateRepositoryMock, + $this->pushedAuthorizationRequestRepositoryMock, + ); + + $cleaner->clean(); + } +}