mirror of
https://github.com/nextcloud/server.git
synced 2026-06-11 01:30:50 -04:00
Merge pull request #60318 from nextcloud/fix/remove-mountconfig-skiptest
fix(files_external): Move MountConfig to non-static services
This commit is contained in:
commit
dfec8dbfc1
22 changed files with 228 additions and 259 deletions
1
.github/workflows/files-external-sftp.yml
vendored
1
.github/workflows/files-external-sftp.yml
vendored
|
|
@ -98,7 +98,6 @@ jobs:
|
|||
- name: PHPUnit
|
||||
run: composer run test:files_external -- \
|
||||
apps/files_external/tests/Storage/SftpTest.php \
|
||||
apps/files_external/tests/Storage/SFTP_KeyTest.php \
|
||||
--log-junit junit.xml \
|
||||
${{ matrix.coverage && '--coverage-clover ./clover.xml' || '' }}
|
||||
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ return array(
|
|||
'OCA\\Files_External\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
|
||||
'OCA\\Files_External\\Service\\BackendService' => $baseDir . '/../lib/Service/BackendService.php',
|
||||
'OCA\\Files_External\\Service\\DBConfigService' => $baseDir . '/../lib/Service/DBConfigService.php',
|
||||
'OCA\\Files_External\\Service\\EncryptionService' => $baseDir . '/../lib/Service/EncryptionService.php',
|
||||
'OCA\\Files_External\\Service\\GlobalStoragesService' => $baseDir . '/../lib/Service/GlobalStoragesService.php',
|
||||
'OCA\\Files_External\\Service\\ImportLegacyStoragesService' => $baseDir . '/../lib/Service/ImportLegacyStoragesService.php',
|
||||
'OCA\\Files_External\\Service\\LegacyStoragesService' => $baseDir . '/../lib/Service/LegacyStoragesService.php',
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ class ComposerStaticInitFiles_External
|
|||
'OCA\\Files_External\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
|
||||
'OCA\\Files_External\\Service\\BackendService' => __DIR__ . '/..' . '/../lib/Service/BackendService.php',
|
||||
'OCA\\Files_External\\Service\\DBConfigService' => __DIR__ . '/..' . '/../lib/Service/DBConfigService.php',
|
||||
'OCA\\Files_External\\Service\\EncryptionService' => __DIR__ . '/..' . '/../lib/Service/EncryptionService.php',
|
||||
'OCA\\Files_External\\Service\\GlobalStoragesService' => __DIR__ . '/..' . '/../lib/Service/GlobalStoragesService.php',
|
||||
'OCA\\Files_External\\Service\\ImportLegacyStoragesService' => __DIR__ . '/..' . '/../lib/Service/ImportLegacyStoragesService.php',
|
||||
'OCA\\Files_External\\Service\\LegacyStoragesService' => __DIR__ . '/..' . '/../lib/Service/LegacyStoragesService.php',
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ namespace OCA\Files_External\Command;
|
|||
use OC\Core\Command\Base;
|
||||
use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException;
|
||||
use OCA\Files_External\Lib\StorageConfig;
|
||||
use OCA\Files_External\MountConfig;
|
||||
use OCA\Files_External\NotFoundException;
|
||||
use OCA\Files_External\Service\BackendService;
|
||||
use OCA\Files_External\Service\GlobalStoragesService;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\Files\StorageNotAvailableException;
|
||||
|
|
@ -23,6 +23,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
class Verify extends Base {
|
||||
public function __construct(
|
||||
protected GlobalStoragesService $globalService,
|
||||
protected BackendService $backendService,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
|
@ -96,7 +97,7 @@ class Verify extends Base {
|
|||
$backend = $storage->getBackend();
|
||||
// update status (can be time-consuming)
|
||||
$storage->setStatus(
|
||||
MountConfig::getBackendStatus(
|
||||
$this->backendService->getBackendStatus(
|
||||
$backend->getStorageClass(),
|
||||
$storage->getBackendOptions(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use OC\Files\Storage\Wrapper\Availability;
|
|||
use OC\Files\Storage\Wrapper\KnownMtime;
|
||||
use OCA\Files_External\Lib\PersonalMount;
|
||||
use OCA\Files_External\Lib\StorageConfig;
|
||||
use OCA\Files_External\MountConfig;
|
||||
use OCA\Files_External\Service\BackendService;
|
||||
use OCA\Files_External\Service\UserGlobalStoragesService;
|
||||
use OCA\Files_External\Service\UserStoragesService;
|
||||
use OCP\Files\Config\IAuthoritativeMountProvider;
|
||||
|
|
@ -39,6 +39,7 @@ class ConfigAdapter implements IMountProvider, IAuthoritativeMountProvider, IPar
|
|||
public function __construct(
|
||||
private UserStoragesService $userStoragesService,
|
||||
private UserGlobalStoragesService $userGlobalStoragesService,
|
||||
private BackendService $backendService,
|
||||
private ClockInterface $clock,
|
||||
) {
|
||||
}
|
||||
|
|
@ -63,7 +64,7 @@ class ConfigAdapter implements IMountProvider, IAuthoritativeMountProvider, IPar
|
|||
*/
|
||||
private function prepareStorageConfig(StorageConfig &$storage, IUser $user): void {
|
||||
foreach ($storage->getBackendOptions() as $option => $value) {
|
||||
$storage->setBackendOption($option, MountConfig::substitutePlaceholdersInConfig($value, $user->getUID()));
|
||||
$storage->setBackendOption($option, $this->backendService->applyConfigHandlers($value, $user->getUID()));
|
||||
}
|
||||
|
||||
$objectStore = $storage->getBackendOption('objectstore');
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
namespace OCA\Files_External\Controller;
|
||||
|
||||
use OCA\Files_External\NotFoundException;
|
||||
use OCA\Files_External\Service\BackendService;
|
||||
use OCA\Files_External\Service\GlobalStoragesService;
|
||||
use OCA\Files_External\Settings\Admin;
|
||||
use OCP\AppFramework\Http;
|
||||
|
|
@ -37,6 +38,7 @@ class GlobalStoragesController extends StoragesController {
|
|||
IUserSession $userSession,
|
||||
IGroupManager $groupManager,
|
||||
IConfig $config,
|
||||
BackendService $backendService,
|
||||
) {
|
||||
parent::__construct(
|
||||
$appName,
|
||||
|
|
@ -46,7 +48,8 @@ class GlobalStoragesController extends StoragesController {
|
|||
$logger,
|
||||
$userSession,
|
||||
$groupManager,
|
||||
$config
|
||||
$config,
|
||||
$backendService,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ use OCA\Files_External\Lib\Auth\AuthMechanism;
|
|||
use OCA\Files_External\Lib\Backend\Backend;
|
||||
use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException;
|
||||
use OCA\Files_External\Lib\StorageConfig;
|
||||
use OCA\Files_External\MountConfig;
|
||||
use OCA\Files_External\NotFoundException;
|
||||
use OCA\Files_External\Service\BackendService;
|
||||
use OCA\Files_External\Service\StoragesService;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http;
|
||||
|
|
@ -48,6 +48,7 @@ abstract class StoragesController extends Controller {
|
|||
protected IUserSession $userSession,
|
||||
protected IGroupManager $groupManager,
|
||||
protected IConfig $config,
|
||||
protected BackendService $backendService,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
|
|
@ -222,7 +223,7 @@ abstract class StoragesController extends Controller {
|
|||
$backend = $storage->getBackend();
|
||||
// update status (can be time-consuming)
|
||||
$storage->setStatus(
|
||||
MountConfig::getBackendStatus(
|
||||
$this->backendService->getBackendStatus(
|
||||
$backend->getStorageClass(),
|
||||
$storage->getBackendOptions(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use OCA\Files_External\Lib\Backend\Backend;
|
|||
use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException;
|
||||
use OCA\Files_External\Lib\StorageConfig;
|
||||
use OCA\Files_External\NotFoundException;
|
||||
use OCA\Files_External\Service\BackendService;
|
||||
use OCA\Files_External\Service\UserGlobalStoragesService;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
|
|
@ -50,6 +51,7 @@ class UserGlobalStoragesController extends StoragesController {
|
|||
IUserSession $userSession,
|
||||
IGroupManager $groupManager,
|
||||
IConfig $config,
|
||||
BackendService $backendService,
|
||||
) {
|
||||
parent::__construct(
|
||||
$appName,
|
||||
|
|
@ -59,7 +61,8 @@ class UserGlobalStoragesController extends StoragesController {
|
|||
$logger,
|
||||
$userSession,
|
||||
$groupManager,
|
||||
$config
|
||||
$config,
|
||||
$backendService,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use OCA\Files_External\Lib\Auth\AuthMechanism;
|
|||
use OCA\Files_External\Lib\Backend\Backend;
|
||||
use OCA\Files_External\Lib\StorageConfig;
|
||||
use OCA\Files_External\NotFoundException;
|
||||
use OCA\Files_External\Service\BackendService;
|
||||
use OCA\Files_External\Service\UserStoragesService;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
|
|
@ -40,6 +41,7 @@ class UserStoragesController extends StoragesController {
|
|||
IUserSession $userSession,
|
||||
IGroupManager $groupManager,
|
||||
IConfig $config,
|
||||
BackendService $backendService,
|
||||
) {
|
||||
parent::__construct(
|
||||
$appName,
|
||||
|
|
@ -49,7 +51,8 @@ class UserStoragesController extends StoragesController {
|
|||
$logger,
|
||||
$userSession,
|
||||
$groupManager,
|
||||
$config
|
||||
$config,
|
||||
$backendService,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,64 +5,34 @@
|
|||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
namespace OCA\Files_External;
|
||||
|
||||
use OC\Files\Storage\Common;
|
||||
use OCA\Files_External\Config\IConfigHandler;
|
||||
use OCA\Files_External\Config\UserContext;
|
||||
use OCA\Files_External\Lib\Backend\Backend;
|
||||
use OCA\Files_External\Service\BackendService;
|
||||
use OCA\Files_External\Service\GlobalStoragesService;
|
||||
use OCA\Files_External\Service\UserGlobalStoragesService;
|
||||
use OCA\Files_External\Service\UserStoragesService;
|
||||
use OCP\Files\StorageNotAvailableException;
|
||||
use OCP\IConfig;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use OCA\Files_External\Service\EncryptionService;
|
||||
use OCP\Server;
|
||||
use phpseclib\Crypt\AES;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Class to configure mount.json globally and for users
|
||||
*/
|
||||
class MountConfig {
|
||||
// TODO: make this class non-static and give it a proper namespace
|
||||
|
||||
public const MOUNT_TYPE_GLOBAL = 'global';
|
||||
public const MOUNT_TYPE_GROUP = 'group';
|
||||
public const MOUNT_TYPE_USER = 'user';
|
||||
public const MOUNT_TYPE_PERSONAL = 'personal';
|
||||
|
||||
// whether to skip backend test (for unit tests, as this static class is not mockable)
|
||||
public static $skipTest = false;
|
||||
|
||||
public function __construct(
|
||||
private UserGlobalStoragesService $userGlobalStorageService,
|
||||
private UserStoragesService $userStorageService,
|
||||
private GlobalStoragesService $globalStorageService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $input
|
||||
* @param string|null $userId
|
||||
* @return mixed
|
||||
* @throws ContainerExceptionInterface
|
||||
* @since 16.0.0
|
||||
* @deprecated 34.0.0 use BackendService instead
|
||||
*/
|
||||
public static function substitutePlaceholdersInConfig($input, ?string $userId = null) {
|
||||
/** @var BackendService $backendService */
|
||||
$backendService = Server::get(BackendService::class);
|
||||
/** @var IConfigHandler[] $handlers */
|
||||
$handlers = $backendService->getConfigHandlers();
|
||||
foreach ($handlers as $handler) {
|
||||
if ($handler instanceof UserContext && $userId !== null) {
|
||||
$handler->setUserId($userId);
|
||||
}
|
||||
$input = $handler->handle($input);
|
||||
}
|
||||
return $input;
|
||||
return Server::get(BackendService::class)->applyConfigHandlers($input, $userId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -73,39 +43,10 @@ class MountConfig {
|
|||
* @param boolean $isPersonal
|
||||
* @return int see self::STATUS_*
|
||||
* @throws \Exception
|
||||
* @deprecated 34.0.0 use BackendService instead
|
||||
*/
|
||||
public static function getBackendStatus($class, $options) {
|
||||
if (self::$skipTest) {
|
||||
return StorageNotAvailableException::STATUS_SUCCESS;
|
||||
}
|
||||
foreach ($options as $key => &$option) {
|
||||
if ($key === 'password') {
|
||||
// no replacements in passwords
|
||||
continue;
|
||||
}
|
||||
$option = self::substitutePlaceholdersInConfig($option);
|
||||
}
|
||||
if (class_exists($class)) {
|
||||
try {
|
||||
/** @var Common $storage */
|
||||
$storage = new $class($options);
|
||||
|
||||
try {
|
||||
$result = $storage->test();
|
||||
$storage->setAvailability($result);
|
||||
if ($result) {
|
||||
return StorageNotAvailableException::STATUS_SUCCESS;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$storage->setAvailability(false);
|
||||
throw $e;
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
Server::get(LoggerInterface::class)->error($exception->getMessage(), ['exception' => $exception, 'app' => 'files_external']);
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
return StorageNotAvailableException::STATUS_ERROR;
|
||||
return Server::get(BackendService::class)->getBackendStatus($class, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -113,15 +54,10 @@ class MountConfig {
|
|||
*
|
||||
* @param array $options mount options
|
||||
* @return array updated options
|
||||
* @deprecated 34.0.0 use EncryptionService instead
|
||||
*/
|
||||
public static function encryptPasswords($options) {
|
||||
if (isset($options['password'])) {
|
||||
$options['password_encrypted'] = self::encryptPassword($options['password']);
|
||||
// do not unset the password, we want to keep the keys order
|
||||
// on load... because that's how the UI currently works
|
||||
$options['password'] = '';
|
||||
}
|
||||
return $options;
|
||||
return Server::get(EncryptionService::class)->encryptPasswords($options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -129,64 +65,19 @@ class MountConfig {
|
|||
*
|
||||
* @param array $options mount options
|
||||
* @return array updated options
|
||||
* @deprecated 34.0.0 use EncryptionService instead
|
||||
*/
|
||||
public static function decryptPasswords($options) {
|
||||
// note: legacy options might still have the unencrypted password in the "password" field
|
||||
if (isset($options['password_encrypted'])) {
|
||||
$options['password'] = self::decryptPassword($options['password_encrypted']);
|
||||
unset($options['password_encrypted']);
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a single password
|
||||
*
|
||||
* @param string $password plain text password
|
||||
* @return string encrypted password
|
||||
*/
|
||||
private static function encryptPassword($password) {
|
||||
$cipher = self::getCipher();
|
||||
$iv = Server::get(ISecureRandom::class)->generate(16);
|
||||
$cipher->setIV($iv);
|
||||
return base64_encode($iv . $cipher->encrypt($password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a single password
|
||||
*
|
||||
* @param string $encryptedPassword encrypted password
|
||||
* @return string plain text password
|
||||
*/
|
||||
private static function decryptPassword($encryptedPassword) {
|
||||
$cipher = self::getCipher();
|
||||
$binaryPassword = base64_decode($encryptedPassword);
|
||||
$iv = substr($binaryPassword, 0, 16);
|
||||
$cipher->setIV($iv);
|
||||
$binaryPassword = substr($binaryPassword, 16);
|
||||
return $cipher->decrypt($binaryPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encryption cipher
|
||||
*
|
||||
* @return AES
|
||||
*/
|
||||
private static function getCipher() {
|
||||
$cipher = new AES(AES::MODE_CBC);
|
||||
$cipher->setKey(Server::get(IConfig::class)->getSystemValue('passwordsalt', null));
|
||||
return $cipher;
|
||||
return Server::get(EncryptionService::class)->decryptPasswords($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a hash based on the given configuration.
|
||||
* This is mostly used to find out whether configurations
|
||||
* are the same.
|
||||
*
|
||||
* @param array $config
|
||||
* @return string
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public static function makeConfigHash($config) {
|
||||
public static function makeConfigHash(array $config): string {
|
||||
$data = json_encode(
|
||||
[
|
||||
'c' => $config['backend'],
|
||||
|
|
@ -195,7 +86,8 @@ class MountConfig {
|
|||
'o' => $config['options'],
|
||||
'p' => $config['priority'] ?? -1,
|
||||
'mo' => $config['mountOptions'] ?? [],
|
||||
]
|
||||
],
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
return hash('md5', $data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@
|
|||
*/
|
||||
namespace OCA\Files_External\Service;
|
||||
|
||||
use OC\Files\Storage\Common;
|
||||
use OCA\Files_External\AppInfo\Application;
|
||||
use OCA\Files_External\Config\IConfigHandler;
|
||||
use OCA\Files_External\Config\UserContext;
|
||||
use OCA\Files_External\ConfigLexicon;
|
||||
use OCA\Files_External\Lib\Auth\AuthMechanism;
|
||||
use OCA\Files_External\Lib\Backend\Backend;
|
||||
|
|
@ -16,8 +18,11 @@ use OCA\Files_External\Lib\Config\IAuthMechanismProvider;
|
|||
use OCA\Files_External\Lib\Config\IBackendProvider;
|
||||
use OCP\EventDispatcher\GenericEvent;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\StorageNotAvailableException;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\Server;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Service class to manage backend definitions
|
||||
|
|
@ -40,24 +45,26 @@ class BackendService {
|
|||
private array $userMountingBackends = [];
|
||||
|
||||
/** @var Backend[] */
|
||||
private $backends = [];
|
||||
private array $backends = [];
|
||||
|
||||
/** @var IBackendProvider[] */
|
||||
private $backendProviders = [];
|
||||
private array $backendProviders = [];
|
||||
|
||||
/** @var AuthMechanism[] */
|
||||
private $authMechanisms = [];
|
||||
private array $authMechanisms = [];
|
||||
|
||||
/** @var IAuthMechanismProvider[] */
|
||||
private $authMechanismProviders = [];
|
||||
private array $authMechanismProviders = [];
|
||||
|
||||
/** @var callable[] */
|
||||
private $configHandlerLoaders = [];
|
||||
private array $configHandlerLoaders = [];
|
||||
|
||||
private $configHandlers = [];
|
||||
/** @var IConfigHandler[] */
|
||||
private array $configHandlers = [];
|
||||
|
||||
public function __construct(
|
||||
protected readonly IAppConfig $appConfig,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -332,9 +339,66 @@ class BackendService {
|
|||
|
||||
/**
|
||||
* @since 16.0.0
|
||||
* @return IConfigHandler[]
|
||||
*/
|
||||
public function getConfigHandlers() {
|
||||
public function getConfigHandlers(): array {
|
||||
$this->loadConfigHandlers();
|
||||
return $this->configHandlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $input
|
||||
* @return mixed
|
||||
* @throws ContainerExceptionInterface
|
||||
*/
|
||||
public function applyConfigHandlers($input, ?string $userId = null) {
|
||||
/** @var IConfigHandler[] $handlers */
|
||||
$handlers = $this->getConfigHandlers();
|
||||
foreach ($handlers as $handler) {
|
||||
if ($handler instanceof UserContext && $userId !== null) {
|
||||
$handler->setUserId($userId);
|
||||
}
|
||||
$input = $handler->handle($input);
|
||||
}
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test connecting using the given backend configuration
|
||||
*
|
||||
* @param string $class backend class name
|
||||
* @param array $options backend configuration options
|
||||
* @return StorageNotAvailableException::STATUS_*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getBackendStatus(string $class, array $options): int {
|
||||
foreach ($options as $key => &$option) {
|
||||
if ($key === 'password') {
|
||||
// no replacements in passwords
|
||||
continue;
|
||||
}
|
||||
$option = $this->applyConfigHandlers($option);
|
||||
}
|
||||
if (class_exists($class)) {
|
||||
try {
|
||||
/** @var Common $storage */
|
||||
$storage = new $class($options);
|
||||
|
||||
try {
|
||||
$result = $storage->test();
|
||||
$storage->setAvailability($result);
|
||||
if ($result) {
|
||||
return StorageNotAvailableException::STATUS_SUCCESS;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$storage->setAvailability(false);
|
||||
throw $e;
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
return StorageNotAvailableException::STATUS_ERROR;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
85
apps/files_external/lib/Service/EncryptionService.php
Normal file
85
apps/files_external/lib/Service/EncryptionService.php
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
namespace OCA\Files_External\Service;
|
||||
|
||||
use OCP\IConfig;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use phpseclib\Crypt\AES;
|
||||
|
||||
class EncryptionService {
|
||||
public function __construct(
|
||||
private IConfig $config,
|
||||
private ISecureRandom $secureRandom,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt passwords in the given config options
|
||||
*
|
||||
* @param array $options mount options
|
||||
* @return array updated options
|
||||
*/
|
||||
public function encryptPasswords(array $options): array {
|
||||
if (isset($options['password'])) {
|
||||
$options['password_encrypted'] = $this->encryptPassword($options['password']);
|
||||
// do not unset the password, we want to keep the keys order
|
||||
// on load... because that's how the UI currently works
|
||||
$options['password'] = '';
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt passwords in the given config options
|
||||
*
|
||||
* @param array $options mount options
|
||||
* @return array updated options
|
||||
*/
|
||||
public function decryptPasswords(array $options): array {
|
||||
// note: legacy options might still have the unencrypted password in the "password" field
|
||||
if (isset($options['password_encrypted'])) {
|
||||
$options['password'] = $this->decryptPassword($options['password_encrypted']);
|
||||
unset($options['password_encrypted']);
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a single password
|
||||
*/
|
||||
private function encryptPassword(string $password): string {
|
||||
$cipher = $this->getCipher();
|
||||
$iv = $this->secureRandom->generate(16);
|
||||
$cipher->setIV($iv);
|
||||
return base64_encode($iv . $cipher->encrypt($password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a single password
|
||||
*/
|
||||
private function decryptPassword(string $encryptedPassword): string {
|
||||
$cipher = $this->getCipher();
|
||||
$binaryPassword = base64_decode($encryptedPassword);
|
||||
$iv = substr($binaryPassword, 0, 16);
|
||||
$cipher->setIV($iv);
|
||||
$binaryPassword = substr($binaryPassword, 16);
|
||||
return $cipher->decrypt($binaryPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encryption cipher
|
||||
*/
|
||||
private function getCipher(): AES {
|
||||
$cipher = new AES(AES::MODE_CBC);
|
||||
$cipher->setKey($this->config->getSystemValue('passwordsalt', null));
|
||||
return $cipher;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,13 +11,6 @@ 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ use Psr\Log\LoggerInterface;
|
|||
* Read mount config from legacy mount.json
|
||||
*/
|
||||
abstract class LegacyStoragesService {
|
||||
/** @var BackendService */
|
||||
protected $backendService;
|
||||
|
||||
public function __construct(
|
||||
protected BackendService $backendService,
|
||||
protected EncryptionService $encryptionService,
|
||||
) {
|
||||
}
|
||||
/**
|
||||
* Read legacy config data
|
||||
*
|
||||
|
|
@ -132,7 +134,7 @@ abstract class LegacyStoragesService {
|
|||
$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'] = MountConfig::decryptPasswords($storageOptions['options']);
|
||||
$storageOptions['options'] = $this->encryptionService->decryptPasswords($storageOptions['options']);
|
||||
if (!isset($storageOptions['backend'])) {
|
||||
$storageOptions['backend'] = $storageOptions['class']; // legacy compat
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,7 +125,12 @@ class ConfigAdapterTest extends TestCase {
|
|||
|
||||
$this->userStoragesService = Server::get(UserStoragesService::class);
|
||||
$this->userGlobalStoragesService = Server::get(UserGlobalStoragesService::class);
|
||||
$this->adapter = new ConfigAdapter($this->userStoragesService, $this->userGlobalStoragesService, $this->createMock(ClockInterface::class));
|
||||
$this->adapter = new ConfigAdapter(
|
||||
$this->userStoragesService,
|
||||
$this->userGlobalStoragesService,
|
||||
$this->createMock(BackendService::class),
|
||||
$this->createMock(ClockInterface::class),
|
||||
);
|
||||
|
||||
$this->user = $this->createMock(IUser::class);
|
||||
$this->user->method('getUID')->willReturn('user1');
|
||||
|
|
|
|||
|
|
@ -49,7 +49,8 @@ class GlobalStoragesControllerTest extends StoragesControllerTestCase {
|
|||
$this->createMock(LoggerInterface::class),
|
||||
$session,
|
||||
$this->createMock(IGroupManager::class),
|
||||
$config
|
||||
$config,
|
||||
$this->createMock(BackendService::class),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ use OCA\Files_External\Lib\Auth\NullMechanism;
|
|||
use OCA\Files_External\Lib\Backend\Backend;
|
||||
use OCA\Files_External\Lib\Backend\SMB;
|
||||
use OCA\Files_External\Lib\StorageConfig;
|
||||
use OCA\Files_External\MountConfig;
|
||||
use OCA\Files_External\NotFoundException;
|
||||
use OCA\Files_External\Service\GlobalStoragesService;
|
||||
use OCA\Files_External\Service\UserStoragesService;
|
||||
|
|
@ -28,11 +27,9 @@ abstract class StoragesControllerTestCase extends \Test\TestCase {
|
|||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
MountConfig::$skipTest = true;
|
||||
}
|
||||
|
||||
protected function tearDown(): void {
|
||||
MountConfig::$skipTest = false;
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,8 @@ class UserStoragesControllerTest extends StoragesControllerTestCase {
|
|||
$this->createMock(LoggerInterface::class),
|
||||
$session,
|
||||
$this->createMock(IGroupManager::class),
|
||||
$config
|
||||
$config,
|
||||
$this->createMock(BackendService::class),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,15 @@ use OCA\Files_External\Lib\Config\IBackendProvider;
|
|||
use OCA\Files_External\Service\BackendService;
|
||||
use OCP\IAppConfig;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class BackendServiceTest extends \Test\TestCase {
|
||||
protected IAppConfig&MockObject $appConfig;
|
||||
protected LoggerInterface&MockObject $logger;
|
||||
|
||||
protected function setUp(): void {
|
||||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -46,7 +49,7 @@ class BackendServiceTest extends \Test\TestCase {
|
|||
}
|
||||
|
||||
public function testRegisterBackend(): void {
|
||||
$service = new BackendService($this->appConfig);
|
||||
$service = new BackendService($this->appConfig, $this->logger);
|
||||
|
||||
$backend = $this->getBackendMock('\Foo\Bar');
|
||||
|
||||
|
|
@ -72,7 +75,7 @@ class BackendServiceTest extends \Test\TestCase {
|
|||
}
|
||||
|
||||
public function testBackendProvider(): void {
|
||||
$service = new BackendService($this->appConfig);
|
||||
$service = new BackendService($this->appConfig, $this->logger);
|
||||
|
||||
$backend1 = $this->getBackendMock('\Foo\Bar');
|
||||
$backend2 = $this->getBackendMock('\Bar\Foo');
|
||||
|
|
@ -91,7 +94,7 @@ class BackendServiceTest extends \Test\TestCase {
|
|||
}
|
||||
|
||||
public function testAuthMechanismProvider(): void {
|
||||
$service = new BackendService($this->appConfig);
|
||||
$service = new BackendService($this->appConfig, $this->logger);
|
||||
|
||||
$backend1 = $this->getAuthMechanismMock('\Foo\Bar');
|
||||
$backend2 = $this->getAuthMechanismMock('\Bar\Foo');
|
||||
|
|
@ -110,7 +113,7 @@ class BackendServiceTest extends \Test\TestCase {
|
|||
}
|
||||
|
||||
public function testMultipleBackendProviders(): void {
|
||||
$service = new BackendService($this->appConfig);
|
||||
$service = new BackendService($this->appConfig, $this->logger);
|
||||
|
||||
$backend1a = $this->getBackendMock('\Foo\Bar');
|
||||
$backend1b = $this->getBackendMock('\Bar\Foo');
|
||||
|
|
@ -147,7 +150,7 @@ class BackendServiceTest extends \Test\TestCase {
|
|||
->with('files_external', 'allow_user_mounting')
|
||||
->willReturn(true);
|
||||
|
||||
$service = new BackendService($this->appConfig);
|
||||
$service = new BackendService($this->appConfig, $this->logger);
|
||||
|
||||
$backendAllowed = $this->getBackendMock('\User\Mount\Allowed');
|
||||
$backendAllowed->expects($this->never())
|
||||
|
|
@ -171,7 +174,7 @@ class BackendServiceTest extends \Test\TestCase {
|
|||
}
|
||||
|
||||
public function testGetAvailableBackends(): void {
|
||||
$service = new BackendService($this->appConfig);
|
||||
$service = new BackendService($this->appConfig, $this->logger);
|
||||
|
||||
$backendAvailable = $this->getBackendMock('\Backend\Available');
|
||||
$backendAvailable->expects($this->once())
|
||||
|
|
@ -212,7 +215,7 @@ class BackendServiceTest extends \Test\TestCase {
|
|||
public function testRegisterConfigHandlerInvalid(array $placeholders): void {
|
||||
$this->expectException(\RuntimeException::class);
|
||||
|
||||
$service = new BackendService($this->appConfig);
|
||||
$service = new BackendService($this->appConfig, $this->logger);
|
||||
$mock = $this->createMock(IConfigHandler::class);
|
||||
$cb = function () use ($mock) {
|
||||
return $mock;
|
||||
|
|
@ -223,7 +226,7 @@ class BackendServiceTest extends \Test\TestCase {
|
|||
}
|
||||
|
||||
public function testConfigHandlers(): void {
|
||||
$service = new BackendService($this->appConfig);
|
||||
$service = new BackendService($this->appConfig, $this->logger);
|
||||
$mock = $this->createMock(IConfigHandler::class);
|
||||
$mock->expects($this->exactly(3))
|
||||
->method('handle');
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ use OCA\Files_External\Lib\Backend\Backend;
|
|||
use OCA\Files_External\Lib\Backend\InvalidBackend;
|
||||
use OCA\Files_External\Lib\Backend\SMB;
|
||||
use OCA\Files_External\Lib\StorageConfig;
|
||||
use OCA\Files_External\MountConfig;
|
||||
use OCA\Files_External\NotFoundException;
|
||||
use OCA\Files_External\Service\BackendService;
|
||||
use OCA\Files_External\Service\DBConfigService;
|
||||
|
|
@ -72,7 +71,6 @@ abstract class StoragesServiceTestCase extends \Test\TestCase {
|
|||
'datadirectory',
|
||||
\OC::$SERVERROOT . '/data/'
|
||||
);
|
||||
MountConfig::$skipTest = true;
|
||||
|
||||
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
|
||||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
|
|
@ -140,7 +138,6 @@ abstract class StoragesServiceTestCase extends \Test\TestCase {
|
|||
}
|
||||
|
||||
protected function tearDown(): void {
|
||||
MountConfig::$skipTest = false;
|
||||
self::$hookCalls = [];
|
||||
if ($this->dbConfig) {
|
||||
$this->dbConfig->clean();
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
namespace OCA\Files_External\Tests\Storage;
|
||||
|
||||
use OCA\Files_External\Lib\Storage\SFTP_Key;
|
||||
|
||||
/**
|
||||
* Class SFTP_KeyTest
|
||||
*
|
||||
*
|
||||
* @package OCA\Files_External\Tests\Storage
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\Group(name: 'DB')]
|
||||
class SFTP_KeyTest extends \Test\Files\Storage\Storage {
|
||||
use ConfigurableStorageTrait;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$id = $this->getUniqueID();
|
||||
$this->loadConfig(__DIR__ . '/../config.php');
|
||||
// Make sure we have an new empty folder to work in
|
||||
$this->config['sftp_key']['root'] .= '/' . $id;
|
||||
$this->instance = new SFTP_Key($this->config['sftp_key']);
|
||||
$this->instance->mkdir('/');
|
||||
}
|
||||
|
||||
protected function shouldRunConfig(mixed $config): bool {
|
||||
return is_array($config) && ($config['sftp_key']['run'] ?? false);
|
||||
}
|
||||
|
||||
protected function tearDown(): void {
|
||||
if ($this->instance) {
|
||||
$this->instance->rmdir('/');
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
|
||||
public function testInvalidAddressShouldThrowException(): void {
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
// I'd use example.com for this, but someone decided to break the spec and make it resolve
|
||||
$this->instance->assertHostAddressValid('notarealaddress...');
|
||||
}
|
||||
|
||||
public function testValidAddressShouldPass(): void {
|
||||
$this->assertTrue($this->instance->assertHostAddressValid('localhost'));
|
||||
}
|
||||
|
||||
|
||||
public function testNegativePortNumberShouldThrowException(): void {
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$this->instance->assertPortNumberValid('-1');
|
||||
}
|
||||
|
||||
|
||||
public function testNonNumericalPortNumberShouldThrowException(): void {
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$this->instance->assertPortNumberValid('a');
|
||||
}
|
||||
|
||||
|
||||
public function testHighPortNumberShouldThrowException(): void {
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$this->instance->assertPortNumberValid('65536');
|
||||
}
|
||||
|
||||
public function testValidPortNumberShouldPass(): void {
|
||||
$this->assertTrue($this->instance->assertPortNumberValid('22222'));
|
||||
}
|
||||
}
|
||||
|
|
@ -1594,14 +1594,6 @@
|
|||
<code><![CDATA[login]]></code>
|
||||
</InvalidReturnType>
|
||||
</file>
|
||||
<file src="apps/files_external/lib/MountConfig.php">
|
||||
<InternalMethod>
|
||||
<code><![CDATA[decrypt]]></code>
|
||||
<code><![CDATA[encrypt]]></code>
|
||||
<code><![CDATA[setIV]]></code>
|
||||
<code><![CDATA[setIV]]></code>
|
||||
</InternalMethod>
|
||||
</file>
|
||||
<file src="apps/files_external/lib/Service/BackendService.php">
|
||||
<DeprecatedClass>
|
||||
<code><![CDATA[new GenericEvent()]]></code>
|
||||
|
|
@ -1614,6 +1606,14 @@
|
|||
<code><![CDATA[registerBackends]]></code>
|
||||
</DeprecatedMethod>
|
||||
</file>
|
||||
<file src="apps/files_external/lib/Service/EncryptionService.php">
|
||||
<InternalMethod>
|
||||
<code><![CDATA[decrypt]]></code>
|
||||
<code><![CDATA[encrypt]]></code>
|
||||
<code><![CDATA[setIV]]></code>
|
||||
<code><![CDATA[setIV]]></code>
|
||||
</InternalMethod>
|
||||
</file>
|
||||
<file src="apps/files_external/lib/Service/StoragesService.php">
|
||||
<DeprecatedMethod>
|
||||
<code><![CDATA[Util::emitHook(
|
||||
|
|
@ -2594,8 +2594,6 @@
|
|||
<code><![CDATA[getAppValue]]></code>
|
||||
<code><![CDATA[getAppValue]]></code>
|
||||
<code><![CDATA[getAppValue]]></code>
|
||||
<code><![CDATA[getAppValue]]></code>
|
||||
<code><![CDATA[getAppValue]]></code>
|
||||
<code><![CDATA[setAppValue]]></code>
|
||||
<code><![CDATA[setAppValue]]></code>
|
||||
</DeprecatedMethod>
|
||||
|
|
|
|||
Loading…
Reference in a new issue