Skip to content

Commit f842070

Browse files
committed
Support logging the impersonator user, if any
1 parent 6db5a35 commit f842070

File tree

5 files changed

+165
-78
lines changed

5 files changed

+165
-78
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
## Unreleased
44

5+
- Support logging the impersonator user, if any (#647)
6+
57
## 4.3.0 (2022-05-30)
6-
- Fix compatibility issue with Symfony >= 6.1.0 (#635)
8+
9+
- Fix compatibility issue with Symfony `>= 6.1.0` (#635)
710
- Add `TracingDriverConnectionInterface::getNativeConnection()` method to get the original driver connection (#597)
811
- Add `options.http_timeout` and `options.http_connect_timeout` configuration options (#593)
912

phpstan-baseline.neon

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -136,40 +136,20 @@ parameters:
136136
path: src/DependencyInjection/SentryExtension.php
137137

138138
-
139-
message: "#^Parameter \\#2 \\$value of method Symfony\\\\Component\\\\DependencyInjection\\\\Container\\:\\:setParameter\\(\\) expects array\\|bool\\|float\\|int\\|string\\|UnitEnum\\|null, mixed given\\.$#"
139+
message: "#^Parameter \\#2 \\$value of method Symfony\\\\Component\\\\DependencyInjection\\\\Container\\:\\:setParameter\\(\\) expects array\\|bool\\|float\\|int\\|string\\|null, mixed given\\.$#"
140140
count: 2
141141
path: src/DependencyInjection/SentryExtension.php
142142

143-
-
144-
message: "#^Call to an undefined method Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\KernelEvent\\:\\:isMasterRequest\\(\\)\\.$#"
145-
count: 1
146-
path: src/EventListener/AbstractTracingRequestListener.php
147-
148143
-
149144
message: "#^Class Sentry\\\\SentryBundle\\\\EventListener\\\\ConsoleCommandListener extends @final class Sentry\\\\SentryBundle\\\\EventListener\\\\ConsoleListener\\.$#"
150145
count: 1
151146
path: src/EventListener/ConsoleCommandListener.php
152147

153148
-
154-
message: "#^Call to an undefined method Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\KernelEvent\\:\\:isMasterRequest\\(\\)\\.$#"
155-
count: 1
156-
path: src/EventListener/RequestListener.php
157-
158-
-
159-
message: "#^Cannot call method getUser\\(\\) on Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface\\|null\\.$#"
160-
count: 1
161-
path: src/EventListener/RequestListener.php
162-
163-
-
164-
message: "#^Parameter \\#1 \\$user of method Sentry\\\\SentryBundle\\\\EventListener\\\\RequestListener\\:\\:getUsername\\(\\) expects object\\|string, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#"
149+
message: "#^Method Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface\\:\\:isAuthenticated\\(\\) invoked with 1 parameter, 0 required\\.$#"
165150
count: 1
166151
path: src/EventListener/RequestListener.php
167152

168-
-
169-
message: "#^Call to an undefined method Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\KernelEvent\\:\\:isMasterRequest\\(\\)\\.$#"
170-
count: 1
171-
path: src/EventListener/SubRequestListener.php
172-
173153
-
174154
message: "#^Parameter \\#1 \\$driver of method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverMiddleware\\:\\:wrap\\(\\) expects Doctrine\\\\DBAL\\\\Driver, mixed given\\.$#"
175155
count: 1
@@ -276,19 +256,19 @@ parameters:
276256
path: tests/End2End/TracingEnd2EndTest.php
277257

278258
-
279-
message: "#^Call to function method_exists\\(\\) with \\$this\\(Sentry\\\\SentryBundle\\\\Tests\\\\EventListener\\\\AuthenticatedTokenStub\\) and 'setAuthenticated' will always evaluate to false\\.$#"
259+
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\\.$#"
280260
count: 1
281261
path: tests/EventListener/RequestListenerTest.php
282262

283263
-
284-
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\\.$#"
264+
message: "#^Parameter \\#3 \\$roles of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\SwitchUserToken constructor expects array\\<string\\>, string given\\.$#"
285265
count: 1
286266
path: tests/EventListener/RequestListenerTest.php
287267

288268
-
289-
message: "#^Call to an undefined method Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\KernelEvent\\:\\:isMasterRequest\\(\\)\\.$#"
269+
message: "#^Parameter \\#5 \\$originatedFromUri of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\SwitchUserToken constructor expects string\\|null, Sentry\\\\SentryBundle\\\\Tests\\\\EventListener\\\\AuthenticatedTokenStub given\\.$#"
290270
count: 1
291-
path: tests/EventListener/SubRequestListenerTest.php
271+
path: tests/EventListener/RequestListenerTest.php
292272

293273
-
294274
message: "#^Call to an undefined method TCacheAdapter of Symfony\\\\Component\\\\Cache\\\\Adapter\\\\AdapterInterface\\:\\:delete\\(\\)\\.$#"
@@ -310,6 +290,11 @@ parameters:
310290
count: 1
311291
path: tests/Tracing/Cache/AbstractTraceableCacheAdapterTest.php
312292

293+
-
294+
message: "#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertSame\\(\\) with array\\{foo\\: Symfony\\\\Component\\\\Cache\\\\CacheItem\\} and Traversable\\<string, Symfony\\\\Component\\\\Cache\\\\CacheItem\\> will always evaluate to false\\.$#"
295+
count: 1
296+
path: tests/Tracing/Cache/AbstractTraceableCacheAdapterTest.php
297+
313298
-
314299
message: "#^Parameter \\#1 \\$decoratedAdapter of method Sentry\\\\SentryBundle\\\\Tests\\\\Tracing\\\\Cache\\\\AbstractTraceableCacheAdapterTest\\<TCacheAdapter of Symfony\\\\Component\\\\Cache\\\\Adapter\\\\AdapterInterface,TDecoratedCacheAdapter of Symfony\\\\Component\\\\Cache\\\\Adapter\\\\AdapterInterface\\>\\:\\:createCacheAdapter\\(\\) expects TDecoratedCacheAdapter of Symfony\\\\Component\\\\Cache\\\\Adapter\\\\AdapterInterface, PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Sentry\\\\SentryBundle\\\\Tests\\\\Tracing\\\\Cache\\\\CacheInterface given\\.$#"
315300
count: 2

src/EventListener/RequestListener.php

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Symfony\Component\HttpKernel\Event\ControllerEvent;
1111
use Symfony\Component\HttpKernel\Event\RequestEvent;
1212
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
13+
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
1314
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
1415
use Symfony\Component\Security\Core\User\UserInterface;
1516

@@ -62,16 +63,11 @@ public function handleKernelRequestEvent(RequestEvent $event): void
6263
return;
6364
}
6465

65-
$token = null;
6666
$userData = new UserDataBag();
6767
$userData->setIpAddress($event->getRequest()->getClientIp());
6868

6969
if (null !== $this->tokenStorage) {
70-
$token = $this->tokenStorage->getToken();
71-
}
72-
73-
if ($this->isTokenAuthenticated($token)) {
74-
$userData->setUsername($this->getUsername($token->getUser()));
70+
$this->setUserData($userData, $this->tokenStorage->getToken());
7571
}
7672

7773
$this->hub->configureScope(static function (Scope $scope) use ($userData): void {
@@ -103,7 +99,7 @@ public function handleKernelControllerEvent(ControllerEvent $event): void
10399
}
104100

105101
/**
106-
* @param UserInterface|object|string $user
102+
* @param UserInterface|object|string|null $user
107103
*/
108104
private function getUsername($user): ?string
109105
{
@@ -128,12 +124,32 @@ private function getUsername($user): ?string
128124
return null;
129125
}
130126

131-
private function isTokenAuthenticated(?TokenInterface $token): bool
127+
private function getImpersonatorUser(TokenInterface $token): ?string
132128
{
133-
if (null === $token) {
134-
return false;
129+
if (!$token instanceof SwitchUserToken) {
130+
return null;
135131
}
136132

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+
{
137153
if (method_exists($token, 'isAuthenticated') && !$token->isAuthenticated(false)) {
138154
return false;
139155
}

tests/EventListener/Fixtures/UserWithIdentifierStub.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,24 @@
88

99
final class UserWithIdentifierStub implements UserInterface
1010
{
11+
/**
12+
* @var string
13+
*/
14+
private $username;
15+
16+
public function __construct(string $username = 'foo_user')
17+
{
18+
$this->username = $username;
19+
}
20+
1121
public function getUserIdentifier(): string
1222
{
1323
return $this->getUsername();
1424
}
1525

1626
public function getUsername(): string
1727
{
18-
return 'foo_user';
28+
return $this->username;
1929
}
2030

2131
public function getRoles(): array

tests/EventListener/RequestListenerTest.php

Lines changed: 113 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\HttpKernel\Kernel;
2323
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
2424
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
25+
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
2526
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
2627
use Symfony\Component\Security\Core\User\UserInterface;
2728

@@ -51,6 +52,9 @@ protected function setUp(): void
5152

5253
/**
5354
* @dataProvider handleKernelRequestEventDataProvider
55+
* @dataProvider handleKernelRequestEventForSymfonyVersionLowerThan54DataProvider
56+
* @dataProvider handleKernelRequestEventForSymfonyVersionGreaterThan54DataProvider
57+
* @dataProvider handleKernelRequestEventForSymfonyVersionLowerThan60DataProvider
5458
*/
5559
public function testHandleKernelRequestEvent(RequestEvent $requestEvent, ?ClientInterface $client, ?TokenInterface $token, ?UserDataBag $expectedUser): void
5660
{
@@ -138,46 +142,6 @@ public function handleKernelRequestEventDataProvider(): \Generator
138142
UserDataBag::createFromUserIpAddress('127.0.0.1'),
139143
];
140144

141-
if (version_compare(Kernel::VERSION, '6.0.0', '<')) {
142-
yield 'token.authenticated = TRUE && token.user INSTANCEOF string' => [
143-
new RequestEvent(
144-
$this->createMock(HttpKernelInterface::class),
145-
new Request([], [], [], [], [], ['REMOTE_ADDR' => '127.0.0.1']),
146-
HttpKernelInterface::MASTER_REQUEST
147-
),
148-
$this->getMockedClientWithOptions(new Options(['send_default_pii' => true])),
149-
new AuthenticatedTokenStub('foo_user'),
150-
new UserDataBag(null, null, '127.0.0.1', 'foo_user'),
151-
];
152-
153-
yield 'token.authenticated = TRUE && token.user INSTANCEOF UserInterface && getUserIdentifier() method DOES NOT EXISTS' => [
154-
new RequestEvent(
155-
$this->createMock(HttpKernelInterface::class),
156-
new Request([], [], [], [], [], ['REMOTE_ADDR' => '127.0.0.1']),
157-
HttpKernelInterface::MASTER_REQUEST
158-
),
159-
$this->getMockedClientWithOptions(new Options(['send_default_pii' => true])),
160-
new AuthenticatedTokenStub(new UserWithoutIdentifierStub()),
161-
new UserDataBag(null, null, '127.0.0.1', 'foo_user'),
162-
];
163-
164-
yield 'token.authenticated = TRUE && token.user INSTANCEOF object && __toString() method EXISTS' => [
165-
new RequestEvent(
166-
$this->createMock(HttpKernelInterface::class),
167-
new Request([], [], [], [], [], ['REMOTE_ADDR' => '127.0.0.1']),
168-
HttpKernelInterface::MASTER_REQUEST
169-
),
170-
$this->getMockedClientWithOptions(new Options(['send_default_pii' => true])),
171-
new AuthenticatedTokenStub(new class() implements \Stringable {
172-
public function __toString(): string
173-
{
174-
return 'foo_user';
175-
}
176-
}),
177-
new UserDataBag(null, null, '127.0.0.1', 'foo_user'),
178-
];
179-
}
180-
181145
yield 'token.authenticated = TRUE && token.user INSTANCEOF UserInterface && getUserIdentifier() method EXISTS' => [
182146
new RequestEvent(
183147
$this->createMock(HttpKernelInterface::class),
@@ -201,6 +165,115 @@ public function __toString(): string
201165
];
202166
}
203167

168+
/**
169+
* @return \Generator<mixed>
170+
*/
171+
public function handleKernelRequestEventForSymfonyVersionLowerThan54DataProvider(): \Generator
172+
{
173+
if (version_compare(Kernel::VERSION, '5.4.0', '>=')) {
174+
return;
175+
}
176+
177+
yield 'token.authenticated = TRUE && token INSTANCEOF SwitchUserToken' => [
178+
new RequestEvent(
179+
$this->createMock(HttpKernelInterface::class),
180+
new Request([], [], [], [], [], ['REMOTE_ADDR' => '127.0.0.1']),
181+
HttpKernelInterface::MASTER_REQUEST
182+
),
183+
$this->getMockedClientWithOptions(new Options(['send_default_pii' => true])),
184+
new SwitchUserToken(
185+
new UserWithIdentifierStub(),
186+
'',
187+
'user_provider',
188+
['ROLE_USER'],
189+
new AuthenticatedTokenStub(new UserWithIdentifierStub('foo_user_impersonator'))
190+
),
191+
UserDataBag::createFromArray([
192+
'ip_address' => '127.0.0.1',
193+
'username' => 'foo_user',
194+
'impersonator_username' => 'foo_user_impersonator',
195+
]),
196+
];
197+
}
198+
199+
/**
200+
* @return \Generator<mixed>
201+
*/
202+
public function handleKernelRequestEventForSymfonyVersionGreaterThan54DataProvider(): \Generator
203+
{
204+
if (version_compare(Kernel::VERSION, '5.4.0', '<')) {
205+
return;
206+
}
207+
208+
yield 'token.authenticated = TRUE && token INSTANCEOF SwitchUserToken' => [
209+
new RequestEvent(
210+
$this->createMock(HttpKernelInterface::class),
211+
new Request([], [], [], [], [], ['REMOTE_ADDR' => '127.0.0.1']),
212+
HttpKernelInterface::MASTER_REQUEST
213+
),
214+
$this->getMockedClientWithOptions(new Options(['send_default_pii' => true])),
215+
new SwitchUserToken(
216+
new UserWithIdentifierStub(),
217+
'main',
218+
['ROLE_USER'],
219+
new AuthenticatedTokenStub(new UserWithIdentifierStub('foo_user_impersonator'))
220+
),
221+
UserDataBag::createFromArray([
222+
'ip_address' => '127.0.0.1',
223+
'username' => 'foo_user',
224+
'impersonator_username' => 'foo_user_impersonator',
225+
]),
226+
];
227+
}
228+
229+
/**
230+
* @return \Generator<mixed>
231+
*/
232+
public function handleKernelRequestEventForSymfonyVersionLowerThan60DataProvider(): \Generator
233+
{
234+
if (version_compare(Kernel::VERSION, '6.0.0', '>=')) {
235+
return;
236+
}
237+
238+
yield 'token.authenticated = TRUE && token.user INSTANCEOF string' => [
239+
new RequestEvent(
240+
$this->createMock(HttpKernelInterface::class),
241+
new Request([], [], [], [], [], ['REMOTE_ADDR' => '127.0.0.1']),
242+
HttpKernelInterface::MASTER_REQUEST
243+
),
244+
$this->getMockedClientWithOptions(new Options(['send_default_pii' => true])),
245+
new AuthenticatedTokenStub('foo_user'),
246+
new UserDataBag(null, null, '127.0.0.1', 'foo_user'),
247+
];
248+
249+
yield 'token.authenticated = TRUE && token.user INSTANCEOF UserInterface && getUserIdentifier() method DOES NOT EXISTS' => [
250+
new RequestEvent(
251+
$this->createMock(HttpKernelInterface::class),
252+
new Request([], [], [], [], [], ['REMOTE_ADDR' => '127.0.0.1']),
253+
HttpKernelInterface::MASTER_REQUEST
254+
),
255+
$this->getMockedClientWithOptions(new Options(['send_default_pii' => true])),
256+
new AuthenticatedTokenStub(new UserWithoutIdentifierStub()),
257+
new UserDataBag(null, null, '127.0.0.1', 'foo_user'),
258+
];
259+
260+
yield 'token.authenticated = TRUE && token.user INSTANCEOF object && __toString() method EXISTS' => [
261+
new RequestEvent(
262+
$this->createMock(HttpKernelInterface::class),
263+
new Request([], [], [], [], [], ['REMOTE_ADDR' => '127.0.0.1']),
264+
HttpKernelInterface::MASTER_REQUEST
265+
),
266+
$this->getMockedClientWithOptions(new Options(['send_default_pii' => true])),
267+
new AuthenticatedTokenStub(new class() implements \Stringable {
268+
public function __toString(): string
269+
{
270+
return 'foo_user';
271+
}
272+
}),
273+
new UserDataBag(null, null, '127.0.0.1', 'foo_user'),
274+
];
275+
}
276+
204277
/**
205278
* @dataProvider handleKernelControllerEventDataProvider
206279
*

0 commit comments

Comments
 (0)