8000 [Security] Added debug:firewall command · symfony/symfony@a9dea1d · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a9dea1d

Browse files
committed
[Security] Added debug:firewall command
1 parent d1fbf75 commit a9dea1d

File tree

4 files changed

+318
-1
lines changed

4 files changed

+318
-1
lines changed

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.3
55
---
66

7+
* Add the `debug:firewall` command.
78
* Deprecate `UserPasswordEncoderCommand` class and the corresponding `user:encode-password` command,
89
use `UserPasswordHashCommand` and `user:hash-password` instead
910
* Deprecate the `security.encoder_factory.generic` service, the `security.encoder_factory` and `Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface` aliases,
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Command;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Bundle\SecurityBundle\Security\FirewallContext;
16+
use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext;
17+
use Symfony\Component\Console\Command\Command;
18+
use Symfony\Component\Console\Input\InputArgument;
19+
use Symfony\Component\Console\Input\InputInterface;
20+
use Symfony\Component\Console\Input\InputOption;
21+
use Symfony\Component\Console\Output\OutputInterface;
22+
use Symfony\Component\Console\Style\SymfonyStyle;
23+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
24+
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
25+
26+
/**
27+
* @author Timo Bakx <timobakx@gmail.com>
28+
*/
29+
final class DebugFirewallCommand extends Command
30+
{
31+
protected static $defaultName = 'debug:firewall';
32+
protected static $defaultDescription = 'Displays information about your security firewall(s)';
33+
34+
private $firewallNames;
35+
private $contexts;
36+
private $eventDispatchers;
37+
private $authenticators;
38+
private $authenticatorManagerEnabled;
39+
40+
/**
41+
* @param string[] $firewallNames
42+
* @param AuthenticatorInterface[][] $authenticators
43+
*/
44+
public function __construct(array $firewallNames, ContainerInterface $contexts, ContainerInterface $eventDispatchers, array $authenticators, bool $authenticatorManagerEnabled)
45+
{
46+
$this->firewallNames = $firewallNames;
47+
$this->contexts = $contexts;
48+
$this->eventDispatchers = $eventDispatchers;
49+
$this->authenticators = $authenticators;
50+
$this->authenticatorManagerEnabled = $authenticatorManagerEnabled;
51+
52+
parent::__construct();
53+
}
54+
55+
protected function configure(): void
56+
{
57+
$exampleName = $this->getExampleName();
58+
59+
$this
60+
->setDescription(self::$defaultDescription)
61+
->setHelp(<<<EOF
62+
The <info>%command.name%</info> command displays the firewalls that are configured
63+
in your application:
64+
65+
<info>php %command.full_name%</info>
66+
67+
You can pass a firewall name to display more detailed information about
68+
a specific firewall:
69+
70+
<info>php %command.full_name% $exampleName</info>
71+
72+
To include all events and event listeners for a specific firewall, use the
73+
<info>events</info> option:
74+
75+
<info>php %command.full_name% --events $exampleName</info>
76+
77+
EOF)
78+
->setDefinition([
79+
new InputArgument('name', InputArgument::OPTIONAL, sprintf('A firewall name (for example "%s")', $exampleName)),
80+
new InputOption('events', null, InputOption::VALUE_NONE, 'Include a list of event listeners (only available in combination with the "name" argument)'),
81+
]);
82+
}
83+
84+
protected function execute(InputInterface $input, OutputInterface $output): int
85+
{
86+
$io = new SymfonyStyle($input, $output);
87+
88+
$name = $input->getArgument('name');
89+
90+
if (null === $name) {
91+
$this->displayFirewallList($io);
92+
93+
return 0;
94+
}
95+
96+
$serviceId = sprintf('security.firewall.map.context.%s', $name);
97+
98+
if (!$this->contexts->has($serviceId)) {
99+
$io->error(sprintf('Firewall %s was not found. Available firewalls are: %s', $name, implode(', ', $this->firewallNames)));
100+
101+
return 1;
102+
}
103+
104+
/** @var FirewallContext $context */
105+
$context = $this->contexts->get($serviceId);
106+
107+
$io->title(sprintf('Firewall "%s"', $name));
108+
109+
$this->displayFirewallSummary($name, $context, $io);
110+
111+
$this->displaySwitchUser($context, $io);
112+
113+
if ($input->getOption('events')) {
114+
$this->displayEventListeners($name, $context, $io);
115+
}
116+
117+
if ($this->authenticatorManagerEnabled) {
118+
$this->displayAuthenticators($name, $io);
119+
}
120+
121+
return 0;
122+
}
123+
124+
protected function displayFirewallList(SymfonyStyle $io): void
125+
{
126+
$io->title('Firewalls');
127+
$io->text('The following firewalls are defined:');
128+
129+
$io->listing($this->firewallNames);
130+
131+
$io->comment(sprintf('To view details of a specific firewall, re-run this command with a firewall name. (e.g. <comment>debug:firewall %s</comment>)', $this->getExampleName()));
132+
}
133+
134+
protected function displayFirewallSummary(string $name, FirewallContext $context, SymfonyStyle $io): void
135+
{
136+
if (null === $context->getConfig()) {
137+
return;
B4EE
138+
}
139+
140+
$rows = [
141+
['Name', $name],
142+
['Context', $context->getConfig()->getContext()],
143+
['Lazy', $context instanceof LazyFirewallContext ? 'Yes' : 'No'],
144+
['Stateless', $context->getConfig()->isStateless() ? 'Yes' : 'No'],
145+
['User Checker', $context->getConfig()->getUserChecker()],
146+
['Provider', $context->getConfig()->getProvider()],
147+
['Entry Point', $context->getConfig()->getEntryPoint()],
148+
['Access Denied URL', $context->getConfig()->getAccessDeniedUrl()],
149+
['Access Denied Handler', $context->getConfig()->getAccessDeniedHandler()],
150+
];
151+
152+
$io->table(
153+
['Option', 'Value'],
154+
$rows
155+
);
156+
}
157+
158+
private function displaySwitchUser(FirewallContext $context, SymfonyStyle $io)
159+
{
160+
if ((null === $config = $context->getConfig()) || (null === $switchUser = $config->getSwitchUser())) {
161+
return;
162+
}
163+
164+
$io->section('User switching');
165+
166+
$io->table(['Option', 'Value'], [
167+
['Parameter', $switchUser['parameter'] ?? ''],
168+
['Provider', $switchUser['provider'] ?? $config->getProvider()],
169+
['User Role', $switchUser['role'] ?? ''],
170+
]);
171+
}
172+
173+
protected function displayEventListeners(string $name, FirewallContext $context, SymfonyStyle $io): void
174+
{
175+
$io->title(sprintf('Event listeners for firewall "%s"', $name));
176+
177+
$dispatcherId = sprintf('security.event_dispatcher.%s', $name);
178+
179+
if (!$this->eventDispatchers->has($dispatcherId)) {
180+
$io->text('No event dispatcher has been registered for this firewall.');
181+
182+
return;
183+
}
184+
185+
/** @var EventDispatcherInterface $dispatcher */
186+
$dispatcher = $this->eventDispatchers->get($dispatcherId);
187+
188+
foreach ($dispatcher->getListeners() as $event => $listeners) {
189+
$io->section(sprintf('"%s" event', $event));
190+
191+
$rows = [];
192+
foreach ($listeners as $order => $listener) {
193+
$rows[] = [
194+
sprintf('#%d', $order + 1),
195+
$this->formatCallable($listener),
196+
$dispatcher->getListenerPriority($event, $listener),
197+
];
198+
}
199+
200+
$io->table(
201+
['Order', 'Callable', 'Priority'],
202+
$rows
203+
);
204+
}
205+
}
206+
207+
private function displayAuthenticators(string $name, SymfonyStyle $io): void
208+
{
209+
$io->title(sprintf('Authenticators for firewall "%s"', $name));
210+
211+
$authenticators = $this->authenticators[$name] ?? [];
212+
213+
if (0 === \count($authenticators)) {
214+
$io->text('No authenticators have been registered for this firewall.');
215+
216+
return;
217+
}
218+
219+
$io->table(
220+
['Classname'],
221+
array_map(
222+
static function ($authenticator) {
223+
return [
224+
\get_class($authenticator),
225+
];
226+
},
227+
$authenticators
228+
)
229+
);
230+
}
231+
232+
private function formatCallable($callable): string
233+
{
234+
if (\is_array($callable)) {
235+
if (\is_object($callable[0])) {
236+
return sprintf('%s::%s()', \get_class($callable[0]), $callable[1]);
237+
}
238+
239+
return sprintf('%s::%s()', $callable[0], $callable[1]);
240+
}
241+
242+
if (\is_string($callable)) {
243+
return sprintf('%s()', $callable);
244+
}
245+
246+
if ($callable instanceof \Closure) {
247+
$r = new \ReflectionFunction($callable);
248+
if (false !== strpos($r->name, '{closure}')) {
249+
return 'Closure()';
250+
}
251+
if ($class = $r->getClosureScopeClass()) {
252+
return sprintf('%s::%s()', $class->name, $r->name);
253+
}
254+
255+
return $r->name.'()';
256+
}
257+
258+
if (method_exists($callable, '__invoke')) {
259+
return sprintf('%s::__invoke()', \get_class($callable));
260+
}
261+
262+
throw new \InvalidArgumentException('Callable is not describable.');
263+
}
264+
265+
private function getExampleName(): string
266+
{
267+
$name = 'main';
268+
269+
if (!\in_array($name, $this->firewallNames, true)) {
270+
$name = reset($this->firewallNames);
271+
}
272+
273+
return $name;
274+
}
275+
}

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ public function load(array $configs, ContainerBuilder $container)
164164
$container->setParameter('security.access.always_authenticate_before_granting', $config['always_authenticate_before_granting']);
165165
$container->setParameter('security.authentication.hide_user_not_found', $config['hide_user_not_found']);
166166

