Skip to content

Commit c63a86a

Browse files
committed
Add authenticator : let the user chose which firewall to update
1 parent 2fb5b40 commit c63a86a

File tree

11 files changed

+264
-24
lines changed

11 files changed

+264
-24
lines changed

src/Maker/MakeAuthenticator.php

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@
1616
use Symfony\Bundle\MakerBundle\FileManager;
1717
use Symfony\Bundle\MakerBundle\Generator;
1818
use Symfony\Bundle\MakerBundle\InputConfiguration;
19+
use Symfony\Bundle\MakerBundle\Security\InteractiveSecurityHelper;
1920
use Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater;
2021
use Symfony\Bundle\MakerBundle\Util\YamlManipulationFailedException;
2122
use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator;
2223
use Symfony\Component\Console\Command\Command;
24+
use Symfony\Component\Console\Exception\InvalidOptionException;
2325
use Symfony\Component\Console\Input\InputArgument;
2426
use Symfony\Bundle\SecurityBundle\SecurityBundle;
2527
use Symfony\Component\Console\Input\InputInterface;
28+
use Symfony\Component\Console\Input\InputOption;
29+
use Symfony\Component\Filesystem\Filesystem;
2630

2731
/**
2832
* @author Ryan Weaver <[email protected]>
@@ -53,6 +57,22 @@ public function configureCommand(Command $command, InputConfiguration $inputConf
5357
;
5458
}
5559

60+
public function interact(InputInterface $input, ConsoleStyle $io, Command $command)
61+
{
62+
$fs = new Filesystem();
63+
64+
if (!$fs->exists($path = 'config/packages/security.yaml')) {
65+
return;
66+
}
67+
68+
$manipulator = new YamlSourceManipulator($this->fileManager->getFileContents($path));
69+
$securityData = $manipulator->getData();
70+
71+
$interactiveSecurityHelper = new InteractiveSecurityHelper();
72+
$command->addOption('firewall-name', null, InputOption::VALUE_OPTIONAL, '');
73+
$interactiveSecurityHelper->guessFirewallName($input, $io, $securityData);
74+
}
75+
5676
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
5777
{
5878
$classNameDetails = $generator->createClassNameDetails(
@@ -66,14 +86,17 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
6686
[]
6787
);
6888

89+
$securityYamlUpdated = false;
6990
$path = 'config/packages/security.yaml';
7091
if ($this->fileManager->fileExists($path)) {
7192
try {
7293
$newYaml = $this->configUpdater->updateForAuthenticator(
7394
$this->fileManager->getFileContents($path),
95+
$input->getOption('firewall-name'),
7496
$classNameDetails->getFullName()
7597
);
7698
$generator->dumpFile($path, $newYaml);
99+
$securityYamlUpdated = true;
77100
} catch (YamlManipulationFailedException $e) {
78101
}
79102
}
@@ -82,10 +105,11 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
82105

83106
$this->writeSuccessMessage($io);
84107

85-
$io->text([
86-
'Next: Customize your new authenticator.',
87-
'Then, configure the "guard" key on your firewall to use it.',
88-
]);
108+
$text = ['Next: Customize your new authenticator.'];
109+
if (!$securityYamlUpdated) {
110+
$text[] = 'Then, configure the "guard" key on your firewall to use it.';
111+
}
112+
$io->text($text);
89113
}
90114

91115
public function configureDependencies(DependencyBuilder $dependencies)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle 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\MakerBundle\Security;
13+
14+
use Symfony\Bundle\MakerBundle\ConsoleStyle;
15+
use Symfony\Component\Console\Input\InputInterface;
16+
17+
/**
18+
* @internal
19+
*/
20+
final class InteractiveSecurityHelper
21+
{
22+
public function guessFirewallName(InputInterface $input, ConsoleStyle $io, array $securityData)
23+
{
24+
$firewalls = array_filter(
25+
$securityData['security']['firewalls'],
26+
function ($item) {
27+
return !isset($item['security']) || true === $item['security'];
28+
}
29+
);
30+
31+
if (count($firewalls) < 2) {
32+
if ($firewalls) {
33+
$input->setOption('firewall-name', key($firewalls));
34+
} else {
35+
$input->setOption('firewall-name', 'main');
36+
}
37+
38+
return;
39+
}
40+
41+
$firewallName = $io->choice('Which firewall you want to update ?', array_keys($firewalls), key($firewalls));
42+
$input->setOption('firewall-name', $firewallName);
43+
}
44+
}

