Skip to content
Snippets Groups Projects
Commit 50b3ded7 authored by Samit Khulve's avatar Samit Khulve Committed by Boris Doesborg
Browse files

Issue #3454910 by samit.310@gmail.com, ptmkenny, naveenvalecha, grevil,...

Issue #3454910 by samit.310@gmail.com, ptmkenny, naveenvalecha, grevil, batigolix, benstallings, web-beest: Drupal 11 compatibility fixes for flood_control
parent 8a83ccf4
Branches
Tags 3.0.0
1 merge request!513454910: Drupal 11 compatibility
Pipeline #294116 passed
......@@ -32,7 +32,7 @@ phpcs:
# Uncomment the lines below if you want to override any of the variables. The following is just an example.
################
variables:
OPT_IN_TEST_PREVIOUS_MAJOR: '1'
OPT_IN_TEST_NEXT_MAJOR: '1'
OPT_IN_TEST_NEXT_MINOR: '1'
_CSPELL_WORDS: 'Unblockmanager,batigolix,Doesborg,fabianderijk,Rijk'
# SKIP_ESLINT: '1'
......
......@@ -4,7 +4,7 @@
"type": "drupal-module",
"license": "GPL-2.0-or-later",
"require": {
"drupal/core": "^9 || ^10"
"drupal/core": "^10.2 || ^11"
},
"extra": {
"drush": {
......
......@@ -3,4 +3,4 @@ description: "Allows configuring hidden flood control options and unblocking IP
configure: flood_control.settings
package: 'Administration'
type: module
core_version_requirement: ^9 || ^10
core_version_requirement: ^10.2 || ^11
......@@ -6,12 +6,14 @@
*/
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Assign newly added permissions to the appropriate roles.
*/
function flood_control_update_9201() {
$roles = user_role_names(FALSE, 'access flood unblock');
$names = array_filter(Role::loadMultiple(), fn(RoleInterface $role) => $role->hasPermission('access flood unblock'));
$roles = array_map(fn(RoleInterface $role) => $role->label(), $names);
foreach ($roles as $roleKey => $roleName) {
user_role_grant_permissions($roleKey, [
'administer flood unblock',
......
<?php
namespace Drupal\flood_control;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Flood\FloodInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Provides Flood Unblock actions.
*/
class FloodUnblockManager implements FloodUnblockManagerInterface {
use StringTranslationTrait;
/**
* The Database Connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The Entity Type Manager Interface.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The Flood Interface.
*
* @var \Drupal\Core\Flood\FloodInterface
*/
protected $flood;
/**
* The Immutable Config.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected $config;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* The logger factory.
*
* @var \Drupal\Core\Logger\LoggerChannelFactory
*/
protected $loggerFactory;
/**
* FloodUnblockAdminForm constructor.
*
* @param \Drupal\Core\Database\Connection $database
* The database connection.
* @param \Drupal\Core\Flood\FloodInterface $flood
* The flood interface.
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The Config Factory Interface.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The Entity Type Manager Interface.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The Messenger Interface.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* The logger factory.
*/
public function __construct(Connection $database, FloodInterface $flood, ConfigFactoryInterface $configFactory, EntityTypeManagerInterface $entityTypeManager, MessengerInterface $messenger, LoggerChannelFactoryInterface $logger_factory) {
$this->database = $database;
$this->flood = $flood;
$this->entityTypeManager = $entityTypeManager;
$this->config = $configFactory->get('user.flood');
$this->messenger = $messenger;
$this->loggerFactory = $logger_factory->get('flood_control');
}
/**
* {@inheritdoc}
*/
public function fetchIdentifiers($results) {
$identifiers = [];
foreach ($results as $result) {
// Sets ip as default value and adds to identifiers array.
$identifiers[$result] = $result;
// Sets location as value and adds to identifiers array.
if (function_exists('smart_ip_get_location')) {
$location = smart_ip_get_location($result);
$location_string = sprintf(" (%s %s %s)", $location['city'], $location['region'], $location['country_code']);
$identifiers[$result] = "$location_string ($result)";
}
// Sets link to user as value and adds to identifiers array.
$parts = explode('-', $result);
if (isset($parts[0]) && isset($parts[1])) {
$uid = $parts[0];
/** @var \Drupal\user\Entity\User $user */
$user = $this->entityTypeManager->getStorage('user')
->load($uid);
if (isset($user)) {
$user_link = $user->toLink($user->getAccountName());
}
else {
$user_link = $this->t('Deleted user: @user', ['@user' => $uid]);
}
$identifiers[$result] = $user_link;
}
}
return $identifiers;
}
/**
* {@inheritdoc}
*/
public function floodUnblockClearEvent($fid) {
$txn = $this->database->startTransaction('flood_unblock_clear');
try {
$query = $this->database->delete('flood')
->condition('fid', $fid);
$success = $query->execute();
if ($success) {
$this->messenger->addMessage($this->t('Flood entries cleared.'), 'status', FALSE);
}
}
catch (\Exception $e) {
// Something went wrong somewhere, so roll back now.
$txn->rollback();
// Log the exception to drupal.
$this->loggerFactory->error($e);
$this->messenger->addMessage($this->t('Error: @error', ['@error' => (string) $e]), 'error');
}
}
/**
* {@inheritdoc}
*/
public function getEvents() {
return [
'user.failed_login_ip' => [
'type' => 'ip',
'label' => $this->t('User failed login IP'),
],
'user.failed_login_user' => [
'type' => 'user',
'label' => $this->t('User failed login user'),
],
'user.http_login' => [
'type' => 'user',
'label' => $this->t('User failed http login'),
],
'user.password_request_ip' => [
'type' => 'user',
'label' => $this->t('User failed password request IP'),
],
'user.password_request_user' => [
'type' => 'user',
'label' => $this->t('User failed password request user'),
],
];
}
/**
* {@inheritdoc}
*/
public function getEventLabel($event) {
$event_mapping = $this->getEvents();
if (array_key_exists($event, $event_mapping)) {
return $event_mapping[$event]['label'];
}
return ucfirst(str_replace(['.', '_'], ' ', $event));
}
/**
* {@inheritdoc}
*/
public function getEventType($event) {
$event_mapping = $this->getEvents();
if (array_key_exists($event, $event_mapping)) {
return $event_mapping[$event]['type'];
}
$parts = explode('.', $event);
return $parts[0];
}
/**
* {@inheritdoc}
*/
public function isBlocked($identifier, $event) {
$type = $this->getEventType($event);
switch ($type) {
case 'user':
return !$this->flood->isAllowed($event, $this->config->get('user_limit'), $this->config->get('user_window'), $identifier);
case 'ip':
return !$this->flood->isAllowed($event, $this->config->get('ip_limit'), $this->config->get('ip_window'), $identifier);
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function getEventIds($event, $identifier = NULL) {
$event_ids = [];
$query = $this->database->select('flood', 'f');
$query->condition('event', $event);
if ($identifier) {
$query->condition('f.identifier', $identifier, 'LIKE');
}
$query->fields('f', ['fid']);
$result = $query->execute();
foreach ($result as $record) {
$event_ids[] = $record->fid;
}
return $event_ids;
}
}
......@@ -25,6 +25,13 @@ abstract class FloodUnblockManagerBase implements FloodUnblockManagerInterface {
*/
protected $config;
/**
* The Entity Type Manager Interface.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* {@inheritdoc}
*/
......
......@@ -21,6 +21,20 @@ class FloodUnblockManagerDatabase extends FloodUnblockManagerBase {
*/
protected $database;
/**
* The Flood Service.
*
* @var \Drupal\Core\Flood\FloodInterface
*/
protected $flood;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $config;
/**
* The Entity Type Manager Interface.
*
......
......@@ -3,6 +3,7 @@
namespace Drupal\flood_control\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\ConfigFormBase;
......@@ -32,8 +33,13 @@ class FloodControlSettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function __construct(ConfigFactoryInterface $config_factory, DateFormatterInterface $dateFormatter, ModuleHandlerInterface $module_handler) {
parent::__construct($config_factory);
public function __construct(
ConfigFactoryInterface $configFactory,
TypedConfigManagerInterface $typedConfigManager,
DateFormatterInterface $dateFormatter,
ModuleHandlerInterface $module_handler,
) {
parent::__construct($configFactory, $typedConfigManager);
$this->dateFormatter = $dateFormatter;
$this->moduleHandler = $module_handler;
}
......@@ -44,8 +50,9 @@ class FloodControlSettingsForm extends ConfigFormBase {
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('config.typed'),
$container->get('date.formatter'),
$container->get('module_handler')
$container->get('module_handler'),
);
}
......
......@@ -4,6 +4,8 @@ namespace Drupal\Tests\flood_control\Functional;
use Drupal\Tests\BrowserTestBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
/**
* Tests that the Flood control UI pages are reachable.
......@@ -55,7 +57,7 @@ class FloodControlUiPageTest extends BrowserTestBase {
/**
* Create required user and other objects in order to run tests.
*/
public function setUp(): void {
protected function setUp(): void {
parent::setUp();
$this->adminUser = $this->drupalCreateUser([]);
......@@ -69,6 +71,7 @@ class FloodControlUiPageTest extends BrowserTestBase {
// Flood backends need a request object. Create a dummy one and insert it
// to the container.
$request = Request::createFromGlobals();
$request->setSession(new Session(new MockArraySessionStorage()));
$this->container->get('request_stack')->push($request);
// The flood table is opportunistically created during first use. In these
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment