Issue #3410582 by catch, Prashant.c, heddn, alexpott, smustgrave, claudiu.cristea, longwave, quietone: Optimize user logins by avoiding duplicate entity queries

(cherry picked from commit 1916b7863e)
merge-requests/7287/head
Alex Pott 2024-03-28 11:14:07 +00:00
parent 2505f212bf
commit 2076c3d9fe
No known key found for this signature in database
GPG Key ID: BDA67E7EE836E5CE
7 changed files with 190 additions and 66 deletions

View File

@ -10,6 +10,7 @@ use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Flood\FloodInterface; use Drupal\Core\Flood\FloodInterface;
use Drupal\Core\Http\Exception\CacheableUnauthorizedHttpException; use Drupal\Core\Http\Exception\CacheableUnauthorizedHttpException;
use Drupal\user\UserAuthenticationInterface;
use Drupal\user\UserAuthInterface; use Drupal\user\UserAuthInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
@ -29,7 +30,7 @@ class BasicAuth implements AuthenticationProviderInterface, AuthenticationProvid
/** /**
* The user auth service. * The user auth service.
* *
* @var \Drupal\user\UserAuthInterface * @var \Drupal\user\UserAuthInterface|\Drupal\user\UserAuthenticationInterface
*/ */
protected $userAuth; protected $userAuth;
@ -52,15 +53,18 @@ class BasicAuth implements AuthenticationProviderInterface, AuthenticationProvid
* *
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory. * The config factory.
* @param \Drupal\user\UserAuthInterface $user_auth * @param \Drupal\user\UserAuthInterface|\Drupal\user\UserAuthenticationInterface $user_auth
* The user authentication service. * The user authentication service.
* @param \Drupal\Core\Flood\FloodInterface $flood * @param \Drupal\Core\Flood\FloodInterface $flood
* The flood service. * The flood service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager service. * The entity type manager service.
*/ */
public function __construct(ConfigFactoryInterface $config_factory, UserAuthInterface $user_auth, FloodInterface $flood, EntityTypeManagerInterface $entity_type_manager) { public function __construct(ConfigFactoryInterface $config_factory, UserAuthInterface|UserAuthenticationInterface $user_auth, FloodInterface $flood, EntityTypeManagerInterface $entity_type_manager) {
$this->configFactory = $config_factory; $this->configFactory = $config_factory;
if (!$user_auth instanceof UserAuthenticationInterface) {
@trigger_error('The $user_auth parameter implementing UserAuthInterface is deprecated in drupal:10.3.0 and will be removed in drupal:12.0.0. Implement UserAuthenticationInterface instead. See https://www.drupal.org/node/3411040');
}
$this->userAuth = $user_auth; $this->userAuth = $user_auth;
$this->flood = $flood; $this->flood = $flood;
$this->entityTypeManager = $entity_type_manager; $this->entityTypeManager = $entity_type_manager;
@ -90,8 +94,17 @@ class BasicAuth implements AuthenticationProviderInterface, AuthenticationProvid
// in to many different user accounts. We have a reasonably high limit // in to many different user accounts. We have a reasonably high limit
// since there may be only one apparent IP for all users at an institution. // since there may be only one apparent IP for all users at an institution.
if ($this->flood->isAllowed('basic_auth.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) { if ($this->flood->isAllowed('basic_auth.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
$account = FALSE;
if ($this->userAuth instanceof UserAuthenticationInterface) {
$lookup = $this->userAuth->lookupAccount($username);
if ($lookup && !$lookup->isBlocked()) {
$account = $lookup;
}
}
else {
$accounts = $this->entityTypeManager->getStorage('user')->loadByProperties(['name' => $username, 'status' => 1]); $accounts = $this->entityTypeManager->getStorage('user')->loadByProperties(['name' => $username, 'status' => 1]);
$account = reset($accounts); $account = reset($accounts);
}
if ($account) { if ($account) {
if ($flood_config->get('uid_only')) { if ($flood_config->get('uid_only')) {
// Register flood events based on the uid only, so they apply for any // Register flood events based on the uid only, so they apply for any
@ -107,10 +120,16 @@ class BasicAuth implements AuthenticationProviderInterface, AuthenticationProvid
// Don't allow login if the limit for this user has been reached. // Don't allow login if the limit for this user has been reached.
// Default is to allow 5 failed attempts every 6 hours. // Default is to allow 5 failed attempts every 6 hours.
if ($this->flood->isAllowed('basic_auth.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) { if ($this->flood->isAllowed('basic_auth.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
$uid = FALSE;
if ($this->userAuth instanceof UserAuthenticationInterface) {
$uid = $this->userAuth->authenticateAccount($account, $password) ? $account->id() : FALSE;
}
else {
$uid = $this->userAuth->authenticate($username, $password); $uid = $this->userAuth->authenticate($username, $password);
}
if ($uid) { if ($uid) {
$this->flood->clear('basic_auth.failed_login_user', $identifier); $this->flood->clear('basic_auth.failed_login_user', $identifier);
return $this->entityTypeManager->getStorage('user')->load($uid); return $account;
} }
else { else {
// Register a per-user failed login event. // Register a per-user failed login event.

View File

@ -6,6 +6,7 @@ use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Routing\RouteProviderInterface; use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\user\UserAuthenticationInterface;
use Drupal\user\UserAuthInterface; use Drupal\user\UserAuthInterface;
use Drupal\user\UserFloodControlInterface; use Drupal\user\UserFloodControlInterface;
use Drupal\user\UserInterface; use Drupal\user\UserInterface;
@ -61,8 +62,7 @@ class UserAuthenticationController extends ControllerBase implements ContainerIn
/** /**
* The user authentication. * The user authentication.
* * @var \Drupal\user\UserAuthInterface|\Drupal\user\UserAuthenticationInterface
* @var \Drupal\user\UserAuthInterface
*/ */
protected $userAuth; protected $userAuth;
@ -103,7 +103,7 @@ class UserAuthenticationController extends ControllerBase implements ContainerIn
* The user storage. * The user storage.
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
* The CSRF token generator. * The CSRF token generator.
* @param \Drupal\user\UserAuthInterface $user_auth * @param \Drupal\user\UserAuthenticationInterface|\Drupal\user\UserAuthInterface $user_auth
* The user authentication. * The user authentication.
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
* The route provider. * The route provider.
@ -114,10 +114,13 @@ class UserAuthenticationController extends ControllerBase implements ContainerIn
* @param \Psr\Log\LoggerInterface $logger * @param \Psr\Log\LoggerInterface $logger
* A logger instance. * A logger instance.
*/ */
public function __construct(UserFloodControlInterface $user_flood_control, UserStorageInterface $user_storage, CsrfTokenGenerator $csrf_token, UserAuthInterface $user_auth, RouteProviderInterface $route_provider, Serializer $serializer, array $serializer_formats, LoggerInterface $logger) { public function __construct(UserFloodControlInterface $user_flood_control, UserStorageInterface $user_storage, CsrfTokenGenerator $csrf_token, UserAuthenticationInterface|UserAuthInterface $user_auth, RouteProviderInterface $route_provider, Serializer $serializer, array $serializer_formats, LoggerInterface $logger) {
$this->userFloodControl = $user_flood_control; $this->userFloodControl = $user_flood_control;
$this->userStorage = $user_storage; $this->userStorage = $user_storage;
$this->csrfToken = $csrf_token; $this->csrfToken = $csrf_token;
if (!$user_auth instanceof UserAuthenticationInterface) {
@trigger_error('The $user_auth parameter implementing UserAuthInterface is deprecated in drupal:10.3.0 and will be removed in drupal:12.0.0. Implement UserAuthenticationInterface instead. See https://www.drupal.org/node/3411040');
}
$this->userAuth = $user_auth; $this->userAuth = $user_auth;
$this->serializer = $serializer; $this->serializer = $serializer;
$this->serializerFormats = $serializer_formats; $this->serializerFormats = $serializer_formats;
@ -178,26 +181,42 @@ class UserAuthenticationController extends ControllerBase implements ContainerIn
$this->floodControl($request, $credentials['name']); $this->floodControl($request, $credentials['name']);
if ($this->userIsBlocked($credentials['name'])) { $account = FALSE;
throw new BadRequestHttpException('The user has not been activated or is blocked.');
if ($this->userAuth instanceof UserAuthenticationInterface) {
$account = $this->userAuth->lookupAccount($credentials['name']);
}
else {
$accounts = $this->userStorage->loadByProperties(['name' => $credentials['name']]);
if ($accounts) {
$account = reset($accounts);
}
} }
if ($uid = $this->userAuth->authenticate($credentials['name'], $credentials['pass'])) { if ($account) {
if ($account->isBlocked()) {
throw new BadRequestHttpException('The user has not been activated or is blocked.');
}
if ($this->userAuth instanceof UserAuthenticationInterface) {
$authenticated = $this->userAuth->authenticateAccount($account, $credentials['pass']) ? $account->id() : FALSE;
}
else {
$authenticated = $this->userAuth->authenticateAccount($credentials['name'], $credentials['pass']);
}
if ($authenticated) {
$this->userFloodControl->clear('user.http_login', $this->getLoginFloodIdentifier($request, $credentials['name'])); $this->userFloodControl->clear('user.http_login', $this->getLoginFloodIdentifier($request, $credentials['name']));
/** @var \Drupal\user\UserInterface $user */ $this->userLoginFinalize($account);
$user = $this->userStorage->load($uid);
$this->userLoginFinalize($user);
// Send basic metadata about the logged in user. // Send basic metadata about the logged in user.
$response_data = []; $response_data = [];
if ($user->get('uid')->access('view', $user)) { if ($account->get('uid')->access('view', $account)) {
$response_data['current_user']['uid'] = $user->id(); $response_data['current_user']['uid'] = $account->id();
} }
if ($user->get('roles')->access('view', $user)) { if ($account->get('roles')->access('view', $account)) {
$response_data['current_user']['roles'] = $user->getRoles(); $response_data['current_user']['roles'] = $account->getRoles();
} }
if ($user->get('name')->access('view', $user)) { if ($account->get('name')->access('view', $account)) {
$response_data['current_user']['name'] = $user->getAccountName(); $response_data['current_user']['name'] = $account->getAccountName();
} }
$response_data['csrf_token'] = $this->csrfToken->get('rest'); $response_data['csrf_token'] = $this->csrfToken->get('rest');
@ -209,6 +228,7 @@ class UserAuthenticationController extends ControllerBase implements ContainerIn
$encoded_response_data = $this->serializer->encode($response_data, $format); $encoded_response_data = $this->serializer->encode($response_data, $format);
return new Response($encoded_response_data); return new Response($encoded_response_data);
} }
}
$flood_config = $this->config('user.flood'); $flood_config = $this->config('user.flood');
if ($identifier = $this->getLoginFloodIdentifier($request, $credentials['name'])) { if ($identifier = $this->getLoginFloodIdentifier($request, $credentials['name'])) {
@ -250,10 +270,10 @@ class UserAuthenticationController extends ControllerBase implements ContainerIn
$users = $this->userStorage->loadByProperties(['mail' => trim($identifier)]); $users = $this->userStorage->loadByProperties(['mail' => trim($identifier)]);
} }
/** @var \Drupal\Core\Session\AccountInterface $account */ /** @var \Drupal\user\UserInterface $account */
$account = reset($users); $account = reset($users);
if ($account && $account->id()) { if ($account && $account->id()) {
if ($this->userIsBlocked($account->getAccountName())) { if ($account->isBlocked()) {
$this->logger->error('Unable to send password reset email for blocked or not yet activated user %identifier.', [ $this->logger->error('Unable to send password reset email for blocked or not yet activated user %identifier.', [
'%identifier' => $identifier, '%identifier' => $identifier,
]); ]);
@ -286,8 +306,13 @@ class UserAuthenticationController extends ControllerBase implements ContainerIn
* *
* @return bool * @return bool
* TRUE if the user is blocked, otherwise FALSE. * TRUE if the user is blocked, otherwise FALSE.
*
* @deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. There
* is no replacement.
* @see https://www.drupal.org/node/3425340
*/ */
protected function userIsBlocked($name) { protected function userIsBlocked($name) {
@trigger_error(__METHOD__ . ' is deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. There is no replacement. See https://www.drupal.org/node/3425340', E_USER_DEPRECATED);
return user_is_blocked($name); return user_is_blocked($name);
} }

View File

@ -7,6 +7,7 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\RendererInterface; use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Render\BareHtmlPageRendererInterface; use Drupal\Core\Render\BareHtmlPageRendererInterface;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\user\UserAuthenticationInterface;
use Drupal\user\UserAuthInterface; use Drupal\user\UserAuthInterface;
use Drupal\user\UserInterface; use Drupal\user\UserInterface;
use Drupal\user\UserStorageInterface; use Drupal\user\UserStorageInterface;
@ -37,7 +38,7 @@ class UserLoginForm extends FormBase {
/** /**
* The user authentication object. * The user authentication object.
* *
* @var \Drupal\user\UserAuthInterface * @var \Drupal\user\UserAuthInterface|\Drupal\user\UserAuthenticationInterface
*/ */
protected $userAuth; protected $userAuth;
@ -62,16 +63,19 @@ class UserLoginForm extends FormBase {
* The user flood control service. * The user flood control service.
* @param \Drupal\user\UserStorageInterface $user_storage * @param \Drupal\user\UserStorageInterface $user_storage
* The user storage. * The user storage.
* @param \Drupal\user\UserAuthInterface $user_auth * @param \Drupal\user\UserAuthInterface|\Drupal\user\UserAuthenticationInterface $user_auth
* The user authentication object. * The user authentication object.
* @param \Drupal\Core\Render\RendererInterface $renderer * @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer. * The renderer.
* @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_renderer * @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_renderer
* The renderer. * The renderer.
*/ */
public function __construct(UserFloodControlInterface $user_flood_control, UserStorageInterface $user_storage, UserAuthInterface $user_auth, RendererInterface $renderer, BareHtmlPageRendererInterface $bare_html_renderer) { public function __construct(UserFloodControlInterface $user_flood_control, UserStorageInterface $user_storage, UserAuthInterface|UserAuthenticationInterface $user_auth, RendererInterface $renderer, BareHtmlPageRendererInterface $bare_html_renderer) {
$this->userFloodControl = $user_flood_control; $this->userFloodControl = $user_flood_control;
$this->userStorage = $user_storage; $this->userStorage = $user_storage;
if (!$user_auth instanceof UserAuthenticationInterface) {
@trigger_error('The $user_auth parameter not implementing UserAuthenticationInterface is deprecated in drupal:10.3.0 and will be removed in drupal:12.0.0. See https://www.drupal.org/node/3411040');
}
$this->userAuth = $user_auth; $this->userAuth = $user_auth;
$this->renderer = $renderer; $this->renderer = $renderer;
$this->bareHtmlPageRenderer = $bare_html_renderer; $this->bareHtmlPageRenderer = $bare_html_renderer;
@ -132,7 +136,6 @@ class UserLoginForm extends FormBase {
$form['actions'] = ['#type' => 'actions']; $form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = ['#type' => 'submit', '#value' => $this->t('Log in')]; $form['actions']['submit'] = ['#type' => 'submit', '#value' => $this->t('Log in')];
$form['#validate'][] = '::validateName';
$form['#validate'][] = '::validateAuthentication'; $form['#validate'][] = '::validateAuthentication';
$form['#validate'][] = '::validateFinal'; $form['#validate'][] = '::validateFinal';
@ -145,7 +148,6 @@ class UserLoginForm extends FormBase {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function submitForm(array &$form, FormStateInterface $form_state) { public function submitForm(array &$form, FormStateInterface $form_state) {
if (empty($uid = $form_state->get('uid'))) { if (empty($uid = $form_state->get('uid'))) {
return; return;
} }
@ -167,8 +169,12 @@ class UserLoginForm extends FormBase {
/** /**
* Sets an error if supplied username has been blocked. * Sets an error if supplied username has been blocked.
*
* @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. There is no replacement.
* @see https://www.drupal.org/node/3410706
*/ */
public function validateName(array &$form, FormStateInterface $form_state) { public function validateName(array &$form, FormStateInterface $form_state) {
@trigger_error(__METHOD__ . ' is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3410706', E_USER_DEPRECATED);
if (!$form_state->isValueEmpty('name') && user_is_blocked($form_state->getValue('name'))) { if (!$form_state->isValueEmpty('name') && user_is_blocked($form_state->getValue('name'))) {
// Blocked in user administration. // Blocked in user administration.
$form_state->setErrorByName('name', $this->t('The username %name has not been activated or is blocked.', ['%name' => $form_state->getValue('name')])); $form_state->setErrorByName('name', $this->t('The username %name has not been activated or is blocked.', ['%name' => $form_state->getValue('name')]));
@ -183,6 +189,7 @@ class UserLoginForm extends FormBase {
public function validateAuthentication(array &$form, FormStateInterface $form_state) { public function validateAuthentication(array &$form, FormStateInterface $form_state) {
$password = trim($form_state->getValue('pass')); $password = trim($form_state->getValue('pass'));
$flood_config = $this->config('user.flood'); $flood_config = $this->config('user.flood');
$account = FALSE;
if (!$form_state->isValueEmpty('name') && strlen($password) > 0) { if (!$form_state->isValueEmpty('name') && strlen($password) > 0) {
// Do not allow any login from the current user's IP if the limit has been // Do not allow any login from the current user's IP if the limit has been
// reached. Default is 50 failed attempts allowed in one hour. This is // reached. Default is 50 failed attempts allowed in one hour. This is
@ -193,9 +200,17 @@ class UserLoginForm extends FormBase {
$form_state->set('flood_control_triggered', 'ip'); $form_state->set('flood_control_triggered', 'ip');
return; return;
} }
$accounts = $this->userStorage->loadByProperties(['name' => $form_state->getValue('name'), 'status' => 1]); if ($this->userAuth instanceof UserAuthenticationInterface) {
$account = $this->userAuth->lookupAccount($form_state->getValue('name'));
}
else {
$accounts = $this->userStorage->loadByProperties(['name' => $form_state->getValue('name')]);
$account = reset($accounts); $account = reset($accounts);
if ($account) { }
if ($account && $account->isBlocked()) {
$form_state->setErrorByName('name', $this->t('The username %name has not been activated or is blocked.', ['%name' => $form_state->getValue('name')]));
}
elseif ($account && $account->isActive()) {
if ($flood_config->get('uid_only')) { if ($flood_config->get('uid_only')) {
// Register flood events based on the uid only, so they apply for any // Register flood events based on the uid only, so they apply for any
// IP address. This is the most secure option. // IP address. This is the most secure option.
@ -226,13 +241,18 @@ class UserLoginForm extends FormBase {
else { else {
$form_state->set('flood_control_skip_clear', 'user'); $form_state->set('flood_control_skip_clear', 'user');
} }
}
// We are not limited by flood control, so try to authenticate. // We are not limited by flood control, so try to authenticate.
// Store $uid in form state as a flag for self::validateFinal(). // Store the user ID in form state as a flag for self::validateFinal().
if ($this->userAuth instanceof UserAuthenticationInterface) {
$form_state->set('uid', $this->userAuth->authenticateAccount($account, $password) ? $account->id() : FALSE);
}
else {
$uid = $this->userAuth->authenticate($form_state->getValue('name'), $password); $uid = $this->userAuth->authenticate($form_state->getValue('name'), $password);
$form_state->set('uid', $uid); $form_state->set('uid', $uid);
} }
} }
}
}
/** /**
* Checks if user was not authenticated, or if too many logins were attempted. * Checks if user was not authenticated, or if too many logins were attempted.

View File

@ -8,7 +8,7 @@ use Drupal\Core\Password\PasswordInterface;
/** /**
* Validates user authentication credentials. * Validates user authentication credentials.
*/ */
class UserAuth implements UserAuthInterface { class UserAuth implements UserAuthInterface, UserAuthenticationInterface {
/** /**
* The entity type manager. * The entity type manager.
@ -41,26 +41,48 @@ class UserAuth implements UserAuthInterface {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function authenticate($username, #[\SensitiveParameter] $password) { public function authenticate($username, #[\SensitiveParameter] $password) {
@trigger_error(__METHOD__ . ' is deprecated in drupal:10.3.0 and will be removed from drupal 12.0.0. Implement \Drupal\user\UserAuthenticationInterface instead. See https://www.drupal.org/node/3411040');
$uid = FALSE; $uid = FALSE;
if (!empty($username) && strlen($password) > 0) { if (!empty($username) && strlen($password) > 0) {
$account_search = $this->entityTypeManager->getStorage('user')->loadByProperties(['name' => $username]); $account_search = $this->entityTypeManager->getStorage('user')->loadByProperties(['name' => $username]);
if ($account = reset($account_search)) { if ($account = reset($account_search)) {
if ($this->passwordChecker->check($password, $account->getPassword())) { if ($this->authenticateAccount($account, $password)) {
// Successful authentication.
$uid = $account->id(); $uid = $account->id();
}
}
}
return $uid;
}
/**
* {@inheritdoc}
*/
public function lookupAccount($identifier): UserInterface|false {
if (!empty($identifier)) {
$account_search = $this->entityTypeManager->getStorage('user')->loadByProperties(['name' => $identifier]);
if ($account = reset($account_search)) {
return $account;
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function authenticateAccount(UserInterface $account, #[\SensitiveParameter] string $password): bool {
if ($this->passwordChecker->check($password, $account->getPassword())) {
// Update user to new password scheme if needed. // Update user to new password scheme if needed.
if ($this->passwordChecker->needsRehash($account->getPassword())) { if ($this->passwordChecker->needsRehash($account->getPassword())) {
$account->setPassword($password); $account->setPassword($password);
$account->save(); $account->save();
} }
return TRUE;
} }
} return FALSE;
}
return $uid;
} }
} }

View File

@ -0,0 +1,37 @@
<?php
namespace Drupal\user;
/**
* An interface for validating user authentication credentials.
*/
interface UserAuthenticationInterface {
/**
* Validates user authentication credentials.
*
* @param string $identifier
* The user identifier to authenticate. Usually the username.
*
* @return Drupal\User\UserInterface|false
* The user account on success, or FALSE on failure to authenticate.
*/
public function lookupAccount($identifier): UserInterface|false;
/**
* Validates user authentication credentials for an account.
*
* This can be used where the account has already been located using the login
* credentials.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account to authenticate.
* @param string $password
* A plain-text password, such as trimmed text from form values.
*
* @return bool
* TRUE on success, FALSE on failure.
*/
public function authenticateAccount(UserInterface $account, #[\SensitiveParameter] string $password): bool;
}

View File

@ -283,8 +283,13 @@ function _user_role_permissions_update($roles) {
* *
* @return bool * @return bool
* TRUE if the user is blocked, FALSE otherwise. * TRUE if the user is blocked, FALSE otherwise.
*
* @deprecated in drupal:11.0.0 and is removed from drupal:12.0.0. Use
* Drupal\user\UserInterface::isBlocked() instead.
* @see https://www.drupal.org/node/3411040
*/ */
function user_is_blocked($name) { function user_is_blocked($name) {
@trigger_error('user_is_blocked() is deprecated in drupal:11.0.0 and is removed from drupal:12.0.0. Use \Drupal\user\UserInterface::isBlocked() instead. See https://www.drupal.org/node/3411040', E_USER_DEPRECATED);
return (bool) \Drupal::entityQuery('user') return (bool) \Drupal::entityQuery('user')
->accessCheck(FALSE) ->accessCheck(FALSE)
->condition('name', $name) ->condition('name', $name)

View File

@ -197,11 +197,9 @@ class StandardPerformanceTest extends PerformanceTestBase {
$expected_queries = [ $expected_queries = [
'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "system.maintenance_mode" ) AND "collection" = "state"', 'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "system.maintenance_mode" ) AND "collection" = "state"',
'SELECT "base_table"."uid" AS "uid", "base_table"."uid" AS "base_table_uid" FROM "users" "base_table" INNER JOIN "users_field_data" "users_field_data" ON "users_field_data"."uid" = "base_table"."uid" WHERE ("users_field_data"."name" LIKE "ACCOUNT_NAME" ESCAPE ' . "'\\\\'" . ') AND ("users_field_data"."status" = 0)',
'SELECT COUNT(*) AS "expression" FROM (SELECT 1 AS "expression" FROM "flood" "f" WHERE ("event" = "user.failed_login_ip") AND ("identifier" = "CLIENT_IP") AND ("timestamp" > "TIMESTAMP")) "subquery"', 'SELECT COUNT(*) AS "expression" FROM (SELECT 1 AS "expression" FROM "flood" "f" WHERE ("event" = "user.failed_login_ip") AND ("identifier" = "CLIENT_IP") AND ("timestamp" > "TIMESTAMP")) "subquery"',
'SELECT "base_table"."uid" AS "uid", "base_table"."uid" AS "base_table_uid" FROM "users" "base_table" INNER JOIN "users_field_data" "users_field_data" ON "users_field_data"."uid" = "base_table"."uid" WHERE ("users_field_data"."name" IN ("ACCOUNT_NAME")) AND ("users_field_data"."status" IN (1)) AND ("users_field_data"."default_langcode" IN (1))',
'SELECT COUNT(*) AS "expression" FROM (SELECT 1 AS "expression" FROM "flood" "f" WHERE ("event" = "user.failed_login_user") AND ("identifier" = "CLIENT_IP") AND ("timestamp" > "TIMESTAMP")) "subquery"',
'SELECT "base_table"."uid" AS "uid", "base_table"."uid" AS "base_table_uid" FROM "users" "base_table" INNER JOIN "users_field_data" "users_field_data" ON "users_field_data"."uid" = "base_table"."uid" WHERE ("users_field_data"."name" IN ("ACCOUNT_NAME")) AND ("users_field_data"."default_langcode" IN (1))', 'SELECT "base_table"."uid" AS "uid", "base_table"."uid" AS "base_table_uid" FROM "users" "base_table" INNER JOIN "users_field_data" "users_field_data" ON "users_field_data"."uid" = "base_table"."uid" WHERE ("users_field_data"."name" IN ("ACCOUNT_NAME")) AND ("users_field_data"."default_langcode" IN (1))',
'SELECT COUNT(*) AS "expression" FROM (SELECT 1 AS "expression" FROM "flood" "f" WHERE ("event" = "user.failed_login_user") AND ("identifier" = "CLIENT_IP") AND ("timestamp" > "TIMESTAMP")) "subquery"',
'INSERT INTO "watchdog" ("uid", "type", "message", "variables", "severity", "link", "location", "referer", "hostname", "timestamp") VALUES ("2", "user", "Session opened for %name.", "WATCHDOG_DATA", 6, "", "LOCATION", "REFERER", "CLIENT_IP", "TIMESTAMP")', 'INSERT INTO "watchdog" ("uid", "type", "message", "variables", "severity", "link", "location", "referer", "hostname", "timestamp") VALUES ("2", "user", "Session opened for %name.", "WATCHDOG_DATA", 6, "", "LOCATION", "REFERER", "CLIENT_IP", "TIMESTAMP")',
'UPDATE "users_field_data" SET "login"="TIMESTAMP" WHERE "uid" = "2"', 'UPDATE "users_field_data" SET "login"="TIMESTAMP" WHERE "uid" = "2"',
'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "drupal.test_wait_terminate" ) AND "collection" = "state"', 'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "drupal.test_wait_terminate" ) AND "collection" = "state"',
@ -225,7 +223,7 @@ class StandardPerformanceTest extends PerformanceTestBase {
]; ];
$recorded_queries = $performance_data->getQueries(); $recorded_queries = $performance_data->getQueries();
$this->assertSame($expected_queries, $recorded_queries); $this->assertSame($expected_queries, $recorded_queries);
$this->assertSame(26, $performance_data->getQueryCount()); $this->assertSame(24, $performance_data->getQueryCount());
$this->assertSame(63, $performance_data->getCacheGetCount()); $this->assertSame(63, $performance_data->getCacheGetCount());
$this->assertSame(1, $performance_data->getCacheSetCount()); $this->assertSame(1, $performance_data->getCacheSetCount());
$this->assertSame(1, $performance_data->getCacheDeleteCount()); $this->assertSame(1, $performance_data->getCacheDeleteCount());
@ -266,15 +264,13 @@ class StandardPerformanceTest extends PerformanceTestBase {
'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "twig_extension_hash_prefix" ) AND "collection" = "state"', 'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "twig_extension_hash_prefix" ) AND "collection" = "state"',
'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "theme:stark" ) AND "collection" = "config.entity.key_store.block"', 'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "theme:stark" ) AND "collection" = "config.entity.key_store.block"',
'SELECT "config"."name" AS "name" FROM "config" "config" WHERE ("collection" = "") AND ("name" LIKE "search.page.%" ESCAPE ' . "'\\\\'" . ') ORDER BY "collection" ASC, "name" ASC', 'SELECT "config"."name" AS "name" FROM "config" "config" WHERE ("collection" = "") AND ("name" LIKE "search.page.%" ESCAPE ' . "'\\\\'" . ') ORDER BY "collection" ASC, "name" ASC',
'SELECT "base_table"."uid" AS "uid", "base_table"."uid" AS "base_table_uid" FROM "users" "base_table" INNER JOIN "users_field_data" "users_field_data" ON "users_field_data"."uid" = "base_table"."uid" WHERE ("users_field_data"."name" LIKE "ACCOUNT_NAME" ESCAPE ' . "'\\\\'" . ') AND ("users_field_data"."status" = 0)',
'SELECT COUNT(*) AS "expression" FROM (SELECT 1 AS "expression" FROM "flood" "f" WHERE ("event" = "user.failed_login_ip") AND ("identifier" = "CLIENT_IP") AND ("timestamp" > "TIMESTAMP")) "subquery"', 'SELECT COUNT(*) AS "expression" FROM (SELECT 1 AS "expression" FROM "flood" "f" WHERE ("event" = "user.failed_login_ip") AND ("identifier" = "CLIENT_IP") AND ("timestamp" > "TIMESTAMP")) "subquery"',
'SELECT "base_table"."uid" AS "uid", "base_table"."uid" AS "base_table_uid" FROM "users" "base_table" INNER JOIN "users_field_data" "users_field_data" ON "users_field_data"."uid" = "base_table"."uid" WHERE ("users_field_data"."name" IN ("ACCOUNT_NAME")) AND ("users_field_data"."status" IN (1)) AND ("users_field_data"."default_langcode" IN (1))', 'SELECT "base_table"."uid" AS "uid", "base_table"."uid" AS "base_table_uid" FROM "users" "base_table" INNER JOIN "users_field_data" "users_field_data" ON "users_field_data"."uid" = "base_table"."uid" WHERE ("users_field_data"."name" IN ("ACCOUNT_NAME")) AND ("users_field_data"."default_langcode" IN (1))',
'SELECT "base"."uid" AS "uid", "base"."uuid" AS "uuid", "base"."langcode" AS "langcode" FROM "users" "base" WHERE "base"."uid" IN (2)', 'SELECT "base"."uid" AS "uid", "base"."uuid" AS "uuid", "base"."langcode" AS "langcode" FROM "users" "base" WHERE "base"."uid" IN (2)',
'SELECT "data".* FROM "users_field_data" "data" WHERE "data"."uid" IN (2) ORDER BY "data"."uid" ASC', 'SELECT "data".* FROM "users_field_data" "data" WHERE "data"."uid" IN (2) ORDER BY "data"."uid" ASC',
'SELECT "t".* FROM "user__roles" "t" WHERE ("entity_id" IN (2)) AND ("deleted" = 0) AND ("langcode" IN ("en", "und", "zxx")) ORDER BY "delta" ASC', 'SELECT "t".* FROM "user__roles" "t" WHERE ("entity_id" IN (2)) AND ("deleted" = 0) AND ("langcode" IN ("en", "und", "zxx")) ORDER BY "delta" ASC',
'SELECT "t".* FROM "user__user_picture" "t" WHERE ("entity_id" IN (2)) AND ("deleted" = 0) AND ("langcode" IN ("en", "und", "zxx")) ORDER BY "delta" ASC', 'SELECT "t".* FROM "user__user_picture" "t" WHERE ("entity_id" IN (2)) AND ("deleted" = 0) AND ("langcode" IN ("en", "und", "zxx")) ORDER BY "delta" ASC',
'SELECT COUNT(*) AS "expression" FROM (SELECT 1 AS "expression" FROM "flood" "f" WHERE ("event" = "user.failed_login_user") AND ("identifier" = "CLIENT_IP") AND ("timestamp" > "TIMESTAMP")) "subquery"', 'SELECT COUNT(*) AS "expression" FROM (SELECT 1 AS "expression" FROM "flood" "f" WHERE ("event" = "user.failed_login_user") AND ("identifier" = "CLIENT_IP") AND ("timestamp" > "TIMESTAMP")) "subquery"',
'SELECT "base_table"."uid" AS "uid", "base_table"."uid" AS "base_table_uid" FROM "users" "base_table" INNER JOIN "users_field_data" "users_field_data" ON "users_field_data"."uid" = "base_table"."uid" WHERE ("users_field_data"."name" IN ("ACCOUNT_NAME")) AND ("users_field_data"."default_langcode" IN (1))',
'INSERT INTO "watchdog" ("uid", "type", "message", "variables", "severity", "link", "location", "referer", "hostname", "timestamp") VALUES ("2", "user", "Session opened for %name.", "WATCHDOG_DATA", 6, "", "LOCATION", "REFERER", "CLIENT_IP", "TIMESTAMP")', 'INSERT INTO "watchdog" ("uid", "type", "message", "variables", "severity", "link", "location", "referer", "hostname", "timestamp") VALUES ("2", "user", "Session opened for %name.", "WATCHDOG_DATA", 6, "", "LOCATION", "REFERER", "CLIENT_IP", "TIMESTAMP")',
'UPDATE "users_field_data" SET "login"="TIMESTAMP" WHERE "uid" = "2"', 'UPDATE "users_field_data" SET "login"="TIMESTAMP" WHERE "uid" = "2"',
'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "drupal.test_wait_terminate" ) AND "collection" = "state"', 'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "drupal.test_wait_terminate" ) AND "collection" = "state"',
@ -294,7 +290,7 @@ class StandardPerformanceTest extends PerformanceTestBase {
]; ];
$recorded_queries = $performance_data->getQueries(); $recorded_queries = $performance_data->getQueries();
$this->assertSame($expected_queries, $recorded_queries); $this->assertSame($expected_queries, $recorded_queries);
$this->assertSame(31, $performance_data->getQueryCount()); $this->assertSame(29, $performance_data->getQueryCount());
$this->assertSame(106, $performance_data->getCacheGetCount()); $this->assertSame(106, $performance_data->getCacheGetCount());
$this->assertSame(1, $performance_data->getCacheSetCount()); $this->assertSame(1, $performance_data->getCacheSetCount());
$this->assertSame(1, $performance_data->getCacheDeleteCount()); $this->assertSame(1, $performance_data->getCacheDeleteCount());