Browse Source

Authentication mechanisms for external storage backends

A backend can now specify generic authentication schemes that it
supports, instead of specifying the parameters for its authentication
method directly. This allows multiple authentication mechanisms to be
implemented for a single scheme, providing altered functionality.

This commit introduces the backend framework for this feature, and so at
this point the UI will be broken as the frontend does not specify the
required information.

Terminology:
 - authentication scheme
    Parameter interface for the authentication method. A backend
    supporting the 'password' scheme accepts two parameters, 'user' and
    'password'.
 - authentication mechanism
    Specific mechanism implementing a scheme. Basic mechanisms may
    forward configuration options directly to the backend, more advanced
    ones may lookup parameters or retrieve them from the session

New dropdown selector for external storage configurations to select the
authentication mechanism to be used.

Authentication mechanisms can have visibilities, just like backends.
The API was extended too to make it easier to add/remove visibilities.
In addition, the concept of 'allowed visibility' has been introduced, so
a backend/auth mechanism can force a maximum visibility level (e.g.
Local storage type) that cannot be overridden by configuration in the
web UI.

An authentication mechanism is a fully instantiated implementation. This
allows an implementation to have dependencies injected into it, e.g. an
\OCP\IDB for database operations.

When a StorageConfig is being prepared for mounting, the authentication
mechanism implementation has manipulateStorage() called,
which inserts the relevant authentication method options into the
storage ready for mounting.
tags/v8.2beta1
Robin McCorkell 8 years ago
parent
commit
272a46ebe1

+ 14
- 0
apps/files_external/appinfo/application.php View File

@@ -51,6 +51,7 @@ class Application extends App {
});

$this->loadBackends();
$this->loadAuthMechanisms();
}

/**
@@ -61,4 +62,17 @@ class Application extends App {
$service = $container->query('OCA\\Files_External\\Service\\BackendService');
}

/**
* Load authentication mechanisms provided by this app
*/
protected function loadAuthMechanisms() {
$container = $this->getContainer();
$service = $container->query('OCA\\Files_External\\Service\\BackendService');

$service->registerAuthMechanisms([
// AuthMechanism::SCHEME_NULL mechanism
$container->query('OCA\Files_External\Lib\Auth\NullMechanism'),
]);
}

}

+ 6
- 0
apps/files_external/controller/globalstoragescontroller.php View File

