Skip to content

Commit 1a18fe5

Browse files
authored
Merge pull request #59774 from nextcloud/backport/59758/stable32
[stable32] fix: Reduce the mixups between apptokens and session ids
2 parents 505fff1 + 310b488 commit 1a18fe5

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
@@ -389,8 +389,15 @@ public function logClientIn($user,
389389
}
390390

391391
try {
392-
$isTokenPassword = $this->isTokenPassword($password);
393-
} catch (ExpiredTokenException $e) {
392+
$dbToken = $this->getTokenFromPassword($password);
393+
$isTokenPassword = $dbToken !== null;
394+
if (($dbToken instanceof PublicKeyToken)
395+
&& ($dbToken->getType() !== IToken::PERMANENT_TOKEN)
396+
) {
397+
// Refuse session tokens here, only app tokens are handled
398+
return false;
399+
}
400+
} catch (ExpiredTokenException) {
394401
// Just return on an expired token no need to check further or record a failed login
395402
return false;
396403
}
@@ -411,7 +418,6 @@ public function logClientIn($user,
411418
}
412419

413420
if ($isTokenPassword) {
414-
$dbToken = $this->tokenProvider->getToken($password);
415421
$userFromToken = $this->manager->get($dbToken->getUID());
416422
$isValidEmailLogin = $userFromToken->getEMailAddress() === $user
417423
&& $this->validateTokenLoginName($userFromToken->getEMailAddress(), $dbToken);
@@ -501,6 +507,24 @@ public function isTokenPassword($password) {
501507
}
502508
}
503509

510+
/**
511+
* Check if the given 'password' is actually a device token
512+
*
513+
* @throws ExpiredTokenException
514+
*/
515+
private function getTokenFromPassword(string $password): ?\OCP\Authentication\Token\IToken {
516+
try {
517+
return $this->tokenProvider->getToken($password);
518+
} catch (ExpiredTokenException $e) {
519+
throw $e;
520+
} catch (InvalidTokenException $ex) {
521+
$this->logger->debug('Token is not valid: ' . $ex->getMessage(), [
522+
'exception' => $ex,
523+
]);
524+
return null;
525+
}
526+
}
527+
504528
protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
505529
if ($refreshCsrfToken) {
506530
// TODO: mock/inject/use non-static
@@ -807,32 +831,39 @@ private function validateTokenLoginName(?string $loginName, IToken $token): bool
807831
*/
808832
public function tryTokenLogin(IRequest $request) {
809833
$authHeader = $request->getHeader('Authorization');
834+
$tokenFromCookie = false;
810835
if (str_starts_with($authHeader, 'Bearer ')) {
811836
$token = substr($authHeader, 7);
812837
} elseif ($request->getCookie($this->config->getSystemValueString('instanceid')) !== null) {
813838
// No auth header, let's try session id, but only if this is an existing
814839
// session and the request has a session cookie
815840
try {
816841
$token = $this->session->getId();
842+
$tokenFromCookie = true;
817843
} catch (SessionNotAvailableException $ex) {
818844
return false;
819845
}
820846
} else {
821847
return false;
822848
}
823849

824-
if (!$this->loginWithToken($token)) {
850+
try {
851+
$dbToken = $this->tokenProvider->getToken($token);
852+
} catch (InvalidTokenException $e) {
853+
// Can't really happen but better safe than sorry
825854
return false;
826855
}
827-
if (!$this->validateToken($token)) {
856+
857+
if ($dbToken instanceof PublicKeyToken && $dbToken->getType() === IToken::TEMPORARY_TOKEN && !$tokenFromCookie) {
858+
// Session token but from Bearer header, not allowed
828859
return false;
829860
}
830861

831-
try {
832-
$dbToken = $this->tokenProvider->getToken($token);
833-
} catch (InvalidTokenException $e) {
834-
// Can't really happen but better save than sorry
835-
return true;
862+
if (!$this->loginWithToken($token)) {
863+
return false;
864+
}
865+
if (!$this->validateToken($token)) {
866+
return false;
836867
}
837868

838869
// 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
@@ -481,16 +481,18 @@ public function testLogClientInWithTokenPassword(): void {
481481
$manager = $this->createMock(Manager::class);
482482
$session = $this->createMock(ISession::class);
483483
$request = $this->createMock(IRequest::class);
484+
$token = $this->createMock(IToken::class);
484485

485486
/** @var Session $userSession */
486487
$userSession = $this->getMockBuilder(Session::class)
487488
->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher])
488-
->onlyMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
489+
->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
489490
->getMock();
490491

491-
$userSession->expects($this->once())
492-
->method('isTokenPassword')
493-
->willReturn(true);
492+
$this->tokenProvider->expects($this->once())
493+
->method('getToken')
494+
->with('I-AM-AN-APP-PASSWORD')
495+
->willReturn($token);
494496
$userSession->expects($this->once())
495497
->method('login')
496498
->with('john', 'I-AM-AN-APP-PASSWORD')
@@ -1230,16 +1232,18 @@ public function testLogClientInThrottlerUsername(): void {
12301232
$manager = $this->createMock(Manager::class);
12311233
$session = $this->createMock(ISession::class);
12321234
$request = $this->createMock(IRequest::class);
1235+
$token = $this->createMock(IToken::class);
12331236

12341237
/** @var Session $userSession */
12351238
$userSession = $this->getMockBuilder(Session::class)
12361239
->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher])
1237-
->onlyMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
1240+
->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
12381241
->getMock();
12391242

1240-
$userSession->expects($this->once())
1241-
->method('isTokenPassword')
1242-
->willReturn(true);
1243+
$this->tokenProvider->expects($this->once())
1244+
->method('getToken')
1245+
->with('I-AM-AN-PASSWORD')
1246+
->willReturn($token);
12431247
$userSession->expects($this->once())
12441248
->method('login')
12451249
->with('john', 'I-AM-AN-PASSWORD')
@@ -1280,12 +1284,13 @@ public function testLogClientInThrottlerEmail(): void {
12801284
/** @var Session $userSession */
12811285
$userSession = $this->getMockBuilder(Session::class)
12821286
->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher])
1283-
->onlyMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
1287+
->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
12841288
->getMock();
12851289

1286-
$userSession->expects($this->once())
1287-
->method('isTokenPassword')
1288-
->willReturn(false);
1290+
$this->tokenProvider->expects($this->once())
1291+
->method('getToken')
1292+
->with('I-AM-AN-PASSWORD')
1293+
->willThrowException(new InvalidTokenException());
12891294
$userSession->expects($this->once())
12901295
->method('login')
12911296
->with('john@foo.bar', 'I-AM-AN-PASSWORD')

0 commit comments

Comments
 (0)