Skip to content

Commit 48072fd

Browse files
authored
Merge pull request #6 from magento-cia/AC-970
AC-970: Adjusting dependency confusion behavior
2 parents d4c827a + da95026 commit 48072fd

File tree

4 files changed

+299
-25
lines changed

4 files changed

+299
-25
lines changed

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "magento/composer-dependency-version-audit-plugin",
33
"type": "composer-plugin",
44
"description": "Validating packages through a composer plugin",
5-
"version": "0.1.2",
5+
"version": "0.1.3",
66
"license": [
77
"OSL-3.0"
88
],

src/Plugin.php

+106-16
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@
99

1010
use Composer\Composer;
1111
use Composer\DependencyResolver\Operation\OperationInterface;
12+
use Composer\DependencyResolver\Request;
1213
use Composer\EventDispatcher\EventSubscriberInterface;
1314
use Composer\Installer;
1415
use Composer\Installer\PackageEvent;
1516
use Composer\IO\IOInterface;
17+
use Composer\Plugin\PluginEvents;
1618
use Composer\Plugin\PluginInterface;
19+
use Composer\Plugin\PrePoolCreateEvent;
1720
use Composer\Repository\ComposerRepository;
21+
use Composer\Repository\FilterRepository;
1822
use Composer\Repository\RepositoryInterface;
1923
use Composer\Package\PackageInterface;
2024
use Exception;
@@ -41,6 +45,11 @@ class Plugin implements PluginInterface, EventSubscriberInterface
4145
*/
4246
private $versionSelector;
4347

