Skip to content

Commit 7e929af

Browse files
committed
fix: Reduce the mixups between apptokens and session ids
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
1 parent 4f723ac commit 7e929af

2 files changed

Lines changed: 59 additions & 22 deletions

File tree

lib/private/User/Session.php

Lines changed: 42 additions & 10 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_User;
@@ -435,8 +436,15 @@ public function logClientIn($user,
435436
}
436437

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

460468
if ($isTokenPassword) {
461-
$dbToken = $this->tokenProvider->getToken($password);
462469
$userFromToken = $this->manager->get($dbToken->getUID());
463470
$isValidEmailLogin = $userFromToken->getEMailAddress() === $user;
464471
} else {
@@ -547,6 +554,24 @@ public function isTokenPassword($password) {
547554
}
548555
}
549556

557+
/**
558+
* Check if the given 'password' is actually a device token
559+
*
560+
* @throws ExpiredTokenException
561+
*/
562+
private function getTokenFromPassword(string $password): ?\OCP\Authentication\Token\IToken {
563+
try {
564+
return $this->tokenProvider->getToken($password);
565+
} catch (ExpiredTokenException $e) {
566+
throw $e;
567+
} catch (InvalidTokenException $ex) {
568+
$this->logger->debug('Token is not valid: ' . $ex->getMessage(), [
569+
'exception' => $ex,
570+
]);
571+
return null;
572+
}
573+
}
574+
550575
protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
551576
if ($refreshCsrfToken) {
552577
// TODO: mock/inject/use non-static
@@ -828,29 +853,36 @@ private function validateToken($token, $user = null) {
828853
*/
829854
public function tryTokenLogin(IRequest $request) {
830855
$authHeader = $request->getHeader('Authorization');
856+
$tokenFromCookie = false;
831857
if (strpos($authHeader, 'Bearer ') === 0) {
832858
$token = substr($authHeader, 7);
833859
} else {
834860
// No auth header, let's try session id
835861
try {
836862
$token = $this->session->getId();
863+
$tokenFromCookie = true;
837864
} catch (SessionNotAvailableException $ex) {
838865
return false;
839866
}
840867
}
841868

842-
if (!$this->loginWithToken($token)) {
869+
try {
870+
$dbToken = $this->tokenProvider->getToken($token);
871+
} catch (InvalidTokenException $e) {
872+
// Can't really happen but better safe than sorry
843873
return false;
844874
}
845-
if (!$this->validateToken($token)) {
875+
876+
if ($dbToken instanceof PublicKeyToken && $dbToken->getType() === IToken::TEMPORARY_TOKEN && !$tokenFromCookie) {
877+
// Session token but from Bearer header, not allowed
846878
return false;
847879
}
848880

849-
try {
850-
$dbToken = $this->tokenProvider->getToken($token);
851-
} catch (InvalidTokenException $e) {
852-
// Can't really happen but better save than sorry
853-
return true;
881+
if (!$this->loginWithToken($token)) {
882+
return false;
883+
}
884+
if (!$this->validateToken($token)) {
885+
return false;
854886
}
855887

856888
// Remember me tokens are not app_passwords

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)