Skip to content

Commit 131ede8

Browse files
authored
Merge pull request #59807 from nextcloud/backport/59804/stable21
[stable21] fix: Reduce the mixups between apptokens and session ids
2 parents 9334847 + 2a42e7e commit 131ede8

2 files changed

Lines changed: 49 additions & 13 deletions

File tree

lib/private/User/Session.php

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
4747
use OC\Authentication\Token\IProvider;
4848
use OC\Authentication\Token\IToken;
49+
use OC\Authentication\Token\PublicKeyToken;
4950
use OC\Hooks\Emitter;
5051
use OC\Hooks\PublicEmitter;
5152
use OC_User;
@@ -445,7 +446,14 @@ public function logClientIn($user,
445446
}
446447

447448
try {
448-
$isTokenPassword = $this->isTokenPassword($password);
449+
$dbToken = $this->getTokenFromPassword($password);
450+
$isTokenPassword = $dbToken !== null;
451+
if (($dbToken instanceof PublicKeyToken)
452+
&& ($dbToken->getType() !== IToken::PERMANENT_TOKEN)
453+
) {
454+
// Refuse session tokens here, only app tokens are handled
455+
return false;
456+
}
449457
} catch (ExpiredTokenException $e) {
450458
// Just return on an expired token no need to check further or record a failed login
451459
return false;
@@ -542,6 +550,24 @@ public function isTokenPassword($password) {
542550
}
543551
}
544552

553+
/**
554+
* Check if the given 'password' is actually a device token
555+
*
556+
* @throws ExpiredTokenException
557+
*/
558+
private function getTokenFromPassword(string $password): ?IToken {
559+
try {
560+
return $this->tokenProvider->getToken($password);
561+
} catch (ExpiredTokenException $e) {
562+
throw $e;
563+
} catch (InvalidTokenException $ex) {
564+
$this->logger->debug('Token is not valid: ' . $ex->getMessage(), [
565+
'exception' => $ex,
566+
]);
567+
return null;
568+
}
569+
}
570+
545571
protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
546572
if ($refreshCsrfToken) {
547573
// TODO: mock/inject/use non-static
@@ -819,29 +845,36 @@ private function validateToken($token, $user = null) {
819845
*/
820846
public function tryTokenLogin(IRequest $request) {
821847
$authHeader = $request->getHeader('Authorization');
848+
$tokenFromCookie = false;
822849
if (strpos($authHeader, 'Bearer ') === 0) {
823850
$token = substr($authHeader, 7);
824851
} else {
825852
// No auth header, let's try session id
826853
try {
827854
$token = $this->session->getId();
855+
$tokenFromCookie = true;
828856
} catch (SessionNotAvailableException $ex) {
829857
return false;
830858
}
831859
}
832860

833-
if (!$this->loginWithToken($token)) {
861+
try {
862+
$dbToken = $this->tokenProvider->getToken($token);
863+
} catch (InvalidTokenException $e) {
864+
// Can't really happen but better safe than sorry
834865
return false;
835866
}
836-
if (!$this->validateToken($token)) {
867+
868+
if ($dbToken instanceof PublicKeyToken && $dbToken->getType() === IToken::TEMPORARY_TOKEN && !$tokenFromCookie) {
869+
// Session token but from Bearer header, not allowed
837870
return false;
838871
}
839872

840-
try {
841-
$dbToken = $this->tokenProvider->getToken($token);
842-
} catch (InvalidTokenException $e) {
843-
// Can't really happen but better save than sorry
844-
return true;
873+
if (!$this->loginWithToken($token)) {
874+
return false;
875+
}
876+
if (!$this->validateToken($token)) {
877+
return false;
845878
}
846879

847880
// Remember me tokens are not app_passwords

tests/lib/User/SessionTest.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
/**
34
* Copyright (c) 2013 Robin Appelman <icewind@owncloud.com>
45
* This file is licensed under the Affero General Public License version 3 or
@@ -488,16 +489,18 @@ public function testLogClientInWithTokenPassword() {
488489
$manager = $this->createMock(Manager::class);
489490
$session = $this->createMock(ISession::class);
490491
$request = $this->createMock(IRequest::class);
492+
$token = $this->createMock(IToken::class);
491493

492494
/** @var \OC\User\Session $userSession */
493495
$userSession = $this->getMockBuilder(Session::class)
494496
->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher])
495-
->setMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
497+
->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
496498
->getMock();
497499

498-
$userSession->expects($this->once())
499-
->method('isTokenPassword')
500-
->willReturn(true);
500+
$this->tokenProvider->expects($this->once())
501+
->method('getToken')
502+
->with('I-AM-AN-APP-PASSWORD')
503+
->willReturn($token);
501504
$userSession->expects($this->once())
502505
->method('login')
503506
->with('john', 'I-AM-AN-APP-PASSWORD')
@@ -1031,7 +1034,7 @@ public function testTryTokenLoginWithDisabledUser() {
10311034
->method('getHeader')
10321035
->with('Authorization')
10331036
->willReturn('Bearer xxxxx');
1034-
$this->tokenProvider->expects($this->once())
1037+
$this->tokenProvider->expects($this->atLeastOnce())
10351038
->method('getToken')
10361039
->with('xxxxx')
10371040
->willReturn($token);

0 commit comments

Comments
 (0)