GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

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/Plugin.php (1 issue)

Labels
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
/**
4
 * This file is part of the Dealerdirect PHP_CodeSniffer Standards
5
 * Composer Installer Plugin package.
6
 *
7
 * @copyright 2016-2018 Dealerdirect B.V.
8
 * @license MIT
9
 */
10
11
namespace Dealerdirect\Composer\Plugin\Installers\PHPCodeSniffer;
12
13
use Composer\Composer;
14
use Composer\EventDispatcher\EventSubscriberInterface;
15
use Composer\IO\IOInterface;
16
use Composer\Package\AliasPackage;
17
use Composer\Package\PackageInterface;
18
use Composer\Package\RootpackageInterface;
19
use Composer\Plugin\PluginInterface;
20
use Composer\Script\Event;
21
use Composer\Script\ScriptEvents;
22
use Composer\Util\Filesystem;
23
use Composer\Util\ProcessExecutor;
24
use Symfony\Component\Finder\Finder;
25
use Symfony\Component\Process\Exception\LogicException;
26
use Symfony\Component\Process\Exception\ProcessFailedException;
27
use Symfony\Component\Process\Exception\RuntimeException;
28
29
/**
30
 * PHP_CodeSniffer standard installation manager.
31
 *
32
 * @author Franck Nijhof <[email protected]>
33
 */
