diff --git a/ci/qa/phpstan-baseline.php b/ci/qa/phpstan-baseline.php index fdff34a5b..1b12cbb64 100644 --- a/ci/qa/phpstan-baseline.php +++ b/ci/qa/phpstan-baseline.php @@ -296,11 +296,6 @@ 'count' => 1, 'path' => __DIR__ . '/../../src/Surfnet/StepupSelfService/SelfServiceBundle/Controller/EntryPointController.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$uri of method Surfnet\\\\StepupSelfService\\\\SelfServiceBundle\\\\Service\\\\ActivationFlowService\\:\\:process\\(\\) expects string, string\\|null given\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/../../src/Surfnet/StepupSelfService/SelfServiceBundle/Controller/EntryPointController.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Surfnet\\\\StepupSelfService\\\\SelfServiceBundle\\\\Controller\\\\ExceptionController\\:\\:getPageTitleAndDescription\\(\\) return type has no value type specified in iterable type array\\.$#', 'count' => 1, diff --git a/config/openconext/parameters.yaml.dist b/config/openconext/parameters.yaml.dist index 7059e0757..ab13b7307 100644 --- a/config/openconext/parameters.yaml.dist +++ b/config/openconext/parameters.yaml.dist @@ -83,6 +83,8 @@ parameters: recovery_method_sms_enabled: true recovery_method_safe_store_code_enabled: true + authentication_context_class_ref: ~ + when@test: parameters: app_secret: $ecretf0rt3st diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 75eef4338..c3bb4a889 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -19,8 +19,9 @@ framework: cookie_httponly: true cookie_secure: true cookie_samesite: none - cookie_lifetime: '%session_max_absolute_lifetime%' - gc_maxlifetime: '%session_max_relative_lifetime%' + cookie_lifetime: 0 + gc_maxlifetime: 0 + fragments: false error_controller: Surfnet\StepupSelfService\SelfServiceBundle\Controller\ExceptionController::show diff --git a/config/services.yaml b/config/services.yaml index 99b13bc51..3f7d4b276 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -56,3 +56,11 @@ services: arguments: - '@security.helper' - "%logout_redirect_url%" + + + Surfnet\StepupSelfService\SelfServiceBundle\EventListener\AuthenticatedUserListener: + Surfnet\StepupSelfService\SelfServiceBundle\EventListener\ExplicitSessionTimeoutListener: + + + Surfnet\StepupSelfService\SelfServiceBundle\Security\Authentication\Session\SessionLifetimeGuard: + alias: self_service.security.authentication.session.session_lifetime_guard diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/EventListener/AuthenticatedUserListener.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/EventListener/AuthenticatedUserListener.php new file mode 100644 index 000000000..6e45bd6b2 --- /dev/null +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/EventListener/AuthenticatedUserListener.php @@ -0,0 +1,65 @@ + ['updateLastInteractionMoment', 6], + ]; + } + + public function updateLastInteractionMoment(RequestEvent $event): void + { + $token = $this->tokenStorage->getToken(); + + if ($token === null || !$this->sessionLifetimeGuard->sessionLifetimeWithinLimits($this->sessionStateHandler)) { + return; + } + $this->logger->notice('Logged in user with a session within time limits detected, updating session state'); + + // see ExplicitSessionTimeoutHandler for the rationale + if ($event->getRequest()->getMethod() === 'GET') { + $this->sessionStateHandler->setCurrentRequestUri($event->getRequest()->getRequestUri()); + } + $this->sessionStateHandler->updateLastInteractionMoment(); + } +} diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/EventListener/ExplicitSessionTimeoutListener.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/EventListener/ExplicitSessionTimeoutListener.php new file mode 100644 index 000000000..1665fa8b0 --- /dev/null +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/EventListener/ExplicitSessionTimeoutListener.php @@ -0,0 +1,102 @@ + ['checkSessionTimeout', 5], + ]; + } + + public function checkSessionTimeout(RequestEvent $event): void + { + $token = $this->tokenStorage->getToken(); + + if ($token === null || $this->sessionLifetimeGuard->sessionLifetimeWithinLimits($this->authenticatedSession)) { + return; + } + + $invalidatedBy = []; + if (!$this->sessionLifetimeGuard->sessionLifetimeWithinAbsoluteLimit($this->authenticatedSession)) { + $invalidatedBy[] = 'absolute'; + } + + if (!$this->sessionLifetimeGuard->sessionLifetimeWithinRelativeLimit($this->authenticatedSession)) { + $invalidatedBy[] = 'relative'; + } + + $this->logger->notice(sprintf( + 'Authenticated user found, but session was determined to be outside of the "%s" time limit. User will ' + . 'be logged out and redirected to session-expired page to attempt new login.', + implode(' and ', $invalidatedBy), + )); + + $request = $event->getRequest(); + + // if the current request was not a GET request we cannot safely redirect to that page after login as it + // may require a form resubmit for instance. Therefor, we redirect to the last GET request (either current + // or previous). + $afterLoginRedirectTo = $this->authenticatedSession->getCurrentRequestUri(); + + if ($event->getRequest()->getMethod() === 'GET') { + $afterLoginRedirectTo = $event->getRequest()->getRequestUri(); + } + + // log the user out using Symfony methodology, see the LogoutListener + $event->setResponse(new RedirectResponse($this->router->generate('selfservice_security_session_expired'))); + + // something to clear cookies + $this->eventDispatcher->dispatch(new LogoutEvent($request, $token)); + $this->tokenStorage->setToken(null); + + // the session is restarted after invalidation during the logout, so we can (re)store the last GET request + $this->authenticatedSession->setCurrentRequestUri($afterLoginRedirectTo); + } +} diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Security/Authentication/AuthenticatedSessionStateHandler.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Security/Authentication/AuthenticatedSessionStateHandler.php index 99342a16b..3b2c8d2b2 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Security/Authentication/AuthenticatedSessionStateHandler.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Security/Authentication/AuthenticatedSessionStateHandler.php @@ -55,7 +55,7 @@ public function hasSeenInteraction(): bool; public function setCurrentRequestUri(string $uri): void; - public function getCurrentRequestUri(): ?string; + public function getCurrentRequestUri(): string; /** * Migrates the current session to a new session id while maintaining all diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Security/Authentication/Session/SessionLifetimeGuard.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Security/Authentication/Session/SessionLifetimeGuard.php index 1a5e2f378..4fc7a2b5c 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Security/Authentication/Session/SessionLifetimeGuard.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Security/Authentication/Session/SessionLifetimeGuard.php @@ -38,7 +38,6 @@ public function sessionLifetimeWithinLimits(AuthenticatedSessionStateHandler $se && $this->sessionLifetimeWithinRelativeLimit($sessionStateHandler); } - public function sessionLifetimeWithinAbsoluteLimit(AuthenticatedSessionStateHandler $sessionStateHandler): bool { if (!$sessionStateHandler->isAuthenticationMomentLogged()) {