migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,370 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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\Config\IConfigHandler;
use OCA\Files_External\Lib\Auth\AuthMechanism;
use OCA\Files_External\Lib\Backend\Backend;
use OCA\Files_External\Lib\Config\IAuthMechanismProvider;
use OCA\Files_External\Lib\Config\IBackendProvider;
use OCP\EventDispatcher\GenericEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
/**
* Service class to manage backend definitions
*/
class BackendService {
/** Visibility constants for VisibilityTrait */
public const VISIBILITY_NONE = 0;
public const VISIBILITY_PERSONAL = 1;
public const VISIBILITY_ADMIN = 2;
//const VISIBILITY_ALIENS = 4;
public const VISIBILITY_DEFAULT = 3; // PERSONAL | ADMIN
/** Priority constants for PriorityTrait */
public 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 = [];
/** @var callable[] */
private $configHandlerLoaders = [];
private $configHandlers = [];
/**
* @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 callForRegistrations() {
static $eventSent = false;
if (!$eventSent) {
\OC::$server->get(IEventDispatcher::class)->dispatch(
'OCA\\Files_External::loadAdditionalBackends',
new GenericEvent()
);
$eventSent = true;
}
}
private function loadBackendProviders() {
$this->callForRegistrations();
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() {
$this->callForRegistrations();
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
}
/**
* registers a configuration handler
*
* The function of the provided $placeholder is mostly to act a sorting
* criteria, so longer placeholders are replaced first. This avoids
* "$user" overwriting parts of "$userMail" and "$userLang", for example.
* The provided value should not contain the $ prefix, only a-z0-9 are
* allowed. Upper case letters are lower cased, the replacement is case-
* insensitive.
*
* The configHandlerLoader should just instantiate the handler on demand.
* For now all handlers are instantiated when a mount is loaded, independent
* of whether the placeholder is present or not. This may change in future.
*
* @since 16.0.0
*/
public function registerConfigHandler(string $placeholder, callable $configHandlerLoader) {
$placeholder = trim(strtolower($placeholder));
if (!(bool)\preg_match('/^[a-z0-9]*$/', $placeholder)) {
throw new \RuntimeException(sprintf(
'Invalid placeholder %s, only [a-z0-9] are allowed', $placeholder
));
}
if ($placeholder === '') {
throw new \RuntimeException('Invalid empty placeholder');
}
if (isset($this->configHandlerLoaders[$placeholder]) || isset($this->configHandlers[$placeholder])) {
throw new \RuntimeException(sprintf('A handler is already registered for %s', $placeholder));
}
$this->configHandlerLoaders[$placeholder] = $configHandlerLoader;
}
protected function loadConfigHandlers():void {
$this->callForRegistrations();
$newLoaded = false;
foreach ($this->configHandlerLoaders as $placeholder => $loader) {
$handler = $loader();
if (!$handler instanceof IConfigHandler) {
throw new \RuntimeException(sprintf(
'Handler for %s is not an instance of IConfigHandler', $placeholder
));
}
$this->configHandlers[$placeholder] = $handler;
$newLoaded = true;
}
$this->configHandlerLoaders = [];
if ($newLoaded) {
// ensure those with longest placeholders come first,
// to avoid substring matches
uksort($this->configHandlers, function ($phA, $phB) {
return strlen($phB) <=> strlen($phA);
});
}
}
/**
* @since 16.0.0
*/
public function getConfigHandlers() {
$this->loadConfigHandlers();
return $this->configHandlers;
}
}
@@ -0,0 +1,534 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\Security\ICrypto;
/**
* Stores the mount config in the database
*/
class DBConfigService {
public const MOUNT_TYPE_ADMIN = 1;
public const MOUNT_TYPE_PERSONAl = 2;
public const APPLICABLE_TYPE_GLOBAL = 1;
public const APPLICABLE_TYPE_GROUP = 2;
public 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;
}
public function getMountById(int $mountId): ?array {
$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 all configured mounts
*
* @return array
*/
public function getAllMounts() {
$builder = $this->connection->getQueryBuilder();
$query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
->from('external_mounts');
return $this->getMountsFromQuery($query);
}
public function getMountsForUser($userId, $groupIds) {
$builder = $this->connection->getQueryBuilder();
$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()->orX(
$builder->expr()->andX( // global mounts
$builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GLOBAL, IQueryBuilder::PARAM_INT)),
$builder->expr()->isNull('a.value')
),
$builder->expr()->andX( // mounts for user
$builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_USER, IQueryBuilder::PARAM_INT)),
$builder->expr()->eq('a.value', $builder->createNamedParameter($userId))
),
$builder->expr()->andX( // mounts for group
$builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GROUP, IQueryBuilder::PARAM_INT)),
$builder->expr()->in('a.value', $builder->createNamedParameter($groupIds, IQueryBuilder::PARAM_STR_ARRAY))
)
));
return $this->getMountsFromQuery($query);
}
public function modifyMountsOnUserDelete(string $uid): void {
$this->modifyMountsOnDelete($uid, self::APPLICABLE_TYPE_USER);
}
public function modifyMountsOnGroupDelete(string $gid): void {
$this->modifyMountsOnDelete($gid, self::APPLICABLE_TYPE_GROUP);
}
protected function modifyMountsOnDelete(string $applicableId, int $applicableType): void {
$builder = $this->connection->getQueryBuilder();
$query = $builder->select(['a.mount_id', $builder->func()->count('a.mount_id', 'count')])
->from('external_applicable', 'a')
->leftJoin('a', 'external_applicable', 'b', $builder->expr()->eq('a.mount_id', 'b.mount_id'))
->where($builder->expr()->andX(
$builder->expr()->eq('b.type', $builder->createNamedParameter($applicableType, IQueryBuilder::PARAM_INT)),
$builder->expr()->eq('b.value', $builder->createNamedParameter($applicableId))
)
)
->groupBy(['a.mount_id']);
$stmt = $query->execute();
$result = $stmt->fetchAll();
$stmt->closeCursor();
foreach ($result as $row) {
if ((int)$row['count'] > 1) {
$this->removeApplicable($row['mount_id'], $applicableType, $applicableId);
} else {
$this->removeMount($row['mount_id']);
}
}
}
/**
* 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 $query->getLastInsertId();
}
/**
* 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();
$builder = $this->connection->getQueryBuilder();
$query = $builder->delete('external_applicable')
->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
$query->execute();
$builder = $this->connection->getQueryBuilder();
$query = $builder->delete('external_config')
->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
$query->execute();
$builder = $this->connection->getQueryBuilder();
$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);
}
try {
$builder = $this->connection->getQueryBuilder();
$builder->insert('external_config')
->setValue('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))
->setValue('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR))
->setValue('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))
->execute();
} catch (UniqueConstraintViolationException $e) {
$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) {
try {
$builder = $this->connection->getQueryBuilder();
$builder->insert('external_options')
->setValue('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))
->setValue('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR))
->setValue('value', $builder->createNamedParameter(json_encode($value), IQueryBuilder::PARAM_STR))
->execute();
} catch (UniqueConstraintViolationException $e) {
$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) {
try {
$builder = $this->connection->getQueryBuilder();
$builder->insert('external_applicable')
->setValue('mount_id', $builder->createNamedParameter($mountId))
->setValue('type', $builder->createNamedParameter($type))
->setValue('value', $builder->createNamedParameter($value))
->execute();
} catch (UniqueConstraintViolationException $e) {
// applicable exists already
}
}
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));
$result = $query->execute();
$rows = $result->fetchAll();
$result->closeCursor();
$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;
}
}
}
@@ -0,0 +1,186 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Stefan Weil <sw@weilnetz.de>
* @author szaimen <szaimen@e.mail.de>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @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;
/**
* Service class to manage global external storage
*/
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(),
\OCA\Files_External\MountConfig::MOUNT_TYPE_USER,
['all']
);
return;
}
$this->triggerApplicableHooks(
$signal,
$storage->getMountPoint(),
\OCA\Files_External\MountConfig::MOUNT_TYPE_USER,
$applicableUsers
);
$this->triggerApplicableHooks(
$signal,
$storage->getMountPoint(),
\OCA\Files_External\MountConfig::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(),
\OCA\Files_External\MountConfig::MOUNT_TYPE_USER,
['all']
);
}
// trigger delete for removed users
$this->triggerApplicableHooks(
Filesystem::signal_delete_mount,
$oldStorage->getMountPoint(),
\OCA\Files_External\MountConfig::MOUNT_TYPE_USER,
$userDeletions
);
// trigger delete for removed groups
$this->triggerApplicableHooks(
Filesystem::signal_delete_mount,
$oldStorage->getMountPoint(),
\OCA\Files_External\MountConfig::MOUNT_TYPE_GROUP,
$groupDeletions
);
// and now add the new users
$this->triggerApplicableHooks(
Filesystem::signal_create_mount,
$newStorage->getMountPoint(),
\OCA\Files_External\MountConfig::MOUNT_TYPE_USER,
$userAdditions
);
// and now add the new groups
$this->triggerApplicableHooks(
Filesystem::signal_create_mount,
$newStorage->getMountPoint(),
\OCA\Files_External\MountConfig::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(),
\OCA\Files_External\MountConfig::MOUNT_TYPE_USER,
['all']
);
}
}
/**
* Get the visibility type for this controller, used in validation
*
* @return int BackendService::VISIBILITY_* constants
*/
public function getVisibilityType() {
return BackendService::VISIBILITY_ADMIN;
}
protected function isApplicable(StorageConfig $config) {
return true;
}
/**
* Get all configured admin and personal mounts
*
* @return StorageConfig[] map of storage id to storage config
*/
public function getStorageForAllUsers() {
$mounts = $this->dbConfig->getAllMounts();
$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);
}
}
@@ -0,0 +1,47 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Joas Schilling <coding@schilljs.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @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;
}
}
@@ -0,0 +1,210 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Stefan Weil <sw@weilnetz.de>
* @author szaimen <szaimen@e.mail.de>
*
* @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;
use Psr\Log\LoggerInterface;
/**
* 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 === \OCA\Files_External\MountConfig::MOUNT_TYPE_USER) {
$applicableUsers = $storageConfig->getApplicableUsers();
if ($applicable !== 'all') {
$applicableUsers[] = $applicable;
$storageConfig->setApplicableUsers($applicableUsers);
}
} elseif ($mountType === \OCA\Files_External\MountConfig::MOUNT_TYPE_GROUP) {
$applicableGroups = $storageConfig->getApplicableGroups();
$applicableGroups[] = $applicable;
$storageConfig->setApplicableGroups($applicableGroups);
}
return $storageConfig;
}
/**
* Read the external storage 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
\OC::$server->get(LoggerInterface::class)->error('Could not parse mount point "' . $rootMountPath . '"', ['app' => 'files_external']);
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'] = \OCA\Files_External\MountConfig::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 = \OCA\Files_External\MountConfig::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
\OC::$server->get(LoggerInterface::class)->error('Could not load storage.', [
'app' => 'files_external',
'exception' => $e,
]);
}
}
}
}
// convert parameter values
foreach ($storages as $storage) {
$storage->getBackend()->validateStorageDefinition($storage);
$storage->getAuthMechanism()->validateStorageDefinition($storage);
}
return $storages;
}
}
@@ -0,0 +1,517 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Jesús Macias <jmacias@solidgear.es>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Stefan Weil <sw@weilnetz.de>
* @author szaimen <szaimen@e.mail.de>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @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\Auth\AuthMechanism;
use OCA\Files_External\Lib\Auth\InvalidAuth;
use OCA\Files_External\Lib\Backend\Backend;
use OCA\Files_External\Lib\Backend\InvalidBackend;
use OCA\Files_External\Lib\DefinitionParameter;
use OCA\Files_External\Lib\StorageConfig;
use OCA\Files_External\NotFoundException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Config\IUserMountCache;
use OCP\Files\Events\InvalidateMountCacheEvent;
use OCP\Files\StorageNotAvailableException;
use Psr\Log\LoggerInterface;
/**
* Service class to manage external storage
*/
abstract class StoragesService {
/** @var BackendService */
protected $backendService;
/**
* @var DBConfigService
*/
protected $dbConfig;
/**
* @var IUserMountCache
*/
protected $userMountCache;
protected IEventDispatcher $eventDispatcher;
/**
* @param BackendService $backendService
* @param DBConfigService $dbConfigService
* @param IUserMountCache $userMountCache
* @param IEventDispatcher $eventDispatcher
*/
public function __construct(
BackendService $backendService,
DBConfigService $dbConfigService,
IUserMountCache $userMountCache,
IEventDispatcher $eventDispatcher
) {
$this->backendService = $backendService;
$this->dbConfig = $dbConfigService;
$this->userMountCache = $userMountCache;
$this->eventDispatcher = $eventDispatcher;
}
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
\OC::$server->get(LoggerInterface::class)->error('Could not load storage.', [
'app' => 'files_external',
'exception' => $e,
]);
return null;
} catch (\InvalidArgumentException $e) {
\OC::$server->get(LoggerInterface::class)->error('Could not load storage.', [
'app' => 'files_external',
'exception' => $e,
]);
return null;
}
}
/**
* Read the external storage 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 int 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) {
$backend = new InvalidBackend($backendIdentifier);
}
$authMechanism = $this->backendService->getAuthMechanism($authMechanismIdentifier);
if (!$authMechanism) {
$authMechanism = new InvalidAuth($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 point 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): void {
$this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent(null));
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);
if ($oldStorage->getBackend() instanceof InvalidBackend) {
throw new NotFoundException('Storage with id "' . $id . '" cannot be edited due to missing backend');
}
$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);
} elseif (!$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) {
if ($value !== DefinitionParameter::UNMODIFIED_PLACEHOLDER) {
$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
\OC\Files\Cache\Storage::cleanByMountId($id);
}
/**
* 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);
/** @var \OC\Files\Storage\Storage $storage */
return $storage->getStorageCache()->getNumericId();
} catch (\Exception $e) {
return -1;
}
}
}
@@ -0,0 +1,207 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Config\IUserMountCache;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserSession;
/**
* 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
* @param IEventDispatcher $eventDispatcher
*/
public function __construct(
BackendService $backendService,
DBConfigService $dbConfig,
IUserSession $userSession,
IGroupManager $groupManager,
IUserMountCache $userMountCache,
IEventDispatcher $eventDispatcher
) {
parent::__construct($backendService, $dbConfig, $userMountCache, $eventDispatcher);
$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 (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;
}
/**
* Gets all storages for the user, admin, personal, global, etc
*
* @param IUser|null $user user to get the storages for, if not set the currently logged in user will be used
* @return StorageConfig[] array of storage configs
*/
public function getAllStoragesForUser(IUser $user = null) {
if (is_null($user)) {
$user = $this->getUser();
}
if (is_null($user)) {
return [];
}
$groupIds = $this->groupManager->getUserGroupIds($user);
$mounts = $this->dbConfig->getMountsForUser($user->getUID(), $groupIds);
$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);
$storages = array_combine($keys, $configs);
return array_filter($storages, [$this, 'validateStorage']);
}
}
@@ -0,0 +1,155 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Stefan Weil <sw@weilnetz.de>
* @author szaimen <szaimen@e.mail.de>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @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 OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Config\IUserMountCache;
use OCP\IUserSession;
/**
* Service class to manage user external storage
* (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
* @param IEventDispatcher $eventDispatcher
*/
public function __construct(
BackendService $backendService,
DBConfigService $dbConfig,
IUserSession $userSession,
IUserMountCache $userMountCache,
IEventDispatcher $eventDispatcher
) {
$this->userSession = $userSession;
parent::__construct($backendService, $dbConfig, $userMountCache, $eventDispatcher);
}
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(),
\OCA\Files_External\MountConfig::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()]);
return parent::addStorage($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) {
// verify ownership through $this->isApplicable() and otherwise throws an exception
$this->getStorage($updatedStorage->getId());
$updatedStorage->setApplicableUsers([$this->getUser()->getUID()]);
return parent::updateStorage($updatedStorage);
}
/**
* Get the visibility type for this controller, used in validation
*
* @return int 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;
}
public function removeStorage($id) {
// verify ownership through $this->isApplicable() and otherwise throws an exception
$this->getStorage($id);
parent::removeStorage($id);
}
}
@@ -0,0 +1,75 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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\IUser;
use OCP\IUserSession;
/**
* 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 $user
* @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;
}
}