From 5536284905b199bd84d92dc615f2d3ac7544cbbb Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Sat, 18 Jan 2025 16:28:23 +0100 Subject: [PATCH] fix: Move login via email logic to local backend Backends can decide which names they accept for login, e.g. with user_ldap you can configure arbitrary login fields. This was a hacky approach to allow login via email, so instead this is now only handled by the local user backend. This also fixes some other related problems: Other logic relys on `backend::get()` which was not handling email, so e.g. password policy could not block users logged in via email if they use out-dated passwords. Similar for other integrations, as the user backend was not consistent with what is a login name and what not. Signed-off-by: Ferdinand Thiessen --- lib/composer/composer/autoload_classmap.php | 1 - lib/composer/composer/autoload_static.php | 1 - lib/private/Authentication/Login/Chain.php | 6 - .../Login/EmailLoginCommand.php | 53 ---- lib/private/User/Database.php | 258 ++++++++++-------- .../Login/EmailLoginCommandTest.php | 148 ---------- 6 files changed, 137 insertions(+), 330 deletions(-) delete mode 100644 lib/private/Authentication/Login/EmailLoginCommand.php delete mode 100644 tests/lib/Authentication/Login/EmailLoginCommandTest.php diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index d20e3f9f50130..db0d7c3e053bc 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1076,7 +1076,6 @@ 'OC\\Authentication\\Login\\ClearLostPasswordTokensCommand' => $baseDir . '/lib/private/Authentication/Login/ClearLostPasswordTokensCommand.php', 'OC\\Authentication\\Login\\CompleteLoginCommand' => $baseDir . '/lib/private/Authentication/Login/CompleteLoginCommand.php', 'OC\\Authentication\\Login\\CreateSessionTokenCommand' => $baseDir . '/lib/private/Authentication/Login/CreateSessionTokenCommand.php', - 'OC\\Authentication\\Login\\EmailLoginCommand' => $baseDir . '/lib/private/Authentication/Login/EmailLoginCommand.php', 'OC\\Authentication\\Login\\FinishRememberedLoginCommand' => $baseDir . '/lib/private/Authentication/Login/FinishRememberedLoginCommand.php', 'OC\\Authentication\\Login\\LoggedInCheckCommand' => $baseDir . '/lib/private/Authentication/Login/LoggedInCheckCommand.php', 'OC\\Authentication\\Login\\LoginData' => $baseDir . '/lib/private/Authentication/Login/LoginData.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index ead08210def82..d965f7efe850b 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1125,7 +1125,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Authentication\\Login\\ClearLostPasswordTokensCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/ClearLostPasswordTokensCommand.php', 'OC\\Authentication\\Login\\CompleteLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/CompleteLoginCommand.php', 'OC\\Authentication\\Login\\CreateSessionTokenCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/CreateSessionTokenCommand.php', - 'OC\\Authentication\\Login\\EmailLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/EmailLoginCommand.php', 'OC\\Authentication\\Login\\FinishRememberedLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/FinishRememberedLoginCommand.php', 'OC\\Authentication\\Login\\LoggedInCheckCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/LoggedInCheckCommand.php', 'OC\\Authentication\\Login\\LoginData' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/LoginData.php', diff --git a/lib/private/Authentication/Login/Chain.php b/lib/private/Authentication/Login/Chain.php index 3cba396afdd3e..0962dc71a8f0c 100644 --- a/lib/private/Authentication/Login/Chain.php +++ b/lib/private/Authentication/Login/Chain.php @@ -18,9 +18,6 @@ class Chain { /** @var UidLoginCommand */ private $uidLoginCommand; - /** @var EmailLoginCommand */ - private $emailLoginCommand; - /** @var LoggedInCheckCommand */ private $loggedInCheckCommand; @@ -48,7 +45,6 @@ class Chain { public function __construct(PreLoginHookCommand $preLoginHookCommand, UserDisabledCheckCommand $userDisabledCheckCommand, UidLoginCommand $uidLoginCommand, - EmailLoginCommand $emailLoginCommand, LoggedInCheckCommand $loggedInCheckCommand, CompleteLoginCommand $completeLoginCommand, CreateSessionTokenCommand $createSessionTokenCommand, @@ -61,7 +57,6 @@ public function __construct(PreLoginHookCommand $preLoginHookCommand, $this->preLoginHookCommand = $preLoginHookCommand; $this->userDisabledCheckCommand = $userDisabledCheckCommand; $this->uidLoginCommand = $uidLoginCommand; - $this->emailLoginCommand = $emailLoginCommand; $this->loggedInCheckCommand = $loggedInCheckCommand; $this->completeLoginCommand = $completeLoginCommand; $this->createSessionTokenCommand = $createSessionTokenCommand; @@ -77,7 +72,6 @@ public function process(LoginData $loginData): LoginResult { $chain ->setNext($this->userDisabledCheckCommand) ->setNext($this->uidLoginCommand) - ->setNext($this->emailLoginCommand) ->setNext($this->loggedInCheckCommand) ->setNext($this->completeLoginCommand) ->setNext($this->createSessionTokenCommand) diff --git a/lib/private/Authentication/Login/EmailLoginCommand.php b/lib/private/Authentication/Login/EmailLoginCommand.php deleted file mode 100644 index 96cb39277fd18..0000000000000 --- a/lib/private/Authentication/Login/EmailLoginCommand.php +++ /dev/null @@ -1,53 +0,0 @@ -userManager = $userManager; - } - - public function process(LoginData $loginData): LoginResult { - if ($loginData->getUser() === false) { - if (!filter_var($loginData->getUsername(), FILTER_VALIDATE_EMAIL)) { - return $this->processNextOrFinishSuccessfully($loginData); - } - - $users = $this->userManager->getByEmail($loginData->getUsername()); - // we only allow login by email if unique - if (count($users) === 1) { - // FIXME: This is a workaround to still stick to configured LDAP login filters - // this can be removed once the email login is properly implemented in the local user backend - // as described in https://github.com/nextcloud/server/issues/5221 - if ($users[0]->getBackendClassName() === 'LDAP') { - return $this->processNextOrFinishSuccessfully($loginData); - } - - $username = $users[0]->getUID(); - if ($username !== $loginData->getUsername()) { - $user = $this->userManager->checkPassword( - $username, - $loginData->getPassword() - ); - if ($user !== false) { - $loginData->setUser($user); - $loginData->setUsername($username); - } - } - } - } - - return $this->processNextOrFinishSuccessfully($loginData); - } -} diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php index d82a3be81e54c..6cbcef9629e94 100644 --- a/lib/private/User/Database.php +++ b/lib/private/User/Database.php @@ -12,7 +12,9 @@ use OCP\AppFramework\Db\TTransactional; use OCP\Cache\CappedMemoryCache; use OCP\EventDispatcher\IEventDispatcher; +use OCP\IConfig; use OCP\IDBConnection; +use OCP\IUserManager; use OCP\Security\Events\ValidatePasswordPolicyEvent; use OCP\Security\IHasher; use OCP\User\Backend\ABackend; @@ -41,17 +43,12 @@ class Database extends ABackend implements ISearchKnownUsersBackend, IGetRealUIDBackend, IPasswordHashBackend { - /** @var CappedMemoryCache */ - private $cache; - /** @var IEventDispatcher */ - private $eventDispatcher; - - /** @var IDBConnection */ - private $dbConn; - - /** @var string */ - private $table; + private CappedMemoryCache $cache; + private IConfig $config; + private ?IDBConnection $dbConnection; + private IEventDispatcher $eventDispatcher; + private string $table; use TTransactional; @@ -65,15 +62,18 @@ public function __construct($eventDispatcher = null, $table = 'users') { $this->cache = new CappedMemoryCache(); $this->table = $table; $this->eventDispatcher = $eventDispatcher ?? \OCP\Server::get(IEventDispatcher::class); + $this->config = \OCP\Server::get(IConfig::class); + $this->dbConnection = null; } /** * FIXME: This function should not be required! */ - private function fixDI() { - if ($this->dbConn === null) { - $this->dbConn = \OC::$server->getDatabaseConnection(); + private function getDbConnection() { + if ($this->dbConnection === null) { + $this->dbConnection = \OCP\Server::get(IDBConnection::class); } + return $this->dbConnection; } /** @@ -87,52 +87,54 @@ private function fixDI() { * itself, not in its subclasses. */ public function createUser(string $uid, string $password): bool { - $this->fixDI(); - - if (!$this->userExists($uid)) { - $this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password)); - - return $this->atomic(function () use ($uid, $password) { - $qb = $this->dbConn->getQueryBuilder(); - $qb->insert($this->table) - ->values([ - 'uid' => $qb->createNamedParameter($uid), - 'password' => $qb->createNamedParameter(\OCP\Server::get(IHasher::class)->hash($password)), - 'uid_lower' => $qb->createNamedParameter(mb_strtolower($uid)), - ]); - - $result = $qb->executeStatement(); - - // Clear cache - unset($this->cache[$uid]); - // Repopulate the cache - $this->loadUser($uid); - - return (bool)$result; - }, $this->dbConn); + if ($this->userExists($uid)) { + return false; } - return false; + $this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password)); + + $dbConn = $this->getDbConnection(); + return $this->atomic(function () use ($uid, $password, $dbConn) { + $qb = $dbConn->getQueryBuilder(); + $qb->insert($this->table) + ->values([ + 'uid' => $qb->createNamedParameter($uid), + 'password' => $qb->createNamedParameter(\OCP\Server::get(IHasher::class)->hash($password)), + 'uid_lower' => $qb->createNamedParameter(mb_strtolower($uid)), + ]); + + $result = $qb->executeStatement(); + + // Clear cache + unset($this->cache[$uid]); + // Repopulate the cache + $this->loadUser($uid); + + return (bool)$result; + }, $dbConn); } /** - * delete a user + * Deletes a user * * @param string $uid The username of the user to delete * @return bool - * - * Deletes a user */ public function deleteUser($uid) { - $this->fixDI(); - // Delete user-group-relation - $query = $this->dbConn->getQueryBuilder(); + $dbConn = $this->getDbConnection(); + $query = $dbConn->getQueryBuilder(); $query->delete($this->table) ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid)))); $result = $query->executeStatement(); if (isset($this->cache[$uid])) { + // If the user logged in through email there is a second cache entry, also unset that. + $email = $this->cache[$uid]['email'] ?? null; + if ($email !== null) { + unset($this->cache[$email]); + } + // Unset the cache entry unset($this->cache[$uid]); } @@ -140,7 +142,8 @@ public function deleteUser($uid) { } private function updatePassword(string $uid, string $passwordHash): bool { - $query = $this->dbConn->getQueryBuilder(); + $dbConn = $this->getDbConnection(); + $query = $dbConn->getQueryBuilder(); $query->update($this->table) ->set('password', $query->createNamedParameter($passwordHash)) ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid)))); @@ -159,35 +162,34 @@ private function updatePassword(string $uid, string $passwordHash): bool { * Change the password of a user */ public function setPassword(string $uid, string $password): bool { - $this->fixDI(); - - if ($this->userExists($uid)) { - $this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password)); + if (!$this->userExists($uid)) { + return false; + } - $hasher = \OCP\Server::get(IHasher::class); - $hashedPassword = $hasher->hash($password); + $this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password)); - $return = $this->updatePassword($uid, $hashedPassword); + $hasher = \OCP\Server::get(IHasher::class); + $hashedPassword = $hasher->hash($password); - if ($return) { - $this->cache[$uid]['password'] = $hashedPassword; - } + $return = $this->updatePassword($uid, $hashedPassword); - return $return; + if ($return) { + $this->cache[$uid]['password'] = $hashedPassword; } - return false; + return $return; } public function getPasswordHash(string $userId): ?string { - $this->fixDI(); if (!$this->userExists($userId)) { return null; } if (!empty($this->cache[$userId]['password'])) { return $this->cache[$userId]['password']; } - $qb = $this->dbConn->getQueryBuilder(); + + $dbConn = $this->getDbConnection(); + $qb = $dbConn->getQueryBuilder(); $qb->select('password') ->from($this->table) ->where($qb->expr()->eq('uid_lower', $qb->createNamedParameter(mb_strtolower($userId)))); @@ -196,6 +198,7 @@ public function getPasswordHash(string $userId): ?string { if ($hash === false) { return null; } + $this->cache[$userId]['password'] = $hash; return $hash; } @@ -204,11 +207,12 @@ public function setPasswordHash(string $userId, string $passwordHash): bool { if (!\OCP\Server::get(IHasher::class)->validate($passwordHash)) { throw new InvalidArgumentException(); } - $this->fixDI(); + $result = $this->updatePassword($userId, $passwordHash); if (!$result) { return false; } + $this->cache[$userId]['password'] = $passwordHash; return true; } @@ -229,21 +233,20 @@ public function setDisplayName(string $uid, string $displayName): bool { throw new \InvalidArgumentException('Invalid displayname'); } - $this->fixDI(); - - if ($this->userExists($uid)) { - $query = $this->dbConn->getQueryBuilder(); - $query->update($this->table) - ->set('displayname', $query->createNamedParameter($displayName)) - ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid)))); - $query->executeStatement(); + if (!$this->userExists($uid)) { + return false; + } - $this->cache[$uid]['displayname'] = $displayName; + $dbConn = $this->getDbConnection(); + $query = $dbConn->getQueryBuilder(); + $query->update($this->table) + ->set('displayname', $query->createNamedParameter($displayName)) + ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid)))); + $query->executeStatement(); - return true; - } + $this->cache[$uid]['displayname'] = $displayName; - return false; + return true; } /** @@ -269,9 +272,8 @@ public function getDisplayName($uid): string { public function getDisplayNames($search = '', $limit = null, $offset = null) { $limit = $this->fixLimit($limit); - $this->fixDI(); - - $query = $this->dbConn->getQueryBuilder(); + $dbConn = $this->getDbConnection(); + $query = $dbConn->getQueryBuilder(); $query->select('uid', 'displayname') ->from($this->table, 'u') @@ -281,9 +283,9 @@ public function getDisplayNames($search = '', $limit = null, $offset = null) { $query->expr()->eq('configkey', $query->expr()->literal('email'))) ) // sqlite doesn't like re-using a single named parameter here - ->where($query->expr()->iLike('uid', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%'))) - ->orWhere($query->expr()->iLike('displayname', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%'))) - ->orWhere($query->expr()->iLike('configvalue', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%'))) + ->where($query->expr()->iLike('uid', $query->createPositionalParameter('%' . $dbConn->escapeLikeParameter($search) . '%'))) + ->orWhere($query->expr()->iLike('displayname', $query->createPositionalParameter('%' . $dbConn->escapeLikeParameter($search) . '%'))) + ->orWhere($query->expr()->iLike('configvalue', $query->createPositionalParameter('%' . $dbConn->escapeLikeParameter($search) . '%'))) ->orderBy($query->func()->lower('displayname'), 'ASC') ->addOrderBy('uid_lower', 'ASC') ->setMaxResults($limit) @@ -309,9 +311,8 @@ public function getDisplayNames($search = '', $limit = null, $offset = null) { public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array { $limit = $this->fixLimit($limit); - $this->fixDI(); - - $query = $this->dbConn->getQueryBuilder(); + $dbConn = $this->getDbConnection(); + $query = $dbConn->getQueryBuilder(); $query->select('u.uid', 'u.displayname') ->from($this->table, 'u') @@ -321,8 +322,8 @@ public function searchKnownUsersByDisplayName(string $searcher, string $pattern, )) ->where($query->expr()->eq('k.known_to', $query->createNamedParameter($searcher))) ->andWhere($query->expr()->orX( - $query->expr()->iLike('u.uid', $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($pattern) . '%')), - $query->expr()->iLike('u.displayname', $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($pattern) . '%')) + $query->expr()->iLike('u.uid', $query->createNamedParameter('%' . $dbConn->escapeLikeParameter($pattern) . '%')), + $query->expr()->iLike('u.displayname', $query->createNamedParameter('%' . $dbConn->escapeLikeParameter($pattern) . '%')) )) ->orderBy('u.displayname', 'ASC') ->addOrderBy('u.uid_lower', 'ASC') @@ -341,7 +342,7 @@ public function searchKnownUsersByDisplayName(string $searcher, string $pattern, /** * Check if the password is correct * - * @param string $loginName The loginname + * @param string $loginName The login name * @param string $password The password * @return string * @@ -368,46 +369,62 @@ public function checkPassword(string $loginName, string $password) { /** * Load an user in the cache * - * @param string $uid the username + * @param string $loginName the username or email * @return boolean true if user was found, false otherwise */ - private function loadUser($uid) { - $this->fixDI(); + private function loadUser(string $loginName, bool $tryEmail = true): bool { + if (isset($this->cache[$loginName])) { + return true; + } - $uid = (string)$uid; - if (!isset($this->cache[$uid])) { - //guests $uid could be NULL or '' - if ($uid === '') { - $this->cache[$uid] = false; - return true; - } + //guests $uid could be NULL or '' + if ($loginName === '') { + $this->cache[$loginName] = false; + return true; + } + + $dbConn = $this->getDbConnection(); + $qb = $dbConn->getQueryBuilder(); + $qb->select('uid', 'displayname', 'password') + ->from($this->table) + ->where( + $qb->expr()->eq( + 'uid_lower', $qb->createNamedParameter(mb_strtolower($loginName)) + ) + ); + $result = $qb->executeQuery(); + $row = $result->fetch(); + $result->closeCursor(); + + // "uid" is primary key, so there can only be a single result + if ($row !== false) { + $this->cache[$loginName] = [ + 'uid' => (string)$row['uid'], + 'displayname' => (string)$row['displayname'], + 'password' => (string)$row['password'], + ]; + return true; + } - $qb = $this->dbConn->getQueryBuilder(); - $qb->select('uid', 'displayname', 'password') - ->from($this->table) - ->where( - $qb->expr()->eq( - 'uid_lower', $qb->createNamedParameter(mb_strtolower($uid)) - ) - ); - $result = $qb->executeQuery(); - $row = $result->fetch(); - $result->closeCursor(); - - // "uid" is primary key, so there can only be a single result - if ($row !== false) { - $this->cache[$uid] = [ - 'uid' => (string)$row['uid'], - 'displayname' => (string)$row['displayname'], - 'password' => (string)$row['password'], - ]; - } else { - $this->cache[$uid] = false; - return false; + // Not found by UID so we try also for email, load uid for email. + if ($tryEmail) { + @[$emailUId] = $this->config->getUsersForUserValue('settings', 'email', mb_strtolower($loginName)); + // If found, try loading it + if ($emailUId !== null && $emailUId !== $loginName) { + $result = $this->loadUser($emailUId, false); + if ($result) { + // Also add cache result for the email + $this->cache[$loginName] = $this->cache[$emailUId]; + // Set a reference to the uid cache entry for also delete email entry on user delete + $this->cache[$emailUId]['email'] = $loginName; + } + return $result; } } - return true; + // Not found by uid nor email, so cache as not existing + $this->cache[$loginName] = false; + return false; } /** @@ -448,7 +465,7 @@ public function userExists($uid) { */ public function getHome(string $uid) { if ($this->userExists($uid)) { - return \OC::$server->getConfig()->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $uid; + return \OCP\Server::get(IConfig::class)->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $uid; } return false; @@ -465,9 +482,8 @@ public function hasUserListings() { * counts the users in the database */ public function countUsers(int $limit = 0): int|false { - $this->fixDI(); - - $query = $this->dbConn->getQueryBuilder(); + $dbConn = $this->getDbConnection(); + $query = $dbConn->getQueryBuilder(); $query->select($query->func()->count('uid')) ->from($this->table); $result = $query->executeQuery(); @@ -503,7 +519,7 @@ public static function preLoginNameUsedAsUserName($param) { throw new \Exception('key uid is expected to be set in $param'); } - $backends = \OC::$server->getUserManager()->getBackends(); + $backends = \OCP\Server::get(IUserManager::class)->getBackends(); foreach ($backends as $backend) { if ($backend instanceof Database) { /** @var \OC\User\Database $backend */ diff --git a/tests/lib/Authentication/Login/EmailLoginCommandTest.php b/tests/lib/Authentication/Login/EmailLoginCommandTest.php deleted file mode 100644 index b34d0d95f4f0e..0000000000000 --- a/tests/lib/Authentication/Login/EmailLoginCommandTest.php +++ /dev/null @@ -1,148 +0,0 @@ -userManager = $this->createMock(IUserManager::class); - - $this->cmd = new EmailLoginCommand( - $this->userManager - ); - } - - public function testProcessAlreadyLoggedIn(): void { - $data = $this->getLoggedInLoginData(); - - $result = $this->cmd->process($data); - - $this->assertTrue($result->isSuccess()); - } - - public function testProcessNotAnEmailLogin(): void { - $data = $this->getFailedLoginData(); - $this->userManager->expects($this->never()) - ->method('getByEmail') - ->with($this->username) - ->willReturn([]); - - $result = $this->cmd->process($data); - - $this->assertTrue($result->isSuccess()); - } - - public function testProcessDuplicateEmailLogin(): void { - $data = $this->getFailedLoginData(); - $data->setUsername('user@example.com'); - $this->userManager->expects($this->once()) - ->method('getByEmail') - ->with('user@example.com') - ->willReturn([ - $this->createMock(IUser::class), - $this->createMock(IUser::class), - ]); - - $result = $this->cmd->process($data); - - $this->assertTrue($result->isSuccess()); - } - - public function testProcessUidIsEmail(): void { - $email = 'user@domain.com'; - $data = $this->getFailedLoginData(); - $data->setUsername($email); - $emailUser = $this->createMock(IUser::class); - $emailUser->expects($this->any()) - ->method('getUID') - ->willReturn($email); - $this->userManager->expects($this->once()) - ->method('getByEmail') - ->with($email) - ->willReturn([ - $emailUser, - ]); - $this->userManager->expects($this->never()) - ->method('checkPassword'); - - $result = $this->cmd->process($data); - - $this->assertTrue($result->isSuccess()); - $this->assertFalse($data->getUser()); - $this->assertEquals($email, $data->getUsername()); - } - - public function testProcessWrongPassword(): void { - $email = 'user@domain.com'; - $data = $this->getFailedLoginData(); - $data->setUsername($email); - $emailUser = $this->createMock(IUser::class); - $emailUser->expects($this->any()) - ->method('getUID') - ->willReturn('user2'); - $this->userManager->expects($this->once()) - ->method('getByEmail') - ->with($email) - ->willReturn([ - $emailUser, - ]); - $this->userManager->expects($this->once()) - ->method('checkPassword') - ->with( - 'user2', - $this->password - ) - ->willReturn(false); - - $result = $this->cmd->process($data); - - $this->assertTrue($result->isSuccess()); - $this->assertFalse($data->getUser()); - $this->assertEquals($email, $data->getUsername()); - } - - public function testProcess(): void { - $email = 'user@domain.com'; - $data = $this->getFailedLoginData(); - $data->setUsername($email); - $emailUser = $this->createMock(IUser::class); - $emailUser->expects($this->any()) - ->method('getUID') - ->willReturn('user2'); - $this->userManager->expects($this->once()) - ->method('getByEmail') - ->with($email) - ->willReturn([ - $emailUser, - ]); - $this->userManager->expects($this->once()) - ->method('checkPassword') - ->with( - 'user2', - $this->password - ) - ->willReturn($emailUser); - - $result = $this->cmd->process($data); - - $this->assertTrue($result->isSuccess()); - $this->assertEquals($emailUser, $data->getUser()); - $this->assertEquals('user2', $data->getUsername()); - } -}