Issues (69)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  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.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  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.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  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.
  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.
  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.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  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.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  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.
  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.
  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.
  Header Injection
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.

js/personal.js (1 issue)

Severity
1
/**
2
 * CMS Pico - Create websites using Pico CMS for Nextcloud.
3
 *
4
 * @copyright Copyright (c) 2017, Maxence Lange (<[email protected]>)
5
 * @copyright Copyright (c) 2019, Daniel Rudolf (<[email protected]>)
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 * This program is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License as
11
 * published by the Free Software Foundation, either version 3 of the
12
 * License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22
23
/** global: OC */
24
/** global: OCA */
25
/** global: jQuery */
26
27
(function (document, $, OC, OCA) {
28
	'use strict';
29
30
	/** @constant {number} */
31
	var WEBSITE_TYPE_PUBLIC = 1;
0 ignored issues
show
The variable WEBSITE_TYPE_PUBLIC seems to be never used. Consider removing it.
Loading history...
32
33
	/** @constant {number} */
34
	var WEBSITE_TYPE_PRIVATE = 2;
35
36
	/**
37
	 * @class
38
	 * @extends OCA.CMSPico.List
39
	 *
40
	 * @param {jQuery}        $element
41
	 * @param {Object}        [options]
42
	 * @param {string}        [options.route]
43
	 * @param {jQuery|string} [options.template]
44
	 * @param {jQuery|string} [options.itemTemplate]
45
	 * @param {jQuery|string} [options.privateSettingsTemplate]
46
	 * @param {jQuery|string} [options.loadingTemplate]
47
	 * @param {jQuery|string} [options.errorTemplate]
48
	 * @param {string}        [options.websiteBaseUrl]
49
	 */
50
	OCA.CMSPico.WebsiteList = function ($element, options) {
51
		this.initialize($element, options);
52
	};
53
54
	/**
55
	 * @lends OCA.CMSPico.WebsiteList.prototype
56
	 */
57
	OCA.CMSPico.WebsiteList.prototype = $.extend({}, OCA.CMSPico.List.prototype, {
58
		/** @member {Object[]} */
59
		websites: [],
60
61
		/** @member {jQuery} */
62
		$itemTemplate: $(),
63
64
		/** @member {jQuery} */
65
		$privateSettingsTemplate: $(),
66
67
		/** @member {string} */
68
		websiteBaseUrl: '',
69
70
		/**
71
		 * @constructs
72
		 *
73
		 * @param {jQuery}        $element
74
		 * @param {Object}        [options]
75
		 * @param {string}        [options.route]
76
		 * @param {jQuery|string} [options.template]
77
		 * @param {jQuery|string} [options.itemTemplate]
78
		 * @param {jQuery|string} [options.privateSettingsTemplate]
79
		 * @param {jQuery|string} [options.loadingTemplate]
80
		 * @param {jQuery|string} [options.errorTemplate]
81
		 * @param {string}        [options.websiteBaseUrl]
82
		 */
83
		initialize: function ($element, options) {
84
			OCA.CMSPico.List.prototype.initialize.apply(this, arguments);
85
86
			options = $.extend({
87
				itemTemplate: $element.data('itemTemplate'),
88
				privateSettingsTemplate: $element.data('privateSettingsTemplate'),
89
				websiteBaseUrl: $element.data('websiteBaseUrl')
90
			}, options);
91
92
			this.$itemTemplate = $(options.itemTemplate);
93
			this.$privateSettingsTemplate = $(options.privateSettingsTemplate);
94
			this.websiteBaseUrl = options.websiteBaseUrl + ((options.websiteBaseUrl.slice(-1) !== '/') ? '/' : '');
95
96
			var signature = 'OCA.CMSPico.WebsiteList.initialize()';
97
			if (!this.$itemTemplate.length) {
98
				throw signature + ': No valid item template given';
99
			}
100
			if (!this.$privateSettingsTemplate.length) {
101
				throw signature + ': No valid private settings template given';
102
			}
103
			if (this.websiteBaseUrl === '/') {
104
				throw signature + ': No valid website base URL given';
105
			}
106
107
			this._init();
108
		},
109
110
		/**
111
		 * @private
112
		 */
113
		_init: function () {
114
			var that = this;
115
			$(document).on('click.CMSPicoWebsiteList', function (event) {
116
				var $target = $(event.target),
117
					$menu;
118
119
				if ($target.is('.icon-ellipsis')) {
120
					$menu = $target.nextAll('.popovermenu');
121
					if ($menu.length) {
122
						$menu.toggleClass('open');
123
					}
124
				} else {
125
					// if clicked inside the menu, don't close it
126
					$menu = $target.closest('.popovermenu');
127
				}
128
129
				that.$element.find('.popovermenu.open').not($menu).removeClass('open');
130
			});
131
		},
132
133
		/**
134
		 * @public
135
		 *
136
		 * @param {Object}   data
137
		 * @param {Object[]} data.websites
138
		 * @param {int}      data.websites[].id
139
		 * @param {string}   data.websites[].user_id
140
		 * @param {string}   data.websites[].name
141
		 * @param {string}   data.websites[].site
142
		 * @param {string}   data.websites[].theme
143
		 * @param {int}      data.websites[].type
144
		 * @param {Object}   data.websites[].options
145
		 * @param {string}   data.websites[].path
146
		 * @param {int}      data.websites[].creation
147
		 * @param {string}   data.websites[].timezone
148
		 */
149
		update: function (data) {
150
			this.websites = data.websites;
151
152
			this._content(this.$template);
153
154
			for (var i = 0, $website; i < data.websites.length; i++) {
155
				$website = this._content(this.$itemTemplate, data.websites[i]);
156
				this._setupItem($website, data.websites[i]);
157
			}
158
159
			this._setup();
160
		},
161
162
		/**
163
		 * @protected
164
		 */
165
		_setup: function () {
166
			this.$element.find('.live-relative-timestamp').each(function() {
167
				var $this = $(this),
168
					time = parseInt($this.data('timestamp'), 10) * 1000;
169
170
				$this
171
					.attr('data-timestamp', time)
172
					.text(OC.Util.relativeModifiedDate(time))
173
					.addClass('has-tooltip')
174
					.attr('title', OC.Util.formatDate(time))
175
					.tooltip();
176
			});
177
		},
178
179
		/**
180
		 * @protected
181
		 *
182
		 * @param {jQuery}   $website
183
		 * @param {Object}   websiteData
184
		 * @param {int}      websiteData.id
185
		 * @param {string}   websiteData.user_id
186
		 * @param {string}   websiteData.name
187
		 * @param {string}   websiteData.site
188
		 * @param {string}   websiteData.theme
189
		 * @param {int}      websiteData.type
190
		 * @param {Object}   websiteData.options
191
		 * @param {string[]} [websiteData.options.group_access]
192
		 * @param {string}   websiteData.path
193
		 * @param {int}      websiteData.creation
194
		 * @param {string}   websiteData.timezone
195
		 */
196
		_setupItem: function ($website, websiteData) {
197
			var that = this;
198
199
			// go to website
200
			var websiteUrl = this.websiteBaseUrl + websiteData.site;
201
			this._clickRedirect($website.find('.action-open'), websiteUrl);
202
203
			// go to website directory
204
			var filesUrl = OC.generateUrl('/apps/files/') + '?dir=' + OC.encodePath(websiteData.path);
205
			this._clickRedirect($website.find('.action-files'), filesUrl);
206
207
			// edit private websites settings
208
			var websitePrivate = (websiteData.type === WEBSITE_TYPE_PRIVATE),
209
				websiteGroupAccess = (websiteData.options || {}).group_access || [];
210
			$website.find('.action-private').each(function () {
211
				var $this = $(this),
212
					$icon = $this.find('[class^="icon-"], [class*=" icon-"]');
213
214
				$icon
215
					.addClass(websitePrivate ? 'icon-lock' : 'icon-lock-open')
216
					.removeClass(websitePrivate ? 'icon-lock-open' : 'icon-lock');
217
218
				var dialog = new OCA.CMSPico.Dialog(that.$privateSettingsTemplate, {
219
					title: $this.data('originalTitle') || $this.prop('title') || $this.text(),
220
					buttons: [
221
						{ type: OCA.CMSPico.Dialog.BUTTON_ABORT },
222
						{ type: OCA.CMSPico.Dialog.BUTTON_SUBMIT }
223
					]
224
				});
225
226
				dialog.on('open.CMSPicoWebsiteList', function () {
227
					var $inputType = this.$element.find('.input-private-' + (!websitePrivate ? 'public' : 'private')),
228
						$inputGroups = this.$element.find('.input-private-groups');
229
230
					$inputType.prop('checked', true);
231
					$inputGroups.val(websiteGroupAccess.join('|'));
232
					OC.Settings.setupGroupsSelect($inputGroups);
233
				});
234
235
				dialog.on('submit.CMSPicoWebsiteList', function () {
236
					var data = OCA.CMSPico.Util.serialize(this.$element);
237
					that._updateItem(websiteData.id, data);
238
				});
239
240
				$this.on('click.CMSPicoWebsiteList', function (event) {
241
					dialog.open();
242
				});
243
			});
244
245
			// change website name
246
			var nameEditable = new OCA.CMSPico.Editable(
247
				$website.find('.name > p'),
248
				$website.find('.name-edit > input')
249
			);
250
251
			nameEditable.on('submit.CMSPicoWebsiteList', function (value, defaultValue) {
252
				if (value !== defaultValue) {
253
					that._updateItem(websiteData.id, { name: value });
254
				}
255
			});
256
257
			$website.find('.action-rename').on('click.CMSPicoWebsiteList', function (event) {
258
				nameEditable.toggle();
259
			});
260
261
			// change website theme
262
			$website.find('.action-theme').each(function () {
263
				var $this = $(this);
264
265
				$this.val(websiteData.theme);
266
267
				$this.on('change.CMSPicoWebsiteList', function (event) {
268
					that._updateItem(websiteData.id, { theme: $(this).val() });
269
				});
270
			});
271
272
			// delete website
273
			$website.find('.action-delete').on('click.CMSPicoWebsiteList', function (event) {
274
				var dialogTitle = t('cms_pico', 'Confirm website deletion'),
275
					dialogText = t('cms_pico', 'This operation will delete the website "{name}". However, all of ' +
276
							'its contents will still be available in your Nextcloud.', { name: websiteData.name });
277
278
				OC.dialogs.confirm(dialogText, dialogTitle, function (result) {
279
					if (result) {
280
						that._api('DELETE', '' + websiteData.id);
281
					}
282
				});
283
			});
284
		},
285
286
		/**
287
		 * @private
288
		 *
289
		 * @param {jQuery} $elements
290
		 * @param {string} url
291
		 */
292
		_clickRedirect: function ($elements, url) {
293
			$elements.each(function () {
294
				var $element = $(this);
295
296
				if ($element.is('a')) {
297
					$element.attr('href', url);
298
				} else {
299
					$element.on('click.CMSPicoWebsiteList', function (event) {
300
						OC.redirect(url);
301
					});
302
				}
303
			});
304
		},
305
306
		/**
307
		 * @private
308
		 *
309
		 * @param {number} item
310
		 * @param {Object} data
311
		 */
312
		_updateItem: function (item, data) {
313
			this._api('POST', '' + item, { data: data });
314
		}
315
	});
316
317
	$('.picocms-website-list').each(function () {
318
		var $this = $(this),
319
			websiteList = new OCA.CMSPico.WebsiteList($this);
320
321
		$this.data('CMSPicoWebsiteList', websiteList);
322
		websiteList.reload();
323
	});
324
325
	/**
326
	 * @class
327
	 * @extends OCA.CMSPico.Form
328
	 *
329
	 * @param {jQuery}        $element
330
	 * @param {Object}        [options]
331
	 * @param {string}        [options.route]
332
	 * @param {jQuery|string} [options.errorTemplate]
333
	 */
334
	OCA.CMSPico.WebsiteForm = function ($element, options) {
335
		this.initialize($element, options);
336
	};
337
338
	/**
339
	 * @lends OCA.CMSPico.WebsiteForm.prototype
340
	 */
341
	OCA.CMSPico.WebsiteForm.prototype = $.extend({}, OCA.CMSPico.Form.prototype, {
342
		/** @member {jQuery} */
343
		$errorTemplate: $(),
344
345
		/**
346
		 * @constructs
347
		 *
348
		 * @param {jQuery}        $element
349
		 * @param {Object}        [options]
350
		 * @param {string}        [options.route]
351
		 * @param {jQuery|string} [options.errorTemplate]
352
		 */
353
		initialize: function ($element, options) {
354
			OCA.CMSPico.Form.prototype.initialize.apply(this, arguments);
355
356
			options = $.extend({ errorTemplate: $element.data('errorTemplate') }, options);
357
358
			this.$errorTemplate = $(options.errorTemplate);
359
360
			if (!this.$errorTemplate.length) {
361
				throw 'OCA.CMSPico.WebsiteForm.initialize(): No valid error template given';
362
			}
363
		},
364
365
		/**
366
		 * @public
367
		 */
368
		prepare: function () {
369
			var that = this,
370
				$form = this.$element.find('form'),
371
				$site = $form.find('.input-site'),
372
				$path = $form.find('.input-path');
373
374
			this._inputSite($site);
375
376
			$site.on('input.CMSPicoWebsiteForm', function (event) {
377
				that._inputSite($(this));
378
			});
379
380
			$path.on('click.CMSPicoWebsiteForm', function (event) {
381
				OC.dialogs.filepicker(
382
					t('cms_pico', 'Choose website directory'),
383
					function (path, type) {
384
						$path.val(path + '/' + $site.val());
385
					},
386
					false,
387
					'httpd/unix-directory',
388
					true
389
				);
390
			});
391
392
			$form.on('submit.CMSPicoWebsiteForm', function (event) {
393
				event.preventDefault();
394
				that.submit();
395
			});
396
		},
397
398
		/**
399
		 * @public
400
		 */
401
		submit: function () {
402
			var $form = this.$element.find('form'),
403
				$submitButton = this.$element.find('.form-submit'),
404
				$loadingButton = this.$element.find('.form-submit-loading'),
405
				data = OCA.CMSPico.Util.serialize($form),
406
				that = this;
407
408
			$form.find('fieldset.form-error')
409
				.removeClass('form-error');
410
411
			$submitButton.hide();
412
			$loadingButton.show();
413
414
			$.ajax({
415
				method: 'POST',
416
				url: OC.generateUrl(this.route),
417
				data: { data: data }
418
			}).done(function (data, textStatus, jqXHR) {
419
				that._success(data);
420
			}).fail(function (jqXHR, textStatus, errorThrown) {
421
				that._formError(jqXHR.responseJSON);
422
423
				$submitButton.show();
424
				$loadingButton.hide();
425
			});
426
		},
427
428
		/**
429
		 * @private
430
		 *
431
		 * @param {jQuery} $site
432
		 */
433
		_inputSite: function ($site) {
434
			var $form = this.$element.find('form'),
435
				$address = $form.find('.input-address'),
436
				$path = $form.find('.input-path'),
437
				value = this._val($site).replace(/[\/\\]/g, '');
438
439
			this._val($site, value);
440
			this._val($address, OC.dirname(this._val($address)) + '/' + value);
441
			this._val($path, OC.dirname(this._val($path)) + '/' + value);
442
		},
443
444
		/**
445
		 * @private
446
		 *
447
		 * @param {Object} data
448
		 */
449
		_success: function (data) {
450
			OC.reload();
451
		},
452
453
		/**
454
		 * @private
455
		 *
456
		 * @param {Object}  [responseData]
457
		 * @param {string}  [responseData.error]
458
		 * @param {string}  [responseData.errorField]
459
		 * @param {int}     [responseData.status]
460
		 * @param {string}  [responseData.exception]
461
		 * @param {?string} [responseData.exceptionMessage]
462
		 * @param {?int}    [responseData.exceptionCode]
463
		 */
464
		_formError: function (responseData) {
465
			responseData = responseData || {};
466
467
			if (responseData.error && responseData.errorField) {
468
				var $inputErrorContainer = this.$element.find('.input-' + responseData.errorField + '-error');
469
470
				if ($inputErrorContainer.length) {
471
					var $inputError = $('<p></p>').text(responseData.error);
472
473
					$inputErrorContainer.empty();
474
					$inputErrorContainer.append($inputError);
475
					$inputErrorContainer.closest('fieldset').addClass('form-error');
476
					return;
477
				}
478
			}
479
480
			var $formErrorContainer = this.$element.find('.input-unknown-error'),
481
				$formError = this.$errorTemplate.octemplate(responseData);
482
483
			if (responseData.error) {
484
				$formError.filter('.error-details').show();
485
			}
486
487
			if (responseData.exception) {
488
				$formError.filter('.exception-details').show();
489
			}
490
491
			$formErrorContainer.empty();
492
			$formErrorContainer.append($formError);
493
			$formErrorContainer.closest('fieldset').addClass('form-error');
494
		}
495
	});
496
497
	$('.picocms-website-form').each(function () {
498
		var $this = $(this),
499
			websiteForm = new OCA.CMSPico.WebsiteForm($this);
500
501
		$this.data('CMSPicoWebsiteForm', websiteForm);
502
		websiteForm.prepare();
503
	});
504
})(document, jQuery, OC, OCA);
505