Skip to content

Commit d1b0f02

Browse files
authored
Merge pull request #18189 from nextcloud/backport/17939/stable17
[stable17] Handle token insert conflicts
2 parents 1402d1b + 55b5e13 commit d1b0f02

File tree

2 files changed

+68
-20
lines changed

2 files changed

+68
-20
lines changed

lib/private/Authentication/Token/Manager.php

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
namespace OC\Authentication\Token;
2525

26+
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
2627
use OC\Authentication\Exceptions\ExpiredTokenException;
2728
use OC\Authentication\Exceptions\InvalidTokenException;
2829
use OC\Authentication\Exceptions\PasswordlessTokenException;
@@ -60,15 +61,29 @@ public function generateToken(string $token,
6061
string $name,
6162
int $type = IToken::TEMPORARY_TOKEN,
6263
int $remember = IToken::DO_NOT_REMEMBER): IToken {
63-
return $this->publicKeyTokenProvider->generateToken(
64-
$token,
65-
$uid,
66-
$loginName,
67-
$password,
68-
$name,
69-
$type,
70-
$remember
71-
);
64+
try {
65+
return $this->publicKeyTokenProvider->generateToken(
66+
$token,
67+
$uid,
68+
$loginName,
69+
$password,
70+
$name,
71+
$type,
72+
$remember
73+
);
74+
} catch (UniqueConstraintViolationException $e) {
75+
// It's rare, but if two requests of the same session (e.g. env-based SAML)
76+
// try to create the session token they might end up here at the same time
77+
// because we use the session ID as token and the db token is created anew
78+
// with every request.
79+
//
80+
// If the UIDs match, then this should be fine.
81+
$existing = $this->getToken($token);
82+
if ($existing->getUID() !== $uid) {
83+
throw new \Exception('Token conflict handled, but UIDs do not match. This should not happen', 0, $e);
84+
}
85+
return $existing;
86+
}
7287
}
7388

7489
/**

tests/lib/Authentication/Token/ManagerTest.php

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
<?php
1+
<?php declare(strict_types=1);
2+
23
/**
34
* @copyright Copyright (c) 2018 Roeland Jago Douma <roeland@famdouma.nl>
45
*
@@ -23,29 +24,23 @@
2324

2425
namespace Test\Authentication\Token;
2526

27+
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
2628
use OC\Authentication\Exceptions\InvalidTokenException;
2729
use OC\Authentication\Exceptions\PasswordlessTokenException;
2830
use OC\Authentication\Token\DefaultToken;
2931
use OC\Authentication\Token\DefaultTokenProvider;
3032
use OC\Authentication\Token\Manager;
3133
use OC\Authentication\Token\PublicKeyToken;
32-
use OC\Authentication\Token\PublicKeyTokenMapper;
3334
use OC\Authentication\Token\PublicKeyTokenProvider;
34-
use OC\Authentication\Token\ExpiredTokenException;
35+
use PHPUnit\Framework\MockObject\MockObject;
3536
use OC\Authentication\Token\IToken;
36-
use OCP\AppFramework\Db\DoesNotExistException;
37-
use OCP\AppFramework\Utility\ITimeFactory;
38-
use OCP\IConfig;
39-
use OCP\ILogger;
40-
use OCP\IUser;
41-
use OCP\Security\ICrypto;
4237
use Test\TestCase;
4338

4439
class ManagerTest extends TestCase {
4540

46-
/** @var PublicKeyTokenProvider|\PHPUnit_Framework_MockObject_MockObject */
41+
/** @var PublicKeyTokenProvider|MockObject */
4742
private $publicKeyTokenProvider;
48-
/** @var DefaultTokenProvider|\PHPUnit_Framework_MockObject_MockObject */
43+
/** @var DefaultTokenProvider|MockObject */
4944
private $defaultTokenProvider;
5045
/** @var Manager */
5146
private $manager;
@@ -92,6 +87,44 @@ public function testGenerateToken() {
9287
$this->assertSame($token, $actual);
9388
}
9489

90+
public function testGenerateConflictingToken() {
91+
/** @var MockObject|UniqueConstraintViolationException $exception */
92+
$exception = $this->createMock(UniqueConstraintViolationException::class);
93+
$this->defaultTokenProvider->expects($this->never())
94+
->method('generateToken');
95+
96+
$token = new PublicKeyToken();
97+
$token->setUid('uid');
98+
99+
$this->publicKeyTokenProvider->expects($this->once())
100+
->method('generateToken')
101+
->with(
102+
'token',
103+
'uid',
104+
'loginName',
105+
'password',
106+
'name',
107+
IToken::TEMPORARY_TOKEN,
108+
IToken::REMEMBER
109+
)->willThrowException($exception);
110+
$this->publicKeyTokenProvider->expects($this->once())
111+
->method('getToken')
112+
->with('token')
113+
->willReturn($token);
114+
115+
$actual = $this->manager->generateToken(
116+
'token',
117+
'uid',
118+
'loginName',
119+
'password',
120+
'name',
121+
IToken::TEMPORARY_TOKEN,
122+
IToken::REMEMBER
123+
);
124+
125+
$this->assertSame($token, $actual);
126+
}
127+
95128
public function tokenData(): array {
96129
return [
97130
[new DefaultToken()],

0 commit comments

Comments
 (0)