From c74014491c96b4ff303e890bce69b8386877ceae Mon Sep 17 00:00:00 2001 From: Wouter Haffmans Date: Sat, 18 Jan 2025 11:45:59 +0100 Subject: [PATCH] fix(User): Match token login name by UID or e-mail address With this changes the login name gets matched against the token user's e-mail address in addition to the login name. This fixes the web login flow of the app, where the session is based on the e-mail address but the token uses the UID. Fixes #44164 Signed-off-by: Wouter Haffmans --- lib/private/User/Session.php | 11 ++++++---- tests/lib/User/SessionTest.php | 39 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 408ebffb3903e..7a07a8a1272f1 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -780,12 +780,15 @@ private function validateToken($token, $user = null) { * Check if login names match */ private function validateTokenLoginName(?string $loginName, IToken $token): bool { - if ($token->getLoginName() !== $loginName) { - // TODO: this makes it impossible to use different login names on browser and client - // e.g. login by e-mail 'user@example.com' on browser for generating the token will not - // allow to use the client token with the login name 'user'. + $tokenUser = $this->manager->get($token->getUID()); + if (!is_null($tokenUser)) { + $tokenEmail = $tokenUser->getEMailAddress(); + } + + if ($token->getLoginName() !== $loginName && (is_null($tokenUser) || $tokenEmail !== $loginName)) { $this->logger->error('App token login name does not match', [ 'tokenLoginName' => $token->getLoginName(), + 'tokenEmailAddress' => $tokenEmail, 'sessionLoginName' => $loginName, 'app' => 'core', 'user' => $token->getUID(), diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php index b3f040d71ec6f..0c46989713275 100644 --- a/tests/lib/User/SessionTest.php +++ b/tests/lib/User/SessionTest.php @@ -516,6 +516,45 @@ public function testLogClientInWithTokenPassword(): void { $this->assertTrue($userSession->logClientIn('john', 'I-AM-AN-APP-PASSWORD', $request, $this->throttler)); } + public function testLogClientInWithEmailTokenPassword(): void { + $manager = $this->createMock(Manager::class); + $session = $this->createMock(ISession::class); + $request = $this->createMock(IRequest::class); + + /** @var Session $userSession */ + $userSession = $this->getMockBuilder(Session::class) + ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher]) + ->setMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser']) + ->getMock(); + + $user = $this->createMock(IUser::class); + $user + ->method('getUID') + ->willReturn('john'); + $user + ->method('getEMailAddress') + ->willReturn('john@example.com'); + $manager + ->method('get') + ->with('john') + ->willReturn($user); + $userSession->expects($this->once()) + ->method('isTokenPassword') + ->willReturn(true); + $userSession->expects($this->once()) + ->method('login') + ->with('john@example.com') + ->willReturn(false); + $token = new PublicKeyToken(); + $token->setLoginName('john'); + $token->setUid('john'); + $this->tokenProvider + ->method('getToken') + ->with('I-AM-AN-APP-PASSWORD') + ->willReturn($token); + + $this->assertTrue($userSession->logClientIn('john@example.com', 'I-AM-AN-APP-PASSWORD', $request, $this->throttler)); + } public function testLogClientInNoTokenPasswordNo2fa(): void { $this->expectException(PasswordLoginForbiddenException::class);