Allow setting user provided credentials from the personal settings page

This commit is contained in:
Robin Appelman 2016-01-19 14:16:11 +01:00
parent f3e9729a5f
commit 860d51487b
6 changed files with 180 additions and 44 deletions

View File

@ -25,6 +25,7 @@ namespace OCA\Files_External\Controller;
use \OCP\IConfig; use \OCP\IConfig;
use OCP\IUser;
use \OCP\IUserSession; use \OCP\IUserSession;
use \OCP\IRequest; use \OCP\IRequest;
use \OCP\IL10N; use \OCP\IL10N;
@ -114,6 +115,7 @@ abstract class StoragesController extends Controller {
$priority $priority
); );
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
\OC::$server->getLogger()->logException($e);
return new DataResponse( return new DataResponse(
[ [
'message' => (string)$this->l10n->t('Invalid backend or authentication mechanism class') 'message' => (string)$this->l10n->t('Invalid backend or authentication mechanism class')

View File

@ -21,12 +21,19 @@
namespace OCA\Files_External\Controller; namespace OCA\Files_External\Controller;
use OCA\Calendar\Sabre\Backend;
use OCA\Files_External\Lib\Auth\AuthMechanism;
use OCA\Files_External\Lib\Auth\Password\UserProvided; use OCA\Files_External\Lib\Auth\Password\UserProvided;
use OCA\Files_external\Lib\StorageConfig;
use OCA\Files_External\Service\UserGlobalStoragesService;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\IL10N;
use OCP\IRequest; use OCP\IRequest;
use OCP\IUserSession; use OCP\IUserSession;
class UserCredentialsController extends Controller { class UserCredentialsController extends StoragesController {
/** /**
* @var UserProvided * @var UserProvided
*/ */
@ -37,10 +44,22 @@ class UserCredentialsController extends Controller {
*/ */
private $userSession; private $userSession;
public function __construct($appName, IRequest $request, UserProvided $authMechanism, IUserSession $userSession) { /**
parent::__construct($appName, $request); * @var UserGlobalStoragesService
*/
private $globalStoragesService;
public function __construct(
$appName, IRequest $request,
UserProvided $authMechanism,
IUserSession $userSession,
IL10N $l10n,
UserGlobalStoragesService $globalStoragesService
) {
parent::__construct($appName, $request, $l10n, $globalStoragesService);
$this->authMechanism = $authMechanism; $this->authMechanism = $authMechanism;
$this->userSession = $userSession; $this->userSession = $userSession;
$this->globalStoragesService = $globalStoragesService;
} }
/** /**
@ -49,8 +68,32 @@ class UserCredentialsController extends Controller {
* @param string $password * @param string $password
* *
* @NoAdminRequired * @NoAdminRequired
* @return DataResponse
*/ */
public function store($storageId, $username, $password) { public function store($storageId, $username, $password) {
$this->authMechanism->saveCredentials($this->userSession->getUser(), $storageId, $username, $password); $this->authMechanism->saveCredentials($this->userSession->getUser(), $storageId, $username, $password);
$storage = $this->globalStoragesService->getStorage($storageId);
$this->updateStorageStatus($storage);
$storage->setBackendOptions([]);
$storage->setMountOptions([]);
$this->manipulateStorageConfig($storage);
return new DataResponse(
$storage,
Http::STATUS_OK
);
}
protected function manipulateStorageConfig(StorageConfig $storage) {
/** @var AuthMechanism */
$authMechanism = $storage->getAuthMechanism();
$authMechanism->manipulateStorageConfig($storage, $this->userSession->getUser());
/** @var Backend */
$backend = $storage->getBackend();
$backend->manipulateStorageConfig($storage, $this->userSession->getUser());
} }
} }

View File

@ -25,6 +25,7 @@ namespace OCA\Files_External\Controller;
use OCA\Files_External\Lib\Auth\AuthMechanism; use OCA\Files_External\Lib\Auth\AuthMechanism;
use \OCP\IConfig; use \OCP\IConfig;
use OCP\IUser;
use \OCP\IUserSession; use \OCP\IUserSession;
use \OCP\IRequest; use \OCP\IRequest;
use \OCP\IL10N; use \OCP\IL10N;

View File

@ -357,6 +357,9 @@ StorageConfig.prototype = {
if (this.mountPoint === '') { if (this.mountPoint === '') {
return false; return false;
} }
if (!this.backend) {
return false;
}
if (this.errors) { if (this.errors) {
return false; return false;
} }
@ -432,6 +435,48 @@ UserStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
_url: 'apps/files_external/userstorages' _url: 'apps/files_external/userstorages'
}); });
/**
* @class OCA.External.Settings.UserGlobalStorageConfig
* @augments OCA.External.Settings.StorageConfig
*
* @classdesc User external storage config
*/
var UserGlobalStorageConfig = function (id) {
this.id = id;
};
UserGlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
/** @lends OCA.External.Settings.UserStorageConfig.prototype */ {
_url: 'apps/files_external/userglobalstorages',
/**
* Creates or saves the storage.
*
* @param {Function} [options.success] success callback, receives result as argument
* @param {Function} [options.error] error callback
*/
save: function (options) {
var self = this;
var url = OC.generateUrl('apps/files_external/usercredentials/{id}', {id: this.id});
$.ajax({
type: 'PUT',
url: url,
contentType: 'application/json',
data: JSON.stringify({
username: this.backendOptions.user,
password: this.backendOptions.password
}),
success: function (result) {
if (_.isFunction(options.success)) {
options.success(result);
}
},
error: options.error
});
}
});
/** /**
* @class OCA.External.Settings.MountOptionsDropdown * @class OCA.External.Settings.MountOptionsDropdown
* *
@ -748,7 +793,7 @@ MountConfigListView.prototype = _.extend({
$.each(authMechanismConfiguration['configuration'], _.partial( $.each(authMechanismConfiguration['configuration'], _.partial(
this.writeParameterInput, $td, _, _, ['auth-param'] this.writeParameterInput, $td, _, _, ['auth-param']
)); ).bind(this));
this.trigger('selectAuthMechanism', this.trigger('selectAuthMechanism',
$tr, authMechanism, authMechanismConfiguration['scheme'], onCompletion $tr, authMechanism, authMechanismConfiguration['scheme'], onCompletion
@ -770,6 +815,7 @@ MountConfigListView.prototype = _.extend({
var $tr = this.$el.find('tr#addMountPoint'); var $tr = this.$el.find('tr#addMountPoint');
this.$el.find('tbody').append($tr.clone()); this.$el.find('tbody').append($tr.clone());
$tr.data('storageConfig', storageConfig);
$tr.find('td').last().attr('class', 'remove'); $tr.find('td').last().attr('class', 'remove');
$tr.find('td.mountOptionsToggle').removeClass('hidden'); $tr.find('td.mountOptionsToggle').removeClass('hidden');
$tr.find('td').last().removeAttr('style'); $tr.find('td').last().removeAttr('style');
@ -805,7 +851,7 @@ MountConfigListView.prototype = _.extend({
$tr.find('td.authentication').append(selectAuthMechanism); $tr.find('td.authentication').append(selectAuthMechanism);
var $td = $tr.find('td.configuration'); var $td = $tr.find('td.configuration');
$.each(backend.configuration, _.partial(this.writeParameterInput, $td)); $.each(backend.configuration, _.partial(this.writeParameterInput, $td).bind(this));
this.trigger('selectBackend', $tr, backend.identifier, onCompletion); this.trigger('selectBackend', $tr, backend.identifier, onCompletion);
this.configureAuthMechanism($tr, storageConfig.authMechanism, onCompletion); this.configureAuthMechanism($tr, storageConfig.authMechanism, onCompletion);
@ -866,8 +912,14 @@ MountConfigListView.prototype = _.extend({
success: function(result) { success: function(result) {
var onCompletion = jQuery.Deferred(); var onCompletion = jQuery.Deferred();
$.each(result, function(i, storageParams) { $.each(result, function(i, storageParams) {
var storageConfig;
var isUserProvidedAuth = storageParams.authMechanism === 'password::userprovided';
storageParams.mountPoint = storageParams.mountPoint.substr(1); // trim leading slash storageParams.mountPoint = storageParams.mountPoint.substr(1); // trim leading slash
var storageConfig = new self._storageConfigClass(); if (isUserProvidedAuth) {
storageConfig = new UserGlobalStorageConfig();
} else {
storageConfig = new self._storageConfigClass();
}
_.extend(storageConfig, storageParams); _.extend(storageConfig, storageParams);
var $tr = self.newStorage(storageConfig, onCompletion); var $tr = self.newStorage(storageConfig, onCompletion);
@ -878,12 +930,16 @@ MountConfigListView.prototype = _.extend({
var $authentication = $tr.find('.authentication'); var $authentication = $tr.find('.authentication');
$authentication.text($authentication.find('select option:selected').text()); $authentication.text($authentication.find('select option:selected').text());
// userglobal storages do not expose configuration data
$tr.find('.configuration').text(t('files_external', 'Admin defined'));
// disable any other inputs // disable any other inputs
$tr.find('.mountOptionsToggle, .remove').empty(); $tr.find('.mountOptionsToggle, .remove').empty();
$tr.find('input, select, button').attr('disabled', 'disabled'); $tr.find('input:not(.user_provided), select:not(.user_provided)').attr('disabled', 'disabled');
if (isUserProvidedAuth) {
$tr.find('.configuration').find(':not(.user_provided)').remove();
} else {
// userglobal storages do not expose configuration data
$tr.find('.configuration').text(t('files_external', 'Admin defined'));
}
}); });
onCompletion.resolve(); onCompletion.resolve();
} }
@ -918,22 +974,40 @@ MountConfigListView.prototype = _.extend({
* @return {jQuery} newly created input * @return {jQuery} newly created input
*/ */
writeParameterInput: function($td, parameter, placeholder, classes) { writeParameterInput: function($td, parameter, placeholder, classes) {
var hasFlag = function(flag) {
return placeholder.indexOf(flag) !== -1;
};
classes = $.isArray(classes) ? classes : []; classes = $.isArray(classes) ? classes : [];
classes.push('added'); classes.push('added');
if (placeholder.indexOf('&') === 0) { if (placeholder.indexOf('&') === 0) {
classes.push('optional'); classes.push('optional');
placeholder = placeholder.substring(1); placeholder = placeholder.substring(1);
} }
if (hasFlag('@')) {
if (this._isPersonal) {
classes.push('user_provided');
} else {
return;
}
}
var newElement; var newElement;
if (placeholder.indexOf('*') === 0) {
newElement = $('<input type="password" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+placeholder.substring(1)+'" />'); var trimmedPlaceholder = placeholder;
} else if (placeholder.indexOf('!') === 0) { var flags = ['@', '*', '!', '#', '&'];
while(flags.indexOf(trimmedPlaceholder[0]) !== -1) {
trimmedPlaceholder = trimmedPlaceholder.substr(1);
}
if (hasFlag('*')) {
newElement = $('<input type="password" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+ trimmedPlaceholder+'" />');
} else if (hasFlag('!')) {
var checkboxId = _.uniqueId('checkbox_'); var checkboxId = _.uniqueId('checkbox_');
newElement = $('<input type="checkbox" id="'+checkboxId+'" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" /><label for="'+checkboxId+'">'+placeholder.substring(1)+'</label>'); newElement = $('<input type="checkbox" id="'+checkboxId+'" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" /><label for="'+checkboxId+'">'+ trimmedPlaceholder+'</label>');
} else if (placeholder.indexOf('#') === 0) { } else if (hasFlag('#')) {
newElement = $('<input type="hidden" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />'); newElement = $('<input type="hidden" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />');
} else { } else {
newElement = $('<input type="text" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+placeholder+'" />'); newElement = $('<input type="text" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+ trimmedPlaceholder+'" />');
} }
highlightInput(newElement); highlightInput(newElement);
$td.append(newElement); $td.append(newElement);
@ -952,7 +1026,12 @@ MountConfigListView.prototype = _.extend({
// new entry // new entry
storageId = null; storageId = null;
} }
var storage = new this._storageConfigClass(storageId);
var storage = $tr.data('storageConfig');
if (!storage) {
storage = new this._storageConfigClass(storageId);
}
storage.errors = null;
storage.mountPoint = $tr.find('.mountPoint input').val(); storage.mountPoint = $tr.find('.mountPoint input').val();
storage.backend = $tr.find('.backend').data('identifier'); storage.backend = $tr.find('.backend').data('identifier');
storage.authMechanism = $tr.find('.selectAuthMechanism').val(); storage.authMechanism = $tr.find('.selectAuthMechanism').val();
@ -966,7 +1045,7 @@ MountConfigListView.prototype = _.extend({
if ($input.attr('type') === 'button') { if ($input.attr('type') === 'button') {
return; return;
} }
if (!isInputValid($input)) { if (!isInputValid($input) && !$input.hasClass('optional')) {
missingOptions.push(parameter); missingOptions.push(parameter);
return; return;
} }
@ -994,7 +1073,7 @@ MountConfigListView.prototype = _.extend({
var users = []; var users = [];
var multiselect = getSelection($tr); var multiselect = getSelection($tr);
$.each(multiselect, function(index, value) { $.each(multiselect, function(index, value) {
var pos = value.indexOf('(group)'); var pos = (value.indexOf)?value.indexOf('(group)'): -1;
if (pos !== -1) { if (pos !== -1) {
groups.push(value.substr(0, pos)); groups.push(value.substr(0, pos));
} else { } else {
@ -1057,7 +1136,9 @@ MountConfigListView.prototype = _.extend({
saveStorageConfig:function($tr, callback, concurrentTimer) { saveStorageConfig:function($tr, callback, concurrentTimer) {
var self = this; var self = this;
var storage = this.getStorageConfig($tr); var storage = this.getStorageConfig($tr);
if (!storage.validate()) { console.log(storage);
if (!storage || !storage.validate()) {
console.log('invalid');
return false; return false;
} }

View File

@ -21,6 +21,7 @@
namespace OCA\Files_External\Lib\Auth\Password; namespace OCA\Files_External\Lib\Auth\Password;
use OCA\Files_External\Lib\DefinitionParameter;
use OCP\IL10N; use OCP\IL10N;
use OCP\IUser; use OCP\IUser;
use OCA\Files_External\Lib\Auth\AuthMechanism; use OCA\Files_External\Lib\Auth\AuthMechanism;
@ -46,7 +47,13 @@ class UserProvided extends AuthMechanism {
->setIdentifier('password::userprovided') ->setIdentifier('password::userprovided')
->setScheme(self::SCHEME_PASSWORD) ->setScheme(self::SCHEME_PASSWORD)
->setText($l->t('User provided')) ->setText($l->t('User provided'))
->addParameters([]); ->addParameters([
(new DefinitionParameter('user', $l->t('Username')))
->setFlag(DefinitionParameter::FLAG_USER_PROVIDED),
(new DefinitionParameter('password', $l->t('Password')))
->setType(DefinitionParameter::VALUE_PASSWORD)
->setFlag(DefinitionParameter::FLAG_USER_PROVIDED),
]);
} }
private function getCredentialsIdentifier($storageId) { private function getCredentialsIdentifier($storageId) {

View File

@ -35,6 +35,7 @@ class DefinitionParameter implements \JsonSerializable {
/** Flag constants */ /** Flag constants */
const FLAG_NONE = 0; const FLAG_NONE = 0;
const FLAG_OPTIONAL = 1; const FLAG_OPTIONAL = 1;
const FLAG_USER_PROVIDED = 2;
/** @var string name of parameter */ /** @var string name of parameter */
private $name; private $name;
@ -121,7 +122,7 @@ class DefinitionParameter implements \JsonSerializable {
* @return bool * @return bool
*/ */
public function isFlagSet($flag) { public function isFlagSet($flag) {
return (bool) $this->flags & $flag; return (bool)($this->flags & $flag);
} }
/** /**
@ -143,10 +144,11 @@ class DefinitionParameter implements \JsonSerializable {
break; break;
} }
switch ($this->getFlags()) { if ($this->isFlagSet(self::FLAG_OPTIONAL)) {
case self::FLAG_OPTIONAL: $prefix = '&' . $prefix;
$prefix = '&' . $prefix; }
break; if ($this->isFlagSet(self::FLAG_USER_PROVIDED)) {
$prefix = '@' . $prefix;
} }
return $prefix . $this->getText(); return $prefix . $this->getText();
@ -160,28 +162,28 @@ class DefinitionParameter implements \JsonSerializable {
* @return bool success * @return bool success
*/ */
public function validateValue(&$value) { public function validateValue(&$value) {
$optional = $this->getFlags() & self::FLAG_OPTIONAL; $optional = $this->isFlagSet(self::FLAG_OPTIONAL) || $this->isFlagSet(self::FLAG_USER_PROVIDED);
switch ($this->getType()) { switch ($this->getType()) {
case self::VALUE_BOOLEAN: case self::VALUE_BOOLEAN:
if (!is_bool($value)) { if (!is_bool($value)) {
switch ($value) { switch ($value) {
case 'true': case 'true':
$value = true; $value = true;
break; break;
case 'false': case 'false':
$value = false; $value = false;
break; break;
default: default:
return false;
}
}
break;
default:
if (!$value && !$optional) {
return false; return false;
} }
} break;
break;
default:
if (!$value && !$optional) {
return false;
}
break;
} }
return true; return true;
} }