Skip to content

Commit cbf2646

Browse files
jrushlowweaverryan
andauthored
[make:registration] drop guard authentication support (#1243)
Co-authored-by: Ryan Weaver <weaverryan+github@gmail.com>
1 parent 744ad00 commit cbf2646

File tree

10 files changed

+462
-60
lines changed

10 files changed

+462
-60
lines changed

src/Maker/MakeRegistrationForm.php

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
use Symfony\Bundle\MakerBundle\InputConfiguration;
2727
use Symfony\Bundle\MakerBundle\Renderer\FormTypeRenderer;
2828
use Symfony\Bundle\MakerBundle\Security\InteractiveSecurityHelper;
29+
use Symfony\Bundle\MakerBundle\Security\Model\Authenticator;
30+
use Symfony\Bundle\MakerBundle\Security\Model\AuthenticatorType;
2931
use Symfony\Bundle\MakerBundle\Str;
3032
use Symfony\Bundle\MakerBundle\Util\ClassDetails;
3133
use Symfony\Bundle\MakerBundle\Util\ClassNameDetails;
@@ -34,6 +36,7 @@
3436
use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator;
3537
use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator;
3638
use Symfony\Bundle\MakerBundle\Validator;
39+
use Symfony\Bundle\SecurityBundle\Security;
3740
use Symfony\Bundle\SecurityBundle\SecurityBundle;
3841
use Symfony\Bundle\TwigBundle\TwigBundle;
3942
use Symfony\Component\Console\Command\Command;
@@ -49,7 +52,6 @@
4952
use Symfony\Component\Routing\Attribute\Route;
5053
use Symfony\Component\Routing\RouterInterface;
5154
use Symfony\Component\Security\Core\User\UserInterface;
52-
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
5355
use Symfony\Component\Translation\Translator;
5456
use Symfony\Component\Validator\Validation;
5557
use Symfony\Contracts\Translation\TranslatorInterface;
@@ -75,8 +77,7 @@ final class MakeRegistrationForm extends AbstractMaker
7577
private $emailGetter;
7678
private $fromEmailAddress;
7779
private $fromEmailName;
78-
private $autoLoginAuthenticator;
79-
private $firewallName;
80+
private ?Authenticator $autoLoginAuthenticator = null;
8081
private $redirectRouteName;
8182
private $addUniqueEntityConstraint;
8283

@@ -110,7 +111,7 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma
110111
$interactiveSecurityHelper = new InteractiveSecurityHelper();
111112

112113
if (null === $this->router) {
113-
throw new RuntimeCommandException('Router have been explicitely disabled in your configuration. This command needs to use the router.');
114+
throw new RuntimeCommandException('Router have been explicitly disabled in your configuration. This command needs to use the router.');
114115
}
115116

116117
if (!$this->fileManager->fileExists($path = 'config/packages/security.yaml')) {
@@ -184,32 +185,20 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma
184185

185186
private function interactAuthenticatorQuestions(ConsoleStyle $io, InteractiveSecurityHelper $interactiveSecurityHelper, array $securityData): void
186187
{
187-
$firewallsData = $securityData['security']['firewalls'] ?? [];
188-
$firewallName = $interactiveSecurityHelper->guessFirewallName(
189-
$io,
190-
$securityData,
191-
'Which firewall key in security.yaml holds the authenticator you want to use for logging in?'
192-
);
188+
// get list of authenticators
189+
$authenticators = $interactiveSecurityHelper->getAuthenticatorsFromConfig($securityData['security']['firewalls'] ?? []);
193190

194-
if (!isset($firewallsData[$firewallName])) {
195-
$io->note('No firewalls found - skipping authentication after registration. You might want to configure your security before running this command.');
191+
if (empty($authenticators)) {
192+
$io->note('No authenticators found - so your user won\'t be automatically authenticated after registering.');
196193

197194
return;
198195
}
199196

200-
$this->firewallName = $firewallName;
201-
202-
// get list of guard authenticators
203-
$authenticatorClasses = $interactiveSecurityHelper->getAuthenticatorClasses($firewallsData[$firewallName]);
204-
if (empty($authenticatorClasses)) {
205-
$io->note('No Guard authenticators found - so your user won\'t be automatically authenticated after registering.');
206-
} else {
207-
$this->autoLoginAuthenticator =
208-
1 === \count($authenticatorClasses) ? $authenticatorClasses[0] : $io->choice(
209-
'Which authenticator\'s onAuthenticationSuccess() should be used after logging in?',
210-
$authenticatorClasses
211-
);
212-
}
197+
$this->autoLoginAuthenticator =
198+
1 === \count($authenticators) ? $authenticators[0] : $io->choice(
199+
'Which authenticator should be used to login the user?',
200+
$authenticators
201+
);
213202
}
214203

215204
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
@@ -312,11 +301,22 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
312301
}
313302
}
314303

315-
if ($this->autoLoginAuthenticator) {
304+
$autoLoginVars = [
305+
'login_after_registration' => null !== $this->autoLoginAuthenticator,
306+
];
307+
308+
if (null !== $this->autoLoginAuthenticator) {
316309
$useStatements->addUseStatement([
317-
$this->autoLoginAuthenticator,
318-
UserAuthenticatorInterface::class,
310+
Security::class,
319311
]);
312+
313+
$autoLoginVars['firewall'] = $this->autoLoginAuthenticator->firewallName;
314+
$autoLoginVars['authenticator'] = sprintf('\'%s\'', $this->autoLoginAuthenticator->type->value);
315+
316+
if (AuthenticatorType::CUSTOM === $this->autoLoginAuthenticator->type) {
317+
$useStatements->addUseStatement($this->autoLoginAuthenticator->authenticatorClass);
318+
$autoLoginVars['authenticator'] = sprintf('%s::class', Str::getShortClassName($this->autoLoginAuthenticator->authenticatorClass));
319+
}
320320
}
321321

322322
if ($isTranslatorAvailable = class_exists(Translator::class)) {
@@ -339,14 +339,11 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
339339
'from_email' => $this->fromEmailAddress,
340340
'from_email_name' => addslashes($this->fromEmailName),
341341
'email_getter' => $this->emailGetter,
342-
'authenticator_class_name' => $this->autoLoginAuthenticator ? Str::getShortClassName($this->autoLoginAuthenticator) : null,
343-
'authenticator_full_class_name' => $this->autoLoginAuthenticator,
344-
'firewall_name' => $this->firewallName,
345342
'redirect_route_name' => $this->redirectRouteName,
346-
'password_hasher_class_details' => $generator->createClassNameDetails(UserPasswordHasherInterface::class, '\\'),
347343
'translator_available' => $isTranslatorAvailable,
348344
],
349-
$userRepoVars
345+
$userRepoVars,
346+
$autoLoginVars,
350347
)
351348
);
352349

