9
9
10
10
use Composer \Composer ;
11
11
use Composer \DependencyResolver \Operation \OperationInterface ;
12
+ use Composer \DependencyResolver \Request ;
12
13
use Composer \EventDispatcher \EventSubscriberInterface ;
13
14
use Composer \Installer ;
14
15
use Composer \Installer \PackageEvent ;
15
16
use Composer \IO \IOInterface ;
17
+ use Composer \Plugin \PluginEvents ;
16
18
use Composer \Plugin \PluginInterface ;
19
+ use Composer \Plugin \PrePoolCreateEvent ;
17
20
use Composer \Repository \ComposerRepository ;
21
+ use Composer \Repository \FilterRepository ;
18
22
use Composer \Repository \RepositoryInterface ;
19
23
use Composer \Package \PackageInterface ;
20
24
use Exception ;
@@ -41,6 +45,11 @@ class Plugin implements PluginInterface, EventSubscriberInterface
41
45
*/
42
46
private $ versionSelector ;
43
47
48
+ /**
49
+ * @var array
50
+ */
51
+ private $ nonFixedPackages ;
52
+
44
53
/**#@+
45
54
* Constant for VBE ALLOW LIST
46
55
*/
@@ -99,10 +108,81 @@ public function uninstall(Composer $composer, IOInterface $io)
99
108
*/
100
109
public static function getSubscribedEvents (): array
101
110
{
102
- return [
111
+ $ events = [
103
112
Installer \PackageEvents::PRE_PACKAGE_INSTALL => 'packageUpdate ' ,
104
113
Installer \PackageEvents::PRE_PACKAGE_UPDATE => 'packageUpdate '
105
114
];
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
+ }
106
186
}
107
187
108
188
/**
@@ -130,34 +210,44 @@ public function packageUpdate(PackageEvent $event): void
130
210
list ($ namespace , $ project ) = explode ("/ " , $ packageName );
131
211
$ isPackageVBE = in_array ($ namespace , self ::VBE_ALLOW_LIST , true );
132
212
133
- if (!$ isPackageVBE ) {
213
+ if ((int )explode ('. ' , Composer::VERSION )[0 ] === 1 ) {
214
+ $ this ->getNonFixedConstraintList ($ event ->getRequest ());
215
+ }
134
216
217
+ if (!$ isPackageVBE ) {
135
218
foreach ($ this ->composer ->getRepositoryManager ()->getRepositories () as $ repository ) {
136
-
219
+ $ found = $ this -> versionSelector -> findBestCandidate ( $ this -> composer , $ packageName , $ repository );
137
220
/** @var RepositoryInterface $repository */
138
221
if ($ repository instanceof ComposerRepository) {
139
- $ found = $ this ->versionSelector ->findBestCandidate ($ this ->composer , $ packageName , $ repository );
140
222
$ repoUrl = $ repository ->getRepoConfig ()['url ' ];
141
223
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 ;
152
236
}
153
237
}
154
238
}
155
239
}
156
- if ($ privateRepoVersion && $ publicRepoVersion && (version_compare ($ publicRepoVersion , $ privateRepoVersion , '> ' ))) {
240
+
241
+ if ($ privateRepoVersion && $ publicRepoVersion && version_compare ($ publicRepoVersion , $ privateRepoVersion , '> ' )) {
157
242
$ exceptionMessage = "Higher matching version {$ publicRepoVersion } of {$ packageName } was found in public repository packagist.org
158
243
than {$ privateRepoVersion } in private {$ privateRepoUrl }. Public package might've been taken over by a malicious entity,
159
244
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
+ }
161
251
}
162
252
}
163
253
}
0 commit comments