8000 [Security][RateLimiter] Allow to use no lock in the rate limiter/logi… · symfony/symfony@dbe11a1 · GitHub
[go: up one dir, main page]

Skip to content

Commit dbe11a1

Browse files
committed
[Security][RateLimiter] Allow to use no lock in the rate limiter/login throttling
1 parent bc9e946 commit dbe11a1

File tree

7 files changed

+64
-8
lines changed

7 files changed

+64
-8
lines changed

UPGRADE-5.3.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ Security
7777
SecurityBundle
7878
--------------
7979

80+
* [BC break] Add `login_throttling.lock_factory` setting defaulting to `null`. Set this option
81+
to `lock.factory` if you need precise login rate limiting with synchronous requests.
8082
* Deprecate `UserPasswordEncoderCommand` class and the corresponding `user:encode-password` command,
8183
use `UserPasswordHashCommand` and `user:hash-password` instead
8284
* Deprecate the `security.encoder_factory.generic` service, the `security.encoder_factory` and `Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface` aliases,

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1860,7 +1860,7 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode)
18601860
->arrayPrototype()
18611861
->children()
18621862
->scalarNode('lock_factory')
1863-
->info('The service ID of the lock factory used by this limiter')
1863+
->info('The service ID of the lock factory used by this limiter (or null to disable locking)')
18641864
->defaultValue('lock.factory')
18651865
->end()
18661866
->scalarNode('cache_pool')

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ class FrameworkExtension extends Extension
200200
private $mailerConfigEnabled = false;
201201
private $httpClientConfigEnabled = false;
202202
private $notifierConfigEnabled = false;
203-
private $lockConfigEnabled = false;
203+
private static $lockConfigEnabled = false;
204204

205205
/**
206206
* Responds to the app.config configuration parameter.
@@ -433,7 +433,7 @@ public function load(array $configs, ContainerBuilder $container)
433433
$this->registerPropertyInfoConfiguration($container, $loader);
434434
}
435435

436-
if ($this->lockConfigEnabled = $this->isConfigEnabled($container, $config['lock'])) {
436+
if (self::$lockConfigEnabled = $this->isConfigEnabled($container, $config['lock'])) {
437437
$this->registerLockConfiguration($config['lock'], $container, $loader);
438438
}
439439

@@ -2326,10 +2326,6 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $
23262326

23272327
private function registerRateLimiterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
23282328
{
2329-
if (!$this->lockConfigEnabled) {
2330-
throw new LogicException('Rate limiter support cannot be enabled without enabling the Lock component.');
2331-
}
2332-
23332329
$loader->load('rate_limiter.php');
23342330

23352331
foreach ($config['limiters'] as $name => $limiterConfig) {
@@ -2344,7 +2340,13 @@ public static function registerRateLimiter(ContainerBuilder $container, string $
23442340

23452341
$limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter'));
23462342

2347-
$limiter->addArgument(new Reference($limiterConfig['lock_factory']));
2343+
if (null !== $limiterConfig['lock_factory']) {
2344+
if (!self::$lockConfigEnabled) {
2345+
throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed and configured.', $name));
2346+
}
2347+
2348+
$limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory']));
2349+
}
23482350
unset($limiterConfig['lock_factory']);
23492351

23502352
$storageId = $limiterConfig['storage_service'] ?? null;

src/Symfony/Bundle/FrameworkBundle/Resources/config/rate_limiter.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
->args([
2525
abstract_arg('config'),
2626
abstract_arg('storage'),
27+
null,
2728
])
2829
;
2930
};

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
use Symfony\Component\Config\FileLocator;
1515
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Exception\LogicException;
17+
use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException;
1618
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
1719
use Symfony\Component\Workflow\Exception\InvalidDefinitionException;
1820

@@ -82,4 +84,50 @@ public function testWorkflowValidationStateMachine()
8284
]);
8385
});
8486
}
87+
88+
public function testRateLimiterWithLockFactory()
89+
{
90+
try {
91+
$this->createContainerFromClosure(function (ContainerBuilder $container) {
92+
$container->loadFromExtension('framework', [
93+
'rate_limiter' => [
94+
'with_lock' => ['policy' => 'fixed_window', 'limit' => 10, 'interval' => '1 hour'],
95+
],
96+
]);
97+
});
98+
99+
$this->fail('No LogicException thrown');
100+
} catch (LogicException $e) {
101+
$this->assertEquals('Rate limiter "with_lock" requires the Lock component to be installed and configured.', $e->getMessage());
102+
}
103+
104+
105+
$container = $this->createContainerFromClosure(function (ContainerBuilder $container) {
106+
$container->loadFromExtension('framework', [
107+
'lock' => true,
108+
'rate_limiter' => [
109+
'with_lock' => ['policy' => 'fixed_window', 'limit' => 10, 'interval' => '1 hour'],
110+
],
111+
]);
112+
});
113+
114+
$withLock = $container->getDefinition('limiter.with_lock');
115+
$this->assertEquals('lock.factory', (string) $withLock->getArgument(2));
116+
}
117+
118+
public function testRateLimiterLockFactory()
119+
{
120+
$this->expectException(OutOfBoundsException::class);
121+
$this->expectExceptionMessage('The argument "2" doesn\'t exist.');
122+
123+
$container = $this->createContainerFromClosure(function (ContainerBuilder $container) {
124+
$container->loadFromExtension('framework', [
125+
'rate_limiter' => [
126+
'without_lock' => ['policy' => 'fixed_window', 'limit' => 10, 'interval' => '1 hour', 'lock_factory' => null],
127+
],
128+
]);
129+
});
130+
131+
$container->getDefinition('limiter.without_lock')->getArgument(2);
132+
}
85133
}

src/Symfony/Bundle/SecurityBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
5.3
55
---
66

7+
* [BC break] Add `login_throttling.lock_factory` setting defaulting to `null` (instead of `lock.factory`)
78
* Add the `debug:firewall` command.
89
* Deprecate `UserPasswordEncoderCommand` class and the corresponding `user:encode-password` command,
910
use `UserPasswordHashCommand` and `user:hash-password` instead

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public function addConfiguration(NodeDefinition $builder)
5454
->children()
5555
->scalarNode('limiter')->info(sprintf('A service id implementing "%s".', RequestRateLimiterInterface::class))->end()
5656
->integerNode('max_attempts')->defaultValue(5)->end()
57+
->scalarNode('lock_factory')->info('The service ID of the lock factory used by the login rate limiter (or null to disable locking)')->defaultNull()->end()
5758
->end();
5859
}
5960

@@ -76,6 +77,7 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal
7677
'policy' => 'fixed_window',
7778
'limit' => $config['max_attempts'],
7879
'interval' => '1 minute',
80+
'lock_factory' => $config['lock_factory'],
7981
];
8082
FrameworkExtension::registerRateLimiter($container, $localId = '_login_local_'.$firewallName, $limiterOptions);
8183

0 commit comments

Comments
 (0)
0