Issues (1)

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/MergePlugin.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
2
/**
3
 * This file is part of the Composer Merge plugin.
4
 *
5
 * Copyright (C) 2015 Bryan Davis, Wikimedia Foundation, and contributors
6
 *
7
 * This software may be modified and distributed under the terms of the MIT
8
 * license. See the LICENSE file for details.
9
 */
10
11
namespace Wikimedia\Composer;
12
13
use Wikimedia\Composer\Merge\ExtraPackage;
14
use Wikimedia\Composer\Merge\MissingFileException;
15
use Wikimedia\Composer\Merge\PluginState;
16
17
use Composer\Composer;
18
use Composer\DependencyResolver\Operation\InstallOperation;
19
use Composer\EventDispatcher\Event as BaseEvent;
20
use Composer\EventDispatcher\EventSubscriberInterface;
21
use Composer\Factory;
22
use Composer\Installer;
23
use Composer\Installer\InstallerEvent;
24
use Composer\Installer\InstallerEvents;
25
use Composer\Installer\PackageEvent;
26
use Composer\Installer\PackageEvents;
27
use Composer\IO\IOInterface;
28
use Composer\Package\RootPackageInterface;
29
use Composer\Plugin\PluginInterface;
30
use Composer\Script\Event as ScriptEvent;
31
use Composer\Script\ScriptEvents;
32
33
/**
34
 * Composer plugin that allows merging multiple composer.json files.
35
 *
36
 * When installed, this plugin will look for a "merge-plugin" key in the
37
 * composer configuration's "extra" section. The value for this key is
38
 * a set of options configuring the plugin.
39
 *
40
 * An "include" setting is required. The value of this setting can be either
41
 * a single value or an array of values. Each value is treated as a glob()
42
 * pattern identifying additional composer.json style configuration files to
43
 * merge into the configuration for the current compser execution.
44
 *
45
 * The "autoload", "autoload-dev", "conflict", "provide", "replace",
46
 * "repositories", "require", "require-dev", and "suggest" sections of the
47
 * found configuration files will be merged into the root package
48
 * configuration as though they were directly included in the top-level
49
 * composer.json file.
50
 *
51
 * If included files specify conflicting package versions for "require" or
52
 * "require-dev", the normal Composer dependency solver process will be used
53
 * to attempt to resolve the conflict. Specifying the 'replace' key as true will
54
 * change this default behaviour so that the last-defined version of a package
55
 * will win, allowing for force-overrides of package defines.
56
 *
57
 * By default the "extra" section is not merged. This can be enabled by
58
 * setitng the 'merge-extra' key to true. In normal mode, when the same key is
59
 * found in both the original and the imported extra section, the version in
60
 * the original config is used and the imported version is skipped. If
61
 * 'replace' mode is active, this behaviour changes so the imported version of
62
 * the key is used, replacing the version in the original config.
63
 *
64
 *
65
 * @code
66
 * {
67
 *     "require": {
68
 *         "wikimedia/composer-merge-plugin": "dev-master"
69
 *     },
70
 *     "extra": {
71
 *         "merge-plugin": {
72
 *             "include": [
73
 *                 "composer.local.json"
74
 *             ]
75
 *         }
76
 *     }
77
 * }
78
 * @endcode
79
 *
80
 * @author Bryan Davis <[email protected]>
81
 */