src/Security/SecurityConfigUpdater.php

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

51-
public function updateForAuthenticator(string $yamlSource, string $authenticatorFQCN)
51+
public function updateForAuthenticator(string $yamlSource, string $firewallName, string $userClass)
5252
{
5353
$this->manipulator = new YamlSourceManipulator($yamlSource);
5454

@@ -60,18 +60,12 @@ public function updateForAuthenticator(string $yamlSource, string $authenticator
6060
$newData['security']['firewalls'] = [];
6161
}
6262

63-
$firewalls = array_filter(
64-
$newData['security']['firewalls'],
65-
function ($item) {
66-
return !isset($item['security']) || true === $item['security'];
67-
}
68-
);
69-
70-
if (!$firewalls) {
71-
$firewalls['main'] = ['anonymous' => true];
63+
if (!isset($newData['security']['firewalls'][$firewallName])) {
64+
$newData['security']['firewalls'][$firewallName] = [];
7265
}
7366

74-
$firewall = $firewalls['main'];
67+
$firewall = $newData['security']['firewalls'][$firewallName];
68+
7569
if (!isset($firewall['guard'])) {
7670
$firewall['guard'] = [];
7771
}
@@ -80,11 +74,14 @@ function ($item) {
8074
$firewall['guard']['authenticators'] = [];
8175
}
8276

83-
$firewall['guard']['authenticators'][] = $authenticatorFQCN;
77+
$firewall['guard']['authenticators'][] = $userClass;
8478

85-
$newData['security']['firewalls']['main'] = $firewall;
86-
$this->manipulator->setData($newData);
79+
if (count($firewall['guard']['authenticators']) > 1) {
80+
$firewall['guard']['entry_point'] = current($firewall['guard']['authenticators']);
81+
}
8782

83+
$newData['security']['firewalls'][$firewallName] = $firewall;
84+
$this->manipulator->setData($newData);
8885
$contents = $this->manipulator->getContents();
8986

9087
return $contents;

tests/Maker/FunctionalTest.php

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
use Symfony\Component\Finder\Finder;
3636
use Symfony\Component\HttpKernel\Kernel;
3737
use Symfony\Component\Routing\RouteCollectionBuilder;
38+
use Symfony\Component\Yaml\Yaml;
3839

3940
class FunctionalTest extends MakerTestCase
4041
{
@@ -282,12 +283,63 @@ public function getCommandTests()
282283
])
283284
];
284285

