From 849d78d17c61b602b26ca02132313c33c62f7699 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Wed, 24 Apr 2024 08:57:44 +0200 Subject: [PATCH] Add throttling support to authenticator maker In the MakeAuthenticator class, a new optional argument 'support-throttling' was added. This allows the user to specify if they want to enable throttling protection during the authentication generation process. If 'support-throttling' is enabled, the LimiterInterface dependency is added and the 'throttling' node is set in the firewall configuration. --- composer.json | 1 + src/Maker/MakeAuthenticator.php | 30 ++++++++++++++++--- src/Security/SecurityConfigUpdater.php | 6 +++- tests/Security/SecurityConfigUpdaterTest.php | 23 ++++++++++++-- ...lls_always_remember_me_and_throttling.yaml | 19 ++++++++++++ 5 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls_always_remember_me_and_throttling.yaml diff --git a/composer.json b/composer.json index 00f629c37..8a8429560 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "doctrine/orm": "^2.15|^3", "symfony/http-client": "^6.4|^7.0", "symfony/phpunit-bridge": "^6.4.1|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", "symfony/security-core": "^6.4|^7.0", "symfony/yaml": "^6.4|^7.0", "twig/twig": "^3.0|^4.x-dev" diff --git a/src/Maker/MakeAuthenticator.php b/src/Maker/MakeAuthenticator.php index 00b029b64..1674fea7e 100644 --- a/src/Maker/MakeAuthenticator.php +++ b/src/Maker/MakeAuthenticator.php @@ -38,6 +38,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\RateLimiter\LimiterInterface; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -209,6 +210,15 @@ function ($answer) { $supportRememberMeValues[$supportRememberMeType] ); } + + $command->addArgument('support-throttling', InputArgument::OPTIONAL); + $input->setArgument( + 'support-throttling', + $io->confirm( + 'Do you want to enable the throttling protection?', + true + ) + ); } } @@ -219,6 +229,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $supportRememberMe = $input->hasArgument('support-remember-me') ? $input->getArgument('support-remember-me') : false; $alwaysRememberMe = $input->hasArgument('always-remember-me') && self::REMEMBER_ME_TYPE_ALWAYS === $input->getArgument('always-remember-me'); + $supportThrottling = $input->hasArgument('support-throttling') ? $input->getArgument('support-throttling') : false; $this->generateAuthenticatorClass( $securityData, @@ -246,7 +257,8 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $input->getArgument('authenticator-class'), $input->hasArgument('logout-setup') ? $input->getArgument('logout-setup') : false, $supportRememberMe, - $alwaysRememberMe + $alwaysRememberMe, + $supportThrottling, ); $generator->dumpFile($path, $newYaml); $securityYamlUpdated = true; @@ -275,7 +287,8 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $input->hasArgument('user-class') ? $input->getArgument('user-class') : null, $input->hasArgument('logout-setup') ? $input->getArgument('logout-setup') : false, $supportRememberMe, - $alwaysRememberMe + $alwaysRememberMe, + $supportThrottling ) ); } @@ -403,7 +416,7 @@ private function generateFormLoginFiles(string $controllerClass, string $userNam } /** @return string[] */ - private function generateNextMessage(bool $securityYamlUpdated, string $authenticatorType, string $authenticatorClass, ?string $userClass, bool $logoutSetup, bool $supportRememberMe, bool $alwaysRememberMe): array + private function generateNextMessage(bool $securityYamlUpdated, string $authenticatorType, string $authenticatorClass, ?string $userClass, bool $logoutSetup, bool $supportRememberMe, bool $alwaysRememberMe, bool $supportThrottling): array { $nextTexts = ['Next:']; $nextTexts[] = '- Customize your new authenticator.'; @@ -416,7 +429,8 @@ private function generateNextMessage(bool $securityYamlUpdated, string $authenti $authenticatorClass, $logoutSetup, $supportRememberMe, - $alwaysRememberMe + $alwaysRememberMe, + $supportThrottling ); $nextTexts[] = "- Your security.yaml could not be updated automatically. You'll need to add the following config manually:\n\n".$yamlExample; } @@ -461,5 +475,13 @@ public function configureDependencies(DependencyBuilder $dependencies, ?InputInt Yaml::class, 'yaml' ); + + $supportThrottling = $input?->hasArgument('support-throttling') ? $input->getArgument('support-throttling') : false; + if ($supportThrottling) { + $dependencies->addClassDependency( + LimiterInterface::class, + 'symfony/rate-limiter' + ); + } } } diff --git a/src/Security/SecurityConfigUpdater.php b/src/Security/SecurityConfigUpdater.php index e713bc1b6..726991f42 100644 --- a/src/Security/SecurityConfigUpdater.php +++ b/src/Security/SecurityConfigUpdater.php @@ -69,7 +69,7 @@ public function updateForUserClass(string $yamlSource, UserClassConfiguration $u return $contents; } - public function updateForAuthenticator(string $yamlSource, string $firewallName, $chosenEntryPoint, string $authenticatorClass, bool $logoutSetup, bool $supportRememberMe, bool $alwaysRememberMe): string + public function updateForAuthenticator(string $yamlSource, string $firewallName, $chosenEntryPoint, string $authenticatorClass, bool $logoutSetup, bool $supportRememberMe, bool $alwaysRememberMe, bool $supportThrottling): string { $this->createYamlSourceManipulator($yamlSource); @@ -145,6 +145,10 @@ public function updateForAuthenticator(string $yamlSource, string $firewallName, } } + if ($supportThrottling) { + $firewall['throttling'] = null; + } + $newData['security']['firewalls'][$firewallName] = $firewall; if (!isset($firewall['logout']) && $logoutSetup) { diff --git a/tests/Security/SecurityConfigUpdaterTest.php b/tests/Security/SecurityConfigUpdaterTest.php index a7076915c..82f1be73c 100644 --- a/tests/Security/SecurityConfigUpdaterTest.php +++ b/tests/Security/SecurityConfigUpdaterTest.php @@ -104,13 +104,13 @@ public function getUserClassTests(): \Generator /** * @dataProvider getAuthenticatorTests */ - public function testUpdateForAuthenticator(string $firewallName, $entryPoint, string $expectedSourceFilename, string $startingSourceFilename, bool $logoutSetup, bool $supportRememberMe, bool $alwaysRememberMe): void + public function testUpdateForAuthenticator(string $firewallName, $entryPoint, string $expectedSourceFilename, string $startingSourceFilename, bool $logoutSetup, bool $supportRememberMe, bool $alwaysRememberMe, bool $supportThrottling): void { $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, $supportRememberMe, $alwaysRememberMe); + $actualSource = $updater->updateForAuthenticator($source, $firewallName, $entryPoint, 'App\\Security\\AppCustomAuthenticator', $logoutSetup, $supportRememberMe, $alwaysRememberMe, $supportThrottling); $expectedSource = file_get_contents(__DIR__.'/yaml_fixtures/expected_authenticator/'.$expectedSourceFilename); $this->assertSame($expectedSource, $actualSource); @@ -126,6 +126,7 @@ public function getAuthenticatorTests(): \Generator false, false, false, + false, ]; yield 'simple_security' => [ @@ -136,6 +137,7 @@ public function getAuthenticatorTests(): \Generator false, false, false, + false, ]; yield 'simple_security_with_firewalls' => [ @@ -146,6 +148,7 @@ public function getAuthenticatorTests(): \Generator false, false, false, + false, ]; yield 'simple_security_with_firewalls_and_authenticator' => [ @@ -156,6 +159,7 @@ public function getAuthenticatorTests(): \Generator false, false, false, + false, ]; yield 'simple_security_with_firewalls_and_logout' => [ @@ -166,6 +170,7 @@ public function getAuthenticatorTests(): \Generator true, false, false, + false, ]; yield 'security_52_with_multiple_authenticators' => [ @@ -176,6 +181,7 @@ public function getAuthenticatorTests(): \Generator false, false, false, + false, ]; yield 'simple_security_with_firewalls_and_remember_me_checkbox' => [ @@ -186,6 +192,7 @@ public function getAuthenticatorTests(): \Generator false, true, false, + false, ]; yield 'simple_security_with_firewalls_and_always_remember_me' => [ @@ -196,6 +203,18 @@ public function getAuthenticatorTests(): \Generator false, true, true, + false, + ]; + + yield 'simple_security_with_firewalls_always_remember_me_and_throttling' => [ + 'main', + null, + 'simple_security_with_firewalls_always_remember_me_and_throttling.yaml', + 'simple_security.yaml', + false, + true, + true, + true, ]; } diff --git a/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls_always_remember_me_and_throttling.yaml b/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls_always_remember_me_and_throttling.yaml new file mode 100644 index 000000000..0c7233ebd --- /dev/null +++ b/tests/Security/yaml_fixtures/expected_authenticator/simple_security_with_firewalls_always_remember_me_and_throttling.yaml @@ -0,0 +1,19 @@ +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: ~ + main: + lazy: true + custom_authenticator: App\Security\AppCustomAuthenticator + + remember_me: + secret: '%kernel.secret%' + lifetime: 604800 + path: / + always_remember_me: true + throttling: null