diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 88e239ca92d56..0dccf1bd5771a 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -418,8 +418,15 @@ public function logClientIn($user, } try { - $isTokenPassword = $this->isTokenPassword($password); - } catch (ExpiredTokenException $e) { + $dbToken = $this->getTokenFromPassword($password); + $isTokenPassword = $dbToken !== null; + if (($dbToken instanceof PublicKeyToken) + && ($dbToken->getType() !== IToken::PERMANENT_TOKEN) + ) { + // Refuse session tokens here, only app tokens are handled + return false; + } + } catch (ExpiredTokenException) { // Just return on an expired token no need to check further or record a failed login return false; } @@ -440,7 +447,6 @@ public function logClientIn($user, } if ($isTokenPassword) { - $dbToken = $this->tokenProvider->getToken($password); $userFromToken = $this->manager->get($dbToken->getUID()); $isValidEmailLogin = $userFromToken->getEMailAddress() === $user && $this->validateTokenLoginName($userFromToken->getEMailAddress(), $dbToken); @@ -530,6 +536,24 @@ public function isTokenPassword($password) { } } + /** + * Check if the given 'password' is actually a device token + * + * @throws ExpiredTokenException + */ + private function getTokenFromPassword(string $password): ?\OCP\Authentication\Token\IToken { + try { + return $this->tokenProvider->getToken($password); + } catch (ExpiredTokenException $e) { + throw $e; + } catch (InvalidTokenException $ex) { + $this->logger->debug('Token is not valid: ' . $ex->getMessage(), [ + 'exception' => $ex, + ]); + return null; + } + } + protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) { if ($refreshCsrfToken) { // TODO: mock/inject/use non-static @@ -836,6 +860,7 @@ private function validateTokenLoginName(?string $loginName, IToken $token): bool */ public function tryTokenLogin(IRequest $request) { $authHeader = $request->getHeader('Authorization'); + $tokenFromCookie = false; if (str_starts_with($authHeader, 'Bearer ')) { $token = substr($authHeader, 7); } elseif ($request->getCookie($this->config->getSystemValueString('instanceid')) !== null) { @@ -843,6 +868,7 @@ public function tryTokenLogin(IRequest $request) { // session and the request has a session cookie try { $token = $this->session->getId(); + $tokenFromCookie = true; } catch (SessionNotAvailableException $ex) { return false; } @@ -850,18 +876,23 @@ public function tryTokenLogin(IRequest $request) { return false; } - if (!$this->loginWithToken($token)) { + try { + $dbToken = $this->tokenProvider->getToken($token); + } catch (InvalidTokenException $e) { + // Can't really happen but better safe than sorry return false; } - if (!$this->validateToken($token)) { + + if ($dbToken instanceof PublicKeyToken && $dbToken->getType() === IToken::TEMPORARY_TOKEN && !$tokenFromCookie) { + // Session token but from Bearer header, not allowed return false; } - try { - $dbToken = $this->tokenProvider->getToken($token); - } catch (InvalidTokenException $e) { - // Can't really happen but better save than sorry - return true; + if (!$this->loginWithToken($token)) { + return false; + } + if (!$this->validateToken($token)) { + return false; } // Set the session variable so we know this is an app password diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php index 08b953d9f140a..6083e01484684 100644 --- a/tests/lib/User/SessionTest.php +++ b/tests/lib/User/SessionTest.php @@ -483,16 +483,18 @@ public function testLogClientInWithTokenPassword() { $manager = $this->createMock(Manager::class); $session = $this->createMock(ISession::class); $request = $this->createMock(IRequest::class); + $token = $this->createMock(IToken::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']) + ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser']) ->getMock(); - $userSession->expects($this->once()) - ->method('isTokenPassword') - ->willReturn(true); + $this->tokenProvider->expects($this->once()) + ->method('getToken') + ->with('I-AM-AN-APP-PASSWORD') + ->willReturn($token); $userSession->expects($this->once()) ->method('login') ->with('john', 'I-AM-AN-APP-PASSWORD') @@ -1232,16 +1234,18 @@ public function testLogClientInThrottlerUsername() { $manager = $this->createMock(Manager::class); $session = $this->createMock(ISession::class); $request = $this->createMock(IRequest::class); + $token = $this->createMock(IToken::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']) + ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser']) ->getMock(); - $userSession->expects($this->once()) - ->method('isTokenPassword') - ->willReturn(true); + $this->tokenProvider->expects($this->once()) + ->method('getToken') + ->with('I-AM-AN-PASSWORD') + ->willReturn($token); $userSession->expects($this->once()) ->method('login') ->with('john', 'I-AM-AN-PASSWORD') @@ -1282,12 +1286,13 @@ public function testLogClientInThrottlerEmail() { /** @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']) + ->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser']) ->getMock(); - $userSession->expects($this->once()) - ->method('isTokenPassword') - ->willReturn(false); + $this->tokenProvider->expects($this->once()) + ->method('getToken') + ->with('I-AM-AN-PASSWORD') + ->willThrowException(new InvalidTokenException()); $userSession->expects($this->once()) ->method('login') ->with('john@foo.bar', 'I-AM-AN-PASSWORD')