Issues (1490)

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.

src/Tools/Builder.php (5 issues)

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 Fabrica\Tools;
4
5
use Fabrica\Helper\BuildInterpolator;
6
use Fabrica\Helper\MailerFactory;
7
use Fabrica\Tools\Logging\BuildLogger;
8
use Fabrica\Models\Infra\Ci\Build;
9
use Fabrica\Tools\Plugin\Util\Factory as PluginFactory;
10
use Fabrica\Tools\Store\BuildErrorWriter;
11
use Fabrica\Tools\Store\Factory;
12
use Psr\Log\LoggerAwareInterface;
13
use Psr\Log\LoggerInterface;
14
use Psr\Log\LogLevel;
15
16
/**
17
 * @author Ricardo Sierra <[email protected]>
18
 */
19
class Builder implements LoggerAwareInterface
20
{
21
    /**
22
     * @var string
23
     */
24
    public $buildPath;
25
26
    /**
27
     * @var string[]
28
     */
29
    public $ignore = [];
30
31
    /**
32
     * @var string[]
33
     */
34
    public $binaryPath = '';
35
36
    /**
37
     * @var string[]
38
     */
39
    public $priorityPath = 'local';
40
41
    /**
42
     * @var string
43
     */
44
    public $directory;
45
46
    /**
47
     * @var string|null
48
     */
49
    protected $currentStage = null;
50
51
    /**
52
     * @var bool
53
     */
54
    protected $verbose = true;
55
56
    /**
57
     * @var \PHPCensor\Model\Build
58
     */
59
    protected $build;
60
61
    /**
62
     * @var LoggerInterface
63
     */
64
    protected $logger;
65
66
    /**
67
     * @var array
68
     */
69
    protected $config = [];
70
71
    /**
72
     * @var string
73
     */
74
    protected $lastOutput;
75
76
    /**
77
     * @var BuildInterpolator
78
     */
79
    protected $interpolator;
80
81
    /**
82
     * @var \Fabrica\Tools\Store\BuildStore
83
     */
84
    protected $store;
85
86
    /**
87
     * @var \PHPCensor\Plugin\Util\Executor
88
     */
89
    protected $pluginExecutor;
90
91
    /**
92
     * @var Helper\CommandExecutorInterface
93
     */
94
    protected $commandExecutor;
95
96
    /**
97
     * @var Logging\BuildLogger
98
     */
99
    protected $buildLogger;
100
101
    /**
102
     * @var BuildErrorWriter
103
     */
104
    private $buildErrorWriter;
105
106
    /**
107
     * Set up the builder.
108
     *
109
     * @param \PHPCensor\Model\Build $build
110
     * @param LoggerInterface        $logger
111
     */
112
    public function __construct(Build $build, LoggerInterface $logger = null)
113
    {
114
        $this->build = $build;
115
        $this->store = Factory::getStore('Build');
116
117
        $this->buildLogger    = new BuildLogger($logger, $build);
118
        $pluginFactory        = $this->buildPluginFactory($build);
119
        $this->pluginExecutor = new Plugin\Util\Executor($pluginFactory, $this->buildLogger);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Fabrica\Tools\Plugi...ry, $this->buildLogger) of type object<Fabrica\Tools\Plugin\Util\Executor> is incompatible with the declared type object<PHPCensor\Plugin\Util\Executor> of property $pluginExecutor.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
120
121
        $executorClass         = 'PHPCensor\Helper\CommandExecutor';
122
        $this->commandExecutor = new $executorClass(
123
            $this->buildLogger,
124
            ROOT_DIR,
125
            $this->verbose
126
        );
127
128
        $this->interpolator     = new BuildInterpolator();
129
        $this->buildErrorWriter = new BuildErrorWriter($this->build->getProjectId(), $this->build->getId());
130
    }
131
132
    /**
133
     * @return BuildLogger
134
     */
135
    public function getBuildLogger()
136
    {
137
        return $this->buildLogger;
138
    }
139
140
    /**
141
     * @return null|string
142
     */
143
    public function getCurrentStage()
144
    {
145
        return $this->currentStage;
146
    }
147
148
    /**
149
     * Set the config array, as read from .php-censor.yml
150
     *
151
     * @param array $config
152
     *
153
     * @throws \Exception
154
     */
155
    public function setConfig(array $config)
156
    {
157
        $this->config = $config;
158
    }
