Use storage id + appframework for ext storage CRUD

- Added StorageConfig class to replace ugly arrays
- Implemented StorageService and StorageController for Global and User
  storages
- Async status checking for storages (from Xenopathic)
- Auto-generate id for external storage configs (not the same as
  storage_id)
- Refactor JS classes for external storage settings, this mostly
  moves/encapsulated existing global event handlers into the
  MountConfigListView class.
- Added some JS unit tests for the external storage UI
This commit is contained in:
Vincent Petry 2014-10-31 11:41:07 +01:00
parent 23cc3cc5f2
commit ce94a998dd
29 changed files with 3803 additions and 455 deletions

View File

@ -1,26 +0,0 @@
<?php
OCP\JSON::checkAppEnabled('files_external');
OCP\JSON::callCheck();
if ($_POST['isPersonal'] == 'true') {
OCP\JSON::checkLoggedIn();
$isPersonal = true;
} else {
OCP\JSON::checkAdminUser();
$isPersonal = false;
}
$mountPoint = (string)$_POST['mountPoint'];
$oldMountPoint = (string)$_POST['oldMountPoint'];
$class = (string)$_POST['class'];
$options = (array)$_POST['classOptions'];
$type = (string)$_POST['mountType'];
$applicable = (string)$_POST['applicable'];
if ($oldMountPoint and $oldMountPoint !== $mountPoint) {
OC_Mount_Config::removeMountPoint($oldMountPoint, $type, $applicable, $isPersonal);
}
$status = OC_Mount_Config::addMountPoint($mountPoint, $class, $options, $type, $applicable, $isPersonal);
OCP\JSON::success(array('data' => array('message' => $status)));

View File

@ -1,23 +0,0 @@
<?php
OCP\JSON::checkAppEnabled('files_external');
OCP\JSON::callCheck();
if (!isset($_POST['isPersonal']))
return;
if (!isset($_POST['mountPoint']))
return;
if (!isset($_POST['mountType']))
return;
if (!isset($_POST['applicable']))
return;
if ($_POST['isPersonal'] == 'true') {
OCP\JSON::checkLoggedIn();
$isPersonal = true;
} else {
OCP\JSON::checkAdminUser();
$isPersonal = false;
}
OC_Mount_Config::removeMountPoint((string)$_POST['mountPoint'], (string)$_POST['mountType'], (string)$_POST['applicable'], $isPersonal);

View File

@ -6,6 +6,8 @@
* later.
* See the COPYING-README file.
*/
$app = new \OCA\Files_external\Appinfo\Application();
$l = \OC::$server->getL10N('files_external');
OC::$CLASSPATH['OC\Files\Storage\StreamWrapper'] = 'files_external/lib/streamwrapper.php';

View File

