Skip to content

Commit e512af9

Browse files
Run session token renewals in a database transaction
The session token renewal does 1) Read the old token 2) Write a new token 3) Delete the old token If two processes succeed to read the old token there can be two new tokens because the queries were not run in a transaction. This is particularly problematic on clustered DBs where 1) would go to a read node and 2) and 3) go to a write node. Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
1 parent 905753b commit e512af9

File tree

1 file changed

+31
-20
lines changed

1 file changed

+31
-20
lines changed

lib/private/Authentication/Token/PublicKeyTokenProvider.php

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,19 @@
3434
use OC\Authentication\Exceptions\TokenPasswordExpiredException;
3535
use OC\Authentication\Exceptions\PasswordlessTokenException;
3636
use OC\Authentication\Exceptions\WipeTokenException;
37+
use OCP\AppFramework\Db\TTransactional;
3738
use OCP\Cache\CappedMemoryCache;
3839
use OCP\AppFramework\Db\DoesNotExistException;
3940
use OCP\AppFramework\Utility\ITimeFactory;
4041
use OCP\IConfig;
42+
use OCP\IDBConnection;
4143
use OCP\Security\ICrypto;
4244
use Psr\Log\LoggerInterface;
4345

4446
class PublicKeyTokenProvider implements IProvider {
47+
48+
use TTransactional;
49+
4550
/** @var PublicKeyTokenMapper */
4651
private $mapper;
4752

@@ -51,6 +56,8 @@ class PublicKeyTokenProvider implements IProvider {
5156
/** @var IConfig */
5257
private $config;
5358

59+
private IDBConnection $db;
60+
5461
/** @var LoggerInterface */
5562
private $logger;
5663

@@ -63,11 +70,13 @@ class PublicKeyTokenProvider implements IProvider {
6370
public function __construct(PublicKeyTokenMapper $mapper,
6471
ICrypto $crypto,
6572
IConfig $config,
73+
IDBConnection $db,
6674
LoggerInterface $logger,
6775
ITimeFactory $time) {
6876
$this->mapper = $mapper;
6977
$this->crypto = $crypto;
7078
$this->config = $config;
79+
$this->db = $db;
7180
$this->logger = $logger;
7281
$this->time = $time;
7382

@@ -158,31 +167,33 @@ public function getTokenById(int $tokenId): IToken {
158167
public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
159168
$this->cache->clear();
160169

161-
$token = $this->getToken($oldSessionId);
170+
$this->atomic(function() use ($oldSessionId, $sessionId) {
171+
$token = $this->getToken($oldSessionId);
162172

163-
if (!($token instanceof PublicKeyToken)) {
164-
throw new InvalidTokenException("Invalid token type");
165-
}
173+
if (!($token instanceof PublicKeyToken)) {
174+
throw new InvalidTokenException("Invalid token type");
175+
}
166176

167-
$password = null;
168-
if (!is_null($token->getPassword())) {
169-
$privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId);
170-
$password = $this->decryptPassword($token->getPassword(), $privateKey);
171-
}
177+
$password = null;
178+
if (!is_null($token->getPassword())) {
179+
$privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId);
180+
$password = $this->decryptPassword($token->getPassword(), $privateKey);
181+
}
172182

173-
$newToken = $this->generateToken(
174-
$sessionId,
175-
$token->getUID(),
176-
$token->getLoginName(),
177-
$password,
178-
$token->getName(),
179-
IToken::TEMPORARY_TOKEN,
180-
$token->getRemember()
181-
);
183+
$newToken = $this->generateToken(
184+
$sessionId,
185+
$token->getUID(),
186+
$token->getLoginName(),
187+
$password,
188+
$token->getName(),
189+
IToken::TEMPORARY_TOKEN,
190+
$token->getRemember()
191+
);
182192

183-
$this->mapper->delete($token);
193+
$this->mapper->delete($token);
184194

185-
return $newToken;
195+
return $newToken;
196+
}, $this->db);
186197
}
187198

188199
public function invalidateToken(string $token) {

0 commit comments

Comments
 (0)