From 820ee25bddb5d6dbe067d2126d90936dcbb52c23 Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Thu, 12 Nov 2020 12:52:25 -0500 Subject: [PATCH] keeping aliens at bay with maker + new security 5.2 features --- src/Generator.php | 1 + src/Maker/MakeAuthenticator.php | 50 ++++++-- .../Security52EmptyAuthenticator.tpl.php | 44 +++++++ .../Security52LoginFormAuthenticator.tpl.php | 62 ++++++++++ src/Security/SecurityConfigUpdater.php | 73 +++++++++-- src/Util/PhpCompatUtil.php | 7 ++ src/Util/YamlSourceManipulator.php | 28 +++-- tests/Maker/MakeAuthenticatorTest.php | 66 ++++++++++ tests/Security/SecurityConfigUpdaterTest.php | 81 ++++++++++++- .../expected_authenticator/empty_source.yaml | 2 +- .../security_52_empty_source.yaml | 6 + ...security_52_with_firewalls_and_logout.yaml | 20 ++++ ...urity_52_with_multiple_authenticators.yaml | 11 ++ .../simple_security_source.yaml | 2 +- .../simple_security_with_firewalls.yaml | 2 +- ...rity_with_firewalls_and_authenticator.yaml | 4 +- ...le_security_with_firewalls_and_logout.yaml | 2 +- ...ith_single_memory_provider_configured.yaml | 2 +- .../source/security_52_empty_security.yaml | 2 + ...security_52_with_firewalls_and_logout.yaml | 12 ++ ...urity_52_with_multiple_authenticators.yaml | 6 + .../simple_security_with_firewalls.yaml | 2 +- ...rity_with_firewalls_and_authenticator.yaml | 4 +- ...le_security_with_firewalls_and_logout.yaml | 2 +- ...ith_single_memory_provider_configured.yaml | 2 +- tests/Util/PhpVersionTest.php | 49 +++++++- tests/Util/YamlSourceManipulatorTest.php | 2 +- .../complex_string_to_array.test | 14 +++ tests/Util/yaml_fixtures/string_to_array.test | 18 +++ .../string_to_array_with_eof_blank_line.test | 21 ++++ .../config/packages/security.yaml | 25 ++++ .../config/packages/security.yaml | 34 ++++++ .../src/Entity/User.php | 113 ++++++++++++++++++ .../src/Repository/UserRepository.php | 38 ++++++ .../tests/SecurityControllerTest.php | 74 ++++++++++++ 35 files changed, 828 insertions(+), 53 deletions(-) create mode 100644 src/Resources/skeleton/authenticator/Security52EmptyAuthenticator.tpl.php create mode 100644 src/Resources/skeleton/authenticator/Security52LoginFormAuthenticator.tpl.php create mode 100644 tests/Security/yaml_fixtures/expected_authenticator/security_52_empty_source.yaml create mode 100644 tests/Security/yaml_fixtures/expected_authenticator/security_52_with_firewalls_and_logout.yaml create mode 100644 tests/Security/yaml_fixtures/expected_authenticator/security_52_with_multiple_authenticators.yaml create mode 100644 tests/Security/yaml_fixtures/source/security_52_empty_security.yaml create mode 100644 tests/Security/yaml_fixtures/source/security_52_with_firewalls_and_logout.yaml create mode 100644 tests/Security/yaml_fixtures/source/security_52_with_multiple_authenticators.yaml create mode 100644 tests/Util/yaml_fixtures/complex_string_to_array.test create mode 100644 tests/Util/yaml_fixtures/string_to_array.test create mode 100644 tests/Util/yaml_fixtures/string_to_array_with_eof_blank_line.test create mode 100644 tests/fixtures/MakeAuthenticatorSecurity52Empty/config/packages/security.yaml create mode 100644 tests/fixtures/MakeAuthenticatorSecurity52LoginForm/config/packages/security.yaml create mode 100644 tests/fixtures/MakeAuthenticatorSecurity52LoginForm/src/Entity/User.php create mode 100644 tests/fixtures/MakeAuthenticatorSecurity52LoginForm/src/Repository/UserRepository.php create mode 100644 tests/fixtures/MakeAuthenticatorSecurity52LoginForm/tests/SecurityControllerTest.php diff --git a/src/Generator.php b/src/Generator.php index 83dee909f..ac98c4978 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -168,6 +168,7 @@ private function addOperation(string $targetPath, string $templateName, array $v $variables['relative_path'] = $this->fileManager->relativizePath($targetPath); $variables['use_attributes'] = $this->phpCompatUtil->canUseAttributes(); + $variables['use_typed_properties'] = $this->phpCompatUtil->canUseTypedProperties(); $templatePath = $templateName; if (!file_exists($templatePath)) { diff --git a/src/Maker/MakeAuthenticator.php b/src/Maker/MakeAuthenticator.php index 41d20b67f..ba9552599 100644 --- a/src/Maker/MakeAuthenticator.php +++ b/src/Maker/MakeAuthenticator.php @@ -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 + * @author Ryan Weaver + * @author Jesse Rushlow * * @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 security.yaml could not be updated automatically. You\'ll need to add the following config manually:\n\n'.$yamlExample; } diff --git a/src/Resources/skeleton/authenticator/Security52EmptyAuthenticator.tpl.php b/src/Resources/skeleton/authenticator/Security52EmptyAuthenticator.tpl.php new file mode 100644 index 000000000..329c3b9e5 --- /dev/null +++ b/src/Resources/skeleton/authenticator/Security52EmptyAuthenticator.tpl.php @@ -0,0 +1,44 @@ + + +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 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 +// */ +// } +} diff --git a/src/Resources/skeleton/authenticator/Security52LoginFormAuthenticator.tpl.php b/src/Resources/skeleton/authenticator/Security52LoginFormAuthenticator.tpl.php new file mode 100644 index 000000000..29308f8db --- /dev/null +++ b/src/Resources/skeleton/authenticator/Security52LoginFormAuthenticator.tpl.php @@ -0,0 +1,62 @@ + + +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 extends AbstractLoginFormAuthenticator +{ + use TargetPathTrait; + + public const LOGIN_ROUTE = 'app_login'; + + private $urlGenerator; + + public function __construct(UrlGeneratorInterface $urlGenerator) + { + $this->urlGenerator = $urlGenerator; + } + + public function authenticate(Request $request): PassportInterface + { + $ = $request->request->get('', ''); + + $request->getSession()->set(Security::LAST_USERNAME, $); + + return new Passport( + new UserBadge($), + 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); + } +} diff --git a/src/Security/SecurityConfigUpdater.php b/src/Security/SecurityConfigUpdater.php index f495f285b..8355df15c 100644 --- a/src/Security/SecurityConfigUpdater.php +++ b/src/Security/SecurityConfigUpdater.php @@ -12,9 +12,13 @@ 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 + * @author Jesse Rushlow + * * @internal */ final class SecurityConfigUpdater @@ -22,6 +26,14 @@ 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. */ @@ -29,6 +41,10 @@ public function updateForUserClass(string $yamlSource, UserClassConfiguration $u { $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() diff --git a/src/Util/PhpCompatUtil.php b/src/Util/PhpCompatUtil.php index 44c612119..5bd2ed5f8 100644 --- a/src/Util/PhpCompatUtil.php +++ b/src/Util/PhpCompatUtil.php @@ -36,6 +36,13 @@ public function canUseAttributes(): bool return version_compare($version, '8alpha', '>=') && Kernel::VERSION_ID >= 50200; } + public function canUseTypedProperties(): bool + { + $version = $this->getPhpVersion(); + + return version_compare($version, '7.4', '>='); + } + protected function getPhpVersion(): string { $rootDirectory = $this->fileManager->getRootDirectory(); diff --git a/src/Util/YamlSourceManipulator.php b/src/Util/YamlSourceManipulator.php index a708e39c0..b6f1a8e9d 100644 --- a/src/Util/YamlSourceManipulator.php +++ b/src/Util/YamlSourceManipulator.php @@ -231,6 +231,13 @@ private function updateData(array $newData) $this->advanceBeyondMultilineArrayLastItem($currentData, $newData); } + if (0 === $this->indentationForDepths[$this->depth] && $this->depth > 1) { + $ident = $this->getPreferredIndentationSize(); + $previousDepth = $this->depth - 1; + + $this->indentationForDepths[$this->depth] = ($ident + $this->indentationForDepths[$previousDepth]); + } + while (\count($currentData) < \count($newData)) { $newKey = array_keys($newData)[\count($currentData)]; @@ -471,9 +478,8 @@ private function changeValueInYaml($value) // we're converting from a scalar to a (multiline) array // this means we need to break onto the next line - // increase the indentation - $this->manuallyIncrementIndentation(); - $newYamlValue = "\n".$this->indentMultilineYamlArray($newYamlValue); + // increase(override) the indentation + $newYamlValue = "\n".$this->indentMultilineYamlArray($newYamlValue, ($this->indentationForDepths[$this->depth] + $this->getPreferredIndentationSize())); } elseif ($this->isCurrentArrayMultiline() && $this->isCurrentArraySequence()) { // we are a multi-line sequence, so drop to next line, indent and add "- " in front $newYamlValue = "\n".$this->indentMultilineYamlArray('- '.$newYamlValue); @@ -948,9 +954,11 @@ private function decrementDepth() --$this->depth; } - private function getCurrentIndentation(): string + private function getCurrentIndentation(int $override = null): string { - return str_repeat(' ', $this->indentationForDepths[$this->depth]); + $indent = $override ?? $this->indentationForDepths[$this->depth]; + + return str_repeat(' ', $indent); } private function log(string $message, $includeContent = false) @@ -1295,19 +1303,21 @@ private function isCharLineBreak(string $char): bool * Usually an empty line needs to be prepended to this result before * adding to the content. */ - private function indentMultilineYamlArray(string $yaml): string + private function indentMultilineYamlArray(string $yaml, int $indentOverride = null): string { + $indent = $this->getCurrentIndentation($indentOverride); + // But, if the *value* is an array, then ITS children will // also need to be indented artificially by the same amount - $yaml = str_replace("\n", "\n".$this->getCurrentIndentation(), $yaml); + $yaml = str_replace("\n", "\n".$indent, $yaml); if ($this->isMultilineString($yaml)) { // Remove extra indentation in case of blank line in multiline string - $yaml = str_replace("\n".$this->getCurrentIndentation()."\n", "\n\n", $yaml); + $yaml = str_replace("\n".$indent."\n", "\n\n", $yaml); } // now indent this level - return $this->getCurrentIndentation().$yaml; + return $indent.$yaml; } private function findPositionOfMultilineCharInLine(int $position): ?int diff --git a/tests/Maker/MakeAuthenticatorTest.php b/tests/Maker/MakeAuthenticatorTest.php index a75aa5ade..345c009a4 100644 --- a/tests/Maker/MakeAuthenticatorTest.php +++ b/tests/Maker/MakeAuthenticatorTest.php @@ -305,5 +305,71 @@ function (string $output, string $directory) { } ), ]; + + yield 'security_52_empty_authenticator' => [ + MakerTestDetails::createTest( + $this->getMakerInstance(MakeAuthenticator::class), + [ + // authenticator type => empty-auth + 0, + // authenticator class name + 'AppCustomAuthenticator', + ] + ) + ->setRequiredPhpVersion(70200) + ->addRequiredPackageVersion('symfony/security-bundle', '>=5.2') + ->setFixtureFilesPath(__DIR__.'/../fixtures/MakeAuthenticatorSecurity52Empty') + ->assert( + function (string $output, string $directory) { + $this->assertStringContainsString('Success', $output); + + $fs = new Filesystem(); + $this->assertTrue($fs->exists(sprintf('%s/src/Security/AppCustomAuthenticator.php', $directory))); + + $securityConfig = Yaml::parse(file_get_contents(sprintf('%s/config/packages/security.yaml', $directory))); + + $this->assertEquals( + 'App\\Security\\AppCustomAuthenticator', + $securityConfig['security']['firewalls']['main']['custom_authenticator'] + ); + } + ), + ]; + + yield 'security_52_login_form_authenticator' => [ + MakerTestDetails::createTest( + $this->getMakerInstance(MakeAuthenticator::class), + [ + // authenticator type => login-form + 1, + // class name + 'AppTestSecurity52LoginFormAuthenticator', + // controller name + 'SecurityController', + // User selector field + 'userEmail', + // Logout Url + 'no', + ] + ) + ->setRequiredPhpVersion(70200) + ->addRequiredPackageVersion('symfony/security-bundle', '>=5.2') + ->addExtraDependencies('doctrine') + ->addExtraDependencies('twig') + ->addExtraDependencies('symfony/form') + ->setFixtureFilesPath(__DIR__.'/../fixtures/MakeAuthenticatorSecurity52LoginForm') + ->configureDatabase() + ->updateSchemaAfterCommand() + ->assert( + function (string $output, string $directory) { + $this->assertStringContainsString('Success', $output); + + $fs = new Filesystem(); + $this->assertTrue($fs->exists(sprintf('%s/src/Controller/SecurityController.php', $directory))); + $this->assertTrue($fs->exists(sprintf('%s/templates/security/login.html.twig', $directory))); + $this->assertTrue($fs->exists(sprintf('%s/src/Security/AppTestSecurity52LoginFormAuthenticator.php', $directory))); + } + ), + ]; } } diff --git a/tests/Security/SecurityConfigUpdaterTest.php b/tests/Security/SecurityConfigUpdaterTest.php index a212ce3a5..05e43cc9e 100644 --- a/tests/Security/SecurityConfigUpdaterTest.php +++ b/tests/Security/SecurityConfigUpdaterTest.php @@ -12,23 +12,40 @@ namespace Symfony\Bundle\MakerBundle\Tests\Security; use PHPUnit\Framework\TestCase; +use Psr\Log\LogLevel; use Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater; use Symfony\Bundle\MakerBundle\Security\UserClassConfiguration; +use Symfony\Component\HttpKernel\Log\Logger; use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder; class SecurityConfigUpdaterTest extends TestCase { + /** + * Set to true to enable low level debug logging during tests for + * the YamlSourceManipulator. + * + * @var bool + */ + private $enableYsmLogging = false; + + /** + * @var Logger|null + */ + private $ysmLogger = null; + /** * @dataProvider getUserClassTests */ public function testUpdateForUserClass(UserClassConfiguration $userConfig, string $expectedSourceFilename, string $startingSourceFilename = 'simple_security.yaml') { + $this->createLogger(); + $userClass = $userConfig->isEntity() ? 'App\\Entity\\User' : 'App\\Security\\User'; if (!$userConfig->isEntity()) { $userConfig->setUserProviderClass('App\\Security\\UserProvider'); } - $updater = new SecurityConfigUpdater(); + $updater = new SecurityConfigUpdater($this->ysmLogger); $source = file_get_contents(__DIR__.'/yaml_fixtures/source/'.$startingSourceFilename); $actualSource = $updater->updateForUserClass($source, $userConfig, $userClass); $expectedSource = file_get_contents(__DIR__.'/yaml_fixtures/expected_user_class/'.$expectedSourceFilename); @@ -83,11 +100,13 @@ public function getUserClassTests() /** * @dataProvider getAuthenticatorTests */ - public function testUpdateForAuthenticator(string $firewallName, $entryPoint, string $expectedSourceFilename, string $startingSourceFilename, bool $logoutSetup) + public function testUpdateForAuthenticator(string $firewallName, $entryPoint, string $expectedSourceFilename, string $startingSourceFilename, bool $logoutSetup, bool $useSecurity51) { - $updater = new SecurityConfigUpdater(); + $this->createLogger(); + + $updater = new SecurityConfigUpdater($this->ysmLogger); $source = file_get_contents(__DIR__.'/yaml_fixtures/source/'.$startingSourceFilename); - $actualSource = $updater->updateForAuthenticator($source, $firewallName, $entryPoint, 'App\\Security\\AppCustomAuthenticator', $logoutSetup); + $actualSource = $updater->updateForAuthenticator($source, $firewallName, $entryPoint, 'App\\Security\\AppCustomAuthenticator', $logoutSetup, $useSecurity51); $expectedSource = file_get_contents(__DIR__.'/yaml_fixtures/expected_authenticator/'.$expectedSourceFilename); $this->assertSame($expectedSource, $actualSource); @@ -101,6 +120,7 @@ public function getAuthenticatorTests() 'empty_source.yaml', 'empty_security.yaml', false, + false, ]; yield 'simple_security' => [ @@ -109,6 +129,7 @@ public function getAuthenticatorTests() 'simple_security_source.yaml', 'simple_security.yaml', false, + false, ]; yield 'simple_security_with_firewalls' => [ @@ -117,6 +138,7 @@ public function getAuthenticatorTests() 'simple_security_with_firewalls.yaml', 'simple_security_with_firewalls.yaml', false, + false, ]; yield 'simple_security_with_firewalls_and_authenticator' => [ @@ -125,6 +147,7 @@ public function getAuthenticatorTests() 'simple_security_with_firewalls_and_authenticator.yaml', 'simple_security_with_firewalls_and_authenticator.yaml', false, + false, ]; yield 'simple_security_with_firewalls_and_logout' => [ @@ -133,6 +156,56 @@ public function getAuthenticatorTests() 'simple_security_with_firewalls_and_logout.yaml', 'simple_security_with_firewalls_and_logout.yaml', true, + false, + ]; + + yield 'security_52_empty_source' => [ + 'main', + null, + 'security_52_empty_source.yaml', + 'security_52_empty_security.yaml', + false, + true, ]; + + yield 'security_52_simple_security_with_firewalls_and_logout' => [ + 'main', + 'App\\Security\\AppCustomAuthenticator', + 'security_52_with_firewalls_and_logout.yaml', + 'security_52_with_firewalls_and_logout.yaml', + true, + true, + ]; + + yield 'security_52_with_multiple_authenticators' => [ + 'main', + 'App\\Security\\AppCustomAuthenticator', + 'security_52_with_multiple_authenticators.yaml', + 'security_52_with_multiple_authenticators.yaml', + false, + true, + ]; + } + + private function createLogger(): void + { + if (!$this->enableYsmLogging) { + return; + } + + $this->ysmLogger = new Logger(LogLevel::DEBUG, 'php://stdout', function (string $level, string $message, array $context) { + $maxLen = max(array_map('strlen', array_keys($context))); + + foreach ($context as $key => $val) { + $message .= sprintf( + "\n %s%s: %s", + str_repeat(' ', $maxLen - \strlen($key)), + $key, + $val + ); + } + + return $message."\n\n"; + }); } } diff --git a/tests/Security/yaml_fixtures/expected_authenticator/empty_source.yaml b/tests/Security/yaml_fixtures/expected_authenticator/empty_source.yaml index 6ad70689c..699784f62 100644 --- a/tests/Security/yaml_fixtures/expected_authenticator/empty_source.yaml +++ b/tests/Security/yaml_fixtures/expected_authenticator/empty_source.yaml @@ -1,6 +1,6 @@ security: firewalls: main: - anonymous: true + anonymous: lazy guard: authenticators: [App\Security\AppCustomAuthenticator] \ No newline at end of file diff --git a/tests/Security/yaml_fixtures/expected_authenticator/security_52_empty_source.yaml b/tests/Security/yaml_fixtures/expected_authenticator/security_52_empty_source.yaml new file mode 100644 index 000000000..26d47aae2 --- /dev/null +++ b/tests/Security/yaml_fixtures/expected_authenticator/security_52_empty_source.yaml @@ -0,0 +1,6 @@ +security: + enable_authenticator_manager: true + firewalls: + main: + lazy: true + custom_authenticator: App\Security\AppCustomAuthenticator diff --git a/tests/Security/yaml_fixtures/expected_authenticator/security_52_with_firewalls_and_logout.yaml b/tests/Security/yaml_fixtures/expected_authenticator/security_52_with_firewalls_and_logout.yaml new file mode 100644 index 000000000..bb191e875 --- /dev/null +++ b/tests/Security/yaml_fixtures/expected_authenticator/security_52_with_firewalls_and_logout.yaml @@ -0,0 +1,20 @@ +security: + enable_authenticator_manager: true + # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers + providers: + in_memory: { memory: ~ } + + firewalls: + dev: + pattern: ^/(_(profiler|wdt)|css|images|js)/ + security: false + main: + lazy: true + custom_authenticator: App\Security\AppCustomAuthenticator + + # the entry_point start() method determines what happens when an anonymous user accesses a protected page + entry_point: App\Security\AppCustomAuthenticator + logout: + path: app_logout + # where to redirect after logout + # target: app_any_route diff --git a/tests/Security/yaml_fixtures/expected_authenticator/security_52_with_multiple_authenticators.yaml b/tests/Security/yaml_fixtures/expected_authenticator/security_52_with_multiple_authenticators.yaml new file mode 100644 index 000000000..77b3bb419 --- /dev/null +++ b/tests/Security/yaml_fixtures/expected_authenticator/security_52_with_multiple_authenticators.yaml @@ -0,0 +1,11 @@ +security: + enable_authenticator_manager: true + firewalls: + main: + lazy: true + custom_authenticator: + - App\Security\SomeOtherAuthenticator + - App\Security\AppCustomAuthenticator + + # the entry_point start() method determines what happens when an anonymous user accesses a protected page + entry_point: App\Security\AppCustomAuthenticator diff --git a/tests/Security/yaml_fixtures/expected_authenticator/simple_security_source.yaml b/tests/Security/yaml_fixtures/expected_authenticator/simple_security_source.yaml index f84a7ce38..83cce0bc7 100644 --- a/tests/Security/yaml_fixtures/expected_authenticator/simple_security_source.yaml +++ b/tests/Security/yaml_fixtures/expected_authenticator/simple_security_source.yaml @@ -6,7 +6,7 @@ security: firewalls: dev: ~ main: - anonymous: true + anonymous: lazy guard: authenticators: - App\Security\AppCustomAuthenticator diff --git a/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls.yaml b/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls.yaml index 7337047e1..183cbb744 100644 --- a/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls.yaml +++ b/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls.yaml @@ -8,7 +8,7 @@ security: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: - anonymous: true + anonymous: lazy guard: authenticators: - App\Security\AppCustomAuthenticator diff --git a/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls_and_authenticator.yaml b/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls_and_authenticator.yaml index 60428439f..a3b3d7e26 100644 --- a/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls_and_authenticator.yaml +++ b/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls_and_authenticator.yaml @@ -8,11 +8,11 @@ security: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: - anonymous: true + anonymous: lazy guard: authenticators: - App\Security\Authenticator - App\Security\AppCustomAuthenticator entry_point: App\Security\AppCustomAuthenticator foo: - anonymous: true + anonymous: lazy diff --git a/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls_and_logout.yaml b/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls_and_logout.yaml index b54a9c364..0a63d44cf 100644 --- a/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls_and_logout.yaml +++ b/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls_and_logout.yaml @@ -8,7 +8,7 @@ security: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: - anonymous: true + anonymous: lazy guard: authenticators: - App\Security\AppCustomAuthenticator diff --git a/tests/Security/yaml_fixtures/expected_user_class/simple_security_with_single_memory_provider_configured.yaml b/tests/Security/yaml_fixtures/expected_user_class/simple_security_with_single_memory_provider_configured.yaml index f9d927a67..2896481a2 100644 --- a/tests/Security/yaml_fixtures/expected_user_class/simple_security_with_single_memory_provider_configured.yaml +++ b/tests/Security/yaml_fixtures/expected_user_class/simple_security_with_single_memory_provider_configured.yaml @@ -14,5 +14,5 @@ security: firewalls: dev: ~ main: - anonymous: true + anonymous: lazy provider: app_user_provider diff --git a/tests/Security/yaml_fixtures/source/security_52_empty_security.yaml b/tests/Security/yaml_fixtures/source/security_52_empty_security.yaml new file mode 100644 index 000000000..f1e0ffdd4 --- /dev/null +++ b/tests/Security/yaml_fixtures/source/security_52_empty_security.yaml @@ -0,0 +1,2 @@ +security: + enable_authenticator_manager: true diff --git a/tests/Security/yaml_fixtures/source/security_52_with_firewalls_and_logout.yaml b/tests/Security/yaml_fixtures/source/security_52_with_firewalls_and_logout.yaml new file mode 100644 index 000000000..5b6b7af8c --- /dev/null +++ b/tests/Security/yaml_fixtures/source/security_52_with_firewalls_and_logout.yaml @@ -0,0 +1,12 @@ +security: + enable_authenticator_manager: true + # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers + providers: + in_memory: { memory: ~ } + + firewalls: + dev: + pattern: ^/(_(profiler|wdt)|css|images|js)/ + security: false + main: + lazy: true diff --git a/tests/Security/yaml_fixtures/source/security_52_with_multiple_authenticators.yaml b/tests/Security/yaml_fixtures/source/security_52_with_multiple_authenticators.yaml new file mode 100644 index 000000000..3de191b3a --- /dev/null +++ b/tests/Security/yaml_fixtures/source/security_52_with_multiple_authenticators.yaml @@ -0,0 +1,6 @@ +security: + enable_authenticator_manager: true + firewalls: + main: + lazy: true + custom_authenticator: App\Security\SomeOtherAuthenticator diff --git a/tests/Security/yaml_fixtures/source/simple_security_with_firewalls.yaml b/tests/Security/yaml_fixtures/source/simple_security_with_firewalls.yaml index e36f35da0..4bf4bf561 100644 --- a/tests/Security/yaml_fixtures/source/simple_security_with_firewalls.yaml +++ b/tests/Security/yaml_fixtures/source/simple_security_with_firewalls.yaml @@ -8,4 +8,4 @@ security: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: - anonymous: true + anonymous: lazy diff --git a/tests/Security/yaml_fixtures/source/simple_security_with_firewalls_and_authenticator.yaml b/tests/Security/yaml_fixtures/source/simple_security_with_firewalls_and_authenticator.yaml index 58deab039..0518625f5 100644 --- a/tests/Security/yaml_fixtures/source/simple_security_with_firewalls_and_authenticator.yaml +++ b/tests/Security/yaml_fixtures/source/simple_security_with_firewalls_and_authenticator.yaml @@ -8,9 +8,9 @@ security: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: - anonymous: true + anonymous: lazy guard: authenticators: - App\Security\Authenticator foo: - anonymous: true + anonymous: lazy diff --git a/tests/Security/yaml_fixtures/source/simple_security_with_firewalls_and_logout.yaml b/tests/Security/yaml_fixtures/source/simple_security_with_firewalls_and_logout.yaml index e36f35da0..4bf4bf561 100644 --- a/tests/Security/yaml_fixtures/source/simple_security_with_firewalls_and_logout.yaml +++ b/tests/Security/yaml_fixtures/source/simple_security_with_firewalls_and_logout.yaml @@ -8,4 +8,4 @@ security: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: - anonymous: true + anonymous: lazy diff --git a/tests/Security/yaml_fixtures/source/simple_security_with_single_memory_provider_configured.yaml b/tests/Security/yaml_fixtures/source/simple_security_with_single_memory_provider_configured.yaml index a99edf3d1..c0f39446d 100644 --- a/tests/Security/yaml_fixtures/source/simple_security_with_single_memory_provider_configured.yaml +++ b/tests/Security/yaml_fixtures/source/simple_security_with_single_memory_provider_configured.yaml @@ -6,5 +6,5 @@ security: firewalls: dev: ~ main: - anonymous: true + anonymous: lazy provider: in_memory diff --git a/tests/Util/PhpVersionTest.php b/tests/Util/PhpVersionTest.php index ac2ca4e0c..74f114494 100644 --- a/tests/Util/PhpVersionTest.php +++ b/tests/Util/PhpVersionTest.php @@ -24,7 +24,7 @@ class PhpVersionTest extends TestCase /** * @dataProvider phpVersionDataProvider */ - public function testUsesPhpPlatformFromComposerJsonFile(string $version, bool $expectedResult): void + public function testUsesPhpPlatformFromComposerJsonFileForCanUseAttributes(string $version, bool $expectedResult): void { $json = sprintf('{"platform-overrides": {"php": "%s"}}', $version); @@ -139,6 +139,53 @@ public function testWithoutPlatformVersionSet(): void self::assertSame(PHP_VERSION, $result); } + + /** + * @dataProvider phpVersionForTypedPropertiesDataProvider + */ + public function testCanUseTypedProperties(string $version, bool $expectedResult): void + { + $json = sprintf('{"platform-overrides": {"php": "%s"}}', $version); + + $mockFileManager = $this->createMock(FileManager::class); + $mockFileManager + ->expects(self::once()) + ->method('getRootDirectory') + ->willReturn('/test') + ; + + $mockFileManager + ->expects(self::once()) + ->method('fileExists') + ->with('/test/composer.lock') + ->willReturn(true) + ; + + $mockFileManager + ->expects(self::once()) + ->method('getFileContents') + ->with('/test/composer.lock') + ->willReturn($json) + ; + + $version = new PhpCompatUtil($mockFileManager); + + $result = $version->canUseTypedProperties(); + + self::assertSame($expectedResult, $result); + } + + public function phpVersionForTypedPropertiesDataProvider(): \Generator + { + yield ['8', true]; + yield ['8.0.1', true]; + yield ['8RC1', true]; + yield ['7.4', true]; + yield ['7.4.6', true]; + yield ['7', false]; + yield ['7.0', false]; + yield ['5.7', false]; + } } class PhpCompatUtilTestFixture extends PhpCompatUtil diff --git a/tests/Util/YamlSourceManipulatorTest.php b/tests/Util/YamlSourceManipulatorTest.php index f79611570..960f098d6 100644 --- a/tests/Util/YamlSourceManipulatorTest.php +++ b/tests/Util/YamlSourceManipulatorTest.php @@ -59,7 +59,7 @@ private function getYamlDataTests() list($source, $changeCode, $expected) = explode('===', $file->getContents()); // Multiline string ends with an \n - $source = rtrim($source, "\n"); + $source = substr_replace($source, '', (\strlen($source) - 1)); $expected = ltrim($expected, "\n"); $data = Yaml::parse($source); diff --git a/tests/Util/yaml_fixtures/complex_string_to_array.test b/tests/Util/yaml_fixtures/complex_string_to_array.test new file mode 100644 index 000000000..a42dad214 --- /dev/null +++ b/tests/Util/yaml_fixtures/complex_string_to_array.test @@ -0,0 +1,14 @@ +main: + custom_authenticator: App\Security\SomeOtherAuthenticator +=== +$string = $data['main']['custom_authenticator']; +$data['main']['custom_authenticator'] = []; +$data['main']['custom_authenticator'][] = $string; +$data['main']['custom_authenticator'][] = 'App\Security\AppCustomAuthenticator'; +$data['main']['entry_point'] = 'Entry'; +=== +main: + custom_authenticator: + - App\Security\SomeOtherAuthenticator + - App\Security\AppCustomAuthenticator + entry_point: Entry \ No newline at end of file diff --git a/tests/Util/yaml_fixtures/string_to_array.test b/tests/Util/yaml_fixtures/string_to_array.test new file mode 100644 index 000000000..4ee858c0d --- /dev/null +++ b/tests/Util/yaml_fixtures/string_to_array.test @@ -0,0 +1,18 @@ +security: + firewalls: + main: + lazy: true + custom_authenticator: App\Security\SomeOtherAuthenticator +=== +$string = $data['security']['firewalls']['main']['custom_authenticator']; +$data['security']['firewalls']['main']['custom_authenticator'] = []; +$data['security']['firewalls']['main']['custom_authenticator'][] = $string; +$data['security']['firewalls']['main']['custom_authenticator'][] = 'App\Security\AppCustomAuthenticator'; +=== +security: + firewalls: + main: + lazy: true + custom_authenticator: + - App\Security\SomeOtherAuthenticator + - App\Security\AppCustomAuthenticator \ No newline at end of file diff --git a/tests/Util/yaml_fixtures/string_to_array_with_eof_blank_line.test b/tests/Util/yaml_fixtures/string_to_array_with_eof_blank_line.test new file mode 100644 index 000000000..95a9ab435 --- /dev/null +++ b/tests/Util/yaml_fixtures/string_to_array_with_eof_blank_line.test @@ -0,0 +1,21 @@ +security: + firewalls: + main: + custom_authenticator: App\Security\SomeOtherAuthenticator + + + +=== +$string = $data['security']['firewalls']['main']['custom_authenticator']; +$data['security']['firewalls']['main']['custom_authenticator'] = []; +$data['security']['firewalls']['main']['custom_authenticator'][] = $string; +$data['security']['firewalls']['main']['custom_authenticator'][] = 'App\Security\AppCustomAuthenticator'; +=== +security: + firewalls: + main: + custom_authenticator: + - App\Security\SomeOtherAuthenticator + - App\Security\AppCustomAuthenticator + + diff --git a/tests/fixtures/MakeAuthenticatorSecurity52Empty/config/packages/security.yaml b/tests/fixtures/MakeAuthenticatorSecurity52Empty/config/packages/security.yaml new file mode 100644 index 000000000..001b36296 --- /dev/null +++ b/tests/fixtures/MakeAuthenticatorSecurity52Empty/config/packages/security.yaml @@ -0,0 +1,25 @@ +security: + enable_authenticator_manager: true + # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers + providers: + in_memory: { memory: ~ } + firewalls: + dev: + pattern: ^/(_(profiler|wdt)|css|images|js)/ + security: false + main: + lazy: true + + # activate different ways to authenticate + + # http_basic: true + # https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate + + # form_login: true + # https://symfony.com/doc/current/security/form_login_setup.html + + # Easy way to control access for large sections of your site + # Note: Only the *first* access control that matches will be used + access_control: + # - { path: ^/admin, roles: ROLE_ADMIN } + # - { path: ^/profile, roles: ROLE_USER } diff --git a/tests/fixtures/MakeAuthenticatorSecurity52LoginForm/config/packages/security.yaml b/tests/fixtures/MakeAuthenticatorSecurity52LoginForm/config/packages/security.yaml new file mode 100644 index 000000000..88c916fde --- /dev/null +++ b/tests/fixtures/MakeAuthenticatorSecurity52LoginForm/config/packages/security.yaml @@ -0,0 +1,34 @@ +security: + encoders: + App\Entity\User: plaintext + + enable_authenticator_manager: true + + # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers + providers: + # used to reload user from session & other features (e.g. switch_user) + app_user_provider: + entity: + class: App\Entity\User + property: userEmail + + firewalls: + dev: + pattern: ^/(_(profiler|wdt)|css|images|js)/ + security: false + main: + + lazy: true + # activate different ways to authenticate + + # http_basic: true + # https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate + + # form_login: true + # https://symfony.com/doc/current/security/form_login_setup.html + + # Easy way to control access for large sections of your site + # Note: Only the *first* access control that matches will be used + access_control: + # - { path: ^/admin, roles: ROLE_ADMIN } + # - { path: ^/profile, roles: ROLE_USER } diff --git a/tests/fixtures/MakeAuthenticatorSecurity52LoginForm/src/Entity/User.php b/tests/fixtures/MakeAuthenticatorSecurity52LoginForm/src/Entity/User.php new file mode 100644 index 000000000..d88f9b5ac --- /dev/null +++ b/tests/fixtures/MakeAuthenticatorSecurity52LoginForm/src/Entity/User.php @@ -0,0 +1,113 @@ +id; + } + + public function getUserEmail() + { + return $this->userEmail; + } + + public function setUserEmail(string $userEmail): self + { + $this->userEmail = $userEmail; + + return $this; + } + + /** + * A visual identifier that represents this user. + * + * @see UserInterface + */ + public function getUsername(): string + { + return (string) $this->userEmail; + } + + /** + * @see UserInterface + */ + public function getRoles(): array + { + $roles = $this->roles; + // guarantee every user at least has ROLE_USER + $roles[] = 'ROLE_USER'; + + return array_unique($roles); + } + + public function setRoles(array $roles): self + { + $this->roles = $roles; + + return $this; + } + + /** + * @see UserInterface + */ + public function getPassword(): string + { + return (string) $this->password; + } + + public function setPassword(string $password): self + { + $this->password = $password; + + return $this; + } + + /** + * @see UserInterface + */ + public function getSalt() + { + // not needed when using the "bcrypt" algorithm in security.yaml + } + + /** + * @see UserInterface + */ + public function eraseCredentials() + { + // If you store any temporary, sensitive data on the user, clear it here + // $this->plainPassword = null; + } +} diff --git a/tests/fixtures/MakeAuthenticatorSecurity52LoginForm/src/Repository/UserRepository.php b/tests/fixtures/MakeAuthenticatorSecurity52LoginForm/src/Repository/UserRepository.php new file mode 100644 index 000000000..3455d5c52 --- /dev/null +++ b/tests/fixtures/MakeAuthenticatorSecurity52LoginForm/src/Repository/UserRepository.php @@ -0,0 +1,38 @@ +setPassword($newEncodedPassword); + $this->_em->persist($user); + $this->_em->flush(); + } +} \ No newline at end of file diff --git a/tests/fixtures/MakeAuthenticatorSecurity52LoginForm/tests/SecurityControllerTest.php b/tests/fixtures/MakeAuthenticatorSecurity52LoginForm/tests/SecurityControllerTest.php new file mode 100644 index 000000000..592ee6874 --- /dev/null +++ b/tests/fixtures/MakeAuthenticatorSecurity52LoginForm/tests/SecurityControllerTest.php @@ -0,0 +1,74 @@ + + */ +class SecurityControllerTest extends WebTestCase +{ + public function testGeneratedAuthenticatorHasExpectedConstructorArgs(): void + { + $authenticatorReflection = new \ReflectionClass(AppTestSecurity52LoginFormAuthenticator::class); + $constructorParameters = $authenticatorReflection->getConstructor()->getParameters(); + + self::assertSame(UrlGeneratorInterface::class, $constructorParameters[0]->getType()->getName()); + self::assertSame('urlGenerator', $constructorParameters[0]->getName()); + } + + public function testLoginFormAuthenticatorUsingSecurity51(): void + { + $client = self::createClient(); + $crawler = $client->request('GET', '/login'); + + self::assertEquals(200, $client->getResponse()->getStatusCode()); + + /** @var EntityManagerInterface $em */ + $em = self::$kernel->getContainer() + ->get('doctrine') + ->getManager(); + + $user = (new User())->setUserEmail('test@symfony.com') + ->setPassword('password'); + $em->persist($user); + $em->flush(); + + $form = $crawler->filter('form')->form(); + $form->setValues( + [ + 'userEmail' => 'test@symfony.com', + 'password' => 'foo', + ] + ); + $crawler = $client->submit($form); + + if (500 === $client->getResponse()->getStatusCode()) { + self::assertEquals('', $crawler->filter('h1.exception-message')->text()); + } + + self::assertEquals(302, $client->getResponse()->getStatusCode()); + + $client->followRedirect(); + + self::assertEquals(200, $client->getResponse()->getStatusCode()); + self::assertStringContainsString('Invalid credentials.', $client->getResponse()->getContent()); + + $form->setValues( + [ + 'userEmail' => 'test@symfony.com', + 'password' => 'password', + ] + ); + $client->submit($form); + + self::assertStringContainsString('TODO: provide a valid redirect', $client->getResponse()->getContent()); + self::assertNotNull($token = $client->getContainer()->get('security.token_storage')->getToken()); + self::assertInstanceOf(User::class, $token->getUser()); + } +}