-
Notifications
You must be signed in to change notification settings - Fork 170
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve logging of the user when he logged in programmatically (#720)
Co-authored-by: Michi Hoffmann <[email protected]>
- Loading branch information
Showing
10 changed files
with
703 additions
and
347 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
29 changes: 29 additions & 0 deletions
29
src/DependencyInjection/Compiler/AddLoginListenerTagPass.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,29 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sentry\SentryBundle\DependencyInjection\Compiler; | ||
|
||
use Sentry\SentryBundle\EventListener\LoginListener; | ||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; | ||
use Symfony\Component\Security\Http\Event\LoginSuccessEvent; | ||
|
||
final class AddLoginListenerTagPass implements CompilerPassInterface | ||
{ | ||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function process(ContainerBuilder $container): void | ||
{ | ||
$listenerDefinition = $container->getDefinition(LoginListener::class); | ||
|
||
if (!class_exists(LoginSuccessEvent::class)) { | ||
$listenerDefinition->addTag('kernel.event_listener', [ | ||
'event' => AuthenticationSuccessEvent::class, | ||
'method' => 'handleAuthenticationSuccessEvent', | ||
]); | ||
} | ||
} | ||
} |
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,153 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sentry\SentryBundle\EventListener; | ||
|
||
use Sentry\State\HubInterface; | ||
use Sentry\State\Scope; | ||
use Sentry\UserDataBag; | ||
use Symfony\Component\HttpKernel\Event\RequestEvent; | ||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; | ||
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; | ||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | ||
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; | ||
use Symfony\Component\Security\Core\User\UserInterface; | ||
use Symfony\Component\Security\Http\Event\LoginSuccessEvent; | ||
|
||
final class LoginListener | ||
{ | ||
use KernelEventForwardCompatibilityTrait; | ||
|
||
/** | ||
* @var HubInterface The current hub | ||
*/ | ||
private $hub; | ||
|
||
/** | ||
* @var TokenStorageInterface|null The token storage | ||
*/ | ||
private $tokenStorage; | ||
|
||
/** | ||
* Constructor. | ||
* | ||
* @param HubInterface $hub The current hub | ||
* @param TokenStorageInterface|null $tokenStorage The token storage | ||
*/ | ||
public function __construct(HubInterface $hub, ?TokenStorageInterface $tokenStorage) | ||
{ | ||
$this->hub = $hub; | ||
$this->tokenStorage = $tokenStorage; | ||
} | ||
|
||
/** | ||
* This method is called for each request handled by the framework and | ||
* fills the Sentry scope with information about the current user. | ||
*/ | ||
public function handleKernelRequestEvent(RequestEvent $event): void | ||
{ | ||
if (null === $this->tokenStorage || !$this->isMainRequest($event)) { | ||
return; | ||
} | ||
|
||
$token = $this->tokenStorage->getToken(); | ||
|
||
if (null !== $token) { | ||
$this->updateUserContext($token); | ||
} | ||
} | ||
|
||
/** | ||
* This method is called after authentication was fully successful. It allows | ||
* to set information like the username of the currently authenticated user | ||
* and of the impersonator, if any, on the Sentry's context. | ||
*/ | ||
public function handleLoginSuccessEvent(LoginSuccessEvent $event): void | ||
{ | ||
$this->updateUserContext($event->getAuthenticatedToken()); | ||
} | ||
|
||
/** | ||
* This method is called when an authentication provider authenticates the | ||
* user. It is the event closest to {@see LoginSuccessEvent} in versions of | ||
* the framework where it doesn't exist. | ||
*/ | ||
public function handleAuthenticationSuccessEvent(AuthenticationSuccessEvent $event): void | ||
{ | ||
$this->updateUserContext($event->getAuthenticationToken()); | ||
} | ||
|
||
private function updateUserContext(TokenInterface $token): void | ||
{ | ||
if (!$this->isTokenAuthenticated($token)) { | ||
return; | ||
} | ||
|
||
$client = $this->hub->getClient(); | ||
|
||
if (null === $client || !$client->getOptions()->shouldSendDefaultPii()) { | ||
return; | ||
} | ||
|
||
$this->hub->configureScope(function (Scope $scope) use ($token): void { | ||
$user = $scope->getUser() ?? new UserDataBag(); | ||
|
||
if (null === $user->getId()) { | ||
$user->setId($this->getUserIdentifier($token->getUser())); | ||
} | ||
|
||
$impersonatorUser = $this->getImpersonatorUser($token); | ||
|
||
if (null !== $impersonatorUser) { | ||
$user->setMetadata('impersonator_username', $impersonatorUser); | ||
} | ||
|
||
$scope->setUser($user); | ||
}); | ||
} | ||
|
||
private function isTokenAuthenticated(TokenInterface $token): bool | ||
{ | ||
if (method_exists($token, 'isAuthenticated') && !$token->isAuthenticated()) { | ||
return false; | ||
} | ||
|
||
return null !== $token->getUser(); | ||
} | ||
|
||
/** | ||
* @param UserInterface|\Stringable|string|null $user | ||
*/ | ||
private function getUserIdentifier($user): ?string | ||
{ | ||
if ($user instanceof UserInterface) { | ||
if (method_exists($user, 'getUserIdentifier')) { | ||
return $user->getUserIdentifier(); | ||
} | ||
|
||
if (method_exists($user, 'getUsername')) { | ||
return $user->getUsername(); | ||
} | ||
} | ||
|
||
if (\is_string($user)) { | ||
return $user; | ||
} | ||
|
||
if (\is_object($user) && method_exists($user, '__toString')) { | ||
return (string) $user; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private function getImpersonatorUser(TokenInterface $token): ?string | ||
{ | ||
if ($token instanceof SwitchUserToken) { | ||
return $this->getUserIdentifier($token->getOriginalToken()->getUser()); | ||
} | ||
|
||
return null; | ||
} | ||
} |
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
Oops, something went wrong.