159
160
    /**
161
     * Access a variable from the .php-censor.yml file.
162
     *
163
     * @param string $key
164
     *
165
     * @return mixed
166
     */
167
    public function getConfig($key = null)
168
    {
169
        $value = null;
170
        if (null === $key) {
171
            $value = $this->config;
172
        } elseif (isset($this->config[$key])) {
173
            $value = $this->config[$key];
174
        }
175
176
        return $value;
177
    }
178
179
    /**
180
     * Access a variable from the config.yml
181
     *
182
     * @param string $key
183
     *
184
     * @return mixed
185
     */
186
    public function getSystemConfig($key)
187
    {
188
        return Config::getInstance()->get($key);
189
    }
190
191
    /**
192
     * @return string The title of the project being built.
193
     *
194
     * @throws Exception\HttpException
195
     */
196
    public function getBuildProjectTitle()
197
    {
198
        return $this->build->getProject()->getTitle();
199
    }
200
201
    /**
202
     * @throws Exception\HttpException
203
     * @throws Exception\InvalidArgumentException
204
     */
205
    public function execute()
206
    {
207
        $this->build->setStatusRunning();
208
        $this->build->setStartDate(new \DateTime());
209
        $this->store->save($this->build);
210
        $this->build->sendStatusPostback();
211
212
        $success = true;
213
214
        $previousBuild = $this->build->getProject()->getPreviousBuild($this->build->getBranch());
215
        $previousState = Build::STATUS_PENDING;
216
217
        if ($previousBuild) {
218
            $previousState = $previousBuild->getStatus();
219
        }
220
221
        try {
222
            // Set up the build:
223
            $this->setupBuild();
224
225
            // Run the core plugin stages:
226
            foreach ([Build::STAGE_SETUP, Build::STAGE_TEST, Build::STAGE_DEPLOY] as $stage) {
227
                $this->currentStage = $stage;
228
                $success &= $this->pluginExecutor->executePlugins($this->config, $stage);
229
                if (!$success) {
230
                    break;
231
                }
232
            }
233
234
            // Set the status so this can be used by complete, success and failure
235
            // stages.
236
            if ($success) {
237
                $this->build->setStatusSuccess();
238
            } else {
239
                $this->build->setStatusFailed();
240
            }
241
        } catch (\Exception $ex) {
242
            $success = false;
243
            $this->build->setStatusFailed();
244
            $this->buildLogger->logFailure('Exception: ' . $ex->getMessage(), $ex);
245
        }
246
247
        try {
248
            if ($success) {
249
                $this->currentStage = Build::STAGE_SUCCESS;
250
                $this->pluginExecutor->executePlugins($this->config, Build::STAGE_SUCCESS);
251
252 View Code Duplication
                if (Build::STATUS_FAILED === $previousState) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
253
                    $this->currentStage = Build::STAGE_FIXED;
254
                    $this->pluginExecutor->executePlugins($this->config, Build::STAGE_FIXED);
255
                }
256
            } else {
257
                $this->currentStage = Build::STAGE_FAILURE;
258
                $this->pluginExecutor->executePlugins($this->config, Build::STAGE_FAILURE);
259
260 View Code Duplication
                if (Build::STATUS_SUCCESS === $previousState || Build::STATUS_PENDING === $previousState) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
261
                    $this->currentStage = Build::STAGE_BROKEN;
262
                    $this->pluginExecutor->executePlugins($this->config, Build::STAGE_BROKEN);
263
                }
264
            }
265
        } catch (\Exception $ex) {
266
            $this->buildLogger->logFailure('Exception: ' . $ex->getMessage(), $ex);
267
        }
268
269
        $this->buildLogger->log('');
270
        if (Build::STATUS_FAILED === $this->build->getStatus()) {
271
            $this->buildLogger->logFailure('BUILD FAILED!');
272
        } else {
273
            $this->buildLogger->logSuccess('BUILD SUCCESS!');
274
        }
275
276
        // Flush errors to make them available to plugins in complete stage
277
        $this->buildErrorWriter->flush();
278
279
        try {
280
            // Complete stage plugins are always run
281
            $this->currentStage = Build::STAGE_COMPLETE;
282
            $this->pluginExecutor->executePlugins($this->config, Build::STAGE_COMPLETE);
283
        } catch (\Exception $ex) {
284
            $this->buildLogger->logFailure('Exception: ' . $ex->getMessage());
285
        }
286
287
        // Update the build in the database, ping any external services, etc.
