From a6f6c6b1452577739fa98885aa9b2a14a0c9f4a0 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sat, 13 Jun 2026 18:29:52 +0200 Subject: [PATCH 1/8] Propagate Symfony responses throughout the codebase (src & modules) --- modules/admin/src/Controller/Config.php | 31 ++-- modules/admin/src/Controller/Federation.php | 19 ++- modules/admin/src/Controller/Test.php | 13 +- modules/core/src/Auth/Process/Cardinality.php | 4 +- .../src/Auth/Process/CardinalitySingle.php | 4 +- .../src/Auth/Process/WarnShortSSOInterval.php | 5 +- .../Auth/Source/AbstractSourceSelector.php | 19 ++- modules/core/src/Auth/UserPassBase.php | 19 +-- modules/core/src/Auth/UserPassOrgBase.php | 15 +- modules/core/src/Controller/ErrorReport.php | 5 +- modules/core/src/Controller/Exception.php | 14 +- modules/core/src/Controller/Login.php | 12 +- modules/core/src/Controller/Logout.php | 43 +++--- modules/core/src/Controller/Redirection.php | 2 +- modules/cron/src/Controller/Cron.php | 17 ++- modules/debugsp/src/Controller/Test.php | 10 +- .../src/Auth/Process/RedirectTest.php | 3 +- .../exampleauth/src/Auth/Source/External.php | 35 ++--- .../src/Auth/Source/StaticSource.php | 6 +- .../src/Controller/ExampleAuth.php | 20 ++- .../multiauth/src/Auth/Source/MultiAuth.php | 46 +++--- .../src/Controller/DiscoController.php | 11 +- .../Process/ExpectedAuthnContextClassRef.php | 3 +- .../saml/src/Auth/Process/NameIDAttribute.php | 4 +- modules/saml/src/Auth/Process/PairwiseID.php | 4 +- .../src/Auth/Process/PersistentNameID.php | 4 +- .../Process/PersistentNameID2TargetedID.php | 6 +- .../src/Auth/Process/SQLPersistentNameID.php | 2 +- modules/saml/src/Auth/Process/SubjectID.php | 8 +- .../saml/src/Auth/Process/TransientNameID.php | 4 +- modules/saml/src/Auth/Source/SP.php | 106 ++++++++------ modules/saml/src/Controller/Disco.php | 12 +- modules/saml/src/Controller/Metadata.php | 10 +- modules/saml/src/Controller/Proxy.php | 13 +- .../saml/src/Controller/ServiceProvider.php | 14 +- modules/saml/src/Controller/SingleLogout.php | 8 +- .../src/Controller/WebBrowserSingleSignOn.php | 8 +- modules/saml/src/Error.php | 10 +- modules/saml/src/IdP/SAML2.php | 132 +++++++++--------- modules/saml/src/Message.php | 2 +- public/admin/index.php | 3 +- public/index.php | 4 +- src/SimpleSAML/Auth/ProcessingChain.php | 16 ++- src/SimpleSAML/Auth/Simple.php | 40 +++--- src/SimpleSAML/Auth/Source.php | 75 ++++++---- src/SimpleSAML/Auth/State.php | 15 +- src/SimpleSAML/Configuration.php | 8 +- src/SimpleSAML/Error/Error.php | 10 +- src/SimpleSAML/IdP.php | 119 +++++++++------- src/SimpleSAML/IdP/IFrameLogoutHandler.php | 21 +-- src/SimpleSAML/IdP/LogoutHandlerInterface.php | 9 +- .../IdP/TraditionalLogoutHandler.php | 37 ++--- .../Metadata/MetaDataStorageHandler.php | 26 ++-- .../MetaDataStorageHandlerFlatFile.php | 6 +- .../Metadata/MetaDataStorageHandlerPdo.php | 13 +- .../MetaDataStorageHandlerSerialize.php | 4 +- .../Metadata/MetaDataStorageHandlerXML.php | 6 +- .../Metadata/MetaDataStorageSource.php | 14 +- src/SimpleSAML/Metadata/Sources/MDQ.php | 6 +- src/SimpleSAML/Utils/Auth.php | 10 +- src/SimpleSAML/Utils/Config/Metadata.php | 8 +- src/SimpleSAML/Utils/HTTP.php | 86 ++++-------- src/SimpleSAML/Utils/XML.php | 2 +- src/SimpleSAML/XHTML/IdPDisco.php | 79 +++++++---- 64 files changed, 722 insertions(+), 598 deletions(-) diff --git a/modules/admin/src/Controller/Config.php b/modules/admin/src/Controller/Config.php index 09b26c031b..1c3d66cda9 100644 --- a/modules/admin/src/Controller/Config.php +++ b/modules/admin/src/Controller/Config.php @@ -15,6 +15,7 @@ use SimpleSAML\Utils; use SimpleSAML\XHTML\Template; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; @@ -79,12 +80,14 @@ public function setAuthUtils(Utils\Auth $authUtils): void * Display basic diagnostic information on hostname, port and protocol. * * @param \Symfony\Component\HttpFoundation\Request $request The current request. - * - * @return \SimpleSAML\XHTML\Template + * @return \Symfony\Component\HttpFoundation\Response */ - public function diagnostics(Request $request): Template + public function diagnostics(Request $request): Response { - $this->authUtils->requireAdmin(); + $response = $this->authUtils->requireAdmin(); + if ($response instanceof Response) { + return $response; + } $t = new Template($this->config, 'admin:diagnostics.twig'); $t->data = [ @@ -113,12 +116,14 @@ public function diagnostics(Request $request): Template * Display the main admin page. * * @param \Symfony\Component\HttpFoundation\Request $request The current request. - * - * @return \SimpleSAML\XHTML\Template + * @return \Symfony\Components\HttpFoundation\Response */ - public function main(/** @scrutinizer ignore-unused */ Request $request): Template + public function main(/** @scrutinizer ignore-unused */ Request $request): Response { - $this->authUtils->requireAdmin(); + $response = $this->authUtils->requireAdmin(); + if ($response instanceof Response) { + return $response; + } $t = new Template($this->config, 'admin:config.twig'); $t->data = [ @@ -172,12 +177,14 @@ protected function getModuleList(): array * Display the output of phpinfo(). * * @param \Symfony\Component\HttpFoundation\Request $request The current request. - * - * @return \Symfony\Component\HttpFoundation\StreamedResponse + * @return \Symfony\Component\HttpFoundation\Response */ - public function phpinfo(/** @scrutinizer ignore-unused */ Request $request): StreamedResponse + public function phpinfo(/** @scrutinizer ignore-unused */ Request $request): Response { - $this->authUtils->requireAdmin(); + $response = $this->authUtils->requireAdmin(); + if ($response instanceof Response) { + return $response; + } $response = new StreamedResponse('phpinfo'); $response->headers->set( diff --git a/modules/admin/src/Controller/Federation.php b/modules/admin/src/Controller/Federation.php index f6486e2b32..9792fdab5b 100644 --- a/modules/admin/src/Controller/Federation.php +++ b/modules/admin/src/Controller/Federation.php @@ -5,7 +5,6 @@ namespace SimpleSAML\Module\admin\Controller; use Exception; -use SAML2\Constants as C; use SimpleSAML\Assert\Assert; use SimpleSAML\Auth; use SimpleSAML\Configuration; @@ -20,6 +19,7 @@ use SimpleSAML\Module\adfs\IdP\ADFS as ADFS_IdP; use SimpleSAML\Module\admin\Event\FederationPageEvent; use SimpleSAML\Module\saml\IdP\SAML2 as SAML2_IdP; +use SimpleSAML\SAML2\Constants as C; use SimpleSAML\Utils; use SimpleSAML\XHTML\Template; use Symfony\Component\HttpFoundation\Request; @@ -77,7 +77,7 @@ public function __construct( protected Configuration $config, ) { $this->menu = new Menu(); - $this->mdHandler = MetaDataStorageHandler::getMetadataHandler(); + $this->mdHandler = MetaDataStorageHandler::getMetadataHandler($config); $this->authUtils = new Utils\Auth(); $this->cryptoUtils = new Utils\Crypto(); } @@ -120,13 +120,16 @@ public function setMetadataStorageHandler(MetadataStorageHandler $mdHandler): vo * Display the federation page. * * @param \Symfony\Component\HttpFoundation\Request $request - * @return \SimpleSAML\XHTML\Template + * @return \Symfony\Component\HttpFoundation\Response * @throws \SimpleSAML\Error\Exception * @throws \SimpleSAML\Error\Exception */ - public function main(/** @scrutinizer ignore-unused */ Request $request): Template + public function main(/** @scrutinizer ignore-unused */ Request $request): Response { - $this->authUtils->requireAdmin(); + $response = $this->authUtils->requireAdmin(); + if ($response instanceof Response) { + return $response; + } // initialize basic metadata array $hostedSPs = $this->getHostedSP(); @@ -507,12 +510,14 @@ public function metadataConverter(Request $request): Template * Download a certificate for a given entity. * * @param \Symfony\Component\HttpFoundation\Request $request The current request. - * * @return \Symfony\Component\HttpFoundation\Response PEM-encoded certificate. */ public function downloadCert(Request $request): Response { - $this->authUtils->requireAdmin(); + $response = $this->authUtils->requireAdmin(); + if ($response instanceof Response) { + return $response; + } $set = $request->query->get('set'); $prefix = $request->query->get('prefix', ''); diff --git a/modules/admin/src/Controller/Test.php b/modules/admin/src/Controller/Test.php index 7e45977bdf..29c02e9963 100644 --- a/modules/admin/src/Controller/Test.php +++ b/modules/admin/src/Controller/Test.php @@ -7,7 +7,6 @@ use SimpleSAML\Assert\Assert; use SimpleSAML\Auth; use SimpleSAML\Configuration; -use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Locale\Translate; use SimpleSAML\Module; use SimpleSAML\Session; @@ -99,11 +98,15 @@ public function setAuthState(Auth\State $authState): void * * @param \Symfony\Component\HttpFoundation\Request $request * @param string|null $as - * @return \SimpleSAML\XHTML\Template|\SimpleSAML\HTTP\RunnableResponse + * @return \Symfony\Component\HttpFoundation\Response */ public function main(Request $request, ?string $as = null): Response { - $this->authUtils->requireAdmin(); + $response = $this->authUtils->requireAdmin(); + if ($response instanceof Response) { + return $response; + } + if (is_null($as)) { $t = new Template($this->config, 'admin:authsource_list.twig'); $t->data = [ @@ -114,7 +117,7 @@ public function main(Request $request, ?string $as = null): Response $authsource = new $this->authSimple($as); if (!is_null($request->query->get('logout'))) { - return new RunnableResponse([$authsource, 'logout'], [Module::getModuleURL('admin/logout')]); + return $authsource->logout(Module::getModuleURL('admin/logout')); } elseif (!is_null($request->query->get(Auth\State::EXCEPTION_PARAM))) { // This is just a simple example of an error /** @var array $state */ @@ -130,7 +133,7 @@ public function main(Request $request, ?string $as = null): Response 'ReturnTo' => $url, Auth\State::RESTART => $url, ]; - return new RunnableResponse([$authsource, 'login'], [$params]); + return $authsource->login($params); } $attributes = $authsource->getAttributes(); diff --git a/modules/core/src/Auth/Process/Cardinality.php b/modules/core/src/Auth/Process/Cardinality.php index e22686804e..6678c694e5 100644 --- a/modules/core/src/Auth/Process/Cardinality.php +++ b/modules/core/src/Auth/Process/Cardinality.php @@ -195,8 +195,8 @@ public function process(array &$state): void if (array_key_exists('core:cardinality:errorAttributes', $state)) { $id = Auth\State::saveState($state, 'core:cardinality'); $url = Module::getModuleURL('core/error/cardinality'); - $this->httpUtils->redirectTrustedURL($url, ['StateId' => $id]); - return; + $response = $httpUtils->redirectTrustedURL($url, ['StateId' => $id]); + $response->send(); } } } diff --git a/modules/core/src/Auth/Process/CardinalitySingle.php b/modules/core/src/Auth/Process/CardinalitySingle.php index c559e295a2..c0a206f2f3 100644 --- a/modules/core/src/Auth/Process/CardinalitySingle.php +++ b/modules/core/src/Auth/Process/CardinalitySingle.php @@ -122,8 +122,8 @@ public function process(array &$state): void if (array_key_exists('core:cardinality:errorAttributes', $state)) { $id = Auth\State::saveState($state, 'core:cardinality'); $url = Module::getModuleURL('core/error/cardinality'); - $this->httpUtils->redirectTrustedURL($url, ['StateId' => $id]); - return; + $response = $httpUtils->redirectTrustedURL($url, ['StateId' => $id]); + $response->send(); } } } diff --git a/modules/core/src/Auth/Process/WarnShortSSOInterval.php b/modules/core/src/Auth/Process/WarnShortSSOInterval.php index 0f3449371e..47f713959d 100644 --- a/modules/core/src/Auth/Process/WarnShortSSOInterval.php +++ b/modules/core/src/Auth/Process/WarnShortSSOInterval.php @@ -7,7 +7,6 @@ use SimpleSAML\Auth; use SimpleSAML\Logger; use SimpleSAML\Module; -use SimpleSAML\Utils; /** * Give a warning to the user if we receive multiple requests in a short time. @@ -52,7 +51,7 @@ public function process(array &$state): void // Save state and redirect $id = Auth\State::saveState($state, 'core:short_sso_interval'); $url = Module::getModuleURL('core/short_sso_interval'); - $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($url, ['StateId' => $id]); + $response = $httpUtils->redirectTrustedURL($url, ['StateId' => $id]); + $response->send(); } } diff --git a/modules/core/src/Auth/Source/AbstractSourceSelector.php b/modules/core/src/Auth/Source/AbstractSourceSelector.php index 6098dde907..fdde74ae05 100644 --- a/modules/core/src/Auth/Source/AbstractSourceSelector.php +++ b/modules/core/src/Auth/Source/AbstractSourceSelector.php @@ -8,6 +8,8 @@ use SimpleSAML\Auth; use SimpleSAML\Configuration; use SimpleSAML\Error; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * Authentication source which delegates authentication to secondary @@ -51,9 +53,10 @@ public function __construct(array $info, array $config) * save the state, and at a later stage, load the state, update it with the authentication * information about the user, and call completeAuth with the state array. * + * @param \Symfony\Component\HttpFoundation\Request $request The current request * @param array &$state Information about the current authentication. */ - public function authenticate(array &$state): void + public function authenticate(Request $request, array &$state): ?Response { $source = $this->selectAuthSource($state); $as = Auth\Source::getById($source); @@ -62,19 +65,23 @@ public function authenticate(array &$state): void } $state['sourceSelector:selected'] = $source; - static::doAuthentication($as, $state); + return parent::doAuthentication($request, $as, $state); } /** + * @param \Symfony\Component\HttpFoundation\Request $request * @param \SimpleSAML\Auth\Source $as * @param array $state - * @return void + * @return \Symfony\Component\HttpFoundation\Response|null */ - public static function doAuthentication(Auth\Source $as, array &$state): void + public static function doAuthentication(Request $request, Auth\Source $as, array &$state): ?Response { try { - $as->authenticate($state); + $response = $as->authenticate($request, $state); + if ($response instanceof Response) { + return $response; + } } catch (Error\Exception $e) { Auth\State::throwException($state, $e); } catch (Exception $e) { @@ -82,7 +89,7 @@ public static function doAuthentication(Auth\Source $as, array &$state): void Auth\State::throwException($state, $e); } - Auth\Source::completeAuth($state); + return static::completeAuth($state); } diff --git a/modules/core/src/Auth/UserPassBase.php b/modules/core/src/Auth/UserPassBase.php index f96bb22e65..caa83323d7 100644 --- a/modules/core/src/Auth/UserPassBase.php +++ b/modules/core/src/Auth/UserPassBase.php @@ -12,6 +12,8 @@ use SimpleSAML\Logger; use SimpleSAML\Module; use SimpleSAML\Utils; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * Helper class for username/password authentication. @@ -194,9 +196,11 @@ public function isRememberMeChecked(): bool * This function saves the information about the login, and redirects to a * login page. * + * @param \Symfony\Component\HttpFoundation\Request $request The current request * @param array &$state Information about the current authentication. + * @param \Symfony\Component\HttpFoundation\Response|null */ - public function authenticate(array &$state): void + public function authenticate(Request $request, array &$state): ?Response { /* * Save the identifier of this authentication source, so that we can @@ -236,7 +240,7 @@ public function authenticate(array &$state): void $attributes = $this->login($username, $password); $state['Attributes'] = $attributes; - return; + return null; } // Save the $state-array, so that we can restore it after a redirect @@ -248,11 +252,9 @@ public function authenticate(array &$state): void */ $url = Module::getModuleURL('core/loginuserpass'); $params = ['AuthState' => $id]; - $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($url, $params); - // The previous function never returns, so this code is never executed. - Assert::true(false); + $httpUtils = new Utils\HTTP(); + return $httpUtils->redirectTrustedURL($url, $params); } @@ -282,8 +284,9 @@ abstract protected function login(string $username, string $password): array; * @param string $authStateId The identifier of the authentication state. * @param string $username The username the user wrote. * @param string $password The password the user wrote. + * @return \Symfony\Component\HttpFoundation\Response */ - public static function handleLogin(string $authStateId, string $username, string $password): void + public static function handleLogin(string $authStateId, string $username, string $password): Response { // Here we retrieve the state array we saved in the authenticate-function. $state = Auth\State::loadState($authStateId, self::STAGEID); @@ -316,6 +319,6 @@ public static function handleLogin(string $authStateId, string $username, string $state['Attributes'] = $attributes; // Return control to SimpleSAMLphp after successful authentication. - Auth\Source::completeAuth($state); + parent::completeAuth($state); } } diff --git a/modules/core/src/Auth/UserPassOrgBase.php b/modules/core/src/Auth/UserPassOrgBase.php index 1f52b850f9..b7360eb3e6 100644 --- a/modules/core/src/Auth/UserPassOrgBase.php +++ b/modules/core/src/Auth/UserPassOrgBase.php @@ -10,6 +10,8 @@ use SimpleSAML\Logger; use SimpleSAML\Module; use SimpleSAML\Utils; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * Helper class for username/password/organization authentication. @@ -206,9 +208,11 @@ public function getRememberOrganizationChecked(): bool * This function saves the information about the login, and redirects to a * login page. * + * @param \Symfony\Component\HttpFoundation\Request The current request * @param array &$state Information about the current authentication. + * @param \Symfony\Component\HttpFoundation\Response|null */ - public function authenticate(array &$state): void + public function authenticate(Request $request, array &$state): ?Response { // We are going to need the authId in order to retrieve this authentication source later $state[self::AUTHID] = $this->authId; @@ -217,8 +221,9 @@ public function authenticate(array &$state): void $url = Module::getModuleURL('core/loginuserpassorg'); $params = ['AuthState' => $id]; + $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($url, $params); + return $httpUtils->redirectTrustedURL($url, $params); } @@ -259,17 +264,19 @@ abstract protected function getOrganizations(): array; * enters a username and password. On success, it will not return. On wrong * username/password failure, and other errors, it will throw an exception. * + * @param \Symfony\Component\HttpFoundation\Request $request The current request * @param string $authStateId The identifier of the authentication state. * @param string $username The username the user wrote. * @param string $password The password the user wrote. * @param string $organization The id of the organization the user chose. */ public static function handleLogin( + Request $request, string $authStateId, string $username, string $password, string $organization, - ): void { + ): Response { /* Retrieve the authentication state. */ $state = Auth\State::loadState($authStateId, self::STAGEID); @@ -314,7 +321,7 @@ public static function handleLogin( $state['PersistentAuthData'][] = self::ORGID; $state['Attributes'] = $attributes; - Auth\Source::completeAuth($state); + return parent::completeAuth($state); } diff --git a/modules/core/src/Controller/ErrorReport.php b/modules/core/src/Controller/ErrorReport.php index a5a9d6ea9b..dc88fc7e79 100644 --- a/modules/core/src/Controller/ErrorReport.php +++ b/modules/core/src/Controller/ErrorReport.php @@ -7,7 +7,6 @@ use Exception as BuiltinException; use SimpleSAML\Configuration; use SimpleSAML\Error; -use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Logger; use SimpleSAML\Session; use SimpleSAML\Utils; @@ -45,7 +44,7 @@ public function __construct( /** * @param \Symfony\Component\HttpFoundation\Request $request - * @return \SimpleSAML\XHTML\Template|\SimpleSAML\HTTP\RunnableResponse + * @return \Symfony\Component\HttpFoundation\Response */ public function main(Request $request): Response { @@ -103,6 +102,6 @@ public function main(Request $request): Response // redirect the user back to this page to clear the POST request $httpUtils = new Utils\HTTP(); - return new RunnableResponse([$httpUtils, 'redirectTrustedURL'], [$httpUtils->getSelfURLNoQuery()]); + return $httpUtils->redirectTrustedURL($httpUtils->getSelfURLNoQuery()); } } diff --git a/modules/core/src/Controller/Exception.php b/modules/core/src/Controller/Exception.php index a9fcfc5c53..c9bb2c1704 100644 --- a/modules/core/src/Controller/Exception.php +++ b/modules/core/src/Controller/Exception.php @@ -122,8 +122,7 @@ public function error(Request $request, string $code): Response * * @param \Symfony\Component\HttpFoundation\Request $request The request that lead to this login operation. * @throws \SimpleSAML\Error\BadRequest - * @return \SimpleSAML\XHTML\Template|\Symfony\Component\HttpFoundation\RedirectResponse - * An HTML template or a redirection if we are not authenticated. + * @return \SimpleSAML\XHTML\Template An HTML template */ public function cardinality(Request $request): Response { @@ -155,7 +154,7 @@ public function cardinality(Request $request): Response * Show missing cookie error. * * @param \Symfony\Component\HttpFoundation\Request $request The request that lead to this login operation. - * @return \SimpleSAML\XHTML\Template|\Symfony\Component\HttpFoundation\RedirectResponse + * @return \Symfony\Component\HttpFoundation\Response * An HTML template or a redirection if we are not authenticated. */ public function nocookie(Request $request): Response @@ -178,12 +177,7 @@ public function nocookie(Request $request): Response * doing it previously. * * @param \Symfony\Component\HttpFoundation\Request $request The request that lead to this login operation. - * - * @return ( - * \SimpleSAML\XHTML\Template| - * \SimpleSAML\HTTP\RunnableResponse| - * \Symfony\Component\HttpFoundation\RedirectResponse - * ) An HTML template, a redirect or a "runnable" response. + * @return \Symfony\Component\HttpFoundation\Response An HTML template, a redirect or a response. * * @throws \SimpleSAML\Error\BadRequest */ @@ -199,7 +193,7 @@ public function shortSsoInterval(Request $request): Response $continue = $request->query->get('continue', false); if ($continue !== false) { // The user has pressed the continue/retry-button - Auth\ProcessingChain::resumeProcessing($state); + return Auth\ProcessingChain::resumeProcessing($state); } $t = new Template($this->config, 'core:short_sso_interval.twig'); diff --git a/modules/core/src/Controller/Login.php b/modules/core/src/Controller/Login.php index 4bb708883b..1fb97ec41e 100644 --- a/modules/core/src/Controller/Login.php +++ b/modules/core/src/Controller/Login.php @@ -101,6 +101,7 @@ public function welcome(): Template * username/password authentication. * * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Symfony\Component\HttpFoudnation\Response */ public function loginuserpass(Request $request): Response { @@ -147,6 +148,7 @@ public static function registerErrorCodeClass(ErrorCodes $ecc): void * @param \Symfony\Component\HttpFoundation\Request $request * @param \SimpleSAML\Module\core\Auth\UserPassBase|\SimpleSAML\Module\core\Auth\UserPassOrgBase $source * @param array $state + * @return \Symfony\Component\HttpFoundation\Response */ private function handleLogin(Request $request, UserPassBase|UserPassOrgBase $source, array $state): Response { @@ -238,9 +240,9 @@ private function handleLogin(Request $request, UserPassBase|UserPassOrgBase $sou try { if ($source instanceof UserPassOrgBase) { - $source::handleLogin($authStateId, $username, $password, $organization); + return $source::handleLogin($authStateId, $username, $password, $organization); } else { - $source::handleLogin($authStateId, $username, $password); + return $source::handleLogin($authStateId, $username, $password); } } catch (Error\Error $e) { // Login failed. Extract error code and parameters, to display the error @@ -362,6 +364,7 @@ private function handleLogin(Request $request, UserPassBase|UserPassOrgBase $sou * username/password/organization authentication. * * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Symfony\Component\HttpFoundation\Response */ public function loginuserpassorg(Request $request): Response { @@ -506,8 +509,9 @@ private function getReturnPath(Request $request): string * This clears the user's IdP discovery choices. * * @param \Symfony\Component\HttpFoundation\Request $request The request that lead to this login operation. + * @return \Symfony\Component\HttpFoundation\RedirectResponse */ - public function cleardiscochoices(Request $request): void + public function cleardiscochoices(Request $request): RedirectResponse { $httpUtils = new Utils\HTTP(); @@ -527,6 +531,6 @@ public function cleardiscochoices(Request $request): void $returnTo = $this->getReturnPath($request); // Redirect to destination. - $httpUtils->redirectTrustedURL($returnTo); + return $httpUtils->redirectTrustedURL($returnTo); } } diff --git a/modules/core/src/Controller/Logout.php b/modules/core/src/Controller/Logout.php index aef2fa3645..baa6e6056f 100644 --- a/modules/core/src/Controller/Logout.php +++ b/modules/core/src/Controller/Logout.php @@ -19,6 +19,7 @@ use SimpleSAML\Utils; use SimpleSAML\XHTML\Template; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use function call_user_func; use function in_array; @@ -72,21 +73,16 @@ public function setAuthState(Auth\State $authState): void /** * Log the user out of a given authentication source. * - * @param \Symfony\Components\HttpFoundation\Request $request The request that lead to this logout operation. + * @param \Symfony\Component\HttpFoundation\Request $request The request that lead to this logout operation. * @param string $as The name of the auth source. - * - * @return \SimpleSAML\HTTP\RunnableResponse A runnable response which will actually perform logout. + * @return \Symfony\Component\HttpFoundation\Response * * @throws \SimpleSAML\Error\CriticalConfigurationError */ - public function logout(Request $request, string $as): RunnableResponse + public function logout(Request $request, string $as): Response { $auth = new Auth\Simple($as); - $returnTo = $this->getReturnPath($request); - return new RunnableResponse( - [$auth, 'logout'], - [$returnTo], - ); + return $auth->login($this->getReturnPath($request)); } @@ -113,9 +109,9 @@ private function getReturnPath(Request $request): string /** * @param \Symfony\Component\HttpFoundation\Request $request The request that lead to this logout operation. - * @return \SimpleSAML\HTTP\RunnableResponse + * @return \Symfony\Component\HttpFoundation\Response */ - public function logoutIframeDone(Request $request): RunnableResponse + public function logoutIframeDone(Request $request): Response { if (!$request->query->has('id')) { throw new Error\BadRequest('Missing required parameter: id'); @@ -123,7 +119,7 @@ public function logoutIframeDone(Request $request): RunnableResponse $id = $request->query->get('id'); $state = $this->authState::loadState($id, 'core:Logout-IFrame'); - $idp = IdP::getByState($state); + $idp = IdP::getByState($this->config, $state); $associations = $idp->getAssociations(); @@ -174,7 +170,7 @@ public function logoutIframeDone(Request $request): RunnableResponse } // we are done - return new RunnableResponse([$idp, 'finishLogout'], [$state]); + return $idp->finishLogout($state); } @@ -188,7 +184,7 @@ public function logoutIframePost(Request $request): RunnableResponse throw new Error\BadRequest('Missing required parameter: idp'); } - $idp = IdP::getById($request->query->get('idp')); + $idp = IdP::getById($this->config, $request->query->get('idp')); if (!$request->query->has('association')) { throw new Error\BadRequest('Missing required parameter: association'); @@ -206,7 +202,7 @@ public function logoutIframePost(Request $request): RunnableResponse } $association = $associations[$assocId]; - $metadata = MetaDataStorageHandler::getMetadataHandler(); + $metadata = MetaDataStorageHandler::getMetadataHandler($this->config); $idpMetadata = $idp->getConfig(); $spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote'); @@ -269,8 +265,8 @@ public function logoutIframe(Request $request): Template } $state = $this->authState::loadState($id, 'core:Logout-IFrame'); - $idp = IdP::getByState($state); - $mdh = MetaDataStorageHandler::getMetadataHandler(); + $idp = IdP::getByState($this->config, $state); + $mdh = MetaDataStorageHandler::getMetadataHandler($this->config); if ($type !== 'init') { // update association state @@ -304,7 +300,7 @@ public function logoutIframe(Request $request): Template if (!isset($sp['core:Logout-IFrame:Timeout'])) { if (method_exists($sp['Handler'], 'getAssociationConfig')) { - $assocIdP = IdP::getByState($sp); + $assocIdP = IdP::getByState($this->config, $sp); $assocConfig = call_user_func([$sp['Handler'], 'getAssociationConfig'], $assocIdP, $sp); $timeout = $assocConfig->getOptionalInteger('core:logout-timeout', 5); $sp['core:Logout-IFrame:Timeout'] = $timeout + time(); @@ -323,7 +319,7 @@ public function logoutIframe(Request $request): Template } try { - $assocIdP = IdP::getByState($sp); + $assocIdP = IdP::getByState($this->config, $sp); $url = call_user_func([$sp['Handler'], 'getLogoutURL'], $assocIdP, $sp, null); $sp['core:Logout-IFrame:URL'] = $url; } catch (BuiltinException $e) { @@ -392,9 +388,9 @@ public function logoutIframe(Request $request): Template /** * @param \Symfony\Component\HttpFoundation\Request $request The request that lead to this logout operation. - * @return \SimpleSAML\HTTP\RunnableResponse + * @return \Symfony\Component\HttpFoundation\Response */ - public function resumeLogout(Request $request): RunnableResponse + public function resumeLogout(Request $request): Response { if (!$request->query->has('id')) { throw new Error\BadRequest('Missing required parameter: id'); @@ -402,9 +398,10 @@ public function resumeLogout(Request $request): RunnableResponse $id = $request->query->get('id'); $state = $this->authState::loadState($id, 'core:Logout:afterbridge'); - $idp = IdP::getByState($state); + $idp = IdP::getByState($this->config, $state); $assocId = $state['core:TerminatedAssocId']; - return new RunnableResponse([$idp->getLogoutHandler(), 'startLogout'], [&$state, $assocId]); + $logoutHandler = $idp->getLogoutHandler(); + return $logoutHandler->startLogout($state, $assocId); } } diff --git a/modules/core/src/Controller/Redirection.php b/modules/core/src/Controller/Redirection.php index f2e2690c8c..e33dc430a0 100644 --- a/modules/core/src/Controller/Redirection.php +++ b/modules/core/src/Controller/Redirection.php @@ -47,7 +47,7 @@ public function __construct( * * @param \Symfony\Component\HttpFoundation\Request $request The request that lead to this login operation. * @throws \SimpleSAML\Error\BadRequest - * @return \SimpleSAML\XHTML\Template|\Symfony\Component\HttpFoundation\RedirectResponse + * @return \Symfony\Component\HttpFoundation\RedirectResponse * An HTML template or a redirection if we are not authenticated. */ public function postredirect(Request $request): Response diff --git a/modules/cron/src/Controller/Cron.php b/modules/cron/src/Controller/Cron.php index 3d6c66807f..337b5a60c0 100644 --- a/modules/cron/src/Controller/Cron.php +++ b/modules/cron/src/Controller/Cron.php @@ -66,12 +66,15 @@ public function setAuthUtils(Utils\Auth $authUtils): void /** * Show cron info. * - * @return \SimpleSAML\XHTML\Template + * @return \Symfony\Component\HttpFoundation\Response * An HTML template or a redirection if we are not authenticated. */ public function info(): Template { - $this->authUtils->requireAdmin(); + $response = $this->authUtils->requireAdmin(); + if ($response instanceof Response) { + return $response; + } $key = $this->cronconfig->getOptionalString('key', 'secret'); $tags = $this->cronconfig->getOptionalArray('allowed_tags', []); @@ -95,6 +98,7 @@ public function info(): Template $t = new Template($this->config, 'cron:croninfo.twig'); $t->data['urls'] = $urls; + return $t; } @@ -104,16 +108,16 @@ public function info(): Template * * This controller will start a cron operation * + * @param \Symfony\Component\HttpFoundation\Request $request * @param string $tag The tag * @param string $key The secret key * @param string $output The output format, defaulting to xhtml * - * @return \SimpleSAML\XHTML\Template|\Symfony\Component\HttpFoundation\Response - * An HTML template, a redirect or a "runnable" response. + * @return \Symfony\Component\HttpFoundation\Response An HTML template * * @throws \SimpleSAML\Error\Exception */ - public function run(string $tag, string $key, string $output = 'xhtml'): Response + public function run(Request $request, string $tag, string $key, string $output = 'xhtml'): Response { $configKey = $this->cronconfig->getString('key'); @@ -170,6 +174,7 @@ public function run(string $tag, string $key, string $output = 'xhtml'): Respons $t->data['summary'] = $summary; return $t; } - return new Response(); + + throw new Error\Exception('Unknown output type.'); } } diff --git a/modules/debugsp/src/Controller/Test.php b/modules/debugsp/src/Controller/Test.php index 444318271b..d4d2c77ce9 100644 --- a/modules/debugsp/src/Controller/Test.php +++ b/modules/debugsp/src/Controller/Test.php @@ -8,7 +8,6 @@ use SimpleSAML\Auth; use SimpleSAML\Configuration; use SimpleSAML\Error; -use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Module; use SimpleSAML\Session; use SimpleSAML\Utils; @@ -102,7 +101,7 @@ private function makeSPList(Request $request, ?string $as = null): Response * * @param \Symfony\Component\HttpFoundation\Request $request * @param string|null $as - * @return \SimpleSAML\XHTML\Template|\SimpleSAML\HTTP\RunnableResponse + * @return \Symfony\Component\HttpFoundation\Response */ public function main(Request $request, ?string $as = null): Response { @@ -127,7 +126,7 @@ public function main(Request $request, ?string $as = null): Response } if (!is_null($request->query->get('logout'))) { - return new RunnableResponse([$authsource, 'logout'], [Module::getModuleURL('debugsp/logout')]); + return $authsource->logout(Module::getModuleURL('debugsp/logout')); } elseif (!is_null($request->query->get(Auth\State::EXCEPTION_PARAM))) { // This is just a simple example of an error /** @var array $state */ @@ -143,7 +142,8 @@ public function main(Request $request, ?string $as = null): Response 'ReturnTo' => $url, Auth\State::RESTART => $url, ]; - return new RunnableResponse([$authsource, 'login'], [$params]); + + return $authsource->login($params); } $attributes = $authsource->getAttributes(); @@ -172,7 +172,7 @@ public function main(Request $request, ?string $as = null): Response * Page to show after logout completed * * @param \Symfony\Component\HttpFoundation\Request $request - * @return \SimpleSAML\XHTML\Template + * @return \Symfony\Component\HttpFoundation\Response */ public function logout(Request $request): Template { diff --git a/modules/exampleauth/src/Auth/Process/RedirectTest.php b/modules/exampleauth/src/Auth/Process/RedirectTest.php index 3167fb6866..62b3107814 100644 --- a/modules/exampleauth/src/Auth/Process/RedirectTest.php +++ b/modules/exampleauth/src/Auth/Process/RedirectTest.php @@ -32,6 +32,7 @@ public function process(array &$state): void $url = Module::getModuleURL('exampleauth/redirecttest'); $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($url, ['StateId' => $id]); + $response = $httpUtils->redirectTrustedURL($url, ['StateId' => $id]); + $response->send(); } } diff --git a/modules/exampleauth/src/Auth/Source/External.php b/modules/exampleauth/src/Auth/Source/External.php index a18b290e5a..71c63b141d 100644 --- a/modules/exampleauth/src/Auth/Source/External.php +++ b/modules/exampleauth/src/Auth/Source/External.php @@ -4,12 +4,12 @@ namespace SimpleSAML\Module\exampleauth\Auth\Source; -use SimpleSAML\Assert\Assert; use SimpleSAML\Auth; use SimpleSAML\Error; use SimpleSAML\Module; use SimpleSAML\Utils; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Session as SymfonySession; /** @@ -100,9 +100,10 @@ private function getUser(): ?array /** * Log in using an external authentication helper. * + * @param \Symfony\Component\HttpFoundation\Request The current request * @param array &$state Information about the current authentication. */ - public function authenticate(array &$state): void + public function authenticate(Request $request, array &$state): ?Response { $attributes = $this->getUser(); if ($attributes !== null) { @@ -113,7 +114,7 @@ public function authenticate(array &$state): void * to the authentication process. */ $state['Attributes'] = $attributes; - return; + return null; } /* @@ -148,7 +149,7 @@ public function authenticate(array &$state): void * option to return the user to a specific page afterwards. */ $returnTo = Module::getModuleURL('exampleauth/resume', [ - 'State' => $stateId, + 'AuthState' => $stateId, ]); /* @@ -167,14 +168,9 @@ public function authenticate(array &$state): void * the real name of the parameter for the login page. */ $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($authPage, [ + return $httpUtils->redirectTrustedURL($authPage, [ 'ReturnTo' => $returnTo, ]); - - /* - * The redirect function never returns, so we never get this far. - */ - Assert::true(false); } @@ -185,17 +181,19 @@ public function authenticate(array &$state): void * entered his or her credentials. * * @param \Symfony\Component\HttpFoundation\Request $request + * @param \SimpleSAML\Auth\State $state + * @return \Symfony\Component\HttpFoundation\Response * * @throws \SimpleSAML\Error\BadRequest * @throws \SimpleSAML\Error\Exception */ - public static function resume(Request $request): void + public static function resume(Request $request, Auth\State $authState): Response { /* * First we need to restore the $state-array. We should have the identifier for * it in the 'State' request parameter. */ - if (!$request->query->has('State')) { + if (!$request->query->has('AuthState')) { throw new Error\BadRequest('Missing "State" parameter.'); } @@ -203,7 +201,7 @@ public static function resume(Request $request): void * Once again, note the second parameter to the loadState function. This must * match the string we used in the saveState-call above. */ - $state = Auth\State::loadState($request->query->get('State'), 'exampleauth:External'); + $state = $authState::loadState($request->query->get('AuthState'), 'exampleauth:External'); /* * Now we have the $state-array, and can use it to locate the authentication @@ -249,12 +247,7 @@ public static function resume(Request $request): void */ $state['Attributes'] = $attributes; - Auth\Source::completeAuth($state); - - /* - * The completeAuth-function never returns, so we never get this far. - */ - Assert::true(false); + return parent::completeAuth($state); } @@ -263,8 +256,9 @@ public static function resume(Request $request): void * by logging out of a SP that supports single logout. * * @param array &$state The logout state array. + * @param \Symfony\Component\HttpFoundation\Response|null */ - public function logout(array &$state): void + public function logout(array &$state): ?Response { $session = new SymfonySession(); if (!$session->getId()) { @@ -277,5 +271,6 @@ public function logout(array &$state): void * If we need to do a redirect to a different page, we could do this * here, but in this example we don't need to do this. */ + return null; } } diff --git a/modules/exampleauth/src/Auth/Source/StaticSource.php b/modules/exampleauth/src/Auth/Source/StaticSource.php index 7be3c04637..9d1c81fc58 100644 --- a/modules/exampleauth/src/Auth/Source/StaticSource.php +++ b/modules/exampleauth/src/Auth/Source/StaticSource.php @@ -7,6 +7,8 @@ use Exception; use SimpleSAML\Auth; use SimpleSAML\Utils; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * Example authentication source. @@ -51,10 +53,12 @@ public function __construct(array $info, array $config) /** * Log in using static attributes. * + * @param \Symfony\Component\HttpFoundation\Request $request The current request * @param array &$state Information about the current authentication. */ - public function authenticate(array &$state): void + public function authenticate(Request $request, array &$state): ?Response { $state['Attributes'] = $this->attributes; + return null; } } diff --git a/modules/exampleauth/src/Controller/ExampleAuth.php b/modules/exampleauth/src/Controller/ExampleAuth.php index 561816e9f1..5e49e0a67d 100644 --- a/modules/exampleauth/src/Controller/ExampleAuth.php +++ b/modules/exampleauth/src/Controller/ExampleAuth.php @@ -7,7 +7,6 @@ use SimpleSAML\Auth; use SimpleSAML\Configuration; use SimpleSAML\Error; -use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Module\exampleauth\Auth\Source\External; use SimpleSAML\Session; use SimpleSAML\Utils; @@ -67,8 +66,7 @@ public function setAuthState(Auth\State $authState): void * Auth testpage. * * @param \Symfony\Component\HttpFoundation\Request $request The current request. - * - * @return \SimpleSAML\XHTML\Template|\SimpleSAML\HTTP\RunnableResponse + * @return \Symfony\Component\HttpFoundation\Response */ public function authpage(Request $request): Response { @@ -145,7 +143,7 @@ public function authpage(Request $request): Response $session->set('mail', $user['mail']); $session->set('type', $user['type']); - return new RunnableResponse([$httpUtils, 'redirectTrustedURL'], [$returnTo]); + return $httpUtils->redirectTrustedURL($returnTo); } } @@ -162,10 +160,9 @@ public function authpage(Request $request): Response * Redirect testpage. * * @param \Symfony\Component\HttpFoundation\Request $request The current request. - * - * @return \SimpleSAML\HTTP\RunnableResponse + * @return \Symfony\Component\HttpFoundation\Response */ - public function redirecttest(Request $request): RunnableResponse + public function redirecttest(Request $request): Response { /** * Request handler for redirect filter test. @@ -178,7 +175,7 @@ public function redirecttest(Request $request): RunnableResponse $state = $this->authState::loadState($stateId, 'exampleauth:redirectfilter-test'); $state['Attributes']['RedirectTest2'] = ['OK']; - return new RunnableResponse([Auth\ProcessingChain::class, 'resumeProcessing'], [$state]); + return Auth\ProcessingChain::resumeProcessing($state); } @@ -186,10 +183,9 @@ public function redirecttest(Request $request): RunnableResponse * Resume testpage. * * @param \Symfony\Component\HttpFoundation\Request $request The current request. - * - * @return \SimpleSAML\HTTP\RunnableResponse + * @return \Symfony\Component\HttpFoundation\Response */ - public function resume(Request $request): RunnableResponse + public function resume(Request $request): Response { /** * This page serves as the point where the user's authentication @@ -197,6 +193,6 @@ public function resume(Request $request): RunnableResponse * * It simply passes control back to the class. */ - return new RunnableResponse([External::class, 'resume'], [$request]); + return External::resume($request, $this->authState); } } diff --git a/modules/multiauth/src/Auth/Source/MultiAuth.php b/modules/multiauth/src/Auth/Source/MultiAuth.php index fd6c45304d..1b6ef38c88 100644 --- a/modules/multiauth/src/Auth/Source/MultiAuth.php +++ b/modules/multiauth/src/Auth/Source/MultiAuth.php @@ -5,14 +5,15 @@ namespace SimpleSAML\Module\multiauth\Auth\Source; use Exception; -use SAML2\Exception\Protocol\NoAuthnContextException; use SimpleSAML\Auth; use SimpleSAML\Configuration; use SimpleSAML\Error; -use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Module; +use SimpleSAML\SAML2\Exception\Protocol\NoAuthnContextException; use SimpleSAML\Session; use SimpleSAML\Utils; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * Authentication source which let the user chooses among a list of @@ -89,12 +90,13 @@ public function __construct(array $info, array $config) * and redirects to a page where the user must select one of these * authentication sources. * - * This method never return. The authentication process is finished - * in the delegateAuthentication method. + * The authentication process is finished in the delegateAuthentication method. * + * @oaram \Symfony\Component\HttpFoundation\Request $request * @param array &$state Information about the current authentication. + * @return \Symfony\Component\HttpFoundation\Response */ - public function authenticate(array &$state): never + public function authenticate(Request $request, array &$state): Response { $state[self::AUTHID] = $this->authId; $state[self::SOURCESID] = $this->sources; @@ -125,7 +127,7 @@ public function authenticate(array &$state): never 'No authentication sources exist for the requested AuthnContextClassRefs: ' . implode(', ', $refs), ); } elseif ($number_of_sources === 1) { - MultiAuth::delegateAuthentication(array_key_first($new_sources), $state); + return MultiAuth::delegateAuthentication($request, array_key_first($new_sources), $state); } } @@ -139,12 +141,12 @@ public function authenticate(array &$state): never $params = ['AuthState' => $id]; // Allows the user to specify the auth source to be used - if (isset($_GET['source'])) { - $params['source'] = $_GET['source']; + if ($request->query->has('source')) { + $params['source'] = $request->query->get('source'); } $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($url, $params); + return $httpUtils->redirectTrustedURL($url, $params); } @@ -156,12 +158,14 @@ public function authenticate(array &$state): never * to be able to logout properly. Then it calls the authenticate method * on such selected authentication source. * + * @param \Symfony\Component\HttpFoundation\Request The current request * @param string $authId Selected authentication source * @param array $state Information about the current authentication. - * @return \SimpleSAML\HTTP\RunnableResponse + * @return \Symfony\Component\HttpFoundation\Response + * * @throws \Exception */ - public static function delegateAuthentication(string $authId, array $state): RunnableResponse + public static function delegateAuthentication(Request $request, string $authId, array $state): Response { $as = Auth\Source::getById($authId); if ($as === null || !array_key_exists($authId, $state[self::SOURCESID])) { @@ -177,26 +181,30 @@ public static function delegateAuthentication(string $authId, array $state): Run Session::DATA_TIMEOUT_SESSION_END, ); - return new RunnableResponse([self::class, 'doAuthentication'], [$as, $state]); + return self::doAuthentication($request, $as, $state); } /** + * @param \Symfony\Component\HttpFoundation\Request The current request * @param \SimpleSAML\Auth\Source $as * @param array $state - * @return void + * @return \Symfony\Component\HttpFoundation\Response */ - public static function doAuthentication(Auth\Source $as, array $state): void + public static function doAuthentication(Request $request, Auth\Source $as, array $state): Response { try { - $as->authenticate($state); + $response = $as->authenticate($request, $state); + if ($response instanceof Response) { + return $reponse; + } } catch (Error\Exception $e) { Auth\State::throwException($state, $e); } catch (Exception $e) { $e = new Error\UnserializableException($e); Auth\State::throwException($state, $e); } - Auth\Source::completeAuth($state); + return parent::completeAuth($state); } @@ -207,8 +215,9 @@ public static function doAuthentication(Auth\Source $as, array $state): void * session and then call the logout method on it. * * @param array &$state Information about the current logout operation. + * @return \Symfony\Component\HttpFoundation\Response */ - public function logout(array &$state): void + public function logout(array &$state): ?Response { // Get the source that was used to authenticate $session = Session::getSessionFromRequest(); @@ -218,8 +227,9 @@ public function logout(array &$state): void if ($source === null) { throw new Exception('Invalid authentication source during logout: ' . $authId); } + // Then, do the logout on it - $source->logout($state); + return $source->logout($state); } diff --git a/modules/multiauth/src/Controller/DiscoController.php b/modules/multiauth/src/Controller/DiscoController.php index 6ed67974fb..f11078ef10 100644 --- a/modules/multiauth/src/Controller/DiscoController.php +++ b/modules/multiauth/src/Controller/DiscoController.php @@ -81,7 +81,7 @@ public function setAuthState(Auth\State $authState): void * delegateAuthentication method on it. * * @param \Symfony\Component\HttpFoundation\Request $request - * @return \SimpleSAML\XHTML\Template|\SimpleSAML\HTTP\RunnableResponse + * @return \Symfony\Component\HttpFoundation\Response * An HTML template or a redirection if we are not authenticated. */ public function discovery(Request $request): Response @@ -103,8 +103,8 @@ public function discovery(Request $request): Response } // Get a preselected source either from the URL or the discovery page - $urlSource = $request->get('source', null); - $discoSource = $request->get('sourceChoice', null); + $urlSource = $request->query->get('source', null); + $discoSource = $request->query->get('sourceChoice', null); $selectedSource = null; if ($urlSource !== null) { @@ -117,12 +117,12 @@ public function discovery(Request $request): Response if ($as !== null) { $as->setPreviousSource($selectedSource); } - return MultiAuth::delegateAuthentication($selectedSource, $state); + return MultiAuth::delegateAuthentication($request, $selectedSource, $state); } if (array_key_exists('multiauth:preselect', $state)) { $source = $state['multiauth:preselect']; - return MultiAuth::delegateAuthentication($source, $state); + return MultiAuth::delegateAuthentication($request, $source, $state); } $t = new Template($this->config, 'multiauth:selectsource.twig'); @@ -130,6 +130,7 @@ public function discovery(Request $request): Response $t->data['authstate'] = $authStateId; $t->data['sources'] = $state[MultiAuth::SOURCESID]; $t->data['preferred'] = is_null($as) ? null : $as->getPreviousSource(); + return $t; } } diff --git a/modules/saml/src/Auth/Process/ExpectedAuthnContextClassRef.php b/modules/saml/src/Auth/Process/ExpectedAuthnContextClassRef.php index b287cbf5b0..9bebee8b2a 100644 --- a/modules/saml/src/Auth/Process/ExpectedAuthnContextClassRef.php +++ b/modules/saml/src/Auth/Process/ExpectedAuthnContextClassRef.php @@ -107,6 +107,7 @@ protected function unauthorized(array &$state): void ); $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($url, ['StateId' => $id]); + $response = $httpUtils->redirectTrustedURL($url, ['StateId' => $id]); + $response->send(); } } diff --git a/modules/saml/src/Auth/Process/NameIDAttribute.php b/modules/saml/src/Auth/Process/NameIDAttribute.php index 735313beea..cc63224283 100644 --- a/modules/saml/src/Auth/Process/NameIDAttribute.php +++ b/modules/saml/src/Auth/Process/NameIDAttribute.php @@ -4,10 +4,10 @@ namespace SimpleSAML\Module\saml\Auth\Process; -use SAML2\Constants; use SimpleSAML\Assert\Assert; use SimpleSAML\Auth\ProcessingFilter; use SimpleSAML\Error; +use SimpleSAML\SAML2\Constants as C; /** * Authentication processing filter to create an attribute from a NameID. @@ -120,7 +120,7 @@ public function process(array &$state): void Assert::notNull($rep->getValue()); if ($rep->getFormat() === null) { - $rep->setFormat(Constants::NAMEID_UNSPECIFIED); + $rep->setFormat(C::NAMEID_UNSPECIFIED); } if ($rep->getSPNameQualifier() === null) { diff --git a/modules/saml/src/Auth/Process/PairwiseID.php b/modules/saml/src/Auth/Process/PairwiseID.php index fad9818f3c..3dff57589a 100644 --- a/modules/saml/src/Auth/Process/PairwiseID.php +++ b/modules/saml/src/Auth/Process/PairwiseID.php @@ -4,7 +4,7 @@ namespace SimpleSAML\Module\saml\Auth\Process; -use SimpleSAML\SAML2\Constants; +use SimpleSAML\SAML2\Constants as C; use function strtolower; @@ -81,6 +81,6 @@ public function process(array &$state): void $value = strtolower($hash . '@' . $scope); $this->validateGeneratedIdentifier($value); - $state['Attributes'][Constants::ATTR_PAIRWISE_ID] = [$value]; + $state['Attributes'][C::ATTR_PAIRWISE_ID] = [$value]; } } diff --git a/modules/saml/src/Auth/Process/PersistentNameID.php b/modules/saml/src/Auth/Process/PersistentNameID.php index e6f6158d78..99cf9278cc 100644 --- a/modules/saml/src/Auth/Process/PersistentNameID.php +++ b/modules/saml/src/Auth/Process/PersistentNameID.php @@ -4,10 +4,10 @@ namespace SimpleSAML\Module\saml\Auth\Process; -use SAML2\Constants; use SimpleSAML\Error; use SimpleSAML\Logger; use SimpleSAML\Module\saml\BaseNameIDGenerator; +use SimpleSAML\SAML2\Constants as C; use SimpleSAML\Utils; /** @@ -38,7 +38,7 @@ public function __construct(array $config, $reserved) { parent::__construct($config, $reserved); - $this->format = Constants::NAMEID_PERSISTENT; + $this->format = C::NAMEID_PERSISTENT; if (!isset($config['identifyingAttribute'])) { throw new Error\Exception("PersistentNameID: Missing required option 'identifyingAttribute'."); diff --git a/modules/saml/src/Auth/Process/PersistentNameID2TargetedID.php b/modules/saml/src/Auth/Process/PersistentNameID2TargetedID.php index ad1fe9f27f..029d86c369 100644 --- a/modules/saml/src/Auth/Process/PersistentNameID2TargetedID.php +++ b/modules/saml/src/Auth/Process/PersistentNameID2TargetedID.php @@ -4,9 +4,9 @@ namespace SimpleSAML\Module\saml\Auth\Process; -use SAML2\Constants; use SimpleSAML\Auth\ProcessingFilter; use SimpleSAML\Logger; +use SimpleSAML\SAML2\Constants as C; /** * Authentication processing filter to create the eduPersonTargetedID attribute from the persistent NameID. @@ -62,14 +62,14 @@ public function __construct(array $config, $reserved) */ public function process(array &$state): void { - if (!isset($state['saml:NameID'][Constants::NAMEID_PERSISTENT])) { + if (!isset($state['saml:NameID'][C::NAMEID_PERSISTENT])) { Logger::warning( 'Unable to generate eduPersonTargetedID because no persistent NameID was available.', ); return; } /** @var \SAML2\XML\saml\NameID $nameID */ - $nameID = $state['saml:NameID'][Constants::NAMEID_PERSISTENT]; + $nameID = $state['saml:NameID'][C::NAMEID_PERSISTENT]; $state['Attributes'][$this->attribute] = [(!$this->nameId) ? $nameID->getValue() : $nameID]; } diff --git a/modules/saml/src/Auth/Process/SQLPersistentNameID.php b/modules/saml/src/Auth/Process/SQLPersistentNameID.php index 497bd5bdce..7d6feb5559 100644 --- a/modules/saml/src/Auth/Process/SQLPersistentNameID.php +++ b/modules/saml/src/Auth/Process/SQLPersistentNameID.php @@ -4,12 +4,12 @@ namespace SimpleSAML\Module\saml\Auth\Process; -use SAML2\Constants as C; use SimpleSAML\Error; use SimpleSAML\Logger; use SimpleSAML\Module\saml\BaseNameIDGenerator; use SimpleSAML\Module\saml\Error as SAMLError; use SimpleSAML\Module\saml\IdP\SQLNameID; +use SimpleSAML\SAML2\Constants as C; /** * Authentication processing filter to generate a persistent NameID. diff --git a/modules/saml/src/Auth/Process/SubjectID.php b/modules/saml/src/Auth/Process/SubjectID.php index 118211ae94..875db28d19 100644 --- a/modules/saml/src/Auth/Process/SubjectID.php +++ b/modules/saml/src/Auth/Process/SubjectID.php @@ -7,7 +7,7 @@ use SimpleSAML\Assert\Assert; use SimpleSAML\Auth; use SimpleSAML\Logger; -use SimpleSAML\SAML2\Constants; +use SimpleSAML\SAML2\Constants as C; use SimpleSAML\SAML2\Exception\ProtocolViolationException; use SimpleSAML\Utils; @@ -153,7 +153,7 @@ public function process(array &$state): void $this->validateGeneratedIdentifier($value); - $state['Attributes'][Constants::ATTR_SUBJECT_ID] = [$value]; + $state['Attributes'][C::ATTR_SUBJECT_ID] = [$value]; } @@ -194,7 +194,7 @@ protected function getIdentifyingAttribute(array $state): ?string * @param array $state * @return string|null * @throws \SimpleSAML\Assert\AssertionFailedException if the scope is an empty string - * @throws \SAML2\Exception\ProtocolViolationException if the pre-conditions are not met + * @throws \SimpleSAML\SAML2\Exception\ProtocolViolationException if the pre-conditions are not met */ protected function getScopeAttribute(array $state): ?string { @@ -232,7 +232,7 @@ protected function getScopeAttribute(array $state): ?string * * @param string $value * @return void - * @throws \SAML2\Exception\ProtocolViolationException if the post-conditions are not met + * @throws \SimpleSAML\SAML2\Exception\ProtocolViolationException if the post-conditions are not met */ protected function validateGeneratedIdentifier(string $value): void { diff --git a/modules/saml/src/Auth/Process/TransientNameID.php b/modules/saml/src/Auth/Process/TransientNameID.php index 4b28ca897e..029759a503 100644 --- a/modules/saml/src/Auth/Process/TransientNameID.php +++ b/modules/saml/src/Auth/Process/TransientNameID.php @@ -4,8 +4,8 @@ namespace SimpleSAML\Module\saml\Auth\Process; -use SAML2\Constants; use SimpleSAML\Module\saml\BaseNameIDGenerator; +use SimpleSAML\SAML2\Constants as C; use SimpleSAML\Utils; /** @@ -26,7 +26,7 @@ public function __construct(array $config, $reserved) { parent::__construct($config, $reserved); - $this->format = Constants::NAMEID_TRANSIENT; + $this->format = C::NAMEID_TRANSIENT; } diff --git a/modules/saml/src/Auth/Source/SP.php b/modules/saml/src/Auth/Source/SP.php index fb3997f374..63f8e00219 100644 --- a/modules/saml/src/Auth/Source/SP.php +++ b/modules/saml/src/Auth/Source/SP.php @@ -27,6 +27,8 @@ use SimpleSAML\Store; use SimpleSAML\Store\StoreFactory; use SimpleSAML\Utils; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use function array_column; @@ -39,6 +41,13 @@ class SP extends Auth\Source */ private string $entityId; + /** + * The global configuration + * + * @var \SimpleSAML\Configuration + */ + private Configuration $config; + /** * The metadata of this SP. * @@ -126,6 +135,7 @@ public function __construct(array $info, array $config) 'Please set a valid and unique SP entityID', ); + $this->config = Configuration::getInstance(); $this->entityId = $entityId; $this->idp = $this->metadata->getOptionalString('idp', null); $this->discoURL = $this->metadata->getOptionalString('discoURL', null); @@ -232,12 +242,11 @@ public function getHostedMetadata(): array } // add technical contact - $globalConfig = Configuration::getInstance(); - $email = $globalConfig->getOptionalString('technicalcontact_email', 'na@example.org'); + $email = $this->config->getOptionalString('technicalcontact_email', 'na@example.org'); if (!empty($email) && $email !== 'na@example.org') { $contact = [ 'emailAddress' => $email, - 'givenName' => $globalConfig->getOptionalString('technicalcontact_name', null), + 'givenName' => $this->config->getOptionalString('technicalcontact_name', null), 'contactType' => 'technical', ]; $metadata['contacts'][] = Utils\Config\Metadata::getContact($contact); @@ -320,17 +329,18 @@ public function getHostedMetadata(): array /** * Retrieve the metadata of an IdP. * + * @param \SimpleSAML\Configuration $config The configuration * @param string $entityId The entity id of the IdP. * @return \SimpleSAML\Configuration The metadata of the IdP. */ - public function getIdPMetadata(string $entityId): Configuration + public function getIdPMetadata(Configuration $config, string $entityId): Configuration { if ($this->idp !== null && $this->idp !== $entityId) { throw new Error\Exception('Cannot retrieve metadata for IdP ' . var_export($entityId, true) . ' because it isn\'t a valid IdP for this SP.'); } - $metadataHandler = MetaDataStorageHandler::getMetadataHandler(); + $metadataHandler = MetaDataStorageHandler::getMetadataHandler($config); return $metadataHandler->getMetaDataConfig($entityId, 'saml20-idp-remote'); } @@ -423,8 +433,7 @@ private function getACSEndpoints(): array */ private function getSLOEndpoints(): array { - $config = Configuration::getInstance(); - $storeType = $config->getOptionalString('store.type', 'phpsession'); + $storeType = $this->config->getOptionalString('store.type', 'phpsession'); $store = StoreFactory::getInstance($storeType); $bindings = $this->metadata->getOptionalArray( @@ -685,13 +694,11 @@ private function startSSO2(Configuration $idpMetadata, array $state): void ], ); } - $ar->setDestination($dst['Location']); + $ar->setDestination($dst['Location']); $b = Binding::getBinding($dst['Binding']); $this->sendSAML2AuthnRequest($b, $ar); - - Assert::true(false); } @@ -744,8 +751,9 @@ public function startSSO(string $idp, array $state): never * Start an IdP discovery service operation. * * @param array $state The state array. + * @return \Symfony\Component\HttpFoundation\Response */ - private function startDisco(array $state): never + private function startDisco(array $state): Response { $id = Auth\State::saveState($state, 'saml:sp:sso'); @@ -772,7 +780,7 @@ private function startDisco(array $state): never } $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($discoURL, $params); + return $httpUtils->redirectTrustedURL($discoURL, $params); } @@ -781,9 +789,11 @@ private function startDisco(array $state): never * * This function saves the information about the login, and redirects to the IdP. * + * @param \Symfony\Component\HttpFoundation\Request $request The current request * @param array &$state Information about the current authentication. + * @return \Symfony\Component\HttpFoundation\Response */ - public function authenticate(array &$state): never + public function authenticate(Request $request, array &$state): Response { // We are going to need the authId in order to retrieve this authentication source later $state['saml:sp:AuthId'] = $this->authId; @@ -796,7 +806,7 @@ public function authenticate(array &$state): never if (isset($state['saml:IDPList']) && sizeof($state['saml:IDPList']) > 0) { // we have a SAML IDPList (we are a proxy): filter the list of IdPs available - $mdh = MetaDataStorageHandler::getMetadataHandler(); + $mdh = MetaDataStorageHandler::getMetadataHandler($this->config); $matchedEntities = $mdh->getMetaDataForEntities($state['saml:IDPList'], 'saml20-idp-remote'); if (empty($matchedEntities)) { @@ -820,10 +830,13 @@ public function authenticate(array &$state): never } if ($idp === null) { - $this->startDisco($state); + $response = $this->startDisco($state); } else { $this->startSSO($idp, $state); + Assert::true(false); } + + return $response; } @@ -861,7 +874,7 @@ public function reauthenticate(array &$state): void * First, check if we recognize any of the IdPs requested. */ - $mdh = MetaDataStorageHandler::getMetadataHandler(); + $mdh = MetaDataStorageHandler::getMetadataHandler($this->config); $known_idps = $mdh->getList(); $intersection = array_intersect($state['saml:IDPList'], array_keys($known_idps)); @@ -896,9 +909,10 @@ public function reauthenticate(array &$state): void $state['core:SP'], )); - $state['saml:sp:IdPMetadata'] = $this->getIdPMetadata($state['saml:sp:IdP']); + $state['saml:sp:IdPMetadata'] = $this->getIdPMetadata($this->config, $state['saml:sp:IdP']); $state['saml:sp:AuthId'] = $this->authId; - self::askForIdPChange($state); + $response = self::askForIdPChange($state); + $response->send(); } /* @@ -943,10 +957,11 @@ public function reauthenticate(array &$state): void * - 'saml:sp:AuthId': the identifier of the current authentication source. * - 'core:IdP': the identifier of the local IdP. * - 'SPMetadata': an array with the metadata of this local SP. + * @return \Symfony\Component\HttpFoundation\Response * - * @throws \SAML2\Exception\Protocol\NoPassiveException In case the authentication request was passive. + * @throws \SimpleSAML\SAML2\Exception\Protocol\NoPassiveException In case the authentication request was passive. */ - public static function askForIdPChange(array &$state): never + public static function askForIdPChange(array &$state): Response { Assert::keyExists($state, 'saml:sp:IdPMetadata'); Assert::keyExists($state, 'saml:sp:AuthId'); @@ -965,7 +980,7 @@ public static function askForIdPChange(array &$state): never $url = Module::getModuleURL('saml/proxy/invalidSession'); $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($url, ['AuthState' => $id]); + return $httpUtils->redirectTrustedURL($url, ['AuthState' => $id]); } @@ -1002,11 +1017,10 @@ public static function tryStepUpAuth(array &$state): never /** * Log the user out before logging in again. * - * This method will never return. - * + * @param \SimpleSAML\Configuration $config The configuration * @param array $state The state array. */ - public static function reauthLogout(array $state): never + public static function reauthLogout(Configuration $config, array $state): Response { Logger::debug('Proxy: logging the user out before re-authentication.'); @@ -1015,8 +1029,8 @@ public static function reauthLogout(array $state): never } $state['Responder'] = [SP::class, 'reauthPostLogout']; - $idp = IdP::getByState($state); - $idp->handleLogoutRequest($state, null); + $idp = IdP::getByState($config, $state); + return $idp->handleLogoutRequest($state, null); } @@ -1024,8 +1038,9 @@ public static function reauthLogout(array $state): never * Complete login operation after re-authenticating the user on another IdP. * * @param array $state The authentication state. + * @return \Symfony\Component\HttpFoundation\Response */ - public static function reauthPostLogin(array $state): never + public static function reauthPostLogin(array $state): Response { Assert::keyExists($state, 'ReturnCallback'); @@ -1035,7 +1050,7 @@ public static function reauthPostLogin(array $state): never $session->doLogin($authId, Auth\State::getPersistentAuthData($state)); // resume the login process - call_user_func($state['ReturnCallback'], $state); + return call_user_func($state['ReturnCallback'], $state); } @@ -1046,8 +1061,9 @@ public static function reauthPostLogin(array $state): never * * @param \SimpleSAML\IdP $idp The IdP we are logging out from. * @param array &$state The state array with the state during logout. + * @return \Symfony\Component\HttpFoundation\Response */ - public static function reauthPostLogout(IdP $idp, array $state): never + public static function reauthPostLogout(IdP $idp, array $state): Response { Assert::keyExists($state, 'saml:sp:AuthId'); @@ -1058,19 +1074,21 @@ public static function reauthPostLogout(IdP $idp, array $state): never } /** @var \SimpleSAML\Module\saml\Auth\Source\SP $sp */ - $sp = Auth\Source::getById($state['saml:sp:AuthId'], Module\saml\Auth\Source\SP::class); + $sp = Auth\Source::getById($state['saml:sp:AuthId'], selfSP::class); Logger::debug('Proxy: logging in again.'); - $sp->authenticate($state); + $request = Request::createFromGlobals(); + return $sp->authenticate($request, $state); } /** * Start a SAML 2 logout operation. * + * @param \SimpleSAML\Configuration $config The configuration * @param array $state The logout state. */ - public function startSLO2(array &$state): void + public function startSLO2(Configuration $config, array &$state): void { Assert::keyExists($state, 'saml:logout:IdP'); Assert::keyExists($state, 'saml:logout:NameID'); @@ -1082,7 +1100,7 @@ public function startSLO2(array &$state): void $nameId = $state['saml:logout:NameID']; $sessionIndex = $state['saml:logout:SessionIndex']; - $idpMetadata = $this->getIdPMetadata($idp); + $idpMetadata = $this->getIdPMetadata($config, $idp); /** @var array $endpoint */ $endpoint = $idpMetadata->getEndpointPrioritizedByBinding( @@ -1128,8 +1146,9 @@ public function startSLO2(array &$state): void * Start logout operation. * * @param array $state The logout state. + * @return \Symfony\Component\HttpFoundation\Response|null */ - public function logout(array &$state): void + public function logout(array &$state): ?Response { Assert::keyExists($state, 'saml:logout:Type'); @@ -1138,10 +1157,10 @@ public function logout(array &$state): void // State variable saml:logout:Type is set to saml1 by us if we cannot properly logout the user if ($logoutType === 'saml1') { - return; + return null; } - $this->startSLO2($state); + return new RunnableResponse([$this, 'startSLO2'], [$this->config, $state]); } @@ -1157,7 +1176,7 @@ public function handleResponse(array $state, string $idp, array $attributes): ne Assert::keyExists($state, 'LogoutState'); Assert::keyExists($state['LogoutState'], 'saml:logout:Type'); - $idpMetadata = $this->getIdPMetadata($idp); + $idpMetadata = $this->getIdPMetadata($this->config, $idp); $spMetadataArray = $this->metadata->toArray(); $idpMetadataArray = $idpMetadata->toArray(); @@ -1195,11 +1214,12 @@ public function handleResponse(array $state, string $idp, array $attributes): ne * Handle a logout request from an IdP. * * @param string $idpEntityId The entity ID of the IdP. + * @return \Symfony\Component\HttpFoundation\Response */ - public function handleLogout(string $idpEntityId): never + public function handleLogout(string $idpEntityId): ?Response { /* Call the logout callback we registered in onProcessingCompleted(). */ - $this->callLogoutCallback($idpEntityId); + return $this->callLogoutCallback($idpEntityId); } @@ -1223,7 +1243,8 @@ public static function handleUnsolicitedAuth(string $authId, array $state, strin $session->doLogin($authId, Auth\State::getPersistentAuthData($state)); $httpUtils = new Utils\HTTP(); - $httpUtils->redirectUntrustedURL($redirectTo); + $response = $httpUtils->redirectUntrustedURL($redirectTo); + $response->send(); } @@ -1231,8 +1252,9 @@ public static function handleUnsolicitedAuth(string $authId, array $state, strin * Called when we have completed the procssing chain. * * @param array $authProcState The processing chain state. + * @return \Symfony\Component\HttpFoundation\Response */ - public static function onProcessingCompleted(array $authProcState): never + public static function onProcessingCompleted(array $authProcState): Response { Assert::keyExists($authProcState, 'saml:sp:IdP'); Assert::keyExists($authProcState, 'saml:sp:State'); @@ -1264,6 +1286,6 @@ public static function onProcessingCompleted(array $authProcState): never self::handleUnsolicitedAuth($sourceId, $state, $redirectTo); } - Auth\Source::completeAuth($state); + return parent::completeAuth($state); } } diff --git a/modules/saml/src/Controller/Disco.php b/modules/saml/src/Controller/Disco.php index d3b9ed9d4f..72dd524843 100644 --- a/modules/saml/src/Controller/Disco.php +++ b/modules/saml/src/Controller/Disco.php @@ -5,8 +5,9 @@ namespace SimpleSAML\Module\saml\Controller; use SimpleSAML\Configuration; -use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\XHTML\IdPDisco; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * Controller class for the saml module. @@ -33,11 +34,12 @@ public function __construct( /** * Built-in IdP discovery service * - * @return \SimpleSAML\HTTP\RunnableResponse + * @param \Symfony\Component\HttpFoundation\Request The current request + * @return \Symfony\Component\HttpFoundation\Response */ - public function disco(): RunnableResponse + public function disco(Request $request): Response { - $disco = new IdPDisco(['saml20-idp-remote'], 'saml'); - return new RunnableResponse([$disco, 'handleRequest']); + $disco = new IdPDisco($request, ['saml20-idp-remote'], 'saml'); + return $disco->handleRequest(); } } diff --git a/modules/saml/src/Controller/Metadata.php b/modules/saml/src/Controller/Metadata.php index e9766dea97..9168357199 100644 --- a/modules/saml/src/Controller/Metadata.php +++ b/modules/saml/src/Controller/Metadata.php @@ -7,7 +7,6 @@ use Exception; use SimpleSAML\Configuration; use SimpleSAML\Error; -use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Metadata as SSPMetadata; use SimpleSAML\Metadata\MetaDataStorageHandler; use SimpleSAML\Module; @@ -43,7 +42,7 @@ public function __construct( protected Configuration $config, ) { $this->authUtils = new Utils\Auth(); - $this->mdHandler = MetaDataStorageHandler::getMetadataHandler(); + $this->mdHandler = MetaDataStorageHandler::getMetadataHandler($config); } @@ -71,7 +70,7 @@ public function setMetadataStorageHandler(MetadataStorageHandler $mdHandler): vo * This endpoint will offer the SAML 2.0 IdP metadata. * * @param \Symfony\Component\HttpFoundation\Request $request - * @return \SimpleSAML\HTTP\RunnableResponse|\Symfony\Component\HttpFoundation\Response + * @return \Symfony\Component\HttpFoundation\Response */ public function metadata(Request $request): Response { @@ -82,7 +81,10 @@ public function metadata(Request $request): Response // check if valid local session exists $protectedMetadata = $this->config->getOptionalBoolean('admin.protectmetadata', false); if ($protectedMetadata && !$this->authUtils->isAdmin()) { - return new RunnableResponse([$this->authUtils, 'requireAdmin']); + $response = $this->authUtils->requireAdmin(); + if ($response instanceof Response) { + return $response; + } } try { diff --git a/modules/saml/src/Controller/Proxy.php b/modules/saml/src/Controller/Proxy.php index 73ff5c5903..c6e2d3d19d 100644 --- a/modules/saml/src/Controller/Proxy.php +++ b/modules/saml/src/Controller/Proxy.php @@ -5,14 +5,13 @@ namespace SimpleSAML\Module\saml\Controller; use Exception; -use SAML2\Constants; use SimpleSAML\Auth; use SimpleSAML\Configuration; use SimpleSAML\Error; -use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\IdP; use SimpleSAML\Module\saml\Auth\Source\SP; use SimpleSAML\Module\saml\Error\NoAvailableIDP; +use SimpleSAML\SAML2\Constants as C; use SimpleSAML\XHTML\Template; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -62,7 +61,7 @@ public function setAuthState(Auth\State $authState): void * Service Provider, since the authenticating IdP is not in the list of IdPs allowed by the SP. * * @param \Symfony\Component\HttpFoundation\Request $request - * @return \SimpleSAML\XHTML\Template|\Symfony\Component\HttpFoundation\Response + * @return \Symfony\Component\HttpFoundation\Response */ public function invalidSession(Request $request): Response { @@ -85,8 +84,8 @@ public function invalidSession(Request $request): Response $state = $this->authState::loadState($stateId, 'core:Logout:afterbridge'); // success! Try to continue with reauthentication, since we no longer have a valid session here - $idp = IdP::getById($state['core:IdP']); - return new RunnableResponse([SP::class, 'reauthPostLogout'], [$idp, $state]); + $idp = IdP::getById($this->config, $state['core:IdP']); + return SP::reauthPostLogout($idp, $state); } if ($request->request->has('cancel')) { @@ -94,7 +93,7 @@ public function invalidSession(Request $request): Response $this->authState::throwException( $state, new NoAvailableIDP( - Constants::STATUS_RESPONDER, + C::STATUS_RESPONDER, 'User refused to reauthenticate with any of the IdPs requested.', ), ); @@ -105,7 +104,7 @@ public function invalidSession(Request $request): Response $as = new \SimpleSAML\Auth\Simple($state['saml:sp:AuthId']); // log the user out before being able to login again - return new RunnableResponse([$as, 'login'], [$state]); + return $as->login($state); } $template = new Template($this->config, 'saml:proxy/invalid_session.twig'); diff --git a/modules/saml/src/Controller/ServiceProvider.php b/modules/saml/src/Controller/ServiceProvider.php index a565061d8c..92db3936d9 100644 --- a/modules/saml/src/Controller/ServiceProvider.php +++ b/modules/saml/src/Controller/ServiceProvider.php @@ -7,8 +7,6 @@ use Exception; use SAML2\Assertion; use SAML2\Binding; -use SAML2\Constants; -use SAML2\Exception\Protocol\UnsupportedBindingException; use SAML2\HTTPArtifact; use SAML2\HTTPPost; use SAML2\HTTPRedirect; @@ -26,6 +24,8 @@ use SimpleSAML\Metadata; use SimpleSAML\Module; use SimpleSAML\Module\saml\Auth\Source\SP; +use SimpleSAML\SAML2\Constants as C; +use SimpleSAML\SAML2\Exception\Protocol\UnsupportedBindingException; use SimpleSAML\Session; use SimpleSAML\Store\StoreFactory; use SimpleSAML\Utils; @@ -103,11 +103,11 @@ public function setAuthUtils(Utils\Auth $authUtils): void * * @param \Symfony\Component\HttpFoundation\Request $request * @param string $sourceId + * @return \Symfony\Component\HttpFoundation\Response * - * @return \SimpleSAML\HTTP\RunnableResponse * @throws \SimpleSAML\Error\Exception */ - public function login(Request $request, string $sourceId): RunnableResponse + public function login(Request $request, string $sourceId): Response { // Initialize all the dependencies $authSource = new Auth\Simple($sourceId); @@ -122,7 +122,7 @@ public function login(Request $request, string $sourceId): RunnableResponse $returnTo = $this->loginHandler($request, $authSource, $spSource, $httpUtils); // Redirect to the returnTo destination - return new RunnableResponse([$httpUtils, 'redirectTrustedURL'], [$returnTo]); + return $httpUtils->redirectTrustedURL($returnTo); } @@ -671,8 +671,8 @@ public function singleLogoutService(string $sourceId): RunnableResponse $dst = $idpMetadata->getEndpointPrioritizedByBinding( 'SingleLogoutService', [ - Constants::BINDING_HTTP_REDIRECT, - Constants::BINDING_HTTP_POST, + C::BINDING_HTTP_REDIRECT, + C::BINDING_HTTP_POST, ], ); diff --git a/modules/saml/src/Controller/SingleLogout.php b/modules/saml/src/Controller/SingleLogout.php index 75443265f9..4bad17c64a 100644 --- a/modules/saml/src/Controller/SingleLogout.php +++ b/modules/saml/src/Controller/SingleLogout.php @@ -4,7 +4,6 @@ namespace SimpleSAML\Module\saml\Controller; -use SAML2\Exception\Protocol\UnsupportedBindingException; use SimpleSAML\Configuration; use SimpleSAML\Error; use SimpleSAML\HTTP\RunnableResponse; @@ -12,6 +11,7 @@ use SimpleSAML\Logger; use SimpleSAML\Metadata\MetaDataStorageHandler; use SimpleSAML\Module; +use SimpleSAML\SAML2\Exception\Protocol\UnsupportedBindingException; use SimpleSAML\Utils; use Symfony\Component\HttpFoundation\Request; @@ -44,7 +44,7 @@ class SingleLogout public function __construct( protected Configuration $config, ) { - $this->mdHandler = MetaDataStorageHandler::getMetadataHandler(); + $this->mdHandler = MetaDataStorageHandler::getMetadataHandler($config); } @@ -87,7 +87,7 @@ public function singleLogout(Request $request): RunnableResponse $httpUtils = new Utils\HTTP(); $idpEntityId = $this->mdHandler->getMetaDataCurrentEntityID('saml20-idp-hosted'); - $idp = $this->idp::getById('saml2:' . $idpEntityId); + $idp = $this->idp::getById($this->config, 'saml2:' . $idpEntityId); if ($request->query->has('ReturnTo')) { return new RunnableResponse( @@ -123,7 +123,7 @@ public function initSingleLogout(Request $request): RunnableResponse } $idpEntityId = $this->mdHandler->getMetaDataCurrentEntityID('saml20-idp-hosted'); - $idp = $this->idp::getById('saml2:' . $idpEntityId); + $idp = $this->idp::getById($this->config, 'saml2:' . $idpEntityId); if (!$request->query->has('RelayState')) { throw new Error\Error(Error\ErrorCodes::NORELAYSTATE); diff --git a/modules/saml/src/Controller/WebBrowserSingleSignOn.php b/modules/saml/src/Controller/WebBrowserSingleSignOn.php index 0fbb2dc625..8f13d5b2d7 100644 --- a/modules/saml/src/Controller/WebBrowserSingleSignOn.php +++ b/modules/saml/src/Controller/WebBrowserSingleSignOn.php @@ -7,7 +7,6 @@ use Exception; use SAML2\ArtifactResolve; use SAML2\ArtifactResponse; -use SAML2\Exception\Protocol\UnsupportedBindingException; use SAML2\SOAP; use SAML2\XML\saml\Issuer; use SimpleSAML\Assert\Assert; @@ -18,6 +17,7 @@ use SimpleSAML\Logger; use SimpleSAML\Metadata; use SimpleSAML\Module; +use SimpleSAML\SAML2\Exception\Protocol\UnsupportedBindingException; use SimpleSAML\Store\StoreFactory; use SimpleSAML\XML\DOMDocumentFactory; @@ -55,7 +55,7 @@ public function artifactResolutionService(): RunnableResponse throw new Error\Error(Error\ErrorCodes::NOACCESS, null, 403); } - $metadata = Metadata\MetaDataStorageHandler::getMetadataHandler(); + $metadata = Metadata\MetaDataStorageHandler::getMetadataHandler($this->config); $idpEntityId = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted'); $idpMetadata = $metadata->getMetaDataConfig($idpEntityId, 'saml20-idp-hosted'); @@ -123,9 +123,9 @@ public function singleSignOnService(): RunnableResponse throw new Error\Error(Error\ErrorCodes::NOACCESS, null, 403); } - $metadata = Metadata\MetaDataStorageHandler::getMetadataHandler(); + $metadata = Metadata\MetaDataStorageHandler::getMetadataHandler($this->config); $idpEntityId = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted'); - $idp = IdP::getById('saml2:' . $idpEntityId); + $idp = IdP::getById($this->config, 'saml2:' . $idpEntityId); try { return new RunnableResponse([Module\saml\IdP\SAML2::class, 'receiveAuthnRequest'], [$idp]); diff --git a/modules/saml/src/Error.php b/modules/saml/src/Error.php index 19b4751913..a23edafd27 100644 --- a/modules/saml/src/Error.php +++ b/modules/saml/src/Error.php @@ -7,7 +7,7 @@ use SimpleSAML\Error as SSPError; use SimpleSAML\Module\saml\Error as SAMLError; use SimpleSAML\Module\saml\Error\NoPassive; -use SimpleSAML\SAML2\Constants; +use SimpleSAML\SAML2\Constants as C; use Throwable; /** @@ -94,7 +94,7 @@ public static function fromException(Throwable $e): SSPError\Exception return $e; } else { $e = new self( - Constants::STATUS_RESPONDER, + C::STATUS_RESPONDER, null, $e::class . ': ' . $e->getMessage(), $e, @@ -121,11 +121,11 @@ public function toException(): SSPError\Exception $e = null; switch ($this->status) { - case Constants::STATUS_RESPONDER: + case C::STATUS_RESPONDER: switch ($this->subStatus) { - case Constants::STATUS_NO_PASSIVE: + case C::STATUS_NO_PASSIVE: $e = new NoPassive( - Constants::STATUS_RESPONDER, + C::STATUS_RESPONDER, $this->statusMessage, ); break; diff --git a/modules/saml/src/IdP/SAML2.php b/modules/saml/src/IdP/SAML2.php index dbab0a355e..e198d8b041 100644 --- a/modules/saml/src/IdP/SAML2.php +++ b/modules/saml/src/IdP/SAML2.php @@ -10,9 +10,7 @@ use SAML2\Assertion; use SAML2\AuthnRequest; use SAML2\Binding; -use SAML2\Constants; use SAML2\EncryptedAssertion; -use SAML2\Exception\Protocol\UnsupportedBindingException; use SAML2\HTTPRedirect; use SAML2\LogoutRequest; use SAML2\LogoutResponse; @@ -34,6 +32,8 @@ use SimpleSAML\Metadata\MetaDataStorageHandler; use SimpleSAML\Module; use SimpleSAML\Module\saml\Message; +use SimpleSAML\SAML2\Constants as C; +use SimpleSAML\SAML2\Exception\Protocol\UnsupportedBindingException; use SimpleSAML\Stats; use SimpleSAML\Utils; use SimpleSAML\XML\DOMDocumentFactory; @@ -72,7 +72,7 @@ public static function sendResponse(array $state): void $consumerURL = $state['saml:ConsumerURL']; $protocolBinding = $state['saml:Binding']; - $idp = IdP::getByState($state); + $idp = IdP::getByState(Configuration::getInstance(), $state); $idpMetadata = $idp->getConfig(); @@ -146,7 +146,7 @@ public static function handleAuthError(Error\Exception $exception, array $state) $consumerURL = $state['saml:ConsumerURL']; $protocolBinding = $state['saml:Binding']; - $idp = IdP::getByState($state); + $idp = IdP::getByState(Configuration::getInstance(), $state); $idpMetadata = $idp->getConfig(); @@ -299,34 +299,35 @@ private static function getAssertionConsumerService( /** * Receive an authentication request. * + * @param \Symfony\Component\HttpFoundation\Request The current request * @param \SimpleSAML\IdP $idp The IdP we are receiving it for. * @throws \SimpleSAML\Error\BadRequest In case an error occurs when trying to receive the request. */ - public static function receiveAuthnRequest(IdP $idp): void + public static function receiveAuthnRequest(Request $request, IdP $idp): void { - $metadata = MetaDataStorageHandler::getMetadataHandler(); + $metadata = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance()); $idpMetadata = $idp->getConfig(); $httpUtils = new Utils\HTTP(); - $supportedBindings = [Constants::BINDING_HTTP_POST]; + $supportedBindings = [C::BINDING_HTTP_POST]; if ($idpMetadata->getOptionalBoolean('saml20.sendartifact', false)) { - $supportedBindings[] = Constants::BINDING_HTTP_ARTIFACT; + $supportedBindings[] = C::BINDING_HTTP_ARTIFACT; } if ($idpMetadata->getOptionalBoolean('saml20.hok.assertion', false)) { - $supportedBindings[] = Constants::BINDING_HOK_SSO; + $supportedBindings[] = C::BINDING_HOK_SSO; } if ($idpMetadata->getOptionalBoolean('saml20.ecp', false)) { - $supportedBindings[] = Constants::BINDING_PAOS; + $supportedBindings[] = C::BINDING_PAOS; } $authnRequestSigned = false; $username = null; - if (isset($_REQUEST['spentityid']) || isset($_REQUEST['providerId'])) { + if ($request->query->has('spentityid') || $request->query->has('providerId')) { /* IdP initiated authentication. */ - if (isset($_REQUEST['cookieTime'])) { - $cookieTime = (int) $_REQUEST['cookieTime']; + if ($request->query->has('cookieTime')) { + $cookieTime = intval($requqest->query->get('cookieTime')); if ($cookieTime + 5 > time()) { /* * Less than five seconds has passed since we were @@ -336,35 +337,33 @@ public static function receiveAuthnRequest(IdP $idp): void } } - $spEntityId = (string) isset($_REQUEST['spentityid']) ? $_REQUEST['spentityid'] : $_REQUEST['providerId']; + $spEntityId = $request->query->has('spentityid') + ? $request->query->get('spentityid') + : $request->query->get('providerId'); $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote'); - if (isset($_REQUEST['RelayState'])) { - $relayState = (string) $_REQUEST['RelayState']; - } elseif (isset($_REQUEST['target'])) { - $relayState = (string) $_REQUEST['target']; - } else { - $relayState = null; + $relayState = null; + if ($request->query->has('RelayState')) { + $relayState = $request->query->get('RelayState'); + } elseif ($request->query->has('target')) { + $relayState = $request->query->get('target'); } - if (isset($_REQUEST['binding'])) { - $protocolBinding = (string) $_REQUEST['binding']; - } else { - $protocolBinding = null; + $protocolBinding = null; + if ($request->query->has('binding')) { + $protocolBinding = $request->query->get('binding'); } - if (isset($_REQUEST['NameIDFormat'])) { - $nameIDFormat = (string) $_REQUEST['NameIDFormat']; - } else { - $nameIDFormat = null; + $nameIDFormat = null; + if ($request->query->has('NameIDFormat')) { + $nameIDFormat = $request->query->get('NameIDFormat'); } - if (isset($_REQUEST['ConsumerURL'])) { - $consumerURL = (string) $_REQUEST['ConsumerURL']; - } elseif (isset($_REQUEST['shire'])) { - $consumerURL = (string) $_REQUEST['shire']; - } else { - $consumerURL = null; + $consumerURL = null; + if ($request->query->has('ConsumerURL')) { + $consumerURL = $request->query->get('ConsumerURL'); + } elseif ($request->query->has('shire')) { + $consumerURL = $request->query->get('shire'); } $requestId = null; @@ -543,7 +542,7 @@ public static function sendLogoutRequest(IdP $idp, array $association, ?string $ { Logger::info('Sending SAML 2.0 LogoutRequest to: ' . var_export($association['saml:entityID'], true)); - $metadata = MetaDataStorageHandler::getMetadataHandler(); + $metadata = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance()); $idpMetadata = $idp->getConfig(); $spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote'); @@ -556,10 +555,11 @@ public static function sendLogoutRequest(IdP $idp, array $association, ?string $ $dst = $spMetadata->getEndpointPrioritizedByBinding( 'SingleLogoutService', [ - Constants::BINDING_HTTP_REDIRECT, - Constants::BINDING_HTTP_POST, + C::BINDING_HTTP_REDIRECT, + C::BINDING_HTTP_POST, ], ); + $binding = Binding::getBinding($dst['Binding']); $lr = self::buildLogoutRequest($idpMetadata, $spMetadata, $association, $relayState); $lr->setDestination($dst['Location']); @@ -582,7 +582,7 @@ public static function sendLogoutResponse(IdP $idp, array $state): void $spEntityId = $state['saml:SPEntityId']; - $metadata = MetaDataStorageHandler::getMetadataHandler(); + $metadata = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance()); $idpMetadata = $idp->getConfig(); $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote'); @@ -593,8 +593,8 @@ public static function sendLogoutResponse(IdP $idp, array $state): void if (isset($state['core:Failed']) && $state['core:Failed']) { $partial = true; $lr->setStatus([ - 'Code' => Constants::STATUS_SUCCESS, - 'SubCode' => Constants::STATUS_PARTIAL_LOGOUT, + 'Code' => C::STATUS_SUCCESS, + 'SubCode' => C::STATUS_PARTIAL_LOGOUT, ]); Logger::info('Sending logout response for partial logout to SP ' . var_export($spEntityId, true)); } else { @@ -612,8 +612,8 @@ public static function sendLogoutResponse(IdP $idp, array $state): void $dst = $spMetadata->getEndpointPrioritizedByBinding( 'SingleLogoutService', [ - Constants::BINDING_HTTP_REDIRECT, - Constants::BINDING_HTTP_POST, + C::BINDING_HTTP_REDIRECT, + C::BINDING_HTTP_POST, ], ); $binding = Binding::getBinding($dst['Binding']); @@ -647,7 +647,7 @@ public static function receiveLogoutMessage(IdP $idp): void $spEntityId = $issuer->getValue(); } - $metadata = MetaDataStorageHandler::getMetadataHandler(); + $metadata = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance()); $idpMetadata = $idp->getConfig(); $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote'); @@ -714,19 +714,19 @@ public static function getLogoutURL(IdP $idp, array $association, ?string $relay { Logger::info('Sending SAML 2.0 LogoutRequest to: ' . var_export($association['saml:entityID'], true)); - $metadata = MetaDataStorageHandler::getMetadataHandler(); + $metadata = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance()); $idpMetadata = $idp->getConfig(); $spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote'); $bindings = [ - Constants::BINDING_HTTP_REDIRECT, - Constants::BINDING_HTTP_POST, + C::BINDING_HTTP_REDIRECT, + C::BINDING_HTTP_POST, ]; /** @var array $dst */ $dst = $spMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', $bindings); - if ($dst['Binding'] === Constants::BINDING_HTTP_POST) { + if ($dst['Binding'] === C::BINDING_HTTP_POST) { $params = ['association' => $association['id'], 'idp' => $idp->getId()]; if ($relayState !== null) { $params['RelayState'] = $relayState; @@ -752,7 +752,7 @@ public static function getLogoutURL(IdP $idp, array $association, ?string $relay */ public static function getAssociationConfig(IdP $idp, array $association): Configuration { - $metadata = MetaDataStorageHandler::getMetadataHandler(); + $metadata = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance()); try { return $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote'); } catch (Exception $e) { @@ -777,7 +777,7 @@ public static function getHostedMetadata(string $entityid, ?MetaDataStorageHandl { $globalConfig = Configuration::getInstance(); if ($handler === null) { - $handler = MetaDataStorageHandler::getMetadataHandler(); + $handler = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance()); } $config = $handler->getMetaDataConfig($entityid, 'saml20-idp-hosted'); @@ -825,7 +825,7 @@ public static function getHostedMetadata(string $entityid, ?MetaDataStorageHandl 'entityid' => $entityid, 'SingleSignOnService' => $sso, 'SingleLogoutService' => $slo, - 'NameIDFormat' => $config->getOptionalArrayizeString('NameIDFormat', [Constants::NAMEID_TRANSIENT]), + 'NameIDFormat' => $config->getOptionalArrayizeString('NameIDFormat', [C::NAMEID_TRANSIENT]), ]; // metadata signing @@ -894,7 +894,7 @@ public static function getHostedMetadata(string $entityid, ?MetaDataStorageHandl if ($config->getOptionalBoolean('saml20.sendartifact', false)) { $metadata['ArtifactResolutionService'][] = [ 'index' => 0, - 'Binding' => Constants::BINDING_SOAP, + 'Binding' => C::BINDING_SOAP, 'Location' => Module::getModuleURL('saml/idp/artifactResolutionService'), ]; } @@ -904,8 +904,8 @@ public static function getHostedMetadata(string $entityid, ?MetaDataStorageHandl array_unshift( $metadata['SingleSignOnService'], [ - 'hoksso:ProtocolBinding' => Constants::BINDING_HTTP_REDIRECT, - 'Binding' => Constants::BINDING_HOK_SSO, + 'hoksso:ProtocolBinding' => C::BINDING_HTTP_REDIRECT, + 'Binding' => C::BINDING_HOK_SSO, 'Location' => Module::getModuleURL('saml/idp/singleSignOnService'), ], ); @@ -915,7 +915,7 @@ public static function getHostedMetadata(string $entityid, ?MetaDataStorageHandl if ($config->getOptionalBoolean('saml20.ecp', false)) { $metadata['SingleSignOnService'][] = [ 'index' => 0, - 'Binding' => Constants::BINDING_SOAP, + 'Binding' => C::BINDING_SOAP, 'Location' => Module::getModuleURL('saml/idp/singleSignOnService'), ]; } @@ -1063,7 +1063,7 @@ private static function encodeAttributes( /** @psalm-suppress PossiblyNullPropertyFetch */ $value = $doc->firstChild->childNodes; } - Assert::isInstanceOfAny($value, [\DOMNodeList::class, \SAML2\XML\saml\NameID::class]); + Assert::isInstanceOfAny($value, [DOMNodeList::class, NameID::class]); break; default: throw new Error\Exception('Invalid encoding for attribute ' . @@ -1110,7 +1110,7 @@ private static function getAttributeNameFormat( } // default - return Constants::NAMEFORMAT_URI; + return C::NAMEFORMAT_URI; } @@ -1150,7 +1150,7 @@ private static function buildAssertion( $issuer = new Issuer(); $issuer->setValue($state['IdPMetadata']['entityid']); - $issuer->setFormat(Constants::NAMEID_ENTITY); + $issuer->setFormat(C::NAMEID_ENTITY); $a->setIssuer($issuer); $audience = array_merge([$spMetadata->getString('entityid')], $spMetadata->getOptionalArray('audience', [])); @@ -1171,9 +1171,9 @@ private static function buildAssertion( // AuthnContext has been set by the upper IdP in front of the proxy, pass it back to the SP behind the proxy $a->setAuthnContextClassRef($state['saml:sp:AuthnContext']); } elseif ($httpUtils->isHTTPS()) { - $a->setAuthnContextClassRef(Constants::AC_PASSWORD_PROTECTED_TRANSPORT); + $a->setAuthnContextClassRef(C::AC_PASSWORD_PROTECTED_TRANSPORT); } else { - $a->setAuthnContextClassRef(Constants::AC_PASSWORD); + $a->setAuthnContextClassRef(C::AC_PASSWORD); } $sessionStart = $now; @@ -1197,7 +1197,7 @@ private static function buildAssertion( // ProtcolBinding of SP's overwrites IdP hosted metadata configuration $hokAssertion = null; - if ($state['saml:Binding'] === Constants::BINDING_HOK_SSO) { + if ($state['saml:Binding'] === C::BINDING_HOK_SSO) { $hokAssertion = true; } if ($hokAssertion === null) { @@ -1206,7 +1206,7 @@ private static function buildAssertion( if ($hokAssertion) { // Holder-of-Key - $sc->setMethod(Constants::CM_HOK); + $sc->setMethod(C::CM_HOK); if ($httpUtils->isHTTPS()) { if (isset($_SERVER['SSL_CLIENT_CERT']) && !empty($_SERVER['SSL_CLIENT_CERT'])) { @@ -1243,7 +1243,7 @@ private static function buildAssertion( } } else { // Bearer - $sc->setMethod(Constants::CM_BEARER); + $sc->setMethod(C::CM_BEARER); } $sc->setSubjectConfirmationData($scd); $a->setSubjectConfirmation([$sc]); @@ -1292,7 +1292,7 @@ private static function generateNameId( $nameIdFormat = current($spMetadata->getOptionalArrayizeString('NameIDFormat', [])); if ($nameIdFormat === false) { $nameIdFormat = current( - $idpMetadata->getOptionalArrayizeString('NameIDFormat', [Constants::NAMEID_TRANSIENT]), + $idpMetadata->getOptionalArrayizeString('NameIDFormat', [C::NAMEID_TRANSIENT]), ); } } @@ -1303,12 +1303,12 @@ private static function generateNameId( } // We have nothing else to work with, so default to transient - if ($nameIdFormat !== Constants::NAMEID_TRANSIENT) { + if ($nameIdFormat !== C::NAMEID_TRANSIENT) { Logger::notice(sprintf( 'Requested NameID of format %s, but can only provide transient', var_export($nameIdFormat, true), )); - $nameIdFormat = Constants::NAMEID_TRANSIENT; + $nameIdFormat = C::NAMEID_TRANSIENT; } $randomUtils = new Utils\Random(); @@ -1468,7 +1468,7 @@ private static function buildResponse( $r = new Response(); $issuer = new Issuer(); $issuer->setValue($idpMetadata->getString('entityid')); - $issuer->setFormat(Constants::NAMEID_ENTITY); + $issuer->setFormat(C::NAMEID_ENTITY); $r->setIssuer($issuer); $r->setDestination($consumerURL); diff --git a/modules/saml/src/Message.php b/modules/saml/src/Message.php index c571de7077..456f331fa7 100644 --- a/modules/saml/src/Message.php +++ b/modules/saml/src/Message.php @@ -821,7 +821,7 @@ private static function processAssertion( $found = true; break; - } catch (\SimpleSAML\Error\Exception $e) { + } catch (SSP_Error\Exception $e) { $lastError = $e->getMessage(); continue; } diff --git a/public/admin/index.php b/public/admin/index.php index f84b8167a2..b48eb00f2e 100644 --- a/public/admin/index.php +++ b/public/admin/index.php @@ -11,7 +11,8 @@ $headers = $config->getOptionalArray('headers.security', Configuration::DEFAULT_SECURITY_HEADERS); $redirect = Module::getModuleURL('admin/'); -$response = new HTTP\RunnableResponse([$httpUtils, 'redirectTrustedURL'], [$redirect]); +$response = $httpUtils->redirectTrustedURL($redirect); + foreach ($headers as $header => $value) { // Some pages may have specific requirements that we must follow. Don't touch them. if (!$response->headers->has($header)) { diff --git a/public/index.php b/public/index.php index 5234b5e748..2b7395b5ff 100644 --- a/public/index.php +++ b/public/index.php @@ -11,11 +11,13 @@ $headers = $config->getOptionalArray('headers.security', Configuration::DEFAULT_SECURITY_HEADERS); $redirect = $config->getOptionalString('frontpage.redirect', Module::getModuleURL('core/welcome')); -$response = new HTTP\RunnableResponse([$httpUtils, 'redirectTrustedURL'], [$redirect]); +$response = $httpUtils->redirectTrustedURL($redirect); + foreach ($headers as $header => $value) { // Some pages may have specific requirements that we must follow. Don't touch them. if (!$response->headers->has($header)) { $response->headers->set($header, $value); } } + $response->send(); diff --git a/src/SimpleSAML/Auth/ProcessingChain.php b/src/SimpleSAML/Auth/ProcessingChain.php index b2997429a1..9667ceba23 100644 --- a/src/SimpleSAML/Auth/ProcessingChain.php +++ b/src/SimpleSAML/Auth/ProcessingChain.php @@ -5,13 +5,14 @@ namespace SimpleSAML\Auth; use Exception; -use SAML2\Exception\Protocol\NoPassiveException; use SimpleSAML\Assert\Assert; use SimpleSAML\Configuration; use SimpleSAML\Error; use SimpleSAML\Logger; use SimpleSAML\Module; +use SimpleSAML\SAML2\Exception\Protocol\NoPassiveException; use SimpleSAML\Utils; +use Symfony\Component\HttpFoundation\Response; use function array_key_exists; use function array_shift; @@ -234,8 +235,9 @@ public function processState(array &$state): void * to whatever exception handler is defined in the state array. * * @param array $state The state we are processing. + * @return \Symfony\Component\HttpFoundation\Response */ - public static function resumeProcessing(array $state): void + public static function resumeProcessing(array $state): Response { while (count($state[self::FILTERS_INDEX]) > 0) { $filter = array_shift($state[self::FILTERS_INDEX]); @@ -264,7 +266,7 @@ public static function resumeProcessing(array $state): void */ $id = State::saveState($state, self::COMPLETED_STAGE); $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($state['ReturnURL'], [self::AUTHPARAM => $id]); + $response = $httpUtils->redirectTrustedURL($state['ReturnURL'], [self::AUTHPARAM => $id]); } else { /* Pass the state to the function defined in $state['ReturnCall']. */ @@ -274,9 +276,11 @@ public static function resumeProcessing(array $state): void $func = $state['ReturnCall']; Assert::isCallable($func); - call_user_func($func, $state); - Assert::true(false); + $response = call_user_func($func, $state); + Assert::isInstanceOf($response, Response::class); } + + return $response; } @@ -305,7 +309,7 @@ public function processStatePassive(array &$state): void try { $filter->process($state); } catch (NoPassiveException $e) { - // Ignore \SAML2\Exception\Protocol\NoPassiveException exceptions + // Ignore \SimpleSAML\SAML2\Exception\Protocol\NoPassiveException exceptions } } } diff --git a/src/SimpleSAML/Auth/Simple.php b/src/SimpleSAML/Auth/Simple.php index b6f69d3b2c..9329d06a6e 100644 --- a/src/SimpleSAML/Auth/Simple.php +++ b/src/SimpleSAML/Auth/Simple.php @@ -10,6 +10,7 @@ use SimpleSAML\Module; use SimpleSAML\Session; use SimpleSAML\Utils; +use Symfony\Component\HttpFoundation\Response; /** * Helper class for simple authentication applications. @@ -93,15 +94,16 @@ public function isAuthenticated(): bool * method for a description. * * @param array $params Various options to the authentication request. See the documentation. + * @return \Symfony\Component\HttpFoundation\Response */ - public function requireAuth(array $params = []): void + public function requireAuth(array $params = []): ?Response { if ($this->session->isValid($this->authSource)) { // Already authenticated - return; + return null; } - $this->login($params); + return $this->login($params); } @@ -115,11 +117,10 @@ public function requireAuth(array $params = []): void * - 'ReturnTo': The URL the user should be returned to after authentication. * - 'ReturnCallback': The function we should call after the user has finished authentication. * - * Please note: this function never returns. - * * @param array $params Various options to the authentication request. + * @return \Symfony\Component\HttpFoundation\Response */ - public function login(array $params = []): void + public function login(array $params = []): Response { if (array_key_exists('KeepPost', $params)) { $keepPost = (bool) $params['KeepPost']; @@ -162,8 +163,7 @@ public function login(array $params = []): void $params[State::RESTART] = $restartURL; } - $as->initLogin($returnTo, $errorURL, $params); - Assert::true(false); + return $as->initLogin($returnTo, $errorURL, $params); } @@ -180,9 +180,10 @@ public function login(array $params = []): void * - 'ReturnStateStage': The stage the state array should be saved with. * * @param string|array|null $params Either the URL the user should be redirected to after logging out, or an array - * with parameters for the logout. If this parameter is null, we will return to the current page. + * with parameters for the logout. If this parameter is null, we will return to the current page. + * @return \Symfony\Component\HttpFoundation\Response */ - public function logout(string|array|null $params = null): void + public function logout(string|array|null $params = null): Response { if ($params === null) { $httpUtils = new Utils\HTTP(); @@ -214,28 +215,29 @@ public function logout(string|array|null $params = null): void $as = Source::getById($this->authSource); if ($as !== null) { - $as->logout($params); + $response = $as->logout($params); + if ($response instanceof Response) { + return $response; + } } } - self::logoutCompleted($params); + return self::logoutCompleted($params); } /** * Called when logout operation completes. * - * This function never returns. - * * @param array $state The state after the logout. + * @return \Symfony\Component\HttpFoundation\Response */ - public static function logoutCompleted(array $state): void + public static function logoutCompleted(array $state): Response { Assert::true(isset($state['ReturnTo']) || isset($state['ReturnCallback'])); if (isset($state['ReturnCallback'])) { - call_user_func($state['ReturnCallback'], $state); - Assert::true(false); + $response = call_user_func($state['ReturnCallback'], $state); } else { $params = []; if (isset($state['ReturnStateParam']) || isset($state['ReturnStateStage'])) { @@ -244,8 +246,10 @@ public static function logoutCompleted(array $state): void $params[$state['ReturnStateParam']] = $stateID; } $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($state['ReturnTo'], $params); + $response = $httpUtils->redirectTrustedURL($state['ReturnTo'], $params); } + + return $response; } diff --git a/src/SimpleSAML/Auth/Source.php b/src/SimpleSAML/Auth/Source.php index d9f1ca4478..49e11d60ee 100644 --- a/src/SimpleSAML/Auth/Source.php +++ b/src/SimpleSAML/Auth/Source.php @@ -11,6 +11,8 @@ use SimpleSAML\Module; use SimpleSAML\Session; use SimpleSAML\Utils; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * This class defines a base class for authentication source. @@ -103,9 +105,10 @@ public function getAuthId(): string * save the state, and at a later stage, load the state, update it with the authentication * information about the user, and call completeAuth with the state array. * + * @param \Symfony\Component\HttpFoundation\Request $request The current request * @param array &$state Information about the current authentication. */ - abstract public function authenticate(array &$state): void; + abstract public function authenticate(Request $request, array &$state): ?Response; /** @@ -136,13 +139,13 @@ public function reauthenticate(array &$state): void /** * Complete authentication. * - * This function should be called if authentication has completed. It will never return, - * except in the case of exceptions. Exceptions thrown from this page should not be caught, + * This function should be called if authentication has completed. It will return a Response, + * unless an exeption is thrown. Exceptions thrown from this page should not be caught, * but should instead be passed to the top-level exception handler. * * @param array &$state Information about the current authentication. */ - public static function completeAuth(array &$state): void + public static function completeAuth(array &$state): Response { Assert::keyExists($state, 'LoginCompletedHandler'); @@ -151,25 +154,26 @@ public static function completeAuth(array &$state): void $func = $state['LoginCompletedHandler']; Assert::isCallable($func); - call_user_func($func, $state); - Assert::true(false); + $response = call_user_func($func, $state); + Assert::isInstanceOf($response, Response::class); + return $response; } /** * Start authentication. * - * This method never returns. - * * @param string|array $return The URL or function we should direct the user to after authentication. If using a - * URL obtained from user input, please make sure to check it by calling \SimpleSAML\Utils\HTTP::checkURLAllowed(). + * URL obtained from user input, please make sure to check it by calling + * \SimpleSAML\Utils\HTTP::checkURLAllowed(). * @param string|null $errorURL The URL we should direct the user to after failed authentication. Can be null, in - * which case a standard error page will be shown. If using a URL obtained from user input, please make sure to - * check it by calling \SimpleSAML\Utils\HTTP::checkURLAllowed(). + * which case a standard error page will be shown. If using a URL obtained from user input, please make sure to + * check it by calling \SimpleSAML\Utils\HTTP::checkURLAllowed(). * @param array $params Extra information about the login. Different authentication requestors may provide different - * information. Optional, will default to an empty array. + * information. Optional, will default to an empty array. + * @return \Symfony\Component\HttpFoundation\Response */ - public function initLogin(string|array $return, ?string $errorURL = null, array $params = []): void + public function initLogin(string|array $return, ?string $errorURL = null, array $params = []): Response { $state = array_merge($params, [ '\SimpleSAML\Auth\Source.id' => $this->authId, @@ -190,26 +194,30 @@ public function initLogin(string|array $return, ?string $errorURL = null, array $state[State::EXCEPTION_HANDLER_URL] = $errorURL; } + $request = Request::createFromGlobals(); try { - $this->authenticate($state); + $response = $this->authenticate($request, $state); + if ($response instanceof Response) { + return $response; + } } catch (Error\Exception $e) { State::throwException($state, $e); } catch (\Exception $e) { $e = new Error\UnserializableException($e); State::throwException($state, $e); } - self::loginCompleted($state); + + return self::loginCompleted($state); } /** * Called when a login operation has finished. * - * This method never returns. - * * @param array $state The state after the login has completed. + * @return \Symfony\Component\HttpFoundation\Response */ - public static function loginCompleted(array $state): void + public static function loginCompleted(array $state): Response { Assert::keyExists($state, '\SimpleSAML\Auth\Source.Return'); Assert::keyExists($state, '\SimpleSAML\Auth\Source.id'); @@ -226,11 +234,13 @@ public static function loginCompleted(array $state): void if (is_string($return)) { // redirect... $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($return); + $response = $httpUtils->redirectTrustedURL($return); } else { - call_user_func($return, $state); + $response = call_user_func($return, $state); + Assert::isInstanceOf($response, Response::class); } - Assert::true(false); + + return $response; } @@ -246,10 +256,12 @@ public static function loginCompleted(array $state): void * showing the user a page, or redirecting, this function should return. * * @param array &$state Information about the current logout operation. + * @return \Symfony\Component\HttpFoundation\Response|null */ - public function logout(array &$state): void + public function logout(array &$state): ?Response { // default logout handler which doesn't do anything + return null; } @@ -261,8 +273,9 @@ public function logout(array &$state): void * but should instead be passed to the top-level exception handler. * * @param array &$state Information about the current authentication. + * @return \Symfony\Component\HttpFoundation\Response */ - public static function completeLogout(array &$state): void + public static function completeLogout(array &$state): Response { Assert::keyExists($state, 'LogoutCompletedHandler'); @@ -271,8 +284,9 @@ public static function completeLogout(array &$state): void $func = $state['LogoutCompletedHandler']; Assert::isCallable($func); - call_user_func($func, $state); - Assert::true(false); + $response = call_user_func($func, $state); + Assert::isInstanceOf($response, Response::class); + return $response; } @@ -440,11 +454,10 @@ protected function addLogoutCallback(string $assoc, array $state): void * This function calls a logout callback based on an association saved with * addLogoutCallback(...). * - * This function always returns. - * * @param string $assoc The logout association which should be called. + * @return \Symfony\Component\HttpFoundation\Response */ - protected function callLogoutCallback(string $assoc): void + protected function callLogoutCallback(string $assoc): ?Response { $id = strlen($this->authId) . ':' . $this->authId . $assoc; @@ -455,7 +468,7 @@ protected function callLogoutCallback(string $assoc): void // FIXME: fix for IdP-first flow (issue 397) -> reevaluate logout callback infrastructure $session->doLogout($this->authId); - return; + return null; } Assert::isArray($data); @@ -466,7 +479,9 @@ protected function callLogoutCallback(string $assoc): void $callbackState = $data['state']; $session->deleteData('\SimpleSAML\Auth\Source.LogoutCallbacks', $id); - call_user_func($callback, $callbackState); + $response = call_user_func($callback, $callbackState); + Assert::isInstanceOf($response, Response::class); + return $response; } diff --git a/src/SimpleSAML/Auth/State.php b/src/SimpleSAML/Auth/State.php index ce4df664b6..0ad4dc6ded 100644 --- a/src/SimpleSAML/Auth/State.php +++ b/src/SimpleSAML/Auth/State.php @@ -11,6 +11,7 @@ use SimpleSAML\Logger; use SimpleSAML\Session; use SimpleSAML\Utils; +use Symfony\Component\HttpFoundation\Response; use function filter_var; use function preg_match; @@ -296,7 +297,8 @@ public static function loadState(string $id, string $stage, bool $allowMissing = throw new Error\NoState(); } - $httpUtils->redirectUntrustedURL($sid['url']); + $response = $httpUtils->redirectUntrustedURL($sid['url']); + $response->send(); } $state = unserialize($state); @@ -320,7 +322,8 @@ public static function loadState(string $id, string $stage, bool $allowMissing = throw new Exception($msg); } - $httpUtils->redirectUntrustedURL($sid['url']); + $response = $httpUtils->redirectUntrustedURL($sid['url']); + $response->send(); } return $state; @@ -365,17 +368,19 @@ public static function throwException(array $state, Error\Exception $exception): $id = self::saveState($state, self::EXCEPTION_STAGE); // Redirect to the exception handler - $httpUtils->redirectTrustedURL( + $response = $httpUtils->redirectTrustedURL( $state[self::EXCEPTION_HANDLER_URL], [self::EXCEPTION_PARAM => $id], ); + $response->send(); } elseif (array_key_exists(self::EXCEPTION_HANDLER_FUNC, $state)) { // Call the exception handler $func = $state[self::EXCEPTION_HANDLER_FUNC]; Assert::isCallable($func); - call_user_func($func, $exception, $state); - Assert::true(false); + $response = call_user_func($func, $exception, $state); + Assert::isInstanceOf($response, Response::class); + $response->send(); } else { /* * No exception handler is defined for the current state. diff --git a/src/SimpleSAML/Configuration.php b/src/SimpleSAML/Configuration.php index 834a5b29ec..eba82dfcfe 100644 --- a/src/SimpleSAML/Configuration.php +++ b/src/SimpleSAML/Configuration.php @@ -7,10 +7,10 @@ use Exception; use ParseError; use SAML2\Binding; -use SAML2\Constants; use SAML2\Exception\Protocol\UnsupportedBindingException; use SimpleSAML\Assert\Assert; use SimpleSAML\Error; +use SimpleSAML\SAML2\Constants as C; use SimpleSAML\Utils; use Symfony\Component\Filesystem\Filesystem; @@ -1178,12 +1178,12 @@ private function getDefaultBinding(string $endpointType): string case 'saml20-idp-remote:SingleSignOnService': case 'saml20-idp-remote:SingleLogoutService': case 'saml20-sp-remote:SingleLogoutService': - return Constants::BINDING_HTTP_REDIRECT; + return C::BINDING_HTTP_REDIRECT; case 'saml20-sp-remote:AssertionConsumerService': - return Constants::BINDING_HTTP_POST; + return C::BINDING_HTTP_POST; case 'saml20-idp-remote:ArtifactResolutionService': case 'attributeauthority-remote:AttributeService': - return Constants::BINDING_SOAP; + return C::BINDING_SOAP; default: throw new Exception('Missing default binding for ' . $endpointType . ' in ' . $set); } diff --git a/src/SimpleSAML/Error/Error.php b/src/SimpleSAML/Error/Error.php index 21a3b0eb66..42081406ed 100644 --- a/src/SimpleSAML/Error/Error.php +++ b/src/SimpleSAML/Error/Error.php @@ -278,11 +278,11 @@ public function show(int $logLevel = Logger::ERR, bool $suppressReport = false): } $show_function = $config->getOptionalArray('errors.show_function', null); - if (isset($show_function)) { - Assert::isCallable($show_function); + Assert::nullOrIsCallable($show_function); + if ($show_function !== null) { $this->setHTTPCode(); - call_user_func($show_function, $config, $data); - Assert::true(false); + $response = call_user_func($show_function, $config, $data); + $response->send(); } else { $t = new Template($config, 'error.twig'); @@ -298,7 +298,5 @@ public function show(int $logLevel = Logger::ERR, bool $suppressReport = false): $t->data = array_merge($t->data, $data); $t->send(); } - - exit; } } diff --git a/src/SimpleSAML/IdP.php b/src/SimpleSAML/IdP.php index 97da6a5ed3..3e84cd7549 100644 --- a/src/SimpleSAML/IdP.php +++ b/src/SimpleSAML/IdP.php @@ -4,8 +4,7 @@ namespace SimpleSAML; -use SAML2\Constants; -use SAML2\Exception\Protocol\NoPassiveException; +use Exception; use SimpleSAML\Assert\Assert; use SimpleSAML\Auth; use SimpleSAML\Configuration; @@ -14,7 +13,10 @@ use SimpleSAML\IdP\LogoutHandlerInterface; use SimpleSAML\IdP\TraditionalLogoutHandler; use SimpleSAML\Metadata\MetaDataStorageHandler; +use SimpleSAML\SAML2\Constants as C; +use SimpleSAML\SAML2\Exception\Protocol\NoPassiveException; use SimpleSAML\Utils; +use Symfony\Component\HttpFoundation\Response; /** * IdP class. @@ -57,6 +59,13 @@ class IdP */ private Configuration $config; + /** + * The global configuration. + * + * @var \SimpleSAML\Configuration + */ + private Configuration $globalConfig; + /** * Our authsource. * @@ -68,30 +77,31 @@ class IdP /** * Initialize an IdP. * + * @param \SimpleSAML\Configuration $config The configuration * @param string $id The identifier of this IdP. * * @throws \SimpleSAML\Error\Exception If the IdP is disabled or no such auth source was found. */ - private function __construct(string $id) + private function __construct(Configuration $config, string $id) { $this->id = $id; $this->associationGroup = $id; + $this->globalConfig = $config; $metadata = MetaDataStorageHandler::getMetadataHandler(); - $globalConfig = Configuration::getInstance(); if (substr($id, 0, 6) === 'saml2:') { - if (!$globalConfig->getOptionalBoolean('enable.saml20-idp', false)) { + if (!$this->globalConfig->getOptionalBoolean('enable.saml20-idp', false)) { throw new Error\Exception('enable.saml20-idp disabled in config.php.'); } $this->config = $metadata->getMetaDataConfig(substr($id, 6), 'saml20-idp-hosted'); } elseif (substr($id, 0, 5) === 'adfs:') { - if (!$globalConfig->getOptionalBoolean('enable.adfs-idp', false)) { + if (!$this->globalConfig->getOptionalBoolean('enable.adfs-idp', false)) { throw new Error\Exception('enable.adfs-idp disabled in config.php.'); } $this->config = $metadata->getMetaDataConfig(substr($id, 5), 'adfs-idp-hosted'); - if ($globalConfig->getOptionalBoolean('enable.saml20-idp', false)) { + if ($this->globalConfig->getOptionalBoolean('enable.saml20-idp', false)) { try { // this makes the ADFS IdP use the same SP associations as the SAML 2.0 IdP $saml2EntityId = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted'); @@ -101,7 +111,7 @@ private function __construct(string $id) } } } else { - throw new \Exception("Protocol not implemented."); + throw new Exception("Protocol not implemented."); } $auth = $this->config->getString('auth'); @@ -127,17 +137,18 @@ public function getId(): string /** * Retrieve an IdP by ID. * + * @param \SimpleSAML\Configuration $config The Configuration * @param string $id The identifier of the IdP. * * @return \SimpleSAML\IdP The IdP. */ - public static function getById(string $id): IdP + public static function getById(Configuration $config, string $id): IdP { if (isset(self::$idpCache[$id])) { return self::$idpCache[$id]; } - $idp = new self($id); + $idp = new self($config, $id); self::$idpCache[$id] = $idp; return $idp; } @@ -146,15 +157,16 @@ public static function getById(string $id): IdP /** * Retrieve the IdP "owning" the state. * + * @param \SimpleSAML\Configuration $config The Configuration * @param array &$state The state array. * * @return \SimpleSAML\IdP The IdP. */ - public static function getByState(array &$state): IdP + public static function getByState(Configuration $config, array &$state): IdP { Assert::notNull($state['core:IdP']); - return self::getById($state['core:IdP']); + return self::getById($config, $state['core:IdP']); } @@ -187,7 +199,7 @@ public function getSPName(string $assocId): ?array if ($prefix === 'saml') { try { $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote'); - } catch (\Exception $e) { + } catch (Exception $e) { return null; } } else { @@ -264,8 +276,9 @@ public function isAuthenticated(): bool * Called after authproc has run. * * @param array $state The authentication request state array. + * @return \Symfony\Component\HttpFoundation\Response */ - public static function postAuthProc(array $state): void + public static function postAuthProc(array $state): Response { Assert::isCallable($state['Responder']); @@ -279,8 +292,9 @@ public static function postAuthProc(array $state): void ); } - call_user_func($state['Responder'], $state); - Assert::true(false); + $response = call_user_func($state['Responder'], $state); + Assert::isInstanceOf($response, Response::class); + return $response; } @@ -288,12 +302,13 @@ public static function postAuthProc(array $state): void * The user is authenticated. * * @param array $state The authentication request state array. + * @return \Symfony\Component\HttpFoundation\Response * * @throws \SimpleSAML\Error\Exception If we are not authenticated. */ - public static function postAuth(array $state): void + public static function postAuth(array $state): Response { - $idp = IdP::getByState($state); + $idp = IdP::getByState(Configuration::getInstance(), $state); if (!$idp->isAuthenticated()) { throw new Error\Exception('Not authenticated.'); @@ -325,7 +340,7 @@ public static function postAuth(array $state): void $pc->processState($state); - self::postAuthProc($state); + return self::postAuthProc($state); } @@ -335,16 +350,17 @@ public static function postAuth(array $state): void * This function authenticates the user. * * @param array &$state The authentication request state. + * @return \Symfony\Component\HttpFoundation\Response * - * @throws \SimpleSAML\Module\saml\Error\NoPassive If we were asked to do passive authentication. + * @throws \SimpleSAML\SAML2\Exception\Protocol\NoPassiveException If we were asked to do passive authentication. */ - private function authenticate(array &$state): void + private function authenticate(array &$state): Response { if (isset($state['isPassive']) && (bool) $state['isPassive']) { - throw new NoPassiveException(Constants::STATUS_RESPONDER . ': Passive authentication not supported.'); + throw new NoPassiveException(C::STATUS_RESPONDER . ': Passive authentication not supported.'); } - $this->authSource->login($state); + return $this->authSource->login($state); } @@ -371,8 +387,9 @@ private function reauthenticate(array &$state): void * Process authentication requests. * * @param array &$state The authentication request state. + * @return \Symfony\Component\HttpFoundation\Response */ - public function handleAuthenticationRequest(array &$state): void + public function handleAuthenticationRequest(array &$state): Response { Assert::notNull($state['Responder']); @@ -400,18 +417,19 @@ public function handleAuthenticationRequest(array &$state): void try { if ($needAuth) { - $this->authenticate($state); - Assert::true(false); + return $this->authenticate($state); } else { $this->reauthenticate($state); } - $this->postAuth($state); + return $this->postAuth($state); } catch (Error\Exception $e) { Auth\State::throwException($state, $e); - } catch (\Exception $e) { + } catch (Exception $e) { $e = new Error\UnserializableException($e); Auth\State::throwException($state, $e); } + + throw new Exception('Should never happen.'); } @@ -447,27 +465,28 @@ public function getLogoutHandler(): LogoutHandlerInterface * This function will never return. * * @param array &$state The logout request state. + * @return \Symfony\Component\HttpFoundation\Response */ - public function finishLogout(array &$state): void + public function finishLogout(array &$state): Response { Assert::notNull($state['Responder']); - $idp = IdP::getByState($state); - call_user_func($state['Responder'], $idp, $state); - Assert::true(false); + $idp = IdP::getByState($this->globalConfig, $state); + $response = call_user_func($state['Responder'], $idp, $state); + Assert::isInstanceOf($response, Response::class); + return $response; } /** * Process a logout request. * - * This function will never return. - * * @param array &$state The logout request state. * @param string|null $assocId The association we received the logout request from, or null if there was no - * association. + * association. + * @return \Symfony\Component\HttpFoundation\Response */ - public function handleLogoutRequest(array &$state, ?string $assocId): void + public function handleLogoutRequest(array &$state, ?string $assocId): Response { Assert::notNull($state['Responder']); Assert::nullOrString($assocId); @@ -487,24 +506,20 @@ public function handleLogoutRequest(array &$state, ?string $assocId): void $this->authSource->logout($returnTo); - if ($assocId !== null) { - $handler = $this->getLogoutHandler(); - $handler->startLogout($state, $assocId); - } - Assert::true(false); + $handler = $this->getLogoutHandler(); + return $handler->startLogout($state, $assocId); } /** * Process a logout response. * - * This function will never return. - * * @param string $assocId The association that is terminated. * @param string|null $relayState The RelayState from the start of the logout. * @param \SimpleSAML\Error\Exception|null $error The error that occurred during session termination (if any). + * @return \Symfony\Component\HttpFoundation\Response */ - public function handleLogoutResponse(string $assocId, ?string $relayState, ?Error\Exception $error = null): void + public function handleLogoutResponse(string $assocId, ?string $relayState, ?Error\Exception $error = null): Response { $index = strpos($assocId, ':'); Assert::integer($index); @@ -513,26 +528,24 @@ public function handleLogoutResponse(string $assocId, ?string $relayState, ?Erro $session->deleteData('core:idp-ssotime', $this->id . ';' . substr($assocId, $index + 1)); $handler = $this->getLogoutHandler(); - $handler->onResponse($assocId, $relayState, $error); + return $handler->onResponse($assocId, $relayState, $error); } /** * Log out, then redirect to a URL. * - * This function never returns. - * * @param string $url The URL the user should be returned to after logout. + * @return \Symfony\Component\HttpFoundation\Response */ - public function doLogoutRedirect(string $url): void + public function doLogoutRedirect(string $url): Response { $state = [ 'Responder' => [IdP::class, 'finishLogoutRedirect'], 'core:Logout:URL' => $url, ]; - $this->handleLogoutRequest($state, null); - Assert::true(false); + return $this->handleLogoutRequest($state, null); } @@ -543,13 +556,13 @@ public function doLogoutRedirect(string $url): void * * @param \SimpleSAML\IdP $idp Deprecated. Will be removed. * @param array &$state The logout state from doLogoutRedirect(). + * @return \Symfony\Component\HttpFoundation\Response */ - public static function finishLogoutRedirect(IdP $idp, array $state): void + public static function finishLogoutRedirect(IdP $idp, array $state): Response { Assert::notNull($state['core:Logout:URL']); $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($state['core:Logout:URL']); - Assert::true(false); + return $httpUtils->redirectTrustedURL($state['core:Logout:URL']); } } diff --git a/src/SimpleSAML/IdP/IFrameLogoutHandler.php b/src/SimpleSAML/IdP/IFrameLogoutHandler.php index 75f9645e8e..9a8f368ccd 100644 --- a/src/SimpleSAML/IdP/IFrameLogoutHandler.php +++ b/src/SimpleSAML/IdP/IFrameLogoutHandler.php @@ -11,6 +11,11 @@ use SimpleSAML\Module; use SimpleSAML\Utils; use SimpleSAML\XHTML\Template; +use Symfony\Component\HttpFoundation\Response; + +use function is_null; +use function sha1; +use function var_export; /** * Class that handles iframe logout. @@ -36,17 +41,18 @@ public function __construct( * * @param array &$state The logout state. * @param string|null $assocId The SP we are logging out from. + * @return \Symfony\Component\HttpFoundation\Response */ - public function startLogout(array &$state, ?string $assocId): void + public function startLogout(array &$state, ?string $assocId): Response { $associations = $this->idp->getAssociations(); if (count($associations) === 0) { - $this->idp->finishLogout($state); + return $this->idp->finishLogout($state); } foreach ($associations as $id => &$association) { - $idp = IdP::getByState($association); + $idp = IdP::getByState(Configuration::getInstance(), $association); $association['core:Logout-IFrame:Name'] = $idp->getSPName($id); $association['core:Logout-IFrame:State'] = 'onhold'; } @@ -72,20 +78,19 @@ public function startLogout(array &$state, ?string $assocId): void $url = Module::getModuleURL('core/logout-iframe', $params); $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($url); + return $httpUtils->redirectTrustedURL($url); } /** * Continue the logout operation. * - * This function will never return. - * * @param string $assocId The association that is terminated. * @param string|null $relayState The RelayState from the start of the logout. * @param \SimpleSAML\Error\Exception|null $error The error that occurred during session termination (if any). + * @return \Symfony\Component\HttpFoundation\Response */ - public function onResponse(string $assocId, ?string $relayState, ?Error\Exception $error = null): void + public function onResponse(string $assocId, ?string $relayState, ?Error\Exception $error = null): Response { $this->idp->terminateAssociation($assocId); @@ -98,6 +103,6 @@ public function onResponse(string $assocId, ?string $relayState, ?Error\Exceptio $t->data['errorMsg'] = $error->getMessage(); } - $t->send(); + return $t; } } diff --git a/src/SimpleSAML/IdP/LogoutHandlerInterface.php b/src/SimpleSAML/IdP/LogoutHandlerInterface.php index 148981d072..13504ddb2a 100644 --- a/src/SimpleSAML/IdP/LogoutHandlerInterface.php +++ b/src/SimpleSAML/IdP/LogoutHandlerInterface.php @@ -6,6 +6,7 @@ use SimpleSAML\Error; use SimpleSAML\IdP; +use Symfony\Component\HttpFoundation\Response; /** * Interface that all logout handlers must implement. @@ -30,18 +31,18 @@ public function __construct(IdP $idp); * * @param array &$state The logout state. * @param string|null $assocId The association that started the logout. + * @return \Symfony\Component\HttpFoundation\Response */ - public function startLogout(array &$state, ?string $assocId): void; + public function startLogout(array &$state, ?string $assocId): Response; /** * Handles responses to our logout requests. * - * This function will never return. - * * @param string $assocId The association that is terminated. * @param string|null $relayState The RelayState from the start of the logout. * @param \SimpleSAML\Error\Exception|null $error The error that occurred during session termination (if any). + * @return \Symfony\Component\HttpFoundation\Response */ - public function onResponse(string $assocId, ?string $relayState, ?Error\Exception $error = null): void; + public function onResponse(string $assocId, ?string $relayState, ?Error\Exception $error = null): Response; } diff --git a/src/SimpleSAML/IdP/TraditionalLogoutHandler.php b/src/SimpleSAML/IdP/TraditionalLogoutHandler.php index c86b3f9c07..05333241ae 100644 --- a/src/SimpleSAML/IdP/TraditionalLogoutHandler.php +++ b/src/SimpleSAML/IdP/TraditionalLogoutHandler.php @@ -4,12 +4,17 @@ namespace SimpleSAML\IdP; -use SimpleSAML\Assert\Assert; +use Exception; use SimpleSAML\Auth; +use SimpleSAML\Configuration; use SimpleSAML\Error; use SimpleSAML\IdP; use SimpleSAML\Logger; use SimpleSAML\Utils; +use Symfony\Component\HttpFoundation\Response; + +use function call_user_func; +use function var_export; /** * Class that handles traditional logout. @@ -33,15 +38,14 @@ public function __construct( /** * Picks the next SP and issues a logout request. * - * This function never returns. - * * @param array &$state The logout state. + * @return \Symfony\Component\HttpFoundation\Response */ - private function logoutNextSP(array &$state): void + private function logoutNextSP(array &$state): Response { $association = array_pop($state['core:LogoutTraditional:Remaining']); if ($association === null) { - $this->idp->finishLogout($state); + return $this->idp->finishLogout($state); } $relayState = Auth\State::saveState($state, 'core:LogoutTraditional', true); @@ -50,18 +54,17 @@ private function logoutNextSP(array &$state): void Logger::info('Logging out of ' . var_export($id, true) . '.'); try { - $idp = IdP::getByState($association); + $idp = IdP::getByState(Configuration::getInstance(), $association); $url = call_user_func([$association['Handler'], 'getLogoutURL'], $idp, $association, $relayState); $httpUtils = new Utils\HTTP(); - $httpUtils->redirectTrustedURL($url); - } catch (\Exception $e) { + return $httpUtils->redirectTrustedURL($url); + } catch (Exception $e) { Logger::warning('Unable to initialize logout to ' . var_export($id, true) . '.'); $this->idp->terminateAssociation($id); $state['core:Failed'] = true; // Try the next SP - $this->logoutNextSP($state); - Assert::true(false); + return $this->logoutNextSP($state); } } @@ -69,31 +72,29 @@ private function logoutNextSP(array &$state): void /** * Start the logout operation. * - * This function never returns. - * * @param array &$state The logout state. * @param string|null $assocId The association that started the logout. + * @return \Symfony\Component\HttpFoundation\Response */ - public function startLogout(array &$state, /** @scrutinizer ignore-unused */?string $assocId): void + public function startLogout(array &$state, /** @scrutinizer ignore-unused */?string $assocId): Response { $state['core:LogoutTraditional:Remaining'] = $this->idp->getAssociations(); - $this->logoutNextSP($state); + return $this->logoutNextSP($state); } /** * Continue the logout operation. * - * This function will never return. - * * @param string $assocId The association that is terminated. * @param string|null $relayState The RelayState from the start of the logout. * @param \SimpleSAML\Error\Exception|null $error The error that occurred during session termination (if any). + * @return \Symfony\Component\HttpFoundation\Response * * @throws \SimpleSAML\Error\Exception If the RelayState was lost during logout. */ - public function onResponse(string $assocId, ?string $relayState, ?Error\Exception $error = null): void + public function onResponse(string $assocId, ?string $relayState, ?Error\Exception $error = null): Response { if ($relayState === null) { throw new Error\Exception('RelayState lost during logout.'); @@ -110,6 +111,6 @@ public function onResponse(string $assocId, ?string $relayState, ?Error\Exceptio $state['core:Failed'] = true; } - $this->logoutNextSP($state); + return $this->logoutNextSP($state); } } diff --git a/src/SimpleSAML/Metadata/MetaDataStorageHandler.php b/src/SimpleSAML/Metadata/MetaDataStorageHandler.php index ccd7627c87..7400f073ee 100644 --- a/src/SimpleSAML/Metadata/MetaDataStorageHandler.php +++ b/src/SimpleSAML/Metadata/MetaDataStorageHandler.php @@ -4,11 +4,11 @@ namespace SimpleSAML\Metadata; -use SAML2\Constants; use SimpleSAML\Assert\Assert; use SimpleSAML\Configuration; use SimpleSAML\Error; use SimpleSAML\Logger; +use SimpleSAML\SAML2\Constants as C; use SimpleSAML\Utils; use SimpleSAML\Utils\ClearableState; @@ -20,6 +20,11 @@ class MetaDataStorageHandler implements ClearableState { + /** + * The configuration + */ + protected Configuration $globalConfig; + /** * This static variable contains a reference to the current * instance of the metadata handler. This variable will be null if @@ -48,7 +53,7 @@ class MetaDataStorageHandler implements ClearableState public static function getMetadataHandler(): MetaDataStorageHandler { if (self::$metadataHandler === null) { - self::$metadataHandler = new MetaDataStorageHandler(); + self::$metadataHandler = new MetaDataStorageHandler(Configuration::getInstance()); } return self::$metadataHandler; @@ -58,12 +63,14 @@ public static function getMetadataHandler(): MetaDataStorageHandler /** * This constructor initializes this metadata storage handler. It will load and * parse the configuration, and initialize the metadata source list. + * + * @param \SimpleSAML\Configuration $globalConfig */ - protected function __construct() + protected function __construct(Configuration $globalConfig) { - $config = Configuration::getInstance(); + $this->globalConfig = $globalConfig; - $sourcesConfig = $config->getOptionalArray('metadata.sources', [['type' => 'flatfile']]); + $sourcesConfig = $this->globalConfig->getOptionalArray('metadata.sources', [['type' => 'flatfile']]); try { $this->sources = MetaDataStorageSource::parseSources($sourcesConfig); @@ -102,16 +109,15 @@ public function getGenerated( } // get the configuration - $config = Configuration::getInstance(); $httpUtils = new Utils\HTTP(); - $baseurl = $httpUtils->getSelfURLHost() . $config->getBasePath(); + $baseurl = $httpUtils->getSelfURLHost() . $this->globalConfig->getBasePath(); if ($overrideHost !== null) { $baseurl = str_replace('://' . $httpUtils->getSelfHost() . '/', '://' . $overrideHost . '/', $baseurl); } if ($set == 'saml20-sp-hosted') { if ($property === 'SingleLogoutServiceBinding') { - return Constants::BINDING_HTTP_REDIRECT; + return C::BINDING_HTTP_REDIRECT; } } elseif ($set == 'saml20-idp-hosted') { switch ($property) { @@ -119,13 +125,13 @@ public function getGenerated( return $baseurl . 'module.php/saml/idp/singleSignOnService'; case 'SingleSignOnServiceBinding': - return Constants::BINDING_HTTP_REDIRECT; + return C::BINDING_HTTP_REDIRECT; case 'SingleLogoutService': return $baseurl . 'module.php/saml/idp/singleLogout'; case 'SingleLogoutServiceBinding': - return Constants::BINDING_HTTP_REDIRECT; + return C::BINDING_HTTP_REDIRECT; } } diff --git a/src/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php b/src/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php index f314f26378..f6126588db 100644 --- a/src/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php +++ b/src/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php @@ -54,13 +54,10 @@ class MetaDataStorageHandlerFlatFile extends MetaDataStorageSource * * @param array $config An associative array with the configuration for this handler. */ - protected function __construct(array $config) + protected function __construct(Configuration $globalConfig, array $config) { parent::__construct(); - // get the configuration - $globalConfig = Configuration::getInstance(); - // find the path to the directory we should search for metadata in if (array_key_exists('directory', $config)) { $this->directory = $config['directory'] ?: 'metadata/'; @@ -95,6 +92,7 @@ protected function __construct(array $config) private function load(string $set): ?array { $metadatasetfile = $this->directory . $set . '.php'; + if ($this->file) { $metadatasetfile = $this->file; } diff --git a/src/SimpleSAML/Metadata/MetaDataStorageHandlerPdo.php b/src/SimpleSAML/Metadata/MetaDataStorageHandlerPdo.php index 3c851fd9ed..375f9c89eb 100644 --- a/src/SimpleSAML/Metadata/MetaDataStorageHandlerPdo.php +++ b/src/SimpleSAML/Metadata/MetaDataStorageHandlerPdo.php @@ -4,6 +4,7 @@ namespace SimpleSAML\Metadata; +use SimpleSAML\Configuration; use SimpleSAML\Database; use SimpleSAML\Error; @@ -57,11 +58,17 @@ class MetaDataStorageHandlerPdo extends MetaDataStorageSource * - 'username': Database user name * - 'password': Password for the database user. * + * @param \SimpleSAML\Configuration $globalConfig The global config. * @param array $config An associative array with the configuration for this handler. - * @phpstan-ignore constructor.unusedParameter + * + * @phpstan-ignore constructor.unusedParameter,constructor.unusedParameter */ - public function __construct(/** @scrutinizer ignore-unused */ array $config) - { + public function __construct( + /** @scrutinizer ignore-unused */ Configuration $globalConfig, + /** @scrutinizer ignore-unused */ array $config, + ) { + parent::__construct(); + $this->db = Database::getInstance(); } diff --git a/src/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php b/src/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php index cf975379df..186070e289 100644 --- a/src/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php +++ b/src/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php @@ -47,13 +47,13 @@ class MetaDataStorageHandlerSerialize extends MetaDataStorageSource * * Parses configuration. * + * @param \SimpleSAML\Configuration $globalConfig The global configuration * @param array $config The configuration for this metadata handler. */ - public function __construct(array $config) + public function __construct(Configuration $globalConfig, array $config) { parent::__construct(); - $globalConfig = Configuration::getInstance(); $cfgHelp = Configuration::loadFromArray($config, 'serialize metadata source'); $this->directory = $cfgHelp->getString('directory'); diff --git a/src/SimpleSAML/Metadata/MetaDataStorageHandlerXML.php b/src/SimpleSAML/Metadata/MetaDataStorageHandlerXML.php index dd29bd04b4..f5843af794 100644 --- a/src/SimpleSAML/Metadata/MetaDataStorageHandlerXML.php +++ b/src/SimpleSAML/Metadata/MetaDataStorageHandlerXML.php @@ -34,17 +34,19 @@ class MetaDataStorageHandlerXML extends MetaDataStorageSource * base directory. * - 'url': URL we should download the metadata from. This is only meant for testing. * + * @param \SimpleSAML\Configuration $globalConfig * @param array $config The configuration for this instance of the XML metadata source. * * @throws \Exception If neither the 'file' or 'url' options are defined in the configuration. */ - protected function __construct(array $config) + protected function __construct(Configuration $globalConfig, array $config) { + parent::__construct(); + $src = $srcXml = null; $context = []; if (array_key_exists('file', $config)) { // get the configuration - $globalConfig = Configuration::getInstance(); $src = $globalConfig->resolvePath($config['file']); $srcXml = file_get_contents($src); } elseif (array_key_exists('url', $config)) { diff --git a/src/SimpleSAML/Metadata/MetaDataStorageSource.php b/src/SimpleSAML/Metadata/MetaDataStorageSource.php index 2170270f7b..5b383140a1 100644 --- a/src/SimpleSAML/Metadata/MetaDataStorageSource.php +++ b/src/SimpleSAML/Metadata/MetaDataStorageSource.php @@ -4,6 +4,7 @@ namespace SimpleSAML\Metadata; +use SimpleSAML\Configuration; use SimpleSAML\Error; use SimpleSAML\Module; use SimpleSAML\Utils; @@ -83,18 +84,19 @@ public static function getSource(array $sourceConfig): MetaDataStorageSource $type = 'flatfile'; } + $config = Configuration::getInstance(); switch ($type) { case 'flatfile': - return new MetaDataStorageHandlerFlatFile($sourceConfig); + return new MetaDataStorageHandlerFlatFile($config, $sourceConfig); case 'xml': - return new MetaDataStorageHandlerXML($sourceConfig); + return new MetaDataStorageHandlerXML($config, $sourceConfig); case 'serialize': - return new MetaDataStorageHandlerSerialize($sourceConfig); + return new MetaDataStorageHandlerSerialize($config, $sourceConfig); case 'mdx': case 'mdq': - return new Sources\MDQ($sourceConfig); + return new Sources\MDQ($config, $sourceConfig); case 'pdo': - return new MetaDataStorageHandlerPdo($sourceConfig); + return new MetaDataStorageHandlerPdo($config, $sourceConfig); case 'directory': return new MetaDataStorageHandlerDirectory($sourceConfig); default: @@ -113,7 +115,7 @@ public static function getSource(array $sourceConfig): MetaDataStorageSource } /** @var \SimpleSAML\Metadata\MetaDataStorageSource */ - return new $className($sourceConfig); + return new $className($config, $sourceConfig); } } diff --git a/src/SimpleSAML/Metadata/Sources/MDQ.php b/src/SimpleSAML/Metadata/Sources/MDQ.php index 53df04f9b8..2c0d0eaff0 100644 --- a/src/SimpleSAML/Metadata/Sources/MDQ.php +++ b/src/SimpleSAML/Metadata/Sources/MDQ.php @@ -75,11 +75,12 @@ class MDQ extends MetaDataStorageSource * - 'cachedir': Directory where metadata can be cached. Optional. * - 'cachelength': Maximum time metadata cah be cached, in seconds. Default to 24 hours (86400 seconds). * - * @param array $config The configuration for this instance of the XML metadata source. + * @param \SimpleSAML\Configuration $globalConfig The global configuration + * @param array $config The configuration for this instance of the XML metadata source. * * @throws \Exception If no server option can be found in the configuration. */ - protected function __construct(array $config) + protected function __construct(Configuration $globalConfig, array $config) { parent::__construct(); @@ -94,7 +95,6 @@ protected function __construct(array $config) } if (array_key_exists('cachedir', $config)) { - $globalConfig = Configuration::getInstance(); $this->cacheDir = $globalConfig->resolvePath($config['cachedir']); } diff --git a/src/SimpleSAML/Utils/Auth.php b/src/SimpleSAML/Utils/Auth.php index 97c4c9b16d..b8de3e4d7d 100644 --- a/src/SimpleSAML/Utils/Auth.php +++ b/src/SimpleSAML/Utils/Auth.php @@ -7,6 +7,7 @@ use SimpleSAML\Auth as Authentication; use SimpleSAML\Error; use SimpleSAML\Session; +use Symfony\Component\HttpFoundation\Response; /** * Auth-related utility methods. @@ -49,19 +50,20 @@ public function isAdmin(): bool * This is a helper function for limiting a page to those with administrative access. It will redirect the user to * a login page if the current user doesn't have admin access. * - * @throws \SimpleSAML\Error\Exception If no "admin" authentication source was configured. + * @return \Symfony\Component\HttpFoundation\Response|null * + * @throws \SimpleSAML\Error\Exception If no "admin" authentication source was configured. */ - public function requireAdmin(): void + public function requireAdmin(): ?Response { if ($this->isAdmin()) { - return; + return null; } // not authenticated as admin user, start authentication if (Authentication\Source::getById('admin') !== null) { $as = new Authentication\Simple('admin'); - $as->login(); + return $as->login(); } else { throw new Error\Exception( 'Cannot find "admin" auth source, and admin privileges are required.', diff --git a/src/SimpleSAML/Utils/Config/Metadata.php b/src/SimpleSAML/Utils/Config/Metadata.php index 021ad5b60e..a2dd1c48c7 100644 --- a/src/SimpleSAML/Utils/Config/Metadata.php +++ b/src/SimpleSAML/Utils/Config/Metadata.php @@ -4,9 +4,9 @@ namespace SimpleSAML\Utils\Config; -use SAML2\Constants; use SAML2\XML\md\ContactPerson; use SimpleSAML\Configuration; +use SimpleSAML\SAML2\Constants as C; /** * Class with utilities to fetch different configuration objects from metadata configuration arrays. @@ -248,7 +248,7 @@ public static function parseNameIdPolicy(?array $nameIdPolicy = null): array { if ($nameIdPolicy === null) { // when NameIDPolicy is unset or set to null, default to transient - return ['Format' => Constants::NAMEID_TRANSIENT, 'AllowCreate' => false]; + return ['Format' => C::NAMEID_TRANSIENT, 'AllowCreate' => false]; } if ($nameIdPolicy === []) { @@ -258,10 +258,10 @@ public static function parseNameIdPolicy(?array $nameIdPolicy = null): array // handle configurations specifying an array in the NameIDPolicy config option $nameIdPolicy_cf = Configuration::loadFromArray($nameIdPolicy); - $format = $nameIdPolicy_cf->getOptionalString('Format', Constants::NAMEID_TRANSIENT); + $format = $nameIdPolicy_cf->getOptionalString('Format', C::NAMEID_TRANSIENT); $allowCreate = $nameIdPolicy_cf->getOptionalBoolean('AllowCreate', true); // SAML Version 2.0 Errata 05 lines 252-255 (pg 12) - if ($format === Constants::NAMEID_TRANSIENT) { + if ($format === C::NAMEID_TRANSIENT) { if ($allowCreate) { $allowCreate = false; } diff --git a/src/SimpleSAML/Utils/HTTP.php b/src/SimpleSAML/Utils/HTTP.php index 40626766bd..104d50b41c 100644 --- a/src/SimpleSAML/Utils/HTTP.php +++ b/src/SimpleSAML/Utils/HTTP.php @@ -15,6 +15,8 @@ use SimpleSAML\XMLSecurity\Constants as C; use SimpleSAML\XMLSecurity\Key\SymmetricKey; use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\HttpClient\HttpClientInterface; use function array_merge; @@ -301,12 +303,13 @@ public function isValidURL(string $url): bool * name of the parameter is the array index. The value of the parameter is the value stored in the index. Both * the name and the value will be urlencoded. If the value is NULL, then the parameter will be encoded as just * the name, without a value. + * @return \Symfony\Component\HttpFoundation\Response * * @throws \InvalidArgumentException If $url is not a string or is empty, or $parameters is not an array. * @throws \SimpleSAML\Error\Exception If $url is not a valid HTTP URL. * */ - private function redirect(string $url, array $parameters = []): void + private function redirect(string $url, array $parameters = []): Response { if (empty($url)) { throw new \InvalidArgumentException('Invalid input parameters.'); @@ -324,38 +327,7 @@ private function redirect(string $url, array $parameters = []): void Logger::warning('Redirecting to a URL longer than 2048 bytes.'); } - if (!headers_sent()) { - // set the location header - header('Location: ' . $url, true, 303); - - // disable caching of this response - header('Pragma: no-cache'); - header('Cache-Control: no-cache, no-store, must-revalidate'); - } - - // show a minimal web page with a clickable link to the URL - echo '' . "\n"; - echo '' . "\n"; - echo '' . "\n"; - echo " \n"; - echo ' ' . "\n"; - echo ' ' . "\n"; - echo " Redirect\n"; - echo " \n"; - echo " \n"; - echo "

Redirect

\n"; - echo '

You were redirected to: '; - echo htmlspecialchars($url) . "\n"; - echo ' ' . "\n"; - echo "

\n"; - echo " \n"; - echo ''; - - // end script execution - if (!defined('SIMPLESAMLPHP_TEST_NOEXIT')) { - exit; - } + return new RedirectResponse($url, 303); } @@ -425,9 +397,7 @@ public function addURLParameters(string $url, array $parameters): string * * @param string|null $retryURL The URL the user should access to retry the operation. Defaults to null. * - * page telling about the missing cookie. * @throws \InvalidArgumentException If $retryURL is neither a string nor null. - * */ public function checkSessionCookie(?string $retryURL = null): void { @@ -437,12 +407,13 @@ public function checkSessionCookie(?string $retryURL = null): void } // we didn't have a session cookie. Redirect to the no-cookie page - $url = Module::getModuleURL('core/error/nocookie'); if ($retryURL !== null) { $url = $this->addURLParameters($url, ['retryURL' => $retryURL]); } - $this->redirectTrustedURL($url); + + $response = $this->redirectTrustedURL($url); + $response->send(); } @@ -994,19 +965,20 @@ public function parseQueryString(string $query_string): array * The function will also generate a simple web page with a clickable link to the target URL. * * @param string $url The URL we should redirect to. This URL may include query parameters. If this URL is a - * relative URL (starting with '/'), then it will be turned into an absolute URL by prefixing it with the absolute - * URL to the root of the website. + * relative URL (starting with '/'), then it will be turned into an absolute URL by prefixing it with the absolute + * URL to the root of the website. * @param string[] $parameters An array with extra query string parameters which should be appended to the URL. The - * name of the parameter is the array index. The value of the parameter is the value stored in the index. Both the - * name and the value will be urlencoded. If the value is NULL, then the parameter will be encoded as just the - * name, without a value. + * name of the parameter is the array index. The value of the parameter is the value stored in the index. Both the + * name and the value will be urlencoded. If the value is NULL, then the parameter will be encoded as just the + * name, without a value. + * @return \Symfony\Component\HttpFoundation\Response * * @throws \InvalidArgumentException If $url is not a string or $parameters is not an array. */ - public function redirectTrustedURL(string $url, array $parameters = []): void + public function redirectTrustedURL(string $url, array $parameters = []): Response { $url = $this->normalizeURL($url); - $this->redirect($url, $parameters); + return $this->redirect($url, $parameters); } @@ -1019,19 +991,20 @@ public function redirectTrustedURL(string $url, array $parameters = []): void * to it. If the site is not trusted, an exception will be thrown. * * @param string $url The URL we should redirect to. This URL may include query parameters. If this URL is a - * relative URL (starting with '/'), then it will be turned into an absolute URL by prefixing it with the absolute - * URL to the root of the website. + * relative URL (starting with '/'), then it will be turned into an absolute URL by prefixing it with the absolute + * URL to the root of the website. * @param string[] $parameters An array with extra query string parameters which should be appended to the URL. The - * name of the parameter is the array index. The value of the parameter is the value stored in the index. Both the - * name and the value will be urlencoded. If the value is NULL, then the parameter will be encoded as just the - * name, without a value. + * name of the parameter is the array index. The value of the parameter is the value stored in the index. Both the + * name and the value will be urlencoded. If the value is NULL, then the parameter will be encoded as just the + * name, without a value. + * @return \Symfony\Component\HttpFoundation\Response * * @throws \InvalidArgumentException If $url is not a string or $parameters is not an array. */ - public function redirectUntrustedURL(string $url, array $parameters = []): void + public function redirectUntrustedURL(string $url, array $parameters = []): Response { $url = $this->checkURLAllowed($url); - $this->redirect($url, $parameters); + return $this->redirect($url, $parameters); } @@ -1230,11 +1203,12 @@ public function isSecureCookieAllowed(): bool * * @param string $destination The destination URL. * @param array $data An associative array with the data to be posted to $destination. + * @return \Symfony\Component\HttpFoundation\Response * * @throws \InvalidArgumentException If $destination is not a string or $data is not an array. * @throws \SimpleSAML\Error\Exception If $destination is not a valid HTTP URL. */ - public function submitPOSTData(string $destination, array $data): void + public function submitPOSTData(string $destination, array $data): Response { if (!$this->isValidURL($destination)) { throw new Error\Exception('Invalid destination URL: ' . $destination); @@ -1245,8 +1219,7 @@ public function submitPOSTData(string $destination, array $data): void if ($allowed && preg_match("#^http:#", $destination) && $this->isHTTPS()) { // we need to post the data to HTTP - $this->redirect($this->getSecurePOSTRedirectURL($destination, $data)); - return; + return $this->redirect($this->getSecurePOSTRedirectURL($destination, $data)); } $p = new Template($config, 'post.twig'); @@ -1260,9 +1233,6 @@ public function submitPOSTData(string $destination, array $data): void } $p->data['slow_post_delay_ms'] = $delay; - $p->send(); - if (!defined('SIMPLESAMLPHP_TEST_NOEXIT')) { - exit(0); - } + return $p; } } diff --git a/src/SimpleSAML/Utils/XML.php b/src/SimpleSAML/Utils/XML.php index 3646cc462f..90c2ff8132 100644 --- a/src/SimpleSAML/Utils/XML.php +++ b/src/SimpleSAML/Utils/XML.php @@ -122,7 +122,7 @@ public function debugSAMLMessage(string|DOMElement $message, string $type): void Logger::debug('Encrypted message:'); break; default: - Assert::true(false); + throw new Exception(sprintf('Unknown message type; %s', $type)); } $str = $this->formatXMLString($message); diff --git a/src/SimpleSAML/XHTML/IdPDisco.php b/src/SimpleSAML/XHTML/IdPDisco.php index 7e82f007ae..fe769d925f 100644 --- a/src/SimpleSAML/XHTML/IdPDisco.php +++ b/src/SimpleSAML/XHTML/IdPDisco.php @@ -10,6 +10,8 @@ use SimpleSAML\Metadata\MetaDataStorageHandler; use SimpleSAML\Session; use SimpleSAML\Utils; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use function array_fill_keys; use function array_intersect; @@ -108,12 +110,14 @@ class IdPDisco * * The constructor does the parsing of the request. If this is an invalid request, it will throw an exception. * + * @param \Symfony\Component\HttpFoundation\Request $request The current request * @param string[] $metadataSets Array with metadata sets we find remote entities in, in prioritized order. * @param string $instance The name of this instance of the discovery service. * * @throws \Exception If the request is invalid. */ public function __construct( + protected Request $request, protected array $metadataSets, protected string $instance, ) { @@ -125,41 +129,46 @@ public function __construct( $this->log('Accessing discovery service.'); // standard discovery service parameters - if (!array_key_exists('entityID', $_GET)) { + if (!$request->query->has('entityID')) { throw new Exception('Missing parameter: entityID'); } else { - $this->spEntityId = $_GET['entityID']; + $this->spEntityId = $request->query->get('entityID'); } - if (!array_key_exists('returnIDParam', $_GET)) { + if (!$request->query->has('returnIDParam')) { $this->returnIdParam = 'entityID'; } else { - $this->returnIdParam = $_GET['returnIDParam']; + $this->returnIdParam = $request->query->get('returnIDParam'); } $this->log('returnIdParam initially set to [' . $this->returnIdParam . ']'); - if (!array_key_exists('return', $_GET)) { + if (!$request->query->has('return')) { throw new Exception('Missing parameter: return'); } else { $httpUtils = new Utils\HTTP(); - $this->returnURL = $httpUtils->checkURLAllowed($_GET['return']); + $this->returnURL = $httpUtils->checkURLAllowed($request->query->get('return')); } $this->isPassive = false; - if (array_key_exists('isPassive', $_GET)) { - if ($_GET['isPassive'] === 'true') { + if ($request->query->get('isPassive')) { + if ($request->query->get('isPassive') === 'true') { $this->isPassive = true; } } $this->log('isPassive initially set to [' . ($this->isPassive ? 'TRUE' : 'FALSE') . ']'); - if (array_key_exists('IdPentityID', $_GET)) { - $this->setIdPentityID = $_GET['IdPentityID']; + if ($request->query->has('IdPentityID')) { + $this->setIdPentityID = $request->query->get('IdPentityID'); } - if (array_key_exists('IDPList', $_REQUEST)) { - $this->scopedIDPList = $_REQUEST['IDPList']; + if ($request->query->get('IDPList')) { + $this->scopedIDPList = $request->query->filter( + 'IDPList', + [], + \FILTER_DEFAULT, + ['flags' => \FILTER_REQUIRE_ARRAY], + ); } } @@ -191,8 +200,8 @@ protected function log(string $message): void protected function getCookie(string $name): ?string { $prefixedName = 'idpdisco_' . $this->instance . '_' . $name; - if (array_key_exists($prefixedName, $_COOKIE)) { - return $_COOKIE[$prefixedName]; + if ($this->request->cookies->has($prefixedName)) { + return $this->request->cookies->get($prefixedName); } else { return null; } @@ -278,8 +287,8 @@ protected function getSelectedIdP(): ?string } // user has clicked on a link, or selected the IdP from a drop-down list - if (array_key_exists('idpentityid', $_GET)) { - return $this->validateIdP($_GET['idpentityid']); + if ($this->request->query->has('idpentityid')) { + return $this->validateIdP($this->request->query->get('idpentityid')); } /* Search for the IdP selection from the form used by the links view. This form uses a name which equals @@ -288,7 +297,7 @@ protected function getSelectedIdP(): ?string * Unfortunately, php replaces periods in the name with underscores, and there is no reliable way to get them * back. Therefore we do some quick and dirty parsing of the query string. */ - $qstr = $_SERVER['QUERY_STRING']; + $qstr = $this->request->server->get('QUERY_STRING'); $matches = []; if (preg_match('/(?:^|&)idp_([^=]+)=/', $qstr, $matches)) { return $this->validateIdP(urldecode($matches[1])); @@ -344,7 +353,11 @@ protected function getPreviousIdP(): ?string protected function getFromCIDRhint(): ?string { foreach ($this->metadataSets as $metadataSet) { - $idp = $this->metadata->getPreferredEntityIdFromCIDRhint($metadataSet, $_SERVER['REMOTE_ADDR']); + $idp = $this->metadata->getPreferredEntityIdFromCIDRhint( + $metadataSet, + $this->request->server->get('REMOTE_ADDR'), + ); + if (!empty($idp)) { return $idp; } @@ -405,7 +418,7 @@ protected function saveIdP(): bool return false; } - if (array_key_exists('remember', $_GET)) { + if ($this->request->request->has('remember')) { return true; } @@ -504,16 +517,19 @@ protected function filterList(array $list): array /** * Check if an IdP is set or if the request is passive, and redirect accordingly. + * + * @preturn \Symfony\Component\HttpFoundation\Response|null */ - protected function start(): void + protected function start(): ?Response { $httpUtils = new Utils\HTTP(); $idp = $this->getTargetIdP(); + if ($idp !== null) { $extDiscoveryStorage = $this->config->getOptionalString('idpdisco.extDiscoveryStorage', null); if ($extDiscoveryStorage !== null) { $this->log('Choice made [' . $idp . '] (Forwarding to external discovery storage)'); - $httpUtils->redirectTrustedURL($extDiscoveryStorage, [ + return $httpUtils->redirectTrustedURL($extDiscoveryStorage, [ 'entityID' => $this->spEntityId, 'IdPentityID' => $idp, 'returnIDParam' => $this->returnIdParam, @@ -525,14 +541,16 @@ protected function start(): void 'Choice made [' . $idp . '] (Redirecting the user back. returnIDParam=' . $this->returnIdParam . ')', ); - $httpUtils->redirectTrustedURL($this->returnURL, [$this->returnIdParam => $idp]); + return $httpUtils->redirectTrustedURL($this->returnURL, [$this->returnIdParam => $idp]); } } if ($this->isPassive) { $this->log('Choice not made. (Redirecting the user back without answer)'); - $httpUtils->redirectTrustedURL($this->returnURL); + return $httpUtils->redirectTrustedURL($this->returnURL); } + + return null; } @@ -540,10 +558,15 @@ protected function start(): void * Handles a request to this discovery service. * * The IdP disco parameters should be set before calling this function. + * + * @return \Symfony\Component\HttpFoundation\Response */ - public function handleRequest(): void + public function handleRequest(): Response { - $this->start(); + $response = $this->start(); + if ($response !== null) { + return $response; + } // no choice made. Show discovery service page $idpList = $this->getIdPList(); @@ -564,7 +587,8 @@ public function handleRequest(): void $selectedIdP, $this->returnIdParam, )); - $httpUtils->redirectTrustedURL( + + return $httpUtils->redirectTrustedURL( $this->returnURL, [$this->returnIdParam => $selectedIdP], ); @@ -618,6 +642,7 @@ function (array $idpentry1, array $idpentry2) { $t->data['urlpattern'] = $httpUtils->getSelfURLNoQuery(); $t->data['rememberenabled'] = $this->config->getOptionalBoolean('idpdisco.enableremember', false); $t->data['rememberchecked'] = $this->config->getOptionalBoolean('idpdisco.rememberchecked', false); - $t->send(); + + return $t; } } From 64c9a5d848d6adcd6dccfcb39cace90c05f06f55 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 16 Jun 2026 07:37:59 +0200 Subject: [PATCH 2/8] Address comments --- .../src/Auth/Process/WarnShortSSOInterval.php | 2 + modules/core/src/Controller/Logout.php | 2 +- src/SimpleSAML/XHTML/Template.php | 2 + tests/Utils/TestAuthSource.php | 7 ++- .../admin/src/Controller/ConfigTest.php | 4 +- .../admin/src/Controller/FederationTest.php | 18 +++++- .../modules/admin/src/Controller/TestTest.php | 27 +++----- .../core/src/Auth/Process/TargetedIDTest.php | 4 +- .../RequestedAuthnContextSelectorTest.php | 61 ++++++------------- .../src/Auth/Source/SourceIPSelectorTest.php | 17 ++++-- .../core/src/Auth/UserPassBaseTest.php | 23 ++++--- .../core/src/Controller/ErrorReportTest.php | 11 ++-- .../modules/core/src/Controller/LoginTest.php | 7 ++- .../core/src/Controller/LogoutTest.php | 10 +-- .../modules/cron/src/Controller/CronTest.php | 39 ++++++++---- .../src/Controller/DiscoControllerTest.php | 20 +++--- .../saml/src/Controller/MetadataTest.php | 3 +- .../src/Controller/ServiceProviderTest.php | 3 +- 18 files changed, 138 insertions(+), 122 deletions(-) diff --git a/modules/core/src/Auth/Process/WarnShortSSOInterval.php b/modules/core/src/Auth/Process/WarnShortSSOInterval.php index 47f713959d..3701865adc 100644 --- a/modules/core/src/Auth/Process/WarnShortSSOInterval.php +++ b/modules/core/src/Auth/Process/WarnShortSSOInterval.php @@ -51,6 +51,8 @@ public function process(array &$state): void // Save state and redirect $id = Auth\State::saveState($state, 'core:short_sso_interval'); $url = Module::getModuleURL('core/short_sso_interval'); + + $httpUtils = new Utils\HTTP(); $response = $httpUtils->redirectTrustedURL($url, ['StateId' => $id]); $response->send(); } diff --git a/modules/core/src/Controller/Logout.php b/modules/core/src/Controller/Logout.php index baa6e6056f..1261be2f4f 100644 --- a/modules/core/src/Controller/Logout.php +++ b/modules/core/src/Controller/Logout.php @@ -82,7 +82,7 @@ public function setAuthState(Auth\State $authState): void public function logout(Request $request, string $as): Response { $auth = new Auth\Simple($as); - return $auth->login($this->getReturnPath($request)); + return $auth->logout($this->getReturnPath($request)); } diff --git a/src/SimpleSAML/XHTML/Template.php b/src/SimpleSAML/XHTML/Template.php index 9bc6039a17..ce1aa143a9 100644 --- a/src/SimpleSAML/XHTML/Template.php +++ b/src/SimpleSAML/XHTML/Template.php @@ -12,6 +12,7 @@ use Exception; use InvalidArgumentException; +use SimpleSAML\Assert\Assert; use SimpleSAML\Configuration; use SimpleSAML\Error; use SimpleSAML\Locale\Localization; @@ -692,6 +693,7 @@ public function getEntityPropertyTranslation(string $property, array $data): str foreach ($tryLanguages as $language) { if (isset($data[$property][$language])) { + Assert::string($data[$property][$language]); return $data[$property][$language]; } } diff --git a/tests/Utils/TestAuthSource.php b/tests/Utils/TestAuthSource.php index 7fd0929bcd..2aa56f226d 100644 --- a/tests/Utils/TestAuthSource.php +++ b/tests/Utils/TestAuthSource.php @@ -5,13 +5,18 @@ namespace SimpleSAML\Test\Utils; use SimpleSAML\Auth\Source; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; class TestAuthSource extends Source { /** + * @param \Symfony\Component\HttpFoundation\Request The current request * @param array &$state + * @return \Symfony\Component\HttpFoundation\Response|null */ - public function authenticate(array &$state): void + public function authenticate(Request $request, array &$state): ?Response { + return null; } } diff --git a/tests/modules/admin/src/Controller/ConfigTest.php b/tests/modules/admin/src/Controller/ConfigTest.php index 80053bc606..50b29b47ba 100644 --- a/tests/modules/admin/src/Controller/ConfigTest.php +++ b/tests/modules/admin/src/Controller/ConfigTest.php @@ -12,6 +12,7 @@ use SimpleSAML\Session; use SimpleSAML\Utils; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * Set of tests for the controllers in the "admin" module. @@ -61,9 +62,10 @@ public function getVersion(): string ); $this->authUtils = new class () extends Utils\Auth { - public function requireAdmin(): void + public function requireAdmin(): ?Response { // stub + return null; } }; diff --git a/tests/modules/admin/src/Controller/FederationTest.php b/tests/modules/admin/src/Controller/FederationTest.php index 163e19dc85..a3f479d358 100644 --- a/tests/modules/admin/src/Controller/FederationTest.php +++ b/tests/modules/admin/src/Controller/FederationTest.php @@ -14,6 +14,7 @@ use SimpleSAML\Utils; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * Set of tests for the controllers in the "admin" module. @@ -73,9 +74,10 @@ protected function setUp(): void ); $this->authUtils = new class () extends Utils\Auth { - public function requireAdmin(): void + public function requireAdmin(): ?Response { // stub + return null; } }; @@ -143,9 +145,10 @@ public function __construct() } - public function authenticate(array &$state): void + public function authenticate(Request $request, array &$state): ?Response { // stub + return null; } @@ -204,6 +207,7 @@ public function testMetadataConverterFileUpload(): void $c->setAuthUtils($this->authUtils); $response = $c->metadataConverter($request); + $this->assertInstanceOf(Response::class, $response); $this->assertTrue($response->isSuccessful()); $this->assertNull($response->data['error']); } @@ -223,6 +227,7 @@ public function testMetadataConverterData(): void $c->setAuthUtils($this->authUtils); $response = $c->metadataConverter($request); + $this->assertInstanceOf(Response::class, $response); $this->assertTrue($response->isSuccessful()); $this->assertNull($response->data['error']); } @@ -242,6 +247,7 @@ public function testMetadataConverterSkipsExpires(): void $c->setAuthUtils($this->authUtils); $response = $c->metadataConverter($request); + $this->assertInstanceOf(Response::class, $response); $this->assertTrue($response->isSuccessful()); $this->assertStringNotContainsString("'expire' =>", $response->data['output']['saml20-idp-remote']); } @@ -262,6 +268,7 @@ public function testMetadataConverterInvalidMetadataShowsError(): void $response = $c->metadataConverter($request); + $this->assertInstanceOf(Response::class, $response); $this->assertTrue($response->isSuccessful()); $this->assertNotNull($response->data['error']); } @@ -282,6 +289,7 @@ public function testMetadataConverterEmptyInput(): void $response = $c->metadataConverter($request); + $this->assertInstanceOf(Response::class, $response); $this->assertTrue($response->isSuccessful()); $this->assertEquals([], $response->data['output']); $this->assertEquals('', $response->data['xmldata']); @@ -310,9 +318,10 @@ public function __construct() } - public function authenticate(array &$state): void + public function authenticate(Request $request, array &$state): ?Response { // stub + return null; } @@ -336,6 +345,7 @@ public static function getById(string $authId, ?string $type = null): ?Auth\Sour $response = $c->downloadCert($request); + $this->assertInstanceOf(Response::class, $response); $this->assertTrue($response->isSuccessful()); $this->assertNotNull($response->headers->get('Content-Disposition')); $this->assertEquals('application/x-pem-file', $response->headers->get('Content-Type')); @@ -376,6 +386,7 @@ public function getMetaDataConfig(string $entityId, string $set): Configuration $response = $c->downloadCert($request); + $this->assertInstanceOf(Response::class, $response); $this->assertTrue($response->isSuccessful()); $this->assertNotNull($response->headers->get('Content-Disposition')); $this->assertEquals('application/x-pem-file', $response->headers->get('Content-Type')); @@ -409,6 +420,7 @@ public function getMetaData(?string $entityId, string $set): array $c->setMetaDataStorageHandler($mdh); $response = $c->showRemoteEntity($request); + $this->assertInstanceOf(Response::class, $response); $this->assertTrue($response->isSuccessful()); } diff --git a/tests/modules/admin/src/Controller/TestTest.php b/tests/modules/admin/src/Controller/TestTest.php index 9c153c4ef0..6584531853 100644 --- a/tests/modules/admin/src/Controller/TestTest.php +++ b/tests/modules/admin/src/Controller/TestTest.php @@ -10,12 +10,13 @@ use SimpleSAML\Auth; use SimpleSAML\Configuration; use SimpleSAML\Error; -use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Module\admin\Controller\Test as TestController; use SimpleSAML\Session; use SimpleSAML\Utils; use SimpleSAML\XHTML\Template; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * Set of tests for the controllers in the "admin" module. @@ -51,9 +52,10 @@ protected function setUp(): void ); $this->authUtils = new class () extends Utils\Auth { - public function requireAdmin(): void + public function requireAdmin(): ?Response { // stub + return null; } }; @@ -105,17 +107,11 @@ public function testMainWithAuthSourceAndLogout(): void $c = new TestController($this->config, $this->session); $c->setAuthUtils($this->authUtils); - $c->setAuthSimple(new class ('admin') extends Auth\Simple { - public function logout($params = null): void - { - // stub - } - }); $response = $c->main($request, 'admin'); - $this->assertInstanceOf(RunnableResponse::class, $response); - $this->assertTrue($response->isSuccessful()); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertTrue($response->isRedirection()); } @@ -166,6 +162,7 @@ public static function loadExceptionState(?string $id = null): ?array */ public function testMainWithAuthSourceNotAuthenticated(): void { + $_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REQUEST_URI'] = '/module.php/admin/test'; $request = Request::create( '/test', @@ -180,18 +177,12 @@ public function isAuthenticated(): bool { return false; } - - - public function login(array $params = []): void - { - // stub - } }); $response = $c->main($request, 'admin'); - $this->assertInstanceOf(RunnableResponse::class, $response); - $this->assertTrue($response->isSuccessful()); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertTrue($response->isRedirection()); } diff --git a/tests/modules/core/src/Auth/Process/TargetedIDTest.php b/tests/modules/core/src/Auth/Process/TargetedIDTest.php index 9d895a1255..b24c693af2 100644 --- a/tests/modules/core/src/Auth/Process/TargetedIDTest.php +++ b/tests/modules/core/src/Auth/Process/TargetedIDTest.php @@ -7,10 +7,10 @@ use Exception; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; -use SAML2\Constants; use SAML2\XML\saml\NameID; use SimpleSAML\Configuration; use SimpleSAML\Module\core\Auth\Process\TargetedID; +use SimpleSAML\SAML2\Constants as C; use SimpleSAML\Utils; /** @@ -111,7 +111,7 @@ public function testWithSrcDst(): void public function testNameIdGeneration(): void { $nameid = new NameID(); - $nameid->setFormat(Constants::NAMEID_PERSISTENT); + $nameid->setFormat(C::NAMEID_PERSISTENT); $nameid->setNameQualifier('urn:example:src:id'); $nameid->setSPNameQualifier('joe'); $nameid->setValue('joe'); diff --git a/tests/modules/core/src/Auth/Source/RequestedAuthnContextSelectorTest.php b/tests/modules/core/src/Auth/Source/RequestedAuthnContextSelectorTest.php index 23eeacc86f..2343f2b808 100644 --- a/tests/modules/core/src/Auth/Source/RequestedAuthnContextSelectorTest.php +++ b/tests/modules/core/src/Auth/Source/RequestedAuthnContextSelectorTest.php @@ -7,13 +7,15 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -use SAML2\Exception\Protocol\NoAuthnContextException; use SimpleSAML\Assert\AssertionFailedException; use SimpleSAML\Auth; use SimpleSAML\Configuration; use SimpleSAML\Error\Exception; use SimpleSAML\Module\core\Auth\Source\AbstractSourceSelector; use SimpleSAML\Module\core\Auth\Source\RequestedAuthnContextSelector; +use SimpleSAML\SAML2\Exception\Protocol\NoAuthnContextException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** */ @@ -88,20 +90,10 @@ public function testAuthenticationVariant1(): void $info = ['AuthId' => 'selector']; $config = $this->sourceConfig->getArray('selector'); - $selector = new class ($info, $config) extends RequestedAuthnContextSelector { - /** - * @param \SimpleSAML\Auth\Source $as - * @param array $state - * @return void - */ - public static function doAuthentication(Auth\Source $as, array &$state): void - { - // Dummy - } - }; - + $selector = RequestedAuthnContextSelector($info, $config); $state = ['saml:RequestedAuthnContext' => ['AuthnContextClassRef' => null]]; - $selector->authenticate($state); + $request = Request::createFromGlobals(); + $selector->authenticate($request, $state); $this->assertArrayNotHasKey('saml:AuthnContextClassRef', $state); } @@ -114,20 +106,10 @@ public function testAuthenticationVariant2(): void $info = ['AuthId' => 'selector']; $config = $this->sourceConfig->getArray('selector'); - $selector = new class ($info, $config) extends RequestedAuthnContextSelector { - /** - * @param \SimpleSAML\Auth\Source $as - * @param array $state - * @return void - */ - public static function doAuthentication(Auth\Source $as, array &$state): void - { - // Dummy - } - }; - + $selector = new RequestedAuthnContextSelector($info, $config); $state = ['saml:RequestedAuthnContext' => ['AuthnContextClassRef' => ['urn:x-simplesamlphp:loa1']]]; - $selector->authenticate($state); + $request = Request::createFromGlobals(); + $selector->authenticate($request, $state); $this->assertArrayHasKey('saml:AuthnContextClassRef', $state); $this->assertEquals('urn:x-simplesamlphp:loa1', $state['saml:AuthnContextClassRef']); } @@ -141,25 +123,15 @@ public function testAuthenticationVariant3(): void $info = ['AuthId' => 'selector']; $config = $this->sourceConfig->getArray('selector'); - $selector = new class ($info, $config) extends RequestedAuthnContextSelector { - /** - * @param \SimpleSAML\Auth\Source $as - * @param array $state - * @return void - */ - public static function doAuthentication(Auth\Source $as, array &$state): void - { - // Dummy - } - }; - + $selector = new RequestedAuthnContextSelector($info, $config); $state = [ 'saml:RequestedAuthnContext' => [ 'AuthnContextClassRef' => ['urn:x-simplesamlphp:loa1'], 'Comparison' => 'exact', ], ]; - $selector->authenticate($state); + $request = Request::createFromGlobals(); + $selector->authenticate($request, $state); $this->assertArrayHasKey('saml:AuthnContextClassRef', $state); $this->assertEquals('urn:x-simplesamlphp:loa1', $state['saml:AuthnContextClassRef']); } @@ -198,13 +170,15 @@ public function testArraySyntaxWorks(): void $selector = new class ($info, $config) extends RequestedAuthnContextSelector { /** + * @param \Symfony\Component\HttpFoundation\Request $request The currect request * @param \SimpleSAML\Auth\Source $as * @param array $state - * @return void + * @param \Symfony\Component\HttpFoundation\Response|null */ - public static function doAuthentication(Auth\Source $as, array &$state): void + public static function doAuthentication(Request $request, Auth\Source $as, array $state): ?Response { // Dummy + return null; } }; @@ -215,7 +189,8 @@ public static function doAuthentication(Auth\Source $as, array &$state): void ], ]; - $selector->authenticate($state); + $request = Request::createFromGlobals(); + $selector->authenticate($request, $state); $this->assertArrayHasKey('saml:AuthnContextClassRef', $state); $this->assertEquals('urn:x-simplesamlphp:loa1', $state['saml:AuthnContextClassRef']); } diff --git a/tests/modules/core/src/Auth/Source/SourceIPSelectorTest.php b/tests/modules/core/src/Auth/Source/SourceIPSelectorTest.php index f86c720dad..5fb8cc7a69 100644 --- a/tests/modules/core/src/Auth/Source/SourceIPSelectorTest.php +++ b/tests/modules/core/src/Auth/Source/SourceIPSelectorTest.php @@ -13,6 +13,8 @@ use SimpleSAML\Error\Exception; use SimpleSAML\Module\core\Auth\Source\AbstractSourceSelector; use SimpleSAML\Module\core\Auth\Source\SourceIPSelector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** */ @@ -113,28 +115,33 @@ public function testAuthentication(): void $selector = new class ($info, $config) extends SourceIPSelector { /** + * @param \Symfony\Component\HttpFoundation\Request $request * @param \SimpleSAML\Auth\Source $as * @param array $state - * @return void + * @return \Symfony\Component\HttpFoundation\Response|null */ - public static function doAuthentication(Auth\Source $as, array &$state): void + public static function doAuthentication(Auth\Source $as, array &$state): ?Response { // Dummy + return null; } /** + * @param \Symfony\Component\HttpFoundation\Request $request * @param array &$state - * @return void + * @return \Symfony\Component\HttpFoundation\Response|null */ - public function authenticate(array &$state): void + public function authenticate(Request $request, array &$state): ?Response { $state['finished'] = true; + return null; } }; $state = []; - $selector->authenticate($state); + $request = Request::createFromGlobals(); + $selector->authenticate($request, $state); $this->assertTrue($state['finished']); } diff --git a/tests/modules/core/src/Auth/UserPassBaseTest.php b/tests/modules/core/src/Auth/UserPassBaseTest.php index 2ab4b6197d..ffa0df1709 100644 --- a/tests/modules/core/src/Auth/UserPassBaseTest.php +++ b/tests/modules/core/src/Auth/UserPassBaseTest.php @@ -6,10 +6,11 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; -use SAML2\Constants; use SimpleSAML\Error\Error as SspError; use SimpleSAML\Error\ErrorCodes; use SimpleSAML\Module\core\Auth\UserPassBase; +use SimpleSAML\SAML2\Constants as C; +use Symfony\Component\HttpFoundation\Request; /** */ @@ -21,7 +22,7 @@ class UserPassBaseTest extends TestCase public function testAuthenticateECPCallsLoginAndSetsAttributes(): void { $state = [ - 'saml:Binding' => Constants::BINDING_PAOS, + 'saml:Binding' => C::BINDING_PAOS, ]; $attributes = ['attrib' => 'val']; @@ -38,8 +39,9 @@ public function testAuthenticateECPCallsLoginAndSetsAttributes(): void ->with($username, $password) ->willReturn($attributes); + $request = Request::createFromGlobals(); /** @var \SimpleSAML\Module\core\Auth\UserPassBase&\PHPUnit\Framework\MockObject\MockObject $stub */ - $stub->authenticate($state); + $stub->authenticate($request, $state); $this->assertSame($attributes, $state['Attributes']); } @@ -53,7 +55,7 @@ public function testAuthenticateECPMissingUsername(): void $this->expectExceptionMessage(ErrorCodes::WRONGUSERPASS); $state = [ - 'saml:Binding' => Constants::BINDING_PAOS, + 'saml:Binding' => C::BINDING_PAOS, ]; unset($_SERVER['PHP_AUTH_USER']); @@ -64,8 +66,9 @@ public function testAuthenticateECPMissingUsername(): void ->onlyMethods(['login']) ->getMock(); + $request = Request::createFromGlobals(); /** @var \SimpleSAML\Module\core\Auth\UserPassBase&\PHPUnit\Framework\MockObject\MockObject $stub */ - $stub->authenticate($state); + $stub->authenticate($request, $state); } @@ -77,7 +80,7 @@ public function testAuthenticateECPMissingPassword(): void $this->expectExceptionMessage(ErrorCodes::WRONGUSERPASS); $state = [ - 'saml:Binding' => Constants::BINDING_PAOS, + 'saml:Binding' => C::BINDING_PAOS, ]; $_SERVER['PHP_AUTH_USER'] = 'username'; @@ -88,8 +91,9 @@ public function testAuthenticateECPMissingPassword(): void ->onlyMethods(['login']) ->getMock(); + $request = Request::createFromGlobals(); /** @var \SimpleSAML\Module\core\Auth\UserPassBase&\PHPUnit\Framework\MockObject\MockObject $stub */ - $stub->authenticate($state); + $stub->authenticate($request, $state); } @@ -98,7 +102,7 @@ public function testAuthenticateECPMissingPassword(): void public function testAuthenticateECPCallsLoginWithForcedUsername(): void { $state = [ - 'saml:Binding' => Constants::BINDING_PAOS, + 'saml:Binding' => C::BINDING_PAOS, ]; $attributes = []; @@ -117,8 +121,9 @@ public function testAuthenticateECPCallsLoginWithForcedUsername(): void ->with($forcedUsername, $password) ->willReturn($attributes); + $request = Request::createFromGlobals(); /** @var \SimpleSAML\Module\core\Auth\UserPassBase&\PHPUnit\Framework\MockObject\MockObject $stub */ $stub->setForcedUsername($forcedUsername); - $stub->authenticate($state); + $stub->authenticate($request, $state); } } diff --git a/tests/modules/core/src/Controller/ErrorReportTest.php b/tests/modules/core/src/Controller/ErrorReportTest.php index b461036bf7..911b8566f5 100644 --- a/tests/modules/core/src/Controller/ErrorReportTest.php +++ b/tests/modules/core/src/Controller/ErrorReportTest.php @@ -8,10 +8,8 @@ use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; use SimpleSAML\Error; -use SimpleSAML\HTTP\RunnableResponse; -use SimpleSAML\Module\core\Controller; use SimpleSAML\Session; -use SimpleSAML\XHTML\Template; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; /** @@ -65,8 +63,7 @@ public function testErrorReportSent(): void $response = $c->main($request); - $this->assertInstanceOf(Template::class, $response); - $this->assertTrue($response->isSuccessful()); + $this->assertTrue($response->isSuccestul()); $this->assertEquals('core:errorreport.twig', $response->getTemplateName()); } @@ -110,7 +107,7 @@ public function testErrorReport(): void $response = $c->main($request); - $this->assertInstanceOf(RunnableResponse::class, $response); - $this->assertTrue($response->isSuccessful()); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertTrue($response->isRedirection()); } } diff --git a/tests/modules/core/src/Controller/LoginTest.php b/tests/modules/core/src/Controller/LoginTest.php index a2676770bb..3dcff3b2e2 100644 --- a/tests/modules/core/src/Controller/LoginTest.php +++ b/tests/modules/core/src/Controller/LoginTest.php @@ -13,6 +13,7 @@ use SimpleSAML\TestUtils\ClearStateTestCase; use SimpleSAML\XHTML\Template; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * Set of tests for the controllers in the "core" module. @@ -114,9 +115,10 @@ public function __construct() } - public function authenticate(array &$state): void + public function authenticate(Request $request, array &$state): ?Response { // stub + return null; } @@ -182,9 +184,10 @@ public function __construct() // stub } - public function authenticate(array &$state): void + public function authenticate(Request $request, array &$state): ?Response { // stub + return null; } public static function getById(string $authId, ?string $type = null): ?UserPassOrgBase diff --git a/tests/modules/core/src/Controller/LogoutTest.php b/tests/modules/core/src/Controller/LogoutTest.php index 0b045a1002..729dfe29e1 100644 --- a/tests/modules/core/src/Controller/LogoutTest.php +++ b/tests/modules/core/src/Controller/LogoutTest.php @@ -8,9 +8,9 @@ use SimpleSAML\Auth; use SimpleSAML\Configuration; use SimpleSAML\Error; -use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Module\core\Controller; use SimpleSAML\TestUtils\ClearStateTestCase; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; /** @@ -66,12 +66,8 @@ public function testLogout(): void $response = $c->logout($request, 'example-authsource'); - $this->assertInstanceOf(RunnableResponse::class, $response); - $this->assertTrue($response->isSuccessful()); - /** @var callable $callable */ - $callable = $response->getCallable(); - $this->assertInstanceOf(Auth\Simple::class, $callable[0]); - $this->assertEquals('logout', $callable[1]); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertTrue($response->isRedirection()); } diff --git a/tests/modules/cron/src/Controller/CronTest.php b/tests/modules/cron/src/Controller/CronTest.php index ef7b685ee3..e0453b54bb 100644 --- a/tests/modules/cron/src/Controller/CronTest.php +++ b/tests/modules/cron/src/Controller/CronTest.php @@ -53,9 +53,10 @@ protected function setUp(): void $this->session = Session::getSessionFromRequest(); $this->authUtils = new class () extends Utils\Auth { - public function requireAdmin(): void + public function requireAdmin(): ?Response { // stub + return null; } }; @@ -84,12 +85,16 @@ public function requireAdmin(): void */ public function testInfo(): void { - $_SERVER['REQUEST_URI'] = '/module.php/cron/info'; + $request = Request::create( + '/info', + 'GET', + ); $c = new Controller\Cron($this->config, $this->session); $c->setAuthUtils($this->authUtils); - $response = $c->info(); + $response = $c->info($request); + $this->assertInstanceOf(Template::class, $response); $this->assertTrue($response->isSuccessful()); $expect = [ 'exec_href' => 'http://localhost/simplesaml/module.php/cron/run/daily/verysecret', @@ -106,10 +111,13 @@ public function testInfo(): void */ public function testRunCorrectKey(): void { - $_SERVER['REQUEST_URI'] = '/module.php/cron/run/daily/verysecret'; + $request = Request::create( + '/run/daily/verysecret', + 'GET', + ); $c = new Controller\Cron($this->config, $this->session); - $response = $c->run('daily', 'verysecret'); + $response = $c->run($request, 'daily', 'verysecret'); $this->assertInstanceOf(Template::class, $response); $this->assertTrue($response->isSuccessful()); @@ -128,14 +136,17 @@ public function testRunCorrectKey(): void */ public function testRunWrongKey(): void { - $_SERVER['REQUEST_URI'] = '/module.php/cron/run/daily/nodice'; + $request = Request::create( + '/run/daily/verysecret', + 'GET', + ); $c = new Controller\Cron($this->config, $this->session); $this->expectException(Error\Exception::class); $this->expectExceptionMessage('Cron: Wrong key "nodice" provided. Cron will not run.'); - $c->run('daily', 'nodice'); + $c->run($request, 'daily', 'nodice'); } @@ -144,14 +155,17 @@ public function testRunWrongKey(): void #[DataProvider('provideDefaultSecret')] public function testRunDefaultSecret(string $secret): void { - $_SERVER['REQUEST_URI'] = '/module.php/cron/run/daily/' . $secret; + $request = Request::create( + '/run/daily/' . $secret, + 'GET', + ); $c = new Controller\Cron($this->config, $this->session); $this->expectException(Error\ConfigurationError::class); $this->expectExceptionMessage('Cron: Possible malicious attempt to run cron tasks with default secret'); - $c->run('daily', $secret); + $c->run($request, 'daily', $secret); } @@ -174,14 +188,17 @@ public function testRunDefaultConfigSecret(string $configKey): void 'simplesaml', ); - $_SERVER['REQUEST_URI'] = '/module.php/cron/run/daily/verysecret'; + $request = Request::create( + '/run/daily/verysecret', + 'GET', + ); $c = new Controller\Cron($this->config, $this->session); $this->expectException(Error\ConfigurationError::class); $this->expectExceptionMessage('Cron: no proper key has been configured.'); - $c->run('daily', 'verysecret'); + $c->run($request, 'daily', 'verysecret'); } diff --git a/tests/modules/multiauth/src/Controller/DiscoControllerTest.php b/tests/modules/multiauth/src/Controller/DiscoControllerTest.php index 955f1a96a8..d049d843a2 100644 --- a/tests/modules/multiauth/src/Controller/DiscoControllerTest.php +++ b/tests/modules/multiauth/src/Controller/DiscoControllerTest.php @@ -129,10 +129,10 @@ public function __construct() } - public function authenticate(array &$state): never + public function authenticate(array &$state): ?Response { // stub - exit(); + return null; } @@ -186,10 +186,10 @@ public function __construct() } - public function authenticate(array &$state): never + public function authenticate(array &$state): ?Response { // stub - exit(); + return null; } @@ -245,10 +245,10 @@ public function __construct() } - public function authenticate(array &$state): never + public function authenticate(array &$state): ?Response { // stub - exit(); + return null; } @@ -304,10 +304,10 @@ public function __construct() } - public function authenticate(array &$state): never + public function authenticate(array &$state): ?Response { // stub - exit(); + return null; } @@ -361,10 +361,10 @@ public function __construct() } - public function authenticate(array &$state): never + public function authenticate(array &$state): ?Response { // stub - exit(); + return null; } diff --git a/tests/modules/saml/src/Controller/MetadataTest.php b/tests/modules/saml/src/Controller/MetadataTest.php index 73c6cfdc6c..a9be21e58d 100644 --- a/tests/modules/saml/src/Controller/MetadataTest.php +++ b/tests/modules/saml/src/Controller/MetadataTest.php @@ -123,9 +123,10 @@ public function getMetaDataCurrentEntityID(string $set, string $type = 'entityid ); $this->authUtils = new class () extends Utils\Auth { - public function requireAdmin(): void + public function requireAdmin(): ?Response { // stub + return null; } }; diff --git a/tests/modules/saml/src/Controller/ServiceProviderTest.php b/tests/modules/saml/src/Controller/ServiceProviderTest.php index 122150b004..a9a826a956 100644 --- a/tests/modules/saml/src/Controller/ServiceProviderTest.php +++ b/tests/modules/saml/src/Controller/ServiceProviderTest.php @@ -87,9 +87,10 @@ protected function setUp(): void Configuration::setPreLoadedConfig($this->authsources, 'authsources.php'); $this->authUtils = new class () extends Utils\Auth { - public function requireAdmin(): void + public function requireAdmin(): ?Response { // stub + return null; } }; From c8d7188a9b04071f6b3d697dfe3bce96ccc507e9 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 17 Jun 2026 18:52:37 +0200 Subject: [PATCH 3/8] Address comments --- modules/admin/src/Controller/Config.php | 2 +- modules/core/src/Auth/Process/Cardinality.php | 2 +- modules/core/src/Auth/Process/CardinalitySingle.php | 2 +- modules/core/src/Auth/Source/AbstractSourceSelector.php | 2 +- modules/core/src/Controller/Login.php | 2 +- modules/exampleauth/src/Auth/Source/External.php | 4 ++-- modules/multiauth/src/Auth/Source/MultiAuth.php | 4 ++-- modules/saml/src/Auth/Source/SP.php | 3 ++- modules/saml/src/IdP/SAML2.php | 2 +- src/SimpleSAML/XHTML/IdPDisco.php | 4 ++-- 10 files changed, 14 insertions(+), 13 deletions(-) diff --git a/modules/admin/src/Controller/Config.php b/modules/admin/src/Controller/Config.php index 1c3d66cda9..8f1006380b 100644 --- a/modules/admin/src/Controller/Config.php +++ b/modules/admin/src/Controller/Config.php @@ -116,7 +116,7 @@ public function diagnostics(Request $request): Response * Display the main admin page. * * @param \Symfony\Component\HttpFoundation\Request $request The current request. - * @return \Symfony\Components\HttpFoundation\Response + * @return \Symfony\Component\HttpFoundation\Response */ public function main(/** @scrutinizer ignore-unused */ Request $request): Response { diff --git a/modules/core/src/Auth/Process/Cardinality.php b/modules/core/src/Auth/Process/Cardinality.php index 6678c694e5..ec53eb5db2 100644 --- a/modules/core/src/Auth/Process/Cardinality.php +++ b/modules/core/src/Auth/Process/Cardinality.php @@ -195,7 +195,7 @@ public function process(array &$state): void if (array_key_exists('core:cardinality:errorAttributes', $state)) { $id = Auth\State::saveState($state, 'core:cardinality'); $url = Module::getModuleURL('core/error/cardinality'); - $response = $httpUtils->redirectTrustedURL($url, ['StateId' => $id]); + $response = $this->httpUtils->redirectTrustedURL($url, ['StateId' => $id]); $response->send(); } } diff --git a/modules/core/src/Auth/Process/CardinalitySingle.php b/modules/core/src/Auth/Process/CardinalitySingle.php index c0a206f2f3..b02ddfeca5 100644 --- a/modules/core/src/Auth/Process/CardinalitySingle.php +++ b/modules/core/src/Auth/Process/CardinalitySingle.php @@ -122,7 +122,7 @@ public function process(array &$state): void if (array_key_exists('core:cardinality:errorAttributes', $state)) { $id = Auth\State::saveState($state, 'core:cardinality'); $url = Module::getModuleURL('core/error/cardinality'); - $response = $httpUtils->redirectTrustedURL($url, ['StateId' => $id]); + $response = $this->httpUtils->redirectTrustedURL($url, ['StateId' => $id]); $response->send(); } } diff --git a/modules/core/src/Auth/Source/AbstractSourceSelector.php b/modules/core/src/Auth/Source/AbstractSourceSelector.php index fdde74ae05..91b436bff0 100644 --- a/modules/core/src/Auth/Source/AbstractSourceSelector.php +++ b/modules/core/src/Auth/Source/AbstractSourceSelector.php @@ -65,7 +65,7 @@ public function authenticate(Request $request, array &$state): ?Response } $state['sourceSelector:selected'] = $source; - return parent::doAuthentication($request, $as, $state); + return static::doAuthentication($request, $as, $state); } diff --git a/modules/core/src/Controller/Login.php b/modules/core/src/Controller/Login.php index 1fb97ec41e..c01ca7ffe0 100644 --- a/modules/core/src/Controller/Login.php +++ b/modules/core/src/Controller/Login.php @@ -101,7 +101,7 @@ public function welcome(): Template * username/password authentication. * * @param \Symfony\Component\HttpFoundation\Request $request - * @param \Symfony\Component\HttpFoudnation\Response + * @return \Symfony\Component\HttpFoundation\Response */ public function loginuserpass(Request $request): Response { diff --git a/modules/exampleauth/src/Auth/Source/External.php b/modules/exampleauth/src/Auth/Source/External.php index 71c63b141d..9ce559433a 100644 --- a/modules/exampleauth/src/Auth/Source/External.php +++ b/modules/exampleauth/src/Auth/Source/External.php @@ -191,10 +191,10 @@ public static function resume(Request $request, Auth\State $authState): Response { /* * First we need to restore the $state-array. We should have the identifier for - * it in the 'State' request parameter. + * it in the 'AuthState' request parameter. */ if (!$request->query->has('AuthState')) { - throw new Error\BadRequest('Missing "State" parameter.'); + throw new Error\BadRequest('Missing "AuthState" parameter.'); } /* diff --git a/modules/multiauth/src/Auth/Source/MultiAuth.php b/modules/multiauth/src/Auth/Source/MultiAuth.php index 1b6ef38c88..3ec39b9af5 100644 --- a/modules/multiauth/src/Auth/Source/MultiAuth.php +++ b/modules/multiauth/src/Auth/Source/MultiAuth.php @@ -92,7 +92,7 @@ public function __construct(array $info, array $config) * * The authentication process is finished in the delegateAuthentication method. * - * @oaram \Symfony\Component\HttpFoundation\Request $request + * @param \Symfony\Component\HttpFoundation\Request $request * @param array &$state Information about the current authentication. * @return \Symfony\Component\HttpFoundation\Response */ @@ -196,7 +196,7 @@ public static function doAuthentication(Request $request, Auth\Source $as, array try { $response = $as->authenticate($request, $state); if ($response instanceof Response) { - return $reponse; + return $response; } } catch (Error\Exception $e) { Auth\State::throwException($state, $e); diff --git a/modules/saml/src/Auth/Source/SP.php b/modules/saml/src/Auth/Source/SP.php index 63f8e00219..e7768aa4ab 100644 --- a/modules/saml/src/Auth/Source/SP.php +++ b/modules/saml/src/Auth/Source/SP.php @@ -12,6 +12,7 @@ use SimpleSAML\Auth; use SimpleSAML\Configuration; use SimpleSAML\Error; +use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\IdP; use SimpleSAML\Logger; use SimpleSAML\Metadata\MetaDataStorageHandler; @@ -1074,7 +1075,7 @@ public static function reauthPostLogout(IdP $idp, array $state): Response } /** @var \SimpleSAML\Module\saml\Auth\Source\SP $sp */ - $sp = Auth\Source::getById($state['saml:sp:AuthId'], selfSP::class); + $sp = Auth\Source::getById($state['saml:sp:AuthId'], self::class); Logger::debug('Proxy: logging in again.'); $request = Request::createFromGlobals(); diff --git a/modules/saml/src/IdP/SAML2.php b/modules/saml/src/IdP/SAML2.php index e198d8b041..26f7f45459 100644 --- a/modules/saml/src/IdP/SAML2.php +++ b/modules/saml/src/IdP/SAML2.php @@ -327,7 +327,7 @@ public static function receiveAuthnRequest(Request $request, IdP $idp): void /* IdP initiated authentication. */ if ($request->query->has('cookieTime')) { - $cookieTime = intval($requqest->query->get('cookieTime')); + $cookieTime = intval($request->query->get('cookieTime')); if ($cookieTime + 5 > time()) { /* * Less than five seconds has passed since we were diff --git a/src/SimpleSAML/XHTML/IdPDisco.php b/src/SimpleSAML/XHTML/IdPDisco.php index fe769d925f..87aee49dd1 100644 --- a/src/SimpleSAML/XHTML/IdPDisco.php +++ b/src/SimpleSAML/XHTML/IdPDisco.php @@ -418,7 +418,7 @@ protected function saveIdP(): bool return false; } - if ($this->request->request->has('remember')) { + if ($this->request->query->has('remember')) { return true; } @@ -518,7 +518,7 @@ protected function filterList(array $list): array /** * Check if an IdP is set or if the request is passive, and redirect accordingly. * - * @preturn \Symfony\Component\HttpFoundation\Response|null + * @return \Symfony\Component\HttpFoundation\Response|null */ protected function start(): ?Response { From fc3c01849ea30ada86c00b416ec089932eff6e1f Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 17 Jun 2026 23:12:30 +0200 Subject: [PATCH 4/8] Fix tests (run 1/2) --- composer.lock | 950 ++++++++++-------- .../RequestedAuthnContextSelectorTest.php | 4 +- .../src/Controller/ExampleAuthTest.php | 72 +- .../src/Auth/Source/MultiAuthTest.php | 16 +- .../src/Controller/DiscoControllerTest.php | 143 +-- .../src/Auth/Process/NameIDAttributeTest.php | 2 +- tests/modules/saml/src/Auth/Source/SPTest.php | 71 +- 7 files changed, 699 insertions(+), 559 deletions(-) diff --git a/composer.lock b/composer.lock index 2a541b83c1..27abfc8ae8 100644 --- a/composer.lock +++ b/composer.lock @@ -290,16 +290,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.11.0", + "version": "2.12.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f" + "reference": "9b38012e7b54f594707e6db52c684dc0a74b3a43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/bbb5e61349fa5cb822b3e87842b951088b76b81f", - "reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/9b38012e7b54f594707e6db52c684dc0a74b3a43", + "reference": "9b38012e7b54f594707e6db52c684dc0a74b3a43", "shasum": "" }, "require": { @@ -389,7 +389,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.11.0" + "source": "https://github.com/guzzle/psr7/tree/2.12.0" }, "funding": [ { @@ -405,7 +405,7 @@ "type": "tidelift" } ], - "time": "2026-06-02T12:30:48+00:00" + "time": "2026-06-16T21:50:11+00:00" }, { "name": "nikic/php-parser", @@ -2100,33 +2100,32 @@ }, { "name": "symfony/error-handler", - "version": "v7.4.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa" + "reference": "d8aeb1abd3fef84795567850d3a567bdb5945ee5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa", - "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/d8aeb1abd3fef84795567850d3a567bdb5945ee5", + "reference": "d8aeb1abd3fef84795567850d3a567bdb5945ee5", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4.1", "psr/log": "^1|^2|^3", "symfony/polyfill-php85": "^1.32", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/var-dumper": "^7.4|^8.0" }, "conflict": { - "symfony/deprecation-contracts": "<2.5", - "symfony/http-kernel": "<6.4" + "symfony/deprecation-contracts": "<2.5" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0", + "symfony/console": "^7.4|^8.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", "symfony/webpack-encore-bundle": "^1.0|^2.0" }, "bin": [ @@ -2158,7 +2157,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.4.8" + "source": "https://github.com/symfony/error-handler/tree/v8.1.0" }, "funding": [ { @@ -2178,7 +2177,7 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/event-dispatcher", @@ -4100,35 +4099,34 @@ }, { "name": "symfony/string", - "version": "v7.4.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde" + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", - "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", + "url": "https://api.github.com/repos/symfony/string/zipball/afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.33", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4.1", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0" + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4167,7 +4165,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.13" + "source": "https://github.com/symfony/string/tree/v8.1.0" }, "funding": [ { @@ -4187,7 +4185,7 @@ "type": "tidelift" } ], - "time": "2026-05-23T15:23:29+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/translation", @@ -4488,31 +4486,31 @@ }, { "name": "symfony/var-dumper", - "version": "v7.4.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd" + "reference": "c2c4df1d21477cc21c9f6dc1b14d07c3abc4963e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9510c3966f749a1d1ff0059e1eabef6cc621e7fd", - "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c2c4df1d21477cc21c9f6dc1b14d07c3abc4963e", + "reference": "c2c4df1d21477cc21c9f6dc1b14d07c3abc4963e", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4.1", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { - "symfony/console": "<6.4" + "symfony/console": "<7.4", + "symfony/error-handler": "<7.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", "twig/twig": "^3.12" }, "bin": [ @@ -4551,7 +4549,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.8" + "source": "https://github.com/symfony/var-dumper/tree/v8.1.0" }, "funding": [ { @@ -4571,7 +4569,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T13:44:50+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/var-exporter", @@ -4876,16 +4874,16 @@ }, { "name": "webmozart/assert", - "version": "2.4.0", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155" + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/9007ea6f45ecf352a9422b36644e4bfc039b9155", - "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70", "shasum": "" }, "require": { @@ -4936,9 +4934,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.4.0" + "source": "https://github.com/webmozarts/assert/tree/2.4.1" }, - "time": "2026-05-20T13:07:01+00:00" + "time": "2026-06-15T15:31:57+00:00" } ], "packages-dev": [ @@ -5973,33 +5971,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "12.5.7", + "version": "14.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "186dab580576598076de6818596d12b61801880e" + "reference": "10d7da3628a99289cdf4c662dd7f0d73f1baec83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/186dab580576598076de6818596d12b61801880e", - "reference": "186dab580576598076de6818596d12b61801880e", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/10d7da3628a99289cdf4c662dd7f0d73f1baec83", + "reference": "10d7da3628a99289cdf4c662dd7f0d73f1baec83", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", + "ext-mbstring": "*", "ext-xmlwriter": "*", "nikic/php-parser": "^5.7.0", - "php": ">=8.3", - "phpunit/php-text-template": "^5.0", - "sebastian/complexity": "^5.0", - "sebastian/environment": "^8.1.2", - "sebastian/lines-of-code": "^4.0.1", - "sebastian/version": "^6.0", + "php": ">=8.4", + "phpunit/php-text-template": "^6.0", + "sebastian/complexity": "^6.0", + "sebastian/environment": "^9.3.2", + "sebastian/git-state": "^1.0", + "sebastian/lines-of-code": "^5.0.1", + "sebastian/version": "^7.0", "theseer/tokenizer": "^2.0.1" }, "require-dev": { - "phpunit/phpunit": "^12.5.28" + "phpunit/phpunit": "^13.2.0" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -6008,7 +6008,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "12.5.x-dev" + "dev-main": "14.2.x-dev" } }, "autoload": { @@ -6037,7 +6037,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.7" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/14.2.2" }, "funding": [ { @@ -6057,32 +6057,32 @@ "type": "tidelift" } ], - "time": "2026-06-01T13:24:19+00:00" + "time": "2026-06-08T11:50:38+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "6.0.1", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5" + "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", - "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", + "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -6110,7 +6110,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.1" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/7.0.0" }, "funding": [ { @@ -6130,28 +6130,28 @@ "type": "tidelift" } ], - "time": "2026-02-02T14:04:18+00:00" + "time": "2026-02-06T04:33:26+00:00" }, { "name": "phpunit/php-invoker", - "version": "6.0.0", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" + "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", - "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", + "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "suggest": { "ext-pcntl": "*" @@ -6159,7 +6159,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -6186,40 +6186,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" + "source": "https://github.com/sebastianbergmann/php-invoker/tree/7.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-invoker", + "type": "tidelift" } ], - "time": "2025-02-07T04:58:58+00:00" + "time": "2026-02-06T04:34:47+00:00" }, { "name": "phpunit/php-text-template", - "version": "5.0.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" + "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", - "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/a47af19f93f76aa3368303d752aa5272ca3299f4", + "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -6246,40 +6258,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/6.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template", + "type": "tidelift" } ], - "time": "2025-02-07T04:59:16+00:00" + "time": "2026-02-06T04:36:37+00:00" }, { "name": "phpunit/php-timer", - "version": "8.0.0", + "version": "9.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" + "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", - "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a0e12065831f6ab0d83120dc61513eb8d9a966f6", + "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "8.0-dev" + "dev-main": "9.0-dev" } }, "autoload": { @@ -6306,28 +6330,40 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" + "source": "https://github.com/sebastianbergmann/php-timer/tree/9.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-timer", + "type": "tidelift" } ], - "time": "2025-02-07T04:59:38+00:00" + "time": "2026-02-06T04:37:53+00:00" }, { "name": "phpunit/phpunit", - "version": "12.5.30", + "version": "13.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "900400a5b616d6fb306f9549f6da33ba615d3fbb" + "reference": "60da0ff1e10a0f72ee18a24117ec3b613a346bba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/900400a5b616d6fb306f9549f6da33ba615d3fbb", - "reference": "900400a5b616d6fb306f9549f6da33ba615d3fbb", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/60da0ff1e10a0f72ee18a24117ec3b613a346bba", + "reference": "60da0ff1e10a0f72ee18a24117ec3b613a346bba", "shasum": "" }, "require": { @@ -6340,22 +6376,24 @@ "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", - "php": ">=8.3", - "phpunit/php-code-coverage": "^12.5.7", - "phpunit/php-file-iterator": "^6.0.1", - "phpunit/php-invoker": "^6.0.0", - "phpunit/php-text-template": "^5.0.0", - "phpunit/php-timer": "^8.0.0", - "sebastian/cli-parser": "^4.2.1", - "sebastian/comparator": "^7.1.8", - "sebastian/diff": "^7.0.0", - "sebastian/environment": "^8.1.2", - "sebastian/exporter": "^7.0.3", - "sebastian/global-state": "^8.0.3", - "sebastian/object-enumerator": "^7.0.0", - "sebastian/recursion-context": "^7.0.1", - "sebastian/type": "^6.0.4", - "sebastian/version": "^6.0.0", + "php": ">=8.4.1", + "phpunit/php-code-coverage": "^14.2.2", + "phpunit/php-file-iterator": "^7.0.0", + "phpunit/php-invoker": "^7.0.0", + "phpunit/php-text-template": "^6.0.0", + "phpunit/php-timer": "^9.0.0", + "sebastian/cli-parser": "^5.0.0", + "sebastian/comparator": "^8.3.0", + "sebastian/diff": "^9.0", + "sebastian/environment": "^9.3.2", + "sebastian/exporter": "^8.1.0", + "sebastian/file-filter": "^1.0", + "sebastian/git-state": "^1.0", + "sebastian/global-state": "^9.0.1", + "sebastian/object-enumerator": "^8.0.0", + "sebastian/recursion-context": "^8.0.0", + "sebastian/type": "^7.0.1", + "sebastian/version": "^7.0.0", "staabm/side-effects-detector": "^1.0.5" }, "bin": [ @@ -6364,7 +6402,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "12.5-dev" + "dev-main": "13.2-dev" } }, "autoload": { @@ -6396,7 +6434,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.30" + "source": "https://github.com/sebastianbergmann/phpunit/tree/13.2.1" }, "funding": [ { @@ -6404,7 +6442,7 @@ "type": "other" } ], - "time": "2026-06-15T13:12:30+00:00" + "time": "2026-06-15T13:14:22+00:00" }, { "name": "predis/predis", @@ -6471,28 +6509,28 @@ }, { "name": "sebastian/cli-parser", - "version": "4.2.1", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "7d05781b13f7dec9043a629a21d086ed74582a15" + "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/7d05781b13f7dec9043a629a21d086ed74582a15", - "reference": "7d05781b13f7dec9043a629a21d086ed74582a15", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/48a4654fa5e48c1c81214e9930048a572d4b23ca", + "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.5.25" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.2-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -6516,7 +6554,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/5.0.0" }, "funding": [ { @@ -6536,31 +6574,31 @@ "type": "tidelift" } ], - "time": "2026-05-17T05:29:34+00:00" + "time": "2026-02-06T04:39:44+00:00" }, { "name": "sebastian/comparator", - "version": "7.1.8", + "version": "8.3.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "7c65c1e79836812819705b473a90c12399542485" + "reference": "c025fc7604afab3f195fab7cdaf72327331af241" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/7c65c1e79836812819705b473a90c12399542485", - "reference": "7c65c1e79836812819705b473a90c12399542485", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/c025fc7604afab3f195fab7cdaf72327331af241", + "reference": "c025fc7604afab3f195fab7cdaf72327331af241", "shasum": "" }, "require": { "ext-dom": "*", "ext-mbstring": "*", - "php": ">=8.3", - "sebastian/diff": "^7.0", - "sebastian/exporter": "^7.0.3" + "php": ">=8.4", + "sebastian/diff": "^9.0", + "sebastian/exporter": "^8.1.0" }, "require-dev": { - "phpunit/phpunit": "^12.5.25" + "phpunit/phpunit": "^13.2" }, "suggest": { "ext-bcmath": "For comparing BcMath\\Number objects" @@ -6568,7 +6606,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "7.1-dev" + "dev-main": "8.3-dev" } }, "autoload": { @@ -6608,7 +6646,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.8" + "source": "https://github.com/sebastianbergmann/comparator/tree/8.3.0" }, "funding": [ { @@ -6628,33 +6666,33 @@ "type": "tidelift" } ], - "time": "2026-05-21T04:45:25+00:00" + "time": "2026-06-05T03:06:45+00:00" }, { "name": "sebastian/complexity", - "version": "5.0.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" + "reference": "c5651c795c98093480df79350cb050813fc7a2f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", - "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c5651c795c98093480df79350cb050813fc7a2f3", + "reference": "c5651c795c98093480df79350cb050813fc7a2f3", "shasum": "" }, "require": { "nikic/php-parser": "^5.0", - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -6678,41 +6716,53 @@ "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" + "source": "https://github.com/sebastianbergmann/complexity/tree/6.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/complexity", + "type": "tidelift" } ], - "time": "2025-02-07T04:55:25+00:00" + "time": "2026-02-06T04:41:32+00:00" }, { "name": "sebastian/diff", - "version": "7.0.0", + "version": "9.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "7ab1ea946c012266ca32390913653d844ecd085f" + "reference": "a3fb6a298a265ff487a91bbea46e03cd01dbb226" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", - "reference": "7ab1ea946c012266ca32390913653d844ecd085f", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/a3fb6a298a265ff487a91bbea46e03cd01dbb226", + "reference": "a3fb6a298a265ff487a91bbea46e03cd01dbb226", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0", - "symfony/process": "^7.2" + "phpunit/phpunit": "^13.2", + "symfony/process": "^7.4.13" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "9.0-dev" } }, "autoload": { @@ -6745,35 +6795,47 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" + "source": "https://github.com/sebastianbergmann/diff/tree/9.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/diff", + "type": "tidelift" } ], - "time": "2025-02-07T04:55:46+00:00" + "time": "2026-06-05T03:04:51+00:00" }, { "name": "sebastian/environment", - "version": "8.1.2", + "version": "9.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439" + "reference": "6c9e487c9eb706a8d258102a1c0b0a3e53e86c2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/9d32c685773823b1983e256ae4ecd48a10d6e439", - "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6c9e487c9eb706a8d258102a1c0b0a3e53e86c2e", + "reference": "6c9e487c9eb706a8d258102a1c0b0a3e53e86c2e", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.5.26" + "phpunit/phpunit": "^13.1.11" }, "suggest": { "ext-posix": "*" @@ -6781,7 +6843,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "8.1-dev" + "dev-main": "9.3-dev" } }, "autoload": { @@ -6809,7 +6871,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/8.1.2" + "source": "https://github.com/sebastianbergmann/environment/tree/9.3.2" }, "funding": [ { @@ -6829,34 +6891,34 @@ "type": "tidelift" } ], - "time": "2026-05-25T13:40:20+00:00" + "time": "2026-05-25T13:41:38+00:00" }, { "name": "sebastian/exporter", - "version": "7.0.3", + "version": "8.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23" + "reference": "c0d29a945f8cf82f300a05e69874508e307ca4c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", - "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c0d29a945f8cf82f300a05e69874508e307ca4c6", + "reference": "c0d29a945f8cf82f300a05e69874508e307ca4c6", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=8.3", - "sebastian/recursion-context": "^7.0.1" + "php": ">=8.4", + "sebastian/recursion-context": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^12.5.25" + "phpunit/phpunit": "^13.1.10" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "8.1-dev" } }, "autoload": { @@ -6899,7 +6961,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.3" + "source": "https://github.com/sebastianbergmann/exporter/tree/8.1.0" }, "funding": [ { @@ -6919,35 +6981,173 @@ "type": "tidelift" } ], - "time": "2026-05-20T04:37:17+00:00" + "time": "2026-05-21T11:50:56+00:00" + }, + { + "name": "sebastian/file-filter", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/file-filter.git", + "reference": "33a26f394330f6faa7684bb9cc73afb7727aae93" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/file-filter/zipball/33a26f394330f6faa7684bb9cc73afb7727aae93", + "reference": "33a26f394330f6faa7684bb9cc73afb7727aae93", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for filtering files", + "homepage": "https://github.com/sebastianbergmann/file-filter", + "support": { + "issues": "https://github.com/sebastianbergmann/file-filter/issues", + "security": "https://github.com/sebastianbergmann/file-filter/security/policy", + "source": "https://github.com/sebastianbergmann/file-filter/tree/1.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/file-filter", + "type": "tidelift" + } + ], + "time": "2026-04-22T07:20:04+00:00" + }, + { + "name": "sebastian/git-state", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/git-state.git", + "reference": "792a952e0eba55b6960a48aeceb9f371aad1f76b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/git-state/zipball/792a952e0eba55b6960a48aeceb9f371aad1f76b", + "reference": "792a952e0eba55b6960a48aeceb9f371aad1f76b", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for describing the state of a Git checkout", + "homepage": "https://github.com/sebastianbergmann/git-state", + "support": { + "issues": "https://github.com/sebastianbergmann/git-state/issues", + "security": "https://github.com/sebastianbergmann/git-state/security/policy", + "source": "https://github.com/sebastianbergmann/git-state/tree/1.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/git-state", + "type": "tidelift" + } + ], + "time": "2026-03-21T12:54:28+00:00" }, { "name": "sebastian/global-state", - "version": "8.0.3", + "version": "9.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9" + "reference": "ba68ba79da690cf7eddefd3ce5b78b20b9ba9945" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b164d3274d6537ab462591c5755f76a8f5b1aae9", - "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ba68ba79da690cf7eddefd3ce5b78b20b9ba9945", + "reference": "ba68ba79da690cf7eddefd3ce5b78b20b9ba9945", "shasum": "" }, "require": { - "php": ">=8.3", - "sebastian/object-reflector": "^5.0", - "sebastian/recursion-context": "^7.0.1" + "php": ">=8.4", + "sebastian/object-reflector": "^6.0", + "sebastian/recursion-context": "^8.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^12.5.28" + "phpunit/phpunit": "^13.1.13" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "8.0-dev" + "dev-main": "9.0-dev" } }, "autoload": { @@ -6973,7 +7173,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.3" + "source": "https://github.com/sebastianbergmann/global-state/tree/9.0.1" }, "funding": [ { @@ -6993,33 +7193,33 @@ "type": "tidelift" } ], - "time": "2026-06-01T15:10:33+00:00" + "time": "2026-06-01T15:11:33+00:00" }, { "name": "sebastian/lines-of-code", - "version": "4.0.1", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e" + "reference": "d2cff273a90c79b0eb590baa682d4b5c318bdbb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d543b8ef219dcd8da262cbb958639a96bedba10e", - "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d2cff273a90c79b0eb590baa682d4b5c318bdbb7", + "reference": "d2cff273a90c79b0eb590baa682d4b5c318bdbb7", "shasum": "" }, "require": { "nikic/php-parser": "^5.7.0", - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.5.25" + "phpunit/phpunit": "^13.1.10" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -7043,7 +7243,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.1" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/5.0.1" }, "funding": [ { @@ -7063,34 +7263,34 @@ "type": "tidelift" } ], - "time": "2026-05-19T16:22:07+00:00" + "time": "2026-05-19T16:23:37+00:00" }, { "name": "sebastian/object-enumerator", - "version": "7.0.0", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" + "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", - "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", + "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", "shasum": "" }, "require": { - "php": ">=8.3", - "sebastian/object-reflector": "^5.0", - "sebastian/recursion-context": "^7.0" + "php": ">=8.4", + "sebastian/object-reflector": "^6.0", + "sebastian/recursion-context": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -7113,40 +7313,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/8.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/object-enumerator", + "type": "tidelift" } ], - "time": "2025-02-07T04:57:48+00:00" + "time": "2026-02-06T04:46:36+00:00" }, { "name": "sebastian/object-reflector", - "version": "5.0.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "4bfa827c969c98be1e527abd576533293c634f6a" + "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", - "reference": "4bfa827c969c98be1e527abd576533293c634f6a", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", + "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -7169,40 +7381,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/6.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/object-reflector", + "type": "tidelift" } ], - "time": "2025-02-07T04:58:17+00:00" + "time": "2026-02-06T04:47:13+00:00" }, { "name": "sebastian/recursion-context", - "version": "7.0.1", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" + "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", - "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/74c5af21f6a5833e91767ca068c4d3dfec15317e", + "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -7233,7 +7457,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/8.0.0" }, "funding": [ { @@ -7253,32 +7477,32 @@ "type": "tidelift" } ], - "time": "2025-08-13T04:44:59+00:00" + "time": "2026-02-06T04:51:28+00:00" }, { "name": "sebastian/type", - "version": "6.0.4", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "82ff822c2edc46724be9f7411d3163021f602773" + "reference": "fee0309275847fefd7636167085e379c1dbf6990" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/82ff822c2edc46724be9f7411d3163021f602773", - "reference": "82ff822c2edc46724be9f7411d3163021f602773", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fee0309275847fefd7636167085e379c1dbf6990", + "reference": "fee0309275847fefd7636167085e379c1dbf6990", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.5.25" + "phpunit/phpunit": "^13.1.10" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -7302,7 +7526,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/6.0.4" + "source": "https://github.com/sebastianbergmann/type/tree/7.0.1" }, "funding": [ { @@ -7322,29 +7546,29 @@ "type": "tidelift" } ], - "time": "2026-05-20T06:45:45+00:00" + "time": "2026-05-20T06:49:11+00:00" }, { "name": "sebastian/version", - "version": "6.0.0", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" + "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", - "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ad37a5552c8e2b88572249fdc19b6da7792e021b", + "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -7368,15 +7592,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/version/issues", "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" + "source": "https://github.com/sebastianbergmann/version/tree/7.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/version", + "type": "tidelift" } ], - "time": "2025-02-07T05:00:38+00:00" + "time": "2026-02-06T04:52:52+00:00" }, { "name": "simplesamlphp/simplesamlphp-test-framework", @@ -7717,107 +7953,27 @@ ], "time": "2026-05-29T05:06:50+00:00" }, - { - "name": "symfony/polyfill-php84", - "version": "v1.38.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", - "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php84\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-26T12:51:13+00:00" - }, { "name": "symfony/property-access", - "version": "v7.4.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "b7dad9dae8b8a47ef7ecc76c8569e7d8c7d90cfc" + "reference": "9261ef060f26cc7b728f67f141ba19b98a6209a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/b7dad9dae8b8a47ef7ecc76c8569e7d8c7d90cfc", - "reference": "b7dad9dae8b8a47ef7ecc76c8569e7d8c7d90cfc", + "url": "https://api.github.com/repos/symfony/property-access/zipball/9261ef060f26cc7b728f67f141ba19b98a6209a9", + "reference": "9261ef060f26cc7b728f67f141ba19b98a6209a9", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/property-info": "^6.4.32|~7.3.10|^7.4.4|^8.0.4" + "php": ">=8.4.1", + "symfony/property-info": "^7.4.4|^8.0.4" }, "require-dev": { - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/var-exporter": "^6.4.1|^7.0.1|^8.0" + "symfony/cache": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -7856,7 +8012,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v7.4.8" + "source": "https://github.com/symfony/property-access/tree/v8.1.0" }, "funding": [ { @@ -7876,41 +8032,37 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/property-info", - "version": "v7.4.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "ac5e82528b986c4f7cfccbf7764b5d2e824d6175" + "reference": "4721e8c56d0cd2378e0ef9a9899f810008b859f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/ac5e82528b986c4f7cfccbf7764b5d2e824d6175", - "reference": "ac5e82528b986c4f7cfccbf7764b5d2e824d6175", + "url": "https://api.github.com/repos/symfony/property-info/zipball/4721e8c56d0cd2378e0ef9a9899f810008b859f7", + "reference": "4721e8c56d0cd2378e0ef9a9899f810008b859f7", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/string": "^6.4|^7.0|^8.0", + "php": ">=8.4.1", + "symfony/string": "^7.4|^8.0", "symfony/type-info": "^7.4.7|^8.0.7" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2|>=7", - "phpdocumentor/type-resolver": "<1.5.1", - "symfony/cache": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/serializer": "<6.4" + "phpdocumentor/type-resolver": "<1.5.1" }, "require-dev": { "phpdocumentor/reflection-docblock": "^5.2|^6.0", "phpstan/phpdoc-parser": "^1.0|^2.0", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4|^7.0|^8.0" + "symfony/cache": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -7946,7 +8098,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.4.8" + "source": "https://github.com/symfony/property-info/tree/v8.1.0" }, "funding": [ { @@ -7966,63 +8118,58 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/serializer", - "version": "v7.4.10", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "268c5aa6c4bd675eddd89348e7ecac292a843ddd" + "reference": "d101886195c5f772cf7033641fe9c40c3e3969e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/268c5aa6c4bd675eddd89348e7ecac292a843ddd", - "reference": "268c5aa6c4bd675eddd89348e7ecac292a843ddd", + "url": "https://api.github.com/repos/symfony/serializer/zipball/d101886195c5f772cf7033641fe9c40c3e3969e1", + "reference": "d101886195c5f772cf7033641fe9c40c3e3969e1", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4.1", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php84": "^1.30" + "symfony/polyfill-ctype": "^1.8" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", - "symfony/dependency-injection": "<6.4", - "symfony/property-access": "<6.4.31|>=7.0,<7.4.2|>=8.0,<8.0.2", - "symfony/property-info": "<6.4", - "symfony/type-info": "<7.2.5", - "symfony/uid": "<6.4", - "symfony/validator": "<6.4", - "symfony/yaml": "<6.4" + "symfony/property-access": "<8.1", + "symfony/property-info": "<7.4", + "symfony/type-info": "<7.4" }, "require-dev": { "phpdocumentor/reflection-docblock": "^5.2|^6.0", "phpstan/phpdoc-parser": "^1.0|^2.0", "seld/jsonlint": "^1.10", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^7.2|^8.0", - "symfony/error-handler": "^6.4|^7.0|^8.0", - "symfony/filesystem": "^6.4|^7.0|^8.0", - "symfony/form": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", - "symfony/property-access": "^6.4.31|^7.4.2|^8.0.2", - "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/cache": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/property-access": "^8.1", + "symfony/property-info": "^7.4|^8.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/type-info": "^7.2.5|^8.0", - "symfony/uid": "^6.4|^7.0|^8.0", - "symfony/validator": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/type-info": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -8050,7 +8197,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.4.10" + "source": "https://github.com/symfony/serializer/tree/v8.1.0" }, "funding": [ { @@ -8070,26 +8217,25 @@ "type": "tidelift" } ], - "time": "2026-05-03T13:03:28+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/type-info", - "version": "v7.4.9", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "cafeedbf157b890e94ac5b83eaed85595106d5d6" + "reference": "9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/cafeedbf157b890e94ac5b83eaed85595106d5d6", - "reference": "cafeedbf157b890e94ac5b83eaed85595106d5d6", + "url": "https://api.github.com/repos/symfony/type-info/zipball/9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7", + "reference": "9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7", "shasum": "" }, "require": { - "php": ">=8.2", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.4.1", + "psr/container": "^1.1|^2.0" }, "conflict": { "phpstan/phpdoc-parser": "<1.30" @@ -8133,7 +8279,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.4.9" + "source": "https://github.com/symfony/type-info/tree/v8.1.0" }, "funding": [ { @@ -8153,7 +8299,7 @@ "type": "tidelift" } ], - "time": "2026-04-22T15:21:55+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "theseer/tokenizer", diff --git a/tests/modules/core/src/Auth/Source/RequestedAuthnContextSelectorTest.php b/tests/modules/core/src/Auth/Source/RequestedAuthnContextSelectorTest.php index 2343f2b808..1acd42d1fe 100644 --- a/tests/modules/core/src/Auth/Source/RequestedAuthnContextSelectorTest.php +++ b/tests/modules/core/src/Auth/Source/RequestedAuthnContextSelectorTest.php @@ -90,7 +90,7 @@ public function testAuthenticationVariant1(): void $info = ['AuthId' => 'selector']; $config = $this->sourceConfig->getArray('selector'); - $selector = RequestedAuthnContextSelector($info, $config); + $selector = new RequestedAuthnContextSelector($info, $config); $state = ['saml:RequestedAuthnContext' => ['AuthnContextClassRef' => null]]; $request = Request::createFromGlobals(); $selector->authenticate($request, $state); @@ -175,7 +175,7 @@ public function testArraySyntaxWorks(): void * @param array $state * @param \Symfony\Component\HttpFoundation\Response|null */ - public static function doAuthentication(Request $request, Auth\Source $as, array $state): ?Response + public static function doAuthentication(Request $request, Auth\Source $as, array &$state): ?Response { // Dummy return null; diff --git a/tests/modules/exampleauth/src/Controller/ExampleAuthTest.php b/tests/modules/exampleauth/src/Controller/ExampleAuthTest.php index 1ef6275e68..ca291f7c27 100644 --- a/tests/modules/exampleauth/src/Controller/ExampleAuthTest.php +++ b/tests/modules/exampleauth/src/Controller/ExampleAuthTest.php @@ -9,10 +9,10 @@ use SimpleSAML\Auth; use SimpleSAML\Configuration; use SimpleSAML\Error; -use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Module\exampleauth\Controller; use SimpleSAML\Session; use SimpleSAML\XHTML\Template; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; /** @@ -46,6 +46,20 @@ protected function setUp(): void $this->session = Session::getSessionFromRequest(); Configuration::setPreLoadedConfig($this->config, 'config.php'); + + Configuration::setPreLoadedConfig( + Configuration::loadFromArray( + [ + 'external-example' => ['exampleauth:External'], + '[ARRAY]', + 'simplesaml', + ], + ), + 'authsources.php', + 'simplesaml', + ); + + $this->session = Session::getSessionFromRequest(); } @@ -81,7 +95,7 @@ public function testAuthpageInvalidReturnTo(): void $request = Request::create( '/authpage', 'POST', - ['ReturnTo' => 'SomeBogusValue'], + ['ReturnTo' => '/SomeBogusValue'], ); $c = new Controller\ExampleAuth($this->config, $this->session); @@ -94,16 +108,15 @@ public function testAuthpageInvalidReturnTo(): void /** - * Test that accessing the authpage-endpoint using GET-method show a login-screen + * Test that accessing the authpage-endpoint without ReturnTo parameter * * @return void */ - public function testAuthpageGetMethod(): void + public function testAuthpageMissingReturnTo(): void { $request = Request::create( '/authpage', 'POST', - ['ReturnTo' => 'State=/'], ); $c = new Controller\ExampleAuth($this->config, $this->session); @@ -114,9 +127,9 @@ public static function loadState(string $id, string $stage, bool $allowMissing = } }); - $response = $c->authpage($request); - $this->assertTrue($response->isSuccessful()); - $this->assertInstanceOf(Template::class, $response); + $this->expectException(Error\Exception::class); + $this->expectExceptionMessage('Missing ReturnTo parameter.'); + $c->authpage($request); } @@ -145,7 +158,7 @@ public static function loadState(string $id, string $stage, bool $allowMissing = $response = $c->authpage($request); $this->assertTrue($response->isSuccessful()); - $this->assertInstanceOf(RunnableResponse::class, $response); + $this->assertInstanceOf(Response::class, $response); } @@ -160,7 +173,7 @@ public function testAuthpagePostMethodIncorrectPassword(): void $request = Request::create( '/authpage', 'POST', - ['ReturnTo' => 'State=/', 'username' => 'user', 'password' => 'something stupid'], + ['ReturnTo' => '/State=/', 'username' => 'user', 'password' => 'something stupid'], ); $c = new Controller\ExampleAuth($this->config, $this->session); @@ -179,21 +192,39 @@ public static function loadState(string $id, string $stage, bool $allowMissing = /** * Test that accessing the resume-endpoint leads to a redirect - * - * @return void */ public function testResume(): void { + $_ESSION['uid'] = 'phpunit'; + $_ESSION['name'] = 'John Doe'; + $_ESSION['mail'] = 'JohnDoe@example.org'; + $_ESSION['type'] = 'member'; + $request = Request::create( '/resume', 'GET', + ['AuthState' => 'someState'], ); $c = new Controller\ExampleAuth($this->config, $this->session); + $c->setAuthState(new class () extends Auth\State { + public static function loadState(string $id, string $stage, bool $allowMissing = false): ?array + { + return [ + 'exampleauth:AuthID' => 'external-example', + 'SimpleSAML\Module\exampleauth\Auth\Source\External.AuthId' => 'example-external', + 'LoginCompletedHandler' => [Auth\Source::class, 'loginCompleted'], + '\SimpleSAML\Auth\Source.Return' => 'https://example.org', + '\SimpleSAML\Auth\Source.id' => 'phpunit', + ]; + } + }); + // @TODO Pass the session down the chain so this test can fully run + $this->expectException(Error\CannotSetCookie::class); $response = $c->resume($request); - $this->assertTrue($response->isSuccessful()); - $this->assertInstanceOf(RunnableResponse::class, $response); + //$this->assertTrue($response->isSuccessful()); + //$this->assertInstanceOf(Response::class, $response); } @@ -207,20 +238,23 @@ public function testRedirect(): void $request = Request::create( '/redirecttest', 'GET', - ['StateId' => 'someState'], + ['AuthState' => 'someState'], ); $c = new Controller\ExampleAuth($this->config, $this->session); $c->setAuthState(new class () extends Auth\State { public static function loadState(string $id, string $stage, bool $allowMissing = false): ?array { - return []; + return [ + 'ReturnURL' => 'https://example.org/phpunit', + Auth\ProcessingChain::FILTERS_INDEX => [], + ]; } }); $response = $c->redirecttest($request); - $this->assertTrue($response->isSuccessful()); - $this->assertInstanceOf(RunnableResponse::class, $response); + $this->assertTrue($response->isRedirection()); + $this->assertInstanceOf(RedirectResponse::class, $response); } @@ -239,7 +273,7 @@ public function testRedirectMissingStateId(): void $c = new Controller\ExampleAuth($this->config, $this->session); $this->expectException(Error\BadRequest::class); - $this->expectExceptionMessage('Missing required StateId query parameter.'); + $this->expectExceptionMessage('Missing required AuthState query parameter.'); $c->redirecttest($request); } diff --git a/tests/modules/multiauth/src/Auth/Source/MultiAuthTest.php b/tests/modules/multiauth/src/Auth/Source/MultiAuthTest.php index 9f29166e91..8e70dee113 100644 --- a/tests/modules/multiauth/src/Auth/Source/MultiAuthTest.php +++ b/tests/modules/multiauth/src/Auth/Source/MultiAuthTest.php @@ -9,6 +9,7 @@ use SimpleSAML\Configuration; use SimpleSAML\Module\multiauth\Auth\Source\MultiAuth; use SimpleSAML\TestUtils\ClearStateTestCase; +use Symfony\Component\HttpFoundation\Request; /** */ @@ -142,6 +143,7 @@ public function testPreselectMustBeValid(): void /** + */ public function testPreselectIsOptional(): void { $sourceConfig = Configuration::loadFromArray([ @@ -182,50 +184,52 @@ public function testPreselectIsOptional(): void $state = []; $source = new MultiAuth(['AuthId' => 'example-multi'], $sourceConfig->getArray('example-multi')); + $request = Request::createFromGlobals(); try { - $source->authenticate($state); + $source->authenticate($request, $state); } catch (Error $e) { } catch (Exception $e) { } $this->assertArrayNotHasKey('multiauth:preselect', $state); } - */ /** + */ public function testPreselectCanBeConfigured(): void { $state = []; + $request = Request::createFromGlobals(); $source = new MultiAuth(['AuthId' => 'example-multi'], $this->sourceConfig->getArray('example-multi')); try { - $source->authenticate($state); + $source->authenticate($request, $state); } catch (Exception $e) { } $this->assertArrayHasKey('multiauth:preselect', $state); $this->assertEquals('example-saml', $state['multiauth:preselect']); } - */ /** + */ public function testStatePreselectHasPriority(): void { $state = ['multiauth:preselect' => 'example-admin']; + $request = Request::createFromGlobals(); $source = new MultiAuth(['AuthId' => 'example-multi'], $this->sourceConfig->getArray('example-multi')); try { - $source->authenticate($state); + $source->authenticate($request, $state); } catch (Exception $e) { } $this->assertArrayHasKey('multiauth:preselect', $state); $this->assertEquals('example-admin', $state['multiauth:preselect']); } - */ } diff --git a/tests/modules/multiauth/src/Controller/DiscoControllerTest.php b/tests/modules/multiauth/src/Controller/DiscoControllerTest.php index d049d843a2..0d55754d15 100644 --- a/tests/modules/multiauth/src/Controller/DiscoControllerTest.php +++ b/tests/modules/multiauth/src/Controller/DiscoControllerTest.php @@ -9,12 +9,12 @@ use SimpleSAML\Auth\State; use SimpleSAML\Configuration; use SimpleSAML\Error; -use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Module\multiauth\Auth\Source\MultiAuth; use SimpleSAML\Module\multiauth\Controller; use SimpleSAML\Session; use SimpleSAML\XHTML\Template; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; /** @@ -30,6 +30,9 @@ class DiscoControllerTest extends TestCase /** @var \SimpleSAML\Session */ protected Session $session; + /** @var \SimpleSAML\Auth\Source */ + protected Source $authSource; + /** * Set up for each test. @@ -69,6 +72,18 @@ protected function setUp(): void 'authsources.php', 'simplesaml', ); + + $this->authSource = new class () extends MultiAuth { + public function __construct() + { + // stub + } + + public static function getById(string $authId, ?string $type = null): ?Source + { + return new static(); + } + }; } @@ -121,26 +136,7 @@ public static function loadState(string $id, string $stage, bool $allowMissing = ]; } }); - - $c->setAuthSource(new class () extends MultiAuth { - public function __construct() - { - // stub - } - - - public function authenticate(array &$state): ?Response - { - // stub - return null; - } - - - public static function getById(string $authId, ?string $type = null): ?Source - { - return new static(); - } - }); + $c->setAuthSource($this->authSource); $response = $c->discovery($request); @@ -178,26 +174,7 @@ public static function loadState(string $id, string $stage, bool $allowMissing = ]; } }); - - $c->setAuthSource(new class () extends MultiAuth { - public function __construct() - { - // stub - } - - - public function authenticate(array &$state): ?Response - { - // stub - return null; - } - - - public static function getById(string $authId, ?string $type = null): ?Source - { - return new static(); - } - }); + $c->setAuthSource($this->authSource); $response = $c->discovery($request); @@ -207,8 +184,7 @@ public static function getById(string $authId, ?string $type = null): ?Source /** - * Test that a valid requests results in a RunnableResponse - * @return void + * Test that a valid requests results in a RedirectResponse */ public function testDiscoveryDelegateAuth1(): void { @@ -237,37 +213,17 @@ public static function loadState(string $id, string $stage, bool $allowMissing = ]; } }); - - $c->setAuthSource(new class () extends MultiAuth { - public function __construct() - { - // stub - } - - - public function authenticate(array &$state): ?Response - { - // stub - return null; - } - - - public static function getById(string $authId, ?string $type = null): ?Source - { - return new static(); - } - }); + $c->setAuthSource($this->authSource); $response = $c->discovery($request); - $this->assertInstanceOf(RunnableResponse::class, $response); - $this->assertTrue($response->isSuccessful()); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertTrue($response->isRedirection()); } /** - * Test that a valid request results in a RunnableResponse - * @return void + * Test that a valid request results in a RedirectResponse */ public function testDiscoveryDelegateAuth1WithPreviousSource(): void { @@ -296,37 +252,17 @@ public static function loadState(string $id, string $stage, bool $allowMissing = ]; } }); - - $c->setAuthSource(new class () extends MultiAuth { - public function __construct() - { - // stub - } - - - public function authenticate(array &$state): ?Response - { - // stub - return null; - } - - - public static function getById(string $authId, ?string $type = null): ?Source - { - return new static(); - } - }); + $c->setAuthSource($this->authSource); $response = $c->discovery($request); - $this->assertInstanceOf(RunnableResponse::class, $response); - $this->assertTrue($response->isSuccessful()); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertTrue($response->isRedirection()); } /** - * Test that a valid request results in a RunnableResponse - * @return void + * Test that a valid request results in a RedirectResponse */ public function testDiscoveryDelegateAuth2(): void { @@ -353,30 +289,11 @@ public static function loadState(string $id, string $stage, bool $allowMissing = ]; } }); - - $c->setAuthSource(new class () extends MultiAuth { - public function __construct() - { - // stub - } - - - public function authenticate(array &$state): ?Response - { - // stub - return null; - } - - - public static function getById(string $authId, ?string $type = null): ?Source - { - return new static(); - } - }); + $c->setAuthSource($this->authSource); $response = $c->discovery($request); - $this->assertInstanceOf(Response::class, $response); - $this->assertTrue($response->isSuccessful()); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertTrue($response->isRedirection()); } } diff --git a/tests/modules/saml/src/Auth/Process/NameIDAttributeTest.php b/tests/modules/saml/src/Auth/Process/NameIDAttributeTest.php index 090a82a9d5..c2bf0d4bbc 100644 --- a/tests/modules/saml/src/Auth/Process/NameIDAttributeTest.php +++ b/tests/modules/saml/src/Auth/Process/NameIDAttributeTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; -use SAML2\Constants; +use SimpleSAML\SAML2\Constants as C; use SAML2\XML\saml\NameID; use SimpleSAML\Error; use SimpleSAML\Module\saml\Auth\Process\NameIDAttribute; diff --git a/tests/modules/saml/src/Auth/Source/SPTest.php b/tests/modules/saml/src/Auth/Source/SPTest.php index 3dee5ca95a..53015de90b 100644 --- a/tests/modules/saml/src/Auth/Source/SPTest.php +++ b/tests/modules/saml/src/Auth/Source/SPTest.php @@ -24,6 +24,7 @@ use SimpleSAML\TestUtils\ClearStateTestCase; use SimpleSAML\XML\Chunk; use SimpleSAML\XML\DOMDocumentFactory; +use Symfony\Component\HttpFoundation\Request; /** * Set of test cases for \SimpleSAML\Module\saml\Auth\Source\SP. @@ -73,6 +74,7 @@ private function getIdpMetadata(): Configuration protected function setUp(): void { parent::setUp(); + $_SERVER['REQUEST_URI'] = '/dummy'; $_SERVER['REQUEST_METHOD'] = 'GET'; @@ -172,13 +174,14 @@ public function testAuthnRequest(): void $xml = $ar->toSignedXML(); /** @var \DOMAttr[] $q */ - $q = Utils::xpQuery($xml, '/samlp:AuthnRequest/@Destination'); + $xpCache = XPath::getXPath($xml); + $q = XPath::xpQuery($xml, '/samlp:AuthnRequest/@Destination', $xpCache); $this->assertEquals( $this->idpConfigArray['SingleSignOnService'][0]['Location'], $q[0]->value, ); - $q = Utils::xpQuery($xml, '/samlp:AuthnRequest/saml:Issuer'); + $q = XPath::xpQuery($xml, '/samlp:AuthnRequest/saml:Issuer', $xpCache); $this->assertEquals( 'urn:x-simplesamlphp:example-sp', $q[0]->textContent, @@ -204,14 +207,26 @@ public function testNameID(): void $xml = $ar->toSignedXML(); + $xpCache = XPath::getXPath($xml); /** @var \DOMAttr[] $q */ - $q = Utils::xpQuery($xml, '/samlp:AuthnRequest/saml:Subject/saml:NameID/@Format'); + $q = XPath::xpQuery( + $xml, + '/samlp:AuthnRequest/saml:Subject/saml:NameID/@Format', + $xpQuery, + ); + $this->assertEquals( $state['saml:NameID']['Format'], $q[0]->value, ); - $q = Utils::xpQuery($xml, '/samlp:AuthnRequest/saml:Subject/saml:NameID'); + $xpCache = XPath::getXPath($xml); + $q = XPath::xpQuery( + $xml, + '/samlp:AuthnRequest/saml:Subject/saml:NameID', + $xpQuery, + ); + $this->assertEquals( $state['saml:NameID']['Value'], $q[0]->textContent, @@ -239,7 +254,13 @@ public function testAuthnContextClassRef(): void $xml = $ar->toSignedXML(); - $q = Utils::xpQuery($xml, '/samlp:AuthnRequest/samlp:RequestedAuthnContext/saml:AuthnContextClassRef'); + $xpCache = XPath::getXPath($xml); + $q = XPath::xpQuery( + $xml, + '/samlp:AuthnRequest/samlp:RequestedAuthnContext/saml:AuthnContextClassRef', + $xpCache, + ); + $this->assertEquals( $state['saml:AuthnContextClassRef'], $q[0]->textContent, @@ -266,8 +287,9 @@ public function testForcedAuthn(): void $xml = $ar->toSignedXML(); + $xpCache = XPath::getXPath($xml); /** @var \DOMAttr[] $q */ - $q = Utils::xpQuery($xml, '/samlp:AuthnRequest/@ForceAuthn'); + $q = XPath::xpQuery($xml, '/samlp:AuthnRequest/@ForceAuthn', $xpCache); $this->assertEquals( $state['ForceAuthn'] ? 'true' : 'false', $q[0]->value, @@ -287,8 +309,10 @@ public function testIdpListWithNoMatchingMetadata(): void $info = ['AuthId' => 'default-sp']; $config = ['entityID' => 'urn:x-simplesamlphp:example-sp']; + + $request = Request::createFromGlobals(); $as = new SpTester($info, $config); - $as->authenticate($state); + $as->authenticate($request, $state); } @@ -315,8 +339,10 @@ public function testIdpListWithExplicitIdpNotMatch(): void 'entityID' => 'urn:x-simplesamlphp:example-sp', 'idp' => 'https://engine.surfconext.nl/authentication/idp/metadata', ]; + + $request = Request::createFromGlobals(); $as = new SpTester($info, $config); - $as->authenticate($state); + $as->authenticate($request, $state); } @@ -342,18 +368,23 @@ public function testIdpListWithExplicitIdpMatch(): void 'entityID' => 'urn:x-simplesamlphp:example-sp', 'idp' => $entityId, ]; + $as = new SpTester($info, $config); + $request = Request::createFromGlobals(); + try { - $as->authenticate($state); + $as->authenticate($request, $state); $this->fail('Expected ExitTestException'); } catch (ExitTestException $e) { $r = $e->getTestResult(); + /** @var \SAML2\AuthnRequest $ar */ $ar = $r['ar']; $xml = $ar->toSignedXML(); + $xpCache = XPath::getXPath($xml); /** @var \DOMAttr[] $q */ - $q = Utils::xpQuery($xml, '/samlp:AuthnRequest/@Destination'); + $q = XPath::xpQuery($xml, '/samlp:AuthnRequest/@Destination', $xpCache); $this->assertEquals( 'https://saml.idp/sso/', $q[0]->value, @@ -382,8 +413,10 @@ public function testIdpListWithSingleMatch(): void $info = ['AuthId' => 'default-sp']; $config = ['entityID' => 'urn:x-simplesamlphp:example-sp']; $as = new SpTester($info, $config); + $request = Request::createFromGlobals(); + try { - $as->authenticate($state); + $as->authenticate($request, $state); $this->fail('Expected ExitTestException'); } catch (ExitTestException $e) { $r = $e->getTestResult(); @@ -391,8 +424,9 @@ public function testIdpListWithSingleMatch(): void $ar = $r['ar']; $xml = $ar->toSignedXML(); + $xpCache = XPath::getXPath($xml); /** @var \DOMAttr[] $q */ - $q = Utils::xpQuery($xml, '/samlp:AuthnRequest/@Destination'); + $q = XPath::xpQuery($xml, '/samlp:AuthnRequest/@Destination', $xpCache); $this->assertEquals( 'https://saml.idp/sso/', $q[0]->value, @@ -418,6 +452,7 @@ public function testIdpListWithMultipleMatch(): void ["type" => "xml", "xml" => $xml1], ], ]; + Configuration::loadFromArray($c, '', 'simplesaml'); $state = [ 'saml:IDPList' => ['noSuchIdp', $entityId, $entityId1], @@ -430,10 +465,13 @@ public function testIdpListWithMultipleMatch(): void // otherwise it will call exit 'discoURL' => 'smtp://invalidurl', ]; + // Http redirect util library requires a request_uri to be set. + $request = Request::createFromGlobals(); $_SERVER['REQUEST_URI'] = 'https://l.example.com/'; + $as = new SpTester($info, $config); - $as->authenticate($state); + $as->authenticate($request, $state); } @@ -1524,7 +1562,7 @@ public function testLogoutRequest(): void ["type" => "xml", "xml" => $xml], ], ]; - Configuration::loadFromArray($c, '', 'simplesaml'); + $this->config = Configuration::loadFromArray($c, '', 'simplesaml'); $state = [ 'saml:logout:IdP' => $entityId, @@ -1541,11 +1579,12 @@ public function testLogoutRequest(): void $xml = $lr->toSignedXML(); - $q = Utils::xpQuery($xml, '/samlp:LogoutRequest/saml:NameID'); + $xpCache = XPath::getXPath($xml); + $q = XPath::xpQuery($xml, '/samlp:LogoutRequest/saml:NameID', $xpQuery); $this->assertCount(1, $q); $this->assertEquals('someone@example.com', $q[0]->nodeValue); - $q = Utils::xpQuery($xml, '/samlp:LogoutRequest/samlp:Extensions'); + $q = XPath::xpQuery($xml, '/samlp:LogoutRequest/samlp:Extensions', $xpQuery); $this->assertCount(1, $q); $this->assertCount(1, $q[0]->childNodes); $this->assertEquals('MyLogoutExtension', $q[0]->firstChild->localName); From 036b962c889590b6c55fe3f3da8dfbd72280dcd7 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Mon, 22 Jun 2026 23:13:33 +0200 Subject: [PATCH 5/8] Fix tests (run 2/2) --- .../modules/saml/src/Controller/DiscoTest.php | 33 ++-- .../saml/src/Controller/MetadataTest.php | 80 ++++----- .../modules/saml/src/Controller/ProxyTest.php | 30 +++- .../src/Controller/ServiceProviderTest.php | 164 +++++++++++------- tests/modules/saml/src/IdP/SAML2Test.php | 33 ++-- tests/src/SimpleSAML/ConfigurationTest.php | 2 +- .../Metadata/MetaDataStorageHandlerTest.php | 20 ++- .../SimpleSAML/Metadata/SAMLParserTest.php | 2 +- .../source1/saml20-idp-hosted.php | 2 +- .../source1/saml20-idp-remote.php | 10 ++ .../SimpleSAML/Utils/Config/MetadataTest.php | 4 +- 11 files changed, 224 insertions(+), 156 deletions(-) create mode 100644 tests/src/SimpleSAML/Metadata/test-metadata/source1/saml20-idp-remote.php diff --git a/tests/modules/saml/src/Controller/DiscoTest.php b/tests/modules/saml/src/Controller/DiscoTest.php index 7d84c84c96..16e75335e6 100644 --- a/tests/modules/saml/src/Controller/DiscoTest.php +++ b/tests/modules/saml/src/Controller/DiscoTest.php @@ -7,8 +7,9 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; -use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Module\saml\Controller; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; /** * Set of tests for the controllers in the "saml" module. @@ -41,27 +42,29 @@ protected function setUp(): void /** - * Test that accessing the disco-endpoint leads to a RunnableResponse + * Test that accessing the disco-endpoint leads to a RedirectResponse * * @return void */ public function testDisco(): void { - $params = [ - 'entityID' => 'urn:entity:phpunit', - 'return' => '/something', - 'isPassive' => 'true', - 'IdPentityID' => 'some:idp:phpunit', - 'returnIDParam' => 'someParam', - 'IDPList' => 'a,b,c', - ]; - - $_GET = array_merge($_GET, $params); - $_SERVER['REQUEST_URI'] = '/disco'; + $request = Request::create( + '/disco', + 'GET', + [ + 'entityID' => 'urn:entity:phpunit', + 'return' => '/something', + 'isPassive' => 'true', + 'IdPentityID' => 'some:idp:phpunit', + 'returnIDParam' => 'someParam', + 'IDPList' => ['a', 'b', 'c'], + ], + ); $c = new Controller\Disco($this->config); - $result = $c->disco(); - $this->assertInstanceOf(RunnableResponse::class, $result); + $result = $c->disco($request); + $this->assertInstanceOf(RedirectResponse::class, $result); + $this->assertTrue($result->isRedirection); } } diff --git a/tests/modules/saml/src/Controller/MetadataTest.php b/tests/modules/saml/src/Controller/MetadataTest.php index a9be21e58d..928236766a 100644 --- a/tests/modules/saml/src/Controller/MetadataTest.php +++ b/tests/modules/saml/src/Controller/MetadataTest.php @@ -9,7 +9,6 @@ use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; use SimpleSAML\Error; -use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Metadata\MetaDataStorageHandler; use SimpleSAML\Module\saml\Controller; use SimpleSAML\Utils; @@ -38,7 +37,18 @@ protected function setUp(): void { parent::setUp(); - $this->mdh = new class () extends MetaDataStorageHandler { + $this->config = Configuration::loadFromArray( + [ + 'module.enable' => ['saml' => true], + 'enable.saml20-idp' => true, + 'admin.protectmetadata' => false, + ], + '[ARRAY]', + 'simplesaml' + ); + Configuration::setPreLoadedConfig($this->config, 'config.php'); + + $this->mdh = new class ($this->config) extends MetaDataStorageHandler { private const string XMLSEC = '../vendor/simplesamlphp/xml-security/resources'; public const string CERT_KEY = self::XMLSEC . '/certificates/selfsigned.simplesamlphp.org.key'; @@ -50,26 +60,27 @@ protected function setUp(): void private array $idps; - public function __construct() + public function __construct(Configuration $config) { + parent::__construct($config); + $this->idps = [ - 'urn:example:simplesaml:idp' => [ - 'name' => 'SimpleSAMLphp Hosted IDP', - 'descr' => 'The local IDP', - 'OrganizationDisplayName' => ['en' => 'My IDP', 'nl' => 'Mijn IDP'], - 'certificate' => self::CERT_PUBLIC, - 'privatekey' => self::CERT_KEY, - - ], - 'urn:example:simplesaml:another:idp' => [ - 'name' => 'SimpleSAMLphp Hosted Another IDP', - 'descr' => 'Different IDP', - 'OrganizationDisplayName' => ['en' => 'Other IDP', 'nl' => 'Andere IDP'], - 'certificate' => self::CERT_PUBLIC, - 'privatekey' => self::CERT_KEY, - - ], - ]; + 'urn:example:simplesaml:idp' => [ + 'name' => 'SimpleSAMLphp Hosted IDP', + 'descr' => 'The local IDP', + 'OrganizationDisplayName' => ['en' => 'My IDP', 'nl' => 'Mijn IDP'], + 'certificate' => self::CERT_PUBLIC, + 'privatekey' => self::CERT_KEY, + ], + + 'urn:example:simplesaml:another:idp' => [ + 'name' => 'SimpleSAMLphp Hosted Another IDP', + 'descr' => 'Different IDP', + 'OrganizationDisplayName' => ['en' => 'Other IDP', 'nl' => 'Andere IDP'], + 'certificate' => self::CERT_PUBLIC, + 'privatekey' => self::CERT_KEY, + ], + ]; } @@ -98,17 +109,6 @@ public function getMetaDataCurrentEntityID(string $set, string $type = 'entityid } }; - $this->config = Configuration::loadFromArray( - [ - 'module.enable' => ['saml' => true], - 'enable.saml20-idp' => true, - 'admin.protectmetadata' => false, - ], - '[ARRAY]', - 'simplesaml', - ); - Configuration::setPreLoadedConfig($this->config, 'config.php'); - Configuration::setPreLoadedConfig( Configuration::loadFromArray( [ @@ -141,6 +141,8 @@ public function requireAdmin(): ?Response #[DataProvider('provideMetadataAccess')] public function testMetadataAccess(bool $authenticated, bool $protected): void { + $_SERVER['REQUEST_METHOD'] = 'GET'; + $config = Configuration::loadFromArray( [ 'module.enable' => ['saml' => true], @@ -160,21 +162,11 @@ public function testMetadataAccess(bool $authenticated, bool $protected): void $c = new Controller\Metadata($config); $c->setMetadataStorageHandler($this->mdh); - if ($authenticated === true) { - // Bypass authentication - mock being authenticated - $c->setAuthUtils($this->authUtils); - } + // Bypass authentication - mock being authenticated + $c->setAuthUtils($this->authUtils); $result = $c->metadata($request); - - if ($protected && !$authenticated) { - $this->assertInstanceOf(RunnableResponse::class, $result); - /** @var callable $callable */ - $callable = $result->getCallable(); - $this->assertEquals("requireAdmin", $callable[1]); - } else { - $this->assertInstanceOf(Response::class, $result); - } + $this->assertInstanceOf(Response::class, $result); if ($protected === true) { $this->assertEquals('no-cache, private', $result->headers->get('cache-control')); diff --git a/tests/modules/saml/src/Controller/ProxyTest.php b/tests/modules/saml/src/Controller/ProxyTest.php index b60bd4afed..81f4c7fff3 100644 --- a/tests/modules/saml/src/Controller/ProxyTest.php +++ b/tests/modules/saml/src/Controller/ProxyTest.php @@ -9,11 +9,12 @@ use SimpleSAML\Auth; use SimpleSAML\Configuration; use SimpleSAML\Error; -use SimpleSAML\HTTP\RunnableResponse; +use SimpleSAML\Metadata\MetadataStorageHandler; use SimpleSAML\Module\saml\Controller; use SimpleSAML\Module\saml\Error\NoAvailableIDP; use SimpleSAML\XHTML\Template; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * Set of tests for the controllers in the "saml" module. @@ -36,7 +37,15 @@ protected function setUp(): void $this->config = Configuration::loadFromArray( [ + 'enable.saml20-idp' => true, 'module.enable' => ['saml' => true], + 'metadatadir' => dirname(__FILE__, 5) . '/src/SimpleSAML/Metadata/test-metadata/source1', + 'metadata.sources' => [ + [ + 'type' => 'flatfile', + 'directory' => dirname(__FILE__, 5) . '/src/SimpleSAML/Metadata/test-metadata/source1', + ], + ], ], '[ARRAY]', 'simplesaml', @@ -60,6 +69,18 @@ protected function setUp(): void } + /** + * Tear down after each test. + */ + protected function tearDown(): void + { + parent::tearDown(); + + $mdh = MetaDataStorageHandler::getMetadataHandler($this->config); + $mdh->clearInternalState(); + } + + /** * Test that accessing the invalidSession-endpoint without StateId leads to an exception * @@ -111,7 +132,7 @@ public static function loadState(string $id, string $stage, bool $allowMissing = /** * Test that accessing the invalidSession-endpoint with StateId and - * with pressing cancel results in a RunnableResponse + * with pressing cancel results in a Response * * @return void */ @@ -141,7 +162,7 @@ public static function loadState(string $id, string $stage, bool $allowMissing = /** * Test that accessing the invalidSession-endpoint with StateId and - * with pressing continue results in a RunnableResponse + * with pressing continue results in a Response * * @return void */ @@ -159,11 +180,12 @@ public static function loadState(string $id, string $stage, bool $allowMissing = { return [ 'saml:sp:AuthId' => 'phpunit', + 'core:IdP' => 'saml2:urn:x-simplesamlphp:some-idp', ]; } }); $result = $c->invalidSession($request); - $this->assertInstanceOf(RunnableResponse::class, $result); + $this->assertInstanceOf(Response::class, $result); } } diff --git a/tests/modules/saml/src/Controller/ServiceProviderTest.php b/tests/modules/saml/src/Controller/ServiceProviderTest.php index a9a826a956..fcc06f76a5 100644 --- a/tests/modules/saml/src/Controller/ServiceProviderTest.php +++ b/tests/modules/saml/src/Controller/ServiceProviderTest.php @@ -11,7 +11,7 @@ use SimpleSAML\Auth; use SimpleSAML\Configuration; use SimpleSAML\Error; -use SimpleSAML\HTTP\RunnableResponse; +use SimpleSAML\Metadata\MetadataStorageHandler; use SimpleSAML\Module\saml\Auth\Source; use SimpleSAML\Module\saml\Controller; use SimpleSAML\Session; @@ -61,12 +61,18 @@ protected function setUp(): void $_SERVER['REQUEST_METHOD'] = 'GET'; $this->httpUtils = new Utils\HTTP(); - $this->session = Session::getSessionFromRequest(); $this->config = Configuration::loadFromArray( [ 'module.enable' => ['saml' => true], 'admin.protectmetadata' => false, 'trusted.url.domains' => ['example.org'], + 'metadatadir' => dirname(__FILE__, 5) . '/src/SimpleSAML/Metadata/test-metadata/source1', + 'metadata.sources' => [ + [ + 'type' => 'flatfile', + 'directory' => dirname(__FILE__, 5) . '/src/SimpleSAML/Metadata/test-metadata/source1', + ], + ], ], '[ARRAY]', 'simplesaml', @@ -86,6 +92,7 @@ protected function setUp(): void ); Configuration::setPreLoadedConfig($this->authsources, 'authsources.php'); + $this->session = Session::getSessionFromRequest(); $this->authUtils = new class () extends Utils\Auth { public function requireAdmin(): ?Response { @@ -107,6 +114,18 @@ public function callHandleLogin( } + /** + * Tear down after each test. + */ + protected function tearDown(): void + { + parent::tearDown(); + + $mdh = MetaDataStorageHandler::getMetadataHandler($this->config); + $mdh->clearInternalState(); + } + + /** * Test that accessing the login-endpoint with a non-SP authsource leads to an exception * @@ -169,13 +188,14 @@ public function testLoginEmptyReturnTo(): void /** - * @TODO: This cannot be tested until we are PSR-7 compliant - * - * Test that accessing the login-endpoint with ReturnTo parameter leads to a RunnableResponse + * Test that accessing the login-endpoint with ReturnTo parameter leads to a Response * * @return void + */ public function testLogin(): void { + $_SERVER['REQUEST_METHOD'] = 'GET'; + $request = Request::create( '/sp/login/phpunit', 'GET', @@ -185,9 +205,8 @@ public function testLogin(): void $c = new Controller\ServiceProvider($this->config, $this->session); $response = $c->login($request, 'phpunit'); - $this->assertInstanceOf(RunnableResponse::class, $response); + $this->assertInstanceOf(Response::class, $response); } - */ /** @@ -537,7 +556,7 @@ public static function loadState(string $id, string $stage, bool $allowMissing = /** - * Test that accessing the discoResponse-endpoint with SP authsource in state results in a RunnableResponse + * Test that accessing the discoResponse-endpoint with SP authsource in state results in a Response * * @return void */ @@ -546,10 +565,27 @@ public function testWithSPAuthSource(): void $request = Request::create( '/discoResponse', 'GET', - ['AuthID' => 'abc123', 'idpentityid' => 'urn:idp:entity'], + ['AuthID' => 'abc123', 'idpentityid' => 'urn:x-simplesamlphp:other-idp'], ); - $c = new Controller\ServiceProvider($this->config, $this->session); + $config = Configuration::loadFromArray( + [ + 'module.enable' => ['saml' => true], + 'admin.protectmetadata' => false, + 'trusted.url.domains' => ['example.org'], + 'metadata.sources' => [ + [ + 'type' => 'flatfile', + 'directory' => dirname(__FILE__, 5) . '/src/SimpleSAML/Metadata/test-metadata/source1', + ], + ], + ], + '[ARRAY]', + 'simplesaml' + ); + Configuration::setPreLoadedConfig($config, 'config.php'); + + $c = new Controller\ServiceProvider($config, $this->session); $c->setAuthState(new class () extends Auth\State { public static function loadState(string $id, string $stage, bool $allowMissing = false): ?array { @@ -560,7 +596,7 @@ public static function loadState(string $id, string $stage, bool $allowMissing = }); $result = $c->discoResponse($request); - $this->assertInstanceOf(RunnableResponse::class, $result); + $this->assertInstanceOf(Response::class, $result); } @@ -585,12 +621,17 @@ public function testWrongAuthnContextClassRef(): void */ public function testACSWithUnknownSourceID(): void { + $request = Request::create( + '/assertionConsumerService', + 'GET' + ); + $c = new Controller\ServiceProvider($this->config, $this->session); $this->expectException(Error\Exception::class); $this->expectExceptionMessage("No authentication source with id 'something' found."); - $c->assertionConsumerService('something'); + $c->assertionConsumerService($request, 'something'); } @@ -601,14 +642,17 @@ public function testACSWithUnknownSourceID(): void */ public function testACSWithUnkownBinding(): void { - $_SERVER['REQUEST_METHOD'] = 'GET'; + $request = Request::create( + '/assertionConsumerService', + 'GET' + ); $c = new Controller\ServiceProvider($this->config, $this->session); $this->expectException(Error\Error::class); $this->expectExceptionMessage(Error\ErrorCodes::ACSPARAMS); - $c->assertionConsumerService('phpunit'); + $c->assertionConsumerService($request, 'phpunit'); } @@ -619,24 +663,22 @@ public function testACSWithUnkownBinding(): void */ public function testACSWithWrongMessage(): void { - $q = [ - 'SAMLRequest' => 'pVJNb9swDP0rhu6O7XjeGiEJkDYoGqDbgibboZdCkahEgEx5Ir11/36y02FdD7n0JPDjPT4+cU6q9Z1c9XzCB/jRA3H23HokORYWoo8ogyJHElULJFnL3erzvZxOStnFwEEHL15BLiMUEUR2AUW2WS/EUw2NrXRp7NWshEPVzJqm+TQzVV1DddC21rUy1tq6norsO0RKyIVIRAlO1MMGiRVySpVVk1fTvKr25ZVsGvnh46PI1mkbh4pH1Im5I1kUgEeHMKE+Wh0QnnmCvlBpf0B2emwunOkKcnj0kJM7Yj7oXf2VfhOQ+hbiDuJPp+Hbw/0/8uSIdf4tO7m28zC4U7TB9KnendKAIabzO82VpjFrwKrec06dyLYv/l47NEnNZWsP5yaSd/v9Nt9+3e3Fcj5wy9GquHyPxhZYGcXqjcR58XrA/HxLX5K0zXobvNO/s9sQW8WXlQ8ZZ3I7tkqOCsmlz0iWex9+3URQDAvBsQdRLM8j/7/Y5R8=', - 'RelayState' => 'https://profile.surfconext.nl/', - 'SAMLEncoding' => 'urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE', - ]; - - $_SERVER['REQUEST_METHOD'] = 'GET'; - $_SERVER['QUERY_STRING'] = http_build_query($q); - $_GET['SAMLRequest'] = $q['SAMLRequest']; - $_GET['RelayState'] = $q['RelayState']; - $_GET['SAMLEncoding'] = $q['SAMLEncoding']; + $request = Request::create( + '/assertionConsumerService', + 'GET', + [ + 'SAMLRequest' => 'pVJNb9swDP0rhu6O7XjeGiEJkDYoGqDbgibboZdCkahEgEx5Ir11/36y02FdD7n0JPDjPT4+cU6q9Z1c9XzCB/jRA3H23HokORYWoo8ogyJHElULJFnL3erzvZxOStnFwEEHL15BLiMUEUR2AUW2WS/EUw2NrXRp7NWshEPVzJqm+TQzVV1DddC21rUy1tq6norsO0RKyIVIRAlO1MMGiRVySpVVk1fTvKr25ZVsGvnh46PI1mkbh4pH1Im5I1kUgEeHMKE+Wh0QnnmCvlBpf0B2emwunOkKcnj0kJM7Yj7oXf2VfhOQ+hbiDuJPp+Hbw/0/8uSIdf4tO7m28zC4U7TB9KnendKAIabzO82VpjFrwKrec06dyLYv/l47NEnNZWsP5yaSd/v9Nt9+3e3Fcj5wy9GquHyPxhZYGcXqjcR58XrA/HxLX5K0zXobvNO/s9sQW8WXlQ8ZZ3I7tkqOCsmlz0iWex9+3URQDAvBsQdRLM8j/7/Y5R8=', + 'RelayState' => 'https://profile.surfconext.nl/', + 'SAMLEncoding' => 'urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE', + ], + ); $c = new Controller\ServiceProvider($this->config, $this->session); $this->expectException(Error\BadRequest::class); $this->expectExceptionMessage('Invalid message received at AssertionConsumerService endpoint.'); - $c->assertionConsumerService('phpunit'); + $c->assertionConsumerService($request, 'phpunit'); } @@ -647,13 +689,13 @@ public function testACSWithWrongMessage(): void */ public function testACSWithCorrectMessageUnknownEntity(): void { - $q = [ - 'SAMLResponse' => 'vVdbc6rIGn2fX2G5H1MJd0RrJ1UgKmDQIIiXl1NANzcRCA2i/vppNLpNJsnMnqo5T9qL/q7r62bxEznbJO/NIMqzFMHWfpukqHcCH9tVkfYyB0WolzpbiHql1zNF/blHP5C9vMjKzMuS9o3J9xYOQrAooyxtt1T5sd2fzqxplxVIhoIMCbkuIEnehSRJ0xRJdroU1fFc6HWA4Hiw3bJhgbDtYxu7wg4QqqCaotJJSwyRFHdP0fcUb1Fsj2V7pLBut2SIyih1ypNVWJY56hFEGW6iexSBh7y8Z4WHqiweUFX4XpJV4CFNiC1Mkiwl8gyVl57gaOnlv5U9tv9HdzsSTQ4GlCB0hqQ8xD84XYHmOzxFMkOh/fSz6UbvlGTxdAkN0yBK4UOJ0zrHzFK4L5ugTlWGMC0j75QsEYEc51E6wCmdn8Stq59ntszSKSv0ftXPAGzZTlLB71lAp909s/I8iFCbeDpHeO+0J164emN3j6JzD3EddV0/1MxDVgQETZIUsdSfTS+EW+c+OhHSsHWx+nuj22HoMgwA0KV53GHKFQTHcR2PZT0SQEHgfAggECB0/8Uw/HeMANzLKMBjVhWX0wO+KpskyC6B9wAUBT/aT3+0WhdzCNTUz07e+k6apThwEh1PwXVYhhloiUmQFVEZbr9sKUU2vu/h3rv3KDb9gbnFEX7FOKX4D729y7RAzj0KHerssHE3gz4sIGa6NZ+pj+0fv0ffqUyrcFLkZ8UWvV/+Xmow3cEkyyHAZ/qtwmakf8vhp537Sfw1RzkK8KT8mw6+de+Xk9NBfdou75YRhJU/UXO3XN7ZQjh2p/mwFsXHUwK3m0/AtfHn5YfRubJ8tuhXCeETSyl2wWg+X5aA3K4SL6ZhcjciFlLVCUcCKUOKMRcSuwfiRCIPSyXNd5bvktb+TkUvuwUi2CWnOgdt42XqgZ75x2dIB0p3bB61VyllUn4Z18mEu6P8TjGtzLkrBfOIOGp70Y6D9apEu1gJkklHFgVlM1TvFra/P2SvSubm0kE6slSaB/PHazk3+f/R1DSGh2t9S47syvgIXhf95pLym1MKn3RV7d8d+31xawZirUpioGrii7Z7jg00m7GRLpKjvvk6MlWXkY2BJBlzUR+S+/5R1KRgYkviyhI3nK7PxFoOVrJtGOqgBjZQtGRFh+QNrnyBjzFu2bY2crc2xoN6eMblQd0l18sJqUfScbWgkDqaJF5q1EroTXRrXutHldQtA/8OmEWDxSdsf8ViCegGqvvGyd9oUGtTSx4YusiORGo+6Eu6Yi9nh/VikoEbXNp/jvdDXZlTtjnbcgnGV7q0OuHiXn8BI/sIZDXw6GHp9qV4vdRIXR35H/sn4v5hdxNR7kuRMZYCo78fr4p0IUzqVzCrZ7WjVJjGZ74bGENLc4GfieZzuIog7U6jTDtoNeXfeeIuj1HCmvYq3w0QVGG5VITnIN90xh3mZSNrzwIYBiwaxMMhHwfbuJgOTCbbE0FpgS4g7PpFUYndi+qNDhRybY3NB/ow4YAwmorHTNtMFuAl7tY2W+zYasdwunMQalUWDVHKWKWPayN0iWzqB3JgLCTJslTnpc7HOSZYgKpuHiez9Tw2SzeKcUNqzMGMjCV1pGDbQfDd/tdhmA+FemkNnnVxc+Yk1PvWpt4PZHF6nrvAkiib9LZ27CjGDe59gWcYn9jzzbpaL439SBYXZ1y3ZGaWeIxxUJVJ6C7qYEXbB6B+PAf1qdZBbQx1UZdEX6hlY6WNs7Ua7ryJaAyGkiHikR6IY2Gws+bkc6BoyKzwhTeF22CW5LnuayKcbqtqPQlNfUUbYbUdTte5I7rCZKjW8/HcPhy01Mw6G35TKv2x2mWRgbodnmbp0JLl1aBeaHJXCUUkvk4zmpo7QrC2GIGotzwNmXFQjJe7NIlFd/yylJeazjqbY+fAK3y926kji/dJn4y0hfLKsHFdn2+Qj5fCFTxfG8TthfLuxnkTCGblxtAr31YTrJ5UuTXEbwCn/F5WNUgE7v3T1l7ZvDgiLCDaT6TDsb7LdEm+i2Uiw3DAYSDLkKxP+RzPOMDlXZ73+TdZcQ75Ppt+lvpR47fRY+fXz/fJeNueC50CFu2vHTUNaU2ycppOC9EvYfEX5dQ9y+gZ9KK8qeX/LKIvyvSz5D88eqsS7wBR8xg1hUkQkwE/04MdXNU/qPwihSsQNW9cnH1ZRN45/LsnT7/Zlw9K8urmw/pdQOJDhdcUyjBtlDvcYoZap+VXSpjucRyu3MSyH3v4sgE03WOZHi382qqmAO4xZZwLuC5Pczzrk5zHeRTPAMahXExcx6V8yDGMA7sQeKB9mx5OusSy+hOon+BvQixpnr79bPR6XrMPwy/4p84KcG3UJ65+Rbno9zRoVo1YO1yZwpIxxaL+wceHFj6k2Y3Hz8w+CfgOuzJwRS/fT9fPq8vwP/0J', - ]; - - $_SERVER['REQUEST_METHOD'] = 'GET'; - $_SERVER['QUERY_STRING'] = http_build_query($q); - $_GET['SAMLResponse'] = $q['SAMLResponse']; + $request = Request::create( + '/assertionConsumerService', + 'GET', + [ + 'SAMLResponse' => 'vVdbc6rIGn2fX2G5H1MJd0RrJ1UgKmDQIIiXl1NANzcRCA2i/vppNLpNJsnMnqo5T9qL/q7r62bxEznbJO/NIMqzFMHWfpukqHcCH9tVkfYyB0WolzpbiHql1zNF/blHP5C9vMjKzMuS9o3J9xYOQrAooyxtt1T5sd2fzqxplxVIhoIMCbkuIEnehSRJ0xRJdroU1fFc6HWA4Hiw3bJhgbDtYxu7wg4QqqCaotJJSwyRFHdP0fcUb1Fsj2V7pLBut2SIyih1ypNVWJY56hFEGW6iexSBh7y8Z4WHqiweUFX4XpJV4CFNiC1Mkiwl8gyVl57gaOnlv5U9tv9HdzsSTQ4GlCB0hqQ8xD84XYHmOzxFMkOh/fSz6UbvlGTxdAkN0yBK4UOJ0zrHzFK4L5ugTlWGMC0j75QsEYEc51E6wCmdn8Stq59ntszSKSv0ftXPAGzZTlLB71lAp909s/I8iFCbeDpHeO+0J164emN3j6JzD3EddV0/1MxDVgQETZIUsdSfTS+EW+c+OhHSsHWx+nuj22HoMgwA0KV53GHKFQTHcR2PZT0SQEHgfAggECB0/8Uw/HeMANzLKMBjVhWX0wO+KpskyC6B9wAUBT/aT3+0WhdzCNTUz07e+k6apThwEh1PwXVYhhloiUmQFVEZbr9sKUU2vu/h3rv3KDb9gbnFEX7FOKX4D729y7RAzj0KHerssHE3gz4sIGa6NZ+pj+0fv0ffqUyrcFLkZ8UWvV/+Xmow3cEkyyHAZ/qtwmakf8vhp537Sfw1RzkK8KT8mw6+de+Xk9NBfdou75YRhJU/UXO3XN7ZQjh2p/mwFsXHUwK3m0/AtfHn5YfRubJ8tuhXCeETSyl2wWg+X5aA3K4SL6ZhcjciFlLVCUcCKUOKMRcSuwfiRCIPSyXNd5bvktb+TkUvuwUi2CWnOgdt42XqgZ75x2dIB0p3bB61VyllUn4Z18mEu6P8TjGtzLkrBfOIOGp70Y6D9apEu1gJkklHFgVlM1TvFra/P2SvSubm0kE6slSaB/PHazk3+f/R1DSGh2t9S47syvgIXhf95pLym1MKn3RV7d8d+31xawZirUpioGrii7Z7jg00m7GRLpKjvvk6MlWXkY2BJBlzUR+S+/5R1KRgYkviyhI3nK7PxFoOVrJtGOqgBjZQtGRFh+QNrnyBjzFu2bY2crc2xoN6eMblQd0l18sJqUfScbWgkDqaJF5q1EroTXRrXutHldQtA/8OmEWDxSdsf8ViCegGqvvGyd9oUGtTSx4YusiORGo+6Eu6Yi9nh/VikoEbXNp/jvdDXZlTtjnbcgnGV7q0OuHiXn8BI/sIZDXw6GHp9qV4vdRIXR35H/sn4v5hdxNR7kuRMZYCo78fr4p0IUzqVzCrZ7WjVJjGZ74bGENLc4GfieZzuIog7U6jTDtoNeXfeeIuj1HCmvYq3w0QVGG5VITnIN90xh3mZSNrzwIYBiwaxMMhHwfbuJgOTCbbE0FpgS4g7PpFUYndi+qNDhRybY3NB/ow4YAwmorHTNtMFuAl7tY2W+zYasdwunMQalUWDVHKWKWPayN0iWzqB3JgLCTJslTnpc7HOSZYgKpuHiez9Tw2SzeKcUNqzMGMjCV1pGDbQfDd/tdhmA+FemkNnnVxc+Yk1PvWpt4PZHF6nrvAkiib9LZ27CjGDe59gWcYn9jzzbpaL439SBYXZ1y3ZGaWeIxxUJVJ6C7qYEXbB6B+PAf1qdZBbQx1UZdEX6hlY6WNs7Ua7ryJaAyGkiHikR6IY2Gws+bkc6BoyKzwhTeF22CW5LnuayKcbqtqPQlNfUUbYbUdTte5I7rCZKjW8/HcPhy01Mw6G35TKv2x2mWRgbodnmbp0JLl1aBeaHJXCUUkvk4zmpo7QrC2GIGotzwNmXFQjJe7NIlFd/yylJeazjqbY+fAK3y926kji/dJn4y0hfLKsHFdn2+Qj5fCFTxfG8TthfLuxnkTCGblxtAr31YTrJ5UuTXEbwCn/F5WNUgE7v3T1l7ZvDgiLCDaT6TDsb7LdEm+i2Uiw3DAYSDLkKxP+RzPOMDlXZ73+TdZcQ75Ppt+lvpR47fRY+fXz/fJeNueC50CFu2vHTUNaU2ycppOC9EvYfEX5dQ9y+gZ9KK8qeX/LKIvyvSz5D88eqsS7wBR8xg1hUkQkwE/04MdXNU/qPwihSsQNW9cnH1ZRN45/LsnT7/Zlw9K8urmw/pdQOJDhdcUyjBtlDvcYoZap+VXSpjucRyu3MSyH3v4sgE03WOZHi382qqmAO4xZZwLuC5Pczzrk5zHeRTPAMahXExcx6V8yDGMA7sQeKB9mx5OusSy+hOon+BvQixpnr79bPR6XrMPwy/4p84KcG3UJ65+Rbno9zRoVo1YO1yZwpIxxaL+wceHFj6k2Y3Hz8w+CfgOuzJwRS/fT9fPq8vwP/0J', + ], + ); $c = new Controller\ServiceProvider($this->config, $this->session); @@ -664,7 +706,7 @@ public function testACSWithCorrectMessageUnknownEntity(): void ]; $this->expectExceptionMessage(json_encode($exceptionMessage)); - $c->assertionConsumerService('phpunit'); + $c->assertionConsumerService($request, 'phpunit'); } @@ -675,12 +717,17 @@ public function testACSWithCorrectMessageUnknownEntity(): void */ public function testSLOWithUnknownSourceID(): void { + $request = Request::create( + '/assertionConsumerService', + 'GET' + ); + $c = new Controller\ServiceProvider($this->config, $this->session); $this->expectException(Error\Exception::class); $this->expectExceptionMessage("No authentication source with id 'something' found."); - $c->singleLogoutService('something'); + $c->singleLogoutService($request, 'something'); } @@ -691,16 +738,17 @@ public function testSLOWithUnknownSourceID(): void */ public function testSLOWithUnkownBinding(): void { - $_SERVER['REQUEST_METHOD'] = 'PUT'; - $_SERVER['QUERY_STRING'] = ''; - unset($_GET['SAMLResponse']); + $request = Request::create( + '/assertionConsumerService', + 'PUT' + ); $c = new Controller\ServiceProvider($this->config, $this->session); $this->expectException(Error\Error::class); $this->expectExceptionMessage(Error\ErrorCodes::SLOSERVICEPARAMS); - $c->singleLogoutService('phpunit'); + $c->singleLogoutService($request, 'phpunit'); } @@ -735,17 +783,15 @@ public function testSLOWithCorrectMessageUnknownEntity(): void SomeSessionIndex2 XML; - $q = [ - 'SAMLRequest' => base64_encode(gzdeflate($x)), - 'RelayState' => 'https://profile.surfconext.nl/', - 'SAMLEncoding' => 'urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE', - ]; - - $_SERVER['REQUEST_METHOD'] = 'GET'; - $_SERVER['QUERY_STRING'] = http_build_query($q); - $_GET['SAMLRequest'] = $q['SAMLRequest']; - $_GET['RelayState'] = $q['RelayState']; - $_GET['SAMLEncoding'] = $q['SAMLEncoding']; + $request = Request::create( + '/assertionConsumerService', + 'GET', + [ + 'SAMLRequest' => base64_encode(gzdeflate($x)), + 'RelayState' => 'https://profile.surfconext.nl/', + 'SAMLEncoding' => 'urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE', + ], + ); $c = new Controller\ServiceProvider($this->config, $this->session); @@ -756,7 +802,7 @@ public function testSLOWithCorrectMessageUnknownEntity(): void ]; $this->expectExceptionMessage(json_encode($exceptionMessage)); - $c->singleLogoutService('phpunit'); + $c->singleLogoutService($request, 'phpunit'); } @@ -784,21 +830,11 @@ public function testMetadataAccess(bool $authenticated, bool $protected): void $c = new Controller\ServiceProvider($config, $this->session); - if ($authenticated === true || $protected === false) { - // Bypass authentication - mock being authenticated - $c->setAuthUtils($this->authUtils); - } + // Bypass authentication - mock being authenticated + $c->setAuthUtils($this->authUtils); $result = $c->metadata($request, 'phpunit'); - - if ($protected && !$authenticated) { - $this->assertInstanceOf(RunnableResponse::class, $result); - /** @var callable $callable */ - $callable = $result->getCallable(); - $this->assertEquals("requireAdmin", $callable[1]); - } else { - $this->assertInstanceOf(Response::class, $result); - } + $this->assertInstanceOf(Response::class, $result); if ($protected === true) { $this->assertEquals('no-cache, private', $result->headers->get('cache-control')); diff --git a/tests/modules/saml/src/IdP/SAML2Test.php b/tests/modules/saml/src/IdP/SAML2Test.php index bfbb54f4bf..450337ea0c 100644 --- a/tests/modules/saml/src/IdP/SAML2Test.php +++ b/tests/modules/saml/src/IdP/SAML2Test.php @@ -14,6 +14,8 @@ use SimpleSAML\TestUtils\ClearStateTestCase; use SimpleSAML\XML\Chunk; use SimpleSAML\XML\DOMDocumentFactory; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** */ @@ -60,7 +62,7 @@ public function testIdPInitiatedLoginMinimumParams(): void $this->assertStringStartsWith( // phpcs:ignore Generic.Files.LineLength.TooLong - 'http://idp.examlple.com/module/saml/idp/singleSignOnService?spentityid=https%3A%2F%2Fsome-sp-entity-id&cookie', + 'http://idp.example.com/module/saml/idp/singleSignOnService?spentityid=https%3A%2F%2Fsome-sp-entity-id&cookie', $state['\SimpleSAML\Auth\State.restartURL'], ); unset($state['saml:AuthnRequestReceivedAt']); // timestamp can't be tested in equality assertion @@ -91,7 +93,7 @@ public function testIdPInitiatedLoginOptionalParams(): void //currently only spentityid and relay state are used in the restart url. $this->assertStringStartsWith( - 'http://idp.examlple.com/module/saml/idp/singleSignOnService?' + 'http://idp.example.com/module/saml/idp/singleSignOnService?' . 'spentityid=https%3A%2F%2Fsome-sp-entity-id&RelayState=http%3A%2F%2Frelay&cookieTime', $state['\SimpleSAML\Auth\State.restartURL'], ); @@ -121,7 +123,7 @@ public function testIdPInitShibCompatyMinimumParams(): void $this->assertStringStartsWith( // phpcs:ignore Generic.Files.LineLength.TooLong - 'http://idp.examlple.com/module/saml/idp/singleSignOnService?spentityid=https%3A%2F%2Fsome-sp-entity-id&cookie', + 'http://idp.example.com/module/saml/idp/singleSignOnService?spentityid=https%3A%2F%2Fsome-sp-entity-id&cookie', $state['\SimpleSAML\Auth\State.restartURL'], ); unset($state['saml:AuthnRequestReceivedAt']); // timestamp can't be tested in equality assertion @@ -150,7 +152,7 @@ public function testIdPInitShibCompatOptionalParams(): void //currently only spentityid and relay state are used in the restart url. $this->assertStringStartsWith( - 'http://idp.examlple.com/module/saml/idp/singleSignOnService?' + 'http://idp.example.com/module/saml/idp/singleSignOnService?' . 'spentityid=https%3A%2F%2Fsome-sp-entity-id&RelayState=http%3A%2F%2Frelay&cookieTime', $state['\SimpleSAML\Auth\State.restartURL'], ); @@ -203,17 +205,10 @@ private function idpInitiatedHelper(array $queryParams): array 'metadata.sources' => [ ["type" => "xml", 'xml' => $spMetadataXml], ], + 'enable.saml20-idp' => true, ], '', 'simplesaml'); - // Since we aren't really running on a webserver some of the url calculations done, such as for restart url - // won't line up perfectly - $_REQUEST = $_REQUEST + $queryParams; - $_SERVER['HTTP_HOST'] = 'idp.examlple.com'; - $_SERVER['REQUEST_URI'] = '/module/saml/idp/singleSignOnService?' . http_build_query($queryParams); - - $state = []; - $idpStub->expects($this->once()) ->method('handleAuthenticationRequest') ->with($this->callback( @@ -225,10 +220,16 @@ function ($arg) use (&$state) { $state = $arg; return true; }, - )); + )) + ->willReturn(new Response('', 200, ['Host' => 'idp.example.com'])); + + $request = Request::create('/singleSingOnService', 'GET', $queryParams, [], [], ['Host' => 'idp.example.com']); + $_REQUEST = $_REQUEST + $queryParams; + $_SERVER['HTTP_HOST'] = 'idp.example.com'; + $_SERVER['REQUEST_URI'] = '/module.php/saml/idp/singleSignOnService?' . http_build_query($queryParams); /** @psalm-suppress InvalidArgument */ - SAML2::receiveAuthnRequest($idpStub); + SAML2::receiveAuthnRequest($request, $idpStub); return $state; } @@ -246,7 +247,7 @@ function ($arg) use (&$state) { */ private function idpMetadataHandlerHelper(array $metadata, array $extraconfig = []): array { - Configuration::loadFromArray([ + $config = Configuration::loadFromArray([ 'metadata.sources' => [ ["type" => "serialize", "directory" => "/tmp"], ], @@ -257,7 +258,7 @@ private function idpMetadataHandlerHelper(array $metadata, array $extraconfig = $metadata['certificate'] = self::CERT_PUBLIC; $metadata['privatekey'] = self::CERT_KEY; - $metaHandler->saveMetadata($metadata['entityid'], 'saml20-idp-hosted', $metadata); + $metaHandler->saveMetadata($config, $metadata['entityid'], 'saml20-idp-hosted', $metadata); $_SERVER['REQUEST_URI'] = '/dummy'; diff --git a/tests/src/SimpleSAML/ConfigurationTest.php b/tests/src/SimpleSAML/ConfigurationTest.php index ad01cf7e81..909b08e199 100644 --- a/tests/src/SimpleSAML/ConfigurationTest.php +++ b/tests/src/SimpleSAML/ConfigurationTest.php @@ -6,10 +6,10 @@ use Exception; use PHPUnit\Framework\Attributes\CoversClass; -use SAML2\Constants; use SimpleSAML\Assert\AssertionFailedException; use SimpleSAML\Configuration; use SimpleSAML\Error; +use SimpleSAML\SAML2\Constants; use SimpleSAML\TestUtils\ClearStateTestCase; /** diff --git a/tests/src/SimpleSAML/Metadata/MetaDataStorageHandlerTest.php b/tests/src/SimpleSAML/Metadata/MetaDataStorageHandlerTest.php index 26aaa9880e..5624d208dc 100644 --- a/tests/src/SimpleSAML/Metadata/MetaDataStorageHandlerTest.php +++ b/tests/src/SimpleSAML/Metadata/MetaDataStorageHandlerTest.php @@ -22,8 +22,8 @@ protected function getHandler(?array $config = null): MetaDataStorageHandler ['type' => 'serialize', 'directory' => __DIR__ . '/test-metadata/source2'], ], ]; - Configuration::loadFromArray($config, '', 'simplesaml'); - return MetaDataStorageHandler::getMetadataHandler(); + $config = Configuration::loadFromArray($config, '', 'simplesaml'); + return MetaDataStorageHandler::getMetadataHandler($config); } @@ -103,7 +103,7 @@ public function testLoadMetadataSet(): void */ public function testLoadMetadataSetEmpty(): void { - $entities = $this->getHandler()->getList('saml20-idp-remote'); + $entities = $this->getHandler()->getList('something stupid'); $this->assertCount(0, $entities); } @@ -196,16 +196,20 @@ public function testSampleEntityIdException(): void public function testCanHaveMultipleHostedIdps(): void { - $config = [ + $this->handler->clearInternalState(); + + $c = [ 'metadata.sources' => [ - ['type' => 'flatfile', 'directory' => __DIR__ . '/test-metadata/source3'], + ['type' => 'flatfile', 'directory' => __DIR__ . '/test-metadata/source1'], ], ]; - $handler = $this->getHandler($config); - $idps = $handler->getList('saml20-idp-hosted'); + $config = Configuration::loadFromArray($c, '', 'simplesaml'); + $this->handler = MetaDataStorageHandler::getMetadataHandler($config); - $this->assertCount(2, $idps); + $this->expectException(AssertionFailedException::class); + $this->expectExceptionMessageMatches('/Please set a valid and unique entityID/'); + $this->handler->getMetaDataCurrent('saml20-idp-hosted'); } diff --git a/tests/src/SimpleSAML/Metadata/SAMLParserTest.php b/tests/src/SimpleSAML/Metadata/SAMLParserTest.php index 58d1c38730..f74d158198 100644 --- a/tests/src/SimpleSAML/Metadata/SAMLParserTest.php +++ b/tests/src/SimpleSAML/Metadata/SAMLParserTest.php @@ -6,8 +6,8 @@ use DOMDocument; use PHPUnit\Framework\Attributes\CoversClass; -use SAML2\Constants; use SimpleSAML\Metadata\SAMLParser; +use SimpleSAML\SAML2\Constants; use SimpleSAML\Test\SigningTestCase; use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XML\Signer; diff --git a/tests/src/SimpleSAML/Metadata/test-metadata/source1/saml20-idp-hosted.php b/tests/src/SimpleSAML/Metadata/test-metadata/source1/saml20-idp-hosted.php index e23f83f21d..cfdf5df96d 100644 --- a/tests/src/SimpleSAML/Metadata/test-metadata/source1/saml20-idp-hosted.php +++ b/tests/src/SimpleSAML/Metadata/test-metadata/source1/saml20-idp-hosted.php @@ -9,5 +9,5 @@ */ $metadata['urn:x-simplesamlphp:example-idp'] = [ - 'auth' => 'example-userpass', + 'auth' => 'phpunit', ]; diff --git a/tests/src/SimpleSAML/Metadata/test-metadata/source1/saml20-idp-remote.php b/tests/src/SimpleSAML/Metadata/test-metadata/source1/saml20-idp-remote.php new file mode 100644 index 0000000000..88973b32f3 --- /dev/null +++ b/tests/src/SimpleSAML/Metadata/test-metadata/source1/saml20-idp-remote.php @@ -0,0 +1,10 @@ + 'https://example.org/module.php/saml/idp/singleSignOnService', +]; diff --git a/tests/src/SimpleSAML/Utils/Config/MetadataTest.php b/tests/src/SimpleSAML/Utils/Config/MetadataTest.php index f9084a81ae..3d25a90112 100644 --- a/tests/src/SimpleSAML/Utils/Config/MetadataTest.php +++ b/tests/src/SimpleSAML/Utils/Config/MetadataTest.php @@ -7,8 +7,8 @@ use InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; -use SAML2\Constants; -use SAML2\XML\md\ContactPerson; +use SimpleSAML\SAML2\Constants; +use SimpleSAML\SAML2\XML\md\ContactPerson; use SimpleSAML\Utils\Config\Metadata; use TypeError; From 886cfab06893f0bfbafb7d83d80fedf83931c6a0 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Mon, 22 Jun 2026 23:18:45 +0200 Subject: [PATCH 6/8] Fix lock-file: it was built with a newer version of PHP --- composer.lock | 950 +++++++++++++++++++++----------------------------- 1 file changed, 402 insertions(+), 548 deletions(-) diff --git a/composer.lock b/composer.lock index 27abfc8ae8..20f9fa12d8 100644 --- a/composer.lock +++ b/composer.lock @@ -216,16 +216,16 @@ }, { "name": "gettext/translator", - "version": "v1.2.1", + "version": "v1.2.3", "source": { "type": "git", "url": "https://github.com/php-gettext/Translator.git", - "reference": "8ae0ac79053bcb732a6c584cd86f7a82ef183161" + "reference": "e6d96e03d8c1afb3ef497799921ad0adfc9f4f96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-gettext/Translator/zipball/8ae0ac79053bcb732a6c584cd86f7a82ef183161", - "reference": "8ae0ac79053bcb732a6c584cd86f7a82ef183161", + "url": "https://api.github.com/repos/php-gettext/Translator/zipball/e6d96e03d8c1afb3ef497799921ad0adfc9f4f96", + "reference": "e6d96e03d8c1afb3ef497799921ad0adfc9f4f96", "shasum": "" }, "require": { @@ -270,7 +270,7 @@ "support": { "email": "oom@oscarotero.com", "issues": "https://github.com/php-gettext/Translator/issues", - "source": "https://github.com/php-gettext/Translator/tree/v1.2.1" + "source": "https://github.com/php-gettext/Translator/tree/v1.2.3" }, "funding": [ { @@ -286,20 +286,20 @@ "type": "patreon" } ], - "time": "2025-01-09T09:20:22+00:00" + "time": "2026-06-18T22:49:04+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.12.0", + "version": "2.12.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "9b38012e7b54f594707e6db52c684dc0a74b3a43" + "reference": "172ef2f4e9824c1e058b7f30be8ae25a02c0f2b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/9b38012e7b54f594707e6db52c684dc0a74b3a43", - "reference": "9b38012e7b54f594707e6db52c684dc0a74b3a43", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/172ef2f4e9824c1e058b7f30be8ae25a02c0f2b7", + "reference": "172ef2f4e9824c1e058b7f30be8ae25a02c0f2b7", "shasum": "" }, "require": { @@ -389,7 +389,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.12.0" + "source": "https://github.com/guzzle/psr7/tree/2.12.1" }, "funding": [ { @@ -405,7 +405,7 @@ "type": "tidelift" } ], - "time": "2026-06-16T21:50:11+00:00" + "time": "2026-06-18T09:49:37+00:00" }, { "name": "nikic/php-parser", @@ -2100,32 +2100,33 @@ }, { "name": "symfony/error-handler", - "version": "v8.1.0", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "d8aeb1abd3fef84795567850d3a567bdb5945ee5" + "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/d8aeb1abd3fef84795567850d3a567bdb5945ee5", - "reference": "d8aeb1abd3fef84795567850d3a567bdb5945ee5", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa", + "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa", "shasum": "" }, "require": { - "php": ">=8.4.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/polyfill-php85": "^1.32", - "symfony/var-dumper": "^7.4|^8.0" + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "conflict": { - "symfony/deprecation-contracts": "<2.5" + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" }, "require-dev": { - "symfony/console": "^7.4|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^7.4|^8.0", - "symfony/serializer": "^7.4|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", "symfony/webpack-encore-bundle": "^1.0|^2.0" }, "bin": [ @@ -2157,7 +2158,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v8.1.0" + "source": "https://github.com/symfony/error-handler/tree/v7.4.8" }, "funding": [ { @@ -2177,7 +2178,7 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/event-dispatcher", @@ -4099,34 +4100,35 @@ }, { "name": "symfony/string", - "version": "v8.1.0", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9" + "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/afd5944f4005862d961efb85c8bbd5c523c4e3c9", - "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "url": "https://api.github.com/repos/symfony/string/zipball/961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", + "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", "shasum": "" }, "require": { - "php": ">=8.4.1", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-intl-grapheme": "^1.33", - "symfony/polyfill-intl-normalizer": "^1.0", - "symfony/polyfill-mbstring": "^1.0" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.4|^8.0", - "symfony/http-client": "^7.4|^8.0", - "symfony/intl": "^7.4|^8.0", + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^7.4|^8.0" + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -4165,7 +4167,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.1.0" + "source": "https://github.com/symfony/string/tree/v7.4.13" }, "funding": [ { @@ -4185,7 +4187,7 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-05-23T15:23:29+00:00" }, { "name": "symfony/translation", @@ -4486,31 +4488,31 @@ }, { "name": "symfony/var-dumper", - "version": "v8.1.0", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "c2c4df1d21477cc21c9f6dc1b14d07c3abc4963e" + "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c2c4df1d21477cc21c9f6dc1b14d07c3abc4963e", - "reference": "c2c4df1d21477cc21c9f6dc1b14d07c3abc4963e", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9510c3966f749a1d1ff0059e1eabef6cc621e7fd", + "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd", "shasum": "" }, "require": { - "php": ">=8.4.1", - "symfony/polyfill-mbstring": "^1.0" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/console": "<7.4", - "symfony/error-handler": "<7.4" + "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", - "symfony/process": "^7.4|^8.0", - "symfony/uid": "^7.4|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", "twig/twig": "^3.12" }, "bin": [ @@ -4549,7 +4551,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v8.1.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.8" }, "funding": [ { @@ -4569,7 +4571,7 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-03-30T13:44:50+00:00" }, { "name": "symfony/var-exporter", @@ -5971,35 +5973,33 @@ }, { "name": "phpunit/php-code-coverage", - "version": "14.2.2", + "version": "12.5.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "10d7da3628a99289cdf4c662dd7f0d73f1baec83" + "reference": "186dab580576598076de6818596d12b61801880e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/10d7da3628a99289cdf4c662dd7f0d73f1baec83", - "reference": "10d7da3628a99289cdf4c662dd7f0d73f1baec83", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/186dab580576598076de6818596d12b61801880e", + "reference": "186dab580576598076de6818596d12b61801880e", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", - "ext-mbstring": "*", "ext-xmlwriter": "*", "nikic/php-parser": "^5.7.0", - "php": ">=8.4", - "phpunit/php-text-template": "^6.0", - "sebastian/complexity": "^6.0", - "sebastian/environment": "^9.3.2", - "sebastian/git-state": "^1.0", - "sebastian/lines-of-code": "^5.0.1", - "sebastian/version": "^7.0", + "php": ">=8.3", + "phpunit/php-text-template": "^5.0", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.1.2", + "sebastian/lines-of-code": "^4.0.1", + "sebastian/version": "^6.0", "theseer/tokenizer": "^2.0.1" }, "require-dev": { - "phpunit/phpunit": "^13.2.0" + "phpunit/phpunit": "^12.5.28" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -6008,7 +6008,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "14.2.x-dev" + "dev-main": "12.5.x-dev" } }, "autoload": { @@ -6037,7 +6037,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/14.2.2" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.7" }, "funding": [ { @@ -6057,32 +6057,32 @@ "type": "tidelift" } ], - "time": "2026-06-08T11:50:38+00:00" + "time": "2026-06-01T13:24:19+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "7.0.0", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50" + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", - "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -6110,7 +6110,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/7.0.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.1" }, "funding": [ { @@ -6130,28 +6130,28 @@ "type": "tidelift" } ], - "time": "2026-02-06T04:33:26+00:00" + "time": "2026-02-02T14:04:18+00:00" }, { "name": "phpunit/php-invoker", - "version": "7.0.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88" + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", - "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.3" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^12.0" }, "suggest": { "ext-pcntl": "*" @@ -6159,7 +6159,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -6186,52 +6186,40 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/7.0.0" + "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-invoker", - "type": "tidelift" } ], - "time": "2026-02-06T04:34:47+00:00" + "time": "2025-02-07T04:58:58+00:00" }, { "name": "phpunit/php-text-template", - "version": "6.0.0", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4" + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/a47af19f93f76aa3368303d752aa5272ca3299f4", - "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -6258,52 +6246,40 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/6.0.0" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template", - "type": "tidelift" } ], - "time": "2026-02-06T04:36:37+00:00" + "time": "2025-02-07T04:59:16+00:00" }, { "name": "phpunit/php-timer", - "version": "9.0.0", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6" + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a0e12065831f6ab0d83120dc61513eb8d9a966f6", - "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "9.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -6330,40 +6306,28 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/9.0.0" + "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-timer", - "type": "tidelift" } ], - "time": "2026-02-06T04:37:53+00:00" + "time": "2025-02-07T04:59:38+00:00" }, { "name": "phpunit/phpunit", - "version": "13.2.1", + "version": "12.5.30", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "60da0ff1e10a0f72ee18a24117ec3b613a346bba" + "reference": "900400a5b616d6fb306f9549f6da33ba615d3fbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/60da0ff1e10a0f72ee18a24117ec3b613a346bba", - "reference": "60da0ff1e10a0f72ee18a24117ec3b613a346bba", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/900400a5b616d6fb306f9549f6da33ba615d3fbb", + "reference": "900400a5b616d6fb306f9549f6da33ba615d3fbb", "shasum": "" }, "require": { @@ -6376,24 +6340,22 @@ "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", - "php": ">=8.4.1", - "phpunit/php-code-coverage": "^14.2.2", - "phpunit/php-file-iterator": "^7.0.0", - "phpunit/php-invoker": "^7.0.0", - "phpunit/php-text-template": "^6.0.0", - "phpunit/php-timer": "^9.0.0", - "sebastian/cli-parser": "^5.0.0", - "sebastian/comparator": "^8.3.0", - "sebastian/diff": "^9.0", - "sebastian/environment": "^9.3.2", - "sebastian/exporter": "^8.1.0", - "sebastian/file-filter": "^1.0", - "sebastian/git-state": "^1.0", - "sebastian/global-state": "^9.0.1", - "sebastian/object-enumerator": "^8.0.0", - "sebastian/recursion-context": "^8.0.0", - "sebastian/type": "^7.0.1", - "sebastian/version": "^7.0.0", + "php": ">=8.3", + "phpunit/php-code-coverage": "^12.5.7", + "phpunit/php-file-iterator": "^6.0.1", + "phpunit/php-invoker": "^6.0.0", + "phpunit/php-text-template": "^5.0.0", + "phpunit/php-timer": "^8.0.0", + "sebastian/cli-parser": "^4.2.1", + "sebastian/comparator": "^7.1.8", + "sebastian/diff": "^7.0.0", + "sebastian/environment": "^8.1.2", + "sebastian/exporter": "^7.0.3", + "sebastian/global-state": "^8.0.3", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/recursion-context": "^7.0.1", + "sebastian/type": "^6.0.4", + "sebastian/version": "^6.0.0", "staabm/side-effects-detector": "^1.0.5" }, "bin": [ @@ -6402,7 +6364,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "13.2-dev" + "dev-main": "12.5-dev" } }, "autoload": { @@ -6434,7 +6396,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/13.2.1" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.30" }, "funding": [ { @@ -6442,7 +6404,7 @@ "type": "other" } ], - "time": "2026-06-15T13:14:22+00:00" + "time": "2026-06-15T13:12:30+00:00" }, { "name": "predis/predis", @@ -6509,28 +6471,28 @@ }, { "name": "sebastian/cli-parser", - "version": "5.0.0", + "version": "4.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca" + "reference": "7d05781b13f7dec9043a629a21d086ed74582a15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/48a4654fa5e48c1c81214e9930048a572d4b23ca", - "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/7d05781b13f7dec9043a629a21d086ed74582a15", + "reference": "7d05781b13f7dec9043a629a21d086ed74582a15", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^12.5.25" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "4.2-dev" } }, "autoload": { @@ -6554,7 +6516,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/5.0.0" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.1" }, "funding": [ { @@ -6574,31 +6536,31 @@ "type": "tidelift" } ], - "time": "2026-02-06T04:39:44+00:00" + "time": "2026-05-17T05:29:34+00:00" }, { "name": "sebastian/comparator", - "version": "8.3.0", + "version": "7.1.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "c025fc7604afab3f195fab7cdaf72327331af241" + "reference": "7c65c1e79836812819705b473a90c12399542485" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/c025fc7604afab3f195fab7cdaf72327331af241", - "reference": "c025fc7604afab3f195fab7cdaf72327331af241", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/7c65c1e79836812819705b473a90c12399542485", + "reference": "7c65c1e79836812819705b473a90c12399542485", "shasum": "" }, "require": { "ext-dom": "*", "ext-mbstring": "*", - "php": ">=8.4", - "sebastian/diff": "^9.0", - "sebastian/exporter": "^8.1.0" + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0.3" }, "require-dev": { - "phpunit/phpunit": "^13.2" + "phpunit/phpunit": "^12.5.25" }, "suggest": { "ext-bcmath": "For comparing BcMath\\Number objects" @@ -6606,7 +6568,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "8.3-dev" + "dev-main": "7.1-dev" } }, "autoload": { @@ -6646,7 +6608,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/8.3.0" + "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.8" }, "funding": [ { @@ -6666,33 +6628,33 @@ "type": "tidelift" } ], - "time": "2026-06-05T03:06:45+00:00" + "time": "2026-05-21T04:45:25+00:00" }, { "name": "sebastian/complexity", - "version": "6.0.0", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "c5651c795c98093480df79350cb050813fc7a2f3" + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c5651c795c98093480df79350cb050813fc7a2f3", - "reference": "c5651c795c98093480df79350cb050813fc7a2f3", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", "shasum": "" }, "require": { "nikic/php-parser": "^5.0", - "php": ">=8.4" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -6716,53 +6678,41 @@ "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/6.0.0" + "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/complexity", - "type": "tidelift" } ], - "time": "2026-02-06T04:41:32+00:00" + "time": "2025-02-07T04:55:25+00:00" }, { "name": "sebastian/diff", - "version": "9.0.0", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "a3fb6a298a265ff487a91bbea46e03cd01dbb226" + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/a3fb6a298a265ff487a91bbea46e03cd01dbb226", - "reference": "a3fb6a298a265ff487a91bbea46e03cd01dbb226", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^13.2", - "symfony/process": "^7.4.13" + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "9.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -6795,47 +6745,35 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/9.0.0" + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/diff", - "type": "tidelift" } ], - "time": "2026-06-05T03:04:51+00:00" + "time": "2025-02-07T04:55:46+00:00" }, { "name": "sebastian/environment", - "version": "9.3.2", + "version": "8.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "6c9e487c9eb706a8d258102a1c0b0a3e53e86c2e" + "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6c9e487c9eb706a8d258102a1c0b0a3e53e86c2e", - "reference": "6c9e487c9eb706a8d258102a1c0b0a3e53e86c2e", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/9d32c685773823b1983e256ae4ecd48a10d6e439", + "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^13.1.11" + "phpunit/phpunit": "^12.5.26" }, "suggest": { "ext-posix": "*" @@ -6843,7 +6781,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "9.3-dev" + "dev-main": "8.1-dev" } }, "autoload": { @@ -6871,7 +6809,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/9.3.2" + "source": "https://github.com/sebastianbergmann/environment/tree/8.1.2" }, "funding": [ { @@ -6891,34 +6829,34 @@ "type": "tidelift" } ], - "time": "2026-05-25T13:41:38+00:00" + "time": "2026-05-25T13:40:20+00:00" }, { "name": "sebastian/exporter", - "version": "8.1.0", + "version": "7.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "c0d29a945f8cf82f300a05e69874508e307ca4c6" + "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c0d29a945f8cf82f300a05e69874508e307ca4c6", - "reference": "c0d29a945f8cf82f300a05e69874508e307ca4c6", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", + "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=8.4", - "sebastian/recursion-context": "^8.0" + "php": ">=8.3", + "sebastian/recursion-context": "^7.0.1" }, "require-dev": { - "phpunit/phpunit": "^13.1.10" + "phpunit/phpunit": "^12.5.25" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "8.1-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -6961,7 +6899,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/8.1.0" + "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.3" }, "funding": [ { @@ -6981,173 +6919,35 @@ "type": "tidelift" } ], - "time": "2026-05-21T11:50:56+00:00" - }, - { - "name": "sebastian/file-filter", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/file-filter.git", - "reference": "33a26f394330f6faa7684bb9cc73afb7727aae93" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/file-filter/zipball/33a26f394330f6faa7684bb9cc73afb7727aae93", - "reference": "33a26f394330f6faa7684bb9cc73afb7727aae93", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for filtering files", - "homepage": "https://github.com/sebastianbergmann/file-filter", - "support": { - "issues": "https://github.com/sebastianbergmann/file-filter/issues", - "security": "https://github.com/sebastianbergmann/file-filter/security/policy", - "source": "https://github.com/sebastianbergmann/file-filter/tree/1.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/file-filter", - "type": "tidelift" - } - ], - "time": "2026-04-22T07:20:04+00:00" - }, - { - "name": "sebastian/git-state", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/git-state.git", - "reference": "792a952e0eba55b6960a48aeceb9f371aad1f76b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/git-state/zipball/792a952e0eba55b6960a48aeceb9f371aad1f76b", - "reference": "792a952e0eba55b6960a48aeceb9f371aad1f76b", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for describing the state of a Git checkout", - "homepage": "https://github.com/sebastianbergmann/git-state", - "support": { - "issues": "https://github.com/sebastianbergmann/git-state/issues", - "security": "https://github.com/sebastianbergmann/git-state/security/policy", - "source": "https://github.com/sebastianbergmann/git-state/tree/1.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/git-state", - "type": "tidelift" - } - ], - "time": "2026-03-21T12:54:28+00:00" + "time": "2026-05-20T04:37:17+00:00" }, { "name": "sebastian/global-state", - "version": "9.0.1", + "version": "8.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "ba68ba79da690cf7eddefd3ce5b78b20b9ba9945" + "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ba68ba79da690cf7eddefd3ce5b78b20b9ba9945", - "reference": "ba68ba79da690cf7eddefd3ce5b78b20b9ba9945", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b164d3274d6537ab462591c5755f76a8f5b1aae9", + "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9", "shasum": "" }, "require": { - "php": ">=8.4", - "sebastian/object-reflector": "^6.0", - "sebastian/recursion-context": "^8.0" + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0.1" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^13.1.13" + "phpunit/phpunit": "^12.5.28" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "9.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -7173,7 +6973,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/9.0.1" + "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.3" }, "funding": [ { @@ -7193,33 +6993,33 @@ "type": "tidelift" } ], - "time": "2026-06-01T15:11:33+00:00" + "time": "2026-06-01T15:10:33+00:00" }, { "name": "sebastian/lines-of-code", - "version": "5.0.1", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "d2cff273a90c79b0eb590baa682d4b5c318bdbb7" + "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d2cff273a90c79b0eb590baa682d4b5c318bdbb7", - "reference": "d2cff273a90c79b0eb590baa682d4b5c318bdbb7", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d543b8ef219dcd8da262cbb958639a96bedba10e", + "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e", "shasum": "" }, "require": { "nikic/php-parser": "^5.7.0", - "php": ">=8.4" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^13.1.10" + "phpunit/phpunit": "^12.5.25" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -7243,7 +7043,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.1" }, "funding": [ { @@ -7263,34 +7063,34 @@ "type": "tidelift" } ], - "time": "2026-05-19T16:23:37+00:00" + "time": "2026-05-19T16:22:07+00:00" }, { "name": "sebastian/object-enumerator", - "version": "8.0.0", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5" + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", - "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", "shasum": "" }, "require": { - "php": ">=8.4", - "sebastian/object-reflector": "^6.0", - "sebastian/recursion-context": "^8.0" + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "8.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -7313,52 +7113,40 @@ "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/8.0.0" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/object-enumerator", - "type": "tidelift" } ], - "time": "2026-02-06T04:46:36+00:00" + "time": "2025-02-07T04:57:48+00:00" }, { "name": "sebastian/object-reflector", - "version": "6.0.0", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200" + "reference": "4bfa827c969c98be1e527abd576533293c634f6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", - "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -7381,52 +7169,40 @@ "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/6.0.0" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/object-reflector", - "type": "tidelift" } ], - "time": "2026-02-06T04:47:13+00:00" + "time": "2025-02-07T04:58:17+00:00" }, { "name": "sebastian/recursion-context", - "version": "8.0.0", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e" + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/74c5af21f6a5833e91767ca068c4d3dfec15317e", - "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "8.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -7457,7 +7233,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/8.0.0" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" }, "funding": [ { @@ -7477,32 +7253,32 @@ "type": "tidelift" } ], - "time": "2026-02-06T04:51:28+00:00" + "time": "2025-08-13T04:44:59+00:00" }, { "name": "sebastian/type", - "version": "7.0.1", + "version": "6.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fee0309275847fefd7636167085e379c1dbf6990" + "reference": "82ff822c2edc46724be9f7411d3163021f602773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fee0309275847fefd7636167085e379c1dbf6990", - "reference": "fee0309275847fefd7636167085e379c1dbf6990", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/82ff822c2edc46724be9f7411d3163021f602773", + "reference": "82ff822c2edc46724be9f7411d3163021f602773", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^13.1.10" + "phpunit/phpunit": "^12.5.25" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -7526,7 +7302,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/7.0.1" + "source": "https://github.com/sebastianbergmann/type/tree/6.0.4" }, "funding": [ { @@ -7546,29 +7322,29 @@ "type": "tidelift" } ], - "time": "2026-05-20T06:49:11+00:00" + "time": "2026-05-20T06:45:45+00:00" }, { "name": "sebastian/version", - "version": "7.0.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b" + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ad37a5552c8e2b88572249fdc19b6da7792e021b", - "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -7592,27 +7368,15 @@ "support": { "issues": "https://github.com/sebastianbergmann/version/issues", "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/7.0.0" + "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/version", - "type": "tidelift" } ], - "time": "2026-02-06T04:52:52+00:00" + "time": "2025-02-07T05:00:38+00:00" }, { "name": "simplesamlphp/simplesamlphp-test-framework", @@ -7953,27 +7717,107 @@ ], "time": "2026-05-29T05:06:50+00:00" }, + { + "name": "symfony/polyfill-php84", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T12:51:13+00:00" + }, { "name": "symfony/property-access", - "version": "v8.1.0", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "9261ef060f26cc7b728f67f141ba19b98a6209a9" + "reference": "b7dad9dae8b8a47ef7ecc76c8569e7d8c7d90cfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/9261ef060f26cc7b728f67f141ba19b98a6209a9", - "reference": "9261ef060f26cc7b728f67f141ba19b98a6209a9", + "url": "https://api.github.com/repos/symfony/property-access/zipball/b7dad9dae8b8a47ef7ecc76c8569e7d8c7d90cfc", + "reference": "b7dad9dae8b8a47ef7ecc76c8569e7d8c7d90cfc", "shasum": "" }, "require": { - "php": ">=8.4.1", - "symfony/property-info": "^7.4.4|^8.0.4" + "php": ">=8.2", + "symfony/property-info": "^6.4.32|~7.3.10|^7.4.4|^8.0.4" }, "require-dev": { - "symfony/cache": "^7.4|^8.0", - "symfony/var-exporter": "^7.4|^8.0" + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4.1|^7.0.1|^8.0" }, "type": "library", "autoload": { @@ -8012,7 +7856,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v8.1.0" + "source": "https://github.com/symfony/property-access/tree/v7.4.8" }, "funding": [ { @@ -8032,37 +7876,41 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/property-info", - "version": "v8.1.0", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "4721e8c56d0cd2378e0ef9a9899f810008b859f7" + "reference": "ac5e82528b986c4f7cfccbf7764b5d2e824d6175" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/4721e8c56d0cd2378e0ef9a9899f810008b859f7", - "reference": "4721e8c56d0cd2378e0ef9a9899f810008b859f7", + "url": "https://api.github.com/repos/symfony/property-info/zipball/ac5e82528b986c4f7cfccbf7764b5d2e824d6175", + "reference": "ac5e82528b986c4f7cfccbf7764b5d2e824d6175", "shasum": "" }, "require": { - "php": ">=8.4.1", - "symfony/string": "^7.4|^8.0", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0|^8.0", "symfony/type-info": "^7.4.7|^8.0.7" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2|>=7", - "phpdocumentor/type-resolver": "<1.5.1" + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/serializer": "<6.4" }, "require-dev": { "phpdocumentor/reflection-docblock": "^5.2|^6.0", "phpstan/phpdoc-parser": "^1.0|^2.0", - "symfony/cache": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", - "symfony/serializer": "^7.4|^8.0" + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -8098,7 +7946,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v8.1.0" + "source": "https://github.com/symfony/property-info/tree/v7.4.8" }, "funding": [ { @@ -8118,58 +7966,63 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/serializer", - "version": "v8.1.0", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "d101886195c5f772cf7033641fe9c40c3e3969e1" + "reference": "268c5aa6c4bd675eddd89348e7ecac292a843ddd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/d101886195c5f772cf7033641fe9c40c3e3969e1", - "reference": "d101886195c5f772cf7033641fe9c40c3e3969e1", + "url": "https://api.github.com/repos/symfony/serializer/zipball/268c5aa6c4bd675eddd89348e7ecac292a843ddd", + "reference": "268c5aa6c4bd675eddd89348e7ecac292a843ddd", "shasum": "" }, "require": { - "php": ">=8.4.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "^1.8" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php84": "^1.30" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", - "symfony/property-access": "<8.1", - "symfony/property-info": "<7.4", - "symfony/type-info": "<7.4" + "symfony/dependency-injection": "<6.4", + "symfony/property-access": "<6.4.31|>=7.0,<7.4.2|>=8.0,<8.0.2", + "symfony/property-info": "<6.4", + "symfony/type-info": "<7.2.5", + "symfony/uid": "<6.4", + "symfony/validator": "<6.4", + "symfony/yaml": "<6.4" }, "require-dev": { "phpdocumentor/reflection-docblock": "^5.2|^6.0", "phpstan/phpdoc-parser": "^1.0|^2.0", "seld/jsonlint": "^1.10", - "symfony/cache": "^7.4|^8.0", - "symfony/config": "^7.4|^8.0", - "symfony/console": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", - "symfony/error-handler": "^7.4|^8.0", - "symfony/filesystem": "^7.4|^8.0", - "symfony/form": "^7.4|^8.0", - "symfony/http-foundation": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", - "symfony/messenger": "^7.4|^8.0", - "symfony/mime": "^7.4|^8.0", - "symfony/property-access": "^8.1", - "symfony/property-info": "^7.4|^8.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^7.2|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/form": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4.31|^7.4.2|^8.0.2", + "symfony/property-info": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/type-info": "^7.4|^8.0", - "symfony/uid": "^7.4|^8.0", - "symfony/validator": "^7.4|^8.0", - "symfony/var-dumper": "^7.4|^8.0", - "symfony/var-exporter": "^7.4|^8.0", - "symfony/yaml": "^7.4|^8.0" + "symfony/type-info": "^7.2.5|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -8197,7 +8050,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v8.1.0" + "source": "https://github.com/symfony/serializer/tree/v7.4.10" }, "funding": [ { @@ -8217,25 +8070,26 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-05-03T13:03:28+00:00" }, { "name": "symfony/type-info", - "version": "v8.1.0", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7" + "reference": "cafeedbf157b890e94ac5b83eaed85595106d5d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7", - "reference": "9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7", + "url": "https://api.github.com/repos/symfony/type-info/zipball/cafeedbf157b890e94ac5b83eaed85595106d5d6", + "reference": "cafeedbf157b890e94ac5b83eaed85595106d5d6", "shasum": "" }, "require": { - "php": ">=8.4.1", - "psr/container": "^1.1|^2.0" + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "phpstan/phpdoc-parser": "<1.30" @@ -8279,7 +8133,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v8.1.0" + "source": "https://github.com/symfony/type-info/tree/v7.4.9" }, "funding": [ { @@ -8299,7 +8153,7 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-04-22T15:21:55+00:00" }, { "name": "theseer/tokenizer", From bad7dd29bab0c6162c83b6de7cba3b9f8cd4d062 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Mon, 22 Jun 2026 23:26:42 +0200 Subject: [PATCH 7/8] Fix method signature --- tests/modules/core/src/Auth/Source/SourceIPSelectorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/modules/core/src/Auth/Source/SourceIPSelectorTest.php b/tests/modules/core/src/Auth/Source/SourceIPSelectorTest.php index 5fb8cc7a69..21b7212303 100644 --- a/tests/modules/core/src/Auth/Source/SourceIPSelectorTest.php +++ b/tests/modules/core/src/Auth/Source/SourceIPSelectorTest.php @@ -120,7 +120,7 @@ public function testAuthentication(): void * @param array $state * @return \Symfony\Component\HttpFoundation\Response|null */ - public static function doAuthentication(Auth\Source $as, array &$state): ?Response + public static function doAuthentication(Request $request, Auth\Source $as, array &$state): ?Response { // Dummy return null; From bf50c5e09254249bbfde89ee65e4c2e09200417e Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Mon, 22 Jun 2026 23:30:33 +0200 Subject: [PATCH 8/8] Fix bugged tests --- modules/cron/src/Controller/Cron.php | 1 + tests/modules/core/src/Controller/ErrorReportTest.php | 3 ++- tests/modules/core/src/Controller/LogoutTest.php | 3 ++- tests/modules/cron/src/Controller/CronTest.php | 2 ++ .../saml/src/Auth/Process/NameIDAttributeTest.php | 2 +- tests/modules/saml/src/Auth/Source/SPTest.php | 10 +++++----- tests/modules/saml/src/IdP/SAML2Test.php | 2 +- .../SimpleSAML/Metadata/MetaDataStorageHandlerTest.php | 6 +++--- 8 files changed, 17 insertions(+), 12 deletions(-) diff --git a/modules/cron/src/Controller/Cron.php b/modules/cron/src/Controller/Cron.php index 337b5a60c0..870b9dd9ff 100644 --- a/modules/cron/src/Controller/Cron.php +++ b/modules/cron/src/Controller/Cron.php @@ -13,6 +13,7 @@ use SimpleSAML\Session; use SimpleSAML\Utils; use SimpleSAML\XHTML\Template; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** diff --git a/tests/modules/core/src/Controller/ErrorReportTest.php b/tests/modules/core/src/Controller/ErrorReportTest.php index 911b8566f5..ca0170e5a9 100644 --- a/tests/modules/core/src/Controller/ErrorReportTest.php +++ b/tests/modules/core/src/Controller/ErrorReportTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; use SimpleSAML\Error; +use SimpleSAML\Module\core\Controller; use SimpleSAML\Session; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -63,7 +64,7 @@ public function testErrorReportSent(): void $response = $c->main($request); - $this->assertTrue($response->isSuccestul()); + $this->assertTrue($response->isSuccesful()); $this->assertEquals('core:errorreport.twig', $response->getTemplateName()); } diff --git a/tests/modules/core/src/Controller/LogoutTest.php b/tests/modules/core/src/Controller/LogoutTest.php index 729dfe29e1..7cead97181 100644 --- a/tests/modules/core/src/Controller/LogoutTest.php +++ b/tests/modules/core/src/Controller/LogoutTest.php @@ -12,6 +12,7 @@ use SimpleSAML\TestUtils\ClearStateTestCase; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * Set of tests for the controllers in the "core" module. @@ -100,7 +101,7 @@ public function testLogoutReturnToAllowedUrl(): void $c = new Controller\Logout($this->config); $response = $c->logout($request, 'example-authsource'); - $this->assertInstanceOf(RunnableResponse::class, $response); + $this->assertInstanceOf(Response::class, $response); $this->assertTrue($response->isSuccessful()); $this->assertEquals('https://example.org/something', $response->getArguments()[0]); } diff --git a/tests/modules/cron/src/Controller/CronTest.php b/tests/modules/cron/src/Controller/CronTest.php index 65d9354768..b08b63d6d0 100644 --- a/tests/modules/cron/src/Controller/CronTest.php +++ b/tests/modules/cron/src/Controller/CronTest.php @@ -14,6 +14,8 @@ use SimpleSAML\Session; use SimpleSAML\Utils; use SimpleSAML\XHTML\Template; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * Set of tests for the controllers in the "cron" module. diff --git a/tests/modules/saml/src/Auth/Process/NameIDAttributeTest.php b/tests/modules/saml/src/Auth/Process/NameIDAttributeTest.php index c2bf0d4bbc..be52e14151 100644 --- a/tests/modules/saml/src/Auth/Process/NameIDAttributeTest.php +++ b/tests/modules/saml/src/Auth/Process/NameIDAttributeTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; -use SimpleSAML\SAML2\Constants as C; +use SimpleSAML\SAML2\Constants; use SAML2\XML\saml\NameID; use SimpleSAML\Error; use SimpleSAML\Module\saml\Auth\Process\NameIDAttribute; diff --git a/tests/modules/saml/src/Auth/Source/SPTest.php b/tests/modules/saml/src/Auth/Source/SPTest.php index 53015de90b..8d02cc4c62 100644 --- a/tests/modules/saml/src/Auth/Source/SPTest.php +++ b/tests/modules/saml/src/Auth/Source/SPTest.php @@ -18,6 +18,7 @@ use SimpleSAML\SAML2\Constants; use SimpleSAML\SAML2\Exception\Protocol\NoAvailableIDPException; use SimpleSAML\SAML2\Exception\Protocol\NoSupportedIDPException; +use SimpleSAML\SAML2\Utils\XPath; use SimpleSAML\Test\Metadata\MetaDataStorageSourceTest; use SimpleSAML\Test\Utils\ExitTestException; use SimpleSAML\Test\Utils\SpTester; @@ -212,7 +213,7 @@ public function testNameID(): void $q = XPath::xpQuery( $xml, '/samlp:AuthnRequest/saml:Subject/saml:NameID/@Format', - $xpQuery, + $xpCache, ); $this->assertEquals( @@ -220,11 +221,10 @@ public function testNameID(): void $q[0]->value, ); - $xpCache = XPath::getXPath($xml); $q = XPath::xpQuery( $xml, '/samlp:AuthnRequest/saml:Subject/saml:NameID', - $xpQuery, + $xpCache, ); $this->assertEquals( @@ -1580,11 +1580,11 @@ public function testLogoutRequest(): void $xml = $lr->toSignedXML(); $xpCache = XPath::getXPath($xml); - $q = XPath::xpQuery($xml, '/samlp:LogoutRequest/saml:NameID', $xpQuery); + $q = XPath::xpQuery($xml, '/samlp:LogoutRequest/saml:NameID', $xpCache); $this->assertCount(1, $q); $this->assertEquals('someone@example.com', $q[0]->nodeValue); - $q = XPath::xpQuery($xml, '/samlp:LogoutRequest/samlp:Extensions', $xpQuery); + $q = XPath::xpQuery($xml, '/samlp:LogoutRequest/samlp:Extensions', $xpCache); $this->assertCount(1, $q); $this->assertCount(1, $q[0]->childNodes); $this->assertEquals('MyLogoutExtension', $q[0]->firstChild->localName); diff --git a/tests/modules/saml/src/IdP/SAML2Test.php b/tests/modules/saml/src/IdP/SAML2Test.php index 450337ea0c..33ba1c39f9 100644 --- a/tests/modules/saml/src/IdP/SAML2Test.php +++ b/tests/modules/saml/src/IdP/SAML2Test.php @@ -252,7 +252,7 @@ private function idpMetadataHandlerHelper(array $metadata, array $extraconfig = ["type" => "serialize", "directory" => "/tmp"], ], ] + $extraconfig, '', 'simplesaml'); - $metaHandler = new MetaDataStorageHandlerSerialize(['directory' => '/tmp']); + $metaHandler = new MetaDataStorageHandlerSerialize($config, ['directory' => '/tmp']); $metadata['entityid'] = 'urn:example:simplesaml:idp'; $metadata['certificate'] = self::CERT_PUBLIC; diff --git a/tests/src/SimpleSAML/Metadata/MetaDataStorageHandlerTest.php b/tests/src/SimpleSAML/Metadata/MetaDataStorageHandlerTest.php index 5624d208dc..477e31d997 100644 --- a/tests/src/SimpleSAML/Metadata/MetaDataStorageHandlerTest.php +++ b/tests/src/SimpleSAML/Metadata/MetaDataStorageHandlerTest.php @@ -196,7 +196,7 @@ public function testSampleEntityIdException(): void public function testCanHaveMultipleHostedIdps(): void { - $this->handler->clearInternalState(); + $this->getHandler()->clearInternalState(); $c = [ 'metadata.sources' => [ @@ -205,11 +205,11 @@ public function testCanHaveMultipleHostedIdps(): void ]; $config = Configuration::loadFromArray($c, '', 'simplesaml'); - $this->handler = MetaDataStorageHandler::getMetadataHandler($config); + $handler = MetaDataStorageHandler::getMetadataHandler($config); $this->expectException(AssertionFailedException::class); $this->expectExceptionMessageMatches('/Please set a valid and unique entityID/'); - $this->handler->getMetaDataCurrent('saml20-idp-hosted'); + $handler->getMetaDataCurrent('saml20-idp-hosted'); }