Commit be7d65ec authored by catch's avatar catch
Browse files

Issue #3225354 by eddie_c, paulocs, Bhanu951, daffie, Spokje, Berdir: Add a...

Issue #3225354 by eddie_c, paulocs, Bhanu951, daffie, Spokje, Berdir: Add a clearByPrefix() method to the flood API
parent 9dc5c66e
Loading
Loading
Loading
Loading
+16 −1
Original line number Diff line number Diff line
@@ -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.
@@ -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}
   */
+4 −1
Original line number Diff line number Diff line
@@ -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);

+15 −1
Original line number Diff line number Diff line
@@ -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.
@@ -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}
   */
+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;

}
+37 −0
Original line number Diff line number Diff line
@@ -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));
    }
  }

}