From 9885d23e2a0aeee3079063059ab71e73fa7f5962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rn=20St=C3=B8ylen?= Date: Sat, 20 May 2023 11:30:39 +0200 Subject: [PATCH 1/4] Ensure stripos() receives a string If file_get_contents() returns false, stripos() will throw a TypeError. Casting to string prevents this from happening. Closes #11470 --- src/Composer/Util/Platform.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php index 2d0c89d88..3c971d105 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -148,7 +148,7 @@ class Platform if ( !ini_get('open_basedir') && is_readable('/proc/version') - && false !== stripos(Silencer::call('file_get_contents', '/proc/version'), 'microsoft') + && false !== stripos((string)Silencer::call('file_get_contents', '/proc/version'), 'microsoft') && !file_exists('/.dockerenv') // docker running inside WSL should not be seen as WSL ) { return self::$isWindowsSubsystemForLinux = true; From 9d965b9c65fa761f948abe141c7f78b2df71af1d Mon Sep 17 00:00:00 2001 From: Stefan Grootscholten Date: Tue, 23 May 2023 23:06:48 +0200 Subject: [PATCH 2/4] Fix authentication issues with private bitbucket repos (#11464) --- src/Composer/Util/AuthHelper.php | 8 +++ tests/Composer/Test/Util/AuthHelperTest.php | 65 +++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/Composer/Util/AuthHelper.php b/src/Composer/Util/AuthHelper.php index ec3476670..3f0312602 100644 --- a/src/Composer/Util/AuthHelper.php +++ b/src/Composer/Util/AuthHelper.php @@ -28,6 +28,8 @@ class AuthHelper protected $config; /** @var array Map of origins to message displayed */ private $displayedOriginAuthentications = []; + /** @var array Map of URLs and whether they already retried with authentication from Bitbucket */ + private $bitbucketRetry = []; public function __construct(IOInterface $io, Config $config) { @@ -164,6 +166,12 @@ class AuthHelper $this->io->setAuthentication($origin, 'x-token-auth', $accessToken); $askForOAuthToken = false; } + } elseif (!isset($this->bitbucketRetry[$url])) { + // when multiple requests fire at the same time, they will all fail and the first one resets the token to be correct above but then the others + // reach the code path and without this fallback they would end up throwing below + // see https://github.com/composer/composer/pull/11464 for more details + $askForOAuthToken = false; + $this->bitbucketRetry[$url] = true; } else { throw new TransportException('Could not authenticate against ' . $origin, 401); } diff --git a/tests/Composer/Test/Util/AuthHelperTest.php b/tests/Composer/Test/Util/AuthHelperTest.php index e9dd992c4..ee5802321 100644 --- a/tests/Composer/Test/Util/AuthHelperTest.php +++ b/tests/Composer/Test/Util/AuthHelperTest.php @@ -538,6 +538,71 @@ class AuthHelperTest extends TestCase $this->authHelper->promptAuthIfNeeded('https://gitlab.com/acme/archive.zip', $origin, 404, 'GitLab requires authentication and it was not provided'); } + public function testPromptAuthIfNeededMultipleBitbucketDownloads(): void + { + $origin = 'bitbucket.org'; + + $expectedResult = [ + 'retry' => true, + 'storeAuth' => false, + ]; + + $authConfig = [ + 'bitbucket.org' => [ + 'access-token' => 'bitbucket_access_token', + 'access-token-expiration' => time() + 1800, + ] + ]; + + $this->config + ->method('get') + ->willReturnMap([ + ['github-domains', 0, []], + ['gitlab-domains', 0, []], + ['bitbucket-oauth', 0, $authConfig], + ['github-domains', 0, []], + ['gitlab-domains', 0, []], + ]); + + $this->io + ->expects($this->exactly(2)) + ->method('hasAuthentication') + ->with($origin) + ->willReturn(true); + + $getAuthenticationReturnValues = [ + ['username' => 'bitbucket_client_id', 'password' => 'bitbucket_client_secret'], + ['username' => 'x-token-auth', 'password' => 'bitbucket_access_token'], + ]; + + $this->io + ->expects($this->exactly(2)) + ->method('getAuthentication') + ->willReturnCallback( + function ($repositoryName) use (&$getAuthenticationReturnValues) { + return array_shift($getAuthenticationReturnValues); + } + ); + + $this->io + ->expects($this->once()) + ->method('setAuthentication') + ->with($origin, 'x-token-auth', 'bitbucket_access_token'); + + $result1 = $this->authHelper->promptAuthIfNeeded('https://bitbucket.org/workspace/repo1/get/hash1.zip', $origin, 401, 'HTTP/2 401 '); + $result2 = $this->authHelper->promptAuthIfNeeded('https://bitbucket.org/workspace/repo2/get/hash2.zip', $origin, 401, 'HTTP/2 401 '); + + $this->assertSame( + $expectedResult, + $result1 + ); + + $this->assertSame( + $expectedResult, + $result2 + ); + } + /** * @param array $auth * From 23654389da876116242590b20eac909c5f174c9b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 23 May 2023 23:15:07 +0200 Subject: [PATCH 3/4] Fix lock file verification to take into account root provider/replacers and output mismatches there more clearly, fixes #11458 (#11475) --- src/Composer/Package/Locker.php | 18 ++++++++++++++-- ...rements-do-not-affect-locked-versions.test | 21 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index f085e5c41..e03b6bd6a 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -18,6 +18,7 @@ use Composer\Pcre\Preg; use Composer\Repository\InstalledRepository; use Composer\Repository\LockArrayRepository; use Composer\Repository\PlatformRepository; +use Composer\Repository\RootPackageRepository; use Composer\Util\ProcessExecutor; use Composer\Package\Dumper\ArrayDumper; use Composer\Package\Loader\ArrayLoader; @@ -514,9 +515,10 @@ class Locker if ($includeDev === true) { $sets[] = ['repo' => $this->getLockedRepository(true), 'method' => 'getDevRequires', 'description' => 'Required (in require-dev)']; } + $rootRepo = new RootPackageRepository($package); foreach ($sets as $set) { - $installedRepo = new InstalledRepository([$set['repo']]); + $installedRepo = new InstalledRepository([$set['repo'], $rootRepo]); foreach (call_user_func([$package, $set['method']]) as $link) { if (PlatformRepository::isPlatformPackage($link->getTarget())) { @@ -527,9 +529,21 @@ class Locker } if ($installedRepo->findPackagesWithReplacersAndProviders($link->getTarget(), $link->getConstraint()) === []) { $results = $installedRepo->findPackagesWithReplacersAndProviders($link->getTarget()); + if ($results !== []) { $provider = reset($results); - $missingRequirementInfo[] = '- ' . $set['description'].' package "' . $link->getTarget() . '" is in the lock file as "'.$provider->getPrettyVersion().'" but that does not satisfy your constraint "'.$link->getPrettyConstraint().'".'; + $description = $provider->getPrettyVersion(); + if ($provider->getName() !== $link->getTarget()) { + foreach (['getReplaces' => 'replaced as %s by %s', 'getProvides' => 'provided as %s by %s'] as $method => $text) { + foreach (call_user_func([$provider, $method]) as $providerLink) { + if ($providerLink->getTarget() === $link->getTarget()) { + $description = sprintf($text, $providerLink->getPrettyConstraint(), $provider->getPrettyName().' '.$provider->getPrettyVersion()); + break 2; + } + } + } + } + $missingRequirementInfo[] = '- ' . $set['description'].' package "' . $link->getTarget() . '" is in the lock file as "'.$description.'" but that does not satisfy your constraint "'.$link->getPrettyConstraint().'".'; } else { $missingRequirementInfo[] = '- ' . $set['description'].' package "' . $link->getTarget() . '" is not present in the lock file.'; } diff --git a/tests/Composer/Test/Fixtures/installer/root-requirements-do-not-affect-locked-versions.test b/tests/Composer/Test/Fixtures/installer/root-requirements-do-not-affect-locked-versions.test index f8db5ff03..32127b612 100644 --- a/tests/Composer/Test/Fixtures/installer/root-requirements-do-not-affect-locked-versions.test +++ b/tests/Composer/Test/Fixtures/installer/root-requirements-do-not-affect-locked-versions.test @@ -17,7 +17,15 @@ The locked version will not get overwritten by an install but fails on invalid p "require": { "foo/bar": "2.0.0", "foo/baz": "2.0.0", - "foo/self": "self.version" + "foo/self": "self.version", + "foo/provided": "1.0.0", + "foo/provided-wrong-version": "1.0.0", + "foo/root-replaced": "1.0.0", + "foo/root-replaced-wrong-version": "1.0.0" + }, + "replace": { + "foo/root-replaced": "^1", + "foo/root-replaced-wrong-version": "^2" } } --LOCK-- @@ -25,7 +33,14 @@ The locked version will not get overwritten by an install but fails on invalid p "packages": [ { "name": "foo/bar", "version": "1.0.0" }, { "name": "foo/baz", "version": "2.0.0" }, - { "name": "foo/self", "version": "1.2.2" } + { + "name": "foo/self", + "version": "1.2.2", + "provide": { + "foo/provided": "^1", + "foo/provided-wrong-version": "^3" + } + } ], "packages-dev": [], "aliases": [], @@ -45,6 +60,8 @@ install Installing dependencies from lock file (including require-dev) Verifying lock file contents can be installed on current platform. - Required package "foo/bar" is in the lock file as "1.0.0" but that does not satisfy your constraint "2.0.0". +- Required package "foo/provided-wrong-version" is in the lock file as "provided as ^3 by foo/self 1.2.2" but that does not satisfy your constraint "1.0.0". +- Required package "foo/root-replaced-wrong-version" is in the lock file as "replaced as ^2 by __root__ 1.2.3" but that does not satisfy your constraint "1.0.0". This usually happens when composer files are incorrectly merged or the composer.json file is manually edited. Read more about correctly resolving merge conflicts https://getcomposer.org/doc/articles/resolving-merge-conflicts.md and prefer using the "require" command over editing the composer.json file directly https://getcomposer.org/doc/03-cli.md#require-r From 3ab83106e087840bd465d7243f527329fb6e8d38 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 23 May 2023 23:49:14 +0200 Subject: [PATCH 4/4] Update changelog --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a48af7ba..6d75a0992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +### [2.5.6] 2023-05-24 + + * BC Warning: Installers and `InstallationManager::getInstallPath` will now return `null` instead of an empty string for metapackages' paths. This may have adverse effects on plugin code using this expecting always a string but it is unlikely (#11455) + * Fixed metapackages showing their install path as the root package's path instead of empty (#11455) + * Fixed lock file verification on `install` to deal better with `replace`/`provide` (#11475) + * Fixed lock file having a more recent modification time than the vendor dir when `require` guesses the constraint after resolution (#11405) + * Fixed numeric default branches with a `v` prefix being treated as non-numeric ones and receiving an alias like e.g. dev-main would (e51d755a08) + * Fixed binary proxies not being transparent when included by another PHP process and returning a value (#11454) + * Fixed support for plugin classes being marked as `readonly` (#11404) + * Fixed `getmypid` being required as it is not always available (#11401) + * Fixed authentication issue when downloading several files from private Bitbucket in parallel (#11464) + ### [2.5.5] 2023-03-21 * Fixed basic auth failures resulting in infinite retry loop (#11320) @@ -1708,6 +1720,7 @@ * Initial release +[2.5.6]: https://github.com/composer/composer/compare/2.5.5...2.5.6 [2.5.5]: https://github.com/composer/composer/compare/2.5.4...2.5.5 [2.5.4]: https://github.com/composer/composer/compare/2.5.3...2.5.4 [2.5.3]: https://github.com/composer/composer/compare/2.5.2...2.5.3