From 272a46ebe1a5e195a078dde74f5f2ad941923d9e Mon Sep 17 00:00:00 2001 From: Robin McCorkell Date: Wed, 12 Aug 2015 10:54:03 +0100 Subject: [PATCH] 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. --- apps/files_external/appinfo/application.php | 14 ++ .../controller/globalstoragescontroller.php | 6 + .../controller/storagescontroller.php | 20 ++- .../controller/userstoragescontroller.php | 6 + apps/files_external/js/settings.js | 107 ++++++++++----- .../files_external/lib/auth/authmechanism.php | 120 +++++++++++++++++ .../files_external/lib/auth/nullmechanism.php | 40 ++++++ apps/files_external/lib/backend/backend.php | 76 +++++++++++ apps/files_external/lib/config.php | 16 ++- .../lib/config/configadapter.php | 3 + apps/files_external/lib/storageconfig.php | 23 ++++ .../lib/storagemodifiertrait.php | 10 ++ apps/files_external/lib/visibilitytrait.php | 7 + apps/files_external/personal.php | 1 + .../files_external/service/backendservice.php | 91 +++++++++++++ .../service/storagesservice.php | 23 ++++ apps/files_external/settings.php | 1 + apps/files_external/templates/settings.php | 127 +++++++++++------- .../controller/storagescontrollertest.php | 38 +++++- .../controller/userstoragescontrollertest.php | 4 + apps/files_external/tests/js/settingsSpec.js | 22 ++- .../service/globalstoragesservicetest.php | 13 ++ .../tests/service/storagesservicetest.php | 44 ++++++ .../tests/service/userstoragesservicetest.php | 6 + .../tests/storageconfigtest.php | 8 ++ 25 files changed, 740 insertions(+), 86 deletions(-) create mode 100644 apps/files_external/lib/auth/authmechanism.php create mode 100644 apps/files_external/lib/auth/nullmechanism.php diff --git a/apps/files_external/appinfo/application.php b/apps/files_external/appinfo/application.php index b8b1fdaa27..19da1f724b 100644 --- a/apps/files_external/appinfo/application.php +++ b/apps/files_external/appinfo/application.php @@ -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'), + ]); + } + } diff --git a/apps/files_external/controller/globalstoragescontroller.php b/apps/files_external/controller/globalstoragescontroller.php index 11f7fd6afa..2c7b6af419 100644 --- a/apps/files_external/controller/globalstoragescontroller.php +++ b/apps/files_external/controller/globalstoragescontroller.php @@ -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, diff --git a/apps/files_external/controller/storagescontroller.php b/apps/files_external/controller/storagescontroller.php index c653b51bf8..5f3779dc8b 100644 --- a/apps/files_external/controller/storagescontroller.php +++ b/apps/files_external/controller/storagescontroller.php @@ -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); diff --git a/apps/files_external/controller/userstoragescontroller.php b/apps/files_external/controller/userstoragescontroller.php index 5a5bff7ba7..e72b51ff65 100644 --- a/apps/files_external/controller/userstoragescontroller.php +++ b/apps/files_external/controller/userstoragescontroller.php @@ -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 ); diff --git a/apps/files_external/js/settings.js b/apps/files_external/js/settings.js index 287b466454..7240c246ea 100644 --- a/apps/files_external/js/settings.js +++ b/apps/files_external/js/settings.js @@ -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. + */ + _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 = $(''); - } else if (placeholder.indexOf('!') === 0) { - newElement = $(''); - } else if (placeholder.indexOf('#') === 0) { - newElement = $(''); - } else { - var class_string = is_optional ? ' optional' : ''; - newElement = $(''); - } - highlightInput(newElement); - $td.append(newElement); - }); - var priorityEl = $(''); - $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 = $(''); + $.each(this._allAuthMechanisms, function(authClass, authMechanism) { + if (backendConfiguration['authSchemes'][authMechanism['scheme']]) { + selectAuthMechanism.append( + $('') + ); } }); + $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 = $(''); + $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 = $(''); + } else if (placeholder.indexOf('!') === 0) { + newElement = $(''); + } else if (placeholder.indexOf('#') === 0) { + newElement = $(''); + } else { + newElement = $(''); + } + 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'); diff --git a/apps/files_external/lib/auth/authmechanism.php b/apps/files_external/lib/auth/authmechanism.php new file mode 100644 index 0000000000..7da57662db --- /dev/null +++ b/apps/files_external/lib/auth/authmechanism.php @@ -0,0 +1,120 @@ + + * + * @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 + * + */ + +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); + } + +} diff --git a/apps/files_external/lib/auth/nullmechanism.php b/apps/files_external/lib/auth/nullmechanism.php new file mode 100644 index 0000000000..396649d731 --- /dev/null +++ b/apps/files_external/lib/auth/nullmechanism.php @@ -0,0 +1,40 @@ + + * + * @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 + * + */ + +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')) + ; + } + +} diff --git a/apps/files_external/lib/backend/backend.php b/apps/files_external/lib/backend/backend.php index e7cd27a1d6..634bcb7bfb 100644 --- a/apps/files_external/lib/backend/backend.php +++ b/apps/files_external/lib/backend/backend.php @@ -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; } diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index 11dec94621..e720677480 100644 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -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; diff --git a/apps/files_external/lib/config/configadapter.php b/apps/files_external/lib/config/configadapter.php index 63615e716a..9829629761 100644 --- a/apps/files_external/lib/config/configadapter.php +++ b/apps/files_external/lib/config/configadapter.php @@ -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; } diff --git a/apps/files_external/lib/storageconfig.php b/apps/files_external/lib/storageconfig.php index cf8271ff4e..96ba2f72ae 100644 --- a/apps/files_external/lib/storageconfig.php +++ b/apps/files_external/lib/storageconfig.php @@ -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; diff --git a/apps/files_external/lib/storagemodifiertrait.php b/apps/files_external/lib/storagemodifiertrait.php index f78116103d..a11d265e84 100644 --- a/apps/files_external/lib/storagemodifiertrait.php +++ b/apps/files_external/lib/storagemodifiertrait.php @@ -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 { diff --git a/apps/files_external/lib/visibilitytrait.php b/apps/files_external/lib/visibilitytrait.php index 06c95dd70c..dfd2d323ca 100644 --- a/apps/files_external/lib/visibilitytrait.php +++ b/apps/files_external/lib/visibilitytrait.php @@ -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 { diff --git a/apps/files_external/personal.php b/apps/files_external/personal.php index e204cdbeb9..fec1c195bb 100644 --- a/apps/files_external/personal.php +++ b/apps/files_external/personal.php @@ -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(); diff --git a/apps/files_external/service/backendservice.php b/apps/files_external/service/backendservice.php index f5859bc727..c1abbcf2b7 100644 --- a/apps/files_external/service/backendservice.php +++ b/apps/files_external/service/backendservice.php @@ -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 + } } diff --git a/apps/files_external/service/storagesservice.php b/apps/files_external/service/storagesservice.php index d3bde0ae96..b8a1824ba2 100644 --- a/apps/files_external/service/storagesservice.php +++ b/apps/files_external/service/storagesservice.php @@ -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); diff --git a/apps/files_external/settings.php b/apps/files_external/settings.php index 7c53db4c0d..7e20af0c60 100644 --- a/apps/files_external/settings.php +++ b/apps/files_external/settings.php @@ -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()); diff --git a/apps/files_external/templates/settings.php b/apps/files_external/templates/settings.php index f931c62eec..2328ea9565 100644 --- a/apps/files_external/templates/settings.php +++ b/apps/files_external/templates/settings.php @@ -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: ?> + + class="" + data-parameter="getName()); ?>" + value="" + placeholder="" + /> + + + + class="" + data-parameter="getName()); ?>" + value="" + /> + + + class="" + data-parameter="getName()); ?>" + value="" + placeholder="" + /> +

