8000 security 51 · symfony/maker-bundle@d9c29d8 · GitHub
[go: up one dir, main page]

Skip to content

Commit d9c29d8

Browse files
committed
security 51
WIP - Security 51 git diff and comments dont mix well pass user selector to template refactor Security51 login form test security51 requires php 7.2 - our tests should too empty authenticators have to worry about aliens too... oops.. forgot something important guess user repository name but Security52 just doesnt have the same ring to it refactored to use new security features if symfony version >= symfony 5.2 we're intentionally alienating support for the new security features in make:auth for symfony 5.1 not passing the user repo remove unused userRepository skip if symfony < 5.2 only one constructor param is used, array not needed security 51
1 parent 38715cd commit d9c29d8

File tree

13 files changed

+575
-34
lines changed

13 files changed

+575
-34
lines changed

src/Generator.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ private function addOperation(string $targetPath, string $templateName, array $v
168168

169169
$variables['relative_path'] = $this->fileManager->relativizePath($targetPath);
170170
$variables['use_attributes'] = $this->phpCompatUtil->canUseAttributes();
171+
$variables['use_typed_properties'] = $this->phpCompatUtil->canUseTypedProperties();
171172

172173
$templatePath = $templateName;
173174
if (!file_exists($templatePath)) {

src/Maker/MakeAuthenticator.php

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
use Symfony\Component\Yaml\Yaml;
4040

4141
/**
42-
* @author Ryan Weaver <ryan@knpuniversity.com>
42+
* @author Ryan Weaver <ryan@symfonycasts.com>
43+
* @author Jesse Rushlow <jr@rushlow.dev>
4344
*
4445
* @internal
4546
*/
@@ -56,6 +57,8 @@ final class MakeAuthenticator extends AbstractMaker
5657

5758
private $doctrineHelper;
5859

60+
private $useSecurity51 = false;
61+
5962
public function __construct(FileManager $fileManager, SecurityConfigUpdater $configUpdater, Generator $generator, DoctrineHelper $doctrineHelper)
6063
{
6164
$this->fileManager = $fileManager;
@@ -84,6 +87,15 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma
8487
$manipulator = new YamlSourceManipulator($this->fileManager->getFileContents($path));
8588
$securityData = $manipulator->getData();
8689

90+
// Determine if we should use new security features introduced in Symfony 5.1
91+
if (!empty($securityData['security']['enable_authenticator_manager']) && $securityData['security']['enable_authenticator_manager']) {
92+
$this->useSecurity51 = true;
93+
}
94+
95+
if ($this->useSecurity51 && Kernel::VERSION_ID < 50200) {
96+
throw new RuntimeCommandException('Maker does not support Authenticators before Symfony 5.2.');
97+
}
98+
8799
// authenticator type
88100
$authenticatorTypeValues = [
89101
'Empty authenticator' => self::AUTH_TYPE_EMPTY_AUTHENTICATOR,
@@ -198,7 +210,8 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
198210
$input->getOption('firewall-name'),
199211
$input->getOption('entry-point'),
200212
$input->getArgument('authenticator-class'),
201-
$input->hasArgument('logout-setup') ? $input->getArgument('logout-setup') : false
213+
$input->hasArgument('logout-setup') ? $input->getArgument('logout-setup') : false,
214+
$this->useSecurity51
202215
);
203216
$generator->dumpFile($path, $newYaml);
204217
$securityYamlUpdated = true;
@@ -233,13 +246,15 @@ private function generateAuthenticatorClass(array $securityData, string $authent
233246
{
234247
// generate authenticator class
235248
if (self::AUTH_TYPE_EMPTY_AUTHENTICATOR === $authenticatorType) {
236-
$this->generator->generateClass(
237-
$authenticatorClass,
238-
'authenticator/EmptyAuthenticator.tpl.php',
239-
[
249+
$template = 'authenticator/EmptyAuthenticator.tpl.php';
250+
251+
if ($this->useSecurity51) {
252+
$template = 'authenticator/Security51EmptyAuthenticator.tpl.php';
253+
}
254+
255+
$this->generator->generateClass($authenticatorClass, $template, [
240256
'provider_key_type_hint' => $this->providerKeyTypeHint(),
241-
]
242-
);
257+
]);
243258

244259
return;
245260
}
@@ -249,19 +264,21 @@ private function generateAuthenticatorClass(array $securityData, string $authent
249264
'Entity\\'
250265
);
251266

252-
$this->generator->generateClass(
253-
$authenticatorClass,
254-
'authenticator/LoginFormAuthenticator.tpl.php',
255-
[
256-
'user_fully_qualified_class_name' => trim($userClassNameDetails->getFullName(), '\\'),
257-
'user_class_name' => $userClassNameDetails->getShortName(),
258-
'username_field' => $userNameField,
259-
'username_field_label' => Str::asHumanWords($userNameField),
260-
'user_needs_encoder' => $this->userClassHasEncoder($securityData, $userClass),
261-
'user_is_entity' => $this->doctrineHelper->isClassAMappedEntity($userClass),
262-
'provider_key_type_hint' => $this->providerKeyTypeHint(),
263-
]
264-
);
267+
$template = 'authenticator/LoginFormAuthenticator.tpl.php';
268+
269+
if ($this->useSecurity51) {
270+
$template = 'authenticator/Security51LoginFormAuthenticator.tpl.php';
271+
}
272+
273+
$this->generator->generateClass($authenticatorClass, $template, [
274+
'user_fully_qualified_class_name' => trim($userClassNameDetails->getFullName(), '\\'),
275+
'user_class_name' => $userClassNameDetails->getShortName(),
276+
'username_field' => $userNameField,
277+
'username_field_label' => Str::asHumanWords($userNameField),
278+
'user_needs_encoder' => $this->userClassHasEncoder($securityData, $userClass),
279+
'user_is_entity' => $this->doctrineHelper->isClassAMappedEntity($userClass),
280+
'provider_key_type_hint' => $this->providerKeyTypeHint(),
281+
]);
265282
}
266283

267284
private function generateFormLoginFiles(string $controllerClass, string $userNameField, bool $logoutSetup)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php echo "<?php\n" ?>
2+
3+
namespace <?php echo $namespace ?>;
4+
5+
use Symfony\Component\HttpFoundation\Request;
6+
use Symfony\Component\HttpFoundation\Response;
7+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
8+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
9+
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
10+
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
11+
12+
class <?php echo $class_name ?> extends AbstractAuthenticator
13+
{
14+
public function supports(Request $request): ?bool
15+
{
16+
// TODO: Implement supports() method.
17+
}
18+
19+
public function authenticate(Request $request): PassportInterface
20+
{
21+
// TODO: Implement authenticate() method.
22+
}
23+
24+
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
25+
{
26+
// TODO: Implement onAuthenticationSuccess() method.
27+
}
28+
29+
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
30+
{
31+
// TODO: Implement onAuthenticationFailure() method.
32+
}
33+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?= "<?php\n" ?>
2+
3+
namespace <?= $namespace ?>;
4+
5+
use Symfony\Component\HttpFoundation\RedirectResponse;
6+
use Symfony\Component\HttpFoundation\Request;
7+
use Symfony\Component\HttpFoundation\Response;
8+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
9+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
10+
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
11+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
12+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
13+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
14+
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
15+
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
16+
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
17+
use Symfony\Component\Security\Http\Util\TargetPathTrait;
18+
19+
class <?= $class_name; ?> extends AbstractLoginFormAuthenticator
20+
{
21+
use TargetPathTrait;
22+
23+
public const LOGIN_ROUTE = 'app_login';
24+
25+
private <?= $use_typed_properties ? 'UrlGeneratorInterface ' : null ?>$urlGenerator;
26+
27+
public function __construct(UrlGeneratorInterface $urlGenerator)
28+
{
29+
$this->urlGenerator = $urlGenerator;
30+
}
31+
32+
public function supports(Request $request): ?bool
33+
{
34+
return self::LOGIN_ROUTE === $request->attributes->get('_route')
35+
&& $request->isMethod('POST');
36+
}
37+
38+
public function authenticate(Request $request): PassportInterface
39+
{
40+
/** @var string $password */
41+
$password = $request->request->get('password', '');
42+
43+
/** @var string $userIdentifier */
44+
$userIdentifier = $request->request->get('<?= $username_field ?>', '');
45+
46+
/** @var string|null */
47+
$csrf = $request->get('_csrf_token');
48+
49+
return new Passport(
50+
new UserBadge($userIdentifier),
51+
new PasswordCredentials($password),
52+
[
53+
new PasswordUpgradeBadge($password),
54+
new CsrfTokenBadge('authenticate', $csrf),
55+
]
56+
);
57+
}
58+
59+
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
60+
{
61+
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
62+
return new RedirectResponse($targetPath);
63+
}
64+
65+
// For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));
66+
throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
67+
}
68+
69+
protected function getLoginUrl(Request $request): string
70+
{
71+
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
72+
}
73+
}

src/Security/SecurityConfigUpdater.php

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
1616

1717
/**
18+
* @author Ryan Weaver <ryan@symfonycasts.com>
19+
* @author Jesse Rushlow <jr@rushlow.dev>
20+
*
1821
* @internal
1922
*/
2023
final class SecurityConfigUpdater
@@ -43,7 +46,7 @@ public function updateForUserClass(string $yamlSource, UserClassConfiguration $u
4346
return $contents;
4447
}
4548

46-
public function updateForAuthenticator(string $yamlSource, string $firewallName, $chosenEntryPoint, string $authenticatorClass, bool $logoutSetup): string
49+
public function updateForAuth 10000 enticator(string $yamlSource, string $firewallName, $chosenEntryPoint, string $authenticatorClass, bool $logoutSetup, bool $useSecurity51 = false): string
4750
{
4851
$this->manipulator = new YamlSourceManipulator($yamlSource);
4952

@@ -61,18 +64,28 @@ public function updateForAuthenticator(string $yamlSource, string $firewallName,
6164

6265
$firewall = $newData['security']['firewalls'][$firewallName];
6366

64-
if (!isset($firewall['guard'])) {
65-
$firewall['guard'] = [];
66-
}
67+
if ($useSecurity51) {
68+
if (!isset($firewall['custom_authenticator'])) {
69+
$firewall['custom_authenticator'] = $authenticatorClass;
70+
}
6771

68-
if (!isset($firewall['guard']['authenticators'])) {
69-
$firewall['guard']['authenticators'] = [];
70-
}
72+
if (!isset($firewall['entry_point'])) {
73+
$firewall['entry_point'] = $authenticatorClass;
74+
}
75+
} else {
76+
if (!isset($firewall['guard'])) {
77+
$firewall['guard'] = [];
78+
}
7179

72-
$firewall['guard']['authenticators'][] = $authenticatorClass;
80+
if (!isset($firewall['guard']['authenticators'])) {
81+
$firewall['guard']['authenticators'] = [];
82+
}
83+
84+
$firewall['guard']['authenticators'][] = $authenticatorClass;
7385

74-
if (\count($firewall['guard']['authenticators']) > 1) {
75-
$firewall['guard']['entry_point'] = $chosenEntryPoint ?? current($firewall['guard']['authenticators']);
86+
if (\count($firewall['guard']['authenticators']) > 1) {
87+
$firewall['guard']['entry_point'] = $chosenEntryPoint ?? current($firewall['guard']['authenticators']);
88+
}
7689
}
7790

7891
if (!isset($firewall['logout']) && $logoutSetup) {
@@ -86,10 +99,10 @@ public function updateForAuthenticator(string $yamlSource, string $firewallName,
8699
}
87100

88101
$newData['security']['firewalls'][$firewallName] = $firewall;
102+
89103
$this->manipulator->setData($newData);
90-
$contents = $this->manipulator->getContents();
91104

92-
return $contents;
105+
return $this->manipulator->getContents();
93106
}
94107

95108
private function normalizeSecurityYamlFile()

src/Util/PhpCompatUtil.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ public function canUseAttributes(): bool
3535
return version_compare($version, '8alpha', '>=');
3636
}
3737

38+
public function canUseTypedProperties(): bool
39+
{
40+
$version = $this->getPhpVersion();
41+
42+
return version_compare($version, '7.4', '>=');
43+
}
44+
3845
protected function getPhpVersion(): string
3946
{
4047
$rootDirectory = $this->fileManager->getRootDirectory();

tests/Maker/MakeAuthenticatorTest.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,5 +305,71 @@ function (string $output, string $directory) {
305305
}
306306
),
307307
];
308+
309+
yield 'security_51_empty_authenticator' => [
310+
MakerTestDetails::createTest(
311+
$this->getMakerInstance(MakeAuthenticator::class),
312+
[
313+
// authenticator type => empty-auth
314+
0,
315+
// authenticator class name
316+
'AppCustomAuthenticator',
317+
]
318+
)
319+
->setRequiredPhpVersion(70200)
320+
->addRequiredPackageVersion('symfony/framework-bundle', '>=5.2')
321+
->setFixtureFilesPath(__DIR__.'/../fixtures/MakeAuthenticatorSecurity51Empty')
322+
->assert(
323+
function (string $output, string $directory) {
324+
$this->assertStringContainsString('Success', $output);
325+
326+
$fs = new Filesystem();
327+
$this->assertTrue($fs->exists(sprintf('%s/src/Security/AppCustomAuthenticator.php', $directory)));
328+
329+
$securityConfig = Yaml::parse(file_get_contents(sprintf('%s/config/packages/security.yaml', $directory)));
330+
331+
$this->assertEquals(
332+
'App\\Security\\AppCustomAuthenticator',
333+
$securityConfig['security']['firewalls']['main']['custom_authenticator']
334+
);
335+
}
336+
),
337+
];
338+
339+
yield 'security_51_login_form_authenticator' => [
340+
MakerTestDetails::createTest(
341+
$this->getMakerInstance(MakeAuthenticator::class),
342+
[
343+
// authenticator type => login-form
344+
1,
345+
// class name
346+
'AppTestSecurity51LoginFormAuthenticator',
347+
// controller name
348+
'SecurityController',
349+
// User selector field
350+
'userEmail',
351+
// Logout Url
352+
'no',
353+
]
354+
)
355+
->setRequiredPhpVersion(70200)
356+
->addRequiredPackageVersion('symfony/framework-bundle', '>=5.2')
357+
->addExtraDependencies('doctrine')
358+
->addExtraDependencies('twig')
359+
->addExtraDependencies('symfony/form')
360+
->setFixtureFilesPath(__DIR__.'/../fixtures/MakeAuthenticatorSecurity51LoginForm')
361+
->configureDatabase()
362+
->updateSchemaAfterCommand()
363+
->assert(
364+
function (string $output, string $directory) {
365+
$this->assertStringContainsString('Success', $output);
366+
367+
$fs = new Filesystem();
368+
$this->assertTrue($fs->exists(sprintf('%s/src/Controller/SecurityController.php', $directory)));
369+
$this->assertTrue($fs->exists(sprintf('%s/templates/security/login.html.twig', $directory)));
370+
$this->assertTrue($fs->exists(sprintf('%s/src/Security/AppTestSecurity51LoginFormAuthenticator.php', $directory)));
371+
}
372+
),
373+
];
308374
}
309375
}

0 commit comments

Comments
 (0)
0