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']);