Skip to content

Commit 4ddfa56

Browse files
come-ncbackportbot[bot]
authored andcommitted
fix: Reduce the mixups between apptokens and session ids
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
1 parent 4f723ac commit 4ddfa56

2 files changed

Lines changed: 58 additions & 15 deletions

File tree

lib/private/User/Session.php

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -435,8 +435,15 @@ public function logClientIn($user,
435435
}
436436

437437
try {
438-
$isTokenPassword = $this->isTokenPassword($password);
439-
} catch (ExpiredTokenException $e) {
438+
$dbToken = $this->getTokenFromPassword($password);
439+
$isTokenPassword = $dbToken !== null;
440+
if (($dbToken instanceof PublicKeyToken)
441+
&& ($dbToken->getType() !== IToken::PERMANENT_TOKEN)
442+
) {
443+
// Refuse session tokens here, only app tokens are handled
444+
return false;
445+
}
446+
} catch (ExpiredTokenException) {
440447
// Just return on an expired token no need to check further or record a failed login
441448
return false;
442449
}
@@ -458,7 +465,6 @@ public function logClientIn($user,
458465
}
459466

460467
if ($isTokenPassword) {
461-
$dbToken = $this->tokenProvider->getToken($password);
462468
$userFromToken = $this->manager->get($dbToken->getUID());
463469
$isValidEmailLogin = $userFromToken->getEMailAddress() === $user;
464470
} else {
@@ -547,6 +553,24 @@ public function isTokenPassword($password) {
547553
}
548554
}
549555

556+
/**
557+
* Check if the given 'password' is actually a device token
558+
*
559+
* @throws ExpiredTokenException
560+
*/
561+
private function getTokenFromPassword(string $password): ?\OCP\Authentication\Token\IToken {
562+
try {
563+
return $this->tokenProvider->getToken($password);
564+
} catch (ExpiredTokenException $e) {
565+
throw $e;
566+
} catch (InvalidTokenException $ex) {
567+
$this->logger->debug('Token is not valid: ' . $ex->getMessage(), [
568+
'exception' => $ex,
569+
]);
570+
return null;
571+
}
572+
}
573+
550574
protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
551575
if ($refreshCsrfToken) {
552576
// TODO: mock/inject/use non-static
@@ -828,17 +852,31 @@ private function validateToken($token, $user = null) {
828852
*/
829853
public function tryTokenLogin(IRequest $request) {
830854
$authHeader = $request->getHeader('Authorization');
855+
$tokenFromCookie = false;
831856
if (strpos($authHeader, 'Bearer ') === 0) {
832857
$token = substr($authHeader, 7);
833858
} else {
834859
// No auth header, let's try session id
835860
try {
836861
$token = $this->session->getId();
862+
$tokenFromCookie = true;
837863
} catch (SessionNotAvailableException $ex) {
838864
return false;
839865
}
840866
}
841867

868+
try {
869+
$dbToken = $this->tokenProvider->getToken($token);
870+
} catch (InvalidTokenException $e) {
871+
// Can't really happen but better safe than sorry
872+
return false;
873+
}
874+
875+
if ($dbToken instanceof PublicKeyToken && $dbToken->getType() === IToken::TEMPORARY_TOKEN && !$tokenFromCookie) {
876+
// Session token but from Bearer header, not allowed
877+
return false;
878+
}
879+
842880
if (!$this->loginWithToken($token)) {
843881
return false;
844882
}

tests/lib/User/SessionTest.php

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -403,16 +403,18 @@ public function testLogClientInWithTokenPassword() {
403403
$manager = $this->createMock(Manager::class);
404404
$session = $this->createMock(ISession::class);
405405
$request = $this->createMock(IRequest::class);
406+
$token = $this->createMock(IToken::class);
406407

407408
/** @var Session $userSession */
408409
$userSession = $this->getMockBuilder(Session::class)
409410
->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher])
410-
->setMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
411+
->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
411412
->getMock();
412413

413-
$userSession->expects($this->once())
414-
->method('isTokenPassword')
415-
->willReturn(true);
414+
$this->tokenProvider->expects($this->once())
415+
->method('getToken')
416+
->with('I-AM-AN-APP-PASSWORD')
417+
->willReturn($token);
416418
$userSession->expects($this->once())
417419
->method('login')
418420
->with('john', 'I-AM-AN-APP-PASSWORD')
@@ -1063,16 +1065,18 @@ public function testLogClientInThrottlerUsername() {
10631065
$manager = $this->createMock(Manager::class);
10641066
$session = $this->createMock(ISession::class);
10651067
$request = $this->createMock(IRequest::class);
1068+
$token = $this->createMock(IToken::class);
10661069

10671070
/** @var Session $userSession */
10681071
$userSession = $this->getMockBuilder(Session::class)
10691072
->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher])
1070-
->setMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
1073+
->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
10711074
->getMock();
10721075

1073-
$userSession->expects($this->once())
1074-
->method('isTokenPassword')
1075-
->willReturn(true);
1076+
$this->tokenProvider->expects($this->once())
1077+
->method('getToken')
1078+
->with('I-AM-AN-PASSWORD')
1079+
->willReturn($token);
10761080
$userSession->expects($this->once())
10771081
->method('login')
10781082
->with('john', 'I-AM-AN-PASSWORD')
@@ -1113,12 +1117,13 @@ public function testLogClientInThrottlerEmail() {
11131117
/** @var Session $userSession */
11141118
$userSession = $this->getMockBuilder(Session::class)
11151119
->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher])
1116-
->setMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
1120+
->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
11171121
->getMock();
11181122

1119-
$userSession->expects($this->once())
1120-
->method('isTokenPassword')
1121-
->willReturn(false);
1123+
$this->tokenProvider->expects($this->once())
1124+
->method('getToken')
1125+
->with('I-AM-AN-PASSWORD')
1126+
->willThrowException(new InvalidTokenException());
11221127
$userSession->expects($this->once())
11231128
->method('login')
11241129
->with('john@foo.bar', 'I-AM-AN-PASSWORD')

0 commit comments

Comments
 (0)