34
class Plugin implements PluginInterface, EventSubscriberInterface
35
{
36
37
    const KEY_MAX_DEPTH = 'phpcodesniffer-search-depth';
38
39
    const MESSAGE_ERROR_WRONG_MAX_DEPTH =
40
        'The value of "%s" (in the composer.json "extra".section) must be an integer larger then %d, %s given.';
41
    const MESSAGE_NOT_INSTALLED = 'PHPCodeSniffer is not installed';
42
    const MESSAGE_NOTHING_TO_INSTALL = 'Nothing to install or update';
43
    const MESSAGE_RUNNING_INSTALLER = 'Running PHPCodeSniffer Composer Installer';
44
45
    const PACKAGE_NAME = 'squizlabs/php_codesniffer';
46
    const PACKAGE_TYPE = 'phpcodesniffer-standard';
47
48
    const PHPCS_CONFIG_KEY = 'installed_paths';
49
50
    /**
51
     * @var Composer
52
     */
53
    private $composer;
54
55
    /**
56
     * @var string
57
     */
58
    private $cwd;
59
60
    /**
61
     * @var Filesystem
62
     */
63
    private $filesystem;
64
65
    /**
66
     * @var array
67
     */
68
    private $installedPaths;
69
70
    /**
71
     * @var IOInterface
72
     */
73
    private $io;
74
75
    /**
76
     * @var ProcessExecutor
77
     */
78
    private $processExecutor;
79
80
    /**
81
     * Triggers the plugin's main functionality.
82
     *
83
     * Makes it possible to run the plugin as a custom command.
84
     *
85
     * @param Event $event
86
     *
87
     * @throws \InvalidArgumentException
88
     * @throws \RuntimeException
89
     * @throws LogicException
90
     * @throws ProcessFailedException
91
     * @throws RuntimeException
92
     */
93
    public static function run(Event $event)
94
    {
95
        $io = $event->getIO();
96
        $composer = $event->getComposer();
97
98
        $instance = new static();
99
100
        $instance->io = $io;
101
        $instance->composer = $composer;
102
        $instance->init();
103
        $instance->onDependenciesChangedEvent();
104
    }
105
106
    /**
107
     * {@inheritDoc}
108
     *
109
     * @throws \RuntimeException
110
     * @throws LogicException
111
     * @throws ProcessFailedException
112
     * @throws RuntimeException
113
     */
114
    public function activate(Composer $composer, IOInterface $io)
115
    {
116
        $this->composer = $composer;
117
        $this->io = $io;
118
119
        $this->init();
120
    }
121
122
    /**
123
     * Prepares the plugin so it's main functionality can be run.
124
     *
125
     * @throws \RuntimeException
126
     * @throws LogicException
127
     * @throws ProcessFailedException
128
     * @throws RuntimeException
129
     */
130
    private function init()
131
    {
132
        $this->cwd = getcwd();
133
        $this->installedPaths = array();
134
135
        $this->processExecutor = new ProcessExecutor($this->io);
136
        $this->filesystem = new Filesystem($this->processExecutor);
137
    }
138
139
    /**
140
     * {@inheritDoc}
141
     */
142
    public static function getSubscribedEvents()
143
    {
144
        return array(
145
            ScriptEvents::POST_INSTALL_CMD => array(
146
                array('onDependenciesChangedEvent', 0),
147
            ),
148
            ScriptEvents::POST_UPDATE_CMD => array(
149
                array('onDependenciesChangedEvent', 0),
150
            ),
151
        );
152
    }
153
154
    /**
155
     * Entry point for post install and post update events.
156
     *
157
     * @throws \InvalidArgumentException
158
     * @throws LogicException
159
     * @throws ProcessFailedException
160
     * @throws RuntimeException
161
     */
162
    public function onDependenciesChangedEvent()
163
    {
164
        $io = $this->io;
165
        $isVerbose = $io->isVerbose();
166
167
        if ($isVerbose) {
168
            $io->write(sprintf('<info>%s</info>', self::MESSAGE_RUNNING_INSTALLER));
169
        }
170
171
        if ($this->isPHPCodeSnifferInstalled() === true) {
172
            $this->loadInstalledPaths();
173
            $installPathCleaned = $this->cleanInstalledPaths();
174
            $installPathUpdated = $this->updateInstalledPaths();
175
176
            if ($installPathCleaned === true || $installPathUpdated === true) {
177
                $this->saveInstalledPaths();
178
            } elseif ($isVerbose) {
179
                $io->write(sprintf('<info>%s</info>', self::MESSAGE_NOTHING_TO_INSTALL));
180
            }
181
        } elseif ($isVerbose) {
182
            $io->write(sprintf('<info>%s</info>', self::MESSAGE_NOT_INSTALLED));
183
        }
184
    }
185
186
    /**
187
     * Load all paths from PHP_CodeSniffer into an array.
188
     *
189
     * @throws LogicException
190
     * @throws ProcessFailedException
191
     * @throws RuntimeException
192
     */
193
    private function loadInstalledPaths()
194
    {
195
        if ($this->isPHPCodeSnifferInstalled() === true) {
196
            $this->processExecutor->execute(
197
                sprintf(
198
                    'phpcs --config-show %s',
199
                    self::PHPCS_CONFIG_KEY
200
                ),
201
                $output,
202
                $this->composer->getConfig()->get('bin-dir')
203
            );
204
205
            $phpcsInstalledPaths = str_replace(self::PHPCS_CONFIG_KEY . ': ', '', $output);
206
            $phpcsInstalledPaths = trim($phpcsInstalledPaths);
207
208
            if ($phpcsInstalledPaths !== '') {
209
                $this->installedPaths = explode(',', $phpcsInstalledPaths);
210
            }
211
        }
212
    }
213
214
    /**
215
     * Save all coding standard paths back into PHP_CodeSniffer
216
     *
217
     * @throws LogicException
218
     * @throws ProcessFailedException
219
     * @throws RuntimeException
220
     */
221
    private function saveInstalledPaths()
222
    {
223
        // Check if we found installed paths to set.
224
        if (count($this->installedPaths) !== 0) {
225
            $paths = implode(',', $this->installedPaths);
226
            $arguments = array('--config-set', self::PHPCS_CONFIG_KEY, $paths);
227
            $configMessage = sprintf(
228
                'PHP CodeSniffer Config <info>%s</info> <comment>set to</comment> <info>%s</info>',
229
                self::PHPCS_CONFIG_KEY,
230
                $paths
231
            );
232
        } else {
233
            // Delete the installed paths if none were found.
234
            $arguments = array('--config-delete', self::PHPCS_CONFIG_KEY);
235
            $configMessage = sprintf(
236
                'PHP CodeSniffer Config <info>%s</info> <comment>delete</comment>',
237
                self::PHPCS_CONFIG_KEY
238
            );
239
        }
240
241
        $this->io->write($configMessage);
242
243
        $this->processExecutor->execute(
244
            sprintf(
245
                'phpcs %s',
246
                implode(' ', $arguments)
247
            ),
248
            $configResult,
249
            $this->composer->getConfig()->get('bin-dir')
250
        );
251
252
        if ($this->io->isVerbose() && !empty($configResult)) {
253
            $this->io->write(sprintf('<info>%s</info>', $configResult));
254
        }
255
    }
256
257
    /**
258
     * Iterate trough all known paths and check if they are still valid.
259
     *
260
     * If path does not exists, is not an directory or isn't readable, the path
261
     * is removed from the list.
262
     *
263
     * @return bool True if changes where made, false otherwise
264
     */
265
    private function cleanInstalledPaths()
266
    {
267
        $changes = false;
268
        foreach ($this->installedPaths as $key => $path) {
269
            // This might be a relative path as well
270
            $alternativePath = realpath($this->getPHPCodeSnifferInstallPath() . DIRECTORY_SEPARATOR . $path);
271
272
            if ((is_dir($path) === false || is_readable($path) === false) &&
273
                (is_dir($alternativePath) === false || is_readable($alternativePath) === false)
274
            ) {
275
                unset($this->installedPaths[$key]);
276
                $changes = true;
277
            }
278
        }
279
        return $changes;
280
    }
281
282
    /**
283
     * Check all installed packages (including the root package) against
284
     * the installed paths from PHP_CodeSniffer and add the missing ones.
285
     *
286
     * @return bool True if changes where made, false otherwise
287
     *
288
     * @throws \InvalidArgumentException
289
     * @throws \RuntimeException
290
     */
291
    private function updateInstalledPaths()
292
    {
293
        $changes = false;
294
295
        $searchPaths = array($this->cwd);
296
        $codingStandardPackages = $this->getPHPCodingStandardPackages();
297
        foreach ($codingStandardPackages as $package) {
298
            $installPath = $this->composer->getInstallationManager()->getInstallPath($package);
299
            if ($this->filesystem->isAbsolutePath($installPath) === false) {
300
                $installPath = $this->filesystem->normalizePath(
301
                    $this->cwd . DIRECTORY_SEPARATOR . $installPath
302
                );
303
            }
304
            $searchPaths[] = $installPath;
305
        }
306
307
        $finder = new Finder();
308
        $finder->files()
309
            ->depth('<= ' . $this->getMaxDepth())
310
            ->depth('>= ' . $this->getMinDepth())
311
            ->ignoreUnreadableDirs()
312
            ->ignoreVCS(true)
313
            ->in($searchPaths)
314
            ->name('ruleset.xml');
315
316
        // Process each found possible ruleset.
317
        foreach ($finder as $ruleset) {
318
            $standardsPath = $ruleset->getPath();
319
320
            // Pick the directory above the directory containing the standard, unless this is the project root.
321
            if ($standardsPath !== $this->cwd) {
322
                $standardsPath = dirname($standardsPath);
323
            }
324
325
            // Use relative paths for local project repositories.
326
            if ($this->isRunningGlobally() === false) {
327
                $standardsPath = $this->filesystem->findShortestPath(
328
                    $this->getPHPCodeSnifferInstallPath(),
329
                    $standardsPath,
330
                    true
331
                );
332
            }
333
334
            // De-duplicate and add when directory is not configured.
335
            if (in_array($standardsPath, $this->installedPaths, true) === false) {
336
                $this->installedPaths[] = $standardsPath;
337
                $changes = true;
338
            }
339
        }
340
341
        return $changes;
342
    }
343
344
    /**
345
     * Iterates through Composers' local repository looking for valid Coding
346
     * Standard packages.
347
     *
348
     * If the package is the RootPackage (the one the plugin is installed into),
349
     * the package is ignored for now since it needs a different install path logic.
350
     *
351
     * @return array Composer packages containing coding standard(s)
352
     */
353
    private function getPHPCodingStandardPackages()
354
    {
355
        $codingStandardPackages = array_filter(
356
            $this->composer->getRepositoryManager()->getLocalRepository()->getPackages(),
357
            function (PackageInterface $package) {
358
                if ($package instanceof AliasPackage) {
359
                    return false;
360
                }
361
                return $package->getType() === Plugin::PACKAGE_TYPE;
362
            }
363
        );
364
365
        if (! $this->composer->getPackage() instanceof RootpackageInterface
366
            && $this->composer->getPackage()->getType() === self::PACKAGE_TYPE
367
        ) {
368
            $codingStandardPackages[] = $this->composer->getPackage();
369
        }
370
371
        return $codingStandardPackages;
372
    }
373
374
    /**
375
     * Searches for the installed PHP_CodeSniffer Composer package
376
     *
377
     * @param null|string|\Composer\Semver\Constraint\ConstraintInterface $versionConstraint to match against
378
     *
379
     * @return PackageInterface|null
380
     */
381
    private function getPHPCodeSnifferPackage($versionConstraint = null)
382
    {
383
        $packages = $this
384
            ->composer
385
            ->getRepositoryManager()
386
            ->getLocalRepository()
387
            ->findPackages(self::PACKAGE_NAME, $versionConstraint);
388
389
        return array_shift($packages);
390
    }
391
392
    /**
393
     * Returns the path to the PHP_CodeSniffer package installation location
394
     *
395
     * @return string
396
     */
397
    private function getPHPCodeSnifferInstallPath()
398
    {
399
        return $this->composer->getInstallationManager()->getInstallPath($this->getPHPCodeSnifferPackage());
0 ignored issues
show
It seems like $this->getPHPCodeSnifferPackage() can be null; however, getInstallPath() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
400
    }
401
402
    /**
403
     * Simple check if PHP_CodeSniffer is installed.
404
     *
405
     * @param null|string|\Composer\Semver\Constraint\ConstraintInterface $versionConstraint to match against
406
     *
407
     * @return bool Whether PHP_CodeSniffer is installed
408
     */
409
    private function isPHPCodeSnifferInstalled($versionConstraint = null)
410
    {
411
        return ($this->getPHPCodeSnifferPackage($versionConstraint) !== null);
412
    }
413
414
    /**
415
     * Test if composer is running "global"
416
     * This check kinda dirty, but it is the "Composer Way"
417
     *
418
     * @return bool Whether Composer is running "globally"
419
     *
420
     * @throws \RuntimeException
421
     */
422
    private function isRunningGlobally()
423
    {
424
        return ($this->composer->getConfig()->get('home') === $this->cwd);
425
    }
426
427
    /**
428
     * Determines the maximum search depth when searching for Coding Standards.
429
     *
430
     * @return int
431
     *
432
     * @throws \InvalidArgumentException
433
     */
434
    private function getMaxDepth()
435
    {
436
        $maxDepth = 3;
437
438
        $extra = $this->composer->getPackage()->getExtra();
439
440
        if (array_key_exists(self::KEY_MAX_DEPTH, $extra)) {
441
            $maxDepth = $extra[self::KEY_MAX_DEPTH];
442
            $minDepth = $this->getMinDepth();
443
444
            if (is_int($maxDepth) === false     /* Must be an integer */
445
                || $maxDepth <= $minDepth       /* Larger than the minimum */
446
                || is_float($maxDepth) === true /* Within the boundaries of integer */
447
            ) {
448
                $message = vsprintf(
449
                    self::MESSAGE_ERROR_WRONG_MAX_DEPTH,
450
                    array(
451
                        'key' => self::KEY_MAX_DEPTH,
452
                        'min' => $minDepth,
453
                        'given' => var_export($maxDepth, true),
454
                    )
455
                );
456
457
                throw new \InvalidArgumentException($message);
458
            }
459
        }
460
461
        return $maxDepth;
462
    }
463
464
    /**
465
     * Returns the minimal search depth for Coding Standard packages.
466
     *
467
     * Usually this is 0, unless PHP_CodeSniffer >= 3 is used.
468
     *
469
     * @return int
470
     */
471
    private function getMinDepth()
472
    {
473
        if ($this->isPHPCodeSnifferInstalled('>= 3.0.0') !== true) {
474
            return 1;
475
        }
476
        return 0;
477
    }
478
}
479