diff --git a/core/modules/user/src/Form/UserLoginForm.php b/core/modules/user/src/Form/UserLoginForm.php index dc0ea6a708f4f6bb0c327623f9297ecec74c27f6..54a3d4280fa0bc38ccaf30b540c4911151767542 100644 --- a/core/modules/user/src/Form/UserLoginForm.php +++ b/core/modules/user/src/Form/UserLoginForm.php @@ -132,6 +132,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['actions'] = ['#type' => 'actions']; $form['actions']['submit'] = ['#type' => 'submit', '#value' => $this->t('Log in')]; + $form['#validate'][] = '::validateName'; $form['#validate'][] = '::validateAuthentication'; $form['#validate'][] = '::validateFinal'; @@ -144,8 +145,8 @@ public function buildForm(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - $uid = $form_state->get('uid'); - if (!$uid) { + + if (empty($uid = $form_state->get('uid'))) { return; } $account = $this->userStorage->load($uid); @@ -166,12 +167,8 @@ public function submitForm(array &$form, FormStateInterface $form_state) { /** * 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) { - @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'))) { // 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')])); @@ -186,7 +183,6 @@ public function validateName(array &$form, FormStateInterface $form_state) { public function validateAuthentication(array &$form, FormStateInterface $form_state) { $password = trim($form_state->getValue('pass')); $flood_config = $this->config('user.flood'); - $account = FALSE; if (!$form_state->isValueEmpty('name') && strlen($password) > 0) { // 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 @@ -197,12 +193,9 @@ public function validateAuthentication(array &$form, FormStateInterface $form_st $form_state->set('flood_control_triggered', 'ip'); return; } - $accounts = $this->userStorage->loadByProperties(['name' => $form_state->getValue('name')]); + $accounts = $this->userStorage->loadByProperties(['name' => $form_state->getValue('name'), 'status' => 1]); $account = reset($accounts); - 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 ($account) { if ($flood_config->get('uid_only')) { // Register flood events based on the uid only, so they apply for any // IP address. This is the most secure option. @@ -233,12 +226,11 @@ public function validateAuthentication(array &$form, FormStateInterface $form_st else { $form_state->set('flood_control_skip_clear', 'user'); } - // We are not limited by flood control, so try to authenticate. - // Store the user ID in form state as a flag for self::validateFinal(). - if ($this->userAuth->authenticateAccount($account, $password)) { - $form_state->set('uid', $account->id()); - } } + // We are not limited by flood control, so try to authenticate. + // Store $uid in form state as a flag for self::validateFinal(). + $uid = $this->userAuth->authenticate($form_state->getValue('name'), $password); + $form_state->set('uid', $uid); } } diff --git a/core/modules/user/src/UserAuth.php b/core/modules/user/src/UserAuth.php index f1e344dc95c313b3bc45d733f8a2f9212bf96532..03ad41af3c03b2b2ea1dae8ff37a92a3135c6844 100644 --- a/core/modules/user/src/UserAuth.php +++ b/core/modules/user/src/UserAuth.php @@ -47,27 +47,20 @@ public function authenticate($username, #[\SensitiveParameter] $password) { $account_search = $this->entityTypeManager->getStorage('user')->loadByProperties(['name' => $username]); if ($account = reset($account_search)) { - if ($this->authenticateAccount($account, $password)) { + if ($this->passwordChecker->check($password, $account->getPassword())) { + // Successful authentication. $uid = $account->id(); + + // Update user to new password scheme if needed. + if ($this->passwordChecker->needsRehash($account->getPassword())) { + $account->setPassword($password); + $account->save(); + } } } } - return $uid; - } - /** - * {@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. - if ($this->passwordChecker->needsRehash($account->getPassword())) { - $account->setPassword($password); - $account->save(); - } - return TRUE; - } - return FALSE; + return $uid; } } diff --git a/core/modules/user/src/UserAuthInterface.php b/core/modules/user/src/UserAuthInterface.php index 1bf8ef1e608a268ac6fee2875ab72740366b43bf..7c3bfcfad413c7eeb39e05097a2b660105c28256 100644 --- a/core/modules/user/src/UserAuthInterface.php +++ b/core/modules/user/src/UserAuthInterface.php @@ -20,20 +20,4 @@ interface UserAuthInterface { */ public function authenticate($username, #[\SensitiveParameter] $password); - /** - * 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; - } diff --git a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php index 3463dd3f05418e5b9037303ff5f94f8d83fcc759..af52184ecc38ef5a1679eb3aee4e3700c99aa9a8 100644 --- a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php +++ b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php @@ -194,9 +194,11 @@ public function testLogin(): void { $expected_queries = [ '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 "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"."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))', '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"', 'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "drupal.test_wait_terminate" ) AND "collection" = "state"', @@ -219,7 +221,7 @@ public function testLogin(): void { ]; $recorded_queries = $performance_data->getQueries(); $this->assertSame($expected_queries, $recorded_queries); - $this->assertSame(23, $performance_data->getQueryCount()); + $this->assertSame(25, $performance_data->getQueryCount()); $this->assertSame(64, $performance_data->getCacheGetCount()); $this->assertSame(1, $performance_data->getCacheSetCount()); $this->assertSame(1, $performance_data->getCacheDeleteCount()); @@ -259,13 +261,15 @@ public function testLoginBlock(): void { '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 "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 "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"."status" IN (1)) 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 "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__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 "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")', '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"', @@ -284,7 +288,7 @@ public function testLoginBlock(): void { ]; $recorded_queries = $performance_data->getQueries(); $this->assertSame($expected_queries, $recorded_queries); - $this->assertSame(27, $performance_data->getQueryCount()); + $this->assertSame(29, $performance_data->getQueryCount()); $this->assertSame(108, $performance_data->getCacheGetCount()); $this->assertSame(1, $performance_data->getCacheSetCount()); $this->assertSame(1, $performance_data->getCacheDeleteCount());