48+
/**
49+
* @var array
50+
*/
51+
private $nonFixedPackages;
52+
4453
/**#@+
4554
* Constant for VBE ALLOW LIST
4655
*/
@@ -99,10 +108,81 @@ public function uninstall(Composer $composer, IOInterface $io)
99108
*/
100109
public static function getSubscribedEvents(): array
101110
{
102-
return [
111+
$events = [
103112
Installer\PackageEvents::PRE_PACKAGE_INSTALL => 'packageUpdate',
104113
Installer\PackageEvents::PRE_PACKAGE_UPDATE => 'packageUpdate'
105114
];
115+
116+
if ((int)explode('.', Composer::VERSION)[0] === 2) {
117+
$events[PluginEvents::PRE_POOL_CREATE] = 'prePoolCreate';
118+
}
119+
120+
return $events;
121+
}
122+
123+
/**
124+
* Get all package installations that use non-fixed version constraints (IE: 2.4.*, ^2.4, etc.)
125+
* this needs to be done for Composer V1 installs since prePoolCreate event doesn't exist in V1
126+
*
127+
* @param Request $request
128+
* @return array
129+
*/
130+
private function getNonFixedConstraintList(Request $request): array
131+
{
132+
if (!$this->nonFixedPackages) {
133+
$constraintList = [];
134+
foreach ($request->getJobs() as $job) {
135+
if ($job['cmd'] === 'install' &&
136+
(strpbrk($job['constraint']->getPrettyString(), "*^-~") ||
137+
preg_match('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $job['constraint']->getPrettyString()
138+
)
139+
)
140+
) {
141+
$constraintList[$job['packageName']] = true;
142+
}
143+
}
144+
$this->nonFixedPackages = $constraintList;
145+
}
146+
return $this->nonFixedPackages;
147+
}
148+
149+
/**
150+
* Event listener for PrePoolCreate event that is used for composer V2
151+
*
152+
* @param PrePoolCreateEvent $event
153+
*/
154+
public function prePoolCreate(PrePoolCreateEvent $event): void
155+
{
156+
if (!$this->nonFixedPackages) {
157+
$constraintList = [];
158+
159+
/**
160+
* get all packages that are in the composer.json under require section, this will be the only time
161+
* we will be able to get constraints for packages in the require section as this request data isn't
162+
* shared in the installer event on composer v2
163+
*/
164+
foreach ($event->getRequest()->getRequires() as $name => $constraint) {
165+
$prettyString = $constraint->getPrettyString();
166+
$multiConstraint = preg_match('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $prettyString);
167+
if (strpbrk($prettyString, "*^-~") || $multiConstraint){
168+
$constraintList[$name] = true;
169+
}
170+
}
171+
172+
/**
173+
* get all sub packages that are now requirements for new packages to install and store their constraints.
174+
*/
175+
foreach ($event->getPackages() as $package) {
176+
foreach ($package->getRequires() as $name => $constraint) {
177+
$prettyConstraint = $constraint->getPrettyConstraint();
178+
$multiConstraint = preg_match('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $prettyConstraint);
179+
if (strpbrk($prettyConstraint, "*^-~")|| $multiConstraint)
180+
$constraintList[$name] = true;
181+
}
182+
}
183+
184+
$this->nonFixedPackages = $constraintList;
185+
}
106186
}
107187

108188
/**
@@ -130,34 +210,44 @@ public function packageUpdate(PackageEvent $event): void
130210
list($namespace, $project) = explode("/", $packageName);
131211
$isPackageVBE = in_array($namespace, self::VBE_ALLOW_LIST, true);
132212

133-
if(!$isPackageVBE) {
213+
if ((int)explode('.', Composer::VERSION)[0] === 1) {
214+
$this->getNonFixedConstraintList($event->getRequest());
215+
}
134216

217+
if(!$isPackageVBE) {
135218
foreach ($this->composer->getRepositoryManager()->getRepositories() as $repository) {
136-
219+
$found = $this->versionSelector->findBestCandidate($this->composer, $packageName, $repository);
137220
/** @var RepositoryInterface $repository */
138221
if ($repository instanceof ComposerRepository) {
139-
$found = $this->versionSelector->findBestCandidate($this->composer, $packageName, $repository);
140222
$repoUrl = $repository->getRepoConfig()['url'];
141223

142-
if ($found) {
143-
if (strpos($repoUrl, self::URL_REPO_PACKAGIST) !== false) {
144-
$publicRepoVersion = $found->getFullPrettyVersion();
145-
} else {
146-
$currentPrivateRepoVersion = $found->getFullPrettyVersion();
147-
//private repo version should hold highest version of package
148-
if (empty($privateRepoVersion) || version_compare($currentPrivateRepoVersion, $privateRepoVersion, '>')) {
149-
$privateRepoVersion = $currentPrivateRepoVersion;
150-
$privateRepoUrl = $repoUrl;
151-
}
224+
} else if ($repository instanceof FilterRepository) {
225+
$repoUrl = $repository->getRepository()->getRepoConfig()['url'];
226+
}
227+
if ($found) {
228+
if (strpos($repoUrl, self::URL_REPO_PACKAGIST) !== false) {
229+
$publicRepoVersion = $found->getFullPrettyVersion();
230+
} else {
231+
$currentPrivateRepoVersion = $found->getFullPrettyVersion();
232+
//private repo version should hold highest version of package
233+
if (empty($privateRepoVersion) || version_compare($currentPrivateRepoVersion, $privateRepoVersion, '>')) {
234+
$privateRepoVersion = $currentPrivateRepoVersion;
235+
$privateRepoUrl = $repoUrl;
152236
}
153237
}
154238
}
155239
}
156-
if ($privateRepoVersion && $publicRepoVersion && (version_compare($publicRepoVersion, $privateRepoVersion, '>'))) {
240+
241+
if ($privateRepoVersion && $publicRepoVersion && version_compare($publicRepoVersion, $privateRepoVersion, '>')) {
157242
$exceptionMessage = "Higher matching version {$publicRepoVersion} of {$packageName} was found in public repository packagist.org
158243
than {$privateRepoVersion} in private {$privateRepoUrl}. Public package might've been taken over by a malicious entity,
159244
please investigate and update package requirement to match the version from the private repository";
160-
throw new Exception($exceptionMessage);
245+
246+
if ($this->nonFixedPackages && array_key_exists($packageName, $this->nonFixedPackages)) {
247+
throw new Exception($exceptionMessage);
248+
} else {
249+
$event->getIO()->writeError('<warning>' . $exceptionMessage . '</warning>');
250+
}
161251
}
162252
}
163253
}

src/Utils/Version.php

-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ public function findBestCandidate(Composer $composer, string $packageName, Repos
3838
$bestCandidate = $this->findBestCandidateComposer1($composer, $packageName, $repository);
3939
} elseif ($composerMajorVersion === 2) {
4040
$bestCandidate = $this->findBestCandidateComposer2($composer, $packageName, $repository);
41-
} else {
42-
throw new Exception("Unrecognized Composer Version");
4341
}
4442

4543
if($bestCandidate instanceof PackageInterface){

0 commit comments

Comments
 (0)