Issues (1798)

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.

apps/files_external/js/settings.js (3 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
/*
2
 * Copyright (c) 2014
3
 *
4
 * This file is licensed under the Affero General Public License version 3
5
 * or later.
6
 *
7
 * See the COPYING-README file.
8
 *
9
 */
10
11
(function(){
12
13
// TODO: move to a separate file
14
var MOUNT_OPTIONS_DROPDOWN_TEMPLATE =
15
	'<div class="drop dropdown mountOptionsDropdown">' +
16
	// FIXME: options are hard-coded for now
17
	'	<div class="optionRow">' +
18
	'		<input id="mountOptionsEncrypt" name="encrypt" type="checkbox" value="true" checked="checked"/>' +
19
	'		<label for="mountOptionsEncrypt">{{t "files_external" "Enable encryption"}}</label>' +
20
	'	</div>' +
21
	'	<div class="optionRow">' +
22
	'		<input id="mountOptionsPreviews" name="previews" type="checkbox" value="true" checked="checked"/>' +
23
	'		<label for="mountOptionsPreviews">{{t "files_external" "Enable previews"}}</label>' +
24
	'	</div>' +
25
	'	<div class="optionRow">' +
26
	'		<input id="mountOptionsSharing" name="enable_sharing" type="checkbox" value="true"/>' +
27
	'		<label for="mountOptionsSharing">{{t "files_external" "Enable sharing"}}</label>' +
28
	'	</div>' +
29
	'	<div class="optionRow">' +
30
	'		<label for="mountOptionsFilesystemCheck">{{t "files_external" "Check for changes"}}</label>' +
31
	'		<select id="mountOptionsFilesystemCheck" name="filesystem_check_changes" data-type="int">' +
32
	'			<option value="0">{{t "files_external" "Never"}}</option>' +
33
	'			<option value="1" selected="selected">{{t "files_external" "Once every direct access"}}</option>' +
34
	'		</select>' +
35
	'	</div>' +
36
	'	<div class="optionRow">' +
37
	'		<input id="mountOptionsEncoding" name="encoding_compatibility" type="checkbox" value="true"/>' +
38
	'		<label for="mountOptionsEncoding">{{mountOptionsEncodingLabel}}</label>' +
39
	'	</div>' +
40
	'</div>';
41
42
/**
43
 * Returns the selection of applicable users in the given configuration row
44
 *
45
 * @param $row configuration row
46
 * @return array array of user names
47
 */
48
function getSelection($row) {
49
	var values = $row.find('.applicableUsers').select2('val');
50
	if (!values || values.length === 0) {
51
		values = [];
52
	}
53
	return values;
54
}
55
56
function highlightBorder($element, highlight) {
57
	$element.toggleClass('warning-input', highlight);
58
	return highlight;
59
}
60
61
function isInputValid($input) {
62
	var optional = $input.hasClass('optional');
63
	switch ($input.attr('type')) {
64
		case 'text':
65
		case 'password':
66
			if ($input.val() === '' && !optional) {
67
				return false;
68
			}
69
			break;
70
	}
71
	return true;
72
}
73
74
function highlightInput($input) {
75
	switch ($input.attr('type')) {
76
		case 'text':
77
		case 'password':
78
			return highlightBorder($input, !isInputValid($input));
79
	}
80
}
81
82
/**
83
 * Initialize select2 plugin on the given elements
84
 *
85
 * @param {Array<Object>} array of jQuery elements
86
 * @param {int} userListLimit page size for result list
87
 */
88
function addSelect2 ($elements, userListLimit) {
89
	if (!$elements.length) {
90
		return;
91
	}
92
	$elements.select2({
93
		placeholder: t('files_external', 'All users. Type to select user or group.'),
94
		allowClear: true,
95
		multiple: true,
96
		//minimumInputLength: 1,
97
		ajax: {
98
			url: OC.generateUrl('apps/files_external/applicable'),
99
			dataType: 'json',
100
			quietMillis: 100,
101
			data: function (term, page) { // page is the one-based page number tracked by Select2
102
				return {
103
					pattern: term, //search term
104
					limit: userListLimit, // page size
105
					offset: userListLimit*(page-1) // page number starts with 0
106
				};
107
			},
108
			results: function (data) {
109
				if (data.status === 'success') {
110
111
					var results = [];
112
					var userCount = 0; // users is an object
113
114
					// add groups
115
					$.each(data.groups, function(i, group) {
116
						results.push({name:group+'(group)', displayname:group, type:'group' });
117
					});
118
					// add users
119
					$.each(data.users, function(id, user) {
120
						userCount++;
121
						results.push({name:id, displayname:user, type:'user' });
122
					});
123
124
125
					var more = (userCount >= userListLimit) || (data.groups.length >= userListLimit);
126
					return {results: results, more: more};
127
				} else {
128
					//FIXME add error handling
129
				}
130
			}
131
		},
132
		initSelection: function(element, callback) {
133
			var users = {};
134
			users['users'] = [];
135
			var toSplit = element.val().split(",");
136
			for (var i = 0; i < toSplit.length; i++) {
137
				users['users'].push(toSplit[i]);
138
			}
139
140
			$.ajax(OC.generateUrl('displaynames'), {
141
				type: 'POST',
142
				contentType: 'application/json',
143
				data: JSON.stringify(users),
144
				dataType: 'json'
145
			}).done(function(data) {
146
				var results = [];
147
				if (data.status === 'success') {
148
					$.each(data.users, function(user, displayname) {
149
						if (displayname !== false) {
150
							results.push({name:user, displayname:displayname, type:'user'});
151
						}
152
					});
153
					callback(results);
154
				} else {
155
					//FIXME add error handling
156
				}
157
			});
158
		},
159
		id: function(element) {
160
			return element.name;
161
		},
162
		formatResult: function (element) {
163
			var $result = $('<span><div class="avatardiv"/><span>'+escapeHTML(element.displayname)+'</span></span>');
164
			var $div = $result.find('.avatardiv')
165
				.attr('data-type', element.type)
166
				.attr('data-name', element.name)
167
				.attr('data-displayname', element.displayname);
168
			if (element.type === 'group') {
169
				var url = OC.imagePath('core','places/contacts-dark'); // TODO better group icon
170
				$div.html('<img width="32" height="32" src="'+url+'">');
171
			}
172
			return $result.get(0).outerHTML;
173
		},
174
		formatSelection: function (element) {
175
			if (element.type === 'group') {
176
				return '<span title="'+escapeHTML(element.name)+'" class="group">'+escapeHTML(element.displayname+' '+t('files_external', '(group)'))+'</span>';
177
			} else {
178
				return '<span title="'+escapeHTML(element.name)+'" class="user">'+escapeHTML(element.displayname)+'</span>';
179
			}
180
		},
181
		escapeMarkup: function (m) { return m; } // we escape the markup in formatResult and formatSelection
182
	}).on('select2-loaded', function() {
183
		$.each($('.avatardiv'), function(i, div) {
184
			var $div = $(div);
185
			if ($div.data('type') === 'user') {
186
				$div.avatar($div.data('name'),32);
187
			}
188
		});
189
	});
190
}
191
192
/**
193
 * @class OCA.External.Settings.StorageConfig
194
 *
195
 * @classdesc External storage config
196
 */
197
var StorageConfig = function(id) {
198
	this.id = id;
199
	this.backendOptions = {};
200
};
201
// Keep this in sync with \OC_Mount_Config::STATUS_*
202
StorageConfig.Status = {
203
	IN_PROGRESS: -1,
204
	SUCCESS: 0,
205
	ERROR: 1,
206
	INDETERMINATE: 2
207
};
208
StorageConfig.Visibility = {
209
	NONE: 0,
210
	PERSONAL: 1,
211
	ADMIN: 2,
212
	DEFAULT: 3
213
};
214
/**
215
 * @memberof OCA.External.Settings
216
 */
217
StorageConfig.prototype = {
218
	_url: null,
219
220
	/**
221
	 * Storage id
222
	 *
223
	 * @type int
224
	 */
225
	id: null,
226
227
	/**
228
	 * Mount point
229
	 *
230
	 * @type string
231
	 */
232
	mountPoint: '',
233
234
	/**
235
	 * Backend
236
	 *
237
	 * @type string
238
	 */
239
	backend: null,
240
241
	/**
242
	 * Authentication mechanism
243
	 *
244
	 * @type string
245
	 */
246
	authMechanism: null,
247
248
	/**
249
	 * Backend-specific configuration
250
	 *
251
	 * @type Object.<string,object>
252
	 */
253
	backendOptions: null,
254
255
	/**
256
	 * Mount-specific options
257
	 *
258
	 * @type Object.<string,object>
259
	 */
260
	mountOptions: null,
261
262
	/**
263
	 * Creates or saves the storage.
264
	 *
265
	 * @param {Function} [options.success] success callback, receives result as argument
266
	 * @param {Function} [options.error] error callback
267
	 */
268
	save: function(options) {
269
		var self = this;
270
		var url = OC.generateUrl(this._url);
271
		var method = 'POST';
272
		if (_.isNumber(this.id)) {
273
			method = 'PUT';
274
			url = OC.generateUrl(this._url + '/{id}', {id: this.id});
275
		}
276
277
		$.ajax({
278
			type: method,
279
			url: url,
280
			contentType: 'application/json',
281
			data: JSON.stringify(this.getData()),
282
			success: function(result) {
283
				self.id = result.id;
284
				if (_.isFunction(options.success)) {
285
					options.success(result);
286
				}
287
			},
288
			error: options.error
289
		});
290
	},
291
292
	/**
293
	 * Returns the data from this object
294
	 *
295
	 * @return {Array} JSON array of the data
296
	 */
297
	getData: function() {
298
		var data = {
299
			mountPoint: this.mountPoint,
300
			backend: this.backend,
301
			authMechanism: this.authMechanism,
302
			backendOptions: this.backendOptions,
303
			testOnly: true
304
		};
305
		if (this.id) {
306
			data.id = this.id;
307
		}
308
		if (this.mountOptions) {
309
			data.mountOptions = this.mountOptions;
310
		}
311
		return data;
312
	},
313
314
	/**
315
	 * Recheck the storage
316
	 *
317
	 * @param {Function} [options.success] success callback, receives result as argument
318
	 * @param {Function} [options.error] error callback
319
	 */
320
	recheck: function(options) {
321
		if (!_.isNumber(this.id)) {
322
			if (_.isFunction(options.error)) {
323
				options.error();
324
			}
325
			return;
326
		}
327
		$.ajax({
328
			type: 'GET',
329
			url: OC.generateUrl(this._url + '/{id}', {id: this.id}),
330
			data: {'testOnly': true},
331
			success: options.success,
332
			error: options.error
333
		});
334
	},
335
336
	/**
337
	 * Deletes the storage
338
	 *
339
	 * @param {Function} [options.success] success callback
340
	 * @param {Function} [options.error] error callback
341
	 */
342
	destroy: function(options) {
343
		if (!_.isNumber(this.id)) {
344
			// the storage hasn't even been created => success
345
			if (_.isFunction(options.success)) {
346
				options.success();
347
			}
348
			return;
349
		}
350
		$.ajax({
351
			type: 'DELETE',
352
			url: OC.generateUrl(this._url + '/{id}', {id: this.id}),
353
			success: options.success,
354
			error: options.error
355
		});
356
	},
357
358
	/**
359
	 * Validate this model
360
	 *
361
	 * @return {boolean} false if errors exist, true otherwise
362
	 */
363
	validate: function() {
364
		if (this.mountPoint === '') {
365
			return false;
366
		}
367
		if (!this.backend) {
368
			return false;
369
		}
370
		if (this.errors) {
371
			return false;
372
		}
373
		return true;
374
	}
375
};
376
377
/**
378
 * @class OCA.External.Settings.GlobalStorageConfig
379
 * @augments OCA.External.Settings.StorageConfig
380
 *
381
 * @classdesc Global external storage config
382
 */
383
var GlobalStorageConfig = function(id) {
384
	this.id = id;
385
	this.applicableUsers = [];
386
	this.applicableGroups = [];
387
};
388
/**
389
 * @memberOf OCA.External.Settings
390
 */
391
GlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
392
	/** @lends OCA.External.Settings.GlobalStorageConfig.prototype */ {
393
	_url: 'apps/files_external/globalstorages',
394
395
	/**
396
	 * Applicable users
397
	 *
398
	 * @type Array.<string>
399
	 */
400
	applicableUsers: null,
401
402
	/**
403
	 * Applicable groups
404
	 *
405
	 * @type Array.<string>
406
	 */
407
	applicableGroups: null,
408
409
	/**
410
	 * Storage priority
411
	 *
412
	 * @type int
413
	 */
414
	priority: null,
415
416
	/**
417
	 * Returns the data from this object
418
	 *
419
	 * @return {Array} JSON array of the data
420
	 */
421
	getData: function() {
422
		var data = StorageConfig.prototype.getData.apply(this, arguments);
423
		return _.extend(data, {
424
			applicableUsers: this.applicableUsers,
425
			applicableGroups: this.applicableGroups,
426
			priority: this.priority,
427
		});
428
	}
429
});
430
431
/**
432
 * @class OCA.External.Settings.UserStorageConfig
433
 * @augments OCA.External.Settings.StorageConfig
434
 *
435
 * @classdesc User external storage config
436
 */
437
var UserStorageConfig = function(id) {
438
	this.id = id;
439
};
440
UserStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
441
	/** @lends OCA.External.Settings.UserStorageConfig.prototype */ {
442
	_url: 'apps/files_external/userstorages'
443
});
444
445
/**
446
 * @class OCA.External.Settings.UserGlobalStorageConfig
447
 * @augments OCA.External.Settings.StorageConfig
448
 *
449
 * @classdesc User external storage config
450
 */
451
var UserGlobalStorageConfig = function (id) {
452
	this.id = id;
453
};
454
UserGlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
455
	/** @lends OCA.External.Settings.UserStorageConfig.prototype */ {
456
457
	_url: 'apps/files_external/userglobalstorages'
458
});
459
460
/**
461
 * @class OCA.External.Settings.MountOptionsDropdown
462
 *
463
 * @classdesc Dropdown for mount options
464
 *
465
 * @param {Object} $container container DOM object
466
 */
