From 7430fbcfbaf61e3b2fb273c81777688c5e930120 Mon Sep 17 00:00:00 2001 From: Christoph Reiter <reiter.christoph@gmail.com> Date: Tue, 8 Nov 2022 15:20:43 +0100 Subject: [PATCH] Move the user session into the core and remove roles and oidc specifics. Instead we provide the session in the core and forward requests to a oidc specific backend. This also means the session can provide useful values even in case it is used from the CLI and unauthenticated. --- src/API/UserSessionInterface.php | 37 +++-------- src/Auth/ProxyAuthenticator.php | 16 ++++- src/Auth/UserSession.php | 98 ++++++++++++++++++++++++++++++ src/Logging/LoggingProcessor.php | 5 +- src/Resources/config/services.yaml | 7 +++ src/TestUtils/TestUserSession.php | 4 +- tests/Auth/AuthenticatorTest.php | 5 +- 7 files changed, 133 insertions(+), 39 deletions(-) create mode 100644 src/Auth/UserSession.php diff --git a/src/API/UserSessionInterface.php b/src/API/UserSessionInterface.php index ce3e3df..8bc5b1b 100644 --- a/src/API/UserSessionInterface.php +++ b/src/API/UserSessionInterface.php @@ -6,58 +6,37 @@ namespace Dbp\Relay\CoreBundle\API; interface UserSessionInterface { - /** - * This gets called with the active JWT before any of the other methods are called. - */ - public function setSessionToken(?array $jwt): void; - /** * The unique identifier of the authenticated user. Or null in case it is called * before the user is known or if the user is a system. - * - * Can be derived from the session token for example. */ public function getUserIdentifier(): ?string; - /** - * Returns a list of Symfony user roles, like ['ROLE_FOOBAR']. - * - * Can be derived from the session token for example. - */ - public function getUserRoles(): array; - /** * Returns an ID represents a "session" of a user which can be used for logging. It should not be possible to * figure out which user is behind the ID based on the ID itself and the ID should change regularly. * This is useful for connecting various requests together for logging while not exposing details about the user. - * - * Can be derived from long running session IDs embedded in the token for example. - * - * Return null in case no logging ID exists */ - public function getSessionLoggingId(): ?string; + public function getSessionLoggingId(): string; + + /** + * @deprecated + */ + public function getUserRoles(): array; /** * Returns a unique caching key that can be used to cache metadata related to the current user session like * any user metadata, authorization related information etc. * It should not be possible to figure out which user is behind the ID based on the ID itself and the ID should * change regularly (after a logout/login or a key refresh for example). - * - * For example a hashed version of the token. - * - * Return null in case no appropriate cache key exists to disable any caching. */ - public function getSessionCacheKey(): ?string; + public function getSessionCacheKey(): string; /** - * Should return the duration the session is valid (as a whole, not from now) in seconds. + * Returns the duration the session is valid (as a whole, not from now) in seconds. * After the specified amount of time has passed the logging ID and the caching key should have changed. * * This is mostly useful for limiting the cache. - * - * For example the lifespan of the token. - * - * Return <0 in case that information isn't available. */ public function getSessionTTL(): int; } diff --git a/src/Auth/ProxyAuthenticator.php b/src/Auth/ProxyAuthenticator.php index 7fdafe8..eb80e47 100644 --- a/src/Auth/ProxyAuthenticator.php +++ b/src/Auth/ProxyAuthenticator.php @@ -19,9 +19,15 @@ class ProxyAuthenticator extends AbstractAuthenticator */ private $authenticators; - public function __construct() + /** + * @var UserSession + */ + private $userSession; + + public function __construct(UserSession $userSession) { $this->authenticators = []; + $this->userSession = $userSession; } public function addAuthenticator(AuthenticatorInterface $sub) @@ -54,7 +60,11 @@ class ProxyAuthenticator extends AbstractAuthenticator $auth = $this->getAuthenticator($request); assert($auth !== null); - return $auth->authenticate($request); + $passport = $auth->authenticate($request); + $provider = $passport->getAttribute('relay_user_session_provider'); + $this->userSession->setProvider($provider); + + return $passport; } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response @@ -67,6 +77,8 @@ class ProxyAuthenticator extends AbstractAuthenticator public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { + $this->userSession->setProvider(null); + $auth = $this->getAuthenticator($request); assert($auth !== null); diff --git a/src/Auth/UserSession.php b/src/Auth/UserSession.php new file mode 100644 index 0000000..461d9f3 --- /dev/null +++ b/src/Auth/UserSession.php @@ -0,0 +1,98 @@ +<?php + +declare(strict_types=1); + +namespace Dbp\Relay\CoreBundle\Auth; + +use Dbp\Relay\CoreBundle\API\UserSessionInterface; +use Dbp\Relay\CoreBundle\API\UserSessionProviderInterface; +use Symfony\Component\Security\Core\Security; +use Symfony\Component\Uid\Uuid; + +/** + * This service provides user session information, either sourcing information from the active auth provider + * or in case it is used from the CLI or unauthenticated then it returns some reasonable defaults. + */ +class UserSession implements UserSessionInterface +{ + /** + * @var ?UserSessionProviderInterface + */ + private $provider; + + /** + * @var Security + */ + private $security; + + public function __construct(?Security $security = null) + { + $this->security = $security; + } + + public function setProvider(?UserSessionProviderInterface $provider) + { + $this->provider = $provider; + } + + public function getUserIdentifier(): ?string + { + if ($this->provider === null) { + return null; + } + + return $this->provider->getUserIdentifier(); + } + + public function getSessionLoggingId(): string + { + $id = null; + if ($this->provider !== null) { + $id = $this->provider->getSessionLoggingId(); + } + if ($id === null) { + $id = 'unknown'; + } + + return $id; + } + + public function getSessionCacheKey(): string + { + $key = null; + if ($this->provider !== null) { + $key = $this->provider->getSessionCacheKey(); + } + if ($key === null) { + $key = (Uuid::v4())->toRfc4122(); + } + + return $key; + } + + public function getSessionTTL(): int + { + $ttl = -1; + if ($this->provider !== null) { + $ttl = $this->provider->getSessionTTL(); + } + if ($ttl === -1) { + $ttl = 60; + } + + return $ttl; + } + + public function getUserRoles(): array + { + if ($this->provider === null) { + return []; + } + $user = $this->security->getUser(); + if ($user === null) { + return []; + } + + return $user->getRoles(); + } +} diff --git a/src/Logging/LoggingProcessor.php b/src/Logging/LoggingProcessor.php index e014fab..0b6c309 100644 --- a/src/Logging/LoggingProcessor.php +++ b/src/Logging/LoggingProcessor.php @@ -43,10 +43,7 @@ final class LoggingProcessor $this->maskUserId($record); // Add a session ID (the same during multiple requests for the same user session) - $loggingId = $this->userDataProvider->getSessionLoggingId(); - if ($loggingId !== null) { - $record['context']['relay-session-id'] = $loggingId; - } + $record['context']['relay-session-id'] = $this->userDataProvider->getSessionLoggingId(); // Add a request ID (the same during the same client request) $request = $this->requestStack->getMainRequest(); diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index 9a56354..fef566f 100644 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -58,6 +58,13 @@ services: autowire: true autoconfigure: true + Dbp\Relay\CoreBundle\Auth\UserSession: + autowire: true + autoconfigure: true + + Dbp\Relay\CoreBundle\API\UserSessionInterface: + '@Dbp\Relay\CoreBundle\Auth\UserSession' + Dbp\Relay\CoreBundle\LocalData\LocalDataAwareEventDispatcher: autowire: true autoconfigure: true diff --git a/src/TestUtils/TestUserSession.php b/src/TestUtils/TestUserSession.php index 1abe587..6726483 100644 --- a/src/TestUtils/TestUserSession.php +++ b/src/TestUtils/TestUserSession.php @@ -41,12 +41,12 @@ class TestUserSession implements UserSessionInterface return $this->roles; } - public function getSessionLoggingId(): ?string + public function getSessionLoggingId(): string { return 'logging-id'; } - public function getSessionCacheKey(): ?string + public function getSessionCacheKey(): string { return 'cache'; } diff --git a/tests/Auth/AuthenticatorTest.php b/tests/Auth/AuthenticatorTest.php index 26d9879..99dc9f9 100644 --- a/tests/Auth/AuthenticatorTest.php +++ b/tests/Auth/AuthenticatorTest.php @@ -6,6 +6,7 @@ namespace Dbp\Relay\CoreBundle\Tests\Auth; use Dbp\Relay\CoreBundle\Auth\AuthenticatorCompilerPass; use Dbp\Relay\CoreBundle\Auth\ProxyAuthenticator; +use Dbp\Relay\CoreBundle\Auth\UserSession; use Dbp\Relay\CoreBundle\TestUtils\TestAuthenticator; use Dbp\Relay\CoreBundle\TestUtils\TestUser; use PHPUnit\Framework\TestCase; @@ -17,13 +18,13 @@ class AuthenticatorTest extends TestCase { public function testSupports() { - $auth = new ProxyAuthenticator(); + $auth = new ProxyAuthenticator(new UserSession()); $this->assertFalse($auth->supports(new Request())); } public function testSingle() { - $auth = new ProxyAuthenticator(); + $auth = new ProxyAuthenticator(new UserSession()); $user = new TestUser(); $sub = new TestAuthenticator($user, 'bla'); $auth->addAuthenticator($sub); -- GitLab