From 517e75283eae1d8a083d17a5613ff1f251fe51ce Mon Sep 17 00:00:00 2001
From: Dave Long <dave@longwaveconsulting.com>
Date: Wed, 8 May 2024 10:22:38 +0100
Subject: [PATCH] Issue #3444978 by catch, Berdir: UserAuth BC layer is not
 working for modules that use it to provide email based logins

(cherry picked from commit 26208e91c01e13f15b7103d9e0071e03cd5d5b0c)
---
 core/modules/user/src/Form/UserLoginForm.php |  8 +-
 core/modules/user/src/UserAuth.php           | 42 +++-------
 core/modules/user/src/UserAuthentication.php | 88 ++++++++++++++++++++
 core/modules/user/user.services.yml          |  2 +-
 4 files changed, 104 insertions(+), 36 deletions(-)
 create mode 100644 core/modules/user/src/UserAuthentication.php

diff --git a/core/modules/user/src/Form/UserLoginForm.php b/core/modules/user/src/Form/UserLoginForm.php
index e3df158e0f51..8fe696d15a53 100644
--- a/core/modules/user/src/Form/UserLoginForm.php
+++ b/core/modules/user/src/Form/UserLoginForm.php
@@ -246,10 +246,10 @@ public function validateAuthentication(array &$form, FormStateInterface $form_st
         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);
-          $form_state->set('uid', $uid);
-        }
+      }
+      elseif (!$this->userAuth instanceof UserAuthenticationInterface) {
+        $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 b4f0c0c356dc..71c7077da6b2 100644
--- a/core/modules/user/src/UserAuth.php
+++ b/core/modules/user/src/UserAuth.php
@@ -8,7 +8,7 @@
 /**
  * Validates user authentication credentials.
  */
-class UserAuth implements UserAuthInterface, UserAuthenticationInterface {
+class UserAuth implements UserAuthInterface {
 
   /**
    * The entity type manager.
@@ -33,6 +33,7 @@ class UserAuth implements UserAuthInterface, UserAuthenticationInterface {
    *   The password service.
    */
   public function __construct(EntityTypeManagerInterface $entity_type_manager, PasswordInterface $password_checker) {
+    @trigger_error(__CLASS__ . ' 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');
     $this->entityTypeManager = $entity_type_manager;
     $this->passwordChecker = $password_checker;
   }
@@ -48,41 +49,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();
-        }
-      }
-    }
-    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;
+          // Update user to new password scheme if needed.
+          if ($this->passwordChecker->needsRehash($account->getPassword())) {
+            $account->setPassword($password);
+            $account->save();
+          }
+        }
       }
     }
-    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.
-      if ($this->passwordChecker->needsRehash($account->getPassword())) {
-        $account->setPassword($password);
-        $account->save();
-      }
-      return TRUE;
-    }
-    return FALSE;
+    return $uid;
   }
 
 }
diff --git a/core/modules/user/src/UserAuthentication.php b/core/modules/user/src/UserAuthentication.php
new file mode 100644
index 000000000000..a7168442a7d1
--- /dev/null
+++ b/core/modules/user/src/UserAuthentication.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Drupal\user;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Password\PasswordInterface;
+
+/**
+ * Validates user authentication credentials.
+ */
+class UserAuthentication implements UserAuthInterface, UserAuthenticationInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The password hashing service.
+   *
+   * @var \Drupal\Core\Password\PasswordInterface
+   */
+  protected $passwordChecker;
+
+  /**
+   * Constructs a UserAuth object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Password\PasswordInterface $password_checker
+   *   The password service.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, PasswordInterface $password_checker) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->passwordChecker = $password_checker;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  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;
+
+    if (!empty($username) && strlen($password) > 0) {
+      $account_search = $this->entityTypeManager->getStorage('user')->loadByProperties(['name' => $username]);
+
+      if ($account = reset($account_search)) {
+        if ($this->authenticateAccount($account, $password)) {
+          $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.
+      if ($this->passwordChecker->needsRehash($account->getPassword())) {
+        $account->setPassword($password);
+        $account->save();
+      }
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+}
diff --git a/core/modules/user/user.services.yml b/core/modules/user/user.services.yml
index e2ec9cc3a067..ec34738d992e 100644
--- a/core/modules/user/user.services.yml
+++ b/core/modules/user/user.services.yml
@@ -43,7 +43,7 @@ services:
     tags:
       - { name: theme_negotiator, priority: -40 }
   user.auth:
-    class: Drupal\user\UserAuth
+    class: Drupal\user\UserAuthentication
     arguments: ['@entity_type.manager', '@password']
   Drupal\user\UserAuthInterface: '@user.auth'
   user.permissions:
-- 
GitLab