@ -12,12 +12,13 @@ use \OCA\Files_External\Controller\AjaxController;
use \OCP\AppFramework\App;
use \OCP\IContainer;
/**
/**
* @package OCA\Files_External\Appinfo
*/
class Application extends App {
public function __construct(array $urlParams=array()) {
parent::__construct('files_external', $urlParams);
$container = $this->getContainer();
/**

View File

@ -22,10 +22,18 @@
namespace OCA\Files_External\Appinfo;
/**
* @var $this OC\Route\Router
**/
$application = new Application();
$application->registerRoutes(
$this,
array(
'resources' => array(
'global_storages' => array('url' => '/globalstorages'),
'user_storages' => array('url' => '/userstorages'),
),
'routes' => array(
array(
'name' => 'Ajax#getSshKeys',
@ -37,13 +45,7 @@ $application->registerRoutes(
)
);
/** @var $this OC\Route\Router */
$this->create('files_external_add_mountpoint', 'ajax/addMountPoint.php')
->actionInclude('files_external/ajax/addMountPoint.php');
$this->create('files_external_remove_mountpoint', 'ajax/removeMountPoint.php')
->actionInclude('files_external/ajax/removeMountPoint.php');
// TODO: move these to app framework
$this->create('files_external_add_root_certificate', 'ajax/addRootCertificate.php')
->actionInclude('files_external/ajax/addRootCertificate.php');
$this->create('files_external_remove_root_certificate', 'ajax/removeRootCertificate.php')

View File

@ -0,0 +1,145 @@
<?php
/**
* ownCloud - files_external
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Vincent Petry <pvince81@owncloud.com>
* @copyright Vincent Petry 2015
*/
namespace OCA\Files_External\Controller;
use \OCP\IConfig;
use \OCP\IUserSession;
use \OCP\IRequest;
use \OCP\AppFramework\Http\DataResponse;
use \OCP\AppFramework\Controller;
use \OCP\AppFramework\Http;
use \OCA\Files_external\Service\GlobalStoragesService;
use \OCA\Files_external\NotFoundException;
use \OCA\Files_external\Lib\StorageConfig;
class GlobalStoragesController extends StoragesController {
/**
* @param string $appName
* @param IRequest $request
* @param IConfig $config
* @param GlobalStoragesService $globalStoragesService
*/
public function __construct(
$AppName,
IRequest $request,
\OCP\IL10N $l10n,
GlobalStoragesService $globalStoragesService
){
parent::__construct(
$AppName,
$request,
$l10n,
$globalStoragesService
);
}
/**
* Create an external storage entry.
*
* @param string $mountPoint storage mount point
* @param string $backendClass backend class name
* @param array $backendOptions backend-specific options
* @param array $applicableUsers users for which to mount the storage
* @param array $applicableGroups groups for which to mount the storage
* @param int $priority priority
*
* @return DataResponse
*/
public function create(
$mountPoint,
$backendClass,
$backendOptions,
$applicableUsers,
$applicableGroups,
$priority
) {
$newStorage = new StorageConfig();
$newStorage->setMountPoint($mountPoint);
$newStorage->setBackendClass($backendClass);
$newStorage->setBackendOptions($backendOptions);
$newStorage->setApplicableUsers($applicableUsers);
$newStorage->setApplicableGroups($applicableGroups);
$newStorage->setPriority($priority);
$response = $this->validate($newStorage);
if (!empty($response)) {
return $response;
}
$newStorage = $this->service->addStorage($newStorage);
$this->updateStorageStatus($newStorage);
return new DataResponse(
$newStorage,
Http::STATUS_CREATED
);
}
/**
* Update an external storage entry.
*
* @param int $id storage id
* @param string $mountPoint storage mount point
* @param string $backendClass backend class name
* @param array $backendOptions backend-specific options
* @param array $applicableUsers users for which to mount the storage
* @param array $applicableGroups groups for which to mount the storage
* @param int $priority priority
*
* @return DataResponse
*/
public function update(
$id,
$mountPoint,
$backendClass,
$backendOptions,
$applicableUsers,
$applicableGroups,
$priority
) {
$storage = new StorageConfig($id);
$storage->setMountPoint($mountPoint);
$storage->setBackendClass($backendClass);
$storage->setBackendOptions($backendOptions);
$storage->setApplicableUsers($applicableUsers);
$storage->setApplicableGroups($applicableGroups);
$storage->setPriority($priority);
$response = $this->validate($storage);
if (!empty($response)) {
return $response;
}
try {
$storage = $this->service->updateStorage($storage);
} catch (NotFoundException $e) {
return new DataResponse(
[
'message' => (string)$this->l10n->t('Storage with id "%i" not found', array($id))
],
Http::STATUS_NOT_FOUND
);
}
$this->updateStorageStatus($storage);
return new DataResponse(
$storage,
Http::STATUS_OK
);
}
}

View File

@ -0,0 +1,157 @@
<?php
/**
* ownCloud - files_external
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Vincent Petry <pvince81@owncloud.com>
* @copyright Vincent Petry 2014
*/
namespace OCA\Files_External\Controller;
use \OCP\IConfig;
use \OCP\IUserSession;
use \OCP\IRequest;
use \OCP\AppFramework\Http\DataResponse;
use \OCP\AppFramework\Controller;
use \OCP\AppFramework\Http;
use \OCA\Files_external\Service\StoragesService;
use \OCA\Files_external\NotFoundException;
use \OCA\Files_external\Lib\StorageConfig;
abstract class StoragesController extends Controller {
/**
* @var \OCP\IL10N
*/
protected $l10n;
/**
* @var StoragesService
*/
protected $service;
/**
* @param string $appName
* @param IRequest $request
* @param IConfig $config
* @param StoragesService $storagesService
*/
public function __construct(
$AppName,
IRequest $request,
\OCP\IL10N $l10n,
StoragesService $storagesService
){
parent::__construct($AppName, $request);
$this->l10n = $l10n;
$this->service = $storagesService;
}
/**
* Validate storage config
*
* @param StorageConfig $storage storage config
*
* @return DataResponse|null returns response in case of validation error
*/
protected function validate(StorageConfig $storage) {
$mountPoint = $storage->getMountPoint();
if ($mountPoint === '' || $mountPoint === '/') {
return new DataResponse(
array(
'message' => (string)$this->l10n->t('Invalid mount point')
),
Http::STATUS_UNPROCESSABLE_ENTITY
);
}
// TODO: validate that other attrs are set
$backends = \OC_Mount_Config::getBackends();
if (!isset($backends[$storage->getBackendClass()])) {
// invalid backend
return new DataResponse(
array(
'message' => (string)$this->l10n->t('Invalid storage backend "%s"', array($storage->getBackendClass()))
),
Http::STATUS_UNPROCESSABLE_ENTITY
);
}
return null;
}
/**
* Check whether the given storage is available / valid.
*
* Note that this operation can be time consuming depending
* on whether the remote storage is available or not.
*
* @param StorageConfig $storage
*/
protected function updateStorageStatus(StorageConfig &$storage) {
// update status (can be time-consuming)
$storage->setStatus(
\OC_Mount_Config::getBackendStatus(
$storage->getBackendClass(),
$storage->getBackendOptions(),
false
)
);
}
/**
* Get an external storage entry.
*
* @param int $id storage id
*
* @return DataResponse
*/
public function show($id) {
try {
$storage = $this->service->getStorage($id);
$this->updateStorageStatus($storage);
} catch (NotFoundException $e) {
return new DataResponse(
[
'message' => (string)$this->l10n->t('Storage with id "%i" not found', array($id))
],
Http::STATUS_NOT_FOUND
);
}
return new DataResponse(
$storage,
Http::STATUS_OK
);
}
/**
* Deletes the storage with the given id.
*
* @param int $id storage id
*
* @return DataResponse
*/
public function destroy($id) {
try {
$this->service->removeStorage($id);
} catch (NotFoundException $e) {
return new DataResponse(
[
'message' => (string)$this->l10n->t('Storage with id "%i" not found', array($id))
],
Http::STATUS_NOT_FOUND
);
}
return new DataResponse([], Http::STATUS_NO_CONTENT);
}
}

View File

@ -0,0 +1,172 @@
<?php
/**
* ownCloud - files_external
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Vincent Petry <pvince81@owncloud.com>
* @copyright Vincent Petry 2015
*/
namespace OCA\Files_External\Controller;
use \OCP\IConfig;
use \OCP\IUserSession;
use \OCP\IRequest;
use \OCP\AppFramework\Http\DataResponse;
use \OCP\AppFramework\Controller;
use \OCP\AppFramework\Http;
use \OCA\Files_external\Service\UserStoragesService;
use \OCA\Files_external\NotFoundException;
use \OCA\Files_external\Lib\StorageConfig;
class UserStoragesController extends StoragesController {
/**
* @param string $appName
* @param IRequest $request
* @param IConfig $config
* @param UserStoragesService $userStoragesService
*/
public function __construct(
$AppName,
IRequest $request,
\OCP\IL10N $l10n,
UserStoragesService $userStoragesService
){
parent::__construct(
$AppName,
$request,
$l10n,
$userStoragesService
);
}
/**
* Validate storage config
*
* @param StorageConfig $storage storage config
*
* @return DataResponse|null returns response in case of validation error
*/
protected function validate(StorageConfig $storage) {
$result = parent::validate($storage);
if ($result != null) {
return $result;
}
// Verify that the mount point applies for the current user
// Prevent non-admin users from mounting local storage and other disabled backends
$allowedBackends = \OC_Mount_Config::getPersonalBackends();
if (!isset($allowedBackends[$storage->getBackendClass()])) {
return new DataResponse(
array(
'message' => (string)$this->l10n->t('Invalid storage backend "%s"', array($storage->getBackendClass()))
),
Http::STATUS_UNPROCESSABLE_ENTITY
);
}
return null;
}
/**
* @NoAdminRequired
* @{inheritdoc}
*/
public function show($id) {
return parent::show($id);
}
/**
* Create an external storage entry.
*
* @param string $mountPoint storage mount point
* @param string $backendClass backend class name
* @param array $backendOptions backend-specific options
*
* @return DataResponse
*
* @NoAdminRequired
*/
public function create(
$mountPoint,
$backendClass,
$backendOptions
) {
$newStorage = new StorageConfig();
$newStorage->setMountPoint($mountPoint);
$newStorage->setBackendClass($backendClass);
$newStorage->setBackendOptions($backendOptions);
$response = $this->validate($newStorage);
if (!empty($response)) {
return $response;
}
$newStorage = $this->service->addStorage($newStorage);
$this->updateStorageStatus($newStorage);
return new DataResponse(
$newStorage,
Http::STATUS_CREATED
);
}
/**
* Update an external storage entry.
*
* @param int $id storage id
* @param string $mountPoint storage mount point
* @param string $backendClass backend class name
* @param array $backendOptions backend-specific options
*
* @return DataResponse
*/
public function update(
$id,
$mountPoint,
$backendClass,
$backendOptions
) {
$storage = new StorageConfig($id);
$storage->setMountPoint($mountPoint);
$storage->setBackendClass($backendClass);
$storage->setBackendOptions($backendOptions);
$response = $this->validate($storage);
if (!empty($response)) {
return $response;
}
try {
$storage = $this->service->updateStorage($storage);
} catch (NotFoundException $e) {
return new DataResponse(
[
'message' => (string)$this->l10n->t('Storage with id "%i" not found', array($id))
],
Http::STATUS_NOT_FOUND
);
}
$this->updateStorageStatus($storage);
return new DataResponse(
$storage,
Http::STATUS_OK
);
}
/**
* {@inheritdoc}
* @NoAdminRequired
*/
public function destroy($id) {
return parent::destroy($id);
}
}

View File

@ -23,7 +23,7 @@ $(document).ready(function() {
$(token).val(result.access_token);
$(token_secret).val(result.access_token_secret);
$(configured).val('true');
OC.MountConfig.saveStorage(tr, function(status) {
OCA.External.Settings.mountConfig.saveStorageConfig(tr, function(status) {
if (status) {
$(tr).find('.configuration input').attr('disabled', 'disabled');
$(tr).find('.configuration').append('<span id="access" style="padding-left:0.5em;">'+t('files_external', 'Access granted')+'</span>');
@ -93,7 +93,7 @@ $(document).ready(function() {
$(configured).val('false');
$(token).val(result.data.request_token);
$(token_secret).val(result.data.request_token_secret);
OC.MountConfig.saveStorage(tr, function() {
OCA.External.Settings.mountConfig.saveStorageConfig(tr, function() {
window.location = result.data.url;
});
} else {

View File

@ -32,7 +32,7 @@ $(document).ready(function() {
if (result && result.status == 'success') {
$(token).val(result.data.token);
$(configured).val('true');
OC.MountConfig.saveStorage(tr, function(status) {
OCA.External.Settings.mountConfig.saveStorageConfig(tr, function(status) {
if (status) {
$(tr).find('.configuration input').attr('disabled', 'disabled');
$(tr).find('.configuration').append($('<span/>')
@ -115,7 +115,7 @@ $(document).ready(function() {
if (result && result.status == 'success') {
$(configured).val('false');
$(token).val('false');
OC.MountConfig.saveStorage(tr, function(status) {
OCA.External.Settings.mountConfig.saveStorageConfig(tr, function(status) {
window.location = result.data.url;
});
} else {

View File

@ -1,20 +1,24 @@
/*
* Copyright (c) 2014
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
(function(){
function updateStatus(statusEl, result){
statusEl.removeClass('success error loading-small');
if (result && result.status === 'success' && result.data.message) {
statusEl.addClass('success');
return true;
} else {
statusEl.addClass('error');
return false;
}
}
/**
* Returns the selection of applicable users in the given configuration row
*
* @param $row configuration row
* @return array array of user names
*/
function getSelection($row) {
var values = $row.find('.applicableUsers').select2('val');
if (!values || values.length === 0) {
values = ['all'];
values = [];
}
return values;
}
@ -31,182 +35,16 @@ function highlightInput($input) {
}
}
OC.MountConfig={
saveStorage:function($tr, callback) {
var mountPoint = $tr.find('.mountPoint input').val();
var oldMountPoint = $tr.find('.mountPoint input').data('mountpoint');
if (mountPoint === '') {
return false;
/**
* Initialize select2 plugin on the given elements
*
* @param {Array<Object>} array of jQuery elements
* @param {int} userListLimit page size for result list
*/
function addSelect2 ($elements, userListLimit) {
if (!$elements.length) {
return;
}
var statusSpan = $tr.find('.status span');
var backendClass = $tr.find('.backend').data('class');
var configuration = $tr.find('.configuration input');
var addMountPoint = true;
if (configuration.length < 1) {
return false;
}
var classOptions = {};
$.each(configuration, function(index, input) {
if ($(input).val() === '' && !$(input).hasClass('optional')) {
addMountPoint = false;
return false;
}
if ($(input).is(':checkbox')) {
if ($(input).is(':checked')) {
classOptions[$(input).data('parameter')] = true;
} else {
classOptions[$(input).data('parameter')] = false;
}
} else {
classOptions[$(input).data('parameter')] = $(input).val();
}
});
if ($('#externalStorage').data('admin') === true) {
var multiselect = getSelection($tr);
}
if (addMountPoint) {
var status = false;
if ($('#externalStorage').data('admin') === true) {
var isPersonal = false;
var oldGroups = $tr.find('.applicable').data('applicable-groups');
var oldUsers = $tr.find('.applicable').data('applicable-users');
var groups = [];
var users = [];
$.each(multiselect, function(index, value) {
var pos = value.indexOf('(group)');
if (pos != -1) {
var mountType = 'group';
var applicable = value.substr(0, pos);
if ($.inArray(applicable, oldGroups) != -1) {
oldGroups.splice($.inArray(applicable, oldGroups), 1);
}
groups.push(applicable);
} else {
var mountType = 'user';
var applicable = value;
if ($.inArray(applicable, oldUsers) != -1) {
oldUsers.splice($.inArray(applicable, oldUsers), 1);
}
users.push(applicable);
}
statusSpan.addClass('loading-small').removeClass('error success');
$.ajax({type: 'POST',
url: OC.filePath('files_external', 'ajax', 'addMountPoint.php'),
data: {
mountPoint: mountPoint,
'class': backendClass,
classOptions: classOptions,
mountType: mountType,
applicable: applicable,
isPersonal: isPersonal,
oldMountPoint: oldMountPoint
},
success: function(result) {
$tr.find('.mountPoint input').data('mountpoint', mountPoint);
status = updateStatus(statusSpan, result);
if (callback) {
callback(status);
}
},
error: function(result){
status = updateStatus(statusSpan, result);
if (callback) {
callback(status);
}
}
});
});
$tr.find('.applicable').data('applicable-groups', groups);
$tr.find('.applicable').data('applicable-users', users);
var mountType = 'group';
$.each(oldGroups, function(index, applicable) {
$.ajax({type: 'POST',
url: OC.filePath('files_external', 'ajax', 'removeMountPoint.php'),
data: {
mountPoint: mountPoint,
'class': backendClass,
classOptions: classOptions,
mountType: mountType,
applicable: applicable,
isPersonal: isPersonal
}
});
});
var mountType = 'user';
$.each(oldUsers, function(index, applicable) {
$.ajax({type: 'POST',
url: OC.filePath('files_external', 'ajax', 'removeMountPoint.php'),
data: {
mountPoint: mountPoint,
'class': backendClass,
classOptions: classOptions,
mountType: mountType,
applicable: applicable,
isPersonal: isPersonal
}
});
});
} else {
var isPersonal = true;
var mountType = 'user';
var applicable = OC.currentUser;
statusSpan.addClass('loading-small').removeClass('error success');
$.ajax({type: 'POST',
url: OC.filePath('files_external', 'ajax', 'addMountPoint.php'),
data: {
mountPoint: mountPoint,
'class': backendClass,
classOptions: classOptions,
mountType: mountType,
applicable: applicable,
isPersonal: isPersonal,
oldMountPoint: oldMountPoint
},
success: function(result) {
$tr.find('.mountPoint input').data('mountpoint', mountPoint);
status = updateStatus(statusSpan, result);
if (callback) {
callback(status);
}
},
error: function(result){
status = updateStatus(statusSpan, result);
if (callback) {
callback(status);
}
}
});
}
return status;
}
}
};
$(document).ready(function() {
var $externalStorage = $('#externalStorage');
//initialize hidden input field with list of users and groups
$externalStorage.find('tr:not(#addMountPoint)').each(function(i,tr) {
var $tr = $(tr);
var $applicable = $tr.find('.applicable');
if ($applicable.length > 0) {
var groups = $applicable.data('applicable-groups');
var groupsId = [];
$.each(groups, function () {
groupsId.push(this + '(group)');
});
var users = $applicable.data('applicable-users');
if (users.indexOf('all') > -1) {
$tr.find('.applicableUsers').val('');
} else {
$tr.find('.applicableUsers').val(groupsId.concat(users).join(','));
}
}
});
var userListLimit = 30;
function addSelect2 ($elements) {
if ($elements.length > 0) {
$elements.select2({
placeholder: t('files_external', 'All users. Type to select user or group.'),
allowClear: true,
@ -223,7 +61,7 @@ $(document).ready(function() {
offset: userListLimit*(page-1) // page number starts with 0
};
},
results: function (data, page) {
results: function (data) {
if (data.status === 'success') {
var results = [];
@ -303,26 +141,387 @@ $(document).ready(function() {
if ($div.data('type') === 'user') {
$div.avatar($div.data('name'),32);
}
})
});
});
}
/**
* @class OCA.External.Settings.StorageConfig
*
* @classdesc External storage config
*/
var StorageConfig = function(id) {
this.id = id;
this.backendOptions = {};
};
// Keep this in sync with \OC_Mount_Config::STATUS_*
StorageConfig.Status = {
IN_PROGRESS: -1,
SUCCESS: 0,
ERROR: 1
};
/**
* @memberof OCA.External.Settings
*/
StorageConfig.prototype = {
_url: null,
/**
* Storage id
*
* @type int
*/
id: null,
/**
* Mount point
*
* @type string
*/
mountPoint: '',
/**
* Backend class name
*
* @type string
*/
backendClass: null,
/**
* Backend-specific configuration
*
* @type Object.<string,object>
*/
backendOptions: null,
/**
* 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(this._url);
var method = 'POST';
if (_.isNumber(this.id)) {
method = 'PUT';
url = OC.generateUrl(this._url + '/{id}', {id: this.id});
}
$.ajax({
type: method,
url: url,
data: this.getData(),
success: function(result) {
self.id = result.id;
if (_.isFunction(options.success)) {
options.success(result);
}
},
error: options.error
});
},
/**
* Returns the data from this object
*
* @return {Array} JSON array of the data
*/
getData: function() {
var data = {
mountPoint: this.mountPoint,
backendClass: this.backendClass,
backendOptions: this.backendOptions
};
if (this.id) {
data.id = this.id;
}
return data;
},
/**
* Recheck the storage
*
* @param {Function} [options.success] success callback, receives result as argument
* @param {Function} [options.error] error callback
*/
recheck: function(options) {
if (!_.isNumber(this.id)) {
if (_.isFunction(options.error)) {
options.error();
}
return;
}
$.ajax({
type: 'GET',
url: OC.generateUrl(this._url + '/{id}', {id: this.id}),
success: options.success,
error: options.error
});
},
/**
* Deletes the storage
*
* @param {Function} [options.success] success callback
* @param {Function} [options.error] error callback
*/
destroy: function(options) {
if (!_.isNumber(this.id)) {
// the storage hasn't even been created => success
if (_.isFunction(options.success)) {
options.success();
}
return;
}
var self = this;
$.ajax({
type: 'DELETE',
url: OC.generateUrl(this._url + '/{id}', {id: this.id}),
success: options.success,
error: options.error
});
},
/**
* Validate this model
*
* @return {boolean} false if errors exist, true otherwise
*/
validate: function() {
if (this.mountPoint === '') {
return false;
}
if (this.errors) {
return false;
}
return true;
}
};
/**
* @class OCA.External.Settings.GlobalStorageConfig
* @augments OCA.External.Settings.StorageConfig
*
* @classdesc Global external storage config
*/
var GlobalStorageConfig = function(id) {
this.id = id;
this.applicableUsers = [];
this.applicableGroups = [];
};
/**
* @memberOf OCA.External.Settings
*/
GlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
/** @lends OCA.External.Settings.GlobalStorageConfig.prototype */ {
_url: 'apps/files_external/globalstorages',
/**
* Applicable users
*
* @type Array.<string>
*/
applicableUsers: null,
/**
* Applicable groups
*
* @type Array.<string>
*/
applicableGroups: null,
/**
* Returns the data from this object
*
* @return {Array} JSON array of the data
*/
getData: function() {
var data = StorageConfig.prototype.getData.apply(this, arguments);
return _.extend(data, {
applicableUsers: this.applicableUsers,
applicableGroups: this.applicableGroups,
});
}
}
addSelect2($('tr:not(#addMountPoint) .applicableUsers'));
});
$externalStorage.on('change', '#selectBackend', function() {
/**
* @class OCA.External.Settings.UserStorageConfig
* @augments OCA.External.Settings.StorageConfig
*
* @classdesc User external storage config
*/
var UserStorageConfig = function(id) {
this.id = id;
};
UserStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
/** @lends OCA.External.Settings.UserStorageConfig.prototype */ {
_url: 'apps/files_external/userstorages'
});
/**
* @class OCA.External.Settings.MountConfigListView
*
* @classdesc Mount configuration list view
*
* @param {Object} $el DOM object containing the list
* @param {Object} [options]
* @param {int} [options.userListLimit] page size in applicable users dropdown
*/
var MountConfigListView = function($el, options) {
this.initialize($el, options);
};
/**
* @memberOf OCA.External.Settings
*/
MountConfigListView.prototype = {
/**
* jQuery element containing the config list
*
* @type Object
*/
$el: null,
/**
* Storage config class
*
* @type Class
*/
_storageConfigClass: null,
/**
* Flag whether the list is about user storage configs (true)
* or global storage configs (false)
*
* @type bool
*/
_isPersonal: false,
/**
* Page size in applicable users dropdown
*
* @type int
*/
_userListLimit: 30,
/**
* List of supported backends
*
* @type Object.<string,Object>
*/
_allBackends: null,
/**
* @param {Object} $el DOM object containing the list
* @param {Object} [options]
* @param {int} [options.userListLimit] page size in applicable users dropdown
*/
initialize: function($el, options) {
this.$el = $el;
this._isPersonal = ($el.data('admin') !== true);
if (this._isPersonal) {
this._storageConfigClass = OCA.External.Settings.UserStorageConfig;
} else {
this._storageConfigClass = OCA.External.Settings.GlobalStorageConfig;
}
if (options && !_.isUndefined(options.userListLimit)) {
this._userListLimit = options.userListLimit;
}
// read the backend config that was carefully crammed
// into the data-configurations attribute of the select
this._allBackends = this.$el.find('.selectBackend').data('configurations');
//initialize hidden input field with list of users and groups
this.$el.find('tr:not(#addMountPoint)').each(function(i,tr) {
var $tr = $(tr);
var $applicable = $tr.find('.applicable');
if ($applicable.length > 0) {
var groups = $applicable.data('applicable-groups');
var groupsId = [];
$.each(groups, function () {
groupsId.push(this + '(group)');
});
var users = $applicable.data('applicable-users');
if (users.indexOf('all') > -1 || users === '') {
$tr.find('.applicableUsers').val('');
} else {
$tr.find('.applicableUsers').val(groupsId.concat(users).join(','));
}
}
});
addSelect2(this.$el.find('tr:not(#addMountPoint) .applicableUsers'), this._userListLimit);
this._initEvents();
},
/**
* Initialize DOM event handlers
*/
_initEvents: function() {
var self = this;
this.$el.on('paste', 'td input', function() {
var $me = $(this);
var $tr = $me.closest('tr');
setTimeout(function() {
highlightInput($me);
self.saveStorageConfig($tr);
}, 20);
});
var timer;
this.$el.on('keyup', 'td input', function() {
clearTimeout(timer);
var $tr = $(this).closest('tr');
$externalStorage.find('tbody').append($tr.clone());
$externalStorage.find('tbody tr').last().find('.mountPoint input').val('');
var selected = $(this).find('option:selected').text();
var backendClass = $(this).val();
highlightInput($(this));
if ($(this).val) {
timer = setTimeout(function() {
self.saveStorageConfig($tr);
}, 2000);
}
});
this.$el.on('change', 'td input:checkbox', function() {
self.saveStorageConfig($(this).closest('tr'));
});
this.$el.on('change', '.applicable', function() {
self.saveStorageConfig($(this).closest('tr'));
});
this.$el.on('click', '.status>span', function() {
self.recheckStorageConfig($(this).closest('tr'));
});
this.$el.on('click', 'td.remove>img', function() {
self.deleteStorageConfig($(this).closest('tr'));
});
this.$el.on('change', '.selectBackend', _.bind(this._onSelectBackend, this));
},
_onSelectBackend: function(event) {
var $target = $(event.target);
var $el = this.$el;
var $tr = $target.closest('tr');
$el.find('tbody').append($tr.clone());
$el.find('tbody tr').last().find('.mountPoint input').val('');
var selected = $target.find('option:selected').text();
var backendClass = $target.val();
$tr.find('.backend').text(selected);
if ($tr.find('.mountPoint input').val() === '') {
$tr.find('.mountPoint input').val(suggestMountPoint(selected));
$tr.find('.mountPoint input').val(this._suggestMountPoint(selected));
}
$tr.addClass(backendClass);
$tr.find('.status').append('<span></span>');
$tr.find('.backend').data('class', backendClass);
var configurations = $(this).data('configurations');
var configurations = this._allBackends;
var $td = $tr.find('td.configuration');
$.each(configurations, function(backend, parameters) {
if (backend === backendClass) {
@ -347,7 +546,7 @@ $(document).ready(function() {
highlightInput(newElement);
$td.append(newElement);
});
if (parameters['custom'] && $externalStorage.find('tbody tr.'+backendClass.replace(/\\/g, '\\\\')).length === 1) {
if (parameters['custom'] && $el.find('tbody tr.'+backendClass.replace(/\\/g, '\\\\')).length === 1) {
OC.addScript('files_external', parameters['custom']);
}
$td.children().not('[type=hidden]').first().focus();
@ -357,11 +556,190 @@ $(document).ready(function() {
$tr.find('td').last().attr('class', 'remove');
$tr.find('td').last().removeAttr('style');
$tr.removeAttr('id');
$(this).remove();
addSelect2($tr.find('.applicableUsers'));
$target.remove();
addSelect2($tr.find('.applicableUsers'), this._userListLimit);
},
/**
* Gets the storage model from the given row
*
* @param $tr row element
* @return {OCA.External.StorageConfig} storage model instance
*/
getStorageConfig: function($tr) {
var storageId = parseInt($tr.attr('data-id'), 10);
if (!storageId) {
// new entry
storageId = null;
}
var storage = new this._storageConfigClass(storageId);
storage.mountPoint = $tr.find('.mountPoint input').val();
storage.backendClass = $tr.find('.backend').data('class');
var classOptions = {};
var configuration = $tr.find('.configuration input');
var missingOptions = [];
$.each(configuration, function(index, input) {
var $input = $(input);
var parameter = $input.data('parameter');
if ($input.attr('type') === 'button') {
return;
}
if ($input.val() === '' && !$input.hasClass('optional')) {
missingOptions.push(parameter);
return;
}
if ($(input).is(':checkbox')) {
if ($(input).is(':checked')) {
classOptions[parameter] = true;
} else {
classOptions[parameter] = false;
}
} else {
classOptions[parameter] = $(input).val();
}
});
function suggestMountPoint(defaultMountPoint) {
storage.backendOptions = classOptions;
if (missingOptions.length) {
storage.errors = {
backendOptions: missingOptions
};
}
// gather selected users and groups
if (!this._isPersonal) {
var groups = [];
var users = [];
var multiselect = getSelection($tr);
$.each(multiselect, function(index, value) {
var pos = value.indexOf('(group)');
if (pos !== -1) {
groups.push(value.substr(0, pos));
} else {
users.push(value);
}
});
// FIXME: this should be done in the multiselect change event instead
$tr.find('.applicable')
.data('applicable-groups', groups)
.data('applicable-users', users);
storage.applicableUsers = users;
storage.applicableGroups = groups;
}
return storage;
},
/**
* Deletes the storage from the given tr
*
* @param $tr storage row
* @param Function callback callback to call after save
*/
deleteStorageConfig: function($tr) {
var self = this;
var configId = $tr.data('id');
if (!_.isNumber(configId)) {
// deleting unsaved storage
$tr.remove();
return;
}
var storage = new this._storageConfigClass(configId);
this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
storage.destroy({
success: function() {
$tr.remove();
},
error: function() {
self.updateStatus($tr, StorageConfig.Status.ERROR);
}
});
},
/**
* Saves the storage from the given tr
*
* @param $tr storage row
* @param Function callback callback to call after save
*/
saveStorageConfig:function($tr, callback) {
var self = this;
var storage = this.getStorageConfig($tr);
if (!storage.validate()) {
return false;
}
this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
storage.save({
success: function(result) {
self.updateStatus($tr, result.status);
$tr.attr('data-id', result.id);
if (_.isFunction(callback)) {
callback(storage);
}
},
error: function() {
self.updateStatus($tr, StorageConfig.Status.ERROR);
}
});
},
/**
* Recheck storage availability
*
* @param {jQuery} $tr storage row
* @return {boolean} success
*/
recheckStorageConfig: function($tr) {
var self = this;
var storage = this.getStorageConfig($tr);
if (!storage.validate()) {
return false;
}
this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
storage.recheck({
success: function(result) {
self.updateStatus($tr, result.status);
},
error: function() {
self.updateStatus($tr, StorageConfig.Status.ERROR);
}
});
},
/**
* Update status display
*
* @param {jQuery} $tr
* @param {int} status
*/
updateStatus: function($tr, status) {
var $statusSpan = $tr.find('.status span');
$statusSpan.removeClass('success error loading-small');
switch (status) {
case StorageConfig.Status.IN_PROGRESS:
$statusSpan.addClass('loading-small');
break;
case StorageConfig.Status.SUCCESS:
$statusSpan.addClass('success');
break;
default:
$statusSpan.addClass('error');
}
},
/**
* Suggest mount point name that doesn't conflict with the existing names in the list
*
* @param {string} defaultMountPoint default name
*/
_suggestMountPoint: function(defaultMountPoint) {
var $el = this.$el;
var pos = defaultMountPoint.indexOf('/');
if (pos !== -1) {
defaultMountPoint = defaultMountPoint.substring(0, pos);
@ -372,7 +750,7 @@ $(document).ready(function() {
var match = true;
while (match && i < 20) {
match = false;
$externalStorage.find('tbody td.mountPoint input').each(function(index, mountPoint) {
$el.find('tbody td.mountPoint input').each(function(index, mountPoint) {
if ($(mountPoint).val() === defaultMountPoint+append) {
match = true;
return false;
@ -385,42 +763,12 @@ $(document).ready(function() {
break;
}
}
return defaultMountPoint+append;
return defaultMountPoint + append;
}
};
$externalStorage.on('paste', 'td input', function() {
var $me = $(this);
var $tr = $me.closest('tr');
setTimeout(function() {
highlightInput($me);
OC.MountConfig.saveStorage($tr);
}, 20);
});
var timer;
$externalStorage.on('keyup', 'td input', function() {
clearTimeout(timer);
var $tr = $(this).closest('tr');
highlightInput($(this));
if ($(this).val) {
timer = setTimeout(function() {
OC.MountConfig.saveStorage($tr);
}, 2000);
}
});
$externalStorage.on('change', 'td input:checkbox', function() {
OC.MountConfig.saveStorage($(this).closest('tr'));
});
$externalStorage.on('change', '.applicable', function() {
OC.MountConfig.saveStorage($(this).closest('tr'));
});
$externalStorage.on('click', '.status>span', function() {
OC.MountConfig.saveStorage($(this).closest('tr'));
});
$(document).ready(function() {
var mountConfigListView = new MountConfigListView($('#externalStorage'));
$('#sslCertificate').on('click', 'td.remove>img', function() {
var $tr = $(this).closest('tr');
@ -429,33 +777,7 @@ $(document).ready(function() {
return true;
});
$externalStorage.on('click', 'td.remove>img', function() {
var $tr = $(this).closest('tr');
var mountPoint = $tr.find('.mountPoint input').val();
if ($externalStorage.data('admin') === true) {
var isPersonal = false;
var multiselect = getSelection($tr);
$.each(multiselect, function(index, value) {
var pos = value.indexOf('(group)');
if (pos != -1) {
var mountType = 'group';
var applicable = value.substr(0, pos);
} else {
var mountType = 'user';
var applicable = value;
}
$.post(OC.filePath('files_external', 'ajax', 'removeMountPoint.php'), { mountPoint: mountPoint, mountType: mountType, applicable: applicable, isPersonal: isPersonal });
});
} else {
var mountType = 'user';
var applicable = OC.currentUser;
var isPersonal = true;
$.post(OC.filePath('files_external', 'ajax', 'removeMountPoint.php'), { mountPoint: mountPoint, mountType: mountType, applicable: applicable, isPersonal: isPersonal });
}
$tr.remove();
});
// TODO: move this into its own View class
var $allowUserMounting = $('#allowUserMounting');
$allowUserMounting.bind('change', function() {
OC.msg.startSaving('#userMountingMsg');
@ -484,6 +806,31 @@ $(document).ready(function() {
}
});
// global instance
OCA.External.Settings.mountConfig = mountConfigListView;
/**
* Legacy
*
* @namespace
* @deprecated use OCA.External.Settings.mountConfig instead
*/
OC.MountConfig = {
saveStorage: _.bind(mountConfigListView.saveStorageConfig, mountConfigListView)
};
});
// export
OCA.External = OCA.External || {};
/**
* @namespace
*/
OCA.External.Settings = OCA.External.Settings || {};
OCA.External.Settings.GlobalStorageConfig = GlobalStorageConfig;
OCA.External.Settings.UserStorageConfig = UserStorageConfig;
OCA.External.Settings.MountConfigListView = MountConfigListView;
})();

View File

@ -42,7 +42,7 @@ $(document).ready(function() {
if (result && result.status === 'success') {
$(config).find('[data-parameter="public_key"]').val(result.data.public_key);
$(config).find('[data-parameter="private_key"]').val(result.data.private_key);
OC.MountConfig.saveStorage(tr, function() {
OCA.External.mountConfig.saveStorageConfig(tr, function() {
// Nothing to do
});
} else {

View File

@ -32,6 +32,10 @@ class OC_Mount_Config {
const MOUNT_TYPE_USER = 'user';
const MOUNT_TYPE_PERSONAL = 'personal';
// getBackendStatus return types
const STATUS_SUCCESS = 0;
const STATUS_ERROR = 1;
// whether to skip backend test (for unit tests, as this static class is not mockable)
public static $skipTest = false;
@ -143,15 +147,9 @@ class OC_Mount_Config {
$mountPoints = array();
$datadir = \OC_Config::getValue("datadirectory", \OC::$SERVERROOT . "/data");
$mount_file = \OC_Config::getValue("mount_file", $datadir . "/mount.json");
$backends = self::getBackends();
//move config file to it's new position
if (is_file(\OC::$SERVERROOT . '/config/mount.json')) {
rename(\OC::$SERVERROOT . '/config/mount.json', $mount_file);
}
// Load system mount points
$mountConfig = self::readData();
@ -349,6 +347,8 @@ class OC_Mount_Config {
$mountPoint = substr($mountPoint, 13);
$config = array(
'id' => (int) $mount['id'],
'storage_id' => (int) $mount['storage_id'],
'class' => $mount['class'],
'mountpoint' => $mountPoint,
'backend' => $backends[$mount['class']]['backend'],
@ -383,6 +383,8 @@ class OC_Mount_Config {
// Remove '/$user/files/' from mount point
$mountPoint = substr($mountPoint, 13);
$config = array(
'id' => (int) $mount['id'],
'storage_id' => (int) $mount['storage_id'],
'class' => $mount['class'],
'mountpoint' => $mountPoint,
'backend' => $backends[$mount['class']]['backend'],
@ -425,6 +427,8 @@ class OC_Mount_Config {
}
$mount['options'] = self::decryptPasswords($mount['options']);
$personal[] = array(
'id' => (int) $mount['id'],
'storage_id' => (int) $mount['storage_id'],
'class' => $mount['class'],
// Remove '/uid/files/' from mount point
'mountpoint' => substr($mountPoint, strlen($uid) + 8),
@ -442,11 +446,11 @@ class OC_Mount_Config {
*
* @param string $class backend class name
* @param array $options backend configuration options
* @return bool true if the connection succeeded, false otherwise
* @return int see self::STATUS_*
*/
private static function getBackendStatus($class, $options, $isPersonal) {
public static function getBackendStatus($class, $options, $isPersonal) {
if (self::$skipTest) {
return true;
return self::STATUS_SUCCESS;
}
foreach ($options as &$option) {
$option = self::setUserVars(OCP\User::getUser(), $option);
@ -454,13 +458,14 @@ class OC_Mount_Config {
if (class_exists($class)) {
try {
$storage = new $class($options);
return $storage->test($isPersonal);
if ($storage->test($isPersonal)) {
return self::STATUS_SUCCESS;
}
} catch (Exception $exception) {
\OCP\Util::logException('files_external', $exception);
return false;
}
}
return false;
return self::STATUS_ERROR;
}
/**
@ -474,6 +479,8 @@ class OC_Mount_Config {
* @param bool $isPersonal Personal or system mount point i.e. is this being called from the personal or admin page
* @param int|null $priority Mount point priority, null for default
* @return boolean
*
* @deprecated use StoragesService#addStorage() instead
*/
public static function addMountPoint($mountPoint,
$class,
@ -537,7 +544,7 @@ class OC_Mount_Config {
self::writeData($isPersonal ? OCP\User::getUser() : null, $mountPoints);
$result = self::getBackendStatus($class, $classOptions, $isPersonal);
if ($result && $isNew) {
if ($result === self::STATUS_SUCCESS && $isNew) {
\OC_Hook::emit(
\OC\Files\Filesystem::CLASSNAME,
\OC\Files\Filesystem::signal_create_mount,
@ -558,6 +565,8 @@ class OC_Mount_Config {
* @param string $applicable User or group to remove mount from
* @param bool $isPersonal Personal or system mount point
* @return bool
*
* @deprecated use StoragesService#removeStorage() instead
*/
public static function removeMountPoint($mountPoint, $mountType, $applicable, $isPersonal = false) {
// Verify that the mount point applies for the current user
@ -622,13 +631,10 @@ class OC_Mount_Config {
* @param string|null $user If not null, personal for $user, otherwise system
* @return array
*/
private static function readData($user = null) {
$parser = new \OC\ArrayParser();
public static function readData($user = null) {
if (isset($user)) {
$phpFile = OC_User::getHome($user) . '/mount.php';
$jsonFile = OC_User::getHome($user) . '/mount.json';
} else {
$phpFile = OC::$SERVERROOT . '/config/mount.php';
$datadir = \OC_Config::getValue('datadirectory', \OC::$SERVERROOT . '/data/');
$jsonFile = \OC_Config::getValue('mount_file', $datadir . '/mount.json');
}
@ -637,11 +643,6 @@ class OC_Mount_Config {
if (is_array($mountPoints)) {
return $mountPoints;
}
} elseif (is_file($phpFile)) {
$mountPoints = $parser->parsePHP(file_get_contents($phpFile));
if (is_array($mountPoints)) {
return $mountPoints;
}
}
return array();
}
@ -652,7 +653,7 @@ class OC_Mount_Config {
* @param string|null $user If not null, personal for $user, otherwise system
* @param array $data Mount points
*/
private static function writeData($user, $data) {
public static function writeData($user, $data) {
if (isset($user)) {
$file = OC_User::getHome($user) . '/mount.json';
} else {
@ -769,7 +770,7 @@ class OC_Mount_Config {
* @param array $options mount options
* @return array updated options
*/
private static function encryptPasswords($options) {
public static function encryptPasswords($options) {
if (isset($options['password'])) {
$options['password_encrypted'] = self::encryptPassword($options['password']);
// do not unset the password, we want to keep the keys order
@ -785,7 +786,7 @@ class OC_Mount_Config {
* @param array $options mount options
* @return array updated options
*/
private static function decryptPasswords($options) {
public static function decryptPasswords($options) {
// note: legacy options might still have the unencrypted password in the "password" field
if (isset($options['password_encrypted'])) {
$options['password'] = self::decryptPassword($options['password_encrypted']);

View File

@ -0,0 +1,15 @@
<?php
/**
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\Files_external;
/**
* Storage is not found
*/
class NotFoundException extends \Exception {
}

View File

@ -0,0 +1,243 @@
<?php
/**
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\Files_external\Lib;
/**
* External storage configuration
*/
class StorageConfig implements \JsonSerializable {
/**
* @var int
*/
private $id;
/**
* @var string
*/
private $backendClass;
/**
* @var array
*/
private $backendOptions = [];
/**
* @var string
*/
private $mountPoint;
/**
* @var int
*/
private $status;
/**
* @var int
*/
private $priority;
/**
* @var array
*/
private $applicableUsers = [];
/**
* @var array
*/
private $applicableGroups = [];
/**
* @param int|null $id config id or null for a new config
*/
public function __construct($id = null) {
$this->id = $id;
}
/**
* Returns the configuration id
*
* @return int
*/
public function getId() {
return $this->id;
}
/**
* Sets the configuration id
*
* @param int configuration id
*/
public function setId($id) {
$this->id = $id;
}
/**
* Returns mount point path relative to the user's
* "files" folder.
*
* @return string path
*/
public function getMountPoint() {
return $this->mountPoint;
}
/**
* Sets mount point path relative to the user's
* "files" folder.
* The path will be normalized.
*
* @param string path
*/
public function setMountPoint($mountPoint) {
$this->mountPoint = \OC\Files\Filesystem::normalizePath($mountPoint);
}
/**
* Returns the external storage backend class name
*
* @return string external storage backend class name
*/
public function getBackendClass() {
return $this->backendClass;
}
/**
* Sets the external storage backend class name
*
* @param string external storage backend class name
*/
public function setBackendClass($backendClass) {
$this->backendClass = $backendClass;
}
/**
* Returns the external storage backend-specific options
*
* @return array backend options
*/
public function getBackendOptions() {
return $this->backendOptions;
}
/**
* Sets the external storage backend-specific options
*
* @param array backend options
*/
public function setBackendOptions($backendOptions) {
$this->backendOptions = $backendOptions;
}
/**
* Returns the mount priority
*
* @return int priority
*/
public function getPriority() {
return $this->priority;
}
/**
* Sets the mount priotity
*
* @param int priority
*/
public function setPriority($priority) {
$this->priority = $priority;
}
/**
* Returns the users for which to mount this storage
*
* @return array applicable users
*/
public function getApplicableUsers() {
return $this->applicableUsers;
}
/**
* Sets the users for which to mount this storage
*
* @param array applicable users
*/
public function setApplicableUsers($applicableUsers) {
if (is_null($applicableUsers)) {
$applicableUsers = [];
}
$this->applicableUsers = $applicableUsers;
}
/**
* Returns the groups for which to mount this storage
*
* @return array applicable groups
*/
public function getApplicableGroups() {
return $this->applicableGroups;
}
/**
* Sets the groups for which to mount this storage
*
* @param array applicable groups
*/
public function setApplicableGroups($applicableGroups) {
if (is_null($applicableGroups)) {
$applicableGroups = [];
}
$this->applicableGroups = $applicableGroups;
}
/**
* Sets the storage status, whether the config worked last time
*
* @return int $status status
*/
public function getStatus() {
return $this->status;
}
/**
* Sets the storage status, whether the config worked last time
*
* @param int $status status
*/
public function setStatus($status) {
$this->status = $status;
}
/**
* Serialize config to JSON
*
* @return array
*/
public function jsonSerialize() {
$result = [];
if (!is_null($this->id)) {
$result['id'] = $this->id;
}
$result['mountPoint'] = $this->mountPoint;
$result['backendClass'] = $this->backendClass;
$result['backendOptions'] = $this->backendOptions;
if (!is_null($this->priority)) {
$result['priority'] = $this->priority;
}
if (!empty($this->applicableUsers)) {
$result['applicableUsers'] = $this->applicableUsers;
}
if (!empty($this->applicableGroups)) {
$result['applicableGroups'] = $this->applicableGroups;
}
if (!is_null($this->status)) {
$result['status'] = $this->status;
}
return $result;
}
}

View File

@ -0,0 +1,192 @@
<?php
/**
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\Files_external\Service;
use \OCP\IUserSession;
use \OC\Files\Filesystem;
use \OCA\Files_external\Lib\StorageConfig;
use \OCA\Files_external\NotFoundException;
/**
* Service class to manage global external storages
*/
class GlobalStoragesService extends StoragesService {
/**
* Write the storages to the configuration.
*
* @param string $user user or null for global config
* @param array $storages map of storage id to storage config
*/
public function writeConfig($storages) {
// let the horror begin
$mountPoints = [];
foreach ($storages as $storageConfig) {
$mountPoint = $storageConfig->getMountPoint();
$oldBackendOptions = $storageConfig->getBackendOptions();
$storageConfig->setBackendOptions(
\OC_Mount_Config::encryptPasswords(
$oldBackendOptions
)
);
// system mount
$rootMountPoint = '/$user/files/' . ltrim($mountPoint, '/');
$applicableUsers = $storageConfig->getApplicableUsers();
$applicableGroups = $storageConfig->getApplicableGroups();
foreach ($applicableUsers as $applicable) {
$this->addMountPoint(
$mountPoints,
\OC_Mount_Config::MOUNT_TYPE_USER,
$applicable,
$rootMountPoint,
$storageConfig
);
}
foreach ($applicableGroups as $applicable) {
$this->addMountPoint(
$mountPoints,
\OC_Mount_Config::MOUNT_TYPE_GROUP,
$applicable,
$rootMountPoint,
$storageConfig
);
}
// if neither "applicableGroups" or "applicableUsers" were set, use "all" user
if (empty($applicableUsers) && empty($applicableGroups)) {
$this->addMountPoint(
$mountPoints,
\OC_Mount_Config::MOUNT_TYPE_USER,
'all',
$rootMountPoint,
$storageConfig
);
}
// restore old backend options where the password was not encrypted,
// because we don't want to change the state of the original object
$storageConfig->setBackendOptions($oldBackendOptions);
}
\OC_Mount_Config::writeData(null, $mountPoints);
}
/**
* Triggers $signal for all applicable users of the given
* storage
*
* @param StorageConfig $storage storage data
* @param string $signal signal to trigger
*/
protected function triggerHooks(StorageConfig $storage, $signal) {
$applicableUsers = $storage->getApplicableUsers();
$applicableGroups = $storage->getApplicableGroups();
if (empty($applicableUsers) && empty($applicableGroups)) {
// raise for user "all"
$this->triggerApplicableHooks(
$signal,
$storage->getMountPoint(),
\OC_Mount_Config::MOUNT_TYPE_USER,
['all']
);
return;
}
$this->triggerApplicableHooks(
$signal,
$storage->getMountPoint(),
\OC_Mount_Config::MOUNT_TYPE_USER,
$applicableUsers
);
$this->triggerApplicableHooks(
$signal,
$storage->getMountPoint(),
\OC_Mount_Config::MOUNT_TYPE_GROUP,
$applicableGroups
);
}
/**
* Triggers signal_create_mount or signal_delete_mount to
* accomodate for additions/deletions in applicableUsers
* and applicableGroups fields.
*
* @param StorageConfig $oldStorage old storage data
* @param StorageConfig $newStorage new storage data
*/
protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $newStorage) {
// if mount point changed, it's like a deletion + creation
if ($oldStorage->getMountPoint() !== $newStorage->getMountPoint()) {
$this->triggerHooks($oldStorage, Filesystem::signal_delete_mount);
$this->triggerHooks($newStorage, Filesystem::signal_create_mount);
return;
}
$userAdditions = array_diff($newStorage->getApplicableUsers(), $oldStorage->getApplicableUsers());
$userDeletions = array_diff($oldStorage->getApplicableUsers(), $newStorage->getApplicableUsers());
$groupAdditions = array_diff($newStorage->getApplicableGroups(), $oldStorage->getApplicableGroups());
$groupDeletions = array_diff($oldStorage->getApplicableGroups(), $newStorage->getApplicableGroups());
// if no applicable were set, raise a signal for "all"
if (empty($oldStorage->getApplicableUsers()) && empty($oldStorage->getApplicableGroups())) {
$this->triggerApplicableHooks(
Filesystem::signal_delete_mount,
$oldStorage->getMountPoint(),
\OC_Mount_Config::MOUNT_TYPE_USER,
['all']
);
}
// trigger delete for removed users
$this->triggerApplicableHooks(
Filesystem::signal_delete_mount,
$oldStorage->getMountPoint(),
\OC_Mount_Config::MOUNT_TYPE_USER,
$userDeletions
);
// trigger delete for removed groups
$this->triggerApplicableHooks(
Filesystem::signal_delete_mount,
$oldStorage->getMountPoint(),
\OC_Mount_Config::MOUNT_TYPE_GROUP,
$groupDeletions
);
// and now add the new users
$this->triggerApplicableHooks(
Filesystem::signal_create_mount,
$newStorage->getMountPoint(),
\OC_Mount_Config::MOUNT_TYPE_USER,
$userAdditions
);
// and now add the new groups
$this->triggerApplicableHooks(
Filesystem::signal_create_mount,
$newStorage->getMountPoint(),
\OC_Mount_Config::MOUNT_TYPE_GROUP,
$groupAdditions
);
// if no applicable, raise a signal for "all"
if (empty($newStorage->getApplicableUsers()) && empty($newStorage->getApplicableGroups())) {
$this->triggerApplicableHooks(
Filesystem::signal_create_mount,
$newStorage->getMountPoint(),
\OC_Mount_Config::MOUNT_TYPE_USER,
['all']
);
}
}
}

View File

@ -0,0 +1,303 @@
<?php
/**
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\Files_external\Service;
use \OCP\IUserSession;
use \OC\Files\Filesystem;
use \OCA\Files_external\Lib\StorageConfig;
use \OCA\Files_external\NotFoundException;
/**
* Service class to manage external storages
*/
abstract class StoragesService {
/**
* Read legacy config data
*
* @return array list of mount configs
*/
protected function readLegacyConfig() {
// read global config
return \OC_Mount_Config::readData();
}
/**
* Read the external storages config
*
* @return array map of storage id to storage config
*/
protected function readConfig() {
$mountPoints = $this->readLegacyConfig();
/**
* Here is the how the horribly messy mount point array looks like
* from the mount.json file:
*
* $storageOptions = $mountPoints[$mountType][$applicable][$mountPath]
*
* - $mountType is either "user" or "group"
* - $applicable is the name of a user or group (or the current user for personal mounts)
* - $mountPath is the mount point path (where the storage must be mounted)
* - $storageOptions is a map of storage options:
* - "priority": storage priority
* - "backend": backend class name
* - "options": backend-specific options
*/
// group by storage id
$storages = [];
foreach ($mountPoints as $mountType => $applicables) {
foreach ($applicables as $applicable => $mountPaths) {
foreach ($mountPaths as $rootMountPath => $storageOptions) {
// the root mount point is in the format "/$user/files/the/mount/point"
// we remove the "/$user/files" prefix
$parts = explode('/', trim($rootMountPath, '/'), 3);
if (count($parts) < 3) {
// something went wrong, skip
\OCP\Util::writeLog(
'files_external',
'Could not parse mount point "' . $rootMountPath . '"',
\OCP\Util::ERROR
);
continue;
}
$relativeMountPath = $parts[2];
$configId = (int)$storageOptions['id'];
if (isset($storages[$configId])) {
$currentStorage = $storages[$configId];
} else {
$currentStorage = new StorageConfig($configId);
$currentStorage->setMountPoint($relativeMountPath);
}
$currentStorage->setBackendClass($storageOptions['class']);
$currentStorage->setBackendOptions($storageOptions['options']);
if (isset($storageOptions['priority'])) {
$currentStorage->setPriority($storageOptions['priority']);
}
if ($mountType === \OC_Mount_Config::MOUNT_TYPE_USER) {
$applicableUsers = $currentStorage->getApplicableUsers();
if ($applicable !== 'all') {
$applicableUsers[] = $applicable;
$currentStorage->setApplicableUsers($applicableUsers);
}
} else if ($mountType === \OC_Mount_Config::MOUNT_TYPE_GROUP) {
$applicableGroups = $currentStorage->getApplicableGroups();
$applicableGroups[] = $applicable;
$currentStorage->setApplicableGroups($applicableGroups);
}
$storages[$configId] = $currentStorage;
}
}
}
// decrypt passwords
foreach ($storages as &$storage) {
$storage->setBackendOptions(
\OC_Mount_Config::decryptPasswords(
$storage->getBackendOptions()
)
);
}
return $storages;
}
/**
* Add mount point into the messy mount point structure
*
* @param array $mountPoints messy array of mount points
* @param string $mountType mount type
* @param string $applicable single applicable user or group
* @param string $rootMountPoint root mount point to use
* @param array $storageConfig storage config to set to the mount point
*/
protected function addMountPoint(&$mountPoints, $mountType, $applicable, $rootMountPoint, $storageConfig) {
if (!isset($mountPoints[$mountType])) {
$mountPoints[$mountType] = [];
}
if (!isset($mountPoints[$mountType][$applicable])) {
$mountPoints[$mountType][$applicable] = [];
}
$options = [
'id' => $storageConfig->getId(),
'class' => $storageConfig->getBackendClass(),
'options' => $storageConfig->getBackendOptions(),
];
if (!is_null($storageConfig->getPriority())) {
$options['priority'] = $storageConfig->getPriority();
}
$mountPoints[$mountType][$applicable][$rootMountPoint] = $options;
}
/**
* Write the storages to the configuration.
*
* @param array $storages map of storage id to storage config
*/
abstract protected function writeConfig($storages);
/**
* Get a storage with status
*
* @param int $id
*
* @return StorageConfig
*/
public function getStorage($id) {
$allStorages = $this->readConfig();
if (!isset($allStorages[$id])) {
throw new NotFoundException('Storage with id "' . $id . '" not found');
}
return $allStorages[$id];
}
/**
* Add new storage to the configuration
*
* @param array $newStorage storage attributes
*
* @return StorageConfig storage config, with added id
*/
public function addStorage(StorageConfig $newStorage) {
$allStorages = $this->readConfig();
$configId = $this->generateNextId($allStorages);
$newStorage->setId($configId);
// add new storage
$allStorages[$configId] = $newStorage;
$this->writeConfig($allStorages);
$this->triggerHooks($newStorage, Filesystem::signal_create_mount);
$newStorage->setStatus(\OC_Mount_Config::STATUS_SUCCESS);
return $newStorage;
}
/**
* Triggers the given hook signal for all the applicables given
*
* @param string $signal signal
* @param string $mountPoint hook mount pount param
* @param string $mountType hook mount type param
* @param array $applicableArray array of applicable users/groups for which to trigger the hook
*/
protected function triggerApplicableHooks($signal, $mountPoint, $mountType, $applicableArray) {
foreach ($applicableArray as $applicable) {
\OC_Hook::emit(
Filesystem::CLASSNAME,
$signal,
[
Filesystem::signal_param_path => $mountPoint,
Filesystem::signal_param_mount_type => $mountType,
Filesystem::signal_param_users => $applicable,
]
);
}
}
/**
* Triggers $signal for all applicable users of the given
* storage
*
* @param StorageConfig $storage storage data
* @param string $signal signal to trigger
*/
abstract protected function triggerHooks(StorageConfig $storage, $signal);
/**
* Triggers signal_create_mount or signal_delete_mount to
* accomodate for additions/deletions in applicableUsers
* and applicableGroups fields.
*
* @param StorageConfig $oldStorage old storage data
* @param StorageConfig $newStorage new storage data
*/
abstract protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $newStorage);
/**
* Update storage to the configuration
*
* @param StorageConfig $updatedStorage storage attributes
*
* @return StorageConfig storage config
* @throws NotFoundException
*/
public function updateStorage(StorageConfig $updatedStorage) {
$allStorages = $this->readConfig();
$id = $updatedStorage->getId();
if (!isset($allStorages[$id])) {
throw new NotFoundException('Storage with id "' . $id . '" not found');
}
$oldStorage = $allStorages[$id];
$allStorages[$id] = $updatedStorage;
$this->writeConfig($allStorages);
$this->triggerChangeHooks($oldStorage, $updatedStorage);
return $this->getStorage($id);
}
/**
* Delete the storage with the given id.
*
* @param int $id storage id
*
* @throws NotFoundException
*/
public function removeStorage($id) {
$allStorages = $this->readConfig();
if (!isset($allStorages[$id])) {
throw new NotFoundException('Storage with id "' . $id . '" not found');
}
$deletedStorage = $allStorages[$id];
unset($allStorages[$id]);
$this->writeConfig($allStorages);
$this->triggerHooks($deletedStorage, Filesystem::signal_delete_mount);
}
/**
* Generates a configuration id to use for a new configuration entry.
*
* @param array $allStorages array of all storage configs
*
* @return int id
*/
protected function generateNextId($allStorages) {
if (empty($allStorages)) {
return 1;
}
// note: this will mess up with with concurrency,
// but so did the mount.json. This horribly hack
// will disappear once we move to DB tables to
// store the config
return max(array_keys($allStorages)) + 1;
}
}

View File

@ -0,0 +1,142 @@
<?php
/**
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\Files_external\Service;
use \OCP\IUserSession;
use \OC\Files\Filesystem;
use \OCA\Files_external\Lib\StorageConfig;
use \OCA\Files_external\NotFoundException;
/**
* Service class to manage user external storages
* (aka personal storages)
*/
class UserStoragesService extends StoragesService {
/**
* @var IUserSession
*/
private $userSession;
public function __construct(
IUserSession $userSession
) {
$this->userSession = $userSession;
}
/**
* Read legacy config data
*
* @return array list of storage configs
*/
protected function readLegacyConfig() {
// read user config
$user = $this->userSession->getUser()->getUID();
return \OC_Mount_Config::readData($user);
}
/**
* Read the external storages config
*
* @return array map of storage id to storage config
*/
protected function readConfig() {
$user = $this->userSession->getUser()->getUID();
// TODO: in the future don't rely on the global config reading code
$storages = parent::readConfig();
$filteredStorages = [];
foreach ($storages as $configId => $storage) {
// filter out all bogus storages that aren't for the current user
if (!in_array($user, $storage->getApplicableUsers())) {
continue;
}
// clear applicable users, should not be used
$storage->setApplicableUsers([]);
// strip out unneeded applicableUser fields
$filteredStorages[$configId] = $storage;
}
return $filteredStorages;
}
/**
* Write the storages to the user's configuration.
*
* @param array $storages map of storage id to storage config
*/
public function writeConfig($storages) {
$user = $this->userSession->getUser()->getUID();
// let the horror begin
$mountPoints = [];
foreach ($storages as $storageConfig) {
$mountPoint = $storageConfig->getMountPoint();
$oldBackendOptions = $storageConfig->getBackendOptions();
$storageConfig->setBackendOptions(
\OC_Mount_Config::encryptPasswords(
$oldBackendOptions
)
);
$rootMountPoint = '/' . $user . '/files/' . ltrim($mountPoint, '/');
$this->addMountPoint(
$mountPoints,
\OC_Mount_Config::MOUNT_TYPE_USER,
$user,
$rootMountPoint,
$storageConfig
);
// restore old backend options where the password was not encrypted,
// because we don't want to change the state of the original object
$storageConfig->setBackendOptions($oldBackendOptions);
}
\OC_Mount_Config::writeData($user, $mountPoints);
}
/**
* Triggers $signal for all applicable users of the given
* storage
*
* @param StorageConfig $storage storage data
* @param string $signal signal to trigger
*/
protected function triggerHooks(StorageConfig $storage, $signal) {
$user = $this->userSession->getUser()->getUID();
// trigger hook for the current user
$this->triggerApplicableHooks(
$signal,
$storage->getMountPoint(),
\OC_Mount_Config::MOUNT_TYPE_USER,
[$user]
);
}
/**
* Triggers signal_create_mount or signal_delete_mount to
* accomodate for additions/deletions in applicableUsers
* and applicableGroups fields.
*
* @param StorageConfig $oldStorage old storage data
* @param StorageConfig $newStorage new storage data
*/
protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $newStorage) {
// if mount point changed, it's like a deletion + creation
if ($oldStorage->getMountPoint() !== $newStorage->getMountPoint()) {
$this->triggerHooks($oldStorage, Filesystem::signal_delete_mount);
$this->triggerHooks($newStorage, Filesystem::signal_create_mount);
}
}
}

View File

@ -13,12 +13,12 @@
</tr>
</thead>
<tbody>
<?php $_['mounts'] = array_merge($_['mounts'], array('' => array())); ?>
<?php $_['mounts'] = array_merge($_['mounts'], array('' => array('id' => ''))); ?>
<?php foreach ($_['mounts'] as $mount): ?>
<tr <?php print_unescaped(isset($mount['mountpoint']) ? 'class="'.OC_Util::sanitizeHTML($mount['class']).'"' : 'id="addMountPoint"'); ?>>
<tr <?php print_unescaped(isset($mount['mountpoint']) ? 'class="'.OC_Util::sanitizeHTML($mount['class']).'"' : 'id="addMountPoint"'); ?> data-id="<?php p($mount['id']) ?>">
<td class="status">
<?php if (isset($mount['status'])): ?>
<span class="<?php p(($mount['status']) ? 'success' : 'error'); ?>"></span>
<span class="<?php p(($mount['status'] === \OC_Mount_Config::STATUS_SUCCESS) ? 'success' : 'error'); ?>"></span>
<?php endif; ?>
</td>
<td class="mountPoint"><input type="text" name="mountPoint"
@ -28,7 +28,7 @@
</td>
<?php if (!isset($mount['mountpoint'])): ?>
<td class="backend">
<select id="selectBackend" data-configurations='<?php p(json_encode($_['backends'])); ?>'>
<select id="selectBackend" class="selectBackend" data-configurations='<?php p(json_encode($_['backends'])); ?>'>
<option value="" disabled selected
style="display:none;"><?php p($l->t('Add storage')); ?></option>
<?php foreach ($_['backends'] as $class => $backend): ?>

View File

@ -0,0 +1,41 @@
<?php
/**
* ownCloud
*
* @author Vincent Petry
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_external\Tests\Controller;
use \OCA\Files_external\Controller\GlobalStoragesController;
use \OCA\Files_external\Service\GlobalStoragesService;
use \OCP\AppFramework\Http;
use \OCA\Files_external\NotFoundException;
class GlobalStoragesControllerTest extends StoragesControllerTest {
public function setUp() {
parent::setUp();
$this->service = $this->getMock('\OCA\Files_external\Service\GlobalStoragesService');
$this->controller = new GlobalStoragesController(
'files_external',
$this->getMock('\OCP\IRequest'),
$this->getMock('\OCP\IL10N'),
$this->service
);
}
}

View File

@ -0,0 +1,217 @@
<?php
/**
* ownCloud
*
* @author Vincent Petry
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_external\Tests\Controller;
use \OCP\AppFramework\Http;
use \OCA\Files_external\Controller\GlobalStoragesController;
use \OCA\Files_external\Service\GlobalStoragesService;
use \OCA\Files_external\Lib\StorageConfig;
use \OCA\Files_external\NotFoundException;
abstract class StoragesControllerTest extends \Test\TestCase {
/**
* @var GlobalStoragesController
*/
protected $controller;
/**
* @var GlobalStoragesService
*/
protected $service;
public function setUp() {
\OC_Mount_Config::$skipTest = true;
}
public function tearDown() {
\OC_Mount_Config::$skipTest = false;
}
public function testAddStorage() {
$storageConfig = new StorageConfig(1);
$storageConfig->setMountPoint('mount');
$this->service->expects($this->once())
->method('addStorage')
->will($this->returnValue($storageConfig));
$response = $this->controller->create(
'mount',
'\OC\Files\Storage\SMB',
array(),
[],
[],
null
);
$data = $response->getData();
$this->assertEquals($storageConfig, $data);
$this->assertEquals(Http::STATUS_CREATED, $response->getStatus());
}
public function testUpdateStorage() {
$storageConfig = new StorageConfig(1);
$storageConfig->setMountPoint('mount');
$this->service->expects($this->once())
->method('updateStorage')
->will($this->returnValue($storageConfig));
$response = $this->controller->update(
1,
'mount',
'\OC\Files\Storage\SMB',
array(),
[],
[],
null
);
$data = $response->getData();
$this->assertEquals($storageConfig, $data);
$this->assertEquals(Http::STATUS_OK, $response->getStatus());
}
function mountPointNamesProvider() {
return array(
array(''),
array('/'),
array('//'),
);
}
/**
* @dataProvider mountPointNamesProvider
*/
public function testAddOrUpdateStorageInvalidMountPoint($mountPoint) {
$this->service->expects($this->never())
->method('addStorage');
$this->service->expects($this->never())
->method('updateStorage');
$response = $this->controller->create(
$mountPoint,
'\OC\Files\Storage\SMB',
array(),
[],
[],
null
);
$this->assertEquals(Http::STATUS_UNPROCESSABLE_ENTITY, $response->getStatus());
$response = $this->controller->update(
1,
$mountPoint,
'\OC\Files\Storage\SMB',
array(),
[],
[],
null
);
$this->assertEquals(Http::STATUS_UNPROCESSABLE_ENTITY, $response->getStatus());
}
public function testAddOrUpdateStorageInvalidBackend() {
$this->service->expects($this->never())
->method('addStorage');
$this->service->expects($this->never())
->method('updateStorage');
$response = $this->controller->create(
'mount',
'\OC\Files\Storage\InvalidStorage',
array(),
[],
[],
null
);
$this->assertEquals(Http::STATUS_UNPROCESSABLE_ENTITY, $response->getStatus());
$response = $this->controller->update(
1,
'mount',
'\OC\Files\Storage\InvalidStorage',
array(),
[],
[],
null
);
$this->assertEquals(Http::STATUS_UNPROCESSABLE_ENTITY, $response->getStatus());
}
public function testUpdateStorageNonExisting() {
$this->service->expects($this->once())
->method('updateStorage')
->will($this->throwException(new NotFoundException()));
$response = $this->controller->update(
255,
'mount',
'\OC\Files\Storage\SMB',
array(),
[],
[],
null
);
$this->assertEquals(Http::STATUS_NOT_FOUND, $response->getStatus());
}
public function testDeleteStorage() {
$this->service->expects($this->once())
->method('removeStorage');
$response = $this->controller->destroy(1);
$this->assertEquals(Http::STATUS_NO_CONTENT, $response->getStatus());
}
public function testDeleteStorageNonExisting() {
$this->service->expects($this->once())
->method('removeStorage')
->will($this->throwException(new NotFoundException()));
$response = $this->controller->destroy(255);
$this->assertEquals(Http::STATUS_NOT_FOUND, $response->getStatus());
}
public function testGetStorage() {
$storageConfig = new StorageConfig(1);
$storageConfig->setMountPoint('test');
$storageConfig->setBackendClass('\OC\Files\Storage\SMB');
$storageConfig->setBackendOptions(['user' => 'test', 'password', 'password123']);
$this->service->expects($this->once())
->method('getStorage')
->with(1)
->will($this->returnValue($storageConfig));
$response = $this->controller->show(1);
$this->assertEquals(Http::STATUS_OK, $response->getStatus());
$this->assertEquals($storageConfig, $response->getData());
}
}

View File

@ -0,0 +1,112 @@
<?php
/**
* ownCloud
*
* @author Vincent Petry
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_external\Tests\Controller;
use \OCA\Files_external\Controller\UserStoragesController;
use \OCA\Files_external\Service\UserStoragesService;
use \OCP\AppFramework\Http;
use \OCA\Files_external\NotFoundException;
class UserStoragesControllerTest extends StoragesControllerTest {
/**
* @var array
*/
private $oldAllowedBackends;
public function setUp() {
parent::setUp();
$this->service = $this->getMockBuilder('\OCA\Files_external\Service\UserStoragesService')
->disableOriginalConstructor()
->getMock();
$this->controller = new UserStoragesController(
'files_external',
$this->getMock('\OCP\IRequest'),
$this->getMock('\OCP\IL10N'),
$this->service
);
$config = \OC::$server->getConfig();
$this->oldAllowedBackends = $config->getAppValue(
'files_external',
'user_mounting_backends',
''
);
$config->setAppValue(
'files_external',
'user_mounting_backends',
'\OC\Files\Storage\SMB'
);
}
public function tearDown() {
$config = \OC::$server->getConfig();
$config->setAppValue(
'files_external',
'user_mounting_backends',
$this->oldAllowedBackends
);
parent::tearDown();
}
function disallowedBackendClassProvider() {
return array(
array('\OC\Files\Storage\Local'),
array('\OC\Files\Storage\FTP'),
);
}
/**
* @dataProvider disallowedBackendClassProvider
*/
public function testAddOrUpdateStorageDisallowedBackend($backendClass) {
$this->service->expects($this->never())
->method('addStorage');
$this->service->expects($this->never())
->method('updateStorage');
$response = $this->controller->create(
'mount',
$backendClass,
array(),
[],
[],
null
);
$this->assertEquals(Http::STATUS_UNPROCESSABLE_ENTITY, $response->getStatus());
$response = $this->controller->update(
1,
'mount',
$backendClass,
array(),
[],
[],
null
);
$this->assertEquals(Http::STATUS_UNPROCESSABLE_ENTITY, $response->getStatus());
}
}

View File

@ -0,0 +1,164 @@
/*
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
describe('OCA.External.Settings tests', function() {
var clock;
var select2Stub;
var select2ApplicableUsers;
beforeEach(function() {
clock = sinon.useFakeTimers();
select2Stub = sinon.stub($.fn, 'select2', function(args) {
if (args === 'val') {
return select2ApplicableUsers;
}
return {
on: function() {}
};
});
// view still requires an existing DOM table
$('#testArea').append(
'<table id="externalStorage" data-admin="true">' +
'<thead></thead>' +
'<tbody>' +
'<tr id="addMountPoint" data-id="">' +
'<td class="status"></td>' +
'<td class="mountPoint"><input type="text" name="mountPoint"/></td>' +
'<td class="backend">' +
'<select class="selectBackend">' +
'<option disable selected>Add storage</option>' +
'<option value="\\OC\\TestBackend">Test Backend</option>' +
'<option value="\\OC\\AnotherTestBackend">Another Test Backend</option>' +
'</select>' +
'</td>' +
'<td class="configuration"></td>' +
'<td class="applicable">' +
'<input type="hidden" class="applicableUsers">' +
'</td>' +
'<td><img alt="Delete" title="Delete" class="svg action"/></td>' +
'</tr>' +
'</tbody>' +
'</table>'
);
// these are usually appended into the data attribute
// within the DOM by the server template
$('#externalStorage .selectBackend:first').data('configurations', {
'\\OC\\TestBackend': {
'backend': 'Test Backend Name',
'configuration': {
'field1': 'Display Name 1',
'field2': '&Display Name 2'
}
},
'\\OC\\AnotherTestBackend': {
'backend': 'Another Test Backend Name',
'configuration': {
'field1': 'Display Name 1',
'field2': '&Display Name 2'
}
}
}
);
});
afterEach(function() {
select2Stub.restore();
clock.restore();
});
describe('storage configuration', function() {
var view;
function selectBackend(backendName) {
view.$el.find('.selectBackend:first').val('\\OC\\TestBackend').trigger('change');
}
beforeEach(function() {
var $el = $('#externalStorage');
view = new OCA.External.Settings.MountConfigListView($el);
});
afterEach(function() {
view = null;
});
describe('selecting backend', function() {
it('populates the row and creates a new empty one', function() {
var $firstRow = view.$el.find('tr:first');
selectBackend('\\OC\\TestBackend');
expect($firstRow.find('.backend').text()).toEqual('Test Backend');
expect($firstRow.find('.selectBackend').length).toEqual(0);
// TODO: check "remove" button visibility
// the suggested mount point name
expect($firstRow.find('[name=mountPoint]').val()).toEqual('TestBackend');
// TODO: check that the options have been created
// TODO: check select2 call on the ".applicableUsers" element
var $emptyRow = $firstRow.next('tr');
expect($emptyRow.length).toEqual(1);
expect($emptyRow.find('.selectBackend').length).toEqual(1);
expect($emptyRow.find('.applicable select').length).toEqual(0);
// TODO: check "remove" button visibility
});
// TODO: test with personal mounts (no applicable fields)
// TODO: test suggested mount point logic
});
describe('saving storages', function() {
it('saves storage after editing config', function() {
var $tr = view.$el.find('tr:first');
selectBackend('\\OC\\TestBackend');
var $field1 = $tr.find('input[data-parameter=field1]');
expect($field1.length).toEqual(1);
$field1.val('test');
$field1.trigger(new $.Event('keyup', {keyCode: 97}));
clock.tick(4000);
expect(fakeServer.requests.length).toEqual(1);
var request = fakeServer.requests[0];
expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_external/globalstorages');
expect(OC.parseQueryString(request.requestBody)).toEqual({
backendClass: '\\OC\\TestBackend',
'backendOptions[field1]': 'test',
'backendOptions[field2]': '',
mountPoint: 'TestBackend'
});
// TODO: respond and check data-id
});
// TODO: tests with "applicableUsers" and "applicableGroups"
// TODO: test with non-optional config parameters
// TODO: test with missing mount point value
// TODO: test with personal mounts (no applicable fields)
// TODO: test save triggers: paste, keyup, checkbox
// TODO: test "custom" field with addScript
// TODO: status indicator
});
describe('update storage', function() {
// TODO
});
describe('delete storage', function() {
// TODO
});
describe('recheck storages', function() {
// TODO
});
});
describe('applicable user list', function() {
// TODO: test select2 retrieval logic
});
describe('allow user mounts section', function() {
// TODO: test allowUserMounting section
});
});

View File

@ -0,0 +1,705 @@
<?php
/**
* ownCloud
*
* @author Vincent Petry
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_external\Tests\Service;
use \OC\Files\Filesystem;
use \OCA\Files_external\Service\GlobalStoragesService;
use \OCA\Files_external\NotFoundException;
use \OCA\Files_external\Lib\StorageConfig;
class GlobalStoragesServiceTest extends StoragesServiceTest {
public function setUp() {
parent::setUp();
$this->service = new GlobalStoragesService();
}
public function tearDown() {
@unlink($this->dataDir . '/mount.json');
parent::tearDown();
}
protected function makeTestStorageData() {
return $this->makeStorageConfig([
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
'password' => 'testPassword',
],
'applicableUsers' => [],
'applicableGroups' => [],
'priority' => 15,
]);
}
function storageDataProvider() {
return [
// all users
[
$this->makeStorageConfig([
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
'password' => 'testPassword',
],
'applicableUsers' => [],
'applicableGroups' => [],
'priority' => 15,
]),
],
// some users
[
$this->makeStorageConfig([
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
'password' => 'testPassword',
],
'applicableUsers' => ['user1', 'user2'],
'applicableGroups' => [],
'priority' => 15,
]),
],
// some groups
[
$this->makeStorageConfig([
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
'password' => 'testPassword',
],
'applicableUsers' => [],
'applicableGroups' => ['group1', 'group2'],
'priority' => 15,
]),
],
// both users and groups
[
$this->makeStorageConfig([
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
'password' => 'testPassword',
],
'applicableUsers' => ['user1', 'user2'],
'applicableGroups' => ['group1', 'group2'],
'priority' => 15,
]),
],
];
}
/**
* @dataProvider storageDataProvider
*/
public function testAddStorage($storage) {
$newStorage = $this->service->addStorage($storage);
$this->assertEquals(1, $newStorage->getId());
$newStorage = $this->service->getStorage(1);
$this->assertEquals($storage->getMountPoint(), $newStorage->getMountPoint());
$this->assertEquals($storage->getBackendClass(), $newStorage->getBackendClass());
$this->assertEquals($storage->getBackendOptions(), $newStorage->getBackendOptions());
$this->assertEquals($storage->getApplicableUsers(), $newStorage->getApplicableUsers());
$this->assertEquals($storage->getApplicableGroups(), $newStorage->getApplicableGroups());
$this->assertEquals($storage->getPriority(), $newStorage->getPriority());
$this->assertEquals(1, $newStorage->getId());
$this->assertEquals(0, $newStorage->getStatus());
// next one gets id 2
$nextStorage = $this->service->addStorage($storage);
$this->assertEquals(2, $nextStorage->getId());
}
/**
* @dataProvider storageDataProvider
*/
public function testUpdateStorage($updatedStorage) {
$storage = $this->makeStorageConfig([
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
'password' => 'testPassword',
],
'applicableUsers' => [],
'applicableGroups' => [],
'priority' => 15,
]);
$newStorage = $this->service->addStorage($storage);
$this->assertEquals(1, $newStorage->getId());
$updatedStorage->setId(1);
$this->service->updateStorage($updatedStorage);
$newStorage = $this->service->getStorage(1);
$this->assertEquals($updatedStorage->getMountPoint(), $newStorage->getMountPoint());
$this->assertEquals($updatedStorage->getBackendOptions()['password'], $newStorage->getBackendOptions()['password']);
$this->assertEquals($updatedStorage->getApplicableUsers(), $newStorage->getApplicableUsers());
$this->assertEquals($updatedStorage->getApplicableGroups(), $newStorage->getApplicableGroups());
$this->assertEquals($updatedStorage->getPriority(), $newStorage->getPriority());
$this->assertEquals(1, $newStorage->getId());
$this->assertEquals(0, $newStorage->getStatus());
}
function hooksAddStorageDataProvider() {
return [
// applicable all
[
[],
[],
// expected hook calls
[
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'all'
],
],
],
// single user
[
['user1'],
[],
// expected hook calls
[
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'user1',
],
],
],
// single group
[
[],
['group1'],
// expected hook calls
[
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group1',
],
],
],
// multiple users
[
['user1', 'user2'],
[],
[
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'user1',
],
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'user2',
],
],
],
// multiple groups
[
[],
['group1', 'group2'],
// expected hook calls
[
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group1'
],
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group2'
],
],
],
// mixed groups and users
[
['user1', 'user2'],
['group1', 'group2'],
// expected hook calls
[
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'user1',
],
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'user2',
],
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group1'
],
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group2'
],
],
],
];
}
/**
* @dataProvider hooksAddStorageDataProvider
*/
public function testHooksAddStorage($applicableUsers, $applicableGroups, $expectedCalls) {
$storage = $this->makeTestStorageData();
$storage->setApplicableUsers($applicableUsers);
$storage->setApplicableGroups($applicableGroups);
$this->service->addStorage($storage);
$this->assertCount(count($expectedCalls), self::$hookCalls);
foreach ($expectedCalls as $index => $call) {
$this->assertHookCall(
self::$hookCalls[$index],
$call[0],
$storage->getMountPoint(),
$call[1],
$call[2]
);
}
}
function hooksUpdateStorageDataProvider() {
return [
[
// nothing to multiple users and groups
[],
[],
['user1', 'user2'],
['group1', 'group2'],
// expected hook calls
[
// delete the "all entry"
[
Filesystem::signal_delete_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'all',
],
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'user1',
],
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'user2',
],
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group1'
],
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group2'
],
],
],
[
// adding a user and a group
['user1'],
['group1'],
['user1', 'user2'],
['group1', 'group2'],
// expected hook calls
[
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'user2',
],
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group2'
],
],
],
[
// removing a user and a group
['user1', 'user2'],
['group1', 'group2'],
['user1'],
['group1'],
// expected hook calls
[
[
Filesystem::signal_delete_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'user2',
],
[
Filesystem::signal_delete_mount,
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group2'
],
],
],
[
// removing all
['user1'],
['group1'],
[],
[],
// expected hook calls
[
[
Filesystem::signal_delete_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'user1',
],
[
Filesystem::signal_delete_mount,
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group1'
],
// create the "all" entry
[
Filesystem::signal_create_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'all'
],
],
],
[
// no changes
['user1'],
['group1'],
['user1'],
['group1'],
// no hook calls
[]
]
];
}
/**
* @dataProvider hooksUpdateStorageDataProvider
*/
public function testHooksUpdateStorage(
$sourceApplicableUsers,
$sourceApplicableGroups,
$updatedApplicableUsers,
$updatedApplicableGroups,
$expectedCalls) {
$storage = $this->makeTestStorageData();
$storage->setApplicableUsers($sourceApplicableUsers);
$storage->setApplicableGroups($sourceApplicableGroups);
$storage = $this->service->addStorage($storage);
$storage->setapplicableUsers($updatedApplicableUsers);
$storage->setapplicableGroups($updatedApplicableGroups);
// reset calls
self::$hookCalls = [];
$this->service->updateStorage($storage);
$this->assertCount(count($expectedCalls), self::$hookCalls);
foreach ($expectedCalls as $index => $call) {
$this->assertHookCall(
self::$hookCalls[$index],
$call[0],
'/mountpoint',
$call[1],
$call[2]
);
}
}
/**
*/
public function testHooksRenameMountPoint() {
$storage = $this->makeTestStorageData();
$storage->setApplicableUsers(['user1', 'user2']);
$storage->setApplicableGroups(['group1', 'group2']);
$storage = $this->service->addStorage($storage);
$storage->setMountPoint('renamedMountpoint');
// reset calls
self::$hookCalls = [];
$this->service->updateStorage($storage);
$expectedCalls = [
// deletes old mount
[
Filesystem::signal_delete_mount,
'/mountpoint',
\OC_Mount_Config::MOUNT_TYPE_USER,
'user1',
],
[
Filesystem::signal_delete_mount,
'/mountpoint',
\OC_Mount_Config::MOUNT_TYPE_USER,
'user2',
],
[
Filesystem::signal_delete_mount,
'/mountpoint',
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group1',
],
[
Filesystem::signal_delete_mount,
'/mountpoint',
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group2',
],
// creates new one
[
Filesystem::signal_create_mount,
'/renamedMountpoint',
\OC_Mount_Config::MOUNT_TYPE_USER,
'user1',
],
[
Filesystem::signal_create_mount,
'/renamedMountpoint',
\OC_Mount_Config::MOUNT_TYPE_USER,
'user2',
],
[
Filesystem::signal_create_mount,
'/renamedMountpoint',
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group1',
],
[
Filesystem::signal_create_mount,
'/renamedMountpoint',
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group2',
],
];
$this->assertCount(count($expectedCalls), self::$hookCalls);
foreach ($expectedCalls as $index => $call) {
$this->assertHookCall(
self::$hookCalls[$index],
$call[0],
$call[1],
$call[2],
$call[3]
);
}
}
function hooksDeleteStorageDataProvider() {
return [
[
['user1', 'user2'],
['group1', 'group2'],
// expected hook calls
[
[
Filesystem::signal_delete_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'user1',
],
[
Filesystem::signal_delete_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'user2',
],
[
Filesystem::signal_delete_mount,
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group1'
],
[
Filesystem::signal_delete_mount,
\OC_Mount_Config::MOUNT_TYPE_GROUP,
'group2'
],
],
],
[
// deleting "all" entry
[],
[],
[
[
Filesystem::signal_delete_mount,
\OC_Mount_Config::MOUNT_TYPE_USER,
'all',
],
],
],
];
}
/**
* @dataProvider hooksDeleteStorageDataProvider
*/
public function testHooksDeleteStorage(
$sourceApplicableUsers,
$sourceApplicableGroups,
$expectedCalls) {
$storage = $this->makeTestStorageData();
$storage->setApplicableUsers($sourceApplicableUsers);
$storage->setApplicableGroups($sourceApplicableGroups);
$storage = $this->service->addStorage($storage);
// reset calls
self::$hookCalls = [];
$this->service->removeStorage($storage->getId());
$this->assertCount(count($expectedCalls), self::$hookCalls);
foreach ($expectedCalls as $index => $call) {
$this->assertHookCall(
self::$hookCalls[$index],
$call[0],
'/mountpoint',
$call[1],
$call[2]
);
}
}
/**
* Make sure it uses the correct format when reading/writing
* the legacy config
*/
public function testLegacyConfigConversionApplicableAll() {
$configFile = $this->dataDir . '/mount.json';
$storage = $this->makeTestStorageData();
$storage = $this->service->addStorage($storage);
$json = json_decode(file_get_contents($configFile), true);
$this->assertCount(1, $json);
$this->assertEquals([\OC_Mount_Config::MOUNT_TYPE_USER], array_keys($json));
$this->assertEquals(['all'], array_keys($json[\OC_Mount_config::MOUNT_TYPE_USER]));
$mountPointData = $json[\OC_Mount_config::MOUNT_TYPE_USER]['all'];
$this->assertEquals(['/$user/files/mountpoint'], array_keys($mountPointData));
$mountPointOptions = current($mountPointData);
$this->assertEquals(1, $mountPointOptions['id']);
$this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']);
$this->assertEquals(15, $mountPointOptions['priority']);
$backendOptions = $mountPointOptions['options'];
$this->assertEquals('value1', $backendOptions['option1']);
$this->assertEquals('value2', $backendOptions['option2']);
$this->assertEquals('', $backendOptions['password']);
$this->assertNotEmpty($backendOptions['password_encrypted']);
}
/**
* Make sure it uses the correct format when reading/writing
* the legacy config
*/
public function testLegacyConfigConversionApplicableUserAndGroup() {
$configFile = $this->dataDir . '/mount.json';
$storage = $this->makeTestStorageData();
$storage->setApplicableUsers(['user1', 'user2']);
$storage->setApplicableGroups(['group1', 'group2']);
$storage = $this->service->addStorage($storage);
$json = json_decode(file_get_contents($configFile), true);
$this->assertCount(2, $json);
$this->assertTrue(isset($json[\OC_Mount_Config::MOUNT_TYPE_USER]));
$this->assertTrue(isset($json[\OC_Mount_Config::MOUNT_TYPE_GROUP]));
$this->assertEquals(['user1', 'user2'], array_keys($json[\OC_Mount_config::MOUNT_TYPE_USER]));
$this->assertEquals(['group1', 'group2'], array_keys($json[\OC_Mount_config::MOUNT_TYPE_GROUP]));
// check that all options are the same for both users and both groups
foreach ($json[\OC_Mount_Config::MOUNT_TYPE_USER] as $mountPointData) {
$this->assertEquals(['/$user/files/mountpoint'], array_keys($mountPointData));
$mountPointOptions = current($mountPointData);
$this->assertEquals(1, $mountPointOptions['id']);
$this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']);
$this->assertEquals(15, $mountPointOptions['priority']);
$backendOptions = $mountPointOptions['options'];
$this->assertEquals('value1', $backendOptions['option1']);
$this->assertEquals('value2', $backendOptions['option2']);
$this->assertEquals('', $backendOptions['password']);
$this->assertNotEmpty($backendOptions['password_encrypted']);
}
foreach ($json[\OC_Mount_Config::MOUNT_TYPE_GROUP] as $mountPointData) {
$this->assertEquals(['/$user/files/mountpoint'], array_keys($mountPointData));
$mountPointOptions = current($mountPointData);
$this->assertEquals(1, $mountPointOptions['id']);
$this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']);
$this->assertEquals(15, $mountPointOptions['priority']);
$backendOptions = $mountPointOptions['options'];
$this->assertEquals('value1', $backendOptions['option1']);
$this->assertEquals('value2', $backendOptions['option2']);
$this->assertEquals('', $backendOptions['password']);
$this->assertNotEmpty($backendOptions['password_encrypted']);
}
}
}

View File

@ -0,0 +1,180 @@
<?php
/**
* ownCloud
*
* @author Vincent Petry
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_external\Tests\Service;
use \OC\Files\Filesystem;
use \OCA\Files_external\NotFoundException;
use \OCA\Files_external\Lib\StorageConfig;
abstract class StoragesServiceTest extends \Test\TestCase {
/**
* @var StoragesService
*/
protected $service;
/**
* Data directory
*
* @var string
*/
protected $dataDir;
/**
* Hook calls
*
* @var array
*/
protected static $hookCalls;
public function setUp() {
self::$hookCalls = array();
$config = \OC::$server->getConfig();
$this->dataDir = $config->getSystemValue(
'datadirectory',
\OC::$SERVERROOT . '/data/'
);
\OC_Mount_Config::$skipTest = true;
\OCP\Util::connectHook(
Filesystem::CLASSNAME,
Filesystem::signal_create_mount,
get_class($this), 'createHookCallback');
\OCP\Util::connectHook(
Filesystem::CLASSNAME,
Filesystem::signal_delete_mount,
get_class($this), 'deleteHookCallback');
}
public function tearDown() {
\OC_Mount_Config::$skipTest = false;
self::$hookCalls = array();
}
/**
* Creates a StorageConfig instance based on array data
*
* @param array data
*
* @return StorageConfig storage config instance
*/
protected function makeStorageConfig($data) {
$storage = new StorageConfig();
if (isset($data['id'])) {
$storage->setId($data['id']);
}
$storage->setMountPoint($data['mountPoint']);
$storage->setBackendClass($data['backendClass']);
$storage->setBackendOptions($data['backendOptions']);
if (isset($data['applicableUsers'])) {
$storage->setApplicableUsers($data['applicableUsers']);
}
if (isset($data['applicableGroups'])) {
$storage->setApplicableGroups($data['applicableGroups']);
}
if (isset($data['priority'])) {
$storage->setPriority($data['priority']);
}
return $storage;
}
/**
* @expectedException \OCA\Files_external\NotFoundException
*/
public function testNonExistingStorage() {
$storage = new StorageConfig(255);
$storage->setMountPoint('mountpoint');
$storage->setBackendClass('\OC\Files\Storage\SMB');
$this->service->updateStorage($storage);
}
public function testDeleteStorage() {
$storage = new StorageConfig(255);
$storage->setMountPoint('mountpoint');
$storage->setBackendClass('\OC\Files\Storage\SMB');
$storage->setBackendOptions(['password' => 'testPassword']);
$newStorage = $this->service->addStorage($storage);
$this->assertEquals(1, $newStorage->getId());
$newStorage = $this->service->removeStorage(1);
$caught = false;
try {
$this->service->getStorage(1);
} catch (NotFoundException $e) {
$caught = true;
}
$this->assertTrue($caught);
}
/**
* @expectedException \OCA\Files_external\NotFoundException
*/
public function testDeleteUnexistingStorage() {
$this->service->removeStorage(255);
}
public static function createHookCallback($params) {
self::$hookCalls[] = array(
'signal' => Filesystem::signal_create_mount,
'params' => $params
);
}
public static function deleteHookCallback($params) {
self::$hookCalls[] = array(
'signal' => Filesystem::signal_delete_mount,
'params' => $params
);
}
/**
* Asserts hook call
*
* @param array $callData hook call data to check
* @param string $signal signal name
* @param string $mountPath mount path
* @param string $mountType mount type
* @param string $applicable applicable users
*/
protected function assertHookCall($callData, $signal, $mountPath, $mountType, $applicable) {
$this->assertEquals($signal, $callData['signal']);
$params = $callData['params'];
$this->assertEquals(
$mountPath,
$params[Filesystem::signal_param_path]
);
$this->assertEquals(
$mountType,
$params[Filesystem::signal_param_mount_type]
);
$this->assertEquals(
$applicable,
$params[Filesystem::signal_param_users]
);
}
}

View File

@ -0,0 +1,200 @@
<?php
/**
* ownCloud
*
* @author Vincent Petry
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_external\Tests\Service;
use \OC\Files\Filesystem;
use \OCA\Files_external\Service\UserStoragesService;
use \OCA\Files_external\NotFoundException;
use \OCA\Files_external\Lib\StorageConfig;
class UserStoragesServiceTest extends StoragesServiceTest {
public function setUp() {
parent::setUp();
$this->userId = $this->getUniqueID('user_');
$this->user = new \OC\User\User($this->userId, null);
$userSession = $this->getMock('\OCP\IUserSession');
$userSession
->expects($this->any())
->method('getUser')
->will($this->returnValue($this->user));
$this->service = new UserStoragesService($userSession);
// create home folder
mkdir($this->dataDir . '/' . $this->userId . '/');
}
public function tearDown() {
@unlink($this->dataDir . '/' . $this->userId . '/mount.json');
parent::tearDown();
}
private function makeTestStorageData() {
return $this->makeStorageConfig([
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
'password' => 'testPassword',
],
]);
}
public function testAddStorage() {
$storage = $this->makeTestStorageData();
$newStorage = $this->service->addStorage($storage);
$this->assertEquals(1, $newStorage->getId());
$newStorage = $this->service->getStorage(1);
$this->assertEquals($storage->getMountPoint(), $newStorage->getMountPoint());
$this->assertEquals($storage->getBackendClass(), $newStorage->getBackendClass());
$this->assertEquals($storage->getBackendOptions(), $newStorage->getBackendOptions());
$this->assertEquals(1, $newStorage->getId());
$this->assertEquals(0, $newStorage->getStatus());
// hook called once for user
$this->assertHookCall(
current(self::$hookCalls),
Filesystem::signal_create_mount,
$storage->getMountPoint(),
\OC_Mount_Config::MOUNT_TYPE_USER,
$this->userId
);
// next one gets id 2
$nextStorage = $this->service->addStorage($storage);
$this->assertEquals(2, $nextStorage->getId());
}
public function testUpdateStorage() {
$storage = $this->makeStorageConfig([
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
'password' => 'testPassword',
],
]);
$newStorage = $this->service->addStorage($storage);
$this->assertEquals(1, $newStorage->getId());
$backendOptions = $newStorage->getBackendOptions();
$backendOptions['password'] = 'anotherPassword';
$newStorage->setBackendOptions($backendOptions);
self::$hookCalls = [];
$newStorage = $this->service->updateStorage($newStorage);
$this->assertEquals('anotherPassword', $newStorage->getBackendOptions()['password']);
// these attributes are unused for user storages
$this->assertEmpty($newStorage->getApplicableUsers());
$this->assertEmpty($newStorage->getApplicableGroups());
$this->assertEquals(1, $newStorage->getId());
$this->assertEquals(0, $newStorage->getStatus());
// no hook calls
$this->assertEmpty(self::$hookCalls);
}
public function testDeleteStorage() {
parent::testDeleteStorage();
// hook called once for user (first one was during test creation)
$this->assertHookCall(
self::$hookCalls[1],
Filesystem::signal_delete_mount,
'/mountpoint',
\OC_Mount_Config::MOUNT_TYPE_USER,
$this->userId
);
}
public function testHooksRenameMountPoint() {
$storage = $this->makeTestStorageData();
$storage = $this->service->addStorage($storage);
$storage->setMountPoint('renamedMountpoint');
// reset calls
self::$hookCalls = [];
$this->service->updateStorage($storage);
// hook called twice
$this->assertHookCall(
self::$hookCalls[0],
Filesystem::signal_delete_mount,
'/mountpoint',
\OC_Mount_Config::MOUNT_TYPE_USER,
$this->userId
);
$this->assertHookCall(
self::$hookCalls[1],
Filesystem::signal_create_mount,
'/renamedMountpoint',
\OC_Mount_Config::MOUNT_TYPE_USER,
$this->userId
);
}
/**
* Make sure it uses the correct format when reading/writing
* the legacy config
*/
public function testLegacyConfigConversion() {
$configFile = $this->dataDir . '/' . $this->userId . '/mount.json';
$storage = $this->makeTestStorageData();
$storage = $this->service->addStorage($storage);
$json = json_decode(file_get_contents($configFile), true);
$this->assertCount(1, $json);
$this->assertEquals([\OC_Mount_Config::MOUNT_TYPE_USER], array_keys($json));
$this->assertEquals([$this->userId], array_keys($json[\OC_Mount_config::MOUNT_TYPE_USER]));
$mountPointData = $json[\OC_Mount_config::MOUNT_TYPE_USER][$this->userId];
$this->assertEquals(['/' . $this->userId . '/files/mountpoint'], array_keys($mountPointData));
$mountPointOptions = current($mountPointData);
$this->assertEquals(1, $mountPointOptions['id']);
$this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']);
$backendOptions = $mountPointOptions['options'];
$this->assertEquals('value1', $backendOptions['option1']);
$this->assertEquals('value2', $backendOptions['option2']);
$this->assertEquals('', $backendOptions['password']);
$this->assertNotEmpty($backendOptions['password_encrypted']);
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* ownCloud
*
* @author Vincent Petry
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_external\Tests;
use \OCA\Files_external\Lib\StorageConfig;
class StorageConfigTest extends \Test\TestCase {
public function testJsonSerialization() {
$storageConfig = new StorageConfig(1);
$storageConfig->setMountPoint('test');
$storageConfig->setBackendClass('\OC\Files\Storage\SMB');
$storageConfig->setBackendOptions(['user' => 'test', 'password' => 'password123']);
$storageConfig->setPriority(128);
$storageConfig->setApplicableUsers(['user1', 'user2']);
$storageConfig->setApplicableGroups(['group1', 'group2']);
$json = $storageConfig->jsonSerialize();
$this->assertEquals(1, $json['id']);
$this->assertEquals('/test', $json['mountPoint']);
$this->assertEquals('\OC\Files\Storage\SMB', $json['backendClass']);
$this->assertEquals('test', $json['backendOptions']['user']);
$this->assertEquals('password123', $json['backendOptions']['password']);
$this->assertEquals(128, $json['priority']);
$this->assertEquals(['user1', 'user2'], $json['applicableUsers']);
$this->assertEquals(['group1', 'group2'], $json['applicableGroups']);
}
}

View File

@ -123,6 +123,9 @@ window.isPhantom = /phantom/i.test(navigator.userAgent);
// reset plugins
OC.Plugins._plugins = [];
// dummy select2 (which isn't loaded during the tests)
$.fn.select2 = function() {};
});
afterEach(function() {
@ -131,6 +134,8 @@ window.isPhantom = /phantom/i.test(navigator.userAgent);
fakeServer.restore();
$testArea.remove();
delete($.fn.select2);
});
})();

View File

@ -64,7 +64,8 @@ module.exports = function(config) {
// only test these files, others are not ready and mess
// up with the global namespace/classes/state
'apps/files_external/js/app.js',
'apps/files_external/js/mountsfilelist.js'
'apps/files_external/js/mountsfilelist.js',
'apps/files_external/js/settings.js'
],
testFiles: ['apps/files_external/tests/js/*.js']
},