167+
if (class_exists(Application::class)) {
168+
$loader->load('debug_console.php');
169+
$debugCommand = $container->getDefinition('security.command.debug_firewall');
170+
$debugCommand->replaceArgument(4, $this->authenticatorManagerEnabled);
171+
}
172+
167173
$this->createFirewalls($config, $container);
168174
$this->createAuthorization($config, $container);
169175
$this->createRoleHierarchy($config, $container);
@@ -298,7 +304,10 @@ private function createFirewalls(array $config, ContainerBuilder $container)
298304
$contextRefs[$contextId] = new Reference($contextId);
299305
$map[$contextId] = $matcher;
300306
}
301-
$mapDef->replaceArgument(0, ServiceLocatorTagPass::register($container, $contextRefs));
307+
308+
$container->setAlias('security.firewall.context_locator', (string) ServiceLocatorTagPass::register($container, $contextRefs));
309+
310+
$mapDef->replaceArgument(0, new Reference('security.firewall.context_locator'));
302311
$mapDef->replaceArgument(1, new IteratorArgument($map));
303312

304313
if (!$this->authenticatorManagerEnabled) {
@@ -503,6 +512,10 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
503512
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
504513

505514
$listeners[] = new Reference('security.firewall.authenticator.'.$id);
515+
516+
// Add authenticators to the debug:firewall command
517+
$debugCommand = $container->getDefinition('security.command.debug_firewall');
518+
$debugCommand->replaceArgument(3, array_merge($debugCommand->getArgument(3), [$id => $authenticators]));
506519
}
507520

508521
$config->replaceArgument(7, $configuredEntryPoint ?: $defaultEntryPoint);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Component\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\Bundle\SecurityBundle\Command\DebugFirewallCommand;
15+
16+
return static function (ContainerConfigurator $container) {
17+
$container->services()
18+
->set('security.command.debug_firewall', DebugFirewallCommand::class)
19+
->args([
20+
param('security.firewalls'),
21+
service('security.firewall.context_locator'),
22+
tagged_locator('event_dispatcher.dispatcher'),
23+
[],
24+
false,
25+
])
26+
->tag('console.command', ['command' => 'debug:firewall'])
27+
;
28+
};

0 commit comments

Comments
 (0)
0