Issues (75)

Security Analysis    not enabled

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.

Migration/Loader/MigrationsLoader.php (2 issues)

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
namespace RDV\Bundle\MigrationBundle\Migration\Loader;
4
5
use Doctrine\DBAL\Connection;
6
use Symfony\Component\DependencyInjection\ContainerInterface;
7
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
8
use Symfony\Component\Finder\Finder;
9
use Symfony\Component\Finder\SplFileInfo;
10
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
11
use Symfony\Component\HttpKernel\KernelInterface;
12
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
13
14
use RDV\Bundle\MigrationBundle\Migration\Migration;
15
use RDV\Bundle\MigrationBundle\Migration\MigrationState;
16
use RDV\Bundle\MigrationBundle\Migration\Installation;
17
use RDV\Bundle\MigrationBundle\Migration\OrderedMigrationInterface;
18
use RDV\Bundle\MigrationBundle\Migration\UpdateBundleVersionMigration;
19
use RDV\Bundle\MigrationBundle\Event\MigrationEvents;
20
use RDV\Bundle\MigrationBundle\Event\PostMigrationEvent;
21
use RDV\Bundle\MigrationBundle\Event\PreMigrationEvent;
22
23
/**
24
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
25
 */
26
class MigrationsLoader
27
{
28
    const DEFAULT_MIGRATIONS_PATH = 'Migrations/Schema';
29
30
    /**
31
     * @var KernelInterface
32
     *
33
     */
34
    protected $kernel;
35
36
    /**
37
     * @var Connection
38
     */
39
    protected $connection;
40
41
    /**
42
     * @var ContainerInterface
43
     */
44
    protected $container;
45
46
    /**
47
     * @var EventDispatcherInterface
48
     */
49
    protected $eventDispatcher;
50
51
    /**
52
     * @var array An array with already loaded bundle migration versions
53
     *             key =   bundle name
54
     *             value = latest loaded version
55
     */
56
    protected $loadedVersions;
57
58
    /**
59
     * @var array An array with bundles we must work from
60
     */
61
    protected $bundles;
62
63
    /**
64
     * @var array An array with excluded bundles
65
     */
66
    protected $excludeBundles;
67
68
    /**
69
     * @var string
70
     */
71
    protected $migrationPath = self::DEFAULT_MIGRATIONS_PATH;
72
73
    /**
74
     * @param KernelInterface          $kernel
75
     * @param Connection               $connection
76
     * @param ContainerInterface       $container
77
     * @param EventDispatcherInterface $eventDispatcher
78
     */
79
    public function __construct(
80
        KernelInterface $kernel,
81
        Connection $connection,
82
        ContainerInterface $container,
83
        EventDispatcherInterface $eventDispatcher
84
    ) {
85
        $this->kernel          = $kernel;
86
        $this->connection      = $connection;
87
        $this->container       = $container;
88
        $this->eventDispatcher = $eventDispatcher;
89
    }
90
91
    /**
92
     * @param array $bundles
93
     */
94
    public function setBundles($bundles)
95
    {
96
        $this->bundles = $bundles;
97
    }
98
99
    /**
100
     * @param array $excludeBundles
101
     */
102
    public function setExcludeBundles($excludeBundles)
103
    {
104
        $this->excludeBundles = $excludeBundles;
105
    }
106
107
    /**
108
     * @param string $migrationPath
109
     * @return $this
110
     */
111
    public function setMigrationPath($migrationPath)
112
    {
113
        $this->migrationPath = $migrationPath;
114
115
        return $this;
116
    }
117
118
    /**
119
     * @return MigrationState[]
120
     */
121
    public function getMigrations()
122
    {
123
        $result = [];
124
125
        // process "pre" migrations
126
        $preEvent = new PreMigrationEvent($this->connection);
127
        $this->eventDispatcher->dispatch(MigrationEvents::PRE_UP, $preEvent);
128
        $preMigrations = $preEvent->getMigrations();
129
        foreach ($preMigrations as $migration) {
130
            $result[] = new MigrationState($migration);
131
        }
132
        $this->loadedVersions = $preEvent->getLoadedVersions();
133
134
        // process main migrations
135
        $migrationDirectories = $this->getMigrationDirectories();
136
        $this->filterMigrations($migrationDirectories);
137
        $this->createMigrationObjects(
138
            $result,
139
            $this->loadMigrationScripts($migrationDirectories)
140
        );
141
142
        $result[] = new MigrationState(new UpdateBundleVersionMigration($result));
143
144
        // process "post" migrations
145
        $postEvent = new PostMigrationEvent($this->connection);
146
        $this->eventDispatcher->dispatch(MigrationEvents::POST_UP, $postEvent);
147
        $postMigrations = $postEvent->getMigrations();
148
        foreach ($postMigrations as $migration) {
149
            $result[] = new MigrationState($migration);
150
        }
151
152
        return $result;
153
    }
154
155
    /**
156
     * Gets a list of all directories contain migration scripts
157
     *
158
     * @return array
159
     *      key   = bundle name
160
     *      value = array
161
     *      .    key   = a migration version (actually it equals the name of migration directory)
162
     *      .            or empty string for root migration directory
163
     *      .    value = full path to a migration directory
164
     */
165
    protected function getMigrationDirectories()
166
    {
167
        $result = [];
168
169
        $bundles = $this->getBundleList();
170
        foreach ($bundles as $bundleName => $bundle) {
171
            $bundleMigrationPath = $this->getBundleMigrationPath($bundle->getPath());
172
173
            if (is_dir($bundleMigrationPath)) {
174
                $bundleMigrationDirectories = [];
175
176
                // get directories contain versioned migration scripts
177
                $finder = new Finder();
178
                $finder->directories()->depth(0)->in($bundleMigrationPath);
179
                /** @var SplFileInfo $directory */
180
                foreach ($finder as $directory) {
181
                    $bundleMigrationDirectories[$directory->getRelativePathname()] = $directory->getPathname();
182
                }
183
                // add root migration directory (it may contains an installation script)
184
                $bundleMigrationDirectories[''] = $bundleMigrationPath;
185
                // sort them by version number (the oldest version first)
186
                if (!empty($bundleMigrationDirectories)) {
187
                    uksort(
188
                        $bundleMigrationDirectories,
189
                        function ($a, $b) {
190
                            return version_compare($a, $b);
191
                        }
192
                    );
193
                }
194
195
                $result[$bundleName] = $bundleMigrationDirectories;
196
            }
197
        }
198
199
        return $result;
200
    }
201
202
    /**
203
     * @param string $bundlePath
204
     * @return string
205
     */
206
    protected function getBundleMigrationPath($bundlePath)
207
    {
208
        return realpath(str_replace('/', DIRECTORY_SEPARATOR, $bundlePath . '/' . $this->migrationPath));
209
    }
210
211
212
    /**
213
     * Finds migration files and call "include_once" for each file
214
     *
215
     * @param array $migrationDirectories
216
     *               key   = bundle name
217
     *               value = array
218
     *               .    key   = a migration version or empty string for root migration directory
219
     *               .    value = full path to a migration directory
220
     *
221
     * @return array loaded files
222
     *               'migrations' => array
223
     *               .      key   = full file path
224
     *               .      value = array
225
     *               .            'bundleName' => bundle name
226
     *               .            'version'    => migration version
227
     *               'installers' => array
228
     *               .      key   = full file path
229
     *               .      value = bundle name
230
     *               'bundles'    => string[] names of bundles
231
     */
232
    protected function loadMigrationScripts(array $migrationDirectories)
233
    {
234
        $migrations = [];
235
        $installers = [];
236
237
        foreach ($migrationDirectories as $bundleName => $bundleMigrationDirectories) {
238
            foreach ($bundleMigrationDirectories as $migrationVersion => $migrationPath) {
239
                $fileFinder = new Finder();
240
                $fileFinder->depth(0)->files()->name('*.php')->in($migrationPath);
241
                foreach ($fileFinder as $file) {
242
                    /** @var SplFileInfo $file */
243
                    $filePath = $file->getPathname();
244
                    include_once $filePath;
245
                    if (empty($migrationVersion)) {
246
                        $installers[$filePath] = $bundleName;
247
                    } else {
248
                        $migrations[$filePath] = ['bundleName' => $bundleName, 'version' => $migrationVersion];
249
                    }
250
                }
251
            }
252
        }
253
254
        return [
255
            'migrations' => $migrations,
256
            'installers' => $installers,
257
            'bundles'    => array_keys($migrationDirectories),
258
        ];
259
    }
260
261
    /**
262
     * Creates an instances of all classes implement migration scripts
263
     *
264
     * @param MigrationState[] $result
265
     * @param array            $files Files contain migration scripts
266
     *                                'migrations' => array
267
     *                                .      key   = full file path
268
     *                                .      value = array
269
     *                                .            'bundleName' => bundle name
270
     *                                .            'version'    => migration version
271
     *                                'installers' => array
272
     *                                .      key   = full file path
273
     *                                .      value = bundle name
274
     *                                'bundles'    => string[] names of bundles
275
     *
276
     * @throws \RuntimeException if a migration script contains more than one class
277
     */
278
    protected function createMigrationObjects(&$result, $files)
279
    {
280
        // load migration objects
281
        list($migrations, $installers) = $this->loadMigrationObjects($files);
282
283
        // remove versioned migrations covered by installers
284
        foreach ($installers as $installer) {
285
            $installerBundleName = $installer['bundleName'];
286
            $installerVersion    = $installer['version'];
287
            foreach ($files['migrations'] as $sourceFile => $migration) {
288
                if ($migration['bundleName'] === $installerBundleName
289
                    && version_compare($migration['version'], $installerVersion) < 1
290
                ) {
291
                    unset($migrations[$sourceFile]);
292
                }
293
            }
294
        }
295
296
        // group migration by bundle & version then sort them within same version
297
        $groupedMigrations = $this->groupAndSortMigrations($files, $migrations);
298
299
        // add migration objects to result tacking into account bundles order
300
        foreach ($files['bundles'] as $bundleName) {
301
            // add installers to the result
302
            foreach ($files['installers'] as $sourceFile => $installerBundleName) {
303
                if ($installerBundleName === $bundleName && isset($migrations[$sourceFile])) {
304
                    /** @var Installation $installer */
305
                    $installer = $migrations[$sourceFile];
306
                    $result[]  = new MigrationState(
307
                        $installer,
308
                        $installerBundleName,
309
                        $installer->getMigrationVersion()
310
                    );
311
                }
312
            }
313
            // add migrations to the result
314
            if (isset($groupedMigrations[$bundleName])) {
315
                foreach ($groupedMigrations[$bundleName] as $version => $versionedMigrations) {
316
                    foreach ($versionedMigrations as $migration) {
317
                        $result[] = new MigrationState(
318
                            $migration,
319
                            $bundleName,
320
                            $version
321
                        );
322
                    }
323
                }
324
            }
325
        }
326
    }
327
328
    /**
329
     * Groups migrations by bundle and version
330
     * Sorts grouped migrations within the same version
331
     *
332
     * @param array $files
333
     * @param array $migrations
334
     *
335
     * @return array
336
     */
337
    protected function groupAndSortMigrations($files, $migrations)
338
    {
339
        $groupedMigrations = [];
340
        foreach ($files['migrations'] as $sourceFile => $migration) {
341
            if (isset($migrations[$sourceFile])) {
342
                $bundleName = $migration['bundleName'];
343
                $version    = $migration['version'];
344
                if (!isset($groupedMigrations[$bundleName])) {
345
                    $groupedMigrations[$bundleName] = [];
346
                }
347
                if (!isset($groupedMigrations[$bundleName][$version])) {
348
                    $groupedMigrations[$bundleName][$version] = [];
349
                }
350
                $groupedMigrations[$bundleName][$version][] = $migrations[$sourceFile];
351
            }
352
        }
353
354
        foreach ($groupedMigrations as $bundleName => $versions) {
355
            foreach ($versions as $version => $versionedMigrations) {
356
                if (count($versionedMigrations) > 1) {
357
                    usort(
358
                        $groupedMigrations[$bundleName][$version],
359
                        function ($a, $b) {
360
                            $aOrder = 0;
361
                            if ($a instanceof OrderedMigrationInterface) {
362
                                $aOrder = $a->getOrder();
363
                            }
364
365
                            $bOrder = 0;
366
                            if ($b instanceof OrderedMigrationInterface) {
367
                                $bOrder = $b->getOrder();
368
                            }
369
370
                            if ($aOrder === $bOrder) {
371
                                return 0;
372
                            } elseif ($aOrder < $bOrder) {
373
                                return -1;
374
                            } else {
375
                                return 1;
376
                            }
377
                        }
378
                    );
379
                }
380
            }
381
        }
382
383
        return $groupedMigrations;
384
    }
385
386
387
    /**
388
     * Loads migration objects
389
     *
390
     * @param $files
391
     *
392
     * @return array
393
     * @throws \RuntimeException
394
     */
395
    protected function loadMigrationObjects($files)
396
    {
397
        $migrations = [];
398
        $installers = [];
399
        $declared   = get_declared_classes();
400
401
        foreach ($declared as $className) {
402
            $reflClass  = new \ReflectionClass($className);
403
            $sourceFile = $reflClass->getFileName();
404
            if (isset($files['migrations'][$sourceFile])) {
405
                if (is_subclass_of($className, 'RDV\Bundle\MigrationBundle\Migration\Migration')) {
0 ignored issues
show
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
406
                    $migration = new $className;
407
                    if (isset($migrations[$sourceFile])) {
408
                        throw new \RuntimeException('A migration script must contains only one class.');
409
                    }
410
                    if ($migration instanceof ContainerAwareInterface) {
411
                        $migration->setContainer($this->container);
412
                    }
413
                    $migrations[$sourceFile] = $migration;
414
                }
415
            } elseif (isset($files['installers'][$sourceFile])) {
416
                if (is_subclass_of($className, 'RDV\Bundle\MigrationBundle\Migration\Installation')) {
0 ignored issues
show
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
417
                    /** @var \RDV\Bundle\MigrationBundle\Migration\Installation $installer */
418
                    $installer = new $className;
419
                    if (isset($migrations[$sourceFile])) {
420
                        throw new \RuntimeException('An installation  script must contains only one class.');
421
                    }
422
                    if ($installer instanceof ContainerAwareInterface) {
423
                        $installer->setContainer($this->container);
424
                    }
425
                    $migrations[$sourceFile] = $installer;
426
                    $installers[$sourceFile] = [
427
                        'bundleName' => $files['installers'][$sourceFile],
428
                        'version'    => $installer->getMigrationVersion(),
429
                    ];
430
                }
431
            }
432
        }
433
434
        return [
435
            $migrations,
436
            $installers
437
        ];
438
    }
439
440
441
    /**
442
     * Removes already installed migrations
443
     *
444
     * @param array $migrationDirectories
445
     *      key   = bundle name
446
     *      value = array
447
     *      .    key   = a migration version or empty string for root migration directory
448
     *      .    value = full path to a migration directory
449
     */
450
    protected function filterMigrations(array &$migrationDirectories)
451
    {
452
        if (!empty($this->loadedVersions)) {
453
            foreach ($migrationDirectories as $bundleName => $bundleMigrationDirectories) {
454
                $loadedVersion = !empty($this->loadedVersions[$bundleName])
455
                    ? $this->loadedVersions[$bundleName]
456
                    : null;
457
                if ($loadedVersion) {
458
                    foreach (array_keys($bundleMigrationDirectories) as $migrationVersion) {
459
                        if (empty($migrationVersion) || version_compare($migrationVersion, $loadedVersion) < 1) {
460
                            unset($migrationDirectories[$bundleName][$migrationVersion]);
461
                        }
462
                    }
463
                }
464
            }
465
        }
466
    }
467
468
    /**
469
     * @return BundleInterface[] key = bundle name
470
     */
471
    protected function getBundleList()
472
    {
473
        $bundles = $this->kernel->getBundles();
474
        if (!empty($this->bundles)) {
475
            $includedBundles = [];
476
            foreach ($this->bundles as $bundleName) {
477
                if (!empty($bundles[$bundleName])) {
478
                    $includedBundles[$bundleName] = $bundles[$bundleName];
479
                }
480
            }
481
            $bundles = $includedBundles;
482
        }
483
        if (!empty($this->excludeBundles)) {
484
            foreach ($this->excludeBundles as $excludeBundle) {
485
                unset($bundles[$excludeBundle]);
486
            }
487
        }
488
489
        return $bundles;
490
    }
491
}
492