288
        $this->build->sendStatusPostback();
289
        $this->build->setFinishDate(new \DateTime());
290
291
        $removeBuilds = (bool)Config::getInstance()->get('php-censor.build.remove_builds', true);
292
        if ($removeBuilds) {
293
            // Clean up:
294
            $this->buildLogger->log('');
295
            $this->buildLogger->logSuccess('REMOVING BUILD.');
296
            $this->build->removeBuildDirectory();
297
        }
298
299
        $this->buildErrorWriter->flush();
300
301
        $this->setErrorTrend();
302
303
        $this->store->save($this->build);
304
    }
305
306
    /**
307
     * @throws Exception\HttpException
308
     * @throws Exception\InvalidArgumentException
309
     */
310
    protected function setErrorTrend()
311
    {
312
        $this->build->setErrorsTotal($this->store->getErrorsCount($this->build->getId()));
313
314
        $trend = $this->store->getBuildErrorsTrend(
315
            $this->build->getId(),
316
            $this->build->getProjectId(),
317
            $this->build->getBranch()
318
        );
319
320 View Code Duplication
        if (isset($trend[1])) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
321
            $previousBuild = $this->store->getById($trend[1]['build_id']);
322
            if ($previousBuild 
323
                && !in_array(
324
                    $previousBuild->getStatus(),
325
                    [Build::STATUS_PENDING, Build::STATUS_RUNNING],
326
                    true
327
                )
328
            ) {
329
                $this->build->setErrorsTotalPrevious((int)$trend[1]['count']);
330
            }
331
        }
332
    }
333
334
    /**
335
     * Used by this class, and plugins, to execute shell commands.
336
     *
337
     * @param array ...$params
338
     *
339
     * @return bool
340
     */
341
    public function executeCommand(...$params)
342
    {
343
        return $this->commandExecutor->executeCommand($params);
344
    }
345
346
    /**
347
     * Returns the output from the last command run.
348
     *
349
     * @return string
350
     */
351
    public function getLastOutput()
352
    {
353
        return $this->commandExecutor->getLastOutput();
354
    }
355
356
    /**
357
     * Specify whether exec output should be logged.
358
     *
359
     * @param bool $enableLog
360
     */
361
    public function logExecOutput($enableLog = true)
362
    {
363
        $this->commandExecutor->logExecOutput = $enableLog;
364
    }
365
366
    /**
367
     * Find a binary required by a plugin.
368
     *
369
     * @param  array|string $binary
370
     * @param  string       $priorityPath
371
     * @param  string       $binaryPath
372
     * @param  array        $binaryName
373
     * @return string
374
     *
375
     * @throws \Exception when no binary has been found.
376
     */
377
    public function findBinary($binary, $priorityPath = 'local', $binaryPath = '', $binaryName = [])
378
    {
379
        return $this->commandExecutor->findBinary($binary, $priorityPath, $binaryPath, $binaryName);
380
    }
381
382
    /**
383
     * Replace every occurrence of the interpolation vars in the given string
384
     * Example: "This is build %PHPCI_BUILD%" => "This is build 182"
385
     *
386
     * @param string $input
387
     *
388
     * @return string
389
     */
390
    public function interpolate($input)
391
    {
392
        return $this->interpolator->interpolate($input);
393
    }
394
395
    /**
396
     * Set up a working copy of the project for building.
397
     *
398
     * @throws \Exception
399
     *
400
     * @return bool
401
     */
402
    protected function setupBuild()
