Issue #3225354 by eddie_c, paulocs, Bhanu951, daffie, Spokje, Berdir: Add a clearByPrefix() method to the flood API

merge-requests/3171/head
catch 2023-01-24 12:38:49 +00:00
parent 9dc5c66ea2
commit be7d65ecf1
5 changed files with 92 additions and 3 deletions

View File

@ -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}
*/

View File

@ -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);

View File

@ -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}
*/

View File

@ -0,0 +1,20 @@
<?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;
}

View File

@ -115,4 +115,41 @@ class FloodTest extends KernelTestBase {
$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));
}
}
}