82
class MergePlugin implements PluginInterface, EventSubscriberInterface
83
{
84
85
    /**
86
     * Offical package name
87
     */
88
    const PACKAGE_NAME = 'wikimedia/composer-merge-plugin';
89
90
    /**
91
     * Name of the composer 1.1 init event.
92
     */
93
    const COMPAT_PLUGINEVENTS_INIT = 'init';
94
95
    /**
96
     * Priority that plugin uses to register callbacks.
97
     */
98
    const CALLBACK_PRIORITY = 50000;
99
100
    /**
101
     * @var Composer $composer
102
     */
103
    protected $composer;
104
105
    /**
106
     * @var PluginState $state
107
     */
108
    protected $state;
109
110
    /**
111
     * @var Logger $logger
112
     */
113
    protected $logger;
114
115
    /**
116
     * Files that have already been fully processed
117
     *
118
     * @var string[] $loaded
119
     */
120
    protected $loaded = array();
121
122
    /**
123
     * Files that have already been partially processed
124
     *
125
     * @var string[] $loadedNoDev
126
     */
127
    protected $loadedNoDev = array();
128
129
    /**
130
     * {@inheritdoc}
131
     */
132 195
    public function activate(Composer $composer, IOInterface $io)
133
    {
134 195
        $this->composer = $composer;
135 195
        $this->state = new PluginState($this->composer);
136 195
        $this->logger = new Logger('merge-plugin', $io);
137 195
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 5
    public static function getSubscribedEvents()
143
    {
144
        return array(
145
            // Use our own constant to make this event optional. Once
146
            // composer-1.1 is required, this can use PluginEvents::INIT
147
            // instead.
148 5
            self::COMPAT_PLUGINEVENTS_INIT =>
149 5
                array('onInit', self::CALLBACK_PRIORITY),
150 5
            InstallerEvents::PRE_DEPENDENCIES_SOLVING =>
151 5
                array('onDependencySolve', self::CALLBACK_PRIORITY),
152 5
            PackageEvents::POST_PACKAGE_INSTALL =>
153 5
                array('onPostPackageInstall', self::CALLBACK_PRIORITY),
154 5
            ScriptEvents::POST_INSTALL_CMD =>
155 5
                array('onPostInstallOrUpdate', self::CALLBACK_PRIORITY),
156 5
            ScriptEvents::POST_UPDATE_CMD =>
157 5
                array('onPostInstallOrUpdate', self::CALLBACK_PRIORITY),
158 5
            ScriptEvents::PRE_AUTOLOAD_DUMP =>
159 5
                array('onInstallUpdateOrDump', self::CALLBACK_PRIORITY),
160 5
            ScriptEvents::PRE_INSTALL_CMD =>
161 5
                array('onInstallUpdateOrDump', self::CALLBACK_PRIORITY),
162 5
            ScriptEvents::PRE_UPDATE_CMD =>
163 5
                array('onInstallUpdateOrDump', self::CALLBACK_PRIORITY),
164 1
        );
165
    }
166
167
    /**
168
     * Handle an event callback for initialization.
169
     *
170
     * @param \Composer\EventDispatcher\Event $event
171
     */
172 70
    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...
173
    {
174 70
        $this->state->loadSettings();
175
        // It is not possible to know if the user specified --dev or --no-dev
176
        // so assume it is false. The dev section will be merged later when
177
        // the other events fire.
178 70
        $this->state->setDevMode(false);
179 70
        $this->mergeFiles($this->state->getIncludes(), false);
180 70
        $this->mergeFiles($this->state->getRequires(), true);
181 70
    }
182
183
    /**
184
     * Handle an event callback for an install, update or dump command by
185
     * checking for "merge-plugin" in the "extra" data and merging package
186
     * contents if found.
187
     *
188
     * @param ScriptEvent $event
189
     */
190 175
    public function onInstallUpdateOrDump(ScriptEvent $event)
191
    {
192 175
        $this->state->loadSettings();
193 175
        $this->state->setDevMode($event->isDevMode());
194 175
        $this->mergeFiles($this->state->getIncludes(), false);
195 175
        $this->mergeFiles($this->state->getRequires(), true);
196
197 170
        if ($event->getName() === ScriptEvents::PRE_AUTOLOAD_DUMP) {
198 170
            $this->state->setDumpAutoloader(true);
199 170
            $flags = $event->getFlags();
200 170
            if (isset($flags['optimize'])) {
201 170
                $this->state->setOptimizeAutoloader($flags['optimize']);
202 34
            }
203 34
        }
204 170
    }
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
     * @throws MissingFileException when required and a pattern returns no
213
     *      results
214
     */
215 175
    protected function mergeFiles(array $patterns, $required = false)
216
    {
217 175
        $root = $this->composer->getPackage();
218
219 175
        $files = array_map(
220 175
            function ($files, $pattern) use ($required) {
221 175
                if ($required && !$files) {
222 5
                    throw new MissingFileException(
223 5
                        "merge-plugin: No files matched required '{$pattern}'"
224 1
                    );
225
                }
226 170
                return $files;
227 175
            },
228 175
            array_map('glob', $patterns),
229 70
            $patterns
230 35
        );
231
232 175
        foreach (array_reduce($files, 'array_merge', array()) as $path) {
233 170
            $this->mergeFile($root, $path);
234 35
        }
235 175
    }
236
237
    /**
238
     * Read a JSON file and merge its contents
239
     *
240
     * @param RootPackageInterface $root
241
     * @param string $path
242
     */
243 170
    protected function mergeFile(RootPackageInterface $root, $path)
244
    {
245 170
        if (isset($this->loaded[$path]) ||
246 170
            (isset($this->loadedNoDev[$path]) && !$this->state->isDevMode())
247 34
        ) {
248 170
            $this->logger->debug(
249 170
                "Already merged <comment>$path</comment> completely"
250 34
            );
251 170
            return;
252
        }
253
254 170
        $package = new ExtraPackage($path, $this->composer, $this->logger);
255
256 170
        if (isset($this->loadedNoDev[$path])) {
257 70
            $this->logger->info(
258 70
                "Loading -dev sections of <comment>{$path}</comment>..."
259 14
            );
260 70
            $package->mergeDevInto($root, $this->state);
261 14
        } else {
262 170
            $this->logger->info("Loading <comment>{$path}</comment>...");
263 170
            $package->mergeInto($root, $this->state);
264
        }
265
266 170
        if ($this->state->isDevMode()) {
267 165
            $this->loaded[$path] = true;
268 33
        } else {
269 75
            $this->loadedNoDev[$path] = true;
270
        }
271
272 170
        if ($this->state->recurseIncludes()) {
273 165
            $this->mergeFiles($package->getIncludes(), false);
274 165
            $this->mergeFiles($package->getRequires(), true);
275 33
        }
276 170
    }
277
278
    /**
279
     * Handle an event callback for pre-dependency solving phase of an install
280
     * or update by adding any duplicate package dependencies found during
281
     * initial merge processing to the request that will be processed by the
282
     * dependency solver.
283
     *
284
     * @param InstallerEvent $event
285
     */
286 170
    public function onDependencySolve(InstallerEvent $event)
287
    {
288 170
        $request = $event->getRequest();
289 170
        foreach ($this->state->getDuplicateLinks('require') as $link) {
290 25
            $this->logger->info(
291 25
                "Adding dependency <comment>{$link}</comment>"
292 5
            );
293 25
            $request->install($link->getTarget(), $link->getConstraint());
294 34
        }
295
296
        // Issue #113: Check devMode of event rather than our global state.
297
        // Composer fires the PRE_DEPENDENCIES_SOLVING event twice for
298
        // `--no-dev` operations to decide which packages are dev only
299
        // requirements.
300 170
        if ($this->state->shouldMergeDev() && $event->isDevMode()) {
301 165
            foreach ($this->state->getDuplicateLinks('require-dev') as $link) {
302 10
                $this->logger->info(
303 10
                    "Adding dev dependency <comment>{$link}</comment>"
304 2
                );
305 10
                $request->install($link->getTarget(), $link->getConstraint());
306 33
            }
307 33
        }
308 170
    }
309
310
    /**
311
     * Handle an event callback following installation of a new package by
312
     * checking to see if the package that was installed was our plugin.
313
     *
314
     * @param PackageEvent $event
315
     */
316 15
    public function onPostPackageInstall(PackageEvent $event)
317
    {
318 15
        $op = $event->getOperation();
319 15
        if ($op instanceof InstallOperation) {
320 15
            $package = $op->getPackage()->getName();
321 15
            if ($package === self::PACKAGE_NAME) {
322 10
                $this->logger->info('composer-merge-plugin installed');
323 10
                $this->state->setFirstInstall(true);
324 10
                $this->state->setLocked(
325 10
                    $event->getComposer()->getLocker()->isLocked()
326 2
                );
327 2
            }
328 3
        }
329 15
    }
330
331
    /**
332
     * Handle an event callback following an install or update command. If our
333
     * plugin was installed during the run then trigger an update command to
334
     * process any merge-patterns in the current config.
335
     *
336
     * @param ScriptEvent $event
337
     */
338 170
    public function onPostInstallOrUpdate(ScriptEvent $event)
339
    {
340
        // @codeCoverageIgnoreStart
341
        if ($this->state->isFirstInstall()) {
342
            $this->state->setFirstInstall(false);
343
            $this->logger->info(
344
                '<comment>' .
345
                'Running additional update to apply merge settings' .
346
                '</comment>'
347
            );
348
349
            $config = $this->composer->getConfig();
350
351
            $preferSource = $config->get('preferred-install') == 'source';
352
            $preferDist = $config->get('preferred-install') == 'dist';
353
354
            $installer = Installer::create(
355
                $event->getIO(),
356
                // Create a new Composer instance to ensure full processing of
357
                // the merged files.
358
                Factory::create($event->getIO(), null, false)
359
            );
360
361
            $installer->setPreferSource($preferSource);
362
            $installer->setPreferDist($preferDist);
363
            $installer->setDevMode($event->isDevMode());
364
            $installer->setDumpAutoloader($this->state->shouldDumpAutoloader());
365
            $installer->setOptimizeAutoloader(
366
                $this->state->shouldOptimizeAutoloader()
367
            );
368
369
            if ($this->state->forceUpdate()) {
370
                // Force update mode so that new packages are processed rather
371
                // than just telling the user that composer.json and
372
                // composer.lock don't match.
373
                $installer->setUpdate(true);
374
            }
375
376
            $installer->run();
377
        }
378
        // @codeCoverageIgnoreEnd
379 170
    }
380
}
381
// vim:sw=4:ts=4:sts=4:et:
382