467
var MountOptionsDropdown = function() {
468
};
469
/**
470
 * @memberof OCA.External.Settings
471
 */
472
MountOptionsDropdown.prototype = {
473
	/**
474
	 * Dropdown element
475
	 *
476
	 * @var Object
477
	 */
478
	$el: null,
479
480
	/**
481
	 * Show dropdown
482
	 *
483
	 * @param {Object} $container container
484
	 * @param {Object} mountOptions mount options
485
	 * @param {Array} visibleOptions enabled mount options
486
	 */
487
	show: function($container, mountOptions, visibleOptions) {
488
		if (MountOptionsDropdown._last) {
489
			MountOptionsDropdown._last.hide();
490
		}
491
492
		var template = MountOptionsDropdown._template;
493
		if (!template) {
494
			template = Handlebars.compile(MOUNT_OPTIONS_DROPDOWN_TEMPLATE);
495
			MountOptionsDropdown._template = template;
496
		}
497
498
		var $el = $(template({
499
			mountOptionsEncodingLabel: t('files_external', 'Compatibility with Mac NFD encoding (slow)'),
500
		}));
501
		this.$el = $el;
502
503
		this.setOptions(mountOptions, visibleOptions);
504
505
		this.$el.appendTo($container);
506
		MountOptionsDropdown._last = this;
507
508
		this.$el.trigger('show');
509
	},
510
511
	hide: function() {
512
		if (this.$el) {
513
			this.$el.trigger('hide');
514
			this.$el.remove();
515
			this.$el = null;
516
			MountOptionsDropdown._last = null;
517
		}
518
	},
519
520
	/**
521
	 * Returns the mount options from the dropdown controls
522
	 *
523
	 * @return {Object} options mount options
524
	 */
525
	getOptions: function() {
526
		var options = {};
527
528
		this.$el.find('input, select').each(function() {
529
			var $this = $(this);
530
			var key = $this.attr('name');
531
			var value = null;
532
			if ($this.attr('type') === 'checkbox') {
533
				value = $this.prop('checked');
534
			} else {
535
				value = $this.val();
536
			}
537
			if ($this.attr('data-type') === 'int') {
538
				value = parseInt(value, 10);
539
			}
540
			options[key] = value;
541
		});
542
		return options;
543
	},
544
545
	/**
546
	 * Sets the mount options to the dropdown controls
547
	 *
548
	 * @param {Object} options mount options
549
	 * @param {Array} visibleOptions enabled mount options
550
	 */
551
	setOptions: function(options, visibleOptions) {
552
		var $el = this.$el;
553
		_.each(options, function(value, key) {
554
			var $optionEl = $el.find('input, select').filterAttr('name', key);
555
			if ($optionEl.attr('type') === 'checkbox') {
556
				if (_.isString(value)) {
557
					value = (value === 'true');
558
				}
559
				$optionEl.prop('checked', !!value);
560
			} else {
561
				$optionEl.val(value);
562
			}
563
		});
564
		$el.find('.optionRow').each(function(i, row){
565
			var $row = $(row);
566
			var optionId = $row.find('input, select').attr('name');
567
			if (visibleOptions.indexOf(optionId) === -1) {
568
				$row.hide();
569
			} else {
570
				$row.show();
571
			}
572
		});
573
	}
574
};
575
576
/**
577
 * @class OCA.External.Settings.MountConfigListView
578
 *
579
 * @classdesc Mount configuration list view
580
 *
581
 * @param {Object} $el DOM object containing the list
582
 * @param {Object} [options]
583
 * @param {int} [options.userListLimit] page size in applicable users dropdown
584
 */
