Loading core/lib/Drupal/Core/Flood/DatabaseBackend.php +16 −1 Original line number Diff line number Diff line Loading @@ -9,7 +9,7 @@ /** * Defines the database flood backend. This is the default Drupal backend. */ class DatabaseBackend implements FloodInterface { class DatabaseBackend implements FloodInterface, PrefixFloodInterface { /** * The database table name. Loading Loading @@ -107,6 +107,21 @@ public function clear($name, $identifier = NULL) { } } /** * {@inheritdoc} */ public function clearByPrefix(string $name, string $prefix): void { try { $this->connection->delete(static::TABLE_NAME) ->condition('event', $name) ->condition('identifier', $prefix . '-%', 'LIKE') ->execute(); } catch (\Exception $e) { $this->catchException($e); } } /** * {@inheritdoc} */ Loading core/lib/Drupal/Core/Flood/FloodInterface.php +4 −1 Original line number Diff line number Diff line Loading @@ -21,7 +21,10 @@ interface FloodInterface { * table from growing indefinitely. * @param string $identifier * (optional) Unique identifier of the current user. Defaults to the current * user's IP address). * user's IP address. The identifier can be given an additional prefix * separated by "-". Flood backends may then optionally implement the * PrefixFloodInterface which allows all flood events that share the same * prefix to be cleared simultaneously. */ public function register($name, $window = 3600, $identifier = NULL); Loading core/lib/Drupal/Core/Flood/MemoryBackend.php +15 −1 Original line number Diff line number Diff line Loading @@ -7,7 +7,7 @@ /** * Defines the memory flood backend. This is used for testing. */ class MemoryBackend implements FloodInterface { class MemoryBackend implements FloodInterface, PrefixFloodInterface { /** * The request stack. Loading Loading @@ -54,6 +54,20 @@ public function clear($name, $identifier = NULL) { unset($this->events[$name][$identifier]); } /** * {@inheritdoc} */ public function clearByPrefix(string $name, string $prefix): void { foreach ($this->events as $event_name => $identifier) { $identifier_key = key($identifier); $identifier_parts = explode("-", $identifier_key); $identifier_prefix = reset($identifier_parts); if ($prefix == $identifier_prefix && $name == $event_name) { unset($this->events[$event_name][$identifier_key]); } } } /** * {@inheritdoc} */ Loading core/lib/Drupal/Core/Flood/PrefixFloodInterface.php 0 → 100644 +20 −0 Original line number Diff line number Diff line <?php namespace Drupal\Core\Flood; /** * Defines an interface for flood controllers that clear by identifier prefix. */ interface PrefixFloodInterface { /** * Makes the flood control mechanism forget an event by identifier prefix. * * @param string $name * The name of an event. * @param string $prefix * The prefix of the identifier to be cleared. */ public function clearByPrefix(string $name, string $prefix): void; } core/modules/system/tests/src/Kernel/System/FloodTest.php +37 −0 Original line number Diff line number Diff line Loading @@ -115,4 +115,41 @@ public function testDatabaseBackend() { $this->assertFalse($flood->isAllowed($name, $threshold)); } /** * Provides an array of backends for testClearByPrefix. */ public function floodBackendProvider() :array { $request_stack = \Drupal::service('request_stack'); $connection = \Drupal::service('database'); return [ new MemoryBackend($request_stack), new DatabaseBackend($connection, $request_stack), ]; } /** * Tests clearByPrefix method on flood backends. */ public function testClearByPrefix() { $threshold = 1; $window_expired = 3600; $identifier = 'prefix-127.0.0.1'; $name = 'flood_test_cleanup'; // We can't use an PHPUnit data provider because we need access to the // container. $backends = $this->floodBackendProvider(); foreach ($backends as $backend) { // Register unexpired event. $backend->register($name, $window_expired, $identifier); // Verify event is not allowed. $this->assertFalse($backend->isAllowed($name, $threshold, $window_expired, $identifier)); // Clear by prefix and verify event is now allowed. $backend->clearByPrefix($name, 'prefix'); $this->assertTrue($backend->isAllowed($name, $threshold)); } } } Loading
core/lib/Drupal/Core/Flood/DatabaseBackend.php +16 −1 Original line number Diff line number Diff line Loading @@ -9,7 +9,7 @@ /** * Defines the database flood backend. This is the default Drupal backend. */ class DatabaseBackend implements FloodInterface { class DatabaseBackend implements FloodInterface, PrefixFloodInterface { /** * The database table name. Loading Loading @@ -107,6 +107,21 @@ public function clear($name, $identifier = NULL) { } } /** * {@inheritdoc} */ public function clearByPrefix(string $name, string $prefix): void { try { $this->connection->delete(static::TABLE_NAME) ->condition('event', $name) ->condition('identifier', $prefix . '-%', 'LIKE') ->execute(); } catch (\Exception $e) { $this->catchException($e); } } /** * {@inheritdoc} */ Loading
core/lib/Drupal/Core/Flood/FloodInterface.php +4 −1 Original line number Diff line number Diff line Loading @@ -21,7 +21,10 @@ interface FloodInterface { * table from growing indefinitely. * @param string $identifier * (optional) Unique identifier of the current user. Defaults to the current * user's IP address). * user's IP address. The identifier can be given an additional prefix * separated by "-". Flood backends may then optionally implement the * PrefixFloodInterface which allows all flood events that share the same * prefix to be cleared simultaneously. */ public function register($name, $window = 3600, $identifier = NULL); Loading
core/lib/Drupal/Core/Flood/MemoryBackend.php +15 −1 Original line number Diff line number Diff line Loading @@ -7,7 +7,7 @@ /** * Defines the memory flood backend. This is used for testing. */ class MemoryBackend implements FloodInterface { class MemoryBackend implements FloodInterface, PrefixFloodInterface { /** * The request stack. Loading Loading @@ -54,6 +54,20 @@ public function clear($name, $identifier = NULL) { unset($this->events[$name][$identifier]); } /** * {@inheritdoc} */ public function clearByPrefix(string $name, string $prefix): void { foreach ($this->events as $event_name => $identifier) { $identifier_key = key($identifier); $identifier_parts = explode("-", $identifier_key); $identifier_prefix = reset($identifier_parts); if ($prefix == $identifier_prefix && $name == $event_name) { unset($this->events[$event_name][$identifier_key]); } } } /** * {@inheritdoc} */ Loading
core/lib/Drupal/Core/Flood/PrefixFloodInterface.php 0 → 100644 +20 −0 Original line number Diff line number Diff line <?php namespace Drupal\Core\Flood; /** * Defines an interface for flood controllers that clear by identifier prefix. */ interface PrefixFloodInterface { /** * Makes the flood control mechanism forget an event by identifier prefix. * * @param string $name * The name of an event. * @param string $prefix * The prefix of the identifier to be cleared. */ public function clearByPrefix(string $name, string $prefix): void; }
core/modules/system/tests/src/Kernel/System/FloodTest.php +37 −0 Original line number Diff line number Diff line Loading @@ -115,4 +115,41 @@ public function testDatabaseBackend() { $this->assertFalse($flood->isAllowed($name, $threshold)); } /** * Provides an array of backends for testClearByPrefix. */ public function floodBackendProvider() :array { $request_stack = \Drupal::service('request_stack'); $connection = \Drupal::service('database'); return [ new MemoryBackend($request_stack), new DatabaseBackend($connection, $request_stack), ]; } /** * Tests clearByPrefix method on flood backends. */ public function testClearByPrefix() { $threshold = 1; $window_expired = 3600; $identifier = 'prefix-127.0.0.1'; $name = 'flood_test_cleanup'; // We can't use an PHPUnit data provider because we need access to the // container. $backends = $this->floodBackendProvider(); foreach ($backends as $backend) { // Register unexpired event. $backend->register($name, $window_expired, $identifier); // Verify event is not allowed. $this->assertFalse($backend->isAllowed($name, $threshold, $window_expired, $identifier)); // Clear by prefix and verify event is now allowed. $backend->clearByPrefix($name, 'prefix'); $this->assertTrue($backend->isAllowed($name, $threshold)); } } }