285-
yield 'auth_empty' => [MakerTestDetails::createTest(
286-
$this->getMakerInstance(MakeAuthenticator::class),
287-
[
288-
// class name
289-
'AppCustomAuthenticator',
290-
])
286+
yield 'auth_empty_one_firewall' => [
287+
MakerTestDetails::createTest(
288+
$this->getMakerInstance(MakeAuthenticator::class),
289+
[
290+
// class name
291+
'AppCustomAuthenticator',
292+
]
293+
)
294+
->addExtraDependencies('security')
295+
->setFixtureFilesPath(__DIR__.'/../fixtures/MakeAuthenticator')
296+
->setRequiredPhpVersion(70100)
297+
->assert(
298+
function (string $output, string $directory) {
299+
$this->assertContains('Success', $output);
300+
301+
$finder = new Finder();
302+
$finder->in($directory.'/src/Security')
303+
->name('AppCustomAuthenticator.php');
304+
$this->assertCount(1, $finder);
305+
306+
$securityConfig = Yaml::parse(file_get_contents("$directory/config/packages/security.yaml"));
307+
$this->assertEquals(
308+
'App\\Security\\AppCustomAuthenticator',
309+
$securityConfig['security']['firewalls']['main']['guard']['authenticators'][0]
310+
);
311+
}
312+
),
313+
];
314+
315+
yield 'auth_empty_multiple_firewalls' => [
316+
MakerTestDetails::createTest(
317+
$this->getMakerInstance(MakeAuthenticator::class),
318+
[
319+
// class name
320+
'AppCustomAuthenticator',
321+
1
322+
]
323+
)
324+
->addExtraDependencies('security')
325+
->setFixtureFilesPath(__DIR__.'/../fixtures/MakeAuthenticatorMultipleFirewalls')
326+
->setRequiredPhpVersion(70100)
327+
->assert(
328+
function (string $output, string $directory) {
329+
$this->assertContains('Success', $output);
330+
331+
$finder = new Finder();
332+
$finder->in($directory.'/src/Security')
333+
->name('AppCustomAuthenticator.php');
334+
$this->assertCount(1, $finder);
335+
336+
$securityConfig = Yaml::parse(file_get_contents("$directory/config/packages/security.yaml"));
337+
$this->assertEquals(
338+
'App\\Security\\AppCustomAuthenticator',
339+
$securityConfig['security']['firewalls']['second']['guard']['authenticators'][0]
340+
);
341+
}
342+
),
291343
];
292344

293345
yield 'user_security_entity_with_password' => [MakerTestDetails::createTest(

tests/Security/SecurityConfigUpdaterTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,39 @@ public function getUserClassTests()
6767
'empty_security.yaml'
6868
];
6969
}
70+
71+
/**
72+
* @dataProvider getAuthenticatorTests
73+
*/
74+
public function testUpdateForAuthenticator(string $firewallName, string $expectedSourceFilename, string $startingSourceFilename)
75+
{
76+
$updater = new SecurityConfigUpdater();
77+
$source = file_get_contents(__DIR__.'/yaml_fixtures/source/'.$startingSourceFilename);
78+
$actualSource = $updater->updateForAuthenticator($source, $firewallName, 'App\\Security\\AppCustomAuthenticator');
79+
$expectedSource = file_get_contents(__DIR__.'/yaml_fixtures/expected_authenticator/'.$expectedSourceFilename);
80+
81+
$this->assertSame($expectedSource, $actualSource);
82+
}
83+
84+
public function getAuthenticatorTests()
85+
{
86+
yield 'empty_source' => [
87+
'main',
88+
'empty_source.yaml',
89+
'empty_security.yaml'
90+
];
91+
92+
// waiting for YamlSourceManipulator to be debugged
93+
// yield 'empty_source' => [
94+
// 'main',
95+
// 'simple_security_source.yaml',
96+
// 'simple_security.yaml'
97+
// ];
98+
99+
yield 'simple_security_with_firewalls' => [
100+
'main',
101+
'simple_security_with_firewalls.yaml',
102+
'simple_security_with_firewalls.yaml'
103+
];
104+
}
70105
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
security:
2+
firewalls:
3+
main:
4+
guard:
5+
authenticators: [App\Security\AppCustomAuthenticator]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
security:
2+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
3+
providers:
4+
in_memory: { memory: ~ }
5+
6+
firewalls:
7+
dev: ~
8+
main: ~
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
security:
2+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
3+
providers:
4+
in_memory: { memory: ~ }
5+
6+
firewalls:
7+
dev:
8+
pattern: ^/(_(profiler|wdt)|css|images|js)/
9+
security: false
10+
main:
11+
anonymous: true
12+
guard:
13+
authenticators:
14+
- App\Security\AppCustomAuthenticator
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
security:
2+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
3+
providers:
4+
in_memory: { memory: ~ }
5+
6+
firewalls:
7+
dev:
8+
pattern: ^/(_(profiler|wdt)|css|images|js)/
9+
security: false
10+
main:
11+
anonymous: true
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
security:
2+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
3+
providers:
4+
in_memory: { memory: ~ }
5+
firewalls:
6+
dev:
7+
pattern: ^/(_(profiler|wdt)|css|images|js)/
8+
security: false
9+
main:
10+
anonymous: true
11+
12+
# activate different ways to authenticate
13+
14+
# http_basic: true
15+
# https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
16+
17+
# form_login: true
18+
# https://symfony.com/doc/current/security/form_login_setup.html
19+
20+
# Easy way to control access for large sections of your site
21+
# Note: Only the *first* access control that matches will be used
22+
access_control:
23+
# - { path: ^/admin, roles: ROLE_ADMIN }
24+
# - { path: ^/profile, roles: ROLE_USER }
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
security:
2+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
3+
providers:
4+
in_memory: { memory: ~ }
5+
firewalls:
6+
dev:
7+
pattern: ^/(_(profiler|wdt)|css|images|js)/
8+
security: false
9+
main:
10+
anonymous: true
11+
second:
12+
anonymous: true
13+
14+
# activate different ways to authenticate
15+
16+
# http_basic: true
17+
# https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
18+
19+
# form_login: true
20+
# https://symfony.com/doc/current/security/form_login_setup.html
21+
22+
# Easy way to control access for large sections of your site
23+
# Note: Only the *first* access control that matches will be used
24+
access_control:
25+
# - { path: ^/admin, roles: ROLE_ADMIN }
26+
# - { path: ^/profile, roles: ROLE_USER }

0 commit comments

Comments
 (0)