Issues (5)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/ComposerPlugin.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php namespace Arcanedev\Composer;
2
3
use Arcanedev\Composer\Entities\Package;
4
use Arcanedev\Composer\Entities\PluginState;
5
use Arcanedev\Composer\Exceptions\MissingFileException;
6
use Arcanedev\Composer\Utilities\Logger;
7
use Composer\Composer;
8
use Composer\DependencyResolver\Operation\InstallOperation;
9
use Composer\DependencyResolver\Request;
10
use Composer\EventDispatcher\Event as BaseEvent;
11
use Composer\EventDispatcher\EventSubscriberInterface;
12
use Composer\Factory;
13
use Composer\Installer;
14
use Composer\Installer\InstallerEvent;
15
use Composer\Installer\InstallerEvents;
16
use Composer\Installer\PackageEvent;
17
use Composer\Installer\PackageEvents;
18
use Composer\IO\IOInterface;
19
use Composer\Package\RootPackageInterface;
20
use Composer\Plugin\PluginInterface;
21
use Composer\Script\Event as ScriptEvent;
22
use Composer\Script\ScriptEvents;
23
24
/**
25
 * Class     ComposerPlugin
26
 *
27
 * @package  Arcanedev\Composer
28
 * @author   ARCANEDEV <[email protected]>
29
 */
