summaryrefslogtreecommitdiffstats
path: root/apps/files_external/lib/Service
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files_external/lib/Service')
-rw-r--r--apps/files_external/lib/Service/BackendService.php282
-rw-r--r--apps/files_external/lib/Service/DBConfigService.php451
-rw-r--r--apps/files_external/lib/Service/GlobalLegacyStoragesService.php44
-rw-r--r--apps/files_external/lib/Service/GlobalStoragesService.php165
-rw-r--r--apps/files_external/lib/Service/ImportLegacyStoragesService.php46
-rw-r--r--apps/files_external/lib/Service/LegacyStoragesService.php209
-rw-r--r--apps/files_external/lib/Service/StoragesService.php527
-rw-r--r--apps/files_external/lib/Service/UserGlobalStoragesService.php174
-rw-r--r--apps/files_external/lib/Service/UserLegacyStoragesService.php54
-rw-r--r--apps/files_external/lib/Service/UserStoragesService.php141
-rw-r--r--apps/files_external/lib/Service/UserTrait.php74
11 files changed, 2167 insertions, 0 deletions
diff --git a/apps/files_external/lib/Service/BackendService.php b/apps/files_external/lib/Service/BackendService.php
new file mode 100644
index 00000000000..c3dc4da0177
--- /dev/null
+++ b/apps/files_external/lib/Service/BackendService.php
@@ -0,0 +1,282 @@
+<?php
+/**
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ *
+ * @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 \OCP\IConfig;
+
+use \OCA\Files_External\Lib\Backend\Backend;
+use \OCA\Files_External\Lib\Auth\AuthMechanism;
+use \OCA\Files_External\Lib\Config\IBackendProvider;
+use \OCA\Files_External\Lib\Config\IAuthMechanismProvider;
+
+/**
+ * Service class to manage backend definitions
+ */
+class BackendService {
+
+ /** Visibility constants for VisibilityTrait */
+ const VISIBILITY_NONE = 0;
+ const VISIBILITY_PERSONAL = 1;
+ const VISIBILITY_ADMIN = 2;
+ //const VISIBILITY_ALIENS = 4;
+
+ const VISIBILITY_DEFAULT = 3; // PERSONAL | ADMIN
+
+ /** Priority constants for PriorityTrait */
+ const PRIORITY_DEFAULT = 100;
+
+ /** @var IConfig */
+ protected $config;
+
+ /** @var bool */
+ private $userMountingAllowed = true;
+
+ /** @var string[] */
+ private $userMountingBackends = [];
+
+ /** @var Backend[] */
+ private $backends = [];
+
+ /** @var IBackendProvider[] */
+ private $backendProviders = [];
+
+ /** @var AuthMechanism[] */
+ private $authMechanisms = [];
+
+ /** @var IAuthMechanismProvider[] */
+ private $authMechanismProviders = [];
+
+ /**
+ * @param IConfig $config
+ */
+ public function __construct(
+ IConfig $config
+ ) {
+ $this->config = $config;
+
+ // Load config values
+ if ($this->config->getAppValue('files_external', 'allow_user_mounting', 'yes') !== 'yes') {
+ $this->userMountingAllowed = false;
+ }
+ $this->userMountingBackends = explode(',',
+ $this->config->getAppValue('files_external', 'user_mounting_backends', '')
+ );
+
+ // if no backend is in the list an empty string is in the array and user mounting is disabled
+ if ($this->userMountingBackends === ['']) {
+ $this->userMountingAllowed = false;
+ }
+ }
+
+ /**
+ * Register a backend provider
+ *
+ * @since 9.1.0
+ * @param IBackendProvider $provider
+ */
+ public function registerBackendProvider(IBackendProvider $provider) {
+ $this->backendProviders[] = $provider;
+ }
+
+ private function loadBackendProviders() {
+ foreach ($this->backendProviders as $provider) {
+ $this->registerBackends($provider->getBackends());
+ }
+ $this->backendProviders = [];
+ }
+
+ /**
+ * Register an auth mechanism provider
+ *
+ * @since 9.1.0
+ * @param IAuthMechanismProvider $provider
+ */
+ public function registerAuthMechanismProvider(IAuthMechanismProvider $provider) {
+ $this->authMechanismProviders[] = $provider;
+ }
+
+ private function loadAuthMechanismProviders() {
+ foreach ($this->authMechanismProviders as $provider) {
+ $this->registerAuthMechanisms($provider->getAuthMechanisms());
+ }
+ $this->authMechanismProviders = [];
+ }
+
+ /**
+ * Register a backend
+ *
+ * @deprecated 9.1.0 use registerBackendProvider()
+ * @param Backend $backend
+ */
+ public function registerBackend(Backend $backend) {
+ if (!$this->isAllowedUserBackend($backend)) {
+ $backend->removeVisibility(BackendService::VISIBILITY_PERSONAL);
+ }
+ foreach ($backend->getIdentifierAliases() as $alias) {
+ $this->backends[$alias] = $backend;
+ }
+ }
+
+ /**
+ * @deprecated 9.1.0 use registerBackendProvider()
+ * @param Backend[] $backends
+ */
+ public function registerBackends(array $backends) {
+ foreach ($backends as $backend) {
+ $this->registerBackend($backend);
+ }
+ }
+ /**
+ * Register an authentication mechanism
+ *
+ * @deprecated 9.1.0 use registerAuthMechanismProvider()
+ * @param AuthMechanism $authMech
+ */
+ public function registerAuthMechanism(AuthMechanism $authMech) {
+ if (!$this->isAllowedAuthMechanism($authMech)) {
+ $authMech->removeVisibility(BackendService::VISIBILITY_PERSONAL);
+ }
+ foreach ($authMech->getIdentifierAliases() as $alias) {
+ $this->authMechanisms[$alias] = $authMech;
+ }
+ }
+
+ /**
+ * @deprecated 9.1.0 use registerAuthMechanismProvider()
+ * @param AuthMechanism[] $mechanisms
+ */
+ public function registerAuthMechanisms(array $mechanisms) {
+ foreach ($mechanisms as $mechanism) {
+ $this->registerAuthMechanism($mechanism);
+ }
+ }
+
+ /**
+ * Get all backends
+ *
+ * @return Backend[]
+ */
+ public function getBackends() {
+ $this->loadBackendProviders();
+ // only return real identifiers, no aliases
+ $backends = [];
+ foreach ($this->backends as $backend) {
+ $backends[$backend->getIdentifier()] = $backend;
+ }
+ return $backends;
+ }
+
+ /**
+ * Get all available backends
+ *
+ * @return Backend[]
+ */
+ public function getAvailableBackends() {
+ return array_filter($this->getBackends(), function($backend) {
+ return !($backend->checkDependencies());
+ });
+ }
+
+ /**
+ * @param string $identifier
+ * @return Backend|null
+ */
+ public function getBackend($identifier) {
+ $this->loadBackendProviders();
+ if (isset($this->backends[$identifier])) {
+ return $this->backends[$identifier];
+ }
+ return null;
+ }
+
+ /**
+ * Get all authentication mechanisms
+ *
+ * @return AuthMechanism[]
+ */
+ public function getAuthMechanisms() {
+ $this->loadAuthMechanismProviders();
+ // only return real identifiers, no aliases
+ $mechanisms = [];
+ foreach ($this->authMechanisms as $mechanism) {
+ $mechanisms[$mechanism->getIdentifier()] = $mechanism;
+ }
+ return $mechanisms;
+ }
+
+ /**
+ * 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);
+ });
+ }
+
+ /**
+ * @param string $identifier
+ * @return AuthMechanism|null
+ */
+ public function getAuthMechanism($identifier) {
+ $this->loadAuthMechanismProviders();
+ if (isset($this->authMechanisms[$identifier])) {
+ return $this->authMechanisms[$identifier];
+ }
+ return null;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isUserMountingAllowed() {
+ return $this->userMountingAllowed;
+ }
+
+ /**
+ * Check a backend if a user is allowed to mount it
+ *
+ * @param Backend $backend
+ * @return bool
+ */
+ protected function isAllowedUserBackend(Backend $backend) {
+ if ($this->userMountingAllowed &&
+ array_intersect($backend->getIdentifierAliases(), $this->userMountingBackends)
+ ) {
+ return true;
+ }
+ 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/lib/Service/DBConfigService.php b/apps/files_external/lib/Service/DBConfigService.php
new file mode 100644
index 00000000000..9f7061eb938
--- /dev/null
+++ b/apps/files_external/lib/Service/DBConfigService.php
@@ -0,0 +1,451 @@
+<?php
+/**
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ * @author Robin Appelman <icewind@owncloud.com>
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ *
+ * @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 OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+use OCP\Security\ICrypto;
+
+/**
+ * Stores the mount config in the database
+ */
+class DBConfigService {
+ const MOUNT_TYPE_ADMIN = 1;
+ const MOUNT_TYPE_PERSONAl = 2;
+
+ const APPLICABLE_TYPE_GLOBAL = 1;
+ const APPLICABLE_TYPE_GROUP = 2;
+ const APPLICABLE_TYPE_USER = 3;
+
+ /**
+ * @var IDBConnection
+ */
+ private $connection;
+
+ /**
+ * @var ICrypto
+ */
+ private $crypto;
+
+ /**
+ * DBConfigService constructor.
+ *
+ * @param IDBConnection $connection
+ * @param ICrypto $crypto
+ */
+ public function __construct(IDBConnection $connection, ICrypto $crypto) {
+ $this->connection = $connection;
+ $this->crypto = $crypto;
+ }
+
+ /**
+ * @param int $mountId
+ * @return array
+ */
+ public function getMountById($mountId) {
+ $builder = $this->connection->getQueryBuilder();
+ $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
+ ->from('external_mounts', 'm')
+ ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
+ $mounts = $this->getMountsFromQuery($query);
+ if (count($mounts) > 0) {
+ return $mounts[0];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get admin defined mounts
+ *
+ * @return array
+ */
+ public function getAdminMounts() {
+ $builder = $this->connection->getQueryBuilder();
+ $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
+ ->from('external_mounts')
+ ->where($builder->expr()->eq('type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
+ return $this->getMountsFromQuery($query);
+ }
+
+ protected function getForQuery(IQueryBuilder $builder, $type, $value) {
+ $query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
+ ->from('external_mounts', 'm')
+ ->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
+ ->where($builder->expr()->eq('a.type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
+
+ if (is_null($value)) {
+ $query = $query->andWhere($builder->expr()->isNull('a.value'));
+ } else {
+ $query = $query->andWhere($builder->expr()->eq('a.value', $builder->createNamedParameter($value)));
+ }
+
+ return $query;
+ }
+
+ /**
+ * Get mounts by applicable
+ *
+ * @param int $type any of the self::APPLICABLE_TYPE_ constants
+ * @param string|null $value user_id, group_id or null for global mounts
+ * @return array
+ */
+ public function getMountsFor($type, $value) {
+ $builder = $this->connection->getQueryBuilder();
+ $query = $this->getForQuery($builder, $type, $value);
+
+ return $this->getMountsFromQuery($query);
+ }
+
+ /**
+ * Get admin defined mounts by applicable
+ *
+ * @param int $type any of the self::APPLICABLE_TYPE_ constants
+ * @param string|null $value user_id, group_id or null for global mounts
+ * @return array
+ */
+ public function getAdminMountsFor($type, $value) {
+ $builder = $this->connection->getQueryBuilder();
+ $query = $this->getForQuery($builder, $type, $value);
+ $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
+
+ return $this->getMountsFromQuery($query);
+ }
+
+ /**
+ * Get admin defined mounts for multiple applicable
+ *
+ * @param int $type any of the self::APPLICABLE_TYPE_ constants
+ * @param string[] $values user_ids or group_ids
+ * @return array
+ */
+ public function getAdminMountsForMultiple($type, array $values) {
+ $builder = $this->connection->getQueryBuilder();
+ $params = array_map(function ($value) use ($builder) {
+ return $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR);
+ }, $values);
+
+ $query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
+ ->from('external_mounts', 'm')
+ ->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
+ ->where($builder->expr()->eq('a.type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
+ ->andWhere($builder->expr()->in('a.value', $params));
+ $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
+
+ return $this->getMountsFromQuery($query);
+ }
+
+ /**
+ * Get user defined mounts by applicable
+ *
+ * @param int $type any of the self::APPLICABLE_TYPE_ constants
+ * @param string|null $value user_id, group_id or null for global mounts
+ * @return array
+ */
+ public function getUserMountsFor($type, $value) {
+ $builder = $this->connection->getQueryBuilder();
+ $query = $this->getForQuery($builder, $type, $value);
+ $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_PERSONAl, IQueryBuilder::PARAM_INT)));
+
+ return $this->getMountsFromQuery($query);
+ }
+
+ /**
+ * Add a mount to the database
+ *
+ * @param string $mountPoint
+ * @param string $storageBackend
+ * @param string $authBackend
+ * @param int $priority
+ * @param int $type self::MOUNT_TYPE_ADMIN or self::MOUNT_TYPE_PERSONAL
+ * @return int the id of the new mount
+ */
+ public function addMount($mountPoint, $storageBackend, $authBackend, $priority, $type) {
+ if (!$priority) {
+ $priority = 100;
+ }
+ $builder = $this->connection->getQueryBuilder();
+ $query = $builder->insert('external_mounts')
+ ->values([
+ 'mount_point' => $builder->createNamedParameter($mountPoint, IQueryBuilder::PARAM_STR),
+ 'storage_backend' => $builder->createNamedParameter($storageBackend, IQueryBuilder::PARAM_STR),
+ 'auth_backend' => $builder->createNamedParameter($authBackend, IQueryBuilder::PARAM_STR),
+ 'priority' => $builder->createNamedParameter($priority, IQueryBuilder::PARAM_INT),
+ 'type' => $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)
+ ]);
+ $query->execute();
+ return (int)$this->connection->lastInsertId('external_mounts');
+ }
+
+ /**
+ * Remove a mount from the database
+ *
+ * @param int $mountId
+ */
+ public function removeMount($mountId) {
+ $builder = $this->connection->getQueryBuilder();
+ $query = $builder->delete('external_mounts')
+ ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
+ $query->execute();
+
+ $query = $builder->delete('external_applicable')
+ ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
+ $query->execute();
+
+ $query = $builder->delete('external_config')
+ ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
+ $query->execute();
+
+ $query = $builder->delete('external_options')
+ ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
+ $query->execute();
+ }
+
+ /**
+ * @param int $mountId
+ * @param string $newMountPoint
+ */
+ public function setMountPoint($mountId, $newMountPoint) {
+ $builder = $this->connection->getQueryBuilder();
+
+ $query = $builder->update('external_mounts')
+ ->set('mount_point', $builder->createNamedParameter($newMountPoint))
+ ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
+
+ $query->execute();
+ }
+
+ /**
+ * @param int $mountId
+ * @param string $newAuthBackend
+ */
+ public function setAuthBackend($mountId, $newAuthBackend) {
+ $builder = $this->connection->getQueryBuilder();
+
+ $query = $builder->update('external_mounts')
+ ->set('auth_backend', $builder->createNamedParameter($newAuthBackend))
+ ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
+
+ $query->execute();
+ }
+
+ /**
+ * @param int $mountId
+ * @param string $key
+ * @param string $value
+ */
+ public function setConfig($mountId, $key, $value) {
+ if ($key === 'password') {
+ $value = $this->encryptValue($value);
+ }
+ $count = $this->connection->insertIfNotExist('*PREFIX*external_config', [
+ 'mount_id' => $mountId,
+ 'key' => $key,
+ 'value' => $value
+ ], ['mount_id', 'key']);
+ if ($count === 0) {
+ $builder = $this->connection->getQueryBuilder();
+ $query = $builder->update('external_config')
+ ->set('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))
+ ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
+ ->andWhere($builder->expr()->eq('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR)));
+ $query->execute();
+ }
+ }
+
+ /**
+ * @param int $mountId
+ * @param string $key
+ * @param string $value
+ */
+ public function setOption($mountId, $key, $value) {
+
+ $count = $this->connection->insertIfNotExist('*PREFIX*external_options', [
+ 'mount_id' => $mountId,
+ 'key' => $key,
+ 'value' => json_encode($value)
+ ], ['mount_id', 'key']);
+ if ($count === 0) {
+ $builder = $this->connection->getQueryBuilder();
+ $query = $builder->update('external_options')
+ ->set('value', $builder->createNamedParameter(json_encode($value), IQueryBuilder::PARAM_STR))
+ ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
+ ->andWhere($builder->expr()->eq('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR)));
+ $query->execute();
+ }
+ }
+
+ public function addApplicable($mountId, $type, $value) {
+ $this->connection->insertIfNotExist('*PREFIX*external_applicable', [
+ 'mount_id' => $mountId,
+ 'type' => $type,
+ 'value' => $value
+ ], ['mount_id', 'type', 'value']);
+ }
+
+ public function removeApplicable($mountId, $type, $value) {
+ $builder = $this->connection->getQueryBuilder();
+ $query = $builder->delete('external_applicable')
+ ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
+ ->andWhere($builder->expr()->eq('type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
+
+ if (is_null($value)) {
+ $query = $query->andWhere($builder->expr()->isNull('value'));
+ } else {
+ $query = $query->andWhere($builder->expr()->eq('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR)));
+ }
+
+ $query->execute();
+ }
+
+ private function getMountsFromQuery(IQueryBuilder $query) {
+ $result = $query->execute();
+ $mounts = $result->fetchAll();
+ $uniqueMounts = [];
+ foreach ($mounts as $mount) {
+ $id = $mount['mount_id'];
+ if (!isset($uniqueMounts[$id])) {
+ $uniqueMounts[$id] = $mount;
+ }
+ }
+ $uniqueMounts = array_values($uniqueMounts);
+
+ $mountIds = array_map(function ($mount) {
+ return $mount['mount_id'];
+ }, $uniqueMounts);
+ $mountIds = array_values(array_unique($mountIds));
+
+ $applicable = $this->getApplicableForMounts($mountIds);
+ $config = $this->getConfigForMounts($mountIds);
+ $options = $this->getOptionsForMounts($mountIds);
+
+ return array_map(function ($mount, $applicable, $config, $options) {
+ $mount['type'] = (int)$mount['type'];
+ $mount['priority'] = (int)$mount['priority'];
+ $mount['applicable'] = $applicable;
+ $mount['config'] = $config;
+ $mount['options'] = $options;
+ return $mount;
+ }, $uniqueMounts, $applicable, $config, $options);
+ }
+
+ /**
+ * Get mount options from a table grouped by mount id
+ *
+ * @param string $table
+ * @param string[] $fields
+ * @param int[] $mountIds
+ * @return array [$mountId => [['field1' => $value1, ...], ...], ...]
+ */
+ private function selectForMounts($table, array $fields, array $mountIds) {
+ if (count($mountIds) === 0) {
+ return [];
+ }
+ $builder = $this->connection->getQueryBuilder();
+ $fields[] = 'mount_id';
+ $placeHolders = array_map(function ($id) use ($builder) {
+ return $builder->createPositionalParameter($id, IQueryBuilder::PARAM_INT);
+ }, $mountIds);
+ $query = $builder->select($fields)
+ ->from($table)
+ ->where($builder->expr()->in('mount_id', $placeHolders));
+ $rows = $query->execute()->fetchAll();
+
+ $result = [];
+ foreach ($mountIds as $mountId) {
+ $result[$mountId] = [];
+ }
+ foreach ($rows as $row) {
+ if (isset($row['type'])) {
+ $row['type'] = (int)$row['type'];
+ }
+ $result[$row['mount_id']][] = $row;
+ }
+ return $result;
+ }
+
+ /**
+ * @param int[] $mountIds
+ * @return array [$id => [['type' => $type, 'value' => $value], ...], ...]
+ */
+ public function getApplicableForMounts($mountIds) {
+ return $this->selectForMounts('external_applicable', ['type', 'value'], $mountIds);
+ }
+
+ /**
+ * @param int[] $mountIds
+ * @return array [$id => ['key1' => $value1, ...], ...]
+ */
+ public function getConfigForMounts($mountIds) {
+ $mountConfigs = $this->selectForMounts('external_config', ['key', 'value'], $mountIds);
+ return array_map([$this, 'createKeyValueMap'], $mountConfigs);
+ }
+
+ /**
+ * @param int[] $mountIds
+ * @return array [$id => ['key1' => $value1, ...], ...]
+ */
+ public function getOptionsForMounts($mountIds) {
+ $mountOptions = $this->selectForMounts('external_options', ['key', 'value'], $mountIds);
+ $optionsMap = array_map([$this, 'createKeyValueMap'], $mountOptions);
+ return array_map(function (array $options) {
+ return array_map(function ($option) {
+ return json_decode($option);
+ }, $options);
+ }, $optionsMap);
+ }
+
+ /**
+ * @param array $keyValuePairs [['key'=>$key, 'value=>$value], ...]
+ * @return array ['key1' => $value1, ...]
+ */
+ private function createKeyValueMap(array $keyValuePairs) {
+ $decryptedPairts = array_map(function ($pair) {
+ if ($pair['key'] === 'password') {
+ $pair['value'] = $this->decryptValue($pair['value']);
+ }
+ return $pair;
+ }, $keyValuePairs);
+ $keys = array_map(function ($pair) {
+ return $pair['key'];
+ }, $decryptedPairts);
+ $values = array_map(function ($pair) {
+ return $pair['value'];
+ }, $decryptedPairts);
+
+ return array_combine($keys, $values);
+ }
+
+ private function encryptValue($value) {
+ return $this->crypto->encrypt($value);
+ }
+
+ private function decryptValue($value) {
+ try {
+ return $this->crypto->decrypt($value);
+ } catch (\Exception $e) {
+ return $value;
+ }
+ }
+}
diff --git a/apps/files_external/lib/Service/GlobalLegacyStoragesService.php b/apps/files_external/lib/Service/GlobalLegacyStoragesService.php
new file mode 100644
index 00000000000..4c44881051a
--- /dev/null
+++ b/apps/files_external/lib/Service/GlobalLegacyStoragesService.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * @author Robin Appelman <icewind@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;
+
+/**
+ * Read admin defined mounts from the legacy mount.json
+ */
+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/lib/Service/GlobalStoragesService.php b/apps/files_external/lib/Service/GlobalStoragesService.php
new file mode 100644
index 00000000000..6032a827735
--- /dev/null
+++ b/apps/files_external/lib/Service/GlobalStoragesService.php
@@ -0,0 +1,165 @@
+<?php
+/**
+ * @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 \OCP\IUserSession;
+use \OC\Files\Filesystem;
+
+use \OCA\Files_external\Lib\StorageConfig;
+use \OCA\Files_external\NotFoundException;
+
+/**
+ * Service class to manage global external storages
+ */
+class GlobalStoragesService extends StoragesService {
+ /**
+ * Triggers $signal for all applicable users of the given
+ * storage
+ *
+ * @param StorageConfig $storage storage data
+ * @param string $signal signal to trigger
+ */
+ protected function triggerHooks(StorageConfig $storage, $signal) {
+ // FIXME: Use as expression in empty once PHP 5.4 support is dropped
+ $applicableUsers = $storage->getApplicableUsers();
+ $applicableGroups = $storage->getApplicableGroups();
+ if (empty($applicableUsers) && empty($applicableGroups)) {
+ // raise for user "all"
+ $this->triggerApplicableHooks(
+ $signal,
+ $storage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ ['all']
+ );
+ return;
+ }
+
+ $this->triggerApplicableHooks(
+ $signal,
+ $storage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ $applicableUsers
+ );
+ $this->triggerApplicableHooks(
+ $signal,
+ $storage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ $applicableGroups
+ );
+ }
+
+ /**
+ * Triggers signal_create_mount or signal_delete_mount to
+ * accommodate for additions/deletions in applicableUsers
+ * and applicableGroups fields.
+ *
+ * @param StorageConfig $oldStorage old storage config
+ * @param StorageConfig $newStorage new storage config
+ */
+ protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $newStorage) {
+ // if mount point changed, it's like a deletion + creation
+ if ($oldStorage->getMountPoint() !== $newStorage->getMountPoint()) {
+ $this->triggerHooks($oldStorage, Filesystem::signal_delete_mount);
+ $this->triggerHooks($newStorage, Filesystem::signal_create_mount);
+ return;
+ }
+
+ $userAdditions = array_diff($newStorage->getApplicableUsers(), $oldStorage->getApplicableUsers());
+ $userDeletions = array_diff($oldStorage->getApplicableUsers(), $newStorage->getApplicableUsers());
+ $groupAdditions = array_diff($newStorage->getApplicableGroups(), $oldStorage->getApplicableGroups());
+ $groupDeletions = array_diff($oldStorage->getApplicableGroups(), $newStorage->getApplicableGroups());
+
+ // FIXME: Use as expression in empty once PHP 5.4 support is dropped
+ // if no applicable were set, raise a signal for "all"
+ $oldApplicableUsers = $oldStorage->getApplicableUsers();
+ $oldApplicableGroups = $oldStorage->getApplicableGroups();
+ if (empty($oldApplicableUsers) && empty($oldApplicableGroups)) {
+ $this->triggerApplicableHooks(
+ Filesystem::signal_delete_mount,
+ $oldStorage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ ['all']
+ );
+ }
+
+ // trigger delete for removed users
+ $this->triggerApplicableHooks(
+ Filesystem::signal_delete_mount,
+ $oldStorage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ $userDeletions
+ );
+
+ // trigger delete for removed groups
+ $this->triggerApplicableHooks(
+ Filesystem::signal_delete_mount,
+ $oldStorage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ $groupDeletions
+ );
+
+ // and now add the new users
+ $this->triggerApplicableHooks(
+ Filesystem::signal_create_mount,
+ $newStorage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ $userAdditions
+ );
+
+ // and now add the new groups
+ $this->triggerApplicableHooks(
+ Filesystem::signal_create_mount,
+ $newStorage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ $groupAdditions
+ );
+
+ // FIXME: Use as expression in empty once PHP 5.4 support is dropped
+ // if no applicable, raise a signal for "all"
+ $newApplicableUsers = $newStorage->getApplicableUsers();
+ $newApplicableGroups = $newStorage->getApplicableGroups();
+ if (empty($newApplicableUsers) && empty($newApplicableGroups)) {
+ $this->triggerApplicableHooks(
+ Filesystem::signal_create_mount,
+ $newStorage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ ['all']
+ );
+ }
+ }
+
+ /**
+ * Get the visibility type for this controller, used in validation
+ *
+ * @return string BackendService::VISIBILITY_* constants
+ */
+ public function getVisibilityType() {
+ return BackendService::VISIBILITY_ADMIN;
+ }
+
+ protected function isApplicable(StorageConfig $config) {
+ return true;
+ }
+}
diff --git a/apps/files_external/lib/Service/ImportLegacyStoragesService.php b/apps/files_external/lib/Service/ImportLegacyStoragesService.php
new file mode 100644
index 00000000000..cb020fb495c
--- /dev/null
+++ b/apps/files_external/lib/Service/ImportLegacyStoragesService.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * @author Robin Appelman <icewind@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;
+
+class ImportLegacyStoragesService extends LegacyStoragesService {
+ private $data;
+
+ /**
+ * @param BackendService $backendService
+ */
+ public function __construct(BackendService $backendService) {
+ $this->backendService = $backendService;
+ }
+
+ public function setData($data) {
+ $this->data = $data;
+ }
+
+ /**
+ * Read legacy config data
+ *
+ * @return array list of mount configs
+ */
+ protected function readLegacyConfig() {
+ return $this->data;
+ }
+}
diff --git a/apps/files_external/lib/Service/LegacyStoragesService.php b/apps/files_external/lib/Service/LegacyStoragesService.php
new file mode 100644
index 00000000000..40f57e45506
--- /dev/null
+++ b/apps/files_external/lib/Service/LegacyStoragesService.php
@@ -0,0 +1,209 @@
+<?php
+/**
+ * @author Robin Appelman <icewind@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 \OCA\Files_external\Lib\StorageConfig;
+
+/**
+ * Read mount config from legacy mount.json
+ */
+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) {
+ // don't 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/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;
+ }
+ }
+}
diff --git a/apps/files_external/lib/Service/UserGlobalStoragesService.php b/apps/files_external/lib/Service/UserGlobalStoragesService.php
new file mode 100644
index 00000000000..b5e2d13c712
--- /dev/null
+++ b/apps/files_external/lib/Service/UserGlobalStoragesService.php
@@ -0,0 +1,174 @@
+<?php
+/**
+ * @author Robin Appelman <icewind@owncloud.com>
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ *
+ * @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 OCP\Files\Config\IUserMountCache;
+use \OCP\IUserSession;
+use \OCP\IGroupManager;
+use \OCA\Files_External\Lib\StorageConfig;
+
+/**
+ * Service class to read global storages applicable to the user
+ * Read-only access available, attempting to write will throw DomainException
+ */
+class UserGlobalStoragesService extends GlobalStoragesService {
+
+ use UserTrait;
+
+ /** @var IGroupManager */
+ protected $groupManager;
+
+ /**
+ * @param BackendService $backendService
+ * @param DBConfigService $dbConfig
+ * @param IUserSession $userSession
+ * @param IGroupManager $groupManager
+ * @param IUserMountCache $userMountCache
+ */
+ public function __construct(
+ BackendService $backendService,
+ DBConfigService $dbConfig,
+ IUserSession $userSession,
+ IGroupManager $groupManager,
+ IUserMountCache $userMountCache
+ ) {
+ parent::__construct($backendService, $dbConfig, $userMountCache);
+ $this->userSession = $userSession;
+ $this->groupManager = $groupManager;
+ }
+
+ /**
+ * Replace config hash ID with real IDs, for migrating legacy storages
+ *
+ * @param StorageConfig[] $storages Storages with real IDs
+ * @param StorageConfig[] $storagesWithConfigHash Storages with config hash IDs
+ */
+ protected function setRealStorageIds(array &$storages, array $storagesWithConfigHash) {
+ // as a read-only view, storage IDs don't need to be real
+ foreach ($storagesWithConfigHash as $storage) {
+ $storages[$storage->getId()] = $storage;
+ }
+ }
+
+ protected function readDBConfig() {
+ $userMounts = $this->dbConfig->getAdminMountsFor(DBConfigService::APPLICABLE_TYPE_USER, $this->getUser()->getUID());
+ $globalMounts = $this->dbConfig->getAdminMountsFor(DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
+ $groups = $this->groupManager->getUserGroupIds($this->getUser());
+ if (is_array($groups) && count($groups) !== 0) {
+ $groupMounts = $this->dbConfig->getAdminMountsForMultiple(DBConfigService::APPLICABLE_TYPE_GROUP, $groups);
+ } else {
+ $groupMounts = [];
+ }
+ return array_merge($userMounts, $groupMounts, $globalMounts);
+ }
+
+ public function addStorage(StorageConfig $newStorage) {
+ throw new \DomainException('UserGlobalStoragesService writing disallowed');
+ }
+
+ public function updateStorage(StorageConfig $updatedStorage) {
+ throw new \DomainException('UserGlobalStoragesService writing disallowed');
+ }
+
+ /**
+ * @param integer $id
+ */
+ public function removeStorage($id) {
+ throw new \DomainException('UserGlobalStoragesService writing disallowed');
+ }
+
+ /**
+ * Get unique storages, in case two are defined with the same mountpoint
+ * Higher priority storages take precedence
+ *
+ * @return StorageConfig[]
+ */
+ public function getUniqueStorages() {
+ $storages = $this->getStorages();
+
+ $storagesByMountpoint = [];
+ foreach ($storages as $storage) {
+ $storagesByMountpoint[$storage->getMountPoint()][] = $storage;
+ }
+
+ $result = [];
+ foreach ($storagesByMountpoint as $storageList) {
+ $storage = array_reduce($storageList, function ($carry, $item) {
+ if (isset($carry)) {
+ $carryPriorityType = $this->getPriorityType($carry);
+ $itemPriorityType = $this->getPriorityType($item);
+ if ($carryPriorityType > $itemPriorityType) {
+ return $carry;
+ } elseif ($carryPriorityType === $itemPriorityType) {
+ if ($carry->getPriority() > $item->getPriority()) {
+ return $carry;
+ }
+ }
+ }
+ return $item;
+ });
+ $result[$storage->getID()] = $storage;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get a priority 'type', where a bigger number means higher priority
+ * user applicable > group applicable > 'all'
+ *
+ * @param StorageConfig $storage
+ * @return int
+ */
+ protected function getPriorityType(StorageConfig $storage) {
+ $applicableUsers = $storage->getApplicableUsers();
+ $applicableGroups = $storage->getApplicableGroups();
+
+ if ($applicableUsers && $applicableUsers[0] !== 'all') {
+ return 2;
+ }
+ if ($applicableGroups) {
+ return 1;
+ }
+ return 0;
+ }
+
+ protected function isApplicable(StorageConfig $config) {
+ $applicableUsers = $config->getApplicableUsers();
+ $applicableGroups = $config->getApplicableGroups();
+
+ if (count($applicableUsers) === 0 && count($applicableGroups) === 0) {
+ return true;
+ }
+ if (in_array($this->getUser()->getUID(), $applicableUsers, true)) {
+ return true;
+ }
+ $groupIds = $this->groupManager->getUserGroupIds($this->getUser());
+ foreach ($groupIds as $groupId) {
+ if (in_array($groupId, $applicableGroups, true)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/apps/files_external/lib/Service/UserLegacyStoragesService.php b/apps/files_external/lib/Service/UserLegacyStoragesService.php
new file mode 100644
index 00000000000..c64520c8b02
--- /dev/null
+++ b/apps/files_external/lib/Service/UserLegacyStoragesService.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @author Robin Appelman <icewind@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 OCP\IUserSession;
+
+/**
+ * Read user defined mounts from the legacy mount.json
+ */
+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);
+ }
+}
diff --git a/apps/files_external/lib/Service/UserStoragesService.php b/apps/files_external/lib/Service/UserStoragesService.php
new file mode 100644
index 00000000000..b64f289b3ee
--- /dev/null
+++ b/apps/files_external/lib/Service/UserStoragesService.php
@@ -0,0 +1,141 @@
+<?php
+/**
+ * @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 OCP\Files\Config\IUserMountCache;
+use \OCP\IUserSession;
+use \OC\Files\Filesystem;
+
+use \OCA\Files_external\Lib\StorageConfig;
+use \OCA\Files_external\NotFoundException;
+use \OCA\Files_External\Service\BackendService;
+use \OCA\Files_External\Service\UserTrait;
+
+/**
+ * Service class to manage user external storages
+ * (aka personal storages)
+ */
+class UserStoragesService extends StoragesService {
+ use UserTrait;
+
+ /**
+ * Create a user storages service
+ *
+ * @param BackendService $backendService
+ * @param DBConfigService $dbConfig
+ * @param IUserSession $userSession user session
+ * @param IUserMountCache $userMountCache
+ */
+ public function __construct(
+ BackendService $backendService,
+ DBConfigService $dbConfig,
+ IUserSession $userSession,
+ IUserMountCache $userMountCache
+ ) {
+ $this->userSession = $userSession;
+ parent::__construct($backendService, $dbConfig, $userMountCache);
+ }
+
+ protected function readDBConfig() {
+ return $this->dbConfig->getUserMountsFor(DBConfigService::APPLICABLE_TYPE_USER, $this->getUser()->getUID());
+ }
+
+ /**
+ * Triggers $signal for all applicable users of the given
+ * storage
+ *
+ * @param StorageConfig $storage storage data
+ * @param string $signal signal to trigger
+ */
+ protected function triggerHooks(StorageConfig $storage, $signal) {
+ $user = $this->getUser()->getUID();
+
+ // trigger hook for the current user
+ $this->triggerApplicableHooks(
+ $signal,
+ $storage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ [$user]
+ );
+ }
+
+ /**
+ * 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
+ */
+ protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $newStorage) {
+ // if mount point changed, it's like a deletion + creation
+ if ($oldStorage->getMountPoint() !== $newStorage->getMountPoint()) {
+ $this->triggerHooks($oldStorage, Filesystem::signal_delete_mount);
+ $this->triggerHooks($newStorage, Filesystem::signal_create_mount);
+ }
+ }
+
+ protected function getType() {
+ return DBConfigService::MOUNT_TYPE_PERSONAl;
+ }
+
+ /**
+ * Add new storage to the configuration
+ *
+ * @param StorageConfig $newStorage storage attributes
+ *
+ * @return StorageConfig storage config, with added id
+ */
+ public function addStorage(StorageConfig $newStorage) {
+ $newStorage->setApplicableUsers([$this->getUser()->getUID()]);
+ $config = parent::addStorage($newStorage);
+ return $config;
+ }
+
+ /**
+ * 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) {
+ $updatedStorage->setApplicableUsers([$this->getUser()->getUID()]);
+ return parent::updateStorage($updatedStorage);
+ }
+
+ /**
+ * Get the visibility type for this controller, used in validation
+ *
+ * @return string BackendService::VISIBILITY_* constants
+ */
+ public function getVisibilityType() {
+ return BackendService::VISIBILITY_PERSONAL;
+ }
+
+ protected function isApplicable(StorageConfig $config) {
+ return ($config->getApplicableUsers() === [$this->getUser()->getUID()]) && $config->getType() === StorageConfig::MOUNT_TYPE_PERSONAl;
+ }
+}
diff --git a/apps/files_external/lib/Service/UserTrait.php b/apps/files_external/lib/Service/UserTrait.php
new file mode 100644
index 00000000000..536c0f67e1f
--- /dev/null
+++ b/apps/files_external/lib/Service/UserTrait.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ *
+ * @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 \OCP\IUserSession;
+use \OCP\IUser;
+
+/**
+ * Trait for getting user information in a service
+ */
+trait UserTrait {
+
+ /** @var IUserSession */
+ protected $userSession;
+
+ /**
+ * User override
+ *
+ * @var IUser|null
+ */
+ private $user = null;
+
+ /**
+ * @return IUser|null
+ */
+ protected function getUser() {
+ if ($this->user) {
+ return $this->user;
+ }
+ return $this->userSession->getUser();
+ }
+
+ /**
+ * Override the user from the session
+ * Unset with ->resetUser() when finished!
+ *
+ * @param IUser
+ * @return self
+ */
+ public function setUser(IUser $user) {
+ $this->user = $user;
+ return $this;
+ }
+
+ /**
+ * Reset the user override
+ *
+ * @return self
+ */
+ public function resetUser() {
+ $this->user = null;
+ return $this;
+ }
+}
+