diff --git a/core/lib/Drupal/Core/Flood/DatabaseBackend.php b/core/lib/Drupal/Core/Flood/DatabaseBackend.php index e1f57a51868..dedb6724d7a 100644 --- a/core/lib/Drupal/Core/Flood/DatabaseBackend.php +++ b/core/lib/Drupal/Core/Flood/DatabaseBackend.php @@ -9,7 +9,7 @@ use Drupal\Core\Database\Connection; /** * Defines the database flood backend. This is the default Drupal backend. */ -class DatabaseBackend implements FloodInterface { +class DatabaseBackend implements FloodInterface, PrefixFloodInterface { /** * The database table name. @@ -107,6 +107,21 @@ class DatabaseBackend implements FloodInterface { } } + /** + * {@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} */ diff --git a/core/lib/Drupal/Core/Flood/FloodInterface.php b/core/lib/Drupal/Core/Flood/FloodInterface.php index fd98398fea8..9f3539fc4f4 100644 --- a/core/lib/Drupal/Core/Flood/FloodInterface.php +++ b/core/lib/Drupal/Core/Flood/FloodInterface.php @@ -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); diff --git a/core/lib/Drupal/Core/Flood/MemoryBackend.php b/core/lib/Drupal/Core/Flood/MemoryBackend.php index 062471b3db8..29d13468eba 100644 --- a/core/lib/Drupal/Core/Flood/MemoryBackend.php +++ b/core/lib/Drupal/Core/Flood/MemoryBackend.php @@ -7,7 +7,7 @@ use Symfony\Component\HttpFoundation\RequestStack; /** * Defines the memory flood backend. This is used for testing. */ -class MemoryBackend implements FloodInterface { +class MemoryBackend implements FloodInterface, PrefixFloodInterface { /** * The request stack. @@ -54,6 +54,20 @@ class MemoryBackend implements FloodInterface { 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} */ diff --git a/core/lib/Drupal/Core/Flood/PrefixFloodInterface.php b/core/lib/Drupal/Core/Flood/PrefixFloodInterface.php new file mode 100644 index 00000000000..3732cc3cc17 --- /dev/null +++ b/core/lib/Drupal/Core/Flood/PrefixFloodInterface.php @@ -0,0 +1,20 @@ +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)); + } + } + }