-
-
Notifications
You must be signed in to change notification settings - Fork 405
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #736 keeping aliens at bay with maker + new security 5.2 feat…
…ures (jrushlow) This PR was squashed before being merged into the 1.0-dev branch. Discussion ---------- keeping aliens at bay with maker + new security 5.2 features Hello Security51! Allows `make:auth` to take advantage of the new security features that were introduced. Starting in Symfony `5.2` when you run `make:auth` MakerBundle will automatically check if you have set: ``` security: enable_authenticator_manager: true ``` If so, MakerBundle will generate the required classes to authenticate users leveraging the new Authenticators. A positive side effect of this PR, all templates can check `$use_typed_properties` to determine if the host is capable of utilizing typed properties that were introduced in PHP 7.4. e.g. - ``` private <?= $use_typed_properties ? 'UrlGeneratorInterface ' : null ?>$urlGenerator; ``` Internally, we've added the `PhpCompatUtil::canUseTypedProperties()` method that is called anytime a Maker needs to generate a twig template. We then inject `$use_typed_properties` into all templates so the developer can add typed properties to a generated template without having to worry about a bunch of behind the scenes logic. Commits ------- 820ee25 keeping aliens at bay with maker + new security 5.2 features
- Loading branch information
Showing
35 changed files
with
828 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,10 +36,12 @@ | |
use Symfony\Component\Form\Form; | ||
use Symfony\Component\HttpKernel\Kernel; | ||
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator; | ||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; | ||
use Symfony\Component\Yaml\Yaml; | ||
|
||
/** | ||
* @author Ryan Weaver <[email protected]> | ||
* @author Ryan Weaver <[email protected]> | ||
* @author Jesse Rushlow <[email protected]> | ||
* | ||
* @internal | ||
*/ | ||
|
@@ -56,6 +58,8 @@ final class MakeAuthenticator extends AbstractMaker | |
|
||
private $doctrineHelper; | ||
|
||
private $useSecurity52 = false; | ||
|
||
public function __construct(FileManager $fileManager, SecurityConfigUpdater $configUpdater, Generator $generator, DoctrineHelper $doctrineHelper) | ||
{ | ||
$this->fileManager = $fileManager; | ||
|
@@ -84,6 +88,15 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma | |
$manipulator = new YamlSourceManipulator($this->fileManager->getFileContents($path)); | ||
$securityData = $manipulator->getData(); | ||
|
||
// Determine if we should use new security features introduced in Symfony 5.2 | ||
if ($securityData['security']['enable_authenticator_manager'] ?? false) { | ||
$this->useSecurity52 = true; | ||
} | ||
|
||
if ($this->useSecurity52 && !class_exists(UserBadge::class)) { | ||
throw new RuntimeCommandException('MakerBundle does not support generating authenticators using the new authenticator system before symfony/security-bundle 5.2. Please upgrade to 5.2 and try again.'); | ||
} | ||
|
||
// authenticator type | ||
$authenticatorTypeValues = [ | ||
'Empty authenticator' => self::AUTH_TYPE_EMPTY_AUTHENTICATOR, | ||
|
@@ -138,10 +151,13 @@ function ($answer) { | |
$input->setOption('firewall-name', $firewallName = $interactiveSecurityHelper->guessFirewallName($io, $securityData)); | ||
|
||
$command->addOption('entry-point', null, InputOption::VALUE_OPTIONAL); | ||
$input->setOption( | ||
'entry-point', | ||
$interactiveSecurityHelper->guessEntryPoint($io, $securityData, $input->getArgument('authenticator-class'), $firewallName) | ||
); | ||
|
||
if (!$this->useSecurity52) { | ||
$input->setOption( | ||
'entry-point', | ||
$interactiveSecurityHelper->guessEntryPoint($io, $securityData, $input->getArgument('authenticator-class'), $firewallName) | ||
); | ||
} | ||
|
||
if (self::AUTH_TYPE_FORM_LOGIN === $input->getArgument('authenticator-type')) { | ||
$command->addArgument('controller-class', InputArgument::REQUIRED); | ||
|
@@ -192,13 +208,21 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen | |
|
||
// update security.yaml with guard config | ||
$securityYamlUpdated = false; | ||
|
||
$entryPoint = $input->getOption('entry-point'); | ||
|
||
if ($this->useSecurity52 && self::AUTH_TYPE_FORM_LOGIN !== $input->getArgument('authenticator-type')) { | ||
$entryPoint = false; | ||
} | ||
|
||
try { | ||
$newYaml = $this->configUpdater->updateForAuthenticator( | ||
$this->fileManager->getFileContents($path = 'config/packages/security.yaml'), | ||
$input->getOption('firewall-name'), | ||
$input->getOption('entry-point'), | ||
$entryPoint, | ||
$input->getArgument('authenticator-class'), | ||
$input->hasArgument('logout-setup') ? $input->getArgument('logout-setup') : false | ||
$input->hasArgument('logout-setup') ? $input->getArgument('logout-setup') : false, | ||
$this->useSecurity52 | ||
); | ||
$generator->dumpFile($path, $newYaml); | ||
$securityYamlUpdated = true; | ||
|
@@ -235,10 +259,8 @@ private function generateAuthenticatorClass(array $securityData, string $authent | |
if (self::AUTH_TYPE_EMPTY_AUTHENTICATOR === $authenticatorType) { | ||
$this->generator->generateClass( | ||
$authenticatorClass, | ||
'authenticator/EmptyAuthenticator.tpl.php', | ||
[ | ||
'provider_key_type_hint' => $this->providerKeyTypeHint(), | ||
] | ||
sprintf('authenticator/%sEmptyAuthenticator.tpl.php', $this->useSecurity52 ? 'Security52' : ''), | ||
['provider_key_type_hint' => $this->providerKeyTypeHint()] | ||
); | ||
|
||
return; | ||
|
@@ -251,12 +273,13 @@ private function generateAuthenticatorClass(array $securityData, string $authent | |
|
||
$this->generator->generateClass( | ||
$authenticatorClass, | ||
'authenticator/LoginFormAuthenticator.tpl.php', | ||
sprintf('authenticator/%sLoginFormAuthenticator.tpl.php', $this->useSecurity52 ? 'Security52' : ''), | ||
[ | ||
'user_fully_qualified_class_name' => trim($userClassNameDetails->getFullName(), '\\'), | ||
'user_class_name' => $userClassNameDetails->getShortName(), | ||
'username_field' => $userNameField, | ||
'username_field_label' => Str::asHumanWords($userNameField), | ||
'username_field_var' => Str::asCamelCase($userNameField), | ||
'user_needs_encoder' => $this->userClassHasEncoder($securityData, $userClass), | ||
'user_is_entity' => $this->doctrineHelper->isClassAMappedEntity($userClass), | ||
'provider_key_type_hint' => $this->providerKeyTypeHint(), | ||
|
@@ -322,7 +345,8 @@ private function generateNextMessage(bool $securityYamlUpdated, string $authenti | |
'main', | ||
null, | ||
$authenticatorClass, | ||
$logoutSetup | ||
$logoutSetup, | ||
$this->useSecurity52 | ||
); | ||
$nextTexts[] = '- Your <info>security.yaml</info> could not be updated automatically. You\'ll need to add the following config manually:\n\n'.$yamlExample; | ||
} | ||
|
44 changes: 44 additions & 0 deletions
44
src/Resources/skeleton/authenticator/Security52EmptyAuthenticator.tpl.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?php echo "<?php\n" ?> | ||
|
||
namespace <?php echo $namespace ?>; | ||
|
||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | ||
use Symfony\Component\Security\Core\Exception\AuthenticationException; | ||
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; | ||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; | ||
|
||
class <?php echo $class_name ?> extends AbstractAuthenticator | ||
{ | ||
public function supports(Request $request): ?bool | ||
{ | ||
// TODO: Implement supports() method. | ||
} | ||
|
||
public function authenticate(Request $request): PassportInterface | ||
{ | ||
// TODO: Implement authenticate() method. | ||
} | ||
|
||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response | ||
{ | ||
// TODO: Implement onAuthenticationSuccess() method. | ||
} | ||
|
||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response | ||
{ | ||
// TODO: Implement onAuthenticationFailure() method. | ||
} | ||
|
||
// public function start(Request $request, AuthenticationException $authException = null): Response | ||
// { | ||
// /* | ||
// * If you would like this class to control what happens when an anonymous user accesses a | ||
// * protected page (e.g. redirect to /login), uncomment this method and make this class | ||
// * implement Symfony\Component\Security\Http\EntryPoint\AuthenticationEntrypointInterface. | ||
// * | ||
// * For more details, see https://symfony.com/doc/current/security/experimental_authenticators.html#configuring-the-authentication-entry-point | ||
// */ | ||
// } | ||
} |
62 changes: 62 additions & 0 deletions
62
src/Resources/skeleton/authenticator/Security52LoginFormAuthenticator.tpl.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
<?= "<?php\n" ?> | ||
|
||
namespace <?= $namespace ?>; | ||
|
||
use Symfony\Component\HttpFoundation\RedirectResponse; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | ||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | ||
use Symfony\Component\Security\Core\Security; | ||
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator; | ||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; | ||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; | ||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; | ||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport; | ||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; | ||
use Symfony\Component\Security\Http\Util\TargetPathTrait; | ||
|
||
class <?= $class_name; ?> extends AbstractLoginFormAuthenticator | ||
{ | ||
use TargetPathTrait; | ||
|
||
public const LOGIN_ROUTE = 'app_login'; | ||
|
||
private <?= $use_typed_properties ? 'UrlGeneratorInterface ' : null ?>$urlGenerator; | ||
|
||
public function __construct(UrlGeneratorInterface $urlGenerator) | ||
{ | ||
$this->urlGenerator = $urlGenerator; | ||
} | ||
|
||
public function authenticate(Request $request): PassportInterface | ||
{ | ||
$<?= $username_field_var ?> = $request->request->get('<?= $username_field ?>', ''); | ||
|
||
$request->getSession()->set(Security::LAST_USERNAME, $<?= $username_field_var ?>); | ||
|
||
return new Passport( | ||
new UserBadge($<?= $username_field_var ?>), | ||
new PasswordCredentials($request->request->get('password', '')), | ||
[ | ||
new CsrfTokenBadge('authenticate', $request->get('_csrf_token')), | ||
] | ||
); | ||
} | ||
|
||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response | ||
{ | ||
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) { | ||
return new RedirectResponse($targetPath); | ||
} | ||
|
||
// For example: | ||
//return new RedirectResponse($this->urlGenerator->generate('some_route')); | ||
throw new \Exception('TODO: provide a valid redirect inside '.__FILE__); | ||
} | ||
|
||
protected function getLoginUrl(Request $request): string | ||
{ | ||
return $this->urlGenerator->generate(self::LOGIN_ROUTE); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,23 +12,39 @@ | |
namespace Symfony\Bundle\MakerBundle\Security; | ||
|
||
use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; | ||
use Symfony\Component\HttpKernel\Log\Logger; | ||
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder; | ||
|
||
/** | ||
* @author Ryan Weaver <[email protected]> | ||
* @author Jesse Rushlow <[email protected]> | ||
* | ||
* @internal | ||
*/ | ||
final class SecurityConfigUpdater | ||
{ | ||
/** @var YamlSourceManipulator */ | ||
private $manipulator; | ||
|
||
/** @var Logger|null */ | ||
private $ysmLogger; | ||
|
||
public function __construct(Logger $ysmLogger = null) | ||
{ | ||
$this->ysmLogger = $ysmLogger; | ||
} | ||
|
||
/** | ||
* Updates security.yaml contents based on a new User class. | ||
*/ | ||
public function updateForUserClass(string $yamlSource, UserClassConfiguration $userConfig, string $userClass): string | ||
{ | ||
$this->manipulator = new YamlSourceManipulator($yamlSource); | ||
|
||
if (null !== $this->ysmLogger) { | ||
$this->manipulator->setLogger($this->ysmLogger); | ||
} | ||
|
||
$this->normalizeSecurityYamlFile(); | ||
|
||
$this->updateProviders($userConfig, $userClass); | ||
|
@@ -43,10 +59,14 @@ public function updateForUserClass(string $yamlSource, UserClassConfiguration $u | |
return $contents; | ||
} | ||
|
||
public function updateForAuthenticator(string $yamlSource, string $firewallName, $chosenEntryPoint, string $authenticatorClass, bool $logoutSetup): string | ||
public function updateForAuthenticator(string $yamlSource, string $firewallName, $chosenEntryPoint, string $authenticatorClass, bool $logoutSetup, bool $useSecurity52): string | ||
{ | ||
$this->manipulator = new YamlSourceManipulator($yamlSource); | ||
|
||
if (null !== $this->ysmLogger) { | ||
$this->manipulator->setLogger($this->ysmLogger); | ||
} | ||
|
||
$this->normalizeSecurityYamlFile(); | ||
|
||
$newData = $this->manipulator->getData(); | ||
|
@@ -56,23 +76,50 @@ public function updateForAuthenticator(string $yamlSource, string $firewallName, | |
} | ||
|
||
if (!isset($newData['security']['firewalls'][$firewallName])) { | ||
$newData['security']['firewalls'][$firewallName] = ['anonymous' => true]; | ||
if ($useSecurity52) { | ||
$newData['security']['firewalls'][$firewallName] = ['lazy' => true]; | ||
} else { | ||
$newData['security']['firewalls'][$firewallName] = ['anonymous' => 'lazy']; | ||
} | ||
} | ||
|
||
$firewall = $newData['security']['firewalls'][$firewallName]; | ||
|
||
if (!isset($firewall['guard'])) { | ||
$firewall['guard'] = []; | ||
} | ||
if ($useSecurity52) { | ||
if (isset($firewall['custom_authenticator'])) { | ||
if (\is_array($firewall['custom_authenticator'])) { | ||
$firewall['custom_authenticator'][] = $authenticatorClass; | ||
} else { | ||
$stringValue = $firewall['custom_authenticator']; | ||
$firewall['custom_authenticator'] = []; | ||
$firewall['custom_authenticator'][] = $stringValue; | ||
$firewall['custom_authenticator'][] = $authenticatorClass; | ||
} | ||
} else { | ||
$firewall['custom_authenticator'] = $authenticatorClass; | ||
} | ||
|
||
if (!isset($firewall['guard']['authenticators'])) { | ||
$firewall['guard']['authenticators'] = []; | ||
} | ||
if (!isset($firewall['entry_point']) && $chosenEntryPoint) { | ||
$firewall['entry_point_empty_line'] = $this->manipulator->createEmptyLine(); | ||
$firewall['entry_point_comment'] = $this->manipulator->createCommentLine( | ||
' the entry_point start() method determines what happens when an anonymous user accesses a protected page' | ||
); | ||
$firewall['entry_point'] = $authenticatorClass; | ||
} | ||
} else { | ||
if (!isset($firewall['guard'])) { | ||
$firewall['guard'] = []; | ||
} | ||
|
||
$firewall['guard']['authenticators'][] = $authenticatorClass; | ||
if (!isset($firewall['guard']['authenticators'])) { | ||
$firewall['guard']['authenticators'] = []; | ||
} | ||
|
||
if (\count($firewall['guard']['authenticators']) > 1) { | ||
$firewall['guard']['entry_point'] = $chosenEntryPoint ?? current($firewall['guard']['authenticators']); | ||
$firewall['guard']['authenticators'][] = $authenticatorClass; | ||
|
||
if (\count($firewall['guard']['authenticators']) > 1) { | ||
$firewall['guard']['entry_point'] = $chosenEntryPoint ?? current($firewall['guard']['authenticators']); | ||
} | ||
} | ||
|
||
if (!isset($firewall['logout']) && $logoutSetup) { | ||
|
@@ -86,10 +133,10 @@ public function updateForAuthenticator(string $yamlSource, string $firewallName, | |
} | ||
|
||
$newData['security']['firewalls'][$firewallName] = $firewall; | ||
|
||
$this->manipulator->setData($newData); | ||
$contents = $this->manipulator->getContents(); | ||
|
||
return $contents; | ||
return $this->manipulator->getContents(); | ||
} | ||
|
||
private function normalizeSecurityYamlFile() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.