Issues (524)

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.

code/model/jobs/DNDeployment.php (12 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
/**
4
 * Class representing a single deplyoment (passed or failed) at a time to a particular environment
5
 *
6
 * @property string $SHA
7
 * @property string $ResqueToken
8
 * @property string $State
9
 * @property int $RefType
10
 * @property string $RefName
11
 * @property SS_Datetime $DeployStarted
12
 * @property SS_Datetime $DeployRequested
13
 *
14
 * @method DNEnvironment Environment()
15
 * @property int EnvironmentID
16
 * @method Member Deployer()
17
 * @property int DeployerID
18
 */
19
class DNDeployment extends DataObject implements Finite\StatefulInterface, HasStateMachine {
0 ignored issues
show
The property $has_one is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
The property $default_sort is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
The property $summary_fields is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
20
21
	const STATE_NEW = 'New';
22
	const STATE_SUBMITTED = 'Submitted';
23
	const STATE_INVALID = 'Invalid';
24
	const STATE_APPROVED = 'Approved';
25
	const STATE_REJECTED = 'Rejected';
26
	const STATE_QUEUED = 'Queued';
27
	const STATE_DEPLOYING = 'Deploying';
28
	const STATE_ABORTING = 'Aborting';
29
	const STATE_COMPLETED = 'Completed';
30
	const STATE_FAILED = 'Failed';
31
	// Deleted is used as 'soft-delete' which will in all regards keep the record in the DB
32
	// but not display it in the UI. This is for auditing and also for UI history updates
33
	const STATE_DELETED = 'Deleted';
34
35
	const TR_NEW = 'new';
36
	const TR_SUBMIT = 'submit';
37
	const TR_INVALIDATE = 'invalidate';
38
	const TR_APPROVE = 'approve';
39
	const TR_REJECT = 'reject';
40
	const TR_QUEUE = 'queue';
41
	const TR_DEPLOY = 'deploy';
42
	const TR_ABORT = 'abort';
43
	const TR_COMPLETE = 'complete';
44
	const TR_FAIL = 'fail';
45
	const TR_DELETE = 'delete';
46
47
	/**
48
	 * @var array
49
	 */
50
	private static $db = array(
51
		"SHA" => "GitSHA",
52
		"ResqueToken" => "Varchar(255)",
53
		// is it a branch, tag etc, see GitDispatcher REF_TYPE_* constants
54
		"RefType" => "Int",
55
		// The ref name that was used to deploy this, e.g. branch name or tag.
56
		// Can't really be inferred from Git history because the commit could appear in lots of
57
		// branches/tags that are irrelevant to the user when it comes to deployment history, and the reference
58
		// may have been deleted.
59
		"RefName" => "Varchar(255)",
60
		"State" => "Enum('New, Submitted, Invalid, Approved, Rejected, Queued, Deploying, Aborting, Completed, Failed, Deleted', 'New')",
0 ignored issues
show
This line exceeds maximum limit of 120 characters; contains 131 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
61
		// JSON serialised DeploymentStrategy.
62
		"Strategy" => "Text",
63
		"Title" => "Varchar(255)",
64
		"Summary" => "Text",
65
		// the date and time the deploy was queued
66
		"DeployStarted" => "SS_Datetime",
67
		// the date and time a deployment was requested to be approved
68
		"DeployRequested" => "SS_Datetime",
69
		"RejectedReason" => "Text"
70
	);
71
72
	/**
73
	 * @var array
74
	 */
75
	private static $has_one = array(
76
		"Environment" => "DNEnvironment",
77
		"Deployer" => "Member",
78
		"Approver" => "Member",
79
		"BackupDataTransfer" => "DNDataTransfer" // denotes an automated backup done for this deployment
80
	);
81
82
	private static $default_sort = '"LastEdited" DESC';
83
84
	private static $dependencies = [
85
		'stateMachineFactory' => '%$StateMachineFactory'
86
	];
87
88
	private static $summary_fields = array(
89
		'LastEdited' => 'Last Edited',
90
		'SHA' => 'SHA',
91
		'State' => 'State',
92
		'Deployer.Name' => 'Deployer'
93
	);
94
95
	public function setResqueToken($token) {
96
		$this->ResqueToken = $token;
97
	}
98
99
	public function getFiniteState() {
100
		return $this->State;
101
	}
102
103
	public function setFiniteState($state) {
104
		$this->State = $state;
105
		$this->write();
106
	}
107
108
	public function getStatus() {
109
		return $this->State;
110
	}
111
112
	public function getMachine() {
113
		return $this->stateMachineFactory->forDNDeployment($this);
0 ignored issues
show
The property stateMachineFactory does not exist on object<DNDeployment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
114
	}
115
116
	public function Link() {
117
		if ($this->Environment()->IsNewDeployEnabled()) {
118
			return \Controller::join_links($this->Environment()->Link(\EnvironmentOverview::ACTION_OVERVIEW), 'deployment', $this->ID);
0 ignored issues
show
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
119
		} else {
120
			return \Controller::join_links($this->Environment()->Link(), 'deploy', $this->ID);
121
		}
122
	}
123
124
	public function LogLink() {
125
		return $this->Link() . '/log';
126
	}
127
128
	public function canView($member = null) {
129
		return $this->Environment()->canView($member);
130
	}
131
132
	/**
133
	 * Return a path to the log file.
134
	 * @return string
135
	 */
136
	protected function logfile() {
137
		return sprintf(
138
			'%s.%s.log',
139
			$this->Environment()->getFullName('.'),
140
			$this->ID
141
		);
142
	}
143
144
	/**
145
	 * @return DeploynautLogFile
146
	 */
147
	public function log() {
148
		return Injector::inst()->createWithArgs('DeploynautLogFile', array($this->logfile()));
149
	}
150
151
	public function LogContent() {
152
		return $this->log()->content();
153
	}
154
155
	/**
156
	 * This remains here for backwards compatibility - we don't want to expose Resque status in here.
157
	 * Resque job (DeployJob) will change statuses as part of its execution.
158
	 *
159
	 * @return string
160
	 */
161
	public function ResqueStatus() {
162
		return $this->State;
163
	}
164
165
	/**
166
	 * Fetch the git repository
167
	 *
168
	 * @return \Gitonomy\Git\Repository|null
169
	 */
170
	public function getRepository() {
171
		if(!$this->SHA) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->SHA of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
172
			return null;
173
		}
174
		return $this->Environment()->Project()->getRepository();
175
	}
176
177
	/**
178
	 * Gets the commit from source. The result is cached upstream in Repository.
179
	 *
180
	 * @return \Gitonomy\Git\Commit|null
181
	 */
182 View Code Duplication
	public function getCommit() {
0 ignored issues
show
This method seems to be duplicated in 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...
183
		$repo = $this->getRepository();
184
		if($repo) {
185
			try {
186
				return $this->Environment()->getCommit($this->SHA);
187
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $ex) {
188
				return null;
189
			}
190
		}
191
192
		return null;
193
	}
194
195
	/**
196
	 * Get the commit URL to the commit associated with this deployment.
197
	 * @return null|string
198
	 */
199
	public function getCommitURL() {
200
		$environment = $this->Environment();
201
		if (!$environment) {
202
			return null;
203
		}
204
		$project = $environment->Project();
205
		if (!$project) {
206
			return null;
207
		}
208
		$interface = $project->getRepositoryInterface();
209
		if (!$interface) {
210
			return null;
211
		}
212
		return $interface->CommitURL . '/' . $this->SHA;
213
	}
214
215
	/**
216
	 * Gets the commit message.
217
	 *
218
	 * @return string|null
219
	 */
220 View Code Duplication
	public function getCommitMessage() {
0 ignored issues
show
This method seems to be duplicated in 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...
221
		$commit = $this->getCommit();
222
		if($commit) {
223
			try {
224
				return Convert::raw2xml($this->Environment()->getCommitMessage($commit));
225
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $e) {
226
				return null;
227
			}
228
		}
229
		return null;
230
	}
231
232
	/**
233
	 * Gets the commit message.
234
	 *
235
	 * @return string|null
236
	 */
237 View Code Duplication
	public function getCommitSubjectMessage() {
0 ignored issues
show
This method seems to be duplicated in 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...
238
		$commit = $this->getCommit();
239
		if($commit) {
240
			try {
241
				return Convert::raw2xml($this->Environment()->getCommitSubjectMessage($commit));
242
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $e) {
243
				return null;
244
			}
245
		}
246
		return null;
247
	}
248
249
	/**
250
	 * Return all tags for the deployed commit.
251
	 *
252
	 * @return ArrayList
253
	 */
254
	public function getTags() {
255
		$commit = $this->Environment()->getCommit($this->SHA);
256
		if(!$commit) {
257
			return new ArrayList([]);
258
		}
259
		$tags = $this->Environment()->getCommitTags($commit);
260
		$returnTags = [];
261
		if (!empty($tags)) {
262
			foreach($tags as $tag) {
263
				$field = Varchar::create('Tag', '255');
264
				$field->setValue($tag->getName());
265
				$returnTags[] = $field;
266
			}
267
		}
268
		return new ArrayList($returnTags);
269
	}
270
271
	/**
272
	 * Collate the list of additional flags to affix to this deployment.
273
	 * Elements of the array will be rendered literally.
274
	 *
275
	 * @return ArrayList
276
	 */
277
	public function getFullDeployMessages() {
278
		$strategy = $this->getDeploymentStrategy();
279
		if ($strategy->getActionCode()!=='full') return null;
280
281
		$changes = $strategy->getChangesModificationNeeded();
282
		$messages = [];
283
		foreach ($changes as $change => $details) {
284
			if ($change==='Code version') continue;
285
286
			$messages[] = [
287
				'Flag' => sprintf(
288
					'<span class="label label-default full-deploy-info-item">%s</span>',
289
					$change[0]
290
				),
291
				'Text' => sprintf('%s changed', $change)
292
			];
293
		}
294
295
		if (empty($messages)) {
296
			$messages[] = [
297
				'Flag' => '',
298
				'Text' => '<i>Environment changes have been made.</i>'
299
			];
300
		}
301
302
		return new ArrayList($messages);
303
	}
304
305
	/**
306
	 * Fetches the latest tag for the deployed commit
307
	 *
308
	 * @return \Varchar|null
309
	 */
310
	public function getTag() {
311
		$tags = $this->getTags();
312
		if($tags->count() > 0) {
313
			return $tags->last();
314
		}
315
		return null;
316
	}
317
318
	/**
319
	 * @return DeploymentStrategy
320
	 */
321
	public function getDeploymentStrategy() {
322
		$environment = $this->Environment();
323
		$strategy = new DeploymentStrategy($environment);
324
		$strategy->fromJSON($this->Strategy);
0 ignored issues
show
The property Strategy does not exist on object<DNDeployment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
325
		return $strategy;
326
	}
327
328
	/**
329
	 * Return a list of things that are going to be deployed, such
330
	 * as the code version, and any infrastructural changes.
331
	 *
332
	 * @return ArrayList
333
	 */
334
	public function getChanges() {
335
		$list = new ArrayList();
336
		$strategy = $this->getDeploymentStrategy();
337
		foreach($strategy->getChanges() as $name => $change) {
338
			$changed = (isset($change['from']) && isset($change['to'])) ? $change['from'] != $change['to'] : null;
339
			$description = isset($change['description']) ? $change['description'] : '';
340
			$compareUrl = null;
341
342
			// if there is a compare URL, and a description or a change (something actually changed)
343
			// then show the URL. Otherwise don't show anything, as there is no comparison to be made.
344
			if ($changed || $description) {
345
				$compareUrl = isset($change['compareUrl']) ? $change['compareUrl'] : '';
346
			}
347
348
			$list->push(new ArrayData([
349
				'Name' => $name,
350
				'From' => isset($change['from']) ? $change['from'] : null,
351
				'To' => isset($change['to']) ? $change['to'] : null,
352
				'Description' => $description,
353
				'Changed' => $changed,
354
				'CompareUrl' => $compareUrl
355
			]));
356
		}
357
358
		return $list;
359
	}
360
361
	/**
362
	 * Start a resque job for this deployment
363
	 *
364
	 * @return string Resque token
365
	 */
366
	public function enqueueDeployment() {
367
		$environment = $this->Environment();
368
		$project = $environment->Project();
369
		$log = $this->log();
370
371
		$args = array(
372
			'environmentName' => $environment->Name,
373
			'repository' => $project->getLocalCVSPath(),
374
			'logfile' => $this->logfile(),
375
			'projectName' => $project->Name,
376
			'env' => $project->getProcessEnv(),
377
			'deploymentID' => $this->ID,
378
			'sigFile' => $this->getSigFile(),
379
		);
380
381
		$strategy = $this->getDeploymentStrategy();
382
		// Inject options.
383
		$args = array_merge($args, $strategy->getOptions());
384
		// Make sure we use the SHA as it was written into this DNDeployment.
385
		$args['sha'] = $this->SHA;
386
387
		if(!$this->DeployerID) {
388
			$this->DeployerID = Member::currentUserID();
389
		}
390
391 View Code Duplication
		if($this->DeployerID) {
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...
392
			$deployer = $this->Deployer();
393
			$message = sprintf(
394
				'Deploy to %s initiated by %s (%s), with IP address %s',
395
				$environment->getFullName(),
396
				$deployer->getName(),
397
				$deployer->Email,
398
				\Controller::curr()->getRequest()->getIP()
399
			);
400
			$log->write($message);
401
		}
402
403
		return Resque::enqueue('deploy', 'DeployJob', $args, true);
404
	}
405
406
	public function getSigFile() {
407
		$dir = DNData::inst()->getSignalDir();
408
		if (!is_dir($dir)) {
409
			`mkdir $dir`;
410
		}
411
		return sprintf(
412
			'%s/deploynaut-signal-%s-%s',
413
			DNData::inst()->getSignalDir(),
414
			$this->ClassName,
415
			$this->ID
416
		);
417
	}
418
419
	/**
420
	 * Signal the worker to self-abort. If we had a reliable way of figuring out the right PID,
421
	 * we could posix_kill directly, but Resque seems to not provide a way to find out the PID
422
	 * from the job nor worker.
423
	 */
424
	public function setSignal($signal) {
425
		$sigFile = $this->getSigFile();
426
		// 2 is SIGINT - we can't use SIGINT constant in the Apache context, only available in workers.
427
		file_put_contents($sigFile, $signal);
428
	}
429
}
430