Skip to content

Commit 1fa0c4b

Browse files
authored
Merge pull request #59785 from nextcloud/backport/59781/stable27
[stable27] fix: Reduce the mixups between apptokens and session ids
2 parents 2949ab8 + f444f83 commit 1fa0c4b

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
@@ -416,8 +416,15 @@ public function logClientIn($user,
416416
}
417417

418418
try {
419-
$isTokenPassword = $this->isTokenPassword($password);
420-
} catch (ExpiredTokenException $e) {
419+
$dbToken = $this->getTokenFromPassword($password);
420+
$isTokenPassword = $dbToken !== null;
421+
if (($dbToken instanceof PublicKeyToken)
422+
&& ($dbToken->getType() !== IToken::PERMANENT_TOKEN)
423+
) {
424+
// Refuse session tokens here, only app tokens are handled
425+
return false;
426+
}
427+
} catch (ExpiredTokenException) {
421428
// Just return on an expired token no need to check further or record a failed login
422429
return false;
423430
}
@@ -438,7 +445,6 @@ public function logClientIn($user,
438445
}
439446

440447
if ($isTokenPassword) {
441-
$dbToken = $this->tokenProvider->getToken($password);
442448
$userFromToken = $this->manager->get($dbToken->getUID());
443449
$isValidEmailLogin = $userFromToken->getEMailAddress() === $user
444450
&& $this->validateTokenLoginName($userFromToken->getEMailAddress(), $dbToken);
@@ -528,6 +534,24 @@ public function isTokenPassword($password) {
528534
}
529535
}
530536

537+
/**
538+
* Check if the given 'password' is actually a device token
539+
*
540+
* @throws ExpiredTokenException
541+
*/
542+
private function getTokenFromPassword(string $password): ?IToken {
543+
try {
544+
return $this->tokenProvider->getToken($password);
545+
} catch (ExpiredTokenException $e) {
546+
throw $e;
547+
} catch (InvalidTokenException $ex) {
548+
$this->logger->debug('Token is not valid: ' . $ex->getMessage(), [
549+
'exception' => $ex,
550+
]);
551+
return null;
552+
}
553+
}
554+
531555
protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
532556
if ($refreshCsrfToken) {
533557
// TODO: mock/inject/use non-static
@@ -833,32 +857,39 @@ private function validateTokenLoginName(?string $loginName, IToken $token): bool
833857
*/
834858
public function tryTokenLogin(IRequest $request) {
835859
$authHeader = $request->getHeader('Authorization');
860+
$tokenFromCookie = false;
836861
if (strpos($authHeader, 'Bearer ') === 0) {
837862
$token = substr($authHeader, 7);
838863
} elseif ($request->getCookie($this->config->getSystemValueString('instanceid')) !== null) {
839864
// No auth header, let's try session id, but only if this is an existing
840865
// session and the request has a session cookie
841866
try {
842867
$token = $this->session->getId();
868+
$tokenFromCookie = true;
843869
} catch (SessionNotAvailableException $ex) {
844870
return false;
845871
}
846872
} else {
847873
return false;
848874
}
849875

850-
if (!$this->loginWithToken($token)) {
876+
try {
877+
$dbToken = $this->tokenProvider->getToken($token);
878+
} catch (InvalidTokenException $e) {
879+
// Can't really happen but better safe than sorry
851880
return false;
852881
}
853-
if (!$this->validateToken($token)) {
882+
883+
if ($dbToken instanceof PublicKeyToken && $dbToken->getType() === IToken::TEMPORARY_TOKEN && !$tokenFromCookie) {
884+
// Session token but from Bearer header, not allowed
854885
return false;
855886
}
856887

857-
try {
858-
$dbToken = $this->tokenProvider->getToken($token);
859-
} catch (InvalidTokenException $e) {
860-
// Can't really happen but better save than sorry
861-
return true;
888+
if (!$this->loginWithToken($token)) {
889+
return false;
890+
}
891+
if (!$this->validateToken($token)) {
892+
return false;
862893
}
863894

864895
// 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
@@ -484,16 +484,18 @@ public function testLogClientInWithTokenPassword() {
484484
$manager = $this->createMock(Manager::class);
485485
$session = $this->createMock(ISession::class);
486486
$request = $this->createMock(IRequest::class);
487+
$token = $this->createMock(IToken::class);
487488

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

494-
$userSession->expects($this->once())
495-
->method('isTokenPassword')
496-
->willReturn(true);
495+
$this->tokenProvider->expects($this->once())
496+
->method('getToken')
497+
->with('I-AM-AN-APP-PASSWORD')
498+
->willReturn($token);
497499
$userSession->expects($this->once())
498500
->method('login')
499501
->with('john', 'I-AM-AN-APP-PASSWORD')
@@ -1233,16 +1235,18 @@ public function testLogClientInThrottlerUsername() {
12331235
$manager = $this->createMock(Manager::class);
12341236
$session = $this->createMock(ISession::class);
12351237
$request = $this->createMock(IRequest::class);
1238+
$token = $this->createMock(IToken::class);
12361239

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

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

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

0 commit comments

Comments
 (0)