diff --git a/apps/files_external/appinfo/info.xml b/apps/files_external/appinfo/info.xml index e86a5e459d1a..f6d583d0a5ad 100644 --- a/apps/files_external/appinfo/info.xml +++ b/apps/files_external/appinfo/info.xml @@ -14,7 +14,7 @@ admin-external-storage false - 0.4.2 + 0.5.0 diff --git a/apps/files_external/appinfo/update.php b/apps/files_external/appinfo/update.php new file mode 100644 index 000000000000..2eedfe9b88fb --- /dev/null +++ b/apps/files_external/appinfo/update.php @@ -0,0 +1,30 @@ + + * + * @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 + * + */ + +$installedVersion = \OC::$server->getConfig()->getAppValue('files_external', 'installed_version'); + +$app = new \OCA\Files_external\Appinfo\Application(); + +// Migration to db config +if (version_compare($installedVersion, '0.5.0', '<')) { + $migrator = $app->getContainer()->query('OCA\Files_external\Migration\StorageMigrator'); + $migrator->migrateGlobal(); +} diff --git a/apps/files_external/lib/config/configadapter.php b/apps/files_external/lib/config/configadapter.php index 3a04512e8a80..ab0a66a3c51c 100644 --- a/apps/files_external/lib/config/configadapter.php +++ b/apps/files_external/lib/config/configadapter.php @@ -23,6 +23,7 @@ namespace OCA\Files_External\Config; +use OCA\Files_external\Migration\StorageMigrator; use OCP\Files\Storage; use OC\Files\Mount\MountPoint; use OCP\Files\Storage\IStorageFactory; @@ -45,17 +46,22 @@ class ConfigAdapter implements IMountProvider { /** @var UserGlobalStoragesService */ private $userGlobalStoragesService; + /** @var StorageMigrator */ + private $migrator; /** * @param UserStoragesService $userStoragesService * @param UserGlobalStoragesService $userGlobalStoragesService + * @param StorageMigrator $migrator */ public function __construct( UserStoragesService $userStoragesService, - UserGlobalStoragesService $userGlobalStoragesService + UserGlobalStoragesService $userGlobalStoragesService, + StorageMigrator $migrator ) { $this->userStoragesService = $userStoragesService; $this->userGlobalStoragesService = $userGlobalStoragesService; + $this->migrator = $migrator; } /** @@ -109,6 +115,8 @@ private function constructStorage(StorageConfig $storageConfig) { * @return \OCP\Files\Mount\IMountPoint[] */ public function getMountsForUser(IUser $user, IStorageFactory $loader) { + $this->migrator->migrateUser(); + $mounts = []; $this->userStoragesService->setUser($user); @@ -125,7 +133,7 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) { $mount = new MountPoint( $impl, - '/'.$user->getUID().'/files' . $storage->getMountPoint(), + '/' . $user->getUID() . '/files' . $storage->getMountPoint(), null, $loader, $storage->getMountOptions() @@ -146,7 +154,7 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) { $this->userStoragesService, $storage->getId(), $impl, - '/'.$user->getUID().'/files' . $storage->getMountPoint(), + '/' . $user->getUID() . '/files' . $storage->getMountPoint(), null, $loader, $storage->getMountOptions() diff --git a/apps/files_external/migration/storagemigrator.php b/apps/files_external/migration/storagemigrator.php new file mode 100644 index 000000000000..931b0210779e --- /dev/null +++ b/apps/files_external/migration/storagemigrator.php @@ -0,0 +1,108 @@ + + * + * @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\Migration; + +use OCA\Files_External\Service\BackendService; +use OCA\Files_External\Service\DBConfigService; +use OCA\Files_external\Service\GlobalLegacyStoragesService; +use OCA\Files_external\Service\GlobalStoragesService; +use OCA\Files_external\Service\LegacyStoragesService; +use OCA\Files_external\Service\StoragesService; +use OCA\Files_external\Service\UserLegacyStoragesService; +use OCA\Files_external\Service\UserStoragesService; +use OCP\IConfig; +use OCP\IUserSession; + +class StorageMigrator { + /** + * @var BackendService + */ + private $backendService; + + /** + * @var DBConfigService + */ + private $dbConfig; + + /** + * @var IUserSession + */ + private $userSession; + + /** + * @var IConfig + */ + private $config; + + /** + * StorageMigrator constructor. + * + * @param BackendService $backendService + * @param DBConfigService $dbConfig + * @param IUserSession $userSession + * @param IConfig $config + */ + public function __construct( + BackendService $backendService, + DBConfigService $dbConfig, + IUserSession $userSession, + IConfig $config + ) { + $this->backendService = $backendService; + $this->dbConfig = $dbConfig; + $this->userSession = $userSession; + $this->config = $config; + } + + private function migrate(LegacyStoragesService $legacyService, StoragesService $storageService) { + $existingStorage = $legacyService->getAllStorages(); + + foreach ($existingStorage as $storage) { + $storageService->addStorage($storage); + } + } + + /** + * Migrate admin configured storages + */ + public function migrateGlobal() { + $legacyService = new GlobalLegacyStoragesService($this->backendService); + $storageService = new GlobalStoragesService($this->backendService, $this->dbConfig); + + $this->migrate($legacyService, $storageService); + } + + /** + * Migrate personal storages configured by the current user + */ + public function migrateUser() { + $userId = $this->userSession->getUser()->getUID(); + $userVersion = $this->config->getUserValue($userId, 'files_external', 'config_version', '0.0.0'); + if (version_compare($userVersion, '0.5.0', '<')) { + $this->config->setUserValue($userId, 'files_external', 'config_version', '0.5.0'); + $legacyService = new UserLegacyStoragesService($this->backendService, $this->userSession); + $storageService = new UserStoragesService($this->backendService, $this->dbConfig, $this->userSession); + + $this->migrate($legacyService, $storageService); + } + } +} diff --git a/apps/files_external/service/globallegacystoragesservice.php b/apps/files_external/service/globallegacystoragesservice.php new file mode 100644 index 000000000000..eb41806dffcb --- /dev/null +++ b/apps/files_external/service/globallegacystoragesservice.php @@ -0,0 +1,41 @@ + + * + * @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\Service; + +class GlobalLegacyStoragesService extends LegacyStoragesService { + /** + * @param BackendService $backendService + */ + public function __construct(BackendService $backendService) { + $this->backendService = $backendService; + } + + /** + * Read legacy config data + * + * @return array list of mount configs + */ + protected function readLegacyConfig() { + // read global config + return \OC_Mount_Config::readData(); + } +} diff --git a/apps/files_external/service/legacystoragesservice.php b/apps/files_external/service/legacystoragesservice.php new file mode 100644 index 000000000000..c9ffa83f98c2 --- /dev/null +++ b/apps/files_external/service/legacystoragesservice.php @@ -0,0 +1,209 @@ + + * + * @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\Service; + +use \OCA\Files_external\Lib\StorageConfig; + +/** + * Service class to manage external storages + */ +abstract class LegacyStoragesService { + /** @var BackendService */ + protected $backendService; + + /** + * Read legacy config data + * + * @return array list of mount configs + */ + abstract protected function readLegacyConfig(); + + /** + * Copy legacy storage options into the given storage config object. + * + * @param StorageConfig $storageConfig storage config to populate + * @param string $mountType mount type + * @param string $applicable applicable user or group + * @param array $storageOptions legacy storage options + * + * @return StorageConfig populated storage config + */ + protected function populateStorageConfigWithLegacyOptions( + &$storageConfig, + $mountType, + $applicable, + $storageOptions + ) { + $backend = $this->backendService->getBackend($storageOptions['backend']); + if (!$backend) { + throw new \UnexpectedValueException('Invalid backend ' . $storageOptions['backend']); + } + $storageConfig->setBackend($backend); + if (isset($storageOptions['authMechanism']) && $storageOptions['authMechanism'] !== 'builtin::builtin') { + $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 ' . $storageOptions['authMechanism']); + } + $storageConfig->setAuthMechanism($authMechanism); + $storageConfig->setBackendOptions($storageOptions['options']); + if (isset($storageOptions['mountOptions'])) { + $storageConfig->setMountOptions($storageOptions['mountOptions']); + } + if (!isset($storageOptions['priority'])) { + $storageOptions['priority'] = $backend->getPriority(); + } + $storageConfig->setPriority($storageOptions['priority']); + if ($mountType === \OC_Mount_Config::MOUNT_TYPE_USER) { + $applicableUsers = $storageConfig->getApplicableUsers(); + if ($applicable !== 'all') { + $applicableUsers[] = $applicable; + $storageConfig->setApplicableUsers($applicableUsers); + } + } else if ($mountType === \OC_Mount_Config::MOUNT_TYPE_GROUP) { + $applicableGroups = $storageConfig->getApplicableGroups(); + $applicableGroups[] = $applicable; + $storageConfig->setApplicableGroups($applicableGroups); + } + return $storageConfig; + } + + /** + * Read the external storages config + * + * @return StorageConfig[] map of storage id to storage config + */ + public function getAllStorages() { + $mountPoints = $this->readLegacyConfig(); + /** + * Here is the how the horribly messy mount point array looks like + * from the mount.json file: + * + * $storageOptions = $mountPoints[$mountType][$applicable][$mountPath] + * + * - $mountType is either "user" or "group" + * - $applicable is the name of a user or group (or the current user for personal mounts) + * - $mountPath is the mount point path (where the storage must be mounted) + * - $storageOptions is a map of storage options: + * - "priority": storage priority + * - "backend": backend identifier + * - "class": LEGACY backend class name + * - "options": backend-specific options + * - "authMechanism": authentication mechanism identifier + * - "mountOptions": mount-specific options (ex: disable previews, scanner, etc) + */ + // group by storage id + /** @var StorageConfig[] $storages */ + $storages = []; + // for storages without id (legacy), group by config hash for + // later processing + $storagesWithConfigHash = []; + foreach ($mountPoints as $mountType => $applicables) { + foreach ($applicables as $applicable => $mountPaths) { + foreach ($mountPaths as $rootMountPath => $storageOptions) { + $currentStorage = null; + /** + * Flag whether the config that was read already has an id. + * If not, it will use a config hash instead and generate + * a proper id later + * + * @var boolean + */ + $hasId = false; + // the root mount point is in the format "/$user/files/the/mount/point" + // we remove the "/$user/files" prefix + $parts = explode('/', ltrim($rootMountPath, '/'), 3); + if (count($parts) < 3) { + // something went wrong, skip + \OCP\Util::writeLog( + 'files_external', + 'Could not parse mount point "' . $rootMountPath . '"', + \OCP\Util::ERROR + ); + continue; + } + $relativeMountPath = rtrim($parts[2], '/'); + // note: we cannot do this after the loop because the decrypted config + // options might be needed for the config hash + $storageOptions['options'] = \OC_Mount_Config::decryptPasswords($storageOptions['options']); + if (!isset($storageOptions['backend'])) { + $storageOptions['backend'] = $storageOptions['class']; // legacy compat + } + if (!isset($storageOptions['authMechanism'])) { + $storageOptions['authMechanism'] = null; // ensure config hash works + } + if (isset($storageOptions['id'])) { + $configId = (int)$storageOptions['id']; + if (isset($storages[$configId])) { + $currentStorage = $storages[$configId]; + } + $hasId = true; + } else { + // missing id in legacy config, need to generate + // but at this point we don't know the max-id, so use + // first group it by config hash + $storageOptions['mountpoint'] = $rootMountPath; + $configId = \OC_Mount_Config::makeConfigHash($storageOptions); + if (isset($storagesWithConfigHash[$configId])) { + $currentStorage = $storagesWithConfigHash[$configId]; + } + } + if (is_null($currentStorage)) { + // create new + $currentStorage = new StorageConfig($configId); + $currentStorage->setMountPoint($relativeMountPath); + } + try { + $this->populateStorageConfigWithLegacyOptions( + $currentStorage, + $mountType, + $applicable, + $storageOptions + ); + if ($hasId) { + $storages[$configId] = $currentStorage; + } else { + $storagesWithConfigHash[$configId] = $currentStorage; + } + } catch (\UnexpectedValueException $e) { + // dont die if a storage backend doesn't exist + \OCP\Util::writeLog( + 'files_external', + 'Could not load storage: "' . $e->getMessage() . '"', + \OCP\Util::ERROR + ); + } + } + } + } + + // convert parameter values + foreach ($storages as $storage) { + $storage->getBackend()->validateStorageDefinition($storage); + $storage->getAuthMechanism()->validateStorageDefinition($storage); + } + return $storages; + } +} diff --git a/apps/files_external/service/userlegacystoragesservice.php b/apps/files_external/service/userlegacystoragesservice.php new file mode 100644 index 000000000000..441fc58806d6 --- /dev/null +++ b/apps/files_external/service/userlegacystoragesservice.php @@ -0,0 +1,51 @@ + + * + * @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\Service; + +use OCP\IUserSession; + +class UserLegacyStoragesService extends LegacyStoragesService { + /** + * @var IUserSession + */ + private $userSession; + + /** + * @param BackendService $backendService + * @param IUserSession $userSession + */ + public function __construct(BackendService $backendService, IUserSession $userSession) { + $this->backendService = $backendService; + $this->userSession = $userSession; + } + + /** + * Read legacy config data + * + * @return array list of storage configs + */ + protected function readLegacyConfig() { + // read user config + $user = $this->userSession->getUser()->getUID(); + return \OC_Mount_Config::readData($user); + } +}