@@ -64,6 +64,7 @@ class GlobalStoragesController extends StoragesController {
*
* @param string $mountPoint storage mount point
* @param string $backendClass backend class name
* @param string $authMechanismClass authentication mechanism class
* @param array $backendOptions backend-specific options
* @param array $mountOptions mount-specific options
* @param array $applicableUsers users for which to mount the storage
@@ -75,6 +76,7 @@ class GlobalStoragesController extends StoragesController {
public function create(
$mountPoint,
$backendClass,
$authMechanismClass,
$backendOptions,
$mountOptions,
$applicableUsers,
@@ -84,6 +86,7 @@ class GlobalStoragesController extends StoragesController {
$newStorage = $this->createStorage(
$mountPoint,
$backendClass,
$authMechanismClass,
$backendOptions,
$mountOptions,
$applicableUsers,
@@ -115,6 +118,7 @@ class GlobalStoragesController extends StoragesController {
* @param int $id storage id
* @param string $mountPoint storage mount point
* @param string $backendClass backend class name
* @param string $authMechanismClass authentication mechansim class
* @param array $backendOptions backend-specific options
* @param array $mountOptions mount-specific options
* @param array $applicableUsers users for which to mount the storage
@@ -127,6 +131,7 @@ class GlobalStoragesController extends StoragesController {
$id,
$mountPoint,
$backendClass,
$authMechanismClass,
$backendOptions,
$mountOptions,
$applicableUsers,
@@ -136,6 +141,7 @@ class GlobalStoragesController extends StoragesController {
$storage = $this->createStorage(
$mountPoint,
$backendClass,
$authMechanismClass,
$backendOptions,
$mountOptions,
$applicableUsers,

+ 19
- 1
apps/files_external/controller/storagescontroller.php View File

@@ -33,6 +33,7 @@ use \OCA\Files_external\Service\StoragesService;
use \OCA\Files_external\NotFoundException;
use \OCA\Files_external\Lib\StorageConfig;
use \OCA\Files_External\Lib\Backend\Backend;
use \OCA\Files_External\Lib\Auth\AuthMechanism;

/**
* Base class for storages controllers
@@ -77,6 +78,7 @@ abstract class StoragesController extends Controller {
*
* @param string $mountPoint storage mount point
* @param string $backendClass backend class name
* @param string $authMechanismClass authentication mechanism class name
* @param array $backendOptions backend-specific options
* @param array|null $mountOptions mount-specific options
* @param array|null $applicableUsers users for which to mount the storage
@@ -88,6 +90,7 @@ abstract class StoragesController extends Controller {
protected function createStorage(
$mountPoint,
$backendClass,
$authMechanismClass,
$backendOptions,
$mountOptions = null,
$applicableUsers = null,
@@ -98,6 +101,7 @@ abstract class StoragesController extends Controller {
return $this->service->createStorage(
$mountPoint,
$backendClass,
$authMechanismClass,
$backendOptions,
$mountOptions,
$applicableUsers,
@@ -107,7 +111,7 @@ abstract class StoragesController extends Controller {
} catch (\InvalidArgumentException $e) {
return new DataResponse(
[
'message' => (string)$this->l10n->t('Invalid backend class "%s"', [$backendClass])
'message' => (string)$this->l10n->t('Invalid backend or authentication mechanism class')
],
Http::STATUS_UNPROCESSABLE_ENTITY
);
@@ -134,6 +138,8 @@ abstract class StoragesController extends Controller {

/** @var Backend */
$backend = $storage->getBackend();
/** @var AuthMechanism */
$authMechanism = $storage->getAuthMechanism();
if (!$backend || $backend->checkDependencies()) {
// invalid backend
return new DataResponse(
@@ -154,6 +160,15 @@ abstract class StoragesController extends Controller {
Http::STATUS_UNPROCESSABLE_ENTITY
);
}
if (!$authMechanism->validateStorage($storage)) {
// unsatisfied parameters
return new DataResponse(
[
'message' => (string)$this->l10n->t('Unsatisfied authentication mechanism parameters')
],
Http::STATUS_UNPROCESSABLE_ENTITY
);
}

return null;
}
@@ -167,6 +182,9 @@ abstract class StoragesController extends Controller {
* @param StorageConfig $storage storage configuration
*/
protected function updateStorageStatus(StorageConfig &$storage) {
/** @var AuthMechanism */
$authMechanism = $storage->getAuthMechanism();
$authMechanism->manipulateStorageConfig($storage);
/** @var Backend */
$backend = $storage->getBackend();
$backend->manipulateStorageConfig($storage);

+ 6
- 0
apps/files_external/controller/userstoragescontroller.php View File

@@ -109,6 +109,7 @@ class UserStoragesController extends StoragesController {
*
* @param string $mountPoint storage mount point
* @param string $backendClass backend class name
* @param string $authMechanismClass authentication mechanism class
* @param array $backendOptions backend-specific options
* @param array $mountOptions backend-specific mount options
*
@@ -119,12 +120,14 @@ class UserStoragesController extends StoragesController {
public function create(
$mountPoint,
$backendClass,
$authMechanismClass,
$backendOptions,
$mountOptions
) {
$newStorage = $this->createStorage(
$mountPoint,
$backendClass,
$authMechanismClass,
$backendOptions,
$mountOptions
);
@@ -152,6 +155,7 @@ class UserStoragesController extends StoragesController {
* @param int $id storage id
* @param string $mountPoint storage mount point
* @param string $backendClass backend class name
* @param string $authMechanismClass authentication mechanism class
* @param array $backendOptions backend-specific options
* @param array $mountOptions backend-specific mount options
*
@@ -163,12 +167,14 @@ class UserStoragesController extends StoragesController {
$id,
$mountPoint,
$backendClass,
$authMechanismClass,
$backendOptions,
$mountOptions
) {
$storage = $this->createStorage(
$mountPoint,
$backendClass,
$authMechanismClass,
$backendOptions,
$mountOptions
);

+ 75
- 32
apps/files_external/js/settings.js View File

@@ -220,6 +220,13 @@ StorageConfig.prototype = {
*/
backendClass: null,

/**
* Authentication mechanism class name
*
* @type string
*/
authMechanismClass: null,

/**
* Backend-specific configuration
*
@@ -273,6 +280,7 @@ StorageConfig.prototype = {
var data = {
mountPoint: this.mountPoint,
backendClass: this.backendClass,
authMechanismClass: this.authMechanismClass,
backendOptions: this.backendOptions
};
if (this.id) {
@@ -579,6 +587,13 @@ MountConfigListView.prototype = {
*/
_allBackends: null,

/**
* List of all supported authentication mechanisms
*
* @type Object.<string,Object>
*/
_allAuthMechanisms: null,

_encryptionEnabled: false,

/**
@@ -605,6 +620,7 @@ MountConfigListView.prototype = {
// read the backend config that was carefully crammed
// into the data-configurations attribute of the select
this._allBackends = this.$el.find('.selectBackend').data('configurations');
this._allAuthMechanisms = this.$el.find('#addMountPoint .authentication').data('mechanisms');

//initialize hidden input field with list of users and groups
this.$el.find('tr:not(#addMountPoint)').each(function(i,tr) {
@@ -660,6 +676,7 @@ MountConfigListView.prototype = {
});

this.$el.on('change', '.selectBackend', _.bind(this._onSelectBackend, this));
this.$el.on('change', '.selectAuthMechanism', _.bind(this._onSelectAuthMechanism, this));
},

_onChange: function(event) {
@@ -694,40 +711,30 @@ MountConfigListView.prototype = {
}
$tr.addClass(backendClass);
$tr.find('.backend').data('class', backendClass);
var configurations = this._allBackends;
var $td = $tr.find('td.configuration');
$.each(configurations, function(backend, parameters) {
if (backend === backendClass) {
$.each(parameters['configuration'], function(parameter, placeholder) {
var is_optional = false;
if (placeholder.indexOf('&') === 0) {
is_optional = true;
placeholder = placeholder.substring(1);
}
var newElement;
if (placeholder.indexOf('*') === 0) {
var class_string = is_optional ? ' optional' : '';
newElement = $('<input type="password" class="added' + class_string + '" data-parameter="'+parameter+'" placeholder="'+placeholder.substring(1)+'" />');
} else if (placeholder.indexOf('!') === 0) {
newElement = $('<label><input type="checkbox" class="added" data-parameter="'+parameter+'" />'+placeholder.substring(1)+'</label>');
} else if (placeholder.indexOf('#') === 0) {
newElement = $('<input type="hidden" class="added" data-parameter="'+parameter+'" />');
} else {
var class_string = is_optional ? ' optional' : '';
newElement = $('<input type="text" class="added' + class_string + '" data-parameter="'+parameter+'" placeholder="'+placeholder+'" />');
}
highlightInput(newElement);
$td.append(newElement);
});
var priorityEl = $('<input type="hidden" class="priority" value="' + parameters['priority'] + '" />');
$tr.append(priorityEl);
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();
return false;
var backendConfiguration = this._allBackends[backendClass];

var selectAuthMechanism = $('<select class="selectAuthMechanism"></select>');
$.each(this._allAuthMechanisms, function(authClass, authMechanism) {
if (backendConfiguration['authSchemes'][authMechanism['scheme']]) {
selectAuthMechanism.append(
$('<option value="'+authClass+'" data-scheme="'+authMechanism['scheme']+'">'+authMechanism['name']+'</option>')
);
}
});
$tr.find('td.authentication').append(selectAuthMechanism);

var $td = $tr.find('td.configuration');
$.each(backendConfiguration['configuration'], _.partial(this.writeParameterInput, $td));

selectAuthMechanism.trigger('change'); // generate configuration parameters for auth mechanism

var priorityEl = $('<input type="hidden" class="priority" value="' + backendConfiguration['priority'] + '" />');
$tr.append(priorityEl);
if (backendConfiguration['custom'] && $el.find('tbody tr.'+backendClass.replace(/\\/g, '\\\\')).length === 1) {
OC.addScript('files_external', backendConfiguration['custom']);
}
$td.children().not('[type=hidden]').first().focus();

$tr.find('td').last().attr('class', 'remove');
$tr.find('td.mountOptionsToggle').removeClass('hidden');
$tr.find('td').last().removeAttr('style');
@@ -736,6 +743,41 @@ MountConfigListView.prototype = {
addSelect2($tr.find('.applicableUsers'), this._userListLimit);
},

_onSelectAuthMechanism: function(event) {
var $target = $(event.target);
var $tr = $target.closest('tr');

var authMechanismClass = $target.val();
var authMechanism = this._allAuthMechanisms[authMechanismClass];
var $td = $tr.find('td.configuration');
$td.find('.auth-param').remove();

$.each(authMechanism['configuration'], _.partial(
this.writeParameterInput, $td, _, _, ['auth-param']
));
},

writeParameterInput: function($td, parameter, placeholder, classes) {
classes = $.isArray(classes) ? classes : [];
classes.push('added');
if (placeholder.indexOf('&') === 0) {
classes.push('optional');
placeholder = placeholder.substring(1);
}
var newElement;
if (placeholder.indexOf('*') === 0) {
newElement = $('<input type="password" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+placeholder.substring(1)+'" />');
} else if (placeholder.indexOf('!') === 0) {
newElement = $('<label><input type="checkbox" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />'+placeholder.substring(1)+'</label>');
} else if (placeholder.indexOf('#') === 0) {
newElement = $('<input type="hidden" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />');
} else {
newElement = $('<input type="text" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+placeholder+'" />');
}
highlightInput(newElement);
$td.append(newElement);
},

/**
* Gets the storage model from the given row
*
@@ -751,6 +793,7 @@ MountConfigListView.prototype = {
var storage = new this._storageConfigClass(storageId);
storage.mountPoint = $tr.find('.mountPoint input').val();
storage.backendClass = $tr.find('.backend').data('class');
storage.authMechanismClass = $tr.find('.selectAuthMechanism').val();

var classOptions = {};
var configuration = $tr.find('.configuration input');

+ 120
- 0
apps/files_external/lib/auth/authmechanism.php View File

@@ -0,0 +1,120 @@
<?php
/**
* @author Robin McCorkell <rmccorkell@owncloud.com>
*
* @copyright Copyright (c) 2015, ownCloud, Inc.
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

namespace OCA\Files_External\Lib\Auth;

use \OCA\Files_External\Lib\StorageConfig;
use \OCA\Files_External\Lib\VisibilityTrait;
use \OCA\Files_External\Lib\FrontendDefinitionTrait;
use \OCA\Files_External\Lib\StorageModifierTrait;

/**
* Authentication mechanism
*
* An authentication mechanism can have services injected during construction,
* such as \OCP\IDB for database operations. This allows an authentication
* mechanism to perform advanced operations based on provided information.
*
* An authenication scheme defines the parameter interface, common to the
* storage implementation, the backend and the authentication mechanism.
* A storage implementation expects parameters according to the authentication
* scheme, which are provided from the authentication mechanism.
*
* This class uses the following traits:
* - VisibilityTrait
* Restrict usage to admin-only/none
* - FrontendDefinitionTrait
* Specify configuration parameters and other definitions
* - StorageModifierTrait
* Object can affect storage mounting
*/
class AuthMechanism implements \JsonSerializable {

/** Standard authentication schemes */
const SCHEME_NULL = 'null';
const SCHEME_PASSWORD = 'password';
const SCHEME_OAUTH1 = 'oauth1';
const SCHEME_OAUTH2 = 'oauth2';
const SCHEME_PUBLICKEY = 'publickey';
const SCHEME_OPENSTACK = 'openstack';

use VisibilityTrait;
use FrontendDefinitionTrait;
use StorageModifierTrait;

/** @var string */
protected $scheme;

/**
* @return string
*/
public function getClass() {
return '\\'.get_class($this);
}

/**
* Get the authentication scheme implemented
* See self::SCHEME_* constants
*
* @return string
*/
public function getScheme() {
return $this->scheme;
}

/**
* @param string $scheme
* @return self
*/
public function setScheme($scheme) {
$this->scheme = $scheme;
return $this;
}

/**
* Serialize into JSON for client-side JS
*
* @return array
*/
public function jsonSerialize() {
$data = $this->jsonSerializeDefinition();
$data['scheme'] = $this->getScheme();

return $data;
}

/**
* Check if parameters are satisfied in a StorageConfig
*
* @param StorageConfig $storage
* @return bool
*/
public function validateStorage(StorageConfig $storage) {
// does the backend actually support this scheme
$supportedSchemes = $storage->getBackend()->getAuthSchemes();
if (!isset($supportedSchemes[$this->getScheme()])) {
return false;
}

return $this->validateStorageDefinition($storage);
}

}

+ 40
- 0
apps/files_external/lib/auth/nullmechanism.php View File

@@ -0,0 +1,40 @@
<?php
/**
* @author Robin McCorkell <rmccorkell@owncloud.com>
*
* @copyright Copyright (c) 2015, ownCloud, Inc.
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

namespace OCA\Files_External\Lib\Auth;

use \OCP\IL10N;
use \OCA\Files_External\Lib\Auth\AuthMechanism;
use \OCA\Files_external\Lib\StorageConfig;

/**
* Null authentication mechanism
*/
class NullMechanism extends AuthMechanism {

public function __construct(IL10N $l) {
$this
->setScheme(self::SCHEME_NULL)
->setText($l->t('None'))
;
}

}

+ 76
- 0
apps/files_external/lib/backend/backend.php View File

@@ -27,9 +27,31 @@ use \OCA\Files_External\Lib\FrontendDefinitionTrait;
use \OCA\Files_External\Lib\PriorityTrait;
use \OCA\Files_External\Lib\DependencyTrait;
use \OCA\Files_External\Lib\StorageModifierTrait;
use \OCA\Files_External\Lib\Auth\AuthMechanism;

/**
* Storage backend
*
* A backend can have services injected during construction,
* such as \OCP\IDB for database operations. This allows a backend
* to perform advanced operations based on provided information.
*
* An authenication scheme defines the parameter interface, common to the
* storage implementation, the backend and the authentication mechanism.
* A storage implementation expects parameters according to the authentication
* scheme, which are provided from the authentication mechanism.
*
* This class uses the following traits:
* - VisibilityTrait
* Restrict usage to admin-only/none
* - FrontendDefinitionTrait
* Specify configuration parameters and other definitions
* - PriorityTrait
* Allow objects to prioritize over others with the same mountpoint
* - DependencyTrait
* The object requires certain dependencies to be met
* - StorageModifierTrait
* Object can affect storage mounting
*/
class Backend implements \JsonSerializable {

@@ -42,6 +64,12 @@ class Backend implements \JsonSerializable {
/** @var string storage class */
private $storageClass;

/** @var array 'scheme' => true, supported authentication schemes */
private $authSchemes = [];

/** @var AuthMechanism|callable authentication mechanism fallback */
private $legacyAuthMechanism;

/**
* @return string
*/
@@ -66,6 +94,53 @@ class Backend implements \JsonSerializable {
return $this;
}

/**
* @return array
*/
public function getAuthSchemes() {
if (empty($this->authSchemes)) {
return [AuthMechanism::SCHEME_NULL => true];
}
return $this->authSchemes;
}

/**
* @param string $scheme
* @return self
*/
public function addAuthScheme($scheme) {
$this->authSchemes[$scheme] = true;
return $this;
}

/**
* @param array $parameters storage parameters, for dynamic mechanism selection
* @return AuthMechanism
*/
public function getLegacyAuthMechanism(array $parameters = []) {
if (is_callable($this->legacyAuthMechanism)) {
return call_user_func($this->legacyAuthMechanism, $parameters);
}
return $this->legacyAuthMechanism;
}

/**
* @param AuthMechanism $authMechanism
* @return self
*/
public function setLegacyAuthMechanism(AuthMechanism $authMechanism) {
$this->legacyAuthMechanism = $authMechanism;
return $this;
}

/**
* @param callable $callback dynamic auth mechanism selection
* @return self
*/
public function setLegacyAuthMechanismCallback(callable $callback) {
$this->legacyAuthMechanism = $callback;
}

/**
* Serialize into JSON for client-side JS
*
@@ -76,6 +151,7 @@ class Backend implements \JsonSerializable {

$data['backend'] = $data['name']; // legacy compat
$data['priority'] = $this->getPriority();
$data['authSchemes'] = $this->getAuthSchemes();

return $data;
}

+ 15
- 1
apps/files_external/lib/config.php View File

@@ -123,7 +123,9 @@ class OC_Mount_Config {
if (!isset($options['priority'])) {
$options['priority'] = $backend->getPriority();
}

if (!isset($options['authMechanism'])) {
$options['authMechanism'] = $backend->getLegacyAuthMechanism($options['options'])->getClass();
}

// Override if priority greater
if ((!isset($mountPoints[$mountPoint]))
@@ -149,6 +151,9 @@ class OC_Mount_Config {
if (!isset($options['priority'])) {
$options['priority'] = $backend->getPriority();
}
if (!isset($options['authMechanism'])) {
$options['authMechanism'] = $backend->getLegacyAuthMechanism($options['options'])->getClass();
}

// Override if priority greater
if ((!isset($mountPoints[$mountPoint]))
@@ -175,6 +180,9 @@ class OC_Mount_Config {
if (!isset($options['priority'])) {
$options['priority'] = $backend->getPriority();
}
if (!isset($options['authMechanism'])) {
$options['authMechanism'] = $backend->getLegacyAuthMechanism($options['options'])->getClass();
}

// Override if priority greater or if priority type different
if ((!isset($mountPoints[$mountPoint]))
@@ -204,6 +212,9 @@ class OC_Mount_Config {
if (!isset($options['priority'])) {
$options['priority'] = $backend->getPriority();
}
if (!isset($options['authMechanism'])) {
$options['authMechanism'] = $backend->getLegacyAuthMechanism($options['options'])->getClass();
}

// Override if priority greater or if priority type different
if ((!isset($mountPoints[$mountPoint]))
@@ -227,6 +238,9 @@ class OC_Mount_Config {
if ($backend->isVisibleFor(BackendService::VISIBILITY_PERSONAL)) {
$options['personal'] = true;
$options['options'] = self::decryptPasswords($options['options']);
if (!isset($options['authMechanism'])) {
$options['authMechanism'] = $backend->getLegacyAuthMechanism($options['options'])->getClass();
}

// Always override previous config
$options['priority_type'] = self::MOUNT_TYPE_PERSONAL;

+ 3
- 0
apps/files_external/lib/config/configadapter.php View File

@@ -68,6 +68,7 @@ class ConfigAdapter implements IMountProvider {
$storage->setBackendOption('objectstore', new $objectClass($objectStore));
}

$storage->getAuthMechanism()->manipulateStorageConfig($storage);
$storage->getBackend()->manipulateStorageConfig($storage);
}

@@ -81,7 +82,9 @@ class ConfigAdapter implements IMountProvider {
$class = $storageConfig->getBackend()->getStorageClass();
$storage = new $class($storageConfig->getBackendOptions());

// auth mechanism should fire first
$storage = $storageConfig->getBackend()->wrapStorage($storage);
$storage = $storageConfig->getAuthMechanism()->wrapStorage($storage);

return $storage;
}

+ 23
- 0
apps/files_external/lib/storageconfig.php View File

@@ -22,6 +22,7 @@
namespace OCA\Files_external\Lib;

use \OCA\Files_External\Lib\Backend\Backend;
use \OCA\Files_External\Lib\Auth\AuthMechanism;

/**
* External storage configuration
@@ -42,6 +43,13 @@ class StorageConfig implements \JsonSerializable {
*/
private $backend;

/**
* Authentication mechanism
*
* @var AuthMechanism
*/
private $authMechanism;

/**
* Backend options
*
@@ -153,6 +161,20 @@ class StorageConfig implements \JsonSerializable {
$this->backend= $backend;
}

/**
* @return AuthMechanism
*/
public function getAuthMechanism() {
return $this->authMechanism;
}

/**
* @param AuthMechanism
*/
public function setAuthMechanism(AuthMechanism $authMechanism) {
$this->authMechanism = $authMechanism;
}

/**
* Returns the external storage backend-specific options
*
@@ -301,6 +323,7 @@ class StorageConfig implements \JsonSerializable {
}
$result['mountPoint'] = $this->mountPoint;
$result['backendClass'] = $this->backend->getClass();
$result['authMechanismClass'] = $this->authMechanism->getClass();
$result['backendOptions'] = $this->backendOptions;
if (!is_null($this->priority)) {
$result['priority'] = $this->priority;

+ 10
- 0
apps/files_external/lib/storagemodifiertrait.php View File

@@ -26,6 +26,16 @@ use \OCA\Files_External\Lib\StorageConfig;

/**
* Trait for objects that can modify StorageConfigs and wrap Storages
*
* When a storage implementation is being prepared for use, the StorageConfig
* is passed through manipulateStorageConfig() to update any parameters as
* necessary. After the storage implementation has been constructed, it is
* passed through wrapStorage(), potentially replacing the implementation with
* a wrapped storage that changes its behaviour.
*
* Certain configuration options need to be set before the implementation is
* constructed, while others are retrieved directly from the storage
* implementation and so need a wrapper to be modified.
*/
trait StorageModifierTrait {


+ 7
- 0
apps/files_external/lib/visibilitytrait.php View File

@@ -25,6 +25,13 @@ use \OCA\Files_External\Service\BackendService;

/**
* Trait to implement visibility mechanics for a configuration class
*
* The standard visibility defines which users/groups can use or see the
* object. The allowed visibility defines the maximum visibility allowed to be
* set on the object. The standard visibility is often set dynamically by
* stored configuration parameters that can be modified by the administrator,
* while the allowed visibility is set directly by the object and cannot be
* modified by the administrator.
*/
trait VisibilityTrait {


+ 1
- 0
apps/files_external/personal.php View File

@@ -40,4 +40,5 @@ $tmpl->assign('isAdminPage', false);
$tmpl->assign('storages', $userStoragesService->getAllStorages());
$tmpl->assign('dependencies', OC_Mount_Config::dependencyMessage($backendService->getBackends()));
$tmpl->assign('backends', $backendService->getBackendsVisibleFor(BackendService::VISIBILITY_PERSONAL));
$tmpl->assign('authMechanisms', $backendService->getAuthMechanisms());
return $tmpl->fetchPage();

+ 91
- 0
apps/files_external/service/backendservice.php View File

@@ -24,6 +24,7 @@ namespace OCA\Files_External\Service;
use \OCP\IConfig;

use \OCA\Files_External\Lib\Backend\Backend;
use \OCA\Files_External\Lib\Auth\AuthMechanism;

/**
* Service class to manage backend definitions
@@ -53,6 +54,9 @@ class BackendService {
/** @var Backend[] */
private $backends = [];

/** @var AuthMechanism[] */
private $authMechanisms = [];

/**
* @param IConfig $config
*/
@@ -90,6 +94,26 @@ class BackendService {
$this->registerBackend($backend);
}
}
/**
* Register an authentication mechanism
*
* @param AuthMechanism $authMech
*/
public function registerAuthMechanism(AuthMechanism $authMech) {
if (!$this->isAllowedAuthMechanism($authMech)) {
$authMech->removeVisibility(BackendService::VISIBILITY_PERSONAL);
}
$this->authMechanisms[$authMech->getClass()] = $authMech;
}

/**
* @param AuthMechanism[] $mechanisms
*/
public function registerAuthMechanisms(array $mechanisms) {
foreach ($mechanisms as $mechanism) {
$this->registerAuthMechanism($mechanism);
}
}

/**
* Get all backends
@@ -146,6 +170,63 @@ class BackendService {
return null;
}

/**
* Get all authentication mechanisms
*
* @return AuthMechanism[]
*/
public function getAuthMechanisms() {
return $this->authMechanisms;
}

/**
* Get all authentication mechanisms for schemes
*
* @param string[] $schemes
* @return AuthMechanism[]
*/
public function getAuthMechanismsByScheme(array $schemes) {
return array_filter($this->getAuthMechanisms(), function($authMech) use ($schemes) {
return in_array($authMech->getScheme(), $schemes, true);
});
}

/**
* Get authentication mechanisms visible for $visibleFor
*
* @param int $visibleFor
* @return AuthMechanism[]
*/
public function getAuthMechanismsVisibleFor($visibleFor) {
return array_filter($this->getAuthMechanisms(), function($authMechanism) use ($visibleFor) {
return $authMechanism->isVisibleFor($visibleFor);
});
}

/**
* Get authentication mechanisms allowed to be visible for $visibleFor
*
* @param int $visibleFor
* @return AuthMechanism[]
*/
public function getAuthMechanismsAllowedVisibleFor($visibleFor) {
return array_filter($this->getAuthMechanisms(), function($authMechanism) use ($visibleFor) {
return $authMechanism->isAllowedVisibleFor($visibleFor);
});
}


/**
* @param string $class
* @return AuthMechanism|null
*/
public function getAuthMechanism($class) {
if (isset($this->authMechanisms[$class])) {
return $this->authMechanisms[$class];
}
return null;
}

/**
* @return bool
*/
@@ -167,4 +248,14 @@ class BackendService {
}
return false;
}

/**
* Check an authentication mechanism if a user is allowed to use it
*
* @param AuthMechanism $authMechanism
* @return bool
*/
protected function isAllowedAuthMechanism(AuthMechanism $authMechanism) {
return true; // not implemented
}
}

+ 23
- 0
apps/files_external/service/storagesservice.php View File

@@ -82,8 +82,22 @@ abstract class StoragesService {
$storageOptions
) {
$backend = $this->backendService->getBackend($storageOptions['class']);
if (!$backend) {
throw new \UnexpectedValueException('Invalid backend class');
}
$storageConfig->setBackend($backend);

if (isset($storageOptions['authMechanism'])) {
$authMechanism = $this->backendService->getAuthMechanism($storageOptions['authMechanism']);
} else {
$authMechanism = $backend->getLegacyAuthMechanism($storageOptions);
$storageOptions['authMechanism'] = 'null'; // to make error handling easier
}
if (!$authMechanism) {
throw new \UnexpectedValueException('Invalid authentication mechanism class');
}
$storageConfig->setAuthMechanism($authMechanism);

$storageConfig->setBackendOptions($storageOptions['options']);
if (isset($storageOptions['mountOptions'])) {
$storageConfig->setMountOptions($storageOptions['mountOptions']);
@@ -128,6 +142,7 @@ abstract class StoragesService {
* - "priority": storage priority
* - "backend": backend class name
* - "options": backend-specific options
* - "authMechanism": authentication mechanism class name
* - "mountOptions": mount-specific options (ex: disable previews, scanner, etc)
*/

@@ -257,6 +272,7 @@ abstract class StoragesService {
$options = [
'id' => $storageConfig->getId(),
'class' => $storageConfig->getBackend()->getClass(),
'authMechanism' => $storageConfig->getAuthMechanism()->getClass(),
'options' => $storageConfig->getBackendOptions(),
];

@@ -335,6 +351,7 @@ abstract class StoragesService {
*
* @param string $mountPoint storage mount point
* @param string $backendClass backend class name
* @param string $authMechanismClass authentication mechanism class
* @param array $backendOptions backend-specific options
* @param array|null $mountOptions mount-specific options
* @param array|null $applicableUsers users for which to mount the storage
@@ -346,6 +363,7 @@ abstract class StoragesService {
public function createStorage(
$mountPoint,
$backendClass,
$authMechanismClass,
$backendOptions,
$mountOptions = null,
$applicableUsers = null,
@@ -356,9 +374,14 @@ abstract class StoragesService {
if (!$backend) {
throw new \InvalidArgumentException('Unable to get backend for backend class '.$backendClass);
}
$authMechanism = $this->backendService->getAuthMechanism($authMechanismClass);
if (!$authMechanism) {
throw new \InvalidArgumentException('Unable to get authentication mechanism for class '.$authMechanismClass);
}
$newStorage = new StorageConfig();
$newStorage->setMountPoint($mountPoint);
$newStorage->setBackend($backend);
$newStorage->setAuthMechanism($authMechanism);
$newStorage->setBackendOptions($backendOptions);
if (isset($mountOptions)) {
$newStorage->setMountOptions($mountOptions);

+ 1
- 0
apps/files_external/settings.php View File

@@ -46,6 +46,7 @@ $tmpl->assign('encryptionEnabled', \OC::$server->getEncryptionManager()->isEnabl
$tmpl->assign('isAdminPage', true);
$tmpl->assign('storages', $globalStoragesService->getAllStorages());
$tmpl->assign('backends', $backendService->getBackendsVisibleFor(BackendService::VISIBILITY_ADMIN));
$tmpl->assign('authMechanisms', $backendService->getAuthMechanisms());
$tmpl->assign('userBackends', $backendService->getBackendsAllowedVisibleFor(BackendService::VISIBILITY_PERSONAL));
$tmpl->assign('dependencies', OC_Mount_Config::dependencyMessage($backendService->getBackends()));
$tmpl->assign('allowUserMounting', $backendService->isUserMountingAllowed());

+ 79
- 48
apps/files_external/templates/settings.php View File

@@ -2,6 +2,56 @@
use \OCA\Files_External\Lib\Backend\Backend;
use \OCA\Files_External\Lib\DefinitionParameter;
use \OCA\Files_External\Service\BackendService;

function writeParameterInput($parameter, $options, $classes = []) {
$value = '';
if (isset($options[$parameter->getName()])) {
$value = $options[$parameter->getName()];
}
$placeholder = $parameter->getText();
$is_optional = $parameter->isFlagSet(DefinitionParameter::FLAG_OPTIONAL);

switch ($parameter->getType()) {
case DefinitionParameter::VALUE_PASSWORD: ?>
<?php if ($is_optional) { $classes[] = 'optional'; } ?>
<input type="password"
<?php if (!empty($classes)): ?> class="<?php p(implode(' ', $classes)); ?>"<?php endif; ?>
data-parameter="<?php p($parameter->getName()); ?>"
value="<?php p($value); ?>"
placeholder="<?php p($placeholder); ?>"
/>
<?php
break;
case DefinitionParameter::VALUE_BOOLEAN: ?>
<label>
<input type="checkbox"
<?php if (!empty($classes)): ?> class="<?php p(implode(' ', $classes)); ?>"<?php endif; ?>
data-parameter="<?php p($parameter->getName()); ?>"
<?php if ($value == 'true'): ?> checked="checked"<?php endif; ?>
/>
<?php p($placeholder); ?>
</label>
<?php
break;
case DefinitionParameter::VALUE_HIDDEN: ?>
<input type="hidden"
<?php if (!empty($classes)): ?> class="<?php p(implode(' ', $classes)); ?>"<?php endif; ?>
data-parameter="<?php p($parameter->getName()); ?>"
value="<?php p($value); ?>"
/>
<?php
break;
default: ?>
<?php if ($is_optional) { $classes[] = 'optional'; } ?>
<input type="text"
<?php if (!empty($classes)): ?> class="<?php p(implode(' ', $classes)); ?>"<?php endif; ?>
data-parameter="<?php p($parameter->getName()); ?>"
value="<?php p($value); ?>"
placeholder="<?php p($placeholder); ?>"
/>
<?php
}
}
?>
<form id="files_external" class="section" data-encryption-enabled="<?php echo $_['encryptionEnabled']?'true': 'false'; ?>">
<h2><?php p($l->t('External Storage')); ?></h2>
@@ -12,6 +62,7 @@
<th></th>
<th><?php p($l->t('Folder name')); ?></th>
<th><?php p($l->t('External storage')); ?></th>
<th><?php p($l->t('Authentication')); ?></th>
<th><?php p($l->t('Configuration')); ?></th>
<?php if ($_['isAdminPage']) print_unescaped('<th>'.$l->t('Available for').'</th>'); ?>
<th>&nbsp;</th>
@@ -31,60 +82,39 @@
</td>
<td class="backend" data-class="<?php p($storage->getBackend()->getClass()); ?>"><?php p($storage->getBackend()->getText()); ?>
</td>
<td class="configuration">
<?php $options = $storage->getBackendOptions(); ?>
<?php foreach ($storage->getBackend()->getParameters() as $parameter): ?>
<td class="authentication">
<select class="selectAuthMechanism">
<?php
$value = '';
if (isset($options[$parameter->getName()])) {
$value = $options[$parameter->getName()];
}
$placeholder = $parameter->getText();
$is_optional = $parameter->isFlagSet(DefinitionParameter::FLAG_OPTIONAL);

switch ($parameter->getType()) {
case DefinitionParameter::VALUE_PASSWORD: ?>
<input type="password"
<?php if ($is_optional): ?> class="optional"<?php endif; ?>
data-parameter="<?php p($parameter->getName()); ?>"
value="<?php p($value); ?>"
placeholder="<?php p($placeholder); ?>"
/>
<?php
break;
case DefinitionParameter::VALUE_BOOLEAN: ?>
<label>
<input type="checkbox"
data-parameter="<?php p($parameter->getName()); ?>"
<?php if ($value == 'true'): ?> checked="checked"<?php endif; ?>
/>
<?php p($placeholder); ?>
</label>
<?php
break;
case DefinitionParameter::VALUE_HIDDEN: ?>
<input type="hidden"
data-parameter="<?php p($parameter->getName()); ?>"
value="<?php p($value); ?>"
/>
<?php
break;
default: ?>
<input type="text"
<?php if ($is_optional): ?> class="optional"<?php endif; ?>
data-parameter="<?php p($parameter->getName()); ?>"
value="<?php p($value); ?>"
placeholder="<?php p($placeholder); ?>"
/>
<?php
}
$authSchemes = $storage->getBackend()->getAuthSchemes();
$authMechanisms = array_filter($_['authMechanisms'], function($mech) use ($authSchemes) {
return isset($authSchemes[$mech->getScheme()]);
});
?>
<?php endforeach; ?>
<?php foreach ($authMechanisms as $mech): ?>
<option value="<?php p($mech->getClass()); ?>" data-scheme="<?php p($mech->getScheme());?>"
<?php if ($mech->getClass() === $storage->getAuthMechanism()->getClass()): ?>selected<?php endif; ?>
><?php p($mech->getText()); ?></option>
<?php endforeach; ?>
</select>
</td>
<td class="configuration">
<?php
$options = $storage->getBackendOptions();
foreach ($storage->getBackend()->getParameters() as $parameter) {
writeParameterInput($parameter, $options);
}
foreach ($storage->getAuthMechanism()->getParameters() as $parameter) {
writeParameterInput($parameter, $options, ['auth-param']);
}

$customJs = $storage->getBackend()->getCustomJs();
if (isset($customJs)) {
\OCP\Util::addScript('files_external', $customJs);
}
$customJsAuth = $storage->getAuthMechanism()->getCustomJs();
if (isset($customJsAuth)) {
\OCP\Util::addScript('files_external', $customJsAuth);
}
?>
</td>
<?php if ($_['isAdminPage']): ?>
@@ -140,7 +170,8 @@
<?php endforeach; ?>
</select>
</td>
<td class="configuration"</td>
<td class="authentication" data-mechanisms='<?php p(json_encode($_['authMechanisms'])); ?>'></td>
<td class="configuration"></td>
<?php if ($_['isAdminPage']): ?>
<td class="applicable" align="right">
<input type="hidden" class="applicableUsers" style="width:20em;" value="" />

+ 36
- 2
apps/files_external/tests/controller/storagescontrollertest.php View File

@@ -58,7 +58,22 @@ abstract class StoragesControllerTest extends \Test\TestCase {
return $backend;
}

protected function getAuthMechMock($scheme = 'null', $class = '\OCA\Files_External\Lib\Auth\NullMechanism') {
$authMech = $this->getMockBuilder('\OCA\Files_External\Lib\Auth\AuthMechanism')
->disableOriginalConstructor()
->getMock();
$authMech->method('getScheme')
->willReturn($scheme);
$authMech->method('getClass')
->willReturn($class);

return $authMech;
}

public function testAddStorage() {
$authMech = $this->getAuthMechMock();
$authMech->method('validateStorage')
->willReturn(true);
$backend = $this->getBackendMock();
$backend->method('validateStorage')
->willReturn(true);
@@ -68,6 +83,7 @@ abstract class StoragesControllerTest extends \Test\TestCase {
$storageConfig = new StorageConfig(1);
$storageConfig->setMountPoint('mount');
$storageConfig->setBackend($backend);
$storageConfig->setAuthMechanism($authMech);
$storageConfig->setBackendOptions([]);

$this->service->expects($this->once())
@@ -80,6 +96,7 @@ abstract class StoragesControllerTest extends \Test\TestCase {
$response = $this->controller->create(
'mount',
'\OC\Files\Storage\SMB',
'\OCA\Files_External\Lib\Auth\NullMechanism',
array(),
[],
[],
@@ -88,11 +105,14 @@ abstract class StoragesControllerTest extends \Test\TestCase {
);

$data = $response->getData();
$this->assertEquals($storageConfig, $data);
$this->assertEquals(Http::STATUS_CREATED, $response->getStatus());
$this->assertEquals($storageConfig, $data);
}

public function testUpdateStorage() {
$authMech = $this->getAuthMechMock();
$authMech->method('validateStorage')
->willReturn(true);
$backend = $this->getBackendMock();
$backend->method('validateStorage')
->willReturn(true);
@@ -102,6 +122,7 @@ abstract class StoragesControllerTest extends \Test\TestCase {
$storageConfig = new StorageConfig(1);
$storageConfig->setMountPoint('mount');
$storageConfig->setBackend($backend);
$storageConfig->setAuthMechanism($authMech);
$storageConfig->setBackendOptions([]);

$this->service->expects($this->once())
@@ -115,6 +136,7 @@ abstract class StoragesControllerTest extends \Test\TestCase {
1,
'mount',
'\OC\Files\Storage\SMB',
'\OCA\Files_External\Lib\Auth\NullMechanism',
array(),
[],
[],
@@ -123,8 +145,8 @@ abstract class StoragesControllerTest extends \Test\TestCase {
);

$data = $response->getData();
$this->assertEquals($storageConfig, $data);
$this->assertEquals(Http::STATUS_OK, $response->getStatus());
$this->assertEquals($storageConfig, $data);
}

function mountPointNamesProvider() {
@@ -142,6 +164,7 @@ abstract class StoragesControllerTest extends \Test\TestCase {
$storageConfig = new StorageConfig(1);
$storageConfig->setMountPoint($mountPoint);
$storageConfig->setBackend($this->getBackendMock());
$storageConfig->setAuthMechanism($this->getAuthMechMock());
$storageConfig->setBackendOptions([]);

$this->service->expects($this->exactly(2))
@@ -155,6 +178,7 @@ abstract class StoragesControllerTest extends \Test\TestCase {
$response = $this->controller->create(
$mountPoint,
'\OC\Files\Storage\SMB',
'\OCA\Files_External\Lib\Auth\NullMechanism',
array(),
[],
[],
@@ -168,6 +192,7 @@ abstract class StoragesControllerTest extends \Test\TestCase {
1,
$mountPoint,
'\OC\Files\Storage\SMB',
'\OCA\Files_External\Lib\Auth\NullMechanism',
array(),
[],
[],
@@ -190,6 +215,7 @@ abstract class StoragesControllerTest extends \Test\TestCase {
$response = $this->controller->create(
'mount',
'\OC\Files\Storage\InvalidStorage',
'\OCA\Files_External\Lib\Auth\NullMechanism',
array(),
[],
[],
@@ -203,6 +229,7 @@ abstract class StoragesControllerTest extends \Test\TestCase {
1,
'mount',
'\OC\Files\Storage\InvalidStorage',
'\OCA\Files_External\Lib\Auth\NullMechanism',
array(),
[],
[],
@@ -214,6 +241,9 @@ abstract class StoragesControllerTest extends \Test\TestCase {
}

public function testUpdateStorageNonExisting() {
$authMech = $this->getAuthMechMock();
$authMech->method('validateStorage')
->willReturn(true);
$backend = $this->getBackendMock();
$backend->method('validateStorage')
->willReturn(true);
@@ -223,6 +253,7 @@ abstract class StoragesControllerTest extends \Test\TestCase {
$storageConfig = new StorageConfig(255);
$storageConfig->setMountPoint('mount');
$storageConfig->setBackend($backend);
$storageConfig->setAuthMechanism($authMech);
$storageConfig->setBackendOptions([]);

$this->service->expects($this->once())
@@ -236,6 +267,7 @@ abstract class StoragesControllerTest extends \Test\TestCase {
255,
'mount',
'\OC\Files\Storage\SMB',
'\OCA\Files_External\Lib\Auth\NullMechanism',
array(),
[],
[],
@@ -265,9 +297,11 @@ abstract class StoragesControllerTest extends \Test\TestCase {

public function testGetStorage() {
$backend = $this->getBackendMock();
$authMech = $this->getAuthMechMock();
$storageConfig = new StorageConfig(1);
$storageConfig->setMountPoint('test');
$storageConfig->setBackend($backend);
$storageConfig->setAuthMechanism($authMech);
$storageConfig->setBackendOptions(['user' => 'test', 'password', 'password123']);
$storageConfig->setMountOptions(['priority' => false]);


+ 4
- 0
apps/files_external/tests/controller/userstoragescontrollertest.php View File

@@ -53,10 +53,12 @@ class UserStoragesControllerTest extends StoragesControllerTest {
$backend->method('isVisibleFor')
->with(BackendService::VISIBILITY_PERSONAL)
->willReturn(false);
$authMech = $this->getAuthMechMock();

$storageConfig = new StorageConfig(1);
$storageConfig->setMountPoint('mount');
$storageConfig->setBackend($backend);
$storageConfig->setAuthMechanism($authMech);
$storageConfig->setBackendOptions([]);

$this->service->expects($this->exactly(2))
@@ -70,6 +72,7 @@ class UserStoragesControllerTest extends StoragesControllerTest {
$response = $this->controller->create(
'mount',
'\OC\Files\Storage\SMB',
'\Auth\Mechanism',
array(),
[],
[],
@@ -83,6 +86,7 @@ class UserStoragesControllerTest extends StoragesControllerTest {
1,
'mount',
'\OC\Files\Storage\SMB',
'\Auth\Mechanism',
array(),
[],
[],

+ 20
- 2
apps/files_external/tests/js/settingsSpec.js View File

@@ -39,6 +39,7 @@ describe('OCA.External.Settings tests', function() {
'<option value="\\OC\\AnotherTestBackend">Another Test Backend</option>' +
'</select>' +
'</td>' +
'<td class="authentication"></td>' +
'<td class="configuration"></td>' +
'<td class="applicable">' +
'<input type="hidden" class="applicableUsers">' +
@@ -58,6 +59,9 @@ describe('OCA.External.Settings tests', function() {
'field1': 'Display Name 1',
'field2': '&Display Name 2'
},
'authSchemes': {
'builtin': true,
},
'priority': 11
},
'\\OC\\AnotherTestBackend': {
@@ -66,10 +70,23 @@ describe('OCA.External.Settings tests', function() {
'field1': 'Display Name 1',
'field2': '&Display Name 2'
},
'authSchemes': {
'builtin': true,
},
'priority': 12
}
}
);

$('#externalStorage #addMountPoint .authentication:first').data('mechanisms', {
'mechanism1': {
'name': 'Mechanism 1',
'configuration': {
},
'scheme': 'builtin',
},
});

});
afterEach(function() {
select2Stub.restore();
@@ -80,7 +97,7 @@ describe('OCA.External.Settings tests', function() {
var view;

function selectBackend(backendName) {
view.$el.find('.selectBackend:first').val('\\OC\\TestBackend').trigger('change');
view.$el.find('.selectBackend:first').val(backendName).trigger('change');
}

beforeEach(function() {
@@ -139,7 +156,8 @@ describe('OCA.External.Settings tests', function() {
var request = fakeServer.requests[0];
expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_external/globalstorages');
expect(JSON.parse(request.requestBody)).toEqual({
backendClass: '\\OC\\TestBackend',
backend: '\\OC\\TestBackend',
authMechanism: 'mechanism1',
backendOptions: {
'field1': 'test',
'field2': ''

+ 13
- 0
apps/files_external/tests/service/globalstoragesservicetest.php View File

@@ -41,6 +41,7 @@ class GlobalStoragesServiceTest extends StoragesServiceTest {
return $this->makeStorageConfig([
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'authMechanismClass' => '\Auth\Mechanism',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
@@ -62,6 +63,7 @@ class GlobalStoragesServiceTest extends StoragesServiceTest {
[
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'authMechanismClass' => '\Auth\Mechanism',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
@@ -77,6 +79,7 @@ class GlobalStoragesServiceTest extends StoragesServiceTest {
[
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'authMechanismClass' => '\Auth\Mechanism',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
@@ -92,6 +95,7 @@ class GlobalStoragesServiceTest extends StoragesServiceTest {
[
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'authMechanismClass' => '\Auth\Mechanism',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
@@ -107,6 +111,7 @@ class GlobalStoragesServiceTest extends StoragesServiceTest {
[
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'authMechanismClass' => '\Auth\Mechanism',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
@@ -134,6 +139,7 @@ class GlobalStoragesServiceTest extends StoragesServiceTest {

$this->assertEquals($storage->getMountPoint(), $newStorage->getMountPoint());
$this->assertEquals($storage->getBackend(), $newStorage->getBackend());
$this->assertEquals($storage->getAuthMechanism(), $newStorage->getAuthMechanism());
$this->assertEquals($storage->getBackendOptions(), $newStorage->getBackendOptions());
$this->assertEquals($storage->getApplicableUsers(), $newStorage->getApplicableUsers());
$this->assertEquals($storage->getApplicableGroups(), $newStorage->getApplicableGroups());
@@ -154,6 +160,7 @@ class GlobalStoragesServiceTest extends StoragesServiceTest {
$storage = $this->makeStorageConfig([
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'authMechanismClass' => '\Auth\Mechanism',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
@@ -641,6 +648,7 @@ class GlobalStoragesServiceTest extends StoragesServiceTest {
$mountPointOptions = current($mountPointData);
$this->assertEquals(1, $mountPointOptions['id']);
$this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']);
$this->assertEquals('\Auth\Mechanism', $mountPointOptions['authMechanism']);
$this->assertEquals(15, $mountPointOptions['priority']);
$this->assertEquals(false, $mountPointOptions['mountOptions']['preview']);

@@ -681,6 +689,7 @@ class GlobalStoragesServiceTest extends StoragesServiceTest {

$this->assertEquals(1, $mountPointOptions['id']);
$this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']);
$this->assertEquals('\Auth\Mechanism', $mountPointOptions['authMechanism']);
$this->assertEquals(15, $mountPointOptions['priority']);
$this->assertEquals(false, $mountPointOptions['mountOptions']['preview']);

@@ -698,6 +707,7 @@ class GlobalStoragesServiceTest extends StoragesServiceTest {

$this->assertEquals(1, $mountPointOptions['id']);
$this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']);
$this->assertEquals('\Auth\Mechanism', $mountPointOptions['authMechanism']);
$this->assertEquals(15, $mountPointOptions['priority']);
$this->assertEquals(false, $mountPointOptions['mountOptions']['preview']);

@@ -723,12 +733,14 @@ class GlobalStoragesServiceTest extends StoragesServiceTest {

$legacyConfig = [
'class' => '\OC\Files\Storage\SMB',
'authMechanism' => '\Auth\Mechanism',
'options' => $legacyBackendOptions,
'mountOptions' => ['preview' => false],
];
// different mount options
$legacyConfig2 = [
'class' => '\OC\Files\Storage\SMB',
'authMechanism' => '\Auth\Mechanism',
'options' => $legacyBackendOptions,
'mountOptions' => ['preview' => true],
];
@@ -740,6 +752,7 @@ class GlobalStoragesServiceTest extends StoragesServiceTest {
// different config
$legacyConfig3 = [
'class' => '\OC\Files\Storage\SMB',
'authMechanism' => '\Auth\Mechanism',
'options' => $legacyBackendOptions2,
'mountOptions' => ['preview' => true],
];

+ 44
- 0
apps/files_external/tests/service/storagesservicetest.php View File

@@ -59,15 +59,39 @@ abstract class StoragesServiceTest extends \Test\TestCase {
);
\OC_Mount_Config::$skipTest = true;

// prepare BackendService mock
$this->backendService =
$this->getMockBuilder('\OCA\Files_External\Service\BackendService')
->disableOriginalConstructor()
->getMock();

$authMechanisms = [
'\Auth\Mechanism' => $this->getAuthMechMock('null', '\Auth\Mechanism'),
'\Other\Auth\Mechanism' => $this->getAuthMechMock('null', '\Other\Auth\Mechanism'),
'\OCA\Files_External\Lib\Auth\NullMechanism' => $this->getAuthMechMock(),
];
$this->backendService->method('getAuthMechanism')
->will($this->returnCallback(function($class) use ($authMechanisms) {
if (isset($authMechanisms[$class])) {
return $authMechanisms[$class];
}
return null;
}));
$this->backendService->method('getAuthMechanismsByScheme')
->will($this->returnCallback(function($schemes) use ($authMechanisms) {
return array_filter($authMechanisms, function ($authMech) use ($schemes) {
return in_array($authMech->getScheme(), $schemes, true);
});
}));
$this->backendService->method('getAuthMechanisms')
->will($this->returnValue($authMechanisms));

$backends = [
'\OC\Files\Storage\SMB' => $this->getBackendMock('\OCA\Files_External\Lib\Backend\SMB', '\OC\Files\Storage\SMB'),
'\OC\Files\Storage\SFTP' => $this->getBackendMock('\OCA\Files_External\Lib\Backend\SFTP', '\OC\Files\Storage\SFTP'),
];
$backends['\OC\Files\Storage\SFTP']->method('getLegacyAuthMechanism')
->willReturn($authMechanisms['\Other\Auth\Mechanism']);
$this->backendService->method('getBackend')
->will($this->returnCallback(function($backendClass) use ($backends) {
if (isset($backends[$backendClass])) {
@@ -105,6 +129,18 @@ abstract class StoragesServiceTest extends \Test\TestCase {
return $backend;
}

protected function getAuthMechMock($scheme = 'null', $class = '\OCA\Files_External\Lib\Auth\NullMechanism') {
$authMech = $this->getMockBuilder('\OCA\Files_External\Lib\Auth\AuthMechanism')
->disableOriginalConstructor()
->getMock();
$authMech->method('getScheme')
->willReturn($scheme);
$authMech->method('getClass')
->willReturn($class);

return $authMech;
}

/**
* Creates a StorageConfig instance based on array data
*
@@ -123,7 +159,11 @@ abstract class StoragesServiceTest extends \Test\TestCase {
// so $data['backend'] can be specified directly
$data['backend'] = $this->backendService->getBackend($data['backendClass']);
}
if (!isset($data['authMechanism'])) {
$data['authMechanism'] = $this->backendService->getAuthMechanism($data['authMechanismClass']);
}
$storage->setBackend($data['backend']);
$storage->setAuthMechanism($data['authMechanism']);
$storage->setBackendOptions($data['backendOptions']);
if (isset($data['applicableUsers'])) {
$storage->setApplicableUsers($data['applicableUsers']);
@@ -146,17 +186,21 @@ abstract class StoragesServiceTest extends \Test\TestCase {
*/
public function testNonExistingStorage() {
$backend = $this->backendService->getBackend('\OC\Files\Storage\SMB');
$authMechanism = $this->backendService->getAuthMechanism('\Auth\Mechanism');
$storage = new StorageConfig(255);
$storage->setMountPoint('mountpoint');
$storage->setBackend($backend);
$storage->setAuthMechanism($authMechanism);
$this->service->updateStorage($storage);
}

public function testDeleteStorage() {
$backend = $this->backendService->getBackend('\OC\Files\Storage\SMB');
$authMechanism = $this->backendService->getAuthMechanism('\Auth\Mechanism');
$storage = new StorageConfig(255);
$storage->setMountPoint('mountpoint');
$storage->setBackend($backend);
$storage->setAuthMechanism($authMechanism);
$storage->setBackendOptions(['password' => 'testPassword']);

$newStorage = $this->service->addStorage($storage);

+ 6
- 0
apps/files_external/tests/service/userstoragesservicetest.php View File

@@ -55,6 +55,7 @@ class UserStoragesServiceTest extends StoragesServiceTest {
return $this->makeStorageConfig([
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'authMechanismClass' => '\Auth\Mechanism',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
@@ -77,6 +78,7 @@ class UserStoragesServiceTest extends StoragesServiceTest {

$this->assertEquals($storage->getMountPoint(), $newStorage->getMountPoint());
$this->assertEquals($storage->getBackend(), $newStorage->getBackend());
$this->assertEquals($storage->getAuthMechanism(), $newStorage->getAuthMechanism());
$this->assertEquals($storage->getBackendOptions(), $newStorage->getBackendOptions());
$this->assertEquals(1, $newStorage->getId());
$this->assertEquals(0, $newStorage->getStatus());
@@ -99,6 +101,7 @@ class UserStoragesServiceTest extends StoragesServiceTest {
$storage = $this->makeStorageConfig([
'mountPoint' => 'mountpoint',
'backendClass' => '\OC\Files\Storage\SMB',
'authMechanismClass' => '\Auth\Mechanism',
'backendOptions' => [
'option1' => 'value1',
'option2' => 'value2',
@@ -192,6 +195,7 @@ class UserStoragesServiceTest extends StoragesServiceTest {
$mountPointOptions = current($mountPointData);
$this->assertEquals(1, $mountPointOptions['id']);
$this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']);
$this->assertEquals('\Auth\Mechanism', $mountPointOptions['authMechanism']);
$this->assertEquals(false, $mountPointOptions['mountOptions']['preview']);

$backendOptions = $mountPointOptions['options'];
@@ -215,12 +219,14 @@ class UserStoragesServiceTest extends StoragesServiceTest {

$legacyConfig = [
'class' => '\OC\Files\Storage\SMB',
'authMechanism' => '\Auth\Mechanism',
'options' => $legacyBackendOptions,
'mountOptions' => ['preview' => false],
];
// different mount options
$legacyConfig2 = [
'class' => '\OC\Files\Storage\SMB',
'authMechanism' => '\Auth\Mechanism',
'options' => $legacyBackendOptions,
'mountOptions' => ['preview' => true],
];

+ 8
- 0
apps/files_external/tests/storageconfigtest.php View File

@@ -32,9 +32,16 @@ class StorageConfigTest extends \Test\TestCase {
$backend->method('getClass')
->willReturn('\OC\Files\Storage\SMB');

$authMech = $this->getMockBuilder('\OCA\Files_External\Lib\Auth\AuthMechanism')
->disableOriginalConstructor()
->getMock();
$authMech->method('getClass')
->willReturn('\Auth\Mechanism');

$storageConfig = new StorageConfig(1);
$storageConfig->setMountPoint('test');
$storageConfig->setBackend($backend);
$storageConfig->setAuthMechanism($authMech);
$storageConfig->setBackendOptions(['user' => 'test', 'password' => 'password123']);
$storageConfig->setPriority(128);
$storageConfig->setApplicableUsers(['user1', 'user2']);
@@ -46,6 +53,7 @@ class StorageConfigTest extends \Test\TestCase {
$this->assertEquals(1, $json['id']);
$this->assertEquals('/test', $json['mountPoint']);
$this->assertEquals('\OC\Files\Storage\SMB', $json['backendClass']);
$this->assertEquals('\Auth\Mechanism', $json['authMechanismClass']);
$this->assertEquals('test', $json['backendOptions']['user']);
$this->assertEquals('password123', $json['backendOptions']['password']);
$this->assertEquals(128, $json['priority']);

Loading…
Cancel
Save