Skip to content

Commit 88461b1

Browse files
authored
Merge pull request #59806 from nextcloud/backport/59804/stable22
[stable22] fix: Reduce the mixups between apptokens and session ids
2 parents 11877fc + 6ce68de commit 88461b1

2 files changed

Lines changed: 61 additions & 21 deletions

File tree

lib/private/User/Session.php

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
4646
use OC\Authentication\Token\IProvider;
4747
use OC\Authentication\Token\IToken;
48+
use OC\Authentication\Token\PublicKeyToken;
4849
use OC\Hooks\Emitter;
4950
use OC\Hooks\PublicEmitter;
5051
use OC\Security\Bruteforce\Throttler;
@@ -446,7 +447,14 @@ public function logClientIn($user,
446447
}
447448

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

561+
/**
562+
* Check if the given 'password' is actually a device token
563+
*
564+
* @throws ExpiredTokenException
565+
*/
566+
private function getTokenFromPassword(string $password): ?IToken {
567+
try {
568+
return $this->tokenProvider->getToken($password);
569+
} catch (ExpiredTokenException $e) {
570+
throw $e;
571+
} catch (InvalidTokenException $ex) {
572+
$this->logger->debug('Token is not valid: ' . $ex->getMessage(), [
573+
'exception' => $ex,
574+
]);
575+
return null;
576+
}
577+
}
578+
553579
protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
554580
if ($refreshCsrfToken) {
555581
// TODO: mock/inject/use non-static
@@ -831,29 +857,36 @@ private function validateToken($token, $user = null) {
831857
*/
832858
public function tryTokenLogin(IRequest $request) {
833859
$authHeader = $request->getHeader('Authorization');
860+
$tokenFromCookie = false;
834861
if (strpos($authHeader, 'Bearer ') === 0) {
835862
$token = substr($authHeader, 7);
836863
} else {
837864
// No auth header, let's try session id
838865
try {
839866
$token = $this->session->getId();
867+
$tokenFromCookie = true;
840868
} catch (SessionNotAvailableException $ex) {
841869
return false;
842870
}
843871
}
844872

845-
if (!$this->loginWithToken($token)) {
873+
try {
874+
$dbToken = $this->tokenProvider->getToken($token);
875+
} catch (InvalidTokenException $e) {
876+
// Can't really happen but better safe than sorry
846877
return false;
847878
}
848-
if (!$this->validateToken($token)) {
879+
880+
if ($dbToken instanceof PublicKeyToken && $dbToken->getType() === IToken::TEMPORARY_TOKEN && !$tokenFromCookie) {
881+
// Session token but from Bearer header, not allowed
849882
return false;
850883
}
851884

852-
try {
853-
$dbToken = $this->tokenProvider->getToken($token);
854-
} catch (InvalidTokenException $e) {
855-
// Can't really happen but better save than sorry
856-
return true;
885+
if (!$this->loginWithToken($token)) {
886+
return false;
887+
}
888+
if (!$this->validateToken($token)) {
889+
return false;
857890
}
858891

859892
// Remember me tokens are not app_passwords

tests/lib/User/SessionTest.php

Lines changed: 20 additions & 13 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
@@ -10,6 +11,7 @@
1011

1112
use OC\AppFramework\Http\Request;
1213
use OC\Authentication\Events\LoginFailed;
14+
use OC\Authentication\Exceptions\InvalidTokenException;
1315
use OC\Authentication\Token\DefaultTokenMapper;
1416
use OC\Authentication\Token\DefaultTokenProvider;
1517
use OC\Authentication\Token\IProvider;
@@ -489,16 +491,18 @@ public function testLogClientInWithTokenPassword() {
489491
$manager = $this->createMock(Manager::class);
490492
$session = $this->createMock(ISession::class);
491493
$request = $this->createMock(IRequest::class);
494+
$token = $this->createMock(IToken::class);
492495

493496
/** @var \OC\User\Session $userSession */
494497
$userSession = $this->getMockBuilder(Session::class)
495498
->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher])
496-
->setMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
499+
->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
497500
->getMock();
498501

499-
$userSession->expects($this->once())
500-
->method('isTokenPassword')
501-
->willReturn(true);
502+
$this->tokenProvider->expects($this->once())
503+
->method('getToken')
504+
->with('I-AM-AN-APP-PASSWORD')
505+
->willReturn($token);
502506
$userSession->expects($this->once())
503507
->method('login')
504508
->with('john', 'I-AM-AN-APP-PASSWORD')
@@ -1032,7 +1036,7 @@ public function testTryTokenLoginWithDisabledUser() {
10321036
->method('getHeader')
10331037
->with('Authorization')
10341038
->willReturn('Bearer xxxxx');
1035-
$this->tokenProvider->expects($this->once())
1039+
$this->tokenProvider->expects($this->atLeastOnce())
10361040
->method('getToken')
10371041
->with('xxxxx')
10381042
->willReturn($token);
@@ -1478,16 +1482,18 @@ public function testLogClientInThrottlerUsername() {
14781482
$manager = $this->createMock(Manager::class);
14791483
$session = $this->createMock(ISession::class);
14801484
$request = $this->createMock(IRequest::class);
1485+
$token = $this->createMock(IToken::class);
14811486

14821487
/** @var Session $userSession */
14831488
$userSession = $this->getMockBuilder(Session::class)
14841489
->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher])
1485-
->setMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
1490+
->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
14861491
->getMock();
14871492

1488-
$userSession->expects($this->once())
1489-
->method('isTokenPassword')
1490-
->willReturn(true);
1493+
$this->tokenProvider->expects($this->once())
1494+
->method('getToken')
1495+
->with('I-AM-AN-PASSWORD')
1496+
->willReturn($token);
14911497
$userSession->expects($this->once())
14921498
->method('login')
14931499
->with('john', 'I-AM-AN-PASSWORD')
@@ -1528,12 +1534,13 @@ public function testLogClientInThrottlerEmail() {
15281534
/** @var Session $userSession */
15291535
$userSession = $this->getMockBuilder(Session::class)
15301536
->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger, $this->dispatcher])
1531-
->setMethods(['isTokenPassword', 'login', 'supportsCookies', 'createSessionToken', 'getUser'])
1537+
->onlyMethods(['login', 'supportsCookies', 'createSessionToken', 'getUser'])
15321538
->getMock();
15331539

1534-
$userSession->expects($this->once())
1535-
->method('isTokenPassword')
1536-
->willReturn(true);
1540+
$this->tokenProvider->expects($this->once())
1541+
->method('getToken')
1542+
->with('I-AM-AN-PASSWORD')
1543+
->willThrowException(new InvalidTokenException());
15371544
$userSession->expects($this->once())
15381545
->method('login')
15391546
->with('john@foo.bar', 'I-AM-AN-PASSWORD')

0 commit comments

Comments
 (0)