30
class ComposerPlugin implements PluginInterface, EventSubscriberInterface
31
{
32
    /* -----------------------------------------------------------------
33
     |  Constants
34
     | -----------------------------------------------------------------
35
     */
36
37
    /**
38
     * Package name
39
     */
40
    const PACKAGE_NAME = 'arcanedev/composer';
41
42
    /**
43
     * Name of the composer 1.1 init event.
44
     */
45
    const COMPAT_PLUGINEVENTS_INIT = 'init';
46
47
    /**
48
     * Plugin key
49
     */
50
    const PLUGIN_KEY = 'merge-plugin';
51
52
    /**
53
     * Priority that plugin uses to register callbacks.
54
     */
55
    const CALLBACK_PRIORITY = 50000;
56
57
    /* -----------------------------------------------------------------
58
     |  Properties
59
     | -----------------------------------------------------------------
60
     */
61
62
    /** @var \Composer\Composer */
63
    protected $composer;
64
65
    /** @var \Arcanedev\Composer\Entities\PluginState */
66
    protected $state;
67
68
    /** @var \Arcanedev\Composer\Utilities\Logger */
69
    protected $logger;
70
71
    /**
72
     * Files that have already been fully processed.
73
     *
74
     * @var array
75
     */
76
    protected $loaded = [];
77
78
    /**
79
     * Files that have already been partially processed.
80
     *
81
     * @var array
82
     */
83
    protected $loadedNoDev = [];
84
85
    /* -----------------------------------------------------------------
86
     |  Main Methods
87
     | -----------------------------------------------------------------
88
     */
89
90
    /**
91
     * Apply plugin modifications to composer
92
     *
93
     * @param  \Composer\Composer        $composer
94
     * @param  \Composer\IO\IOInterface  $io
95
     */
96 114
    public function activate(Composer $composer, IOInterface $io)
97
    {
98 114
        $this->composer = $composer;
99 114
        $this->state    = new PluginState($composer);
100 114
        $this->logger   = new Logger('merge-plugin', $io);
101 114
}
102
103
    /**
104
     * Returns an array of event names this subscriber wants to listen to.
105
     *
106
     * @return array
107
     */
108 3
    public static function getSubscribedEvents()
109
    {
110
        return [
111
            // Use our own constant to make this event optional.
112
            // Once composer-1.1 is required, this can use PluginEvents::INIT instead.
113 3
            self::COMPAT_PLUGINEVENTS_INIT            => ['onInit', self::CALLBACK_PRIORITY],
114 3
            InstallerEvents::PRE_DEPENDENCIES_SOLVING => ['onDependencySolve', self::CALLBACK_PRIORITY],
115 3
            PackageEvents::POST_PACKAGE_INSTALL       => ['onPostPackageInstall', self::CALLBACK_PRIORITY],
116 3
            ScriptEvents::POST_INSTALL_CMD            => ['onPostInstallOrUpdate', self::CALLBACK_PRIORITY],
117 3
            ScriptEvents::POST_UPDATE_CMD             => ['onPostInstallOrUpdate', self::CALLBACK_PRIORITY],
118 3
            ScriptEvents::PRE_AUTOLOAD_DUMP           => ['onInstallUpdateOrDump', self::CALLBACK_PRIORITY],
119 3
            ScriptEvents::PRE_INSTALL_CMD             => ['onInstallUpdateOrDump', self::CALLBACK_PRIORITY],
120 3
            ScriptEvents::PRE_UPDATE_CMD              => ['onInstallUpdateOrDump', self::CALLBACK_PRIORITY],
121
        ];
122
    }
123
124
    /**
125
     * Handle an event callback for initialization.
126
     *
127
     * @param  \Composer\EventDispatcher\Event  $event
128
     */
129 21
    public function onInit(BaseEvent $event)
0 ignored issues
show
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
130
    {
131 21
        $this->state->loadSettings();
132
        // It is not possible to know if the user specified --dev or --no-dev so assume it is false.
133
        // The dev section will be merged later when the other events fire.
134 21
        $this->state->setDevMode(false);
135 21
        $this->mergeFiles($this->state->getIncludes(), false);
136 21
        $this->mergeFiles($this->state->getRequires(), true);
137 21
    }
138
139
    /**
140
     * Handle an event callback for pre-dependency solving phase of an install
141
     * or update by adding any duplicate package dependencies found during
142
     * initial merge processing to the request that will be processed by the
143
     * dependency solver.
144
     *
145
     * @param  \Composer\Installer\InstallerEvent  $event
146
     */
147 99
    public function onDependencySolve(InstallerEvent $event)
148
    {
149 99
        $request = $event->getRequest();
150
151 99
        $this->installRequires(
152 99
            $request, $this->state->getDuplicateLinks('require')
153
        );
154
155
        // Check devMode of event rather than our global state.
156
        // Composer fires the PRE_DEPENDENCIES_SOLVING event twice for `--no-dev`
157
        // operations to decide which packages are dev only requirements.
158 99
        if ($this->state->shouldMergeDev() && $event->isDevMode()) {
159 99
            $this->installRequires(
160 99
                $request, $this->state->getDuplicateLinks('require-dev'), true
161
            );
162
        }
163 99
    }
164
165
    /**
166
     * Install requirements.
167
     *
168
     * @param  \Composer\DependencyResolver\Request  $request
169
     * @param  \Composer\Package\Link[]              $links
170
     * @param  bool                                  $dev
171
     */
172 99
    private function installRequires(Request $request, array $links, $dev = false)
173
    {
174 99
        foreach ($links as $link) {
175 9
            $this->logger->info($dev
176 6
                ? "Adding dev dependency <comment>{$link}</comment>"
177 9
                : "Adding dependency <comment>{$link}</comment>"
178
            );
179 9
            $request->install($link->getTarget(), $link->getConstraint());
180
        }
181 99
    }
182
183
    /**
184
     * Handle an event callback for an install or update or dump-autoload command by checking
185
     * for "merge-patterns" in the "extra" data and merging package contents if found.
186
     *
187
     * @param  \Composer\Script\Event  $event
188
     */
189 102
    public function onInstallUpdateOrDump(ScriptEvent $event)
190
    {
191 102
        $this->state->loadSettings();
192 102
        $this->state->setDevMode($event->isDevMode());
193 102
        $this->mergeFiles($this->state->getIncludes());
194 102
        $this->mergeFiles($this->state->getRequires(), true);
195
196 99
        if ($event->getName() === ScriptEvents::PRE_AUTOLOAD_DUMP) {
197 99
            $this->state->setDumpAutoloader(true);
198 99
            $flags = $event->getFlags();
199
200 99
            if (isset($flags['optimize'])) {
201 99
                $this->state->setOptimizeAutoloader($flags['optimize']);
202
            }
203
        }
204 99
    }
205
206
    /**
207
     * Find configuration files matching the configured glob patterns and
208
     * merge their contents with the master package.
209
     *
210
     * @param  array  $patterns  List of files/glob patterns
211
     * @param  bool   $required  Are the patterns required to match files?
212
     *
213
     * @throws \Arcanedev\Composer\Exceptions\MissingFileException
214
     */
215 102
    protected function mergeFiles(array $patterns, $required = false)
216
    {
217 102
        $root  = $this->composer->getPackage();
218
        $files = array_map(function ($files, $pattern) use ($required) {
219 102
            if ($required && ! $files) {
220 3
                throw new MissingFileException(
221 3
                    "merge-plugin: No files matched required '{$pattern}'"
222
                );
223
            }
224
225 99
            return $files;
226 102
        }, array_map('glob', $patterns), $patterns);
227
228 102
        foreach (array_reduce($files, 'array_merge', []) as $path) {
229 99
            $this->mergeFile($root, $path);
230
        }
231 102
    }
232
233
    /**
234
     * Read a JSON file and merge its contents
235
     *
236
     * @param  \Composer\Package\RootPackageInterface  $root
237
     * @param  string                                  $path
238
     */
239 99
    private function mergeFile(RootPackageInterface $root, $path)
240
    {
241
        if (
242 99
            isset($this->loaded[$path]) ||
243 99
            (isset($this->loadedNoDev[$path]) && ! $this->state->isDevMode())
244
        ) {
245 99
            $this->logger->debug("Already merged <comment>$path</comment> completely");
246 99
            return;
247
        }
248
249 99
        $package = new Package($path, $this->composer, $this->logger);
250
251
        // If something was already loaded, merge just the dev section.
252 99
        if (isset($this->loadedNoDev[$path])) {
253 21
            $this->logger->info("Loading -dev sections of <comment>{$path}</comment>...");
254 21
            $package->mergeDevInto($root, $this->state);
255
        }
256
        else {
257 99
            $this->logger->info("Loading <comment>{$path}</comment>...");
258 99
            $package->mergeInto($root, $this->state);
259
        }
260
261 99
        if ($this->state->isDevMode())
262 99
            $this->loaded[$path] = true;
263
        else
264 21
            $this->loadedNoDev[$path] = true;
265
266 99
        if ($this->state->recurseIncludes()) {
267 96
            $this->mergeFiles($package->getIncludes());
268 96
            $this->mergeFiles($package->getRequires(), true);
269
        }
270 99
    }
271
272
    /**
273
     * Handle an event callback following installation of a new package by
274
     * checking to see if the package that was installed was our plugin.
275
     *
276
     * @param  \Composer\Installer\PackageEvent  $event
277
     */
278 9
    public function onPostPackageInstall(PackageEvent $event)
279
    {
280 9
        $op = $event->getOperation();
281
282 9
        if ($op instanceof InstallOperation) {
283 9
            $package = $op->getPackage()->getName();
284
285 9
            if ($package === self::PACKAGE_NAME) {
286 6
                $this->logger->debug('Composer merge-plugin installed');
287 6
                $this->state->setFirstInstall(true);
288 6
                $this->state->setLocked(
289 6
                    $event->getComposer()->getLocker()->isLocked()
290
                );
291
            }
292
        }
293 9
    }
294
295
    /**
296
     * Handle an event callback following an install or update command. If our
297
     * plugin was installed during the run then trigger an update command to
298
     * process any merge-patterns in the current config.
299
     *
300
     * @param  \Composer\Script\Event  $event
301
     *
302
     * @codeCoverageIgnore
303
     */
304
    public function onPostInstallOrUpdate(ScriptEvent $event)
305
    {
306
        if ($this->state->isFirstInstall()) {
307
            $this->state->setFirstInstall(false);
308
            $this->logger->info(
309
                '<comment>Running additional update to apply merge settings</comment>'
310
            );
311
            $this->runFirstInstall($event);
312
        }
313
    }
314
315
    /**
316
     * Run first install.
317
     *
318
     * @param  \Composer\Script\Event  $event
319
     *
320
     * @throws \Exception
321
     *
322
     * @codeCoverageIgnore
323
     */
324
    private function runFirstInstall(ScriptEvent $event)
325
    {
326
        $installer    = Installer::create(
327
            $event->getIO(),
328
            // Create a new Composer instance to ensure full processing of the merged files.
329
            Factory::create($event->getIO(), null, false)
330
        );
331
332
        $installer->setPreferSource($this->isPreferredInstall('source'));
333
        $installer->setPreferDist($this->isPreferredInstall('dist'));
334
        $installer->setDevMode($event->isDevMode());
335
        $installer->setDumpAutoloader($this->state->shouldDumpAutoloader());
336
        $installer->setOptimizeAutoloader($this->state->shouldOptimizeAutoloader());
337
338
        if ($this->state->forceUpdate()) {
339
            // Force update mode so that new packages are processed rather than just telling
340
            // the user that composer.json and composer.lock don't match.
341
            $installer->setUpdate(true);
342
        }
343
344
        $installer->run();
345
    }
346
347
    /* -----------------------------------------------------------------
348
     |  Check Methods
349
     | -----------------------------------------------------------------
350
     */
351
352
    /**
353
     * Check the preferred install (source or dist).
354
     *
355
     * @param  string  $preferred
356
     *
357
     * @return bool
358
     *
359
     * @codeCoverageIgnore
360
     */
361
    private function isPreferredInstall($preferred)
362
    {
363
        return $this->composer->getConfig()->get('preferred-install') === $preferred;
364
    }
365
}
366