585
var MountConfigListView = function($el, options) {
586
	this.initialize($el, options);
587
};
588
589
MountConfigListView.ParameterFlags = {
590
	OPTIONAL: 1,
591
	USER_PROVIDED: 2
592
};
593
594
MountConfigListView.ParameterTypes = {
595
	TEXT: 0,
596
	BOOLEAN: 1,
597
	PASSWORD: 2,
598
	HIDDEN: 3
599
};
600
601
/**
602
 * @memberOf OCA.External.Settings
603
 */
604
MountConfigListView.prototype = _.extend({
605
606
	/**
607
	 * jQuery element containing the config list
608
	 *
609
	 * @type Object
610
	 */
611
	$el: null,
612
613
	/**
614
	 * Storage config class
615
	 *
616
	 * @type Class
617
	 */
618
	_storageConfigClass: null,
619
620
	/**
621
	 * Flag whether the list is about user storage configs (true)
622
	 * or global storage configs (false)
623
	 *
624
	 * @type bool
625
	 */
626
	_isPersonal: false,
627
628
	/**
629
	 * Page size in applicable users dropdown
630
	 *
631
	 * @type int
632
	 */
633
	_userListLimit: 30,
634
635
	/**
636
	 * List of supported backends
637
	 *
638
	 * @type Object.<string,Object>
639
	 */
640
	_allBackends: null,
641
642
	/**
643
	 * List of all supported authentication mechanisms
644
	 *
645
	 * @type Object.<string,Object>
646
	 */
647
	_allAuthMechanisms: null,
648
649
	_encryptionEnabled: false,
650
651
	/**
652
	 * @param {Object} $el DOM object containing the list
653
	 * @param {Object} [options]
654
	 * @param {int} [options.userListLimit] page size in applicable users dropdown
655
	 */
656
	initialize: function($el, options) {
657
		this.$el = $el;
658
		this._isPersonal = ($el.data('admin') !== true);
659
		if (this._isPersonal) {
660
			this._storageConfigClass = OCA.External.Settings.UserStorageConfig;
661
		} else {
662
			this._storageConfigClass = OCA.External.Settings.GlobalStorageConfig;
663
		}
664
665
		if (options && !_.isUndefined(options.userListLimit)) {
666
			this._userListLimit = options.userListLimit;
667
		}
668
669
		this._encryptionEnabled = options.encryptionEnabled;
670
		this._allowUserMountSharing = options.allowUserMountSharing;
671
672
		// read the backend config that was carefully crammed
673
		// into the data-configurations attribute of the select
674
		this._allBackends = this.$el.find('.selectBackend').data('configurations');
675
		this._allAuthMechanisms = this.$el.find('#addMountPoint .authentication').data('mechanisms');
676
	},
677
678
	/**
679
	 * Custom JS event handlers
680
	 * Trigger callback for all existing configurations
681
	 */
682
	whenSelectBackend: function(callback) {
683
		this.$el.find('tbody tr:not(#addMountPoint)').each(function(i, tr) {
684
			var backend = $(tr).find('.backend').data('identifier');
685
			callback($(tr), backend);
686
		});
687
		this.on('selectBackend', callback);
688
	},
689
	whenSelectAuthMechanism: function(callback) {
690
		var self = this;
691
		this.$el.find('tbody tr:not(#addMountPoint)').each(function(i, tr) {
692
			var authMechanism = $(tr).find('.selectAuthMechanism').val();
693
			if (authMechanism !== undefined) {
694
				var onCompletion = jQuery.Deferred();
695
				callback($(tr), authMechanism, self._allAuthMechanisms[authMechanism]['scheme'], onCompletion);
0 ignored issues
show
['scheme'] could be written in dot notation.

You can rewrite this statement in dot notation:

var obj = { };
obj['foo'] = 'bar'; // Bad
obj.foo = 'bar'; // Good
Loading history...
696
				onCompletion.resolve();
697
			}
698
		});
699
		this.on('selectAuthMechanism', callback);
700
	},
701
702
	/**
703
	 * Initialize DOM event handlers
704
	 */
705
	_initEvents: function() {
706
		var self = this;
707
708
		var onChangeHandler = _.bind(this._onChange, this);
709
		//this.$el.on('input', 'td input', onChangeHandler);
710
		this.$el.on('keyup', 'td input', onChangeHandler);
711
		this.$el.on('paste', 'td input', onChangeHandler);
712
		this.$el.on('change', 'td input:checkbox', onChangeHandler);
713
		this.$el.on('change', '.applicable', onChangeHandler);
714
715
		this.$el.on('click', '.status>span', function() {
716
			self.recheckStorageConfig($(this).closest('tr'));
717
		});
718
719
		this.$el.on('click', 'td.remove>img', function() {
720
			self.deleteStorageConfig($(this).closest('tr'));
721
		});
722
723
		this.$el.on('click', 'td.mountOptionsToggle>img', function() {
724
			self._showMountOptionsDropdown($(this).closest('tr'));
725
		});
726
727
		this.$el.on('change', '.selectBackend', _.bind(this._onSelectBackend, this));
728
		this.$el.on('change', '.selectAuthMechanism', _.bind(this._onSelectAuthMechanism, this));
729
	},
730
731
	_onChange: function(event) {
732
		var self = this;
733
		var $target = $(event.target);
734
		if ($target.closest('.dropdown').length) {
735
			// ignore dropdown events
736
			return;
737
		}
738
		highlightInput($target);
739
		var $tr = $target.closest('tr');
740
		this.updateStatus($tr, null);
741
742
		var timer = $tr.data('save-timer');
743
		clearTimeout(timer);
744
		timer = setTimeout(function() {
745
			self.saveStorageConfig($tr, null, timer);
746
		}, 2000);
747
		$tr.data('save-timer', timer);
748
	},
749
750
	_onSelectBackend: function(event) {
751
		var $target = $(event.target);
752
		var $tr = $target.closest('tr');
753
754
		var storageConfig = new this._storageConfigClass();
755
		storageConfig.mountPoint = $tr.find('.mountPoint input').val();
756
		storageConfig.backend = $target.val();
757
		$tr.find('.mountPoint input').val('');
758
759
		var onCompletion = jQuery.Deferred();
760
		$tr = this.newStorage(storageConfig, onCompletion);
761
		onCompletion.resolve();
762
763
		$tr.find('td.configuration').children().not('[type=hidden]').first().focus();
764
		this.saveStorageConfig($tr);
765
	},
766
767
	_onSelectAuthMechanism: function(event) {
768
		var $target = $(event.target);
769
		var $tr = $target.closest('tr');
770
		var authMechanism = $target.val();
771
772
		var onCompletion = jQuery.Deferred();
773
		this.configureAuthMechanism($tr, authMechanism, onCompletion);
774
		onCompletion.resolve();
775
776
		this.saveStorageConfig($tr);
777
	},
778
779
	/**
780
	 * Execute the target callback when all the deferred objects have been
781
	 * finished, either resolved or rejected
782
	 *
783
	 * @param {Deferred[]} deferreds array with all the deferred objects to check
784
	 * this will usually be a list of ajax requests, but other deferred
785
	 * objects can be included
786
	 * @param {callback} callback the callback to be executed after all the
787
	 * deferred object have finished
788
	 */
789
	_executeCallbackWhenFinished: function(deferreds, callback) {
790
		var self = this;
791
792
		$.when.apply($, deferreds).always(function() {
793
			var pendingDeferreds = [];
794
795
			// some deferred objects can still be pending to be resolved
796
			// or rejected. Get all of those which are still pending so we can
797
			// make another call
798
			if (deferreds.length > 0) {
799
				for (i = 0 ; i < deferreds.length ; i++) {
800
					if (deferreds[i].state() === 'pending') {
801
						pendingDeferreds.push(deferreds[i]);
802
					}
803
				}
804
			}
805
806
			if (pendingDeferreds.length > 0) {
807
				self._executeCallbackWhenFinished(pendingDeferreds, callback);
808
			} else {
809
				callback();
810
			}
811
		});
812
	},
813
814
	/**
815
	 * Configure the storage config with a new authentication mechanism
816
	 *
817
	 * @param {jQuery} $tr config row
818
	 * @param {string} authMechanism
819
	 * @param {jQuery.Deferred} onCompletion
820
	 */
821
	configureAuthMechanism: function($tr, authMechanism, onCompletion) {
822
		var authMechanismConfiguration = this._allAuthMechanisms[authMechanism];
823
		var $td = $tr.find('td.configuration');
824
		$td.find('.auth-param').remove();
825
826
		$.each(authMechanismConfiguration['configuration'], _.partial(
0 ignored issues
show
['configuration'] could be written in dot notation.

You can rewrite this statement in dot notation:

var obj = { };
obj['foo'] = 'bar'; // Bad
obj.foo = 'bar'; // Good
Loading history...
827
			this.writeParameterInput, $td, _, _, ['auth-param']
828
		).bind(this));
829
830
		this.trigger('selectAuthMechanism',
831
			$tr, authMechanism, authMechanismConfiguration['scheme'], onCompletion
0 ignored issues
show
['scheme'] could be written in dot notation.

You can rewrite this statement in dot notation:

var obj = { };
obj['foo'] = 'bar'; // Bad
obj.foo = 'bar'; // Good
Loading history...
832
		);
833
	},
834
835
	/**
836
	 * Create a config row for a new storage
837
	 *
838
	 * @param {StorageConfig} storageConfig storage config to pull values from
839
	 * @param {jQuery.Deferred} onCompletion
840
	 * @return {jQuery} created row
841
	 */
842
	newStorage: function(storageConfig, onCompletion) {
843
		var mountPoint = storageConfig.mountPoint;
844
		var backend = this._allBackends[storageConfig.backend];
845
		var isInvalidAuth = false;
846
847
		if (!backend) {
848
			backend = {
849
				name: 'Unknown backend: ' + storageConfig.backend,
850
				invalid: true
851
			};
852
		}
853
		if (backend && storageConfig.authMechanism && !this._allAuthMechanisms[storageConfig.authMechanism]) {
854
			// mark invalid to prevent editing
855
			backend.invalid = true;
856
			isInvalidAuth = true;
857
		}
858
859
		// FIXME: Replace with a proper Handlebar template
860
		var $tr = this.$el.find('tr#addMountPoint');
861
		var $trCloned = $tr.clone();
862
		$trCloned.find('td.mountPoint input').removeAttr('disabled');
863
		this.$el.find('tbody').append($trCloned);
864
865
		$tr.data('storageConfig', storageConfig);
866
		$tr.show();
867
		$tr.find('td').last().attr('class', 'remove');
868
		$tr.find('td.mountOptionsToggle').removeClass('hidden');
869
		$tr.find('td').last().removeAttr('style');
870
		$tr.removeAttr('id');
871
		addSelect2($tr.find('.applicableUsers'), this._userListLimit);
872
873
		if (storageConfig.id) {
874
			$tr.data('id', storageConfig.id);
875
		}
876
877
		$tr.find('.backend').text(backend.name);
878
		if (mountPoint === '') {
879
			mountPoint = this._suggestMountPoint(backend.name);
880
		}
881
		$tr.find('.mountPoint input').val(mountPoint);
882
		$tr.addClass(backend.identifier);
883
		$tr.find('.backend').data('identifier', backend.identifier);
884
885
		if (backend.invalid || isInvalidAuth) {
886
			$tr.addClass('invalid');
887
			$tr.find('[name=mountPoint]').prop('disabled', true);
888
			$tr.find('.applicable,.mountOptionsToggle').empty();
889
			this.updateStatus($tr, false, 'Unknown backend: ' + backend.name);
890
			if (isInvalidAuth) {
891
				$tr.find('td.authentication').append('<span class="error-invalid">' +
892
					t('files_external', 'Unknown auth backend "{b}"', {b: storageConfig.authMechanism}) +
893
					'</span>'
894
				);
895
			}
896
897
			$tr.find('td.configuration').append('<span class="error-invalid">' +
898
				t('files_external', 'Please make sure that the app that provides this backend is installed and enabled') +
899
				'</span>'
900
			);
901
902
			return $tr;
903
		}
904
905
		var selectAuthMechanism = $('<select class="selectAuthMechanism"></select>');
906
		var neededVisibility = (this._isPersonal) ? StorageConfig.Visibility.PERSONAL : StorageConfig.Visibility.ADMIN;
907
		$.each(this._allAuthMechanisms, function(authIdentifier, authMechanism) {
908
			if (backend.authSchemes[authMechanism.scheme] && (authMechanism.visibility & neededVisibility)) {
909
				selectAuthMechanism.append(
910
					$('<option value="'+authMechanism.identifier+'" data-scheme="'+authMechanism.scheme+'">'+authMechanism.name+'</option>')
911
				);
912
			}
913
		});
914
		if (storageConfig.authMechanism) {
915
			selectAuthMechanism.val(storageConfig.authMechanism);
916
		} else {
917
			storageConfig.authMechanism = selectAuthMechanism.val();
918
		}
919
		$tr.find('td.authentication').append(selectAuthMechanism);
920
921
		var $td = $tr.find('td.configuration');
922
		$.each(backend.configuration, _.partial(this.writeParameterInput, $td).bind(this));
923
924
		this.trigger('selectBackend', $tr, backend.identifier, onCompletion);
925
		this.configureAuthMechanism($tr, storageConfig.authMechanism, onCompletion);
926
927
		if (storageConfig.backendOptions) {
928
			$td.find('input, select').each(function() {
929
				var input = $(this);
930
				var val = storageConfig.backendOptions[input.data('parameter')];
931
				if (val !== undefined) {
932
					if(input.is('input:checkbox')) {
933
						input.prop('checked', val);
934
					}
935
					input.val(storageConfig.backendOptions[input.data('parameter')]);
936
					highlightInput(input);
937
				}
938
			});
939
		}
940
941
		var applicable = [];
942
		if (storageConfig.applicableUsers) {
943
			applicable = applicable.concat(storageConfig.applicableUsers);
944
		}
945
		if (storageConfig.applicableGroups) {
946
			applicable = applicable.concat(
947
				_.map(storageConfig.applicableGroups, function(group) {
948
					return group+'(group)';
949
				})
950
			);
951
		}
952
		$tr.find('.applicableUsers').val(applicable).trigger('change');
953
954
		var priorityEl = $('<input type="hidden" class="priority" value="' + backend.priority + '" />');
955
		$tr.append(priorityEl);
956
957
		if (storageConfig.mountOptions) {
958
			$tr.find('input.mountOptions').val(JSON.stringify(storageConfig.mountOptions));
959
		} else {
960
			// FIXME default backend mount options
961
			$tr.find('input.mountOptions').val(JSON.stringify({
962
				'encrypt': true,
963
				'previews': true,
964
				'enable_sharing': false,
965
				'filesystem_check_changes': 1,
966
				'encoding_compatibility': false
967
			}));
968
		}
969
970
		return $tr;
971
	},
972
973
	/**
974
	 * Load storages into config rows
975
	 */
976
	loadStorages: function() {
977
		var self = this;
978
		var ajaxRequests = [];
979
980
		if (this._isPersonal) {
981
			// load userglobal storages
982
			var fixedStoragesRequest = $.ajax({
983
				type: 'GET',
984
				url: OC.generateUrl('apps/files_external/userglobalstorages'),
985
				data: {'testOnly' : true},
986
				contentType: 'application/json',
987
				success: function(result) {
988
					var onCompletion = jQuery.Deferred();
989
					$.each(result, function(i, storageParams) {
990
						var storageConfig;
991
						var isUserGlobal = storageParams.type === 'system' && self._isPersonal;
992
						storageParams.mountPoint = storageParams.mountPoint.substr(1); // trim leading slash
993
						if (isUserGlobal) {
994
							storageConfig = new UserGlobalStorageConfig();
995
						} else {
996
							storageConfig = new self._storageConfigClass();
997
						}
998
						_.extend(storageConfig, storageParams);
999
						var $tr = self.newStorage(storageConfig, onCompletion);
1000
1001
						// userglobal storages must be at the top of the list
1002
						$tr.detach();
1003
						self.$el.prepend($tr);
1004
1005
						var $authentication = $tr.find('.authentication');
1006
						$authentication.text($authentication.find('select option:selected').text());
1007
1008
						// disable any other inputs
1009
						$tr.find('.mountOptionsToggle, .remove').empty();
1010
						$tr.find('input:not(.user_provided), select:not(.user_provided)').attr('disabled', 'disabled');
1011
1012
						if (isUserGlobal) {
1013
							$tr.find('.configuration').find(':not(.user_provided)').remove();
1014
						} else {
1015
							// userglobal storages do not expose configuration data
1016
							$tr.find('.configuration').text(t('files_external', 'Admin defined'));
1017
						}
1018
					});
1019
					onCompletion.resolve();
1020
				}
1021
			});
1022
			ajaxRequests.push(fixedStoragesRequest);
1023
		}
1024
1025
		var url = this._storageConfigClass.prototype._url;
1026
1027
		var changeableStoragesRequest = $.ajax({
1028
			type: 'GET',
1029
			url: OC.generateUrl(url),
1030
			contentType: 'application/json',
1031
			success: function(result) {
1032
				var onCompletion = jQuery.Deferred();
1033
				$.each(result, function(i, storageParams) {
1034
					storageParams.mountPoint = storageParams.mountPoint.substr(1); // trim leading slash
1035
					var storageConfig = new self._storageConfigClass();
1036
					_.extend(storageConfig, storageParams);
1037
					var $tr = self.newStorage(storageConfig, onCompletion);
1038
					self.recheckStorageConfig($tr);
1039
				});
1040
				onCompletion.resolve();
1041
			}
1042
		});
1043
		ajaxRequests.push(changeableStoragesRequest);
1044
1045
		this._executeCallbackWhenFinished(ajaxRequests, function() {
1046
			$('#body-settings').trigger('mountConfigLoaded');
1047
		});
1048
	},
1049
1050
	/**
1051
	 * @param {jQuery} $td
1052
	 * @param {string} parameter
1053
	 * @param {string} placeholder
1054
	 * @param {Array} classes
1055
	 * @return {jQuery} newly created input
1056
	 */
1057
	writeParameterInput: function($td, parameter, placeholder, classes) {
1058
		var hasFlag = function(flag) {
1059
			return (placeholder.flags & flag) === flag;
1060
		};
1061
		classes = $.isArray(classes) ? classes : [];
1062
		classes.push('added');
1063
		if (hasFlag(MountConfigListView.ParameterFlags.OPTIONAL)) {
1064
			classes.push('optional');
1065
		}
1066
1067
		if (hasFlag(MountConfigListView.ParameterFlags.USER_PROVIDED)) {
1068
			if (this._isPersonal) {
1069
				classes.push('user_provided');
1070
			} else {
1071
				return;
1072
			}
1073
		}
1074
1075
		var newElement;
1076
1077
		var trimmedPlaceholder = placeholder.value;
1078
		if (placeholder.type === MountConfigListView.ParameterTypes.PASSWORD) {
1079
			newElement = $('<input type="password" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+ trimmedPlaceholder+'" autocomplete="off" />');
1080
		} else if (placeholder.type === MountConfigListView.ParameterTypes.BOOLEAN) {
1081
			var checkboxId = _.uniqueId('checkbox_');
1082
			newElement = $('<div><label><input type="checkbox" id="'+checkboxId+'" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />'+ trimmedPlaceholder+'</label></div>');
1083
		} else if (placeholder.type === MountConfigListView.ParameterTypes.HIDDEN) {
1084
			newElement = $('<input type="hidden" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />');
1085
		} else {
1086
			newElement = $('<input type="text" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+ trimmedPlaceholder+'" />');
1087
		}
1088
		highlightInput(newElement);
1089
		$td.append(newElement);
1090
		return newElement;
1091
	},
1092
1093
	/**
1094
	 * Gets the storage model from the given row
1095
	 *
1096
	 * @param $tr row element
1097
	 * @return {OCA.External.StorageConfig} storage model instance
1098
	 */
1099
	getStorageConfig: function($tr) {
1100
		var storageId = $tr.data('id');
1101
		if (!storageId) {
1102
			// new entry
1103
			storageId = null;
1104
		}
1105
1106
		var storage = $tr.data('storageConfig');
1107
		if (!storage) {
1108
			storage = new this._storageConfigClass(storageId);
1109
		}
1110
		storage.errors = null;
1111
		storage.mountPoint = $tr.find('.mountPoint input').val();
1112
		storage.backend = $tr.find('.backend').data('identifier');
1113
		storage.authMechanism = $tr.find('.selectAuthMechanism').val();
1114
1115
		var classOptions = {};
1116
		var configuration = $tr.find('.configuration input');
1117
		var missingOptions = [];
1118
		$.each(configuration, function(index, input) {
1119
			var $input = $(input);
1120
			var parameter = $input.data('parameter');
1121
			if ($input.attr('type') === 'button') {
1122
				return;
1123
			}
1124
			if (!isInputValid($input) && !$input.hasClass('optional')) {
1125
				missingOptions.push(parameter);
1126
				return;
1127
			}
1128
			if ($(input).is(':checkbox')) {
1129
				if ($(input).is(':checked')) {
1130
					classOptions[parameter] = true;
1131
				} else {
1132
					classOptions[parameter] = false;
1133
				}
1134
			} else {
1135
				classOptions[parameter] = $(input).val();
1136
			}
1137
		});
1138
1139
		storage.backendOptions = classOptions;
1140
		if (missingOptions.length) {
1141
			storage.errors = {
1142
				backendOptions: missingOptions
1143
			};
1144
		}
1145
1146
		// gather selected users and groups
1147
		if (!this._isPersonal) {
1148
			var groups = [];
1149
			var users = [];
1150
			var multiselect = getSelection($tr);
1151
			$.each(multiselect, function(index, value) {
1152
				var pos = (value.indexOf)?value.indexOf('(group)'): -1;
1153
				if (pos !== -1) {
1154
					groups.push(value.substr(0, pos));
1155
				} else {
1156
					users.push(value);
1157
				}
1158
			});
1159
			// FIXME: this should be done in the multiselect change event instead
1160
			$tr.find('.applicable')
1161
				.data('applicable-groups', groups)
1162
				.data('applicable-users', users);
1163
1164
			storage.applicableUsers = users;
1165
			storage.applicableGroups = groups;
1166
1167
			storage.priority = parseInt($tr.find('input.priority').val() || '100', 10);
1168
		}
1169
1170
		var mountOptions = $tr.find('input.mountOptions').val();
1171
		if (mountOptions) {
1172
			storage.mountOptions = JSON.parse(mountOptions);
1173
		}
1174
1175
		return storage;
1176
	},
1177
1178
	/**
1179
	 * Deletes the storage from the given tr
1180
	 *
1181
	 * @param $tr storage row
1182
	 * @param Function callback callback to call after save
1183
	 */
1184
	deleteStorageConfig: function($tr) {
1185
		var self = this;
1186
		var configId = $tr.data('id');
1187
		if (!_.isNumber(configId)) {
1188
			// deleting unsaved storage
1189
			$tr.remove();
1190
			return;
1191
		}
1192
		var storage = new this._storageConfigClass(configId);
1193
		this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
1194
1195
		storage.destroy({
1196
			success: function() {
1197
				$tr.remove();
1198
			},
1199
			error: function() {
1200
				self.updateStatus($tr, StorageConfig.Status.ERROR);
1201
			}
1202
		});
1203
	},
1204
1205
	/**
1206
	 * Saves the storage from the given tr
1207
	 *
1208
	 * @param $tr storage row
1209
	 * @param Function callback callback to call after save
1210
	 * @param concurrentTimer only update if the timer matches this
1211
	 */
1212
	saveStorageConfig:function($tr, callback, concurrentTimer) {
1213
		var self = this;
1214
		var storage = this.getStorageConfig($tr);
1215
		if (!storage || !storage.validate()) {
1216
			return false;
1217
		}
1218
1219
		this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
1220
		storage.save({
1221
			success: function(result) {
1222
				if (concurrentTimer === undefined
1223
					|| $tr.data('save-timer') === concurrentTimer
1224
				) {
1225
					self.updateStatus($tr, result.status);
1226
					$tr.data('id', result.id);
1227
1228
					if (_.isFunction(callback)) {
1229
						callback(storage);
1230
					}
1231
				}
1232
			},
1233
			error: function() {
1234
				if (concurrentTimer === undefined
1235
					|| $tr.data('save-timer') === concurrentTimer
1236
				) {
1237
					self.updateStatus($tr, StorageConfig.Status.ERROR);
1238
				}
1239
			}
1240
		});
1241
	},
1242
1243
	/**
1244
	 * Recheck storage availability
1245
	 *
1246
	 * @param {jQuery} $tr storage row
1247
	 * @return {boolean} success
1248
	 */
1249
	recheckStorageConfig: function($tr) {
1250
		var self = this;
1251
		var storage = this.getStorageConfig($tr);
1252
		if (!storage.validate()) {
1253
			return false;
1254
		}
1255
1256
		this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
1257
		storage.recheck({
1258
			success: function(result) {
1259
				self.updateStatus($tr, result.status, result.statusMessage);
1260
			},
1261
			error: function() {
1262
				self.updateStatus($tr, StorageConfig.Status.ERROR);
1263
			}
1264
		});
1265
	},
1266
1267
	/**
1268
	 * Update status display
1269
	 *
1270
	 * @param {jQuery} $tr
1271
	 * @param {int} status
1272
	 * @param {string} message
1273
	 */
1274
	updateStatus: function($tr, status, message) {
1275
		var $statusSpan = $tr.find('.status span');
1276
		$statusSpan.removeClass('loading-small success indeterminate error');
1277
		switch (status) {
1278
			case null:
1279
				// remove status
1280
				break;
1281
			case StorageConfig.Status.IN_PROGRESS:
1282
				$statusSpan.addClass('loading-small');
1283
				break;
1284
			case StorageConfig.Status.SUCCESS:
1285
				$statusSpan.addClass('success');
1286
				break;
1287
			case StorageConfig.Status.INDETERMINATE:
1288
				$statusSpan.addClass('indeterminate');
1289
				break;
1290
			default:
1291
				$statusSpan.addClass('error');
1292
		}
1293
		$statusSpan.attr('data-original-title', (typeof message === 'string') ? message : '');
1294
	},
1295
1296
	/**
1297
	 * Suggest mount point name that doesn't conflict with the existing names in the list
1298
	 *
1299
	 * @param {string} defaultMountPoint default name
1300
	 */
1301
	_suggestMountPoint: function(defaultMountPoint) {
1302
		var $el = this.$el;
1303
		var pos = defaultMountPoint.indexOf('/');
1304
		if (pos !== -1) {
1305
			defaultMountPoint = defaultMountPoint.substring(0, pos);
1306
		}
1307
		defaultMountPoint = defaultMountPoint.replace(/\s+/g, '');
1308
		var i = 1;
1309
		var append = '';
1310
		var match = true;
1311
		while (match && i < 20) {
1312
			match = false;
1313
			$el.find('tbody td.mountPoint input').each(function(index, mountPoint) {
1314
				if ($(mountPoint).val() === defaultMountPoint+append) {
1315
					match = true;
1316
					return false;
1317
				}
1318
			});
1319
			if (match) {
1320
				append = i;
1321
				i++;
1322
			} else {
1323
				break;
1324
			}
1325
		}
1326
		return defaultMountPoint + append;
1327
	},
1328
1329
	/**
1330
	 * Toggles the mount options dropdown
1331
	 *
1332
	 * @param {Object} $tr configuration row
1333
	 */
1334
	_showMountOptionsDropdown: function($tr) {
1335
		if (this._preventNextDropdown) {
1336
			// prevented because the click was on the toggle
1337
			this._preventNextDropdown = false;
1338
			return;
1339
		}
1340
		var self = this;
1341
		var storage = this.getStorageConfig($tr);
1342
		var $toggle = $tr.find('.mountOptionsToggle');
1343
		var dropDown = new MountOptionsDropdown();
1344
		var visibleOptions = [
1345
			'previews',
1346
			'filesystem_check_changes',
1347
			'encoding_compatibility'
1348
		];
1349
		if (this._encryptionEnabled) {
1350
			visibleOptions.push('encrypt');
1351
		}
1352
		if (!this._isPersonal || this._allowUserMountSharing) {
1353
			visibleOptions.push('enable_sharing');
1354
		}
1355
1356
		dropDown.show($toggle, storage.mountOptions || [], visibleOptions);
1357
		$('body').on('mouseup.mountOptionsDropdown', function(event) {
1358
			var $target = $(event.target);
1359
			if ($toggle.has($target).length) {
1360
				// why is it always so hard to make dropdowns behave ?
1361
				// this prevents the click on the toggle to cause
1362
				// the dropdown to reopen itself
1363
				// (preventDefault doesn't work here because the click
1364
				// event is already in the queue and cannot be cancelled)
1365
				self._preventNextDropdown = true;
1366
			}
1367
			if ($target.closest('.dropdown').length) {
1368
				return;
1369
			}
1370
			dropDown.hide();
1371
		});
1372
1373
		dropDown.$el.on('hide', function() {
1374
			var mountOptions = dropDown.getOptions();
1375
			$('body').off('mouseup.mountOptionsDropdown');
1376
			$tr.find('input.mountOptions').val(JSON.stringify(mountOptions));
1377
			self.saveStorageConfig($tr);
1378
		});
1379
	}
1380
}, OC.Backbone.Events);
1381
1382
$(document).ready(function() {
1383
	var enabled = $('#files_external').attr('data-encryption-enabled');
1384
	var encryptionEnabled = (enabled ==='true')? true: false;
1385
	var mountConfigListView = new MountConfigListView($('#externalStorage'), {
1386
		encryptionEnabled: encryptionEnabled,
1387
		allowUserMountSharing: (parseInt($('#allowUserMountSharing').val(), 10) === 1)
1388
	});
1389
1390
	// init the mountConfigListView events after the storages are loaded
1391
	$('#body-settings').on('mountConfigLoaded', function() {
1392
		mountConfigListView._initEvents();
1393
	});
1394
	mountConfigListView.loadStorages();
1395
1396
	// TODO: move this into its own View class
1397
	var $allowUserMounting = $('#allowUserMounting');
1398
	$allowUserMounting.bind('change', function() {
1399
		OC.msg.startSaving('#userMountingMsg');
1400
		if (this.checked) {
1401
			OC.AppConfig.setValue('files_external', 'allow_user_mounting', 'yes');
1402
			$('input[name="allowUserMountingBackends\\[\\]"]').prop('checked', true);
1403
			$('#userMountingBackends').removeClass('hidden');
1404
			$('input[name="allowUserMountingBackends\\[\\]"]').eq(0).trigger('change');
1405
		} else {
1406
			OC.AppConfig.setValue('files_external', 'allow_user_mounting', 'no');
1407
			$('#userMountingBackends').addClass('hidden');
1408
		}
1409
		OC.msg.finishedSaving('#userMountingMsg', {status: 'success', data: {message: t('files_external', 'Saved')}});
1410
	});
1411
1412
	var $allowUserMountSharing = $('#allowUserMountSharing');
1413
	$allowUserMountSharing.bind('change', function() {
1414
		OC.msg.startSaving('#userMountSharingMsg');
1415
		OC.AppConfig.setValue('core', 'allow_user_mount_sharing', this.checked ? 'yes' : 'no');
1416
		OC.msg.finishedSaving('#userMountSharingMsg', {status: 'success', data: {message: t('files_external', 'Saved')}});
1417
	});
1418
1419
	$('input[name="allowUserMountingBackends\\[\\]"]').bind('change', function() {
1420
		OC.msg.startSaving('#userMountingMsg');
1421
1422
		var userMountingBackends = $('input[name="allowUserMountingBackends\\[\\]"]:checked').map(function(){
1423
			return $(this).val();
1424
		}).get();
1425
		var deprecatedBackends = $('input[name="allowUserMountingBackends\\[\\]"][data-deprecate-to]').map(function(){
1426
			if ($.inArray($(this).data('deprecate-to'), userMountingBackends) !== -1) {
1427
				return $(this).val();
1428
			}
1429
			return null;
1430
		}).get();
1431
		userMountingBackends = userMountingBackends.concat(deprecatedBackends);
1432
1433
		OC.AppConfig.setValue('files_external', 'user_mounting_backends', userMountingBackends.join());
1434
		OC.msg.finishedSaving('#userMountingMsg', {status: 'success', data: {message: t('files_external', 'Saved')}});
1435
1436
		// disable allowUserMounting
1437
		if(userMountingBackends.length === 0) {
1438
			$allowUserMounting.prop('checked', false);
1439
			$allowUserMounting.trigger('change');
1440
1441
		}
1442
	});
1443
1444
	$('#files_external input[name=enableExternalStorage]').on('change', function(event) {
1445
		var $target = $(event.target);
1446
		var checked = $target.is(':checked');
1447
1448
		var saveSetting = function(checked) {
1449
			var value = checked ? 'yes' : 'no';
1450
			OC.AppConfig.setValue('core', 'enable_external_storage', value);
1451
			$('#files_external_settings').toggleClass('hidden', !checked);
1452
		};
1453
1454
		if (checked === false) {
1455
			OC.dialogs.confirm(
1456
				t('files_external', 'Disabling external storage will unmount all storages for all users, are you sure ?'),
1457
				t('files_external', 'Disable external storage'),
1458
				function(confirmation) {
1459
					if (confirmation) {
1460
						saveSetting(false);
1461
					} else {
1462
						$target.prop('checked', true);
1463
					}
1464
				},
1465
				true
1466
			);
1467
		} else {
1468
			saveSetting(true);
1469
		}
1470
	});
1471
1472
	// global instance
1473
	OCA.External.Settings.mountConfig = mountConfigListView;
1474
1475
	/**
1476
	 * Legacy
1477
	 *
1478
	 * @namespace
1479
	 * @deprecated use OCA.External.Settings.mountConfig instead
1480
	 */
1481
	OC.MountConfig = {
1482
		saveStorage: _.bind(mountConfigListView.saveStorageConfig, mountConfigListView)
1483
	};
1484
});
1485
1486
// export
1487
1488
OCA.External = OCA.External || {};
1489
/**
1490
 * @namespace
1491
 */
1492
OCA.External.Settings = OCA.External.Settings || {};
1493
1494
OCA.External.Settings.GlobalStorageConfig = GlobalStorageConfig;
1495
OCA.External.Settings.UserStorageConfig = UserStorageConfig;
1496
OCA.External.Settings.MountConfigListView = MountConfigListView;
1497
1498
/**
1499
 * @namespace OAuth2 namespace which is used to verify a storage adapter
1500
 *            using AuthMechanism as oauth2::oauth2
1501
 */
1502
OCA.External.Settings.OAuth2 = OCA.External.Settings.OAuth2 || {};
1503
1504
/**
1505
 * This function sends a request to the given backendUrl and gets the OAuth2 URL
1506
 * for any given backend storage, executes the callback if any, set the data-* parameters
1507
 * of the storage and REDIRECTS the client to Authentication page
1508
 *
1509
 * @param  {String}   backendUrl The backend URL to which request will be sent
1510
 * @param  {Object}   data       Keys -> (backend_id, client_id, client_secret, redirect, tr)
1511
 */
1512
OCA.External.Settings.OAuth2.getAuthUrl = function (backendUrl, data) {
1513
	var $tr = data['tr'];
1514
	var configured = $tr.find('[data-parameter="configured"]');
1515
	var token = $tr.find('.configuration [data-parameter="token"]');
1516
1517
	$.post(backendUrl, {
1518
			step: 1,
1519
			client_id: data['client_id'],
1520
			client_secret: data['client_secret'],
1521
			redirect: data['redirect'],
1522
		}, function (result) {
1523
			if (result && result.status == 'success') {
1524
				$(configured).val('false');
1525
				$(token).val('false');
1526
1527
				OCA.External.Settings.mountConfig.saveStorageConfig($tr, function(status) {
1528
					if (!result.data.url) {
1529
						OC.dialogs.alert('Auth URL not set',
1530
							t('files_external', 'No URL provided by backend ' + data['backend_id'])
1531
						);
1532
						$tr.trigger('oauth_step1_finished', [{
1533
							success: false,
1534
							tr: $tr,
1535
							data: data
1536
						}]);
1537
					} else {
1538
						$tr.trigger('oauth_step1_finished', [{
1539
							success: true,
1540
							tr: $tr,
1541
							data: data
1542
						}]);
1543
						window.location = result.data.url;
1544
					}
1545
				});
1546
			} else {
1547
				OC.dialogs.alert(result.data.message,
1548
					t('files_external', 'Error getting OAuth2 URL for ' + data['backend_id'])
1549
				);
1550
				$tr.trigger('oauth_step1_finished', [{
1551
					success: false,
1552
					tr: $tr,
1553
					data: data
1554
				}]);
1555
			}
1556
		}
1557
	);
1558
}
1559
1560
/**
1561
 * This function verifies the OAuth2 code returned to the client after verification
1562
 * by sending request to the backend with the given CODE and if the code is verified
1563
 * it sets the data-* params to configured and disables the authorize buttons
1564
 *
1565
 * @param  {String}   backendUrl The backend URL to which request will be sent
1566
 * @param  {Object}   data       Keys -> (backend_id, client_id, client_secret, redirect, tr, code)
1567
 * @return {Promise} jQuery Deferred Promise object
1568
 */
1569
OCA.External.Settings.OAuth2.verifyCode = function (backendUrl, data) {
1570
	var $tr = data['tr'];
1571
	var configured = $tr.find('[data-parameter="configured"]');
1572
	var token = $tr.find('.configuration [data-parameter="token"]');
1573
	var statusSpan = $tr.find('.status span');
1574
	statusSpan.removeClass().addClass('waiting');
1575
1576
	var deferredObject = $.Deferred();
1577
	$.post(backendUrl, {
1578
			step: 2,
1579
			client_id: data['client_id'],
1580
			client_secret: data['client_secret'],
1581
			redirect: data['redirect'],
1582
			code: data['code'],
1583
		}, function (result) {
1584
			if (result && result.status == 'success') {
1585
				$(token).val(result.data.token);
1586
				$(configured).val('true');	
1587
				
1588
				OCA.External.Settings.mountConfig.saveStorageConfig($tr, function(status) {
1589
					if (status) {
1590
						$tr.find('.configuration input.auth-param')
1591
							.attr('disabled', 'disabled')
1592
							.addClass('disabled-success')
1593
					}
1594
					deferredObject.resolve(status);
1595
				});
1596
			} else {
1597
				deferredObject.reject(result.data.message);
1598
			}
1599
		}
1600
	);
1601
	return deferredObject.promise();
1602
}
1603
1604
})();
1605