From a3a9a0e30a224b9c690489e6d106b6886d1b6386 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Tue, 7 Jan 2020 05:14:48 +0100 Subject: [PATCH] [SecurityBundle] Fix collecting traceable listeners info using anonymous: lazy --- .../Debug/TraceableFirewallListener.php | 44 ++++++++++-- .../Debug/TraceableListenerTrait.php | 42 +++++++++++ .../Debug/WrappedLazyListener.php | 72 +++++++++++++++++++ .../SecurityBundle/Debug/WrappedListener.php | 45 +----------- 4 files changed, 156 insertions(+), 47 deletions(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php index 62be170ddc..e7f9df1221 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php @@ -12,7 +12,10 @@ namespace Symfony\Bundle\SecurityBundle\Debug; use Symfony\Bundle\SecurityBundle\EventListener\FirewallListener; +use Symfony\Bundle\SecurityBundle\Security\FirewallContext; +use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext; use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Security\Http\Firewall\AbstractListener; /** * Firewall collecting called listeners. @@ -21,7 +24,7 @@ use Symfony\Component\HttpKernel\Event\RequestEvent; */ final class TraceableFirewallListener extends FirewallListener { - private $wrappedListeners; + private $wrappedListeners = []; public function getWrappedListeners() { @@ -30,14 +33,47 @@ final class TraceableFirewallListener extends FirewallListener protected function callListeners(RequestEvent $event, iterable $listeners) { + $wrappedListeners = []; + $wrappedLazyListeners = []; + foreach ($listeners as $listener) { - $wrappedListener = new WrappedListener($listener); - $wrappedListener($event); - $this->wrappedListeners[] = $wrappedListener->getInfo(); + if ($listener instanceof LazyFirewallContext) { + \Closure::bind(function () use (&$wrappedLazyListeners, &$wrappedListeners) { + $listeners = []; + foreach ($this->listeners as $listener) { + if ($listener instanceof AbstractListener) { + $listener = new WrappedLazyListener($listener); + $listeners[] = $listener; + $wrappedLazyListeners[] = $listener; + } else { + $listeners[] = function (RequestEvent $event) use ($listener, &$wrappedListeners) { + $wrappedListener = new WrappedListener($listener); + $wrappedListener($event); + $wrappedListeners[] = $wrappedListener->getInfo(); + }; + } + } + $this->listeners = $listeners; + }, $listener, FirewallContext::class)(); + + $listener($event); + } else { + $wrappedListener = $listener instanceof AbstractListener ? new WrappedLazyListener($listener) : new WrappedListener($listener); + $wrappedListener($event); + $wrappedListeners[] = $wrappedListener->getInfo(); + } if ($event->hasResponse()) { break; } } + + if ($wrappedLazyListeners) { + foreach ($wrappedLazyListeners as $lazyListener) { + $this->wrappedListeners[] = $lazyListener->getInfo(); + } + } + + $this->wrappedListeners = array_merge($this->wrappedListeners, $wrappedListeners); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php new file mode 100644 index 0000000000..7b65d86b75 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Debug; + +use Symfony\Component\Security\Http\Firewall\LegacyListenerTrait; + +/** + * @author Robin Chalas + * + * @internal + */ +trait TraceableListenerTrait +{ + use LegacyListenerTrait; + + private $response; + private $listener; + private $time; + private $stub; + + /** + * Proxies all method calls to the original listener. + */ + public function __call(string $method, array $arguments) + { + return $this->listener->{$method}(...$arguments); + } + + public function getWrappedListener() + { + return $this->listener; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php new file mode 100644 index 0000000000..649a4065ef --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Debug; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Security\Core\Exception\LazyResponseException; +use Symfony\Component\Security\Http\Firewall\AbstractListener; +use Symfony\Component\Security\Http\Firewall\ListenerInterface; +use Symfony\Component\VarDumper\Caster\ClassStub; + +/** + * Wraps a lazy security listener. + * + * @author Robin Chalas + * + * @internal + */ +final class WrappedLazyListener extends AbstractListener implements ListenerInterface +{ + use TraceableListenerTrait; + + public function __construct(AbstractListener $listener) + { + $this->listener = $listener; + } + + public function supports(Request $request): ?bool + { + return $this->listener->supports($request); + } + + /** + * {@inheritdoc} + */ + public function authenticate(RequestEvent $event) + { + $startTime = microtime(true); + + try { + $ret = $this->listener->authenticate($event); + } catch (LazyResponseException $e) { + $this->response = $e->getResponse(); + + throw $e; + } finally { + $this->time = microtime(true) - $startTime; + } + + $this->response = $event->getResponse(); + + return $ret; + } + + public function getInfo(): array + { + return [ + 'response' => $this->response, + 'time' => $this->time, + 'stub' => $this->stub ?? $this->stub = ClassStub::wrapCallable($this->listener), + ]; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php index 0bc7fdda9e..e7728f2fae 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php @@ -12,7 +12,6 @@ namespace Symfony\Bundle\SecurityBundle\Debug; use Symfony\Component\HttpKernel\Event\RequestEvent; -use Symfony\Component\Security\Http\Firewall\LegacyListenerTrait; use Symfony\Component\Security\Http\Firewall\ListenerInterface; use Symfony\Component\VarDumper\Caster\ClassStub; @@ -25,13 +24,7 @@ use Symfony\Component\VarDumper\Caster\ClassStub; */ final class WrappedListener implements ListenerInterface { - use LegacyListenerTrait; - - private $response; - private $listener; - private $time; - private $stub; - private static $hasVarDumper; + use TraceableListenerTrait; /** * @param callable $listener @@ -57,46 +50,12 @@ final class WrappedListener implements ListenerInterface $this->response = $event->getResponse(); } - /** - * Proxies all method calls to the original listener. - */ - public function __call(string $method, array $arguments) - { - return $this->listener->{$method}(...$arguments); - } - - public function getWrappedListener() - { - return $this->listener; - } - public function getInfo(): array { - if (null !== $this->stub) { - // no-op - } elseif (self::$hasVarDumper ?? self::$hasVarDumper = class_exists(ClassStub::class)) { - $this->stub = ClassStub::wrapCallable($this->listener); - } elseif (\is_array($this->listener)) { - $this->stub = (\is_object($this->listener[0]) ? \get_class($this->listener[0]) : $this->listener[0]).'::'.$this->listener[1]; - } elseif ($this->listener instanceof \Closure) { - $r = new \ReflectionFunction($this->listener); - if (false !== strpos($r->name, '{closure}')) { - $this->stub = 'closure'; - } elseif ($class = $r->getClosureScopeClass()) { - $this->stub = $class->name.'::'.$r->name; - } else { - $this->stub = $r->name; - } - } elseif (\is_string($this->listener)) { - $this->stub = $this->listener; - } else { - $this->stub = \get_class($this->listener).'::__invoke'; - } - return [ 'response' => $this->response, 'time' => $this->time, - 'stub' => $this->stub, + 'stub' => $this->stub ?? $this->stub = ClassStub::wrapCallable($this->listener), ]; } }