-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Prior to this change there we weren't able to keep track of sessions that got lost. This change allows us to see every time a session is created and distinguish them by their correlation id. Log an error on a route that requires an active session when there is none Prior to this change all routes were able to called, even though the user might not have had an active session This change will start logging errors when the session wasn't found, or is in an unexpected state Listen to all routes and log the state of the session Prior to this change session information got lost. We had no way of tracking down what happened to user sessions in the logs. This change logs whether a session existed and if it's in a valid state. Log information is enriched with a correlation id to be able to distinguish them. Enable session requirement check for enrollment Inject session name into the session check services That way we always follow the configured session name set in the framework.yaml Inject the correlation salt That way we do not hard code a security measure in the code base. And allow for manual setting of that SALT Enable Session constraint testing on Authn routes
- Loading branch information
Showing
16 changed files
with
848 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
|
||
/** | ||
* Copyright 2024 SURFnet B.V. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
declare(strict_types = 1); | ||
|
||
namespace Surfnet\Tiqr\Attribute; | ||
|
||
use Attribute; | ||
|
||
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] | ||
class RequiresActiveSession | ||
{ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
src/EventSubscriber/RequiresActiveSessionAttributeListener.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<?php | ||
|
||
/** | ||
* Copyright 2024 SURFnet B.V. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
declare(strict_types = 1); | ||
|
||
namespace Surfnet\Tiqr\EventSubscriber; | ||
|
||
use Psr\Log\LoggerInterface; | ||
use RuntimeException; | ||
use Surfnet\Tiqr\Attribute\RequiresActiveSession; | ||
use Surfnet\Tiqr\Service\SessionCorrelationIdService; | ||
use Surfnet\Tiqr\WithContextLogger; | ||
use Symfony\Component\EventDispatcher\EventSubscriberInterface; | ||
use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; | ||
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; | ||
use Symfony\Component\HttpKernel\KernelEvents; | ||
use Symfony\Component\Security\Core\Exception\AccessDeniedException; | ||
use function is_array; | ||
|
||
/** | ||
* This listener acts when the given route has a #[RequiresActiveSession] attribute. | ||
* When a route is marked as to have a required active session this listener will deny access when there is none. | ||
*/ | ||
final readonly class RequiresActiveSessionAttributeListener implements EventSubscriberInterface | ||
{ | ||
private string $sessionName; | ||
|
||
public function __construct( | ||
Check failure on line 43 in src/EventSubscriber/RequiresActiveSessionAttributeListener.php GitHub Actions / run-qa-tests
|
||
private LoggerInterface $logger, | ||
private SessionCorrelationIdService $sessionCorrelationIdService, | ||
private array $sessionOptions, | ||
) { | ||
if (!array_key_exists('name', $this->sessionOptions)) { | ||
throw new RuntimeException( | ||
'The session name (PHP session cookie identifier) could not be found in the session configuration.' | ||
); | ||
} | ||
$this->sessionName = $this->sessionOptions['name']; | ||
} | ||
|
||
public function onKernelControllerArguments(ControllerArgumentsEvent $event): void | ||
{ | ||
if (!is_array($event->getAttributes()[RequiresActiveSession::class] ?? null)) { | ||
return; | ||
} | ||
|
||
$logger = WithContextLogger::from($this->logger, [ | ||
'correlationId' => $this->sessionCorrelationIdService->generateCorrelationId() ?? '', | ||
'route' => $event->getRequest()->getRequestUri(), | ||
]); | ||
|
||
try { | ||
$sessionId = $event->getRequest()->getSession()->getId(); | ||
$sessionCookieId = $event->getRequest()->cookies->get($this->sessionName); | ||
|
||
if (!$sessionCookieId) { | ||
$logger->error('Route requires active session. Active session wasn\'t found. No session cookie was set.'); | ||
|
||
throw new AccessDeniedException(); | ||
} | ||
|
||
if ($sessionId !== $sessionCookieId) { | ||
$logger->error('Route requires active session. Session does not match session cookie.'); | ||
|
||
throw new AccessDeniedException(); | ||
} | ||
} catch (SessionNotFoundException) { | ||
$logger->error('Route requires active session. Active session wasn\'t found.'); | ||
|
||
throw new AccessDeniedException(); | ||
} | ||
} | ||
|
||
public static function getSubscribedEvents(): array | ||
{ | ||
return [KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments', 20]]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
<?php | ||
|
||
/** | ||
* Copyright 2024 SURFnet B.V. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
declare(strict_types = 1); | ||
|
||
namespace Surfnet\Tiqr\EventSubscriber; | ||
|
||
use Psr\Log\LoggerInterface; | ||
use RuntimeException; | ||
use Surfnet\Tiqr\Service\SessionCorrelationIdService; | ||
use Surfnet\Tiqr\WithContextLogger; | ||
use Symfony\Component\EventDispatcher\EventSubscriberInterface; | ||
use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; | ||
use Symfony\Component\HttpKernel\Event\RequestEvent; | ||
use Symfony\Component\HttpKernel\KernelEvents; | ||
|
||
/** | ||
* Listen to all incoming requests and log the session state information. | ||
*/ | ||
final readonly class SessionStateListener implements EventSubscriberInterface | ||
{ | ||
private string $sessionName; | ||
|
||
public function __construct( | ||
private LoggerInterface $logger, | ||
private SessionCorrelationIdService $sessionCorrelationIdService, | ||
private array $sessionOptions, | ||
) { | ||
if (!array_key_exists('name', $this->sessionOptions)) { | ||
throw new RuntimeException( | ||
'The session name (PHP session cookie identifier) could not be found in the session configuration.' | ||
); | ||
} | ||
$this->sessionName = $this->sessionOptions['name']; | ||
} | ||
|
||
public function onKernelRequest(RequestEvent $event): void | ||
{ | ||
$logger = WithContextLogger::from($this->logger, [ | ||
'correlationId' => $this->sessionCorrelationIdService->generateCorrelationId() ?? '', | ||
'route' => $event->getRequest()->getRequestUri(), | ||
]); | ||
|
||
$sessionCookieId = $event->getRequest()->cookies->get($this->sessionName); | ||
if ($sessionCookieId === null) { | ||
$logger->info('User made a request without a session cookie.'); | ||
return; | ||
} | ||
|
||
$logger->info('User made a request with a session cookie.'); | ||
|
||
try { | ||
$sessionId = $event->getRequest()->getSession()->getId(); | ||
$logger->info('User has a session.'); | ||
|
||
if ($sessionId !== $sessionCookieId) { | ||
$logger->error('The session cookie does not match the session id.'); | ||
return; | ||
} | ||
} catch (SessionNotFoundException) { | ||
$logger->info('Session not found.'); | ||
return; | ||
} | ||
|
||
$logger->info('User session matches the session cookie.'); | ||
} | ||
|
||
public static function getSubscribedEvents(): array | ||
{ | ||
return [KernelEvents::REQUEST => ['onKernelRequest', 20]]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<?php | ||
|
||
/** | ||
* Copyright 2024 SURFnet B.V. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
declare(strict_types = 1); | ||
|
||
namespace Surfnet\Tiqr\Service; | ||
|
||
use RuntimeException; | ||
use Symfony\Component\HttpFoundation\RequestStack; | ||
|
||
final readonly class SessionCorrelationIdService | ||
{ | ||
private string $sessionName; | ||
|
||
public function __construct( | ||
private RequestStack $requestStack, | ||
private array $sessionOptions, | ||
private string $sessionCorrelationSalt, | ||
) { | ||
if (!array_key_exists('name', $this->sessionOptions)) { | ||
throw new RuntimeException( | ||
'The session name (PHP session cookie identifier) could not be found in the session configuration.' | ||
); | ||
} | ||
if (empty($this->sessionCorrelationSalt)) { | ||
throw new RuntimeException('Please configure a non empty session correlation salt.'); | ||
} | ||
|
||
$this->sessionName = $this->sessionOptions['name']; | ||
} | ||
|
||
public function generateCorrelationId(): ?string | ||
{ | ||
$sessionCookie = $this->requestStack->getMainRequest()?->cookies->get($this->sessionName); | ||
|
||
if ($sessionCookie === null) { | ||
return null; | ||
} | ||
|
||
return hash('sha256', $this->sessionCorrelationSalt . substr($sessionCookie, 0, 10)); | ||
} | ||
} |
Oops, something went wrong.