Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3ec5e96

Browse files
committed
[Security][WIP] Add authenticators info to the profiler
1 parent 626d9aa commit 3ec5e96

File tree

16 files changed

+669
-274
lines changed

16 files changed

+669
-274
lines changed

UPGRADE-5.4.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Messenger
5151
SecurityBundle
5252
--------------
5353

54+
* Deprecate `FirewallConfig::getListeners()`, use `FirewallConfig::getAuthenticators()` instead
5455
* Deprecate `security.authentication.basic_entry_point` and `security.authentication.retry_entry_point` services, the logic is moved into the
5556
`HttpBasicAuthenticator` and `ChannelListener` respectively
5657
* Deprecate not setting `$authenticatorManagerEnabled` to `true` in `SecurityDataCollector` and `DebugFirewallCommand`

UPGRADE-6.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ Security
394394
SecurityBundle
395395
--------------
396396

397+
* Remove `FirewallConfig::getListeners()`, use `FirewallConfig::getAuthenticators()` instead
397398
* Remove `security.authentication.basic_entry_point` and `security.authentication.retry_entry_point` services,
398399
the logic is moved into the `HttpBasicAuthenticator` and `ChannelListener` respectively
399400
* Remove `SecurityFactoryInterface` and `SecurityExtension::addSecurityListenerFactory()` in favor of

src/Symfony/Bundle/SecurityBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
5.4
55
---
66

7+
* Deprecate `FirewallConfig::getListeners()`, use `FirewallConfig::getAuthenticators()` instead
78
* Deprecate `security.authentication.basic_entry_point` and `security.authentication.retry_entry_point` services, the logic is moved into the
89
`HttpBasicAuthenticator` and `ChannelListener` respectively
910
* Deprecate `FirewallConfig::allowsAnonymous()` and the `allows_anonymous` from the data collector data, there will be no anonymous concept as of version 6.

src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ public function collect(Request $request, Response $response, \Throwable $except
194194
'access_denied_handler' => $firewallConfig->getAccessDeniedHandler(),
195195
'access_denied_url' => $firewallConfig->getAccessDeniedUrl(),
196196
'user_checker' => $firewallConfig->getUserChecker(),
197-
'listeners' => $firewallConfig->getListeners(),
197+
'authenticators' => $firewallConfig->getAuthenticators(),
198198
];
199199

200200
// generate exit impersonation path from current request
@@ -215,6 +215,7 @@ public function collect(Request $request, Response $response, \Throwable $except
215215
}
216216

217217
$this->data['authenticator_manager_enabled'] = $this->authenticatorManagerEnabled;
218+
$this->data['authenticators'] = $this->firewall ? $this->firewall->getAuthenticatorsInfo() : [];
218219
}
219220

