summaryrefslogtreecommitdiffstats
path: root/apps/files_external/lib/Service/StoragesService.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files_external/lib/Service/StoragesService.php')
-rw-r--r--apps/files_external/lib/Service/StoragesService.php527
1 files changed, 527 insertions, 0 deletions
diff --git a/apps/files_external/lib/Service/StoragesService.php b/apps/files_external/lib/Service/StoragesService.php
new file mode 100644
index 00000000000..0999c0a7adf
--- /dev/null
+++ b/apps/files_external/lib/Service/StoragesService.php
@@ -0,0 +1,527 @@
+<?php
+/**
+ * @author Jesús Macias <jmacias@solidgear.es>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Robin Appelman <icewind@owncloud.com>
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Files_External\Service;
+
+use \OC\Files\Filesystem;
+use \OCA\Files_external\Lib\StorageConfig;
+use \OCA\Files_external\NotFoundException;
+use \OCA\Files_External\Lib\Backend\Backend;
+use \OCA\Files_External\Lib\Auth\AuthMechanism;
+use OCP\Files\Config\IUserMountCache;
+use \OCP\Files\StorageNotAvailableException;
+
+/**
+ * Service class to manage external storages
+ */
+abstract class StoragesService {
+
+ /** @var BackendService */
+ protected $backendService;
+
+ /**
+ * @var DBConfigService
+ */
+ protected $dbConfig;
+
+ /**
+ * @var IUserMountCache
+ */
+ protected $userMountCache;
+
+ /**
+ * @param BackendService $backendService
+ * @param DBConfigService $dbConfigService
+ * @param IUserMountCache $userMountCache
+ */
+ public function __construct(BackendService $backendService, DBConfigService $dbConfigService, IUserMountCache $userMountCache) {
+ $this->backendService = $backendService;
+ $this->dbConfig = $dbConfigService;
+ $this->userMountCache = $userMountCache;
+ }
+
+ protected function readDBConfig() {
+ return $this->dbConfig->getAdminMounts();
+ }
+
+ protected function getStorageConfigFromDBMount(array $mount) {
+ $applicableUsers = array_filter($mount['applicable'], function ($applicable) {
+ return $applicable['type'] === DBConfigService::APPLICABLE_TYPE_USER;
+ });
+ $applicableUsers = array_map(function ($applicable) {
+ return $applicable['value'];
+ }, $applicableUsers);
+
+ $applicableGroups = array_filter($mount['applicable'], function ($applicable) {
+ return $applicable['type'] === DBConfigService::APPLICABLE_TYPE_GROUP;
+ });
+ $applicableGroups = array_map(function ($applicable) {
+ return $applicable['value'];
+ }, $applicableGroups);
+
+ try {
+ $config = $this->createStorage(
+ $mount['mount_point'],
+ $mount['storage_backend'],
+ $mount['auth_backend'],
+ $mount['config'],
+ $mount['options'],
+ array_values($applicableUsers),
+ array_values($applicableGroups),
+ $mount['priority']
+ );
+ $config->setType($mount['type']);
+ $config->setId((int)$mount['mount_id']);
+ return $config;
+ } catch (\UnexpectedValueException $e) {
+ // don't die if a storage backend doesn't exist
+ \OCP\Util::writeLog(
+ 'files_external',
+ 'Could not load storage: "' . $e->getMessage() . '"',
+ \OCP\Util::ERROR
+ );
+ return null;
+ } catch (\InvalidArgumentException $e) {
+ \OCP\Util::writeLog(
+ 'files_external',
+ 'Could not load storage: "' . $e->getMessage() . '"',
+ \OCP\Util::ERROR
+ );
+ return null;
+ }
+ }
+
+ /**
+ * Read the external storages config
+ *
+ * @return array map of storage id to storage config
+ */
+ protected function readConfig() {
+ $mounts = $this->readDBConfig();
+ $configs = array_map([$this, 'getStorageConfigFromDBMount'], $mounts);
+ $configs = array_filter($configs, function ($config) {
+ return $config instanceof StorageConfig;
+ });
+
+ $keys = array_map(function (StorageConfig $config) {
+ return $config->getId();
+ }, $configs);
+
+ return array_combine($keys, $configs);
+ }
+
+ /**
+ * Get a storage with status
+ *
+ * @param int $id storage id
+ *
+ * @return StorageConfig
+ * @throws NotFoundException if the storage with the given id was not found
+ */
+ public function getStorage($id) {
+ $mount = $this->dbConfig->getMountById($id);
+
+ if (!is_array($mount)) {
+ throw new NotFoundException('Storage with id "' . $id . '" not found');
+ }
+
+ $config = $this->getStorageConfigFromDBMount($mount);
+ if ($this->isApplicable($config)) {
+ return $config;
+ } else {
+ throw new NotFoundException('Storage with id "' . $id . '" not found');
+ }
+ }
+
+ /**
+ * Check whether this storage service should provide access to a storage
+ *
+ * @param StorageConfig $config
+ * @return bool
+ */
+ abstract protected function isApplicable(StorageConfig $config);
+
+ /**
+ * Gets all storages, valid or not
+ *
+ * @return StorageConfig[] array of storage configs
+ */
+ public function getAllStorages() {
+ return $this->readConfig();
+ }
+
+ /**
+ * Gets all valid storages
+ *
+ * @return StorageConfig[]
+ */
+ public function getStorages() {
+ return array_filter($this->getAllStorages(), [$this, 'validateStorage']);
+ }
+
+ /**
+ * Validate storage
+ * FIXME: De-duplicate with StoragesController::validate()
+ *
+ * @param StorageConfig $storage
+ * @return bool
+ */
+ protected function validateStorage(StorageConfig $storage) {
+ /** @var Backend */
+ $backend = $storage->getBackend();
+ /** @var AuthMechanism */
+ $authMechanism = $storage->getAuthMechanism();
+
+ if (!$backend->isVisibleFor($this->getVisibilityType())) {
+ // not permitted to use backend
+ return false;
+ }
+ if (!$authMechanism->isVisibleFor($this->getVisibilityType())) {
+ // not permitted to use auth mechanism
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the visibility type for this controller, used in validation
+ *
+ * @return string BackendService::VISIBILITY_* constants
+ */
+ abstract public function getVisibilityType();
+
+ /**
+ * @return integer
+ */
+ protected function getType() {
+ return DBConfigService::MOUNT_TYPE_ADMIN;
+ }
+
+ /**
+ * Add new storage to the configuration
+ *
+ * @param StorageConfig $newStorage storage attributes
+ *
+ * @return StorageConfig storage config, with added id
+ */
+ public function addStorage(StorageConfig $newStorage) {
+ $allStorages = $this->readConfig();
+
+ $configId = $this->dbConfig->addMount(
+ $newStorage->getMountPoint(),
+ $newStorage->getBackend()->getIdentifier(),
+ $newStorage->getAuthMechanism()->getIdentifier(),
+ $newStorage->getPriority(),
+ $this->getType()
+ );
+
+ $newStorage->setId($configId);
+
+ foreach ($newStorage->getApplicableUsers() as $user) {
+ $this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_USER, $user);
+ }
+ foreach ($newStorage->getApplicableGroups() as $group) {
+ $this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
+ }
+ foreach ($newStorage->getBackendOptions() as $key => $value) {
+ $this->dbConfig->setConfig($configId, $key, $value);
+ }
+ foreach ($newStorage->getMountOptions() as $key => $value) {
+ $this->dbConfig->setOption($configId, $key, $value);
+ }
+
+ if (count($newStorage->getApplicableUsers()) === 0 && count($newStorage->getApplicableGroups()) === 0) {
+ $this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
+ }
+
+ // add new storage
+ $allStorages[$configId] = $newStorage;
+
+ $this->triggerHooks($newStorage, Filesystem::signal_create_mount);
+
+ $newStorage->setStatus(StorageNotAvailableException::STATUS_SUCCESS);
+ return $newStorage;
+ }
+
+ /**
+ * Create a storage from its parameters
+ *
+ * @param string $mountPoint storage mount point
+ * @param string $backendIdentifier backend identifier
+ * @param string $authMechanismIdentifier authentication mechanism identifier
+ * @param array $backendOptions backend-specific options
+ * @param array|null $mountOptions mount-specific options
+ * @param array|null $applicableUsers users for which to mount the storage
+ * @param array|null $applicableGroups groups for which to mount the storage
+ * @param int|null $priority priority
+ *
+ * @return StorageConfig
+ */
+ public function createStorage(
+ $mountPoint,
+ $backendIdentifier,
+ $authMechanismIdentifier,
+ $backendOptions,
+ $mountOptions = null,
+ $applicableUsers = null,
+ $applicableGroups = null,
+ $priority = null
+ ) {
+ $backend = $this->backendService->getBackend($backendIdentifier);
+ if (!$backend) {
+ throw new \InvalidArgumentException('Unable to get backend for ' . $backendIdentifier);
+ }
+ $authMechanism = $this->backendService->getAuthMechanism($authMechanismIdentifier);
+ if (!$authMechanism) {
+ throw new \InvalidArgumentException('Unable to get authentication mechanism for ' . $authMechanismIdentifier);
+ }
+ $newStorage = new StorageConfig();
+ $newStorage->setMountPoint($mountPoint);
+ $newStorage->setBackend($backend);
+ $newStorage->setAuthMechanism($authMechanism);
+ $newStorage->setBackendOptions($backendOptions);
+ if (isset($mountOptions)) {
+ $newStorage->setMountOptions($mountOptions);
+ }
+ if (isset($applicableUsers)) {
+ $newStorage->setApplicableUsers($applicableUsers);
+ }
+ if (isset($applicableGroups)) {
+ $newStorage->setApplicableGroups($applicableGroups);
+ }
+ if (isset($priority)) {
+ $newStorage->setPriority($priority);
+ }
+
+ return $newStorage;
+ }
+
+ /**
+ * Triggers the given hook signal for all the applicables given
+ *
+ * @param string $signal signal
+ * @param string $mountPoint hook mount pount param
+ * @param string $mountType hook mount type param
+ * @param array $applicableArray array of applicable users/groups for which to trigger the hook
+ */
+ protected function triggerApplicableHooks($signal, $mountPoint, $mountType, $applicableArray) {
+ foreach ($applicableArray as $applicable) {
+ \OCP\Util::emitHook(
+ Filesystem::CLASSNAME,
+ $signal,
+ [
+ Filesystem::signal_param_path => $mountPoint,
+ Filesystem::signal_param_mount_type => $mountType,
+ Filesystem::signal_param_users => $applicable,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Triggers $signal for all applicable users of the given
+ * storage
+ *
+ * @param StorageConfig $storage storage data
+ * @param string $signal signal to trigger
+ */
+ abstract protected function triggerHooks(StorageConfig $storage, $signal);
+
+ /**
+ * Triggers signal_create_mount or signal_delete_mount to
+ * accommodate for additions/deletions in applicableUsers
+ * and applicableGroups fields.
+ *
+ * @param StorageConfig $oldStorage old storage data
+ * @param StorageConfig $newStorage new storage data
+ */
+ abstract protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $newStorage);
+
+ /**
+ * Update storage to the configuration
+ *
+ * @param StorageConfig $updatedStorage storage attributes
+ *
+ * @return StorageConfig storage config
+ * @throws NotFoundException if the given storage does not exist in the config
+ */
+ public function updateStorage(StorageConfig $updatedStorage) {
+ $id = $updatedStorage->getId();
+
+ $existingMount = $this->dbConfig->getMountById($id);
+
+ if (!is_array($existingMount)) {
+ throw new NotFoundException('Storage with id "' . $id . '" not found while updating storage');
+ }
+
+ $oldStorage = $this->getStorageConfigFromDBMount($existingMount);
+
+ $removedUsers = array_diff($oldStorage->getApplicableUsers(), $updatedStorage->getApplicableUsers());
+ $removedGroups = array_diff($oldStorage->getApplicableGroups(), $updatedStorage->getApplicableGroups());
+ $addedUsers = array_diff($updatedStorage->getApplicableUsers(), $oldStorage->getApplicableUsers());
+ $addedGroups = array_diff($updatedStorage->getApplicableGroups(), $oldStorage->getApplicableGroups());
+
+ $oldUserCount = count($oldStorage->getApplicableUsers());
+ $oldGroupCount = count($oldStorage->getApplicableGroups());
+ $newUserCount = count($updatedStorage->getApplicableUsers());
+ $newGroupCount = count($updatedStorage->getApplicableGroups());
+ $wasGlobal = ($oldUserCount + $oldGroupCount) === 0;
+ $isGlobal = ($newUserCount + $newGroupCount) === 0;
+
+ foreach ($removedUsers as $user) {
+ $this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_USER, $user);
+ }
+ foreach ($removedGroups as $group) {
+ $this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
+ }
+ foreach ($addedUsers as $user) {
+ $this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_USER, $user);
+ }
+ foreach ($addedGroups as $group) {
+ $this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
+ }
+
+ if ($wasGlobal && !$isGlobal) {
+ $this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
+ } else if (!$wasGlobal && $isGlobal) {
+ $this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
+ }
+
+ $changedConfig = array_diff_assoc($updatedStorage->getBackendOptions(), $oldStorage->getBackendOptions());
+ $changedOptions = array_diff_assoc($updatedStorage->getMountOptions(), $oldStorage->getMountOptions());
+
+ foreach ($changedConfig as $key => $value) {
+ $this->dbConfig->setConfig($id, $key, $value);
+ }
+ foreach ($changedOptions as $key => $value) {
+ $this->dbConfig->setOption($id, $key, $value);
+ }
+
+ if ($updatedStorage->getMountPoint() !== $oldStorage->getMountPoint()) {
+ $this->dbConfig->setMountPoint($id, $updatedStorage->getMountPoint());
+ }
+
+ if ($updatedStorage->getAuthMechanism()->getIdentifier() !== $oldStorage->getAuthMechanism()->getIdentifier()) {
+ $this->dbConfig->setAuthBackend($id, $updatedStorage->getAuthMechanism()->getIdentifier());
+ }
+
+ $this->triggerChangeHooks($oldStorage, $updatedStorage);
+
+ if (($wasGlobal && !$isGlobal) || count($removedGroups) > 0) { // to expensive to properly handle these on the fly
+ $this->userMountCache->remoteStorageMounts($this->getStorageId($updatedStorage));
+ } else {
+ $storageId = $this->getStorageId($updatedStorage);
+ foreach ($removedUsers as $userId) {
+ $this->userMountCache->removeUserStorageMount($storageId, $userId);
+ }
+ }
+
+ return $this->getStorage($id);
+ }
+
+ /**
+ * Delete the storage with the given id.
+ *
+ * @param int $id storage id
+ *
+ * @throws NotFoundException if no storage was found with the given id
+ */
+ public function removeStorage($id) {
+ $existingMount = $this->dbConfig->getMountById($id);
+
+ if (!is_array($existingMount)) {
+ throw new NotFoundException('Storage with id "' . $id . '" not found');
+ }
+
+ $this->dbConfig->removeMount($id);
+
+ $deletedStorage = $this->getStorageConfigFromDBMount($existingMount);
+ $this->triggerHooks($deletedStorage, Filesystem::signal_delete_mount);
+
+ // delete oc_storages entries and oc_filecache
+ try {
+ $rustyStorageId = $this->getRustyStorageIdFromConfig($deletedStorage);
+ \OC\Files\Cache\Storage::remove($rustyStorageId);
+ } catch (\Exception $e) {
+ // can happen either for invalid configs where the storage could not
+ // be instantiated or whenever $user vars where used, in which case
+ // the storage id could not be computed
+ \OCP\Util::writeLog(
+ 'files_external',
+ 'Exception: "' . $e->getMessage() . '"',
+ \OCP\Util::ERROR
+ );
+ }
+ }
+
+ /**
+ * Returns the rusty storage id from oc_storages from the given storage config.
+ *
+ * @param StorageConfig $storageConfig
+ * @return string rusty storage id
+ */
+ private function getRustyStorageIdFromConfig(StorageConfig $storageConfig) {
+ // if any of the storage options contains $user, it is not possible
+ // to compute the possible storage id as we don't know which users
+ // mounted it already (and we certainly don't want to iterate over ALL users)
+ foreach ($storageConfig->getBackendOptions() as $value) {
+ if (strpos($value, '$user') !== false) {
+ throw new \Exception('Cannot compute storage id for deletion due to $user vars in the configuration');
+ }
+ }
+
+ // note: similar to ConfigAdapter->prepateStorageConfig()
+ $storageConfig->getAuthMechanism()->manipulateStorageConfig($storageConfig);
+ $storageConfig->getBackend()->manipulateStorageConfig($storageConfig);
+
+ $class = $storageConfig->getBackend()->getStorageClass();
+ $storageImpl = new $class($storageConfig->getBackendOptions());
+
+ return $storageImpl->getId();
+ }
+
+ /**
+ * Construct the storage implementation
+ *
+ * @param StorageConfig $storageConfig
+ * @return int
+ */
+ private function getStorageId(StorageConfig $storageConfig) {
+ try {
+ $class = $storageConfig->getBackend()->getStorageClass();
+ /** @var \OC\Files\Storage\Storage $storage */
+ $storage = new $class($storageConfig->getBackendOptions());
+
+ // auth mechanism should fire first
+ $storage = $storageConfig->getBackend()->wrapStorage($storage);
+ $storage = $storageConfig->getAuthMechanism()->wrapStorage($storage);
+
+ return $storage->getStorageCache()->getNumericId();
+ } catch (\Exception $e) {
+ return -1;
+ }
+ }
+}