Skip to content

Commit d1e5128

Browse files
ste93crycleptric
andauthored
Improve logging of the user when he logged in programmatically (#720)
Co-authored-by: Michi Hoffmann <[email protected]>
1 parent 5472681 commit d1e5128

File tree

10 files changed

+703
-347
lines changed

10 files changed

+703
-347
lines changed

Diff for: composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
"symfony/http-kernel": "^4.4.20||^5.0.11||^6.0",
3838
"symfony/polyfill-php80": "^1.22",
3939
"symfony/psr-http-message-bridge": "^1.2||^2.0",
40-
"symfony/security-core": "^4.4.20||^5.0.11||^6.0"
40+
"symfony/security-core": "^4.4.20||^5.0.11||^6.0",
41+
"symfony/security-http": "^4.4.20||^5.0.11||^6.0"
4142
},
4243
"require-dev": {
4344
"doctrine/dbal": "^2.13||^3.0",

Diff for: phpstan-baseline.neon

+15-5
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@ parameters:
160160
count: 1
161161
path: src/EventListener/ConsoleCommandListener.php
162162

163+
-
164+
message: "#^Call to an undefined method Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\KernelEvent\\:\\:isMasterRequest\\(\\)\\.$#"
165+
count: 1
166+
path: src/EventListener/LoginListener.php
167+
163168
-
164169
message: "#^Call to an undefined method Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\KernelEvent\\:\\:isMasterRequest\\(\\)\\.$#"
165170
count: 1
@@ -313,27 +318,32 @@ parameters:
313318
-
314319
message: "#^Call to function method_exists\\(\\) with \\$this\\(Sentry\\\\SentryBundle\\\\Tests\\\\EventListener\\\\AuthenticatedTokenStub\\) and 'setAuthenticated' will always evaluate to false\\.$#"
315320
count: 1
316-
path: tests/EventListener/RequestListenerTest.php
321+
path: tests/EventListener/LoginListenerTest.php
317322

318323
-
319324
message: "#^Parameter \\#1 \\$user of method Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\AbstractToken\\:\\:setUser\\(\\) expects Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface, string\\|Stringable\\|Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface given\\.$#"
320325
count: 1
321-
path: tests/EventListener/RequestListenerTest.php
326+
path: tests/EventListener/LoginListenerTest.php
327+
328+
-
329+
message: "#^Parameter \\#2 \\$firewallName of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\SwitchUserToken constructor expects string, null given\\.$#"
330+
count: 1
331+
path: tests/EventListener/LoginListenerTest.php
322332

323333
-
324334
message: "#^Parameter \\#3 \\$roles of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\SwitchUserToken constructor expects array\\<string\\>, string given\\.$#"
325335
count: 1
326-
path: tests/EventListener/RequestListenerTest.php
336+
path: tests/EventListener/LoginListenerTest.php
327337

328338
-
329339
message: "#^Parameter \\#4 \\$originalToken of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\SwitchUserToken constructor expects Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface, array\\<int, string\\> given\\.$#"
330340
count: 1
331-
path: tests/EventListener/RequestListenerTest.php
341+
path: tests/EventListener/LoginListenerTest.php
332342

333343
-
334344
message: "#^Parameter \\#5 \\$originatedFromUri of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\SwitchUserToken constructor expects string\\|null, Sentry\\\\SentryBundle\\\\Tests\\\\EventListener\\\\AuthenticatedTokenStub given\\.$#"
335345
count: 1
336-
path: tests/EventListener/RequestListenerTest.php
346+
path: tests/EventListener/LoginListenerTest.php
337347

338348
-
339349
message: "#^Call to an undefined method Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\KernelEvent\\:\\:isMasterRequest\\(\\)\\.$#"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sentry\SentryBundle\DependencyInjection\Compiler;
6+
7+
use Sentry\SentryBundle\EventListener\LoginListener;
8+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
9+
use Symfony\Component\DependencyInjection\ContainerBuilder;
10+
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
11+
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
12+
13+
final class AddLoginListenerTagPass implements CompilerPassInterface
14+
{
15+
/**
16+
* {@inheritdoc}
17+
*/
18+
public function process(ContainerBuilder $container): void
19+
{
20+
$listenerDefinition = $container->getDefinition(LoginListener::class);
21+
22+
if (!class_exists(LoginSuccessEvent::class)) {
23+
$listenerDefinition->addTag('kernel.event_listener', [
24+
'event' => AuthenticationSuccessEvent::class,
25+
'method' => 'handleAuthenticationSuccessEvent',
26+
]);
27+
}
28+
}
29+
}

Diff for: src/EventListener/LoginListener.php

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sentry\SentryBundle\EventListener;
6+
7+
use Sentry\State\HubInterface;
8+
use Sentry\State\Scope;
9+
use Sentry\UserDataBag;
10+
use Symfony\Component\HttpKernel\Event\RequestEvent;
11+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
12+
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
13+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
14+
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
15+
use Symfony\Component\Security\Core\User\UserInterface;
16+
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
17+
18+
final class LoginListener
19+
{
20+
use KernelEventForwardCompatibilityTrait;
21+
22+
/**
23+
* @var HubInterface The current hub
24+
*/
25+
private $hub;
26+
27+
/**
28+
* @var TokenStorageInterface|null The token storage
29+
*/
30+
private $tokenStorage;
31+
32+
/**
33+
* Constructor.
34+
*
35+
* @param HubInterface $hub The current hub
36+
* @param TokenStorageInterface|null $tokenStorage The token storage
37+
*/
38+
public function __construct(HubInterface $hub, ?TokenStorageInterface $tokenStorage)
39+
{
40+
$this->hub = $hub;
41+
$this->tokenStorage = $tokenStorage;
42+
}
43+
44+
/**
45+
* This method is called for each request handled by the framework and
46+
* fills the Sentry scope with information about the current user.
47+
*/
48+
public function handleKernelRequestEvent(RequestEvent $event): void
49+
{
50+
if (null === $this->tokenStorage || !$this->isMainRequest($event)) {
51+
return;
52+
}
53+
54+
$token = $this->tokenStorage->getToken();
55+
56+
if (null !== $token) {
57+
$this->updateUserContext($token);
58+
}
59+
}
60+
61+
/**
62+
* This method is called after authentication was fully successful. It allows
63+
* to set information like the username of the currently authenticated user
64+
* and of the impersonator, if any, on the Sentry's context.
65+
*/
66+
public function handleLoginSuccessEvent(LoginSuccessEvent $event): void
67+
{
68+
$this->updateUserContext($event->getAuthenticatedToken());
69+
}
70+
71+
/**
72+
* This method is called when an authentication provider authenticates the
73+
* user. It is the event closest to {@see LoginSuccessEvent} in versions of
74+
* the framework where it doesn't exist.
75+
*/
76+
public function handleAuthenticationSuccessEvent(AuthenticationSuccessEvent $event): void
77+
{
78+
$this->updateUserContext($event->getAuthenticationToken());
79+
}
80+
81+
private function updateUserContext(TokenInterface $token): void
82+
{
83+
if (!$this->isTokenAuthenticated($token)) {
84+
return;
85+
}
86+
87+
$client = $this->hub->getClient();
88+
89+
if (null === $client || !$client->getOptions()->shouldSendDefaultPii()) {
90+
return;
91+
}
92+
93+
$this->hub->configureScope(function (Scope $scope) use ($token): void {
94+
$user = $scope->getUser() ?? new UserDataBag();
95+
96+
if (null === $user->getId()) {
97+
$user->setId($this->getUserIdentifier($token->getUser()));
98+
}
99+
100+
$impersonatorUser = $this->getImpersonatorUser($token);
101+
102+
if (null !== $impersonatorUser) {
103+
$user->setMetadata('impersonator_username', $impersonatorUser);
104+
}
105+
106+
$scope->setUser($user);
107+
});
108+
}
109+
110+
private function isTokenAuthenticated(TokenInterface $token): bool
111+
{
112+
if (method_exists($token, 'isAuthenticated') && !$token->isAuthenticated()) {
113+
return false;
114+
}
115+
116+
return null !== $token->getUser();
117+
}
118+
119+
/**
120+
* @param UserInterface|\Stringable|string|null $user
121+
*/
122+
private function getUserIdentifier($user): ?string
123+
{
124+
if ($user instanceof UserInterface) {
125+
if (method_exists($user, 'getUserIdentifier')) {
126+
return $user->getUserIdentifier();
127+
}
128+
129+
if (method_exists($user, 'getUsername')) {
130+
return $user->getUsername();
131+
}
132+
}
133+
134+
if (\is_string($user)) {
135+
return $user;
136+
}
137+
138+
if (\is_object($user) && method_exists($user, '__toString')) {
139+
return (string) $user;
140+
}
141+
142+
return null;
143+
}
144+
145+
private function getImpersonatorUser(TokenInterface $token): ?string
146+
{
147+
if ($token instanceof SwitchUserToken) {
148+
return $this->getUserIdentifier($token->getOriginalToken()->getUser());
149+
}
150+
151+
return null;
152+
}
153+
}

Diff for: src/EventListener/RequestListener.php

+8-79
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@
99
use Sentry\UserDataBag;
1010
use Symfony\Component\HttpKernel\Event\ControllerEvent;
1111
use Symfony\Component\HttpKernel\Event\RequestEvent;
12-
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
13-
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
14-
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
15-
use Symfony\Component\Security\Core\User\UserInterface;
1612

1713
/**
1814
* This listener ensures that a new {@see \Sentry\State\Scope} is created for
@@ -28,21 +24,14 @@ final class RequestListener
2824
*/
2925
private $hub;
3026

31-
/**
32-
* @var TokenStorageInterface|null The token storage
33-
*/
34-
private $tokenStorage;
35-
3627
/**
3728
* Constructor.
3829
*
39-
* @param HubInterface $hub The current hub
40-
* @param TokenStorageInterface|null $tokenStorage The token storage
30+
* @param HubInterface $hub The current hub
4131
*/
42-
public function __construct(HubInterface $hub, ?TokenStorageInterface $tokenStorage)
32+
public function __construct(HubInterface $hub/* , ?TokenStorageInterface $tokenStorage */)
4333
{
4434
$this->hub = $hub;
45-
$this->tokenStorage = $tokenStorage;
4635
}
4736

4837
/**
@@ -63,15 +52,14 @@ public function handleKernelRequestEvent(RequestEvent $event): void
6352
return;
6453
}
6554

66-
$userData = new UserDataBag();
67-
$userData->setIpAddress($event->getRequest()->getClientIp());
55+
$this->hub->configureScope(static function (Scope $scope) use ($event): void {
56+
$user = $scope->getUser() ?? new UserDataBag();
6857

69-
if (null !== $this->tokenStorage) {
70-
$this->setUserData($userData, $this->tokenStorage->getToken());
71-
}
58+
if (null === $user->getIpAddress()) {
59+
$user->setIpAddress($event->getRequest()->getClientIp());
60+
}
7261

73-
$this->hub->configureScope(static function (Scope $scope) use ($userData): void {
74-
$scope->setUser($userData);
62+
$scope->setUser($user);
7563
});
7664
}
7765

@@ -97,63 +85,4 @@ public function handleKernelControllerEvent(ControllerEvent $event): void
9785
$scope->setTag('route', $route);
9886
});
9987
}
100-
101-
/**
102-
* @param UserInterface|object|string|null $user
103-
*/
104-
private function getUsername($user): ?string
105-
{
106-
if ($user instanceof UserInterface) {
107-
if (method_exists($user, 'getUserIdentifier')) {
108-
return $user->getUserIdentifier();
109-
}
110-
111-
if (method_exists($user, 'getUsername')) {
112-
return $user->getUsername();
113-
}
114-
}
115-
116-
if (\is_string($user)) {
117-
return $user;
118-
}
119-
120-
if (\is_object($user) && method_exists($user, '__toString')) {
121-
return (string) $user;
122-
}
123-
124-
return null;
125-
}
126-
127-
private function getImpersonatorUser(TokenInterface $token): ?string
128-
{
129-
if (!$token instanceof SwitchUserToken) {
130-
return null;
131-
}
132-
133-
return $this->getUsername($token->getOriginalToken()->getUser());
134-
}
135-
136-
private function setUserData(UserDataBag $userData, ?TokenInterface $token): void
137-
{
138-
if (null === $token || !$this->isTokenAuthenticated($token)) {
139-
return;
140-
}
141-
142-
$userData->setUsername($this->getUsername($token->getUser()));
143-
144-
$impersonatorUser = $this->getImpersonatorUser($token);
145-
146-
if (null !== $impersonatorUser) {
147-
$userData->setMetadata('impersonator_username', $impersonatorUser);
148-
}
149-
}
150-
151-
private function isTokenAuthenticated(TokenInterface $token): bool
152-
{
153-
if (method_exists($token, 'isAuthenticated') && !$token->isAuthenticated(false)) {
154-
return false;
155-
}
156-
157-
return null !== $token->getUser();
158-
}
15988
}

