From aeb204bb1dd284127cc3b18f0b237eb88063dacc Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 11 May 2022 13:06:59 +0200 Subject: [PATCH] Fix race condition where multiple http requests requiring auth end up failing, fixes #10763 --- src/Composer/Util/AuthHelper.php | 10 +++++++++- src/Composer/Util/Http/CurlDownloader.php | 4 ++-- src/Composer/Util/RemoteFilesystem.php | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Composer/Util/AuthHelper.php b/src/Composer/Util/AuthHelper.php index c208a1018..305746f17 100644 --- a/src/Composer/Util/AuthHelper.php +++ b/src/Composer/Util/AuthHelper.php @@ -79,11 +79,12 @@ class AuthHelper * @param int $statusCode HTTP status code that triggered this call * @param string|null $reason a message/description explaining why this was called * @param string[] $headers + * @param int $retryCount the amount of retries already done on this URL * @return array|null containing retry (bool) and storeAuth (string|bool) keys, if retry is true the request should be * retried, if storeAuth is true then on a successful retry the authentication should be persisted to auth.json * @phpstan-return ?array{retry: bool, storeAuth: string|bool} */ - public function promptAuthIfNeeded($url, $origin, $statusCode, $reason = null, $headers = array()) + public function promptAuthIfNeeded($url, $origin, $statusCode, $reason = null, $headers = array(), $retryCount = 0) { $storeAuth = false; @@ -200,8 +201,15 @@ class AuthHelper throw new TransportException($message, $statusCode); } + // fail if we already have auth if ($this->io->hasAuthentication($origin)) { + // if two or more requests are started together for the same host, and the first + // received authentication already, we let the others retry before failing them + if ($retryCount === 0) { + return array('retry' => true, 'storeAuth' => false); + } + throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode); } diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 3cbdf4079..ed85b2c83 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -528,7 +528,7 @@ class CurlDownloader private function isAuthenticatedRetryNeeded(array $job, Response $response) { if (in_array($response->getStatusCode(), array(401, 403)) && $job['attributes']['retryAuthFailure']) { - $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], $response->getStatusCode(), $response->getStatusMessage(), $response->getHeaders()); + $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], $response->getStatusCode(), $response->getStatusMessage(), $response->getHeaders(), $job['attributes']['retries']); if ($result['retry']) { return $result; @@ -560,7 +560,7 @@ class CurlDownloader if ($needsAuthRetry) { if ($job['attributes']['retryAuthFailure']) { - $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], 401); + $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], 401, null, array(), $job['attributes']['retries']); if ($result['retry']) { return $result; } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index f35964975..fb27a34e3 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -645,7 +645,7 @@ class RemoteFilesystem */ protected function promptAuthAndRetry($httpStatus, $reason = null, $headers = array()) { - $result = $this->authHelper->promptAuthIfNeeded($this->fileUrl, $this->originUrl, $httpStatus, $reason, $headers); + $result = $this->authHelper->promptAuthIfNeeded($this->fileUrl, $this->originUrl, $httpStatus, $reason, $headers, 1 /** always pass 1 as RemoteFilesystem is single threaded there is no race condition possible */); $this->storeAuth = $result['storeAuth']; $this->retry = $result['retry'];