Skip to content

Commit 8f3005b

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 a0b5986 commit 8f3005b

2 files changed

Lines changed: 58 additions & 22 deletions

File tree

lib/private/User/Session.php

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -418,8 +418,15 @@ public function logClientIn($user,
418418
}
419419

420420
try {
421-
$isTokenPassword = $this->isTokenPassword($password);
422-
} catch (ExpiredTokenException $e) {
421+
$dbToken = $this->getTokenFromPassword($password);
422+
$isTokenPassword = $dbToken !== null;
423+
if (($dbToken instanceof PublicKeyToken)
424+
&& ($dbToken->getType() !== IToken::PERMANENT_TOKEN)
425+
) {
426+
// Refuse session tokens here, only app tokens are handled
427+
return false;
428+
}
429+
} catch (ExpiredTokenException) {
423430
// Just return on an expired token no need to check further or record a failed login
424431
return false;
425432
}
@@ -440,7 +447,6 @@ public function logClientIn($user,
440447
}
441448

442449
if ($isTokenPassword) {
443-
$dbToken = $this->tokenProvider->getToken($password);
444450
$userFromToken = $this->manager->get($dbToken->getUID());
445451
$isValidEmailLogin = $userFromToken->getEMailAddress() === $user
446452
&& $this->validateTokenLoginName($userFromToken->getEMailAddress(), $dbToken);
@@ -530,6 +536,24 @@ public function isTokenPassword($password) {
530536
}
531537
}
532538

539+
/**
540+
* Check if the given 'password' is actually a device token
541+
*
542+
* @throws ExpiredTokenException
543+
*/
544+
private function getTokenFromPassword(string $password): ?\OCP\Authentication\Token\IToken {
545+
try {
546+
return $this->tokenProvider->getToken($password);
547+
} catch (ExpiredTokenException $e) {
548+
throw $e;
549+
} catch (InvalidTokenException $ex) {
550+
$this->logger->debug('Token is not valid: ' . $ex->getMessage(), [
551+
'exception' => $ex,
552+
]);
553+
return null;
554+
}
555+
}
556+
533557
protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
534558
if ($refreshCsrfToken) {
535559
// TODO: mock/inject/use non-static
@@ -836,32 +860,39 @@ private function validateTokenLoginName(?string $loginName, IToken $token): bool
836860
*/
837861
public function tryTokenLogin(IRequest $request) {
838862
$authHeader = $request->getHeader('Authorization');
863+
$tokenFromCookie = false;
839864
if (str_starts_with($authHeader, 'Bearer ')) {
840865
$token = substr($authHeader, 7);
841866
} elseif ($request->getCookie($this->config->getSystemValueString('instanceid')) !== null) {
842867
// No auth header, let's try session id, but only if this is an existing
843868
// session and the request has a session cookie
844869
try {
845870
$token = $this->session->getId();
871+
$tokenFromCookie = true;
846872
} catch (SessionNotAvailableException $ex) {
847873
return false;
848874
}
849875
} else {
850876
return false;
851877
}
852878

853-
if (!$this->loginWithToken($token)) {
879+
try {
880+
$dbToken = $this->tokenProvider->getToken($token);
881+
} catch (InvalidTokenException $e) {
882+
// Can't really happen but better safe than sorry
854883
return false;
855884
}
856-
if (!$this->validateToken($token)) {
885+
886+
if ($dbToken instanceof PublicKeyToken && $dbToken->getType() === IToken::TEMPORARY_TOKEN && !$tokenFromCookie) {
887+
// Session token but from Bearer header, not allowed
857888
return false;
858889
}
859890

860-
try {
861-
$dbToken = $this->tokenProvider->getToken($token);
862-
} catch (InvalidTokenException $e) {
863-
// Can't really happen but better save than sorry
864-
return true;
891+
if (!$this->loginWithToken($token)) {
892+
return false;
893+
}
894+
if (!$this->validateToken($token)) {
895+
return false;
865896
}
866897

867898
// Set the session variable so we know this is an app password

tests/lib/User/SessionTest.php

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -483,16 +483,18 @@ public function testLogClientInWithTokenPassword() {
483483
$manager = $this->createMock(Manager::class);
484484
$session = $this->createMock(ISession::class);
485485
$request = $this->createMock(IRequest::class);
486+
$token = $this->createMock(IToken::class);
486487

487488
/** @var Session $userSession */
488489
$userSession = $this->getMockBuilder(Session::class)
489490
->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher])
490-
->setMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
491+
->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
491492
->getMock();
492493

493-
$userSession->expects($this->once())
494-
->method('isTokenPassword')
495-
->willReturn(true);
494+
$this->tokenProvider->expects($this->once())
495+
->method('getToken')
496+
->with('I-AM-AN-APP-PASSWORD')
497+
->willReturn($token);
496498
$userSession->expects($this->once())
497499
->method('login')
498500
->with('john', 'I-AM-AN-APP-PASSWORD')
@@ -1232,16 +1234,18 @@ public function testLogClientInThrottlerUsername() {
12321234
$manager = $this->createMock(Manager::class);
12331235
$session = $this->createMock(ISession::class);
12341236
$request = $this->createMock(IRequest::class);
1237+
$token = $this->createMock(IToken::class);
12351238

12361239
/** @var Session $userSession */
12371240
$userSession = $this->getMockBuilder(Session::class)
12381241
->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher])
1239-
->setMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
1242+
->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
12401243
->getMock();
12411244

1242-
$userSession->expects($this->once())
1243-
->method('isTokenPassword')
1244-
->willReturn(true);
1245+
$this->tokenProvider->expects($this->once())
1246+
->method('getToken')
1247+
->with('I-AM-AN-PASSWORD')
1248+
->willReturn($token);
12451249
$userSession->expects($this->once())
12461250
->method('login')
12471251
->with('john', 'I-AM-AN-PASSWORD')
@@ -1282,12 +1286,13 @@ public function testLogClientInThrottlerEmail() {
12821286
/** @var Session $userSession */
12831287
$userSession = $this->getMockBuilder(Session::class)
12841288
->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher])
1285-
->setMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
1289+
->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
12861290
->getMock();
12871291

1288-
$userSession->expects($this->once())
1289-
->method('isTokenPassword')
1290-
->willReturn(false);
1292+
$this->tokenProvider->expects($this->once())
1293+
->method('getToken')
1294+
->with('I-AM-AN-PASSWORD')
1295+
->willThrowException(new InvalidTokenException());
12911296
$userSession->expects($this->once())
12921297
->method('login')
12931298
->with('john@foo.bar', 'I-AM-AN-PASSWORD')

0 commit comments

Comments
 (0)