<?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\ControllerEvent;
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\User\UserInterface;
/**
* This listener ensures that a new {@see \Sentry\State\Scope} is created for
* each request and that it is filled with useful information, e.g. the IP
* address of the client.
*/
final class RequestListener
{
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.
*
* @param RequestEvent $event The event
*/
public function handleKernelRequestEvent(RequestEvent $event): void
{
if (!$this->isMainRequest($event)) {
return;
}
$client = $this->hub->getClient();
if (null === $client || !$client->getOptions()->shouldSendDefaultPii()) {
return;
}
$userData = new UserDataBag();
$userData->setIpAddress($event->getRequest()->getClientIp());
if (null !== $this->tokenStorage) {
$this->setUserData($userData, $this->tokenStorage->getToken());
}
$this->hub->configureScope(static function (Scope $scope) use ($userData): void {
$scope->setUser($userData);
});
}
/**
* This method is called for each request handled by the framework and
* sets the route on the current Sentry scope.
*
* @param ControllerEvent $event The event
*/
public function handleKernelControllerEvent(ControllerEvent $event): void
{
if (!$this->isMainRequest($event)) {
return;
}
$route = $event->getRequest()->attributes->get('_route');
if (!\is_string($route)) {
return;
}
$this->hub->configureScope(static function (Scope $scope) use ($route): void {
$scope->setTag('route', $route);
});
}
/**
* @param UserInterface|object|string|null $user
*/
private function getUsername($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 null;
}
return $this->getUsername($token->getOriginalToken()->getUser());
}
private function setUserData(UserDataBag $userData, ?TokenInterface $token): void
{
if (null === $token || !$this->isTokenAuthenticated($token)) {
return;
}
$userData->setUsername($this->getUsername($token->getUser()));
$impersonatorUser = $this->getImpersonatorUser($token);
if (null !== $impersonatorUser) {
$userData->setMetadata('impersonator_username', $impersonatorUser);
}
}
private function isTokenAuthenticated(TokenInterface $token): bool
{
if (method_exists($token, 'isAuthenticated') && !$token->isAuthenticated(false)) {
return false;
}
return null !== $token->getUser();
}
}