t('External Storage')); ?>

@@ -12,6 +62,7 @@ t('Folder name')); ?> t('External storage')); ?> + t('Authentication')); ?> t('Configuration')); ?> '.$l->t('Available for').''); ?>   @@ -31,60 +82,39 @@ getBackend()->getText()); ?> - - getBackendOptions(); ?> - getBackend()->getParameters() as $parameter): ?> + + class="optional" - data-parameter="getName()); ?>" - value="" - placeholder="" - /> - - - - - - class="optional" - data-parameter="getName()); ?>" - value="" - placeholder="" - /> - getBackend()->getAuthSchemes(); + $authMechanisms = array_filter($_['authMechanisms'], function($mech) use ($authSchemes) { + return isset($authSchemes[$mech->getScheme()]); + }); ?> - + + + + + + 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); + } ?> @@ -140,7 +170,8 @@ - + '> + diff --git a/apps/files_external/tests/controller/storagescontrollertest.php b/apps/files_external/tests/controller/storagescontrollertest.php index f3e8c9afba..735e760c09 100644 --- a/apps/files_external/tests/controller/storagescontrollertest.php +++ b/apps/files_external/tests/controller/storagescontrollertest.php @@ -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]); diff --git a/apps/files_external/tests/controller/userstoragescontrollertest.php b/apps/files_external/tests/controller/userstoragescontrollertest.php index 99825f2639..9f1a8df8d2 100644 --- a/apps/files_external/tests/controller/userstoragescontrollertest.php +++ b/apps/files_external/tests/controller/userstoragescontrollertest.php @@ -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(), [], [], diff --git a/apps/files_external/tests/js/settingsSpec.js b/apps/files_external/tests/js/settingsSpec.js index 7cb86d7270..67a8127712 100644 --- a/apps/files_external/tests/js/settingsSpec.js +++ b/apps/files_external/tests/js/settingsSpec.js @@ -39,6 +39,7 @@ describe('OCA.External.Settings tests', function() { '' + '' + '' + + '' + '' + '' + '' + @@ -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': '' diff --git a/apps/files_external/tests/service/globalstoragesservicetest.php b/apps/files_external/tests/service/globalstoragesservicetest.php index 422f3543f3..d5f99431a5 100644 --- a/apps/files_external/tests/service/globalstoragesservicetest.php +++ b/apps/files_external/tests/service/globalstoragesservicetest.php @@ -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], ]; diff --git a/apps/files_external/tests/service/storagesservicetest.php b/apps/files_external/tests/service/storagesservicetest.php index 99e179cc93..1429fb1818 100644 --- a/apps/files_external/tests/service/storagesservicetest.php +++ b/apps/files_external/tests/service/storagesservicetest.php @@ -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); diff --git a/apps/files_external/tests/service/userstoragesservicetest.php b/apps/files_external/tests/service/userstoragesservicetest.php index 98a9993918..1e57eedd32 100644 --- a/apps/files_external/tests/service/userstoragesservicetest.php +++ b/apps/files_external/tests/service/userstoragesservicetest.php @@ -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], ]; diff --git a/apps/files_external/tests/storageconfigtest.php b/apps/files_external/tests/storageconfigtest.php index 69edb36e70..8039990999 100644 --- a/apps/files_external/tests/storageconfigtest.php +++ b/apps/files_external/tests/storageconfigtest.php @@ -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']);