diff options
Diffstat (limited to 'lib/private/Authentication/Token/PublicKeyTokenProvider.php')
-rw-r--r-- | lib/private/Authentication/Token/PublicKeyTokenProvider.php | 131 |
1 files changed, 73 insertions, 58 deletions
diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index 490f6830a85..6fd2bebc195 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -38,7 +38,8 @@ use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\TTransactional; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Authentication\Token\IToken as OCPIToken; -use OCP\Cache\CappedMemoryCache; +use OCP\ICache; +use OCP\ICacheFactory; use OCP\IConfig; use OCP\IDBConnection; use OCP\IUserManager; @@ -48,6 +49,8 @@ use Psr\Log\LoggerInterface; class PublicKeyTokenProvider implements IProvider { public const TOKEN_MIN_LENGTH = 22; + /** Token cache TTL in seconds */ + private const TOKEN_CACHE_TTL = 10; use TTransactional; @@ -68,10 +71,11 @@ class PublicKeyTokenProvider implements IProvider { /** @var ITimeFactory */ private $time; - /** @var CappedMemoryCache */ + /** @var ICache */ private $cache; - private IHasher $hasher; + /** @var IHasher */ + private $hasher; public function __construct(PublicKeyTokenMapper $mapper, ICrypto $crypto, @@ -79,7 +83,8 @@ class PublicKeyTokenProvider implements IProvider { IDBConnection $db, LoggerInterface $logger, ITimeFactory $time, - IHasher $hasher) { + IHasher $hasher, + ICacheFactory $cacheFactory) { $this->mapper = $mapper; $this->crypto = $crypto; $this->config = $config; @@ -87,7 +92,9 @@ class PublicKeyTokenProvider implements IProvider { $this->logger = $logger; $this->time = $time; - $this->cache = new CappedMemoryCache(); + $this->cache = $cacheFactory->isLocalCacheAvailable() + ? $cacheFactory->createLocal('authtoken_') + : $cacheFactory->createInMemory(); $this->hasher = $hasher; } @@ -129,7 +136,7 @@ class PublicKeyTokenProvider implements IProvider { } // Add the token to the cache - $this->cache[$dbToken->getToken()] = $dbToken; + $this->cacheToken($dbToken); return $dbToken; } @@ -157,43 +164,56 @@ class PublicKeyTokenProvider implements IProvider { } $tokenHash = $this->hashToken($tokenId); + if ($token = $this->getTokenFromCache($tokenHash)) { + $this->checkToken($token); + return $token; + } - if (isset($this->cache[$tokenHash])) { - if ($this->cache[$tokenHash] instanceof DoesNotExistException) { - $ex = $this->cache[$tokenHash]; - throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex); - } - $token = $this->cache[$tokenHash]; - } else { + try { + $token = $this->mapper->getToken($tokenHash); + $this->cacheToken($token); + } catch (DoesNotExistException $ex) { try { - $token = $this->mapper->getToken($tokenHash); - $this->cache[$token->getToken()] = $token; - } catch (DoesNotExistException $ex) { - try { - $token = $this->mapper->getToken($this->hashTokenWithEmptySecret($tokenId)); - $this->cache[$token->getToken()] = $token; - $this->rotate($token, $tokenId, $tokenId); - } catch (DoesNotExistException $ex2) { - $this->cache[$tokenHash] = $ex2; - throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex); - } + $token = $this->mapper->getToken($this->hashTokenWithEmptySecret($tokenId)); + $this->rotate($token, $tokenId, $tokenId); + } catch (DoesNotExistException) { + $this->cacheInvalidHash($tokenHash); + throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex); } } - if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) { - throw new ExpiredTokenException($token); - } + $this->checkToken($token); - if ($token->getType() === OCPIToken::WIPE_TOKEN) { - throw new WipeTokenException($token); - } + return $token; + } - if ($token->getPasswordInvalid() === true) { - //The password is invalid we should throw an TokenPasswordExpiredException - throw new TokenPasswordExpiredException($token); + /** + * @throws InvalidTokenException when token doesn't exist + */ + private function getTokenFromCache(string $tokenHash): ?PublicKeyToken { + $serializedToken = $this->cache->get($tokenHash); + if (null === $serializedToken) { + if ($this->cache->hasKey($tokenHash)) { + throw new InvalidTokenException('Token does not exist: ' . $tokenHash); + } + + return null; } - return $token; + $token = unserialize($serializedToken, [ + 'allowed_classes' => [PublicKeyToken::class], + ]); + + return $token instanceof PublicKeyToken ? $token : null; + } + + private function cacheToken(PublicKeyToken $token): void { + $this->cache->set($token->getToken(), serialize($token), self::TOKEN_CACHE_TTL); + } + + private function cacheInvalidHash(string $tokenHash) { + // Invalid entries can be kept longer in cache since it’s unlikely to reuse them + $this->cache->set($tokenHash, null, self::TOKEN_CACHE_TTL * 2); } public function getTokenById(int $tokenId): OCPIToken { @@ -203,6 +223,12 @@ class PublicKeyTokenProvider implements IProvider { throw new InvalidTokenException("Token with ID $tokenId does not exist: " . $ex->getMessage(), 0, $ex); } + $this->checkToken($token); + + return $token; + } + + private function checkToken($token): void { if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) { throw new ExpiredTokenException($token); } @@ -215,13 +241,9 @@ class PublicKeyTokenProvider implements IProvider { //The password is invalid we should throw an TokenPasswordExpiredException throw new TokenPasswordExpiredException($token); } - - return $token; } public function renewSessionToken(string $oldSessionId, string $sessionId): OCPIToken { - $this->cache->clear(); - return $this->atomic(function () use ($oldSessionId, $sessionId) { $token = $this->getToken($oldSessionId); @@ -243,7 +265,9 @@ class PublicKeyTokenProvider implements IProvider { OCPIToken::TEMPORARY_TOKEN, $token->getRemember() ); + $this->cacheToken($newToken); + $this->cacheInvalidHash($token->getToken()); $this->mapper->delete($token); return $newToken; @@ -251,21 +275,23 @@ class PublicKeyTokenProvider implements IProvider { } public function invalidateToken(string $token) { - $this->cache->clear(); - + $tokenHash = $this->hashToken($token); $this->mapper->invalidate($this->hashToken($token)); $this->mapper->invalidate($this->hashTokenWithEmptySecret($token)); + $this->cacheInvalidHash($tokenHash); } public function invalidateTokenById(string $uid, int $id) { - $this->cache->clear(); + $token = $this->mapper->getTokenById($id); + if ($token->getUID() !== $uid) { + return; + } + $this->mapper->invalidate($token->getToken()); + $this->cacheInvalidHash($token->getToken()); - $this->mapper->deleteById($uid, $id); } public function invalidateOldTokens() { - $this->cache->clear(); - $olderThan = $this->time->getTime() - $this->config->getSystemValueInt('session_lifetime', 60 * 60 * 24); $this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']); $this->mapper->invalidateOld($olderThan, OCPIToken::DO_NOT_REMEMBER); @@ -275,23 +301,18 @@ class PublicKeyTokenProvider implements IProvider { } public function invalidateLastUsedBefore(string $uid, int $before): void { - $this->cache->clear(); - $this->mapper->invalidateLastUsedBefore($uid, $before); } public function updateToken(OCPIToken $token) { - $this->cache->clear(); - if (!($token instanceof PublicKeyToken)) { throw new InvalidTokenException("Invalid token type"); } $this->mapper->update($token); + $this->cacheToken($token); } public function updateTokenActivity(OCPIToken $token) { - $this->cache->clear(); - if (!($token instanceof PublicKeyToken)) { throw new InvalidTokenException("Invalid token type"); } @@ -304,6 +325,7 @@ class PublicKeyTokenProvider implements IProvider { if ($token->getLastActivity() < ($now - $activityInterval)) { $token->setLastActivity($now); $this->mapper->updateActivity($token, $now); + $this->cacheToken($token); } } @@ -328,8 +350,6 @@ class PublicKeyTokenProvider implements IProvider { } public function setPassword(OCPIToken $token, string $tokenId, string $password) { - $this->cache->clear(); - if (!($token instanceof PublicKeyToken)) { throw new InvalidTokenException("Invalid token type"); } @@ -355,8 +375,6 @@ class PublicKeyTokenProvider implements IProvider { } public function rotate(OCPIToken $token, string $oldTokenId, string $newTokenId): OCPIToken { - $this->cache->clear(); - if (!($token instanceof PublicKeyToken)) { throw new InvalidTokenException("Invalid token type"); } @@ -480,19 +498,16 @@ class PublicKeyTokenProvider implements IProvider { } public function markPasswordInvalid(OCPIToken $token, string $tokenId) { - $this->cache->clear(); - if (!($token instanceof PublicKeyToken)) { throw new InvalidTokenException("Invalid token type"); } $token->setPasswordInvalid(true); $this->mapper->update($token); + $this->cacheToken($token); } public function updatePasswords(string $uid, string $password) { - $this->cache->clear(); - // prevent setting an empty pw as result of pw-less-login if ($password === '' || !$this->config->getSystemValueBool('auth.storeCryptedPassword', true)) { return; |