Diff for: src/Resources/config/services.xml

+8-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343

4444
<service id="Sentry\SentryBundle\EventListener\RequestListener" class="Sentry\SentryBundle\EventListener\RequestListener">
4545
<argument type="service" id="Sentry\State\HubInterface" />
46-
<argument type="service" id="security.token_storage" on-invalid="ignore" />
4746

4847
<tag name="kernel.event_listener" event="kernel.request" method="handleKernelRequestEvent" priority="5" />
4948
<tag name="kernel.event_listener" event="kernel.controller" method="handleKernelControllerEvent" priority="10" />
@@ -87,6 +86,14 @@
8786
<tag name="kernel.event_listener" event="Symfony\Component\Messenger\Event\WorkerMessageHandledEvent" method="handleWorkerMessageHandledEvent" priority="50" />
8887
</service>
8988

89+
<service id="Sentry\SentryBundle\EventListener\LoginListener" class="Sentry\SentryBundle\EventListener\LoginListener">
90+
<argument type="service" id="Sentry\State\HubInterface" />
91+
<argument type="service" id="security.token_storage" on-invalid="ignore" />
92+
93+
<tag name="kernel.event_listener" event="kernel.request" method="handleKernelRequestEvent" />
94+
<tag name="kernel.event_listener" event="Symfony\Component\Security\Http\Event\LoginSuccessEvent" method="handleLoginSuccessEvent" />
95+
</service>
96+
9097
<service id="Sentry\SentryBundle\Command\SentryTestCommand" class="Sentry\SentryBundle\Command\SentryTestCommand">
9198
<tag name="console.command" command="sentry:test" />
9299
</service>

0 commit comments

Comments
 (0)