diff --git a/lib/Exception/TokenRefreshLockedException.php b/lib/Exception/TokenRefreshLockedException.php new file mode 100644 index 00000000..ee4ef3c6 --- /dev/null +++ b/lib/Exception/TokenRefreshLockedException.php @@ -0,0 +1,14 @@ +getToken(); + try { + $token = $this->getToken(); + } catch (TokenRefreshLockedException) { + $this->logger->debug('[TokenService] checkLoginToken: the token refresh is locked by another process'); + return; + } if ($token === null) { $this->logger->debug('[TokenService] checkLoginToken: token is null'); // if we don't have a token but we had one once, @@ -152,11 +159,20 @@ public function reauthenticate(int $providerId) { /** * @param Token $token * @return Token - * @throws \JsonException * @throws DoesNotExistException * @throws MultipleObjectsReturnedException + * @throws TokenRefreshLockedException + * @throws \JsonException */ public function refresh(Token $token): Token { + // check lock + $sessionLocked = $this->session->get(self::REFRESH_LOCK_KEY); + if ($sessionLocked !== null) { + throw new TokenRefreshLockedException(); + } + // acquire lock + $this->session->set(self::REFRESH_LOCK_KEY, 1); + $oidcProvider = $this->providerMapper->getProvider($token->getProviderId()); $discovery = $this->discoveryService->obtainDiscovery($oidcProvider); @@ -188,13 +204,16 @@ public function refresh(Token $token): Token { $bodyArray = json_decode(trim($body), true, 512, JSON_THROW_ON_ERROR); $this->logger->debug('[TokenService] ---- Refresh token success'); - return $this->storeToken( + $refreshedToken = $this->storeToken( array_merge( $bodyArray, ['provider_id' => $token->getProviderId()], ) ); + $this->session->remove(self::REFRESH_LOCK_KEY); + return $refreshedToken; } catch (\Exception $e) { + $this->session->remove(self::REFRESH_LOCK_KEY); $this->logger->error('[TokenService] Failed to refresh token ', ['exception' => $e]); // Failed to refresh, return old token which will be retried or otherwise timeout if expired return $token; @@ -229,7 +248,12 @@ public function getExchangedToken(string $targetAudience, array $extraScopes = [ } $this->logger->debug('[TokenService] Starting token exchange'); - $loginToken = $this->getToken(); + try { + $loginToken = $this->getToken(); + } catch (TokenRefreshLockedException $e) { + $this->logger->error('[TokenService] Failed to exchange token. The login token refresh failed because it was locked'); + throw new TokenExchangeFailedException('Failed to exchange token. The login token refresh failed because it was locked', 0, $e); + } if ($loginToken === null) { $this->logger->debug('[TokenService] Failed to exchange token, no login token found in the session'); throw new TokenExchangeFailedException('Failed to exchange token, no login token found in the session');