220221
/**
@@ -370,6 +371,14 @@ public function getListeners()
370371
return $this->data['listeners'];
371372
}
372373

374+
/**
375+
* @return array|Data
376+
*/
377+
public function getAuthenticators()
378+
{
379+
return $this->data['authenticators'];
380+
}
381+
373382
/**
374383
* {@inheritdoc}
375384
*/
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\SecurityBundle\Debug\Authenticator;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\Response;
16+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
17+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
18+
use Symfony\Component\Security\Guard\Authenticator\GuardBridgeAuthenticator;
19+
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
20+
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
21+
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
22+
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
23+
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
24+
use Symfony\Component\Security\Http\EntryPoint\Exception\NotAnEntryPointException;
25+
use Symfony\Component\VarDumper\Caster\ClassStub;
26+
27+
/**
28+
* @author Robin Chalas <[email protected]>
29+
*
30+
* @internal
31+
*/
32+
final class TraceableAuthenticator implements AuthenticatorInterface, InteractiveAuthenticatorInterface, AuthenticationEntryPointInterface
33+
{
34+
private $authenticator;
35+
private $passport;
36+
private $duration;
37+
private $stub;
38+
39+
public function __construct(AuthenticatorInterface $authenticator)
40+
{
41+
$this->authenticator = $authenticator;
42+
}
43+
44+
public function getInfo(): array
45+
{
46+
return [
47+
'supports' => true,
48+
'passport' => $this->passport,
49+
'duration' => $this->duration,
50+
'stub' => $this->stub ?? $this->stub = new ClassStub(\get_class($this->authenticator instanceof GuardBridgeAuthenticator ? $this->authenticator->getGuardAuthenticator() : $this->authenticator)),
51+
];
52+
}
53+
54+
public function supports(Request $request): ?bool
55+
{
56+
return $this->authenticator->supports($request);
57+
}
58+
59+
public function authenticate(Request $request): PassportInterface
60+
{
61+
$startTime = microtime(true);
62+
$this->passport = $this->authenticator->authenticate($request);
63+
$this->duration = microtime(true) - $startTime;
64+
65+
return $this->passport;
66+
}
67+
68+
public function createToken(Passport $passport, string $firewallName): TokenInterface
69+
{
70+
return method_exists($this->authenticator, 'createToken') ? $this->authenticator->createToken($passport, $firewallName) : $this->authenticator->createAuthenticatedToken($passport, $firewallName);
71+
}
72+
73+
public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
74+
{
75+
return $this->authenticator->createAuthenticatedToken($passport, $firewallName);
76+
}
77+
78+
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
79+
{
80+
return $this->authenticator->onAuthenticationSuccess($request, $token, $firewallName);
81+
}
82+
83+
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
84+
{
85+
return $this->authenticator->onAuthenticationFailure($request, $exception);
86+
}
87+
88+
public function start(Request $request, AuthenticationException $authException = null): Response
89+
{
90+
if (!$this->authenticator instanceof AuthenticationEntryPointInterface) {
91+
throw new NotAnEntryPointException();
92+
}
93+
94+
return $this->authenticator->start($request, $authException);
95+
}
96+
97+
public function isInteractive(): bool
98+
{
99+
return $this->authenticator instanceof InteractiveAuthenticatorInterface && $this->authenticator->isInteractive();
100+
}
101+
102+
public function __call($method, $args)
103+
{
104+
return $this->authenticator->{$method}(...$args);
105+
}
106+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\SecurityBundle\Debug\Authenticator;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpKernel\Event\RequestEvent;
16+
use Symfony\Component\Security\Http\Firewall\AbstractListener;
17+
use Symfony\Component\Security\Http\Firewall\AuthenticatorManagerListener;
18+
use Symfony\Component\VarDumper\Caster\ClassStub;
19+
20+
/**
21+
* Decorates the AuthenticatorManagerListener to collect information about security authenticators.
22+
*
23+
* @author Robin Chalas <[email protected]>
24+
*
25+
* @internal
26+
*/
27+
class TraceableAuthenticatorManagerListener extends AbstractListener
28+
{
29+
private $authenticationManagerListener;
30+
private $authenticatorsInfo = [];
31+
32+
public function __construct(AuthenticatorManagerListener $authenticationManagerListener)
33+
{
34+
$this->authenticationManagerListener = $authenticationManagerListener;
35+
}
36+
37+
public function supports(Request $request): ?bool
38+
{
39+
return $this->authenticationManagerListener->supports($request);
40+
}
41+
42+
public function authenticate(RequestEvent $event): void
43+
{
44+
$request = $event->getRequest();
45+
46+
if (!$authenticators = $request->attributes->get('_security_authenticators')) {
47+
return;
48+
}
49+
50+
foreach ($request->attributes->get('_security_skipped_authenticators') as $skippedAuthenticator) {
51+
$this->authenticatorsInfo[] = [
52+
'supports' => false,
53+
'stub' => new ClassStub(\get_class($skippedAuthenticator)),
54+
'passport' => null,
55+
'duration' => 0,
56+
];
57+
}
58+
59+
foreach ($authenticators as $key => $authenticator) {
60+
$authenticators[$key] = new TraceableAuthenticator($authenticator);
61+
}
62+
63+
$request->attributes->set('_security_authenticators', $authenticators);
64+
65+
$this->authenticationManagerListener->authenticate($event);
66+
67+
foreach ($authenticators as $authenticator) {
68+
$this->authenticatorsInfo[] = $authenticator->getInfo();
69+
}
70+
}
71+
72+
public function getAuthenticatorManagerListener(): AuthenticatorManagerListener
73+
{
74+
return $this->authenticationManagerListener;
75+
}
76+
77+
public function getAuthenticatorsInfo(): array
78+
{
79+
return $this->authenticatorsInfo;
80+
}
81+
}

src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,47 @@
1111

1212
namespace Symfony\Bundle\SecurityBundle\Debug;
1313

14+
use Symfony\Bundle\SecurityBundle\Debug\Authenticator\TraceableAuthenticatorManagerListener;
1415
use Symfony\Bundle\SecurityBundle\EventListener\FirewallListener;
1516
use Symfony\Bundle\SecurityBundle\Security\FirewallContext;
1617
use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext;
1718
use Symfony\Component\HttpKernel\Event\RequestEvent;
1819
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
1920

2021
/**
21-
* Firewall collecting called listeners.
22+
* Firewall collecting called security listeners and authenticators.
2223
*
2324
* @author Robin Chalas <[email protected]>
2425
*/
2526
final class TraceableFirewallListener extends FirewallListener
2627
{
2728
private $wrappedListeners = [];
29+
private $authenticatorsInfo = [];
2830

2931
public function getWrappedListeners()
3032
{
3133
return $this->wrappedListeners;
3234
}
3335

36+
public function getAuthenticatorsInfo(): array
37+
{
38+
return $this->authenticatorsInfo;
39+
}
40+
3441
protected function callListeners(RequestEvent $event, iterable $listeners)
3542
{
3643
$wrappedListeners = [];
3744
$wrappedLazyListeners = [];
45+
$authenticatorManagerListener = null;
3846

3947
foreach ($listeners as $listener) {
4048
if ($listener instanceof LazyFirewallContext) {
41-
\Closure::bind(function () use (&$wrappedLazyListeners, &$wrappedListeners) {
49+
\Closure::bind(function () use (&$wrappedLazyListeners, &$wrappedListeners, &$authenticatorManagerListener) {
4250
$listeners = [];
4351
foreach ($this->listeners as $listener) {
52+
if (!$authenticatorManagerListener && $listener instanceof TraceableAuthenticatorManagerListener) {
53+
$authenticatorManagerListener = $listener;
54+
}
4455
if ($listener instanceof FirewallListenerInterface) {
4556
$listener = new WrappedLazyListener($listener);
4657
$listeners[] = $listener;
@@ -61,6 +72,9 @@ protected function callListeners(RequestEvent $event, iterable $listeners)
6172
$wrappedListener = $listener instanceof FirewallListenerInterface ? new WrappedLazyListener($listener) : new WrappedListener($listener);
6273
$wrappedListener($event);
6374
$wrappedListeners[] = $wrappedListener->getInfo();
75+
if (!$authenticatorManagerListener && $listener instanceof TraceableAuthenticatorManagerListener) {
76+
$authenticatorManagerListener = $listener;
77+
}
6478
}
6579

6680
if ($event->hasResponse()) {
@@ -75,5 +89,9 @@ protected function callListeners(RequestEvent $event, iterable $listeners)
7589
}
7690

7791
$this->wrappedListeners = array_merge($this->wrappedListeners, $wrappedListeners);
92+
93+
if ($authenticatorManagerListener) {
94+
$this->authenticatorsInfo = $authenticatorManagerListener->getAuthenticatorsInfo();
95+
}
7896
}
7997
}

src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Bundle\SecurityBundle\Debug;
1313

14+
use Symfony\Bundle\SecurityBundle\Debug\Authenticator\TraceableAuthenticatorManagerListener;
1415
use Symfony\Component\VarDumper\Caster\ClassStub;
1516

1617
/**
@@ -43,7 +44,7 @@ public function getInfo(): array
4344
return [
4445
'response' => $this->response,
4546
'time' => $this->time,
46-
'stub' => $this->stub ?? $this->stub = ClassStub::wrapCallable($this->listener),
47+
'stub' => $this->stub ?? $this->stub = ClassStub::wrapCallable($this->listener instanceof TraceableAuthenticatorManagerListener ? $this->listener->getAuthenticatorManagerListener() : $this->listener),
4748
];
4849
}
4950
}

src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Bundle\SecurityBundle\DependencyInjection;
1313

1414
use Symfony\Bridge\Twig\Extension\LogoutUrlExtension;
15+
use Symfony\Bundle\SecurityBundle\Debug\Authenticator\TraceableAuthenticatorManagerListener;
1516
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
1617
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FirewallListenerFactoryInterface;
1718
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
@@ -504,6 +505,14 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
504505
->replaceArgument(0, new Reference($managerId))
505506
;
506507

508+
if ($container->hasDefinition('debug.security.firewall') && $this->authenticatorManagerEnabled) {
509+
$container
510+
->register('debug.security.firewall.authenticator.'.$id, TraceableAuthenticatorManagerListener::class)
511+
->setDecoratedService('security.firewall.authenticator.'.$id)
512+
->setArguments([new Reference('debug.security.firewall.authenticator.'.$id.'.inner')])
513+
;
514+
}
515+
507516
// user checker listener
508517
$container
509518
->setDefinition('security.listener.user_checker.'.$id, new ChildDefinition('security.listener.user_checker'))
@@ -542,11 +551,17 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
542551

543552
foreach ($this->getSortedFactories() as $factory) {
544553
$key = str_replace('-', '_', $factory->getKey());
545-
if (\array_key_exists($key, $firewall)) {
554+
if ('custom_authenticators' !== $key && \array_key_exists($key, $firewall)) {
546555
$listenerKeys[] = $key;
547556
}
548557
}
549558

559+
if ($firewall['custom_authenticators'] ?? false) {
560+
foreach ($firewall['custom_authenticators'] as $customAuthenticatorId) {
561+
$listenerKeys[] = $customAuthenticatorId;
562+
}
563+
}
564+
550565
$config->replaceArgument(10, $listenerKeys);
551566
$config->replaceArgument(11, $firewall['switch_user'] ?? null);
552567

0 commit comments

Comments
 (0)