403
    {
404
        $this->buildPath = $this->build->getBuildPath();
405
406
        $this->commandExecutor->setBuildPath($this->buildPath);
407
408
        $this->build->handleConfigBeforeClone($this);
409
410
        // Create a working copy of the project:
411
        if (!$this->build->createWorkingCopy($this, $this->buildPath)) {
412
            throw new \Exception('Could not create a working copy.');
413
        }
414
415
        chdir($this->buildPath);
416
417
        $this->interpolator->setupInterpolationVars(
418
            $this->build,
419
            APP_URL
420
        );
421
422
        // Does the project's .php-censor.yml request verbose mode?
423
        if (!isset($this->config['build_settings']['verbose']) || !$this->config['build_settings']['verbose']) {
424
            $this->verbose = false;
425
        }
426
427
        // Does the project have any paths it wants plugins to ignore?
428
        if (!empty($this->config['build_settings']['ignore'])) {
429
            $this->ignore = $this->config['build_settings']['ignore'];
430
        }
431
432
        if (!empty($this->config['build_settings']['binary_path'])) {
433
            $this->binaryPath = rtrim(
0 ignored issues
show
Documentation Bug introduced by
It seems like rtrim($this->interpolate...y_path']), '/\\') . '/' of type string is incompatible with the declared type array<integer,string> of property $binaryPath.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
434
                $this->interpolate($this->config['build_settings']['binary_path']),
435
                '/\\'
436
            ) . '/';
437
        }
438
439
        if (!empty($this->config['build_settings']['priority_path']) 
440
            && in_array(
441
                $this->config['build_settings']['priority_path'],
442
                Plugin::AVAILABLE_PRIORITY_PATHS,
443
                true
444
            )
445
        ) {
446
            $this->priorityPath = $this->config['build_settings']['priority_path'];
447
        }
448
449
        $directory = $this->buildPath;
450
451
        // Does the project have a global directory for plugins ?
452
        if (!empty($this->config['build_settings']['directory'])) {
453
            $directory = $this->config['build_settings']['directory'];
454
        }
455
456
        $this->directory = rtrim(
457
            $this->interpolate($directory),
458
            '/\\'
459
        ) . '/';
460
461
        $this->buildLogger->logSuccess(sprintf('Working copy created: %s', $this->buildPath));
462
463
        return true;
464
    }
465
466
    /**
467
     * Sets a logger instance on the object
468
     *
469
     * @param LoggerInterface $logger
470
     */
471
    public function setLogger(LoggerInterface $logger)
472
    {
473
        $this->buildLogger->setLogger($logger);
474
    }
475
476
    /**
477
     * Write to the build log.
478
     *
479
     * @param string $message
480
     * @param string $level
481
     * @param array  $context
482
     */
483
    public function log($message, $level = LogLevel::INFO, $context = [])
484
    {
485
        $this->buildLogger->log($message, $level, $context);
486
    }
487
488
    /**
489
     * Add a warning-coloured message to the log.
490
     *
491
     * @param string $message
492
     */
493
    public function logWarning($message)
494
    {
495
        $this->buildLogger->logWarning($message);
496
    }
497
498
    /**
499
     * Add a success-coloured message to the log.
500
     *
501
     * @param string $message
502
     */
503
    public function logSuccess($message)
504
    {
505
        $this->buildLogger->logSuccess($message);
506
    }
507
508
    /**
509
     * Add a failure-coloured message to the log.
510
     *
511
     * @param string     $message
512
     * @param \Exception $exception The exception that caused the error.
513
     */
514
    public function logFailure($message, \Exception $exception = null)
515
    {
516
        $this->buildLogger->logFailure($message, $exception);
517
    }
518
519
    /**
520
     * Add a debug-coloured message to the log.
521
     *
522
     * @param string $message
523
     */
524
    public function logDebug($message)
525
    {
526
        $this->buildLogger->logDebug($message);
527
    }
528
529
    /**
530
     * Returns a configured instance of the plugin factory.
531
     *
532
     * @param Build $build
533
     *
534
     * @return PluginFactory
535
     */
536
    private function buildPluginFactory(Build $build)
537
    {
538
        $pluginFactory = new PluginFactory();
539
540
        $self = $this;
541
        $pluginFactory->registerResource(
542
            function () use ($self) {
543
                return $self;
544
            },
545
            null,
546
            'PHPCensor\Builder'
547
        );
548
549
        $pluginFactory->registerResource(
550
            function () use ($build) {
551
                return $build;
552
            },
553
            null,
554
            'PHPCensor\Model\Build'
555
        );
556
557
        $logger = $this->logger;
558
        $pluginFactory->registerResource(
559
            function () use ($logger) {
560
                return $logger;
561
            },
562
            null,
563
            'Psr\Log\LoggerInterface'
564
        );
565
566
        $pluginFactory->registerResource(
567
            function () use ($self) {
568
                $factory = new MailerFactory($self->getSystemConfig('php-censor'));
569
                return $factory->getSwiftMailerFromConfig();
570
            },
571
            null,
572
            'Swift_Mailer'
573
        );
574
575
        return $pluginFactory;
576
    }
577
578
    /**
579
     * @return BuildErrorWriter
580
     */
581
    public function getBuildErrorWriter()
582
    {
583
        return $this->buildErrorWriter;
584
    }
585
}
586