src/Resources/skeleton/registration/RegistrationController.tpl.php

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public function __construct(<?= $email_verifier_class_details->getShortName() ?>
1616

1717
<?php endif; ?>
1818
<?= $generator->generateRouteForControllerMethod($route_path, $route_name) ?>
19-
public function register(Request $request, <?= $password_hasher_class_details->getShortName() ?> $userPasswordHasher<?= $authenticator_full_class_name ? sprintf(', UserAuthenticatorInterface $userAuthenticator, %s $authenticator', $authenticator_class_name) : '' ?>, EntityManagerInterface $entityManager): Response
19+
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher<?= $login_after_registration ? ', Security $security': '' ?>, EntityManagerInterface $entityManager): Response
2020
{
2121
$user = new <?= $user_class_name ?>();
2222
$form = $this->createForm(<?= $form_class_name ?>::class, $user);
@@ -25,7 +25,7 @@ public function register(Request $request, <?= $password_hasher_class_details->g
2525
if ($form->isSubmitted() && $form->isValid()) {
2626
// encode the plain password
2727
$user->set<?= ucfirst($password_field) ?>(
28-
$userPasswordHasher->hashPassword(
28+
$userPasswordHasher->hashPassword(
2929
$user,
3030
$form->get('plainPassword')->getData()
3131
)
@@ -44,14 +44,11 @@ public function register(Request $request, <?= $password_hasher_class_details->g
4444
->htmlTemplate('registration/confirmation_email.html.twig')
4545
);
4646
<?php endif; ?>
47+
4748
// do anything else you need here, like send an email
4849

49-
<?php if ($authenticator_full_class_name): ?>
50-
return $userAuthenticator->authenticateUser(
51-
$user,
52-
$authenticator,
53-
$request
54-
);
50+
<?php if ($login_after_registration): ?>
51+
return $security->login($user, <?= $authenticator ?>, '<?= $firewall ?>');
5552
<?php else: ?>
5653
return $this->redirectToRoute('<?= $redirect_route_name ?>');
5754
<?php endif; ?>

src/Security/InteractiveSecurityHelper.php

Lines changed: 92 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Bundle\MakerBundle\Security;
1313

14+
use Symfony\Bundle\MakerBundle\Security\Model\Authenticator;
15+
use Symfony\Bundle\MakerBundle\Security\Model\AuthenticatorType;
1416
use Symfony\Bundle\MakerBundle\Str;
1517
use Symfony\Bundle\MakerBundle\Validator;
1618
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -140,24 +142,6 @@ public function guessPasswordField(SymfonyStyle $io, string $userClass): string
140142
);
141143
}
142144

143-
public function getAuthenticatorClasses(array $firewallData): array
144-
{
145-
if (isset($firewallData['guard'])) {
146-
return array_filter($firewallData['guard']['authenticators'] ?? [], static fn ($authenticator) => class_exists($authenticator));
147-
}
148-
149-
if (isset($firewallData['custom_authenticator'])) {
150-
$authenticators = $firewallData['custom_authenticator'];
151-
if (\is_string($authenticators)) {
152-
$authenticators = [$authenticators];
153-
}
154-
155-
return array_filter($authenticators, static fn ($authenticator) => class_exists($authenticator));
156-
}
157-
158-
return [];
159-
}
160-
161145
public function guessPasswordSetter(SymfonyStyle $io, string $userClass): string
162146
{
163147
if (null === ($methodChoices = $this->methodNameGuesser($userClass, 'setPassword'))) {
@@ -196,6 +180,96 @@ public function guessIdGetter(SymfonyStyle $io, string $userClass): string
196180
);
197181
}
198182

183+
/**
184+
* @param array<string, array<string, mixed>> $firewalls Config data from security.firewalls
185+
*
186+
* @return Authenticator[]
187+
*/
188+
public function getAuthenticatorsFromConfig(array $firewalls): array
189+
{
190+
$authenticators = [];
191+
192+
/* Iterate over each firewall that exists e.g. security.firewalls.main
193+
* $firewallName could be "main" or "dev", etc...
194+
* $firewallConfig should be an array of the firewalls params
195+
*/
196+
foreach ($firewalls as $firewallName => $firewallConfig) {
197+
if (!\is_array($firewallConfig)) {
198+
continue;
199+
}
200+
201+
$authenticators = [
202+
...$authenticators,
203+
...$this->getAuthenticatorsFromConfigData($firewallConfig, $firewallName),
204+
];
205+
}
206+
207+
return $authenticators;
208+
}
209+
210+
/**
211+
* Pass in a firewalls config e.g. security.firewalls.main like:
212+
* pattern: ^/path
213+
* form_login:
214+
* login_path: app_login
215+
* custom_authenticator:
216+
* - App\Security\MyAuthenticator
217+
*
218+
* @param array<string, mixed> $firewallConfig
219+
*
220+
* @return Authenticator[]
221+
*/
222+
private function getAuthenticatorsFromConfigData(array $firewallConfig, string $firewallName): array
223+
{
224+
$authenticators = [];
225+
226+
foreach ($firewallConfig as $potentialAuthenticator => $configData) {
227+
// Check if $potentialAuthenticator is a supported authenticator or if its some other key.
228+
if (null === ($authenticator = AuthenticatorType::tryFrom($potentialAuthenticator))) {
229+
// $potentialAuthenticator is probably something like "pattern" or "lazy", not an authenticator
230+
continue;
231+
}
232+
233+
// $potentialAuthenticator is a supported authenticator. Check if it's a custom_authenticator.
234+
if (AuthenticatorType::CUSTOM !== $authenticator) {
235+
// We found a "built in" authenticator - "form_login", "json_login", etc...
236+
$authenticators[] = new Authenticator($authenticator, $firewallName);
237+
238+
continue;
239+
}
240+
241+
/*
242+
* $potentialAuthenticator = custom_authenticator.
243+
* $configData is either [App\MyAuthenticator] or (string) App\MyAuthenticator
244+
*/
245+
$customAuthenticators = $this->getCustomAuthenticators($configData, $firewallName);
246+
247+
$authenticators = [...$authenticators, ...$customAuthenticators];
248+
}
249+
250+
return $authenticators;
251+
}
252+
253+
/**
254+
* @param string|array<string> $customAuthenticators A single entry from custom_authenticators or an array of authenticators
255+
*
256+
* @return Authenticator[]
257+
*/
258+
private function getCustomAuthenticators(string|array $customAuthenticators, string $firewallName): array
259+
{
260+
if (\is_string($customAuthenticators)) {
261+
$customAuthenticators = [$customAuthenticators];
262+
}
263+
264+
$authenticators = [];
265+
266+
foreach ($customAuthenticators as $customAuthenticatorClass) {
267+
$authenticators[] = new Authenticator(AuthenticatorType::CUSTOM, $firewallName, $customAuthenticatorClass);
268+
}
269+
270+
return $authenticators;
271+
}
272+
199273
private function methodNameGuesser(string $className, string $suspectedMethodName): ?array
200274
{
201275
$reflectionClass = new \ReflectionClass($className);

src/Security/Model/Authenticator.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle 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\MakerBundle\Security\Model;
13+
14+
/**
15+
* @author Jesse Rushlow<jr@rushlow.dev>
16+
*
17+
* @internal
18+
*/
19+
final class Authenticator
20+
{
21+
public function __construct(
22+
public AuthenticatorType $type,
23+
public string $firewallName,
24+
public ?string $authenticatorClass = null,
25+
) {
26+
}
27+
28+
/**
29+
* Useful for asking questions like "Which authenticator do you want to use?".
30+
*/
31+
public function __toString(): string
32+
{
33+
return sprintf(
34+
'"%s" in the "%s" firewall',
35+
$this->authenticatorClass ?? $this->type->value,
36+
$this->firewallName,
37+
);
38+
}
39+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle 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\MakerBundle\Security\Model;
13+
14+
/**
15+
* @author Jesse Rushlow <jr@rushlow.dev>
16+
*
17+
* @internal
18+
*/
19+
enum AuthenticatorType: string
20+
{
21+
case FORM_LOGIN = 'form_login';
22+
case JSON_LOGIN = 'json_login';
23+
case HTTP_BASIC = 'http_basic';
24+
case LOGIN_LINK = 'login_link';
25+
case ACCESS_TOKEN = 'access_token';
26+
case X509 = 'x509';
27+
case REMOTE_USER = 'remote_user';
28+
29+
case CUSTOM = 'custom_authenticator';
30+
}

0 commit comments

Comments
 (0)