Skip to content

Commit 2186549

Browse files
committed
Add entry point if one or more authenticators exist on chosen firewall
1 parent c63a86a commit 2186549

File tree

14 files changed

+534
-16
lines changed

14 files changed

+534
-16
lines changed

src/Maker/MakeAuthenticator.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,13 @@ final class MakeAuthenticator extends AbstractMaker
3737

3838
private $configUpdater;
3939

40-
public function __construct(FileManager $fileManager, SecurityConfigUpdater $configUpdater)
40+
private $generator;
41+
42+
public function __construct(FileManager $fileManager, SecurityConfigUpdater $configUpdater, Generator $generator)
4143
{
4244
$this->fileManager = $fileManager;
4345
$this->configUpdater = $configUpdater;
46+
$this->generator = $generator;
4447
}
4548

4649
public static function getCommandName(): string
@@ -69,8 +72,12 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma
6972
$securityData = $manipulator->getData();
7073

7174
$interactiveSecurityHelper = new InteractiveSecurityHelper();
75+
7276
$command->addOption('firewall-name', null, InputOption::VALUE_OPTIONAL, '');
7377
$interactiveSecurityHelper->guessFirewallName($input, $io, $securityData);
78+
79+
$command->addOption('entry-point', null, InputOption::VALUE_OPTIONAL);
80+
$interactiveSecurityHelper->guessEntryPoint($input, $io, $this->generator, $securityData);
7481
}
7582

7683
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
@@ -93,6 +100,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
93100
$newYaml = $this->configUpdater->updateForAuthenticator(
94101
$this->fileManager->getFileContents($path),
95102
$input->getOption('firewall-name'),
103+
$input->getOption('entry-point'),
96104
$classNameDetails->getFullName()
97105
);
98106
$generator->dumpFile($path, $newYaml);

src/Resources/config/makers.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<service id="maker.maker.make_authenticator" class="Symfony\Bundle\MakerBundle\Maker\MakeAuthenticator">
1111
<argument type="service" id="maker.file_manager" />
1212
<argument type="service" id="maker.security_config_updater" />
13+
<argument type="service" id="maker.generator" />
1314
<tag name="maker.command" />
1415
</service>
1516

src/Security/InteractiveSecurityHelper.php

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,25 @@
1111

1212
namespace Symfony\Bundle\MakerBundle\Security;
1313

14-
use Symfony\Bundle\MakerBundle\ConsoleStyle;
14+
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
15+
use Symfony\Bundle\MakerBundle\Generator;
1516
use Symfony\Component\Console\Input\InputInterface;
17+
use Symfony\Component\Console\Style\SymfonyStyle;
1618

1719
/**
1820
* @internal
1921
*/
2022
final class InteractiveSecurityHelper
2123
{
22-
public function guessFirewallName(InputInterface $input, ConsoleStyle $io, array $securityData)
24+
/**
25+
* @param InputInterface $input
26+
* @param SymfonyStyle $io
27+
* @param array $securityData
28+
*/
29+
public function guessFirewallName(InputInterface $input, SymfonyStyle $io, array $securityData)
2330
{
2431
$firewalls = array_filter(
25-
$securityData['security']['firewalls'],
32+
$securityData['security']['firewalls'] ?? [],
2633
function ($item) {
2734
return !isset($item['security']) || true === $item['security'];
2835
}
@@ -41,4 +48,47 @@ function ($item) {
4148
$firewallName = $io->choice('Which firewall you want to update ?', array_keys($firewalls), key($firewalls));
4249
$input->setOption('firewall-name', $firewallName);
4350
}
51+
52+
/**
53+
* @param InputInterface $input
54+
* @param SymfonyStyle $io
55+
* @param Generator $generator
56+
* @param array $securityData
57+
*/
58+
public function guessEntryPoint(InputInterface $input, SymfonyStyle $io, Generator $generator, array $securityData)
59+
{
60+
$firewallName = $input->getOption('firewall-name');
61+
if (!$firewallName) {
62+
throw new RuntimeCommandException("Option \"firewall-name\" must be provided.");
63+
}
64+
65+
if (!isset($securityData['security'])) {
66+
$securityData['security'] = [];
67+
}
68+
69+
if (!isset($securityData['security']['firewalls'])) {
70+
$securityData['security']['firewalls'] = [];
71+
}
72+
73+
$firewalls = $securityData['security']['firewalls'];
74+
if (!isset($firewalls[$firewallName])) {
75+
throw new RuntimeCommandException("Firewall \"$firewallName\" does not exist");
76+
}
77+
78+
if (!isset($firewalls[$firewallName]['guard'])
79+
|| !isset($firewalls[$firewallName]['guard']['authenticators'])
80+
|| !$firewalls[$firewallName]['guard']['authenticators']) {
81+
return;
82+
}
83+
84+
$authenticators = $firewalls[$firewallName]['guard']['authenticators'];
85+
$classNameDetails = $generator->createClassNameDetails(
86+
$input->getArgument('authenticator-class'),
87+
'Security\\'
88+
);
89+
$authenticators[] = $classNameDetails->getFullName();
90+
91+
$entryPoint = $io->choice('Which authenticator will be the entry point ?', $authenticators, current($authenticators));
92+
$input->setOption('entry-point', $entryPoint);
93+
}
4494
}

src/Security/SecurityConfigUpdater.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,15 @@ public function updateForUserClass(string $yamlSource, UserClassConfiguration $u
4848
return $contents;
4949
}
5050

51-
public function updateForAuthenticator(string $yamlSource, string $firewallName, string $userClass)
51+
/**
52+
* @param string $yamlSource
53+
* @param string $firewallName
54+
* @param string $chosenEntryPoint
55+
* @param string $authenticatorClass
56+
*
57+
* @return string
58+
*/
59+
public function updateForAuthenticator(string $yamlSource, string $firewallName, $chosenEntryPoint, string $authenticatorClass)
5260
{
5361
$this->manipulator = new YamlSourceManipulator($yamlSource);
5462

@@ -74,10 +82,10 @@ public function updateForAuthenticator(string $yamlSource, string $firewallName,
7482
$firewall['guard']['authenticators'] = [];
7583
}
7684

77-
$firewall['guard']['authenticators'][] = $userClass;
85+
$firewall['guard']['authenticators'][] = $authenticatorClass;
7886

7987
if (count($firewall['guard']['authenticators']) > 1) {
80-
$firewall['guard']['entry_point'] = current($firewall['guard']['authenticators']);
88+
$firewall['guard']['entry_point'] = $chosenEntryPoint ?? current($firewall['guard']['authenticators']);
8189
}
8290

8391
$newData['security']['firewalls'][$firewallName] = $firewall;

tests/Maker/FunctionalTest.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,67 @@ function (string $output, string $directory) {
342342
),
343343
];
344344

345+
yield 'auth_empty_existing_authenticator' => [
346+
MakerTestDetails::createTest(
347+
$this->getMakerInstance(MakeAuthenticator::class),
348+
[
349+
// class name
350+
'AppCustomAuthenticator',
351+
1
352+
]
353+
)
354+
->addExtraDependencies('security')
355+
->setFixtureFilesPath(__DIR__.'/../fixtures/MakeAuthenticatorExistingAuthenticator')
356+
->setRequiredPhpVersion(70100)
357+
->assert(
358+
function (string $output, string $directory) {
359+
$this->assertContains('Success', $output);
360+
361+
$finder = new Finder();
362+
$finder->in($directory.'/src/Security')
363+
->name('AppCustomAuthenticator.php');
364+
$this->assertCount(1, $finder);
365+
366+
$securityConfig = Yaml::parse(file_get_contents("$directory/config/packages/security.yaml"));
367+
$this->assertEquals(
368+
'App\\Security\\AppCustomAuthenticator',
369+
$securityConfig['security']['firewalls']['main']['guard']['entry_point']
370+
);
371+
}
372+
),
373+
];
374+
375+
yield 'auth_empty_multiple_firewalls_existing_authenticator' => [
376+
MakerTestDetails::createTest(
377+
$this->getMakerInstance(MakeAuthenticator::class),
378+
[
379+
// class name
380+
'AppCustomAuthenticator',
381+
1,
382+
1
383+
]
384+
)
385+
->addExtraDependencies('security')
386+
->setFixtureFilesPath(__DIR__.'/../fixtures/MakeAuthenticatorMultipleFirewallsExistingAuthenticator')
387+
->setRequiredPhpVersion(70100)
388+
->assert(
389+
function (string $output, string $directory) {
390+
$this->assertContains('Success', $output);
391+
392+
$finder = new Finder();
393+
$finder->in($directory.'/src/Security')
394+
->name('AppCustomAuthenticator.php');
395+
$this->assertCount(1, $finder);
396+
397+
$securityConfig = Yaml::parse(file_get_contents("$directory/config/packages/security.yaml"));
398+
$this->assertEquals(
399+
'App\\Security\\AppCustomAuthenticator',
400+
$securityConfig['security']['firewalls']['second']['guard']['entry_point']
401+
);
402+
}
403+
),
404+
];
405+
345406
yield 'user_security_entity_with_password' => [MakerTestDetails::createTest(
346407
$this->getMakerInstance(MakeUser::class),
347408
[
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\MakerBundle\Tests\Security;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Symfony\Bundle\MakerBundle\Generator;
7+
use Symfony\Bundle\MakerBundle\Security\InteractiveSecurityHelper;
8+
use Symfony\Bundle\MakerBundle\Util\ClassNameDetails;
9+
use Symfony\Component\Console\Input\InputInterface;
10+
use Symfony\Component\Console\Style\SymfonyStyle;
11+
12+
class InteractiveSecurityHelperTest extends TestCase
13+
{
14+
/**
15+
* @dataProvider getFirewallNameTests
16+
*
17+
* @param array $securityData
18+
* @param string $expectedFirewallName
19+
* @param bool $multipleValues
20+
*/
21+
public function testGuessFirewallName(array $securityData, string $expectedFirewallName, $multipleValues = false)
22+
{
23+
/** @var InputInterface|\PHPUnit_Framework_MockObject_MockObject $input */
24+
$input = $this->createMock(InputInterface::class);
25+
$input->expects($this->once())
26+
->method('setOption')
27+
->with('firewall-name', $expectedFirewallName);
28+
29+
/** @var SymfonyStyle|\PHPUnit_Framework_MockObject_MockObject $io */
30+
$io = $this->createMock(SymfonyStyle::class);
31+
$io->expects($this->exactly(false === $multipleValues ? 0 : 1))
32+
->method('choice')
33+
->willReturn($expectedFirewallName);
34+
35+
$helper = new InteractiveSecurityHelper();
36+
$helper->guessFirewallName($input, $io, $securityData);
37+
}
38+
39+
public function getFirewallNameTests()
40+
{
41+
yield 'empty_security' => [
42+
[],
43+
'main',
44+
];
45+
46+
yield 'no_firewall' => [
47+
['security' => ['firewalls' => []]],
48+
'main',
49+
];
50+
51+
yield 'no_secured_firewall' => [
52+
['security' => ['firewalls' => ['dev' => ['security' => false]]]],
53+
'main',
54+
];
55+
56+
yield 'main_firewall' => [
57+
['security' => ['firewalls' => ['dev' => ['security' => false], 'main' => null]]],
58+
'main',
59+
];
60+
61+
yield 'foo_firewall' => [
62+
['security' => ['firewalls' => ['dev' => ['security' => false], 'foo' => null]]],
63+
'foo',
64+
];
65+
66+
yield 'foo_bar_firewalls_1' => [
67+
['security' => ['firewalls' => ['dev' => ['security' => false], 'foo' => null, 'bar' => null]]],
68+
'foo',
69+
true,
70+
];
71+
72+
yield 'foo_bar_firewalls_2' => [
73+
['security' => ['firewalls' => ['dev' => ['security' => false], 'foo' => null, 'bar' => null]]],
74+
'bar',
75+
true,
76+
];
77+
}
78+
79+
/**
80+
* @expectedException \Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException
81+
*/
82+
public function testGuessEntryPointWithNoFirewallNameThrowsException()
83+
{
84+
/** @var InputInterface|\PHPUnit_Framework_MockObject_MockObject $input */
85+
$input = $this->createMock(InputInterface::class);
86+
87+
/** @var SymfonyStyle|\PHPUnit_Framework_MockObject_MockObject $io */
88+
$io = $this->createMock(SymfonyStyle::class);
89+
90+
/** @var Generator|\PHPUnit_Framework_MockObject_MockObject $generator */
91+
$generator = $this->createMock(Generator::class);
92+
93+
$helper = new InteractiveSecurityHelper();
94+
$helper->guessEntryPoint($input, $io, $generator, []);
95+
}
96+
97+
/**
98+
* @expectedException \Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException
99+
*/
100+
public function testGuessEntryPointWithNonExistingFirewallThrowsException()
101+
{
102+
/** @var InputInterface|\PHPUnit_Framework_MockObject_MockObject $input */
103+
$input = $this->createMock(InputInterface::class);
104+
$input->expects($this->once())
105+
->method('getOption')
106+
->with('firewall-name')
107+
->willReturn('foo');
108+
109+
/** @var SymfonyStyle|\PHPUnit_Framework_MockObject_MockObject $io */
110+
$io = $this->createMock(SymfonyStyle::class);
111+
112+
/** @var Generator|\PHPUnit_Framework_MockObject_MockObject $generator */
113+
$generator = $this->createMock(Generator::class);
114+
115+
$helper = new InteractiveSecurityHelper();
116+
$helper->guessEntryPoint($input, $io, $generator, []);
117+
}
118+
119+
/**
120+
* @dataProvider getEntryPointTests
121+
*/
122+
public function testGuestEntryPoint(array $securityData, string $firewallName, bool $multipleAuthenticators = false)
123+
{
124+
/** @var InputInterface|\PHPUnit_Framework_MockObject_MockObject $input */
125+
$input = $this->createMock(InputInterface::class);
126+
$input->expects($this->once())
127+
->method('getOption')
128+
->with('firewall-name')
129+
->willReturn($firewallName);
130+
131+
$input->expects($this->exactly(false === $multipleAuthenticators ? 0 : 1))
132+
->method('getArgument')
133+
->with('authenticator-class')
134+
->willReturn('NewAuthenticator');
135+
136+
137+
/** @var SymfonyStyle|\PHPUnit_Framework_MockObject_MockObject $io */
138+
$io = $this->createMock(SymfonyStyle::class);
139+
$io->expects($this->exactly(false === $multipleAuthenticators ? 0 : 1))
140+
->method('choice');
141+
142+
/** @var Generator|\PHPUnit_Framework_MockObject_MockObject $generator */
143+
$generator = $this->createMock(Generator::class);
144+
$generator->expects($this->exactly(false === $multipleAuthenticators ? 0 : 1))
145+
->method('createClassNameDetails')
146+
->willReturn(new ClassNameDetails('App\\Security\\NewAuthenticator', 'App\\Security'));
147+
148+
$helper = new InteractiveSecurityHelper();
149+
$helper->guessEntryPoint($input, $io, $generator, $securityData);
150+
}
151+
152+
public function getEntryPointTests()
153+
{
154+
yield 'no_guard' => [
155+
['security' => ['firewalls' => ['main' => []]]],
156+
'main'
157+
];
158+
159+
yield 'no_authenticators_key' => [
160+
['security' => ['firewalls' => ['main' => ['guard' => []]]]],
161+
'main'
162+
];
163+
164+
yield 'no_authenticator' => [
165+
['security' => ['firewalls' => ['main' => ['guard' => ['authenticators' => []]]]]],
166+
'main'
167+
];
168+
169+
yield 'one_authenticator' => [
170+
['security' => ['firewalls' => ['main' => ['guard' => ['authenticators' => ['App\\Security\\Authenticator']]]]]],
171+
'main',
172+
true
173+
];
174+
}
175+
}

0 commit comments

Comments
 (0)