diff options
Diffstat (limited to 'apps/files_external/lib')
118 files changed, 2735 insertions, 4334 deletions
diff --git a/apps/files_external/lib/AppInfo/Application.php b/apps/files_external/lib/AppInfo/Application.php index 6f8018746b3..a6c2aff947b 100644 --- a/apps/files_external/lib/AppInfo/Application.php +++ b/apps/files_external/lib/AppInfo/Application.php @@ -1,40 +1,19 @@ <?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> - * @author Ross Nicoll <jrn@jrn.me.uk> - * @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/> - * + * 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\AppInfo; +use OCA\Files\Event\LoadAdditionalScriptsEvent; use OCA\Files_External\Config\ConfigAdapter; use OCA\Files_External\Config\UserPlaceholderHandler; +use OCA\Files_External\ConfigLexicon; use OCA\Files_External\Lib\Auth\AmazonS3\AccessKey; use OCA\Files_External\Lib\Auth\Builtin; use OCA\Files_External\Lib\Auth\NullMechanism; -use OCA\Files_External\Lib\Auth\OAuth1\OAuth1; use OCA\Files_External\Lib\Auth\OAuth2\OAuth2; use OCA\Files_External\Lib\Auth\OpenStack\OpenStackV2; use OCA\Files_External\Lib\Auth\OpenStack\OpenStackV3; @@ -62,52 +41,45 @@ use OCA\Files_External\Lib\Backend\Swift; use OCA\Files_External\Lib\Config\IAuthMechanismProvider; use OCA\Files_External\Lib\Config\IBackendProvider; use OCA\Files_External\Listener\GroupDeletedListener; +use OCA\Files_External\Listener\LoadAdditionalListener; use OCA\Files_External\Listener\UserDeletedListener; use OCA\Files_External\Service\BackendService; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; +use OCP\AppFramework\QueryException; use OCP\Files\Config\IMountProviderCollection; use OCP\Group\Events\GroupDeletedEvent; use OCP\User\Events\UserDeletedEvent; -require_once __DIR__ . '/../../3rdparty/autoload.php'; - /** * @package OCA\Files_External\AppInfo */ class Application extends App implements IBackendProvider, IAuthMechanismProvider, IBootstrap { + public const APP_ID = 'files_external'; /** * Application constructor. * - * @throws \OCP\AppFramework\QueryException + * @throws QueryException */ public function __construct(array $urlParams = []) { - parent::__construct('files_external', $urlParams); + parent::__construct(self::APP_ID, $urlParams); } public function register(IRegistrationContext $context): void { $context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class); $context->registerEventListener(GroupDeletedEvent::class, GroupDeletedListener::class); + $context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class); + $context->registerConfigLexicon(ConfigLexicon::class); } public function boot(IBootContext $context): void { - $context->injectFn(function (IMountProviderCollection $mountProviderCollection, ConfigAdapter $configAdapter) { + $context->injectFn(function (IMountProviderCollection $mountProviderCollection, ConfigAdapter $configAdapter): void { $mountProviderCollection->registerProvider($configAdapter); }); - \OCA\Files\App::getNavigationManager()->add(function () { - $l = \OC::$server->getL10N('files_external'); - return [ - 'id' => 'extstoragemounts', - 'appname' => 'files_external', - 'script' => 'list.php', - 'order' => 30, - 'name' => $l->t('External storage'), - ]; - }); - $context->injectFn(function (BackendService $backendService, UserPlaceholderHandler $userConfigHandler) { + $context->injectFn(function (BackendService $backendService, UserPlaceholderHandler $userConfigHandler): void { $backendService->registerBackendProvider($this); $backendService->registerAuthMechanismProvider($this); $backendService->registerConfigHandler('user', function () use ($userConfigHandler) { @@ -163,9 +135,6 @@ class Application extends App implements IBackendProvider, IAuthMechanismProvide $container->get(GlobalAuth::class), $container->get(UserGlobalAuth::class), - // AuthMechanism::SCHEME_OAUTH1 mechanisms - $container->get(OAuth1::class), - // AuthMechanism::SCHEME_OAUTH2 mechanisms $container->get(OAuth2::class), diff --git a/apps/files_external/lib/BackgroundJob/CredentialsCleanup.php b/apps/files_external/lib/BackgroundJob/CredentialsCleanup.php index 138d4d7de2d..90a5ae17ab2 100644 --- a/apps/files_external/lib/BackgroundJob/CredentialsCleanup.php +++ b/apps/files_external/lib/BackgroundJob/CredentialsCleanup.php @@ -3,25 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl> - * - * @author Robin Appelman <robin@icewind.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\BackgroundJob; @@ -29,36 +12,27 @@ use OCA\Files_External\Lib\Auth\Password\LoginCredentials; use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\Service\UserGlobalStoragesService; use OCP\AppFramework\Utility\ITimeFactory; -use OCP\BackgroundJob\IJob; use OCP\BackgroundJob\TimedJob; -use OCP\Security\ICredentialsManager; use OCP\IUser; use OCP\IUserManager; +use OCP\Security\ICredentialsManager; class CredentialsCleanup extends TimedJob { - private $credentialsManager; - private $userGlobalStoragesService; - private $userManager; - public function __construct( ITimeFactory $time, - ICredentialsManager $credentialsManager, - UserGlobalStoragesService $userGlobalStoragesService, - IUserManager $userManager + private ICredentialsManager $credentialsManager, + private UserGlobalStoragesService $userGlobalStoragesService, + private IUserManager $userManager, ) { parent::__construct($time); - $this->credentialsManager = $credentialsManager; - $this->userGlobalStoragesService = $userGlobalStoragesService; - $this->userManager = $userManager; - // run every day $this->setInterval(24 * 60 * 60); - $this->setTimeSensitivity(IJob::TIME_INSENSITIVE); + $this->setTimeSensitivity(self::TIME_INSENSITIVE); } protected function run($argument) { - $this->userManager->callForSeenUsers(function (IUser $user) { + $this->userManager->callForSeenUsers(function (IUser $user): void { $storages = $this->userGlobalStoragesService->getAllStoragesForUser($user); $usesLoginCredentials = array_reduce($storages, function (bool $uses, StorageConfig $storage) { diff --git a/apps/files_external/lib/Command/Applicable.php b/apps/files_external/lib/Command/Applicable.php index 29d5459200e..4d5e264bfaf 100644 --- a/apps/files_external/lib/Command/Applicable.php +++ b/apps/files_external/lib/Command/Applicable.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.nl> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; @@ -28,6 +11,7 @@ use OC\Core\Command\Base; use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\NotFoundException; use OCA\Files_External\Service\GlobalStoragesService; +use OCP\AppFramework\Http; use OCP\IGroupManager; use OCP\IUserManager; use Symfony\Component\Console\Input\InputArgument; @@ -36,33 +20,15 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class Applicable extends Base { - /** - * @var GlobalStoragesService - */ - protected $globalService; - - /** - * @var IUserManager - */ - private $userManager; - - /** - * @var IGroupManager - */ - private $groupManager; - public function __construct( - GlobalStoragesService $globalService, - IUserManager $userManager, - IGroupManager $groupManager + protected GlobalStoragesService $globalService, + private IUserManager $userManager, + private IGroupManager $groupManager, ) { parent::__construct(); - $this->globalService = $globalService; - $this->userManager = $userManager; - $this->groupManager = $groupManager; } - protected function configure() { + protected function configure(): void { $this ->setName('files_external:applicable') ->setDescription('Manage applicable users and groups for a mount') @@ -105,12 +71,12 @@ class Applicable extends Base { $mount = $this->globalService->getStorage($mountId); } catch (NotFoundException $e) { $output->writeln('<error>Mount with id "' . $mountId . ' not found, check "occ files_external:list" to get available mounts</error>'); - return 404; + return Http::STATUS_NOT_FOUND; } - if ($mount->getType() === StorageConfig::MOUNT_TYPE_PERSONAl) { + if ($mount->getType() === StorageConfig::MOUNT_TYPE_PERSONAL) { $output->writeln('<error>Can\'t change applicables on personal mounts</error>'); - return 1; + return self::FAILURE; } $addUsers = $input->getOption('add-user'); @@ -125,13 +91,13 @@ class Applicable extends Base { foreach ($addUsers as $addUser) { if (!$this->userManager->userExists($addUser)) { $output->writeln('<error>User "' . $addUser . '" not found</error>'); - return 404; + return Http::STATUS_NOT_FOUND; } } foreach ($addGroups as $addGroup) { if (!$this->groupManager->groupExists($addGroup)) { $output->writeln('<error>Group "' . $addGroup . '" not found</error>'); - return 404; + return Http::STATUS_NOT_FOUND; } } @@ -153,6 +119,6 @@ class Applicable extends Base { 'users' => $applicableUsers, 'groups' => $applicableGroups ]); - return 0; + return self::SUCCESS; } } diff --git a/apps/files_external/lib/Command/Backends.php b/apps/files_external/lib/Command/Backends.php index 60d035f997f..7fab0477adf 100644 --- a/apps/files_external/lib/Command/Backends.php +++ b/apps/files_external/lib/Command/Backends.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @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/> - * + * 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\Command; @@ -33,17 +17,13 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class Backends extends Base { - /** @var BackendService */ - private $backendService; - - public function __construct(BackendService $backendService + public function __construct( + private BackendService $backendService, ) { parent::__construct(); - - $this->backendService = $backendService; } - protected function configure() { + protected function configure(): void { $this ->setName('files_external:backends') ->setDescription('Show available authentication and storage backends') @@ -73,24 +53,24 @@ class Backends extends Base { if ($type) { if (!isset($data[$type])) { $output->writeln('<error>Invalid type "' . $type . '". Possible values are "authentication" or "storage"</error>'); - return 1; + return self::FAILURE; } $data = $data[$type]; if ($backend) { if (!isset($data[$backend])) { $output->writeln('<error>Unknown backend "' . $backend . '" of type "' . $type . '"</error>'); - return 1; + return self::FAILURE; } $data = $data[$backend]; } } $this->writeArrayInOutputFormat($input, $output, $data); - return 0; + return self::SUCCESS; } - private function serializeAuthBackend(\JsonSerializable $backend) { + private function serializeAuthBackend(\JsonSerializable $backend): array { $data = $backend->jsonSerialize(); $result = [ 'name' => $data['name'], @@ -113,9 +93,9 @@ class Backends extends Base { * @param DefinitionParameter[] $parameters * @return string[] */ - private function formatConfiguration(array $parameters) { + private function formatConfiguration(array $parameters): array { $configuration = array_filter($parameters, function (DefinitionParameter $parameter) { - return $parameter->getType() !== DefinitionParameter::VALUE_HIDDEN; + return $parameter->isFlagSet(DefinitionParameter::FLAG_HIDDEN); }); return array_map(function (DefinitionParameter $parameter) { return $parameter->getTypeName(); diff --git a/apps/files_external/lib/Command/Config.php b/apps/files_external/lib/Command/Config.php index 5d0675b04bc..883b4a2f3e7 100644 --- a/apps/files_external/lib/Command/Config.php +++ b/apps/files_external/lib/Command/Config.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Ardinis <Ardinis@users.noreply.github.com> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @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/> - * + * 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\Command; @@ -28,22 +11,19 @@ use OC\Core\Command\Base; use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\NotFoundException; use OCA\Files_External\Service\GlobalStoragesService; +use OCP\AppFramework\Http; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class Config extends Base { - /** - * @var GlobalStoragesService - */ - protected $globalService; - - public function __construct(GlobalStoragesService $globalService) { + public function __construct( + protected GlobalStoragesService $globalService, + ) { parent::__construct(); - $this->globalService = $globalService; } - protected function configure() { + protected function configure(): void { $this ->setName('files_external:config') ->setDescription('Manage backend configuration for a mount') @@ -70,7 +50,7 @@ class Config extends Base { $mount = $this->globalService->getStorage($mountId); } catch (NotFoundException $e) { $output->writeln('<error>Mount with id "' . $mountId . ' not found, check "occ files_external:list" to get available mounts"</error>'); - return 404; + return Http::STATUS_NOT_FOUND; } $value = $input->getArgument('value'); @@ -79,15 +59,13 @@ class Config extends Base { } else { $this->getOption($mount, $key, $output); } - return 0; + return self::SUCCESS; } /** - * @param StorageConfig $mount * @param string $key - * @param OutputInterface $output */ - protected function getOption(StorageConfig $mount, $key, OutputInterface $output) { + protected function getOption(StorageConfig $mount, $key, OutputInterface $output): void { if ($key === 'mountpoint' || $key === 'mount_point') { $value = $mount->getMountPoint(); } else { @@ -96,16 +74,14 @@ class Config extends Base { if (!is_string($value) && json_decode(json_encode($value)) === $value) { // show bools and objects correctly $value = json_encode($value); } - $output->writeln($value); + $output->writeln((string)$value); } /** - * @param StorageConfig $mount * @param string $key * @param string $value - * @param OutputInterface $output */ - protected function setOption(StorageConfig $mount, $key, $value, OutputInterface $output) { + protected function setOption(StorageConfig $mount, $key, $value, OutputInterface $output): void { $decoded = json_decode($value, true); if (!is_null($decoded) && json_encode($decoded) === $value) { $value = $decoded; diff --git a/apps/files_external/lib/Command/Create.php b/apps/files_external/lib/Command/Create.php index 99991b893cd..3307015518a 100644 --- a/apps/files_external/lib/Command/Create.php +++ b/apps/files_external/lib/Command/Create.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.nl> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; @@ -33,7 +16,9 @@ use OCA\Files_External\Lib\DefinitionParameter; use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\Service\BackendService; use OCA\Files_External\Service\GlobalStoragesService; +use OCA\Files_External\Service\StoragesService; use OCA\Files_External\Service\UserStoragesService; +use OCP\AppFramework\Http; use OCP\IUserManager; use OCP\IUserSession; use Symfony\Component\Console\Input\ArrayInput; @@ -43,42 +28,17 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class Create extends Base { - /** - * @var GlobalStoragesService - */ - private $globalService; - - /** - * @var UserStoragesService - */ - private $userService; - - /** - * @var IUserManager - */ - private $userManager; - - /** @var BackendService */ - private $backendService; - - /** @var IUserSession */ - private $userSession; - - public function __construct(GlobalStoragesService $globalService, - UserStoragesService $userService, - IUserManager $userManager, - IUserSession $userSession, - BackendService $backendService + public function __construct( + private GlobalStoragesService $globalService, + private UserStoragesService $userService, + private IUserManager $userManager, + private IUserSession $userSession, + private BackendService $backendService, ) { parent::__construct(); - $this->globalService = $globalService; - $this->userService = $userService; - $this->userManager = $userManager; - $this->userSession = $userSession; - $this->backendService = $backendService; } - protected function configure() { + protected function configure(): void { $this ->setName('files_external:create') ->setDescription('Create a new mount configuration') @@ -119,7 +79,7 @@ class Create extends Base { } protected function execute(InputInterface $input, OutputInterface $output): int { - $user = (string) $input->getOption('user'); + $user = (string)$input->getOption('user'); $mountPoint = $input->getArgument('mount_point'); $storageIdentifier = $input->getArgument('storage_backend'); $authIdentifier = $input->getArgument('authentication_backend'); @@ -130,32 +90,32 @@ class Create extends Base { if (!Filesystem::isValidPath($mountPoint)) { $output->writeln('<error>Invalid mountpoint "' . $mountPoint . '"</error>'); - return 1; + return self::FAILURE; } if (is_null($storageBackend)) { $output->writeln('<error>Storage backend with identifier "' . $storageIdentifier . '" not found (see `occ files_external:backends` for possible values)</error>'); - return 404; + return Http::STATUS_NOT_FOUND; } if (is_null($authBackend)) { $output->writeln('<error>Authentication backend with identifier "' . $authIdentifier . '" not found (see `occ files_external:backends` for possible values)</error>'); - return 404; + return Http::STATUS_NOT_FOUND; } $supportedSchemes = array_keys($storageBackend->getAuthSchemes()); if (!in_array($authBackend->getScheme(), $supportedSchemes)) { $output->writeln('<error>Authentication backend "' . $authIdentifier . '" not valid for storage backend "' . $storageIdentifier . '" (see `occ files_external:backends storage ' . $storageIdentifier . '` for possible values)</error>'); - return 1; + return self::FAILURE; } $config = []; foreach ($configInput as $configOption) { - if (!strpos($configOption, '=')) { + if (!str_contains($configOption, '=')) { $output->writeln('<error>Invalid mount configuration option "' . $configOption . '"</error>'); - return 1; + return self::FAILURE; } [$key, $value] = explode('=', $configOption, 2); if (!$this->validateParam($key, $value, $storageBackend, $authBackend)) { $output->writeln('<error>Unknown configuration for backends "' . $key . '"</error>'); - return 1; + return self::FAILURE; } $config[$key] = $value; } @@ -169,7 +129,7 @@ class Create extends Base { if ($user) { if (!$this->userManager->userExists($user)) { $output->writeln('<error>User "' . $user . '" not found</error>'); - return 1; + return self::FAILURE; } $mount->setApplicableUsers([$user]); } @@ -184,10 +144,10 @@ class Create extends Base { $output->writeln((string)$mount->getId()); } } - return 0; + return self::SUCCESS; } - private function validateParam($key, &$value, Backend $storageBackend, AuthMechanism $authBackend) { + private function validateParam(string $key, &$value, Backend $storageBackend, AuthMechanism $authBackend): bool { $params = array_merge($storageBackend->getParameters(), $authBackend->getParameters()); foreach ($params as $param) { /** @var DefinitionParameter $param */ @@ -201,7 +161,7 @@ class Create extends Base { return false; } - private function showMount($user, StorageConfig $mount, InputInterface $input, OutputInterface $output) { + private function showMount(string $user, StorageConfig $mount, InputInterface $input, OutputInterface $output): void { $listCommand = new ListCommand($this->globalService, $this->userService, $this->userSession, $this->userManager); $listInput = new ArrayInput([], $listCommand->getDefinition()); $listInput->setOption('output', $input->getOption('output')); @@ -209,16 +169,16 @@ class Create extends Base { $listCommand->listMounts($user, [$mount], $listInput, $output); } - protected function getStorageService($userId) { - if (!empty($userId)) { - $user = $this->userManager->get($userId); - if (is_null($user)) { - throw new NoUserException("user $userId not found"); - } - $this->userSession->setUser($user); - return $this->userService; - } else { + protected function getStorageService(string $userId): StoragesService { + if (empty($userId)) { return $this->globalService; } + + $user = $this->userManager->get($userId); + if (is_null($user)) { + throw new NoUserException("user $userId not found"); + } + $this->userSession->setUser($user); + return $this->userService; } } diff --git a/apps/files_external/lib/Command/Delete.php b/apps/files_external/lib/Command/Delete.php index a41161792b5..9f121250f7d 100644 --- a/apps/files_external/lib/Command/Delete.php +++ b/apps/files_external/lib/Command/Delete.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; @@ -27,8 +11,10 @@ use OC\Core\Command\Base; use OCA\Files_External\NotFoundException; use OCA\Files_External\Service\GlobalStoragesService; use OCA\Files_External\Service\UserStoragesService; +use OCP\AppFramework\Http; use OCP\IUserManager; use OCP\IUserSession; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -37,35 +23,17 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; class Delete extends Base { - /** - * @var GlobalStoragesService - */ - protected $globalService; - - /** - * @var UserStoragesService - */ - protected $userService; - - /** - * @var IUserSession - */ - protected $userSession; - - /** - * @var IUserManager - */ - protected $userManager; - - public function __construct(GlobalStoragesService $globalService, UserStoragesService $userService, IUserSession $userSession, IUserManager $userManager) { + public function __construct( + protected GlobalStoragesService $globalService, + protected UserStoragesService $userService, + protected IUserSession $userSession, + protected IUserManager $userManager, + protected QuestionHelper $questionHelper, + ) { parent::__construct(); - $this->globalService = $globalService; - $this->userService = $userService; - $this->userSession = $userSession; - $this->userManager = $userManager; } - protected function configure() { + protected function configure(): void { $this ->setName('files_external:delete') ->setDescription('Delete an external mount') @@ -88,7 +56,7 @@ class Delete extends Base { $mount = $this->globalService->getStorage($mountId); } catch (NotFoundException $e) { $output->writeln('<error>Mount with id "' . $mountId . ' not found, check "occ files_external:list" to get available mounts"</error>'); - return 404; + return Http::STATUS_NOT_FOUND; } $noConfirm = $input->getOption('yes'); @@ -99,15 +67,16 @@ class Delete extends Base { $listInput->setOption('output', $input->getOption('output')); $listCommand->listMounts(null, [$mount], $listInput, $output); + /** @var QuestionHelper $questionHelper */ $questionHelper = $this->getHelper('question'); $question = new ConfirmationQuestion('Delete this mount? [y/N] ', false); if (!$questionHelper->ask($input, $output, $question)) { - return 1; + return self::FAILURE; } } $this->globalService->removeStorage($mountId); - return 0; + return self::SUCCESS; } } diff --git a/apps/files_external/lib/Command/Dependencies.php b/apps/files_external/lib/Command/Dependencies.php new file mode 100644 index 00000000000..d2db8a8c9af --- /dev/null +++ b/apps/files_external/lib/Command/Dependencies.php @@ -0,0 +1,57 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCA\Files_External\Command; + +use OC\Core\Command\Base; +use OCA\Files_External\Service\BackendService; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Dependencies extends Base { + public function __construct( + private readonly BackendService $backendService, + ) { + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName('files_external:dependencies') + ->setDescription('Show information about the backend dependencies'); + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $storageBackends = $this->backendService->getBackends(); + + $anyMissing = false; + + foreach ($storageBackends as $backend) { + if ($backend->getDeprecateTo() !== null) { + continue; + } + $missingDependencies = $backend->checkDependencies(); + if ($missingDependencies) { + $anyMissing = true; + $output->writeln($backend->getText() . ':'); + foreach ($missingDependencies as $missingDependency) { + if ($missingDependency->getMessage()) { + $output->writeln(" - <comment>{$missingDependency->getDependency()}</comment>: {$missingDependency->getMessage()}"); + } else { + $output->writeln(" - <comment>{$missingDependency->getDependency()}</comment>"); + } + } + } + } + + if (!$anyMissing) { + $output->writeln('<info>All dependencies are met</info>'); + } + + return self::SUCCESS; + } +} diff --git a/apps/files_external/lib/Command/Export.php b/apps/files_external/lib/Command/Export.php index 622c4b46078..59484d0e788 100644 --- a/apps/files_external/lib/Command/Export.php +++ b/apps/files_external/lib/Command/Export.php @@ -1,24 +1,9 @@ <?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/> - * + * 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\Command; @@ -29,7 +14,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class Export extends ListCommand { - protected function configure() { + protected function configure(): void { $this ->setName('files_external:export') ->setDescription('Export mount configurations') @@ -54,6 +39,6 @@ class Export extends ListCommand { $listInput->setOption('show-password', true); $listInput->setOption('full', true); $listCommand->execute($listInput, $output); - return 0; + return self::SUCCESS; } } diff --git a/apps/files_external/lib/Command/Import.php b/apps/files_external/lib/Command/Import.php index 53ae0d7c5bf..a9ed76fbe40 100644 --- a/apps/files_external/lib/Command/Import.php +++ b/apps/files_external/lib/Command/Import.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.nl> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; @@ -30,6 +13,7 @@ use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\Service\BackendService; use OCA\Files_External\Service\GlobalStoragesService; use OCA\Files_External\Service\ImportLegacyStoragesService; +use OCA\Files_External\Service\StoragesService; use OCA\Files_External\Service\UserStoragesService; use OCP\IUserManager; use OCP\IUserSession; @@ -40,49 +24,18 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class Import extends Base { - /** - * @var GlobalStoragesService - */ - private $globalService; - - /** - * @var UserStoragesService - */ - private $userService; - - /** - * @var IUserSession - */ - private $userSession; - - /** - * @var IUserManager - */ - private $userManager; - - /** @var ImportLegacyStoragesService */ - private $importLegacyStorageService; - - /** @var BackendService */ - private $backendService; - - public function __construct(GlobalStoragesService $globalService, - UserStoragesService $userService, - IUserSession $userSession, - IUserManager $userManager, - ImportLegacyStoragesService $importLegacyStorageService, - BackendService $backendService + public function __construct( + private GlobalStoragesService $globalService, + private UserStoragesService $userService, + private IUserSession $userSession, + private IUserManager $userManager, + private ImportLegacyStoragesService $importLegacyStorageService, + private BackendService $backendService, ) { parent::__construct(); - $this->globalService = $globalService; - $this->userService = $userService; - $this->userSession = $userSession; - $this->userManager = $userManager; - $this->importLegacyStorageService = $importLegacyStorageService; - $this->backendService = $backendService; } - protected function configure() { + protected function configure(): void { $this ->setName('files_external:import') ->setDescription('Import mount configurations') @@ -107,25 +60,25 @@ class Import extends Base { } protected function execute(InputInterface $input, OutputInterface $output): int { - $user = (string) $input->getOption('user'); + $user = (string)$input->getOption('user'); $path = $input->getArgument('path'); if ($path === '-') { $json = file_get_contents('php://stdin'); } else { if (!file_exists($path)) { $output->writeln('<error>File not found: ' . $path . '</error>'); - return 1; + return self::FAILURE; } $json = file_get_contents($path); } if (!is_string($json) || strlen($json) < 2) { $output->writeln('<error>Error while reading json</error>'); - return 1; + return self::FAILURE; } $data = json_decode($json, true); if (!is_array($data)) { $output->writeln('<error>Error while parsing json</error>'); - return 1; + return self::FAILURE; } $isLegacy = isset($data['user']) || isset($data['group']); @@ -135,7 +88,7 @@ class Import extends Base { foreach ($mounts as $mount) { if ($mount->getBackendOption('password') === false) { $output->writeln('<error>Failed to decrypt password</error>'); - return 1; + return self::FAILURE; } } } else { @@ -160,13 +113,13 @@ class Import extends Base { foreach ($mounts as $mount) { foreach ($existingMounts as $existingMount) { if ( - $existingMount->getMountPoint() === $mount->getMountPoint() && - $existingMount->getApplicableGroups() === $mount->getApplicableGroups() && - $existingMount->getApplicableUsers() === $mount->getApplicableUsers() && - $existingMount->getBackendOptions() === $mount->getBackendOptions() + $existingMount->getMountPoint() === $mount->getMountPoint() + && $existingMount->getApplicableGroups() === $mount->getApplicableGroups() + && $existingMount->getApplicableUsers() === $mount->getApplicableUsers() + && $existingMount->getBackendOptions() === $mount->getBackendOptions() ) { - $output->writeln("<error>Duplicate mount (" . $mount->getMountPoint() . ")</error>"); - return 1; + $output->writeln('<error>Duplicate mount (' . $mount->getMountPoint() . ')</error>'); + return self::FAILURE; } } } @@ -174,7 +127,7 @@ class Import extends Base { if ($input->getOption('dry')) { if (count($mounts) === 0) { $output->writeln('<error>No mounts to be imported</error>'); - return 1; + return self::FAILURE; } $listCommand = new ListCommand($this->globalService, $this->userService, $this->userSession, $this->userManager); $listInput = new ArrayInput([], $listCommand->getDefinition()); @@ -186,10 +139,10 @@ class Import extends Base { $storageService->addStorage($mount); } } - return 0; + return self::SUCCESS; } - private function parseData(array $data) { + private function parseData(array $data): StorageConfig { $mount = new StorageConfig($data['mount_id']); $mount->setMountPoint($data['mount_point']); $mount->setBackend($this->getBackendByClass($data['storage'])); @@ -197,12 +150,12 @@ class Import extends Base { $mount->setAuthMechanism($authBackend); $mount->setBackendOptions($data['configuration']); $mount->setMountOptions($data['options']); - $mount->setApplicableUsers(isset($data['applicable_users']) ? $data['applicable_users'] : []); - $mount->setApplicableGroups(isset($data['applicable_groups']) ? $data['applicable_groups'] : []); + $mount->setApplicableUsers($data['applicable_users'] ?? []); + $mount->setApplicableGroups($data['applicable_groups'] ?? []); return $mount; } - private function getBackendByClass($className) { + private function getBackendByClass(string $className) { $backends = $this->backendService->getBackends(); foreach ($backends as $backend) { if ($backend->getStorageClass() === $className) { @@ -211,16 +164,16 @@ class Import extends Base { } } - protected function getStorageService($userId) { - if (!empty($userId)) { - $user = $this->userManager->get($userId); - if (is_null($user)) { - throw new NoUserException("user $userId not found"); - } - $this->userSession->setUser($user); - return $this->userService; - } else { + protected function getStorageService(string $userId): StoragesService { + if (empty($userId)) { return $this->globalService; } + + $user = $this->userManager->get($userId); + if (is_null($user)) { + throw new NoUserException("user $userId not found"); + } + $this->userSession->setUser($user); + return $this->userService; } } diff --git a/apps/files_external/lib/Command/ListCommand.php b/apps/files_external/lib/Command/ListCommand.php index fadf5d8de27..350916b6c2c 100644 --- a/apps/files_external/lib/Command/ListCommand.php +++ b/apps/files_external/lib/Command/ListCommand.php @@ -1,27 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.nl> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; @@ -29,6 +11,7 @@ use OC\Core\Command\Base; use OC\User\NoUserException; use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\Service\GlobalStoragesService; +use OCA\Files_External\Service\StoragesService; use OCA\Files_External\Service\UserStoragesService; use OCP\IUserManager; use OCP\IUserSession; @@ -39,37 +22,18 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class ListCommand extends Base { - /** - * @var GlobalStoragesService - */ - protected $globalService; - - /** - * @var UserStoragesService - */ - protected $userService; - - /** - * @var IUserSession - */ - protected $userSession; - - /** - * @var IUserManager - */ - protected $userManager; - public const ALL = -1; - public function __construct(GlobalStoragesService $globalService, UserStoragesService $userService, IUserSession $userSession, IUserManager $userManager) { + public function __construct( + protected GlobalStoragesService $globalService, + protected UserStoragesService $userService, + protected IUserSession $userSession, + protected IUserManager $userManager, + ) { parent::__construct(); - $this->globalService = $globalService; - $this->userService = $userService; - $this->userSession = $userSession; - $this->userManager = $userManager; } - protected function configure() { + protected function configure(): void { $this ->setName('files_external:list') ->setDescription('List configured admin or personal mounts') @@ -102,33 +66,31 @@ class ListCommand extends Base { $mounts = $this->globalService->getStorageForAllUsers(); $userId = self::ALL; } else { - $userId = $input->getArgument('user_id'); + $userId = (string)$input->getArgument('user_id'); $storageService = $this->getStorageService($userId); $mounts = $storageService->getAllStorages(); } $this->listMounts($userId, $mounts, $input, $output); - return 0; + return self::SUCCESS; } /** - * @param string $userId + * @param ?string|ListCommand::ALL $userId * @param StorageConfig[] $mounts - * @param InputInterface $input - * @param OutputInterface $output */ - public function listMounts($userId, array $mounts, InputInterface $input, OutputInterface $output) { + public function listMounts($userId, array $mounts, InputInterface $input, OutputInterface $output): void { $outputType = $input->getOption('output'); if (count($mounts) === 0) { if ($outputType === self::OUTPUT_FORMAT_JSON || $outputType === self::OUTPUT_FORMAT_JSON_PRETTY) { $output->writeln('[]'); } else { if ($userId === self::ALL) { - $output->writeln("<info>No mounts configured</info>"); + $output->writeln('<info>No mounts configured</info>'); } elseif ($userId) { $output->writeln("<info>No mounts configured by $userId</info>"); } else { - $output->writeln("<info>No admin mounts configured</info>"); + $output->writeln('<info>No admin mounts configured</info>'); } } return; @@ -145,12 +107,12 @@ class ListCommand extends Base { } if (!$input->getOption('show-password')) { - $hideKeys = ['password', 'refresh_token', 'token', 'client_secret', 'public_key', 'private_key']; + $hideKeys = ['key', 'bucket', 'secret', 'password', 'refresh_token', 'token', 'client_secret', 'public_key', 'private_key']; foreach ($mounts as $mount) { $config = $mount->getBackendOptions(); foreach ($config as $key => $value) { if (in_array($key, $hideKeys)) { - $mount->setBackendOption($key, '***'); + $mount->setBackendOption($key, '***REMOVED SENSITIVE VALUE***'); } } } @@ -262,16 +224,16 @@ class ListCommand extends Base { } } - protected function getStorageService($userId) { - if (!empty($userId)) { - $user = $this->userManager->get($userId); - if (is_null($user)) { - throw new NoUserException("user $userId not found"); - } - $this->userSession->setUser($user); - return $this->userService; - } else { + protected function getStorageService(string $userId): StoragesService { + if (empty($userId)) { return $this->globalService; } + + $user = $this->userManager->get($userId); + if (is_null($user)) { + throw new NoUserException("user $userId not found"); + } + $this->userSession->setUser($user); + return $this->userService; } } diff --git a/apps/files_external/lib/Command/Notify.php b/apps/files_external/lib/Command/Notify.php index 340dd2fdca1..0982aa5598b 100644 --- a/apps/files_external/lib/Command/Notify.php +++ b/apps/files_external/lib/Command/Notify.php @@ -1,35 +1,14 @@ <?php + +declare(strict_types=1); + /** - * @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl> - * - * @author Ari Selseng <ari@selseng.net> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.nl> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Command; use Doctrine\DBAL\Exception\DriverException; -use OC\Core\Command\Base; -use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; -use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\Service\GlobalStoragesService; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Notify\IChange; @@ -37,39 +16,25 @@ use OCP\Files\Notify\INotifyHandler; use OCP\Files\Notify\IRenameChange; use OCP\Files\Storage\INotifyStorage; use OCP\Files\Storage\IStorage; -use OCP\Files\StorageNotAvailableException; use OCP\IDBConnection; -use OCP\ILogger; use OCP\IUserManager; +use Psr\Log\LoggerInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -class Notify extends Base { - /** @var GlobalStoragesService */ - private $globalService; - /** @var IDBConnection */ - private $connection; - /** @var ILogger */ - private $logger; - /** @var IUserManager */ - private $userManager; - +class Notify extends StorageAuthBase { public function __construct( + private IDBConnection $connection, + private LoggerInterface $logger, GlobalStoragesService $globalService, - IDBConnection $connection, - ILogger $logger, - IUserManager $userManager + IUserManager $userManager, ) { - parent::__construct(); - $this->globalService = $globalService; - $this->connection = $connection; - $this->logger = $logger; - $this->userManager = $userManager; + parent::__construct($globalService, $userManager); } - protected function configure() { + protected function configure(): void { $this ->setName('files_external:notify') ->setDescription('Listen for active update notifications for a configured external mount') @@ -107,82 +72,15 @@ class Notify extends Base { parent::configure(); } - private function getUserOption(InputInterface $input): ?string { - if ($input->getOption('user')) { - return (string)$input->getOption('user'); - } elseif (isset($_ENV['NOTIFY_USER'])) { - return (string)$_ENV['NOTIFY_USER']; - } elseif (isset($_SERVER['NOTIFY_USER'])) { - return (string)$_SERVER['NOTIFY_USER']; - } else { - return null; - } - } - - private function getPasswordOption(InputInterface $input): ?string { - if ($input->getOption('password')) { - return (string)$input->getOption('password'); - } elseif (isset($_ENV['NOTIFY_PASSWORD'])) { - return (string)$_ENV['NOTIFY_PASSWORD']; - } elseif (isset($_SERVER['NOTIFY_PASSWORD'])) { - return (string)$_SERVER['NOTIFY_PASSWORD']; - } else { - return null; - } - } - protected function execute(InputInterface $input, OutputInterface $output): int { - $mount = $this->globalService->getStorage($input->getArgument('mount_id')); - if (is_null($mount)) { - $output->writeln('<error>Mount not found</error>'); - return 1; - } - $noAuth = false; - - $userOption = $this->getUserOption($input); - $passwordOption = $this->getPasswordOption($input); - - // if only the user is provided, we get the user object to pass along to the auth backend - // this allows using saved user credentials - $user = ($userOption && !$passwordOption) ? $this->userManager->get($userOption) : null; - - try { - $authBackend = $mount->getAuthMechanism(); - $authBackend->manipulateStorageConfig($mount, $user); - } catch (InsufficientDataForMeaningfulAnswerException $e) { - $noAuth = true; - } catch (StorageNotAvailableException $e) { - $noAuth = true; + [$mount, $storage] = $this->createStorage($input, $output); + if ($storage === null) { + return self::FAILURE; } - if ($userOption) { - $mount->setBackendOption('user', $userOption); - } - if ($passwordOption) { - $mount->setBackendOption('password', $passwordOption); - } - - try { - $backend = $mount->getBackend(); - $backend->manipulateStorageConfig($mount, $user); - } catch (InsufficientDataForMeaningfulAnswerException $e) { - $noAuth = true; - } catch (StorageNotAvailableException $e) { - $noAuth = true; - } - - try { - $storage = $this->createStorage($mount); - } catch (\Exception $e) { - $output->writeln('<error>Error while trying to create storage</error>'); - if ($noAuth) { - $output->writeln('<error>Username and/or password required</error>'); - } - return 1; - } if (!$storage instanceof INotifyStorage) { $output->writeln('<error>Mount of type "' . $mount->getBackend()->getText() . '" does not support active update notifications</error>'); - return 1; + return self::FAILURE; } $dryRun = $input->getOption('dry-run'); @@ -197,22 +95,17 @@ class Notify extends Base { $this->selfTest($storage, $notifyHandler, $output); } - $notifyHandler->listen(function (IChange $change) use ($mount, $output, $dryRun) { + $notifyHandler->listen(function (IChange $change) use ($mount, $output, $dryRun): void { $this->logUpdate($change, $output); if ($change instanceof IRenameChange) { $this->markParentAsOutdated($mount->getId(), $change->getTargetPath(), $output, $dryRun); } $this->markParentAsOutdated($mount->getId(), $change->getPath(), $output, $dryRun); }); - return 0; - } - - private function createStorage(StorageConfig $mount) { - $class = $mount->getBackend()->getStorageClass(); - return new $class($mount->getBackendOptions()); + return self::SUCCESS; } - private function markParentAsOutdated($mountId, $path, OutputInterface $output, bool $dryRun) { + private function markParentAsOutdated($mountId, $path, OutputInterface $output, bool $dryRun): void { $parent = ltrim(dirname($path), '/'); if ($parent === '.') { $parent = ''; @@ -221,7 +114,7 @@ class Notify extends Base { try { $storages = $this->getStorageIds($mountId, $parent); } catch (DriverException $ex) { - $this->logger->logException($ex, ['message' => 'Error while trying to find correct storage ids.', 'level' => ILogger::WARN]); + $this->logger->warning('Error while trying to find correct storage ids.', ['exception' => $ex]); $this->connection = $this->reconnectToDatabase($this->connection, $output); $output->writeln('<info>Needed to reconnect to the database</info>'); $storages = $this->getStorageIds($mountId, $path); @@ -243,7 +136,7 @@ class Notify extends Base { $storageIds = array_values(array_unique($storageIds)); if ($dryRun) { - $output->writeln(" dry-run: skipping database write"); + $output->writeln(' dry-run: skipping database write'); } else { $result = $this->updateParent($storageIds, $parent); if ($result === 0) { @@ -253,22 +146,17 @@ class Notify extends Base { } } - private function logUpdate(IChange $change, OutputInterface $output) { - switch ($change->getType()) { - case INotifyStorage::NOTIFY_ADDED: - $text = 'added'; - break; - case INotifyStorage::NOTIFY_MODIFIED: - $text = 'modified'; - break; - case INotifyStorage::NOTIFY_REMOVED: - $text = 'removed'; - break; - case INotifyStorage::NOTIFY_RENAMED: - $text = 'renamed'; - break; - default: - return; + private function logUpdate(IChange $change, OutputInterface $output): void { + $text = match ($change->getType()) { + INotifyStorage::NOTIFY_ADDED => 'added', + INotifyStorage::NOTIFY_MODIFIED => 'modified', + INotifyStorage::NOTIFY_REMOVED => 'removed', + INotifyStorage::NOTIFY_RENAMED => 'renamed', + default => '', + }; + + if ($text === '') { + return; } $text .= ' ' . $change->getPath(); @@ -280,7 +168,7 @@ class Notify extends Base { } private function getStorageIds(int $mountId, string $path): array { - $pathHash = md5(trim((string)\OC_Util::normalizeUnicode($path), '/')); + $pathHash = md5(trim(\OC_Util::normalizeUnicode($path), '/')); $qb = $this->connection->getQueryBuilder(); return $qb ->select('storage_id', 'user_id') @@ -292,12 +180,7 @@ class Notify extends Base { ->fetchAll(); } - /** - * @param array $storageIds - * @param string $parent - * @return int - */ - private function updateParent($storageIds, $parent) { + private function updateParent(array $storageIds, string $parent): int { $pathHash = md5(trim(\OC_Util::normalizeUnicode($parent), '/')); $qb = $this->connection->getQueryBuilder(); return $qb @@ -305,24 +188,22 @@ class Notify extends Base { ->set('size', $qb->createNamedParameter(-1, IQueryBuilder::PARAM_INT)) ->where($qb->expr()->in('storage', $qb->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY, ':storage_ids'))) ->andWhere($qb->expr()->eq('path_hash', $qb->createNamedParameter($pathHash, IQueryBuilder::PARAM_STR))) - ->execute(); + ->executeStatement(); } - /** - * @return \OCP\IDBConnection - */ - private function reconnectToDatabase(IDBConnection $connection, OutputInterface $output) { + private function reconnectToDatabase(IDBConnection $connection, OutputInterface $output): IDBConnection { try { $connection->close(); } catch (\Exception $ex) { - $this->logger->logException($ex, ['app' => 'files_external', 'message' => 'Error while disconnecting from DB', 'level' => ILogger::WARN]); + $this->logger->warning('Error while disconnecting from DB', ['exception' => $ex]); $output->writeln("<info>Error while disconnecting from database: {$ex->getMessage()}</info>"); } - while (!$connection->isConnected()) { + $connected = false; + while (!$connected) { try { - $connection->connect(); + $connected = $connection->connect(); } catch (\Exception $ex) { - $this->logger->logException($ex, ['app' => 'files_external', 'message' => 'Error while re-connecting to database', 'level' => ILogger::WARN]); + $this->logger->warning('Error while re-connecting to database', ['exception' => $ex]); $output->writeln("<info>Error while re-connecting to database: {$ex->getMessage()}</info>"); sleep(60); } @@ -331,9 +212,12 @@ class Notify extends Base { } - private function selfTest(IStorage $storage, INotifyHandler $notifyHandler, OutputInterface $output) { + private function selfTest(IStorage $storage, INotifyHandler $notifyHandler, OutputInterface $output): void { usleep(100 * 1000); //give time for the notify to start - $storage->file_put_contents('/.nc_test_file.txt', 'test content'); + if (!$storage->file_put_contents('/.nc_test_file.txt', 'test content')) { + $output->writeln('Failed to create test file for self-test'); + return; + } $storage->mkdir('/.nc_test_folder'); $storage->file_put_contents('/.nc_test_folder/subfile.txt', 'test content'); diff --git a/apps/files_external/lib/Command/Option.php b/apps/files_external/lib/Command/Option.php index 9f7d4a30bcd..3fda3fcb3cf 100644 --- a/apps/files_external/lib/Command/Option.php +++ b/apps/files_external/lib/Command/Option.php @@ -1,24 +1,9 @@ <?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/> - * + * 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\Command; @@ -27,7 +12,7 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; class Option extends Config { - protected function configure() { + protected function configure(): void { $this ->setName('files_external:option') ->setDescription('Manage mount options for a mount') @@ -47,25 +32,21 @@ class Option extends Config { } /** - * @param StorageConfig $mount * @param string $key - * @param OutputInterface $output */ - protected function getOption(StorageConfig $mount, $key, OutputInterface $output) { + protected function getOption(StorageConfig $mount, $key, OutputInterface $output): void { $value = $mount->getMountOption($key); if (!is_string($value)) { // show bools and objects correctly $value = json_encode($value); } - $output->writeln($value); + $output->writeln((string)$value); } /** - * @param StorageConfig $mount * @param string $key * @param string $value - * @param OutputInterface $output */ - protected function setOption(StorageConfig $mount, $key, $value, OutputInterface $output) { + protected function setOption(StorageConfig $mount, $key, $value, OutputInterface $output): void { $decoded = json_decode($value, true); if (!is_null($decoded)) { $value = $decoded; diff --git a/apps/files_external/lib/Command/Scan.php b/apps/files_external/lib/Command/Scan.php new file mode 100644 index 00000000000..75d98878baa --- /dev/null +++ b/apps/files_external/lib/Command/Scan.php @@ -0,0 +1,166 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files_External\Command; + +use OC\Files\Cache\Scanner; +use OCA\Files_External\Service\GlobalStoragesService; +use OCP\IUserManager; +use OCP\Lock\LockedException; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Scan extends StorageAuthBase { + protected float $execTime = 0; + protected int $foldersCounter = 0; + protected int $filesCounter = 0; + + public function __construct( + GlobalStoragesService $globalService, + IUserManager $userManager, + ) { + parent::__construct($globalService, $userManager); + } + + protected function configure(): void { + $this + ->setName('files_external:scan') + ->setDescription('Scan an external storage for changed files') + ->addArgument( + 'mount_id', + InputArgument::REQUIRED, + 'the mount id of the mount to scan' + )->addOption( + 'user', + 'u', + InputOption::VALUE_REQUIRED, + 'The username for the remote mount (required only for some mount configuration that don\'t store credentials)' + )->addOption( + 'password', + 'p', + InputOption::VALUE_REQUIRED, + 'The password for the remote mount (required only for some mount configuration that don\'t store credentials)' + )->addOption( + 'path', + '', + InputOption::VALUE_OPTIONAL, + 'The path in the storage to scan', + '' + )->addOption( + 'unscanned', + '', + InputOption::VALUE_NONE, + 'only scan files which are marked as not fully scanned' + ); + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + [, $storage] = $this->createStorage($input, $output); + if ($storage === null) { + return 1; + } + + $path = $input->getOption('path'); + + $this->execTime = -microtime(true); + + /** @var Scanner $scanner */ + $scanner = $storage->getScanner(); + + $scanner->listen('\OC\Files\Cache\Scanner', 'scanFile', function (string $path) use ($output): void { + $output->writeln("\tFile\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE); + ++$this->filesCounter; + $this->abortIfInterrupted(); + }); + + $scanner->listen('\OC\Files\Cache\Scanner', 'scanFolder', function (string $path) use ($output): void { + $output->writeln("\tFolder\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE); + ++$this->foldersCounter; + $this->abortIfInterrupted(); + }); + + try { + if ($input->getOption('unscanned')) { + if ($path !== '') { + $output->writeln('<error>--unscanned is mutually exclusive with --path</error>'); + return 1; + } + $scanner->backgroundScan(); + } else { + $scanner->scan($path); + } + } catch (LockedException $e) { + if (is_string($e->getReadablePath()) && str_starts_with($e->getReadablePath(), 'scanner::')) { + if ($e->getReadablePath() === 'scanner::') { + $output->writeln('<error>Another process is already scanning this storage</error>'); + } else { + $output->writeln('<error>Another process is already scanning \'' . substr($e->getReadablePath(), strlen('scanner::')) . '\' in this storage</error>'); + } + } else { + throw $e; + } + } + + $this->presentStats($output); + + return 0; + } + + /** + * @param OutputInterface $output + */ + protected function presentStats(OutputInterface $output): void { + // Stop the timer + $this->execTime += microtime(true); + + $headers = [ + 'Folders', 'Files', 'Elapsed time' + ]; + + $this->showSummary($headers, [], $output); + } + + /** + * Shows a summary of operations + * + * @param string[] $headers + * @param string[] $rows + * @param OutputInterface $output + */ + protected function showSummary(array $headers, array $rows, OutputInterface $output): void { + $niceDate = $this->formatExecTime(); + if (!$rows) { + $rows = [ + $this->foldersCounter, + $this->filesCounter, + $niceDate, + ]; + } + $table = new Table($output); + $table + ->setHeaders($headers) + ->setRows([$rows]); + $table->render(); + } + + + /** + * Formats microtime into a human readable format + * + * @return string + */ + protected function formatExecTime(): string { + $secs = round($this->execTime); + # convert seconds into HH:MM:SS form + return sprintf('%02d:%02d:%02d', ($secs / 3600), ($secs / 60 % 60), $secs % 60); + } +} diff --git a/apps/files_external/lib/Command/StorageAuthBase.php b/apps/files_external/lib/Command/StorageAuthBase.php new file mode 100644 index 00000000000..6f830a08a60 --- /dev/null +++ b/apps/files_external/lib/Command/StorageAuthBase.php @@ -0,0 +1,114 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +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\NotFoundException; +use OCA\Files_External\Service\GlobalStoragesService; +use OCP\Files\Storage\IStorage; +use OCP\Files\StorageNotAvailableException; +use OCP\IUserManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +abstract class StorageAuthBase extends Base { + public function __construct( + protected GlobalStoragesService $globalService, + protected IUserManager $userManager, + ) { + parent::__construct(); + } + + private function getUserOption(InputInterface $input): ?string { + if ($input->getOption('user')) { + return (string)$input->getOption('user'); + } + + return $_ENV['NOTIFY_USER'] ?? $_SERVER['NOTIFY_USER'] ?? null; + } + + private function getPasswordOption(InputInterface $input): ?string { + if ($input->getOption('password')) { + return (string)$input->getOption('password'); + } + + return $_ENV['NOTIFY_PASSWORD'] ?? $_SERVER['NOTIFY_PASSWORD'] ?? null; + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return array + * @psalm-return array{0: StorageConfig, 1: IStorage}|array{0: null, 1: null} + */ + protected function createStorage(InputInterface $input, OutputInterface $output): array { + try { + /** @var StorageConfig|null $mount */ + $mount = $this->globalService->getStorage((int)$input->getArgument('mount_id')); + } catch (NotFoundException $e) { + $output->writeln('<error>Mount not found</error>'); + return [null, null]; + } + if (is_null($mount)) { + $output->writeln('<error>Mount not found</error>'); + return [null, null]; + } + $noAuth = false; + + $userOption = $this->getUserOption($input); + $passwordOption = $this->getPasswordOption($input); + + // if only the user is provided, we get the user object to pass along to the auth backend + // this allows using saved user credentials + $user = ($userOption && !$passwordOption) ? $this->userManager->get($userOption) : null; + + try { + $authBackend = $mount->getAuthMechanism(); + $authBackend->manipulateStorageConfig($mount, $user); + } catch (InsufficientDataForMeaningfulAnswerException $e) { + $noAuth = true; + } catch (StorageNotAvailableException $e) { + $noAuth = true; + } + + if ($userOption) { + $mount->setBackendOption('user', $userOption); + } + if ($passwordOption) { + $mount->setBackendOption('password', $passwordOption); + } + + try { + $backend = $mount->getBackend(); + $backend->manipulateStorageConfig($mount, $user); + } catch (InsufficientDataForMeaningfulAnswerException $e) { + $noAuth = true; + } catch (StorageNotAvailableException $e) { + $noAuth = true; + } + + try { + $class = $mount->getBackend()->getStorageClass(); + /** @var IStorage $storage */ + $storage = new $class($mount->getBackendOptions()); + if (!$storage->test()) { + throw new \Exception(); + } + return [$mount, $storage]; + } catch (\Exception $e) { + $output->writeln('<error>Error while trying to create storage</error>'); + if ($noAuth) { + $output->writeln('<error>Username and/or password required</error>'); + } + return [null, null]; + } + } +} diff --git a/apps/files_external/lib/Command/Verify.php b/apps/files_external/lib/Command/Verify.php index 0febfa15825..ecebbe0f7e6 100644 --- a/apps/files_external/lib/Command/Verify.php +++ b/apps/files_external/lib/Command/Verify.php @@ -1,36 +1,19 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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> - * - * @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/> - * + * 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\Command; use OC\Core\Command\Base; -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\GlobalStoragesService; +use OCP\AppFramework\Http; use OCP\Files\StorageNotAvailableException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -38,17 +21,13 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class Verify extends Base { - /** - * @var GlobalStoragesService - */ - protected $globalService; - - public function __construct(GlobalStoragesService $globalService) { + public function __construct( + protected GlobalStoragesService $globalService, + ) { parent::__construct(); - $this->globalService = $globalService; } - protected function configure() { + protected function configure(): void { $this ->setName('files_external:verify') ->setDescription('Verify mount configuration') @@ -73,7 +52,7 @@ class Verify extends Base { $mount = $this->globalService->getStorage($mountId); } catch (NotFoundException $e) { $output->writeln('<error>Mount with id "' . $mountId . ' not found, check "occ files_external:list" to get available mounts"</error>'); - return 404; + return Http::STATUS_NOT_FOUND; } $this->updateStorageStatus($mount, $configInput, $output); @@ -83,19 +62,17 @@ class Verify extends Base { 'code' => $mount->getStatus(), 'message' => $mount->getStatusMessage() ]); - return 0; + return self::SUCCESS; } - private function manipulateStorageConfig(StorageConfig $storage) { - /** @var AuthMechanism */ + private function manipulateStorageConfig(StorageConfig $storage): void { $authMechanism = $storage->getAuthMechanism(); $authMechanism->manipulateStorageConfig($storage); - /** @var Backend */ $backend = $storage->getBackend(); $backend->manipulateStorageConfig($storage); } - private function updateStorageStatus(StorageConfig &$storage, $configInput, OutputInterface $output) { + private function updateStorageStatus(StorageConfig &$storage, $configInput, OutputInterface $output): void { try { try { $this->manipulateStorageConfig($storage); @@ -114,18 +91,16 @@ class Verify extends Base { $storage->setBackendOption($key, $value); } - /** @var Backend */ $backend = $storage->getBackend(); // update status (can be time-consuming) $storage->setStatus( - \OCA\Files_External\MountConfig::getBackendStatus( + MountConfig::getBackendStatus( $backend->getStorageClass(), $storage->getBackendOptions(), - false ) ); } catch (InsufficientDataForMeaningfulAnswerException $e) { - $status = $e->getCode() ? $e->getCode() : StorageNotAvailableException::STATUS_INDETERMINATE; + $status = $e->getCode() ?: StorageNotAvailableException::STATUS_INDETERMINATE; $storage->setStatus( $status, $e->getMessage() diff --git a/apps/files_external/lib/Config/ConfigAdapter.php b/apps/files_external/lib/Config/ConfigAdapter.php index 57faf91fcdc..a46c0fd5c66 100644 --- a/apps/files_external/lib/Config/ConfigAdapter.php +++ b/apps/files_external/lib/Config/ConfigAdapter.php @@ -1,87 +1,71 @@ <?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 Julius Härtl <jus@bitgrid.net> - * @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 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/> - * + * 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\Config; +use OC\Files\Cache\Storage; use OC\Files\Storage\FailedStorage; 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\UserGlobalStoragesService; use OCA\Files_External\Service\UserStoragesService; +use OCP\AppFramework\QueryException; use OCP\Files\Config\IMountProvider; -use OCP\Files\Storage; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\ObjectStore\IObjectStore; +use OCP\Files\Storage\IConstructableStorage; +use OCP\Files\Storage\IStorage; use OCP\Files\Storage\IStorageFactory; use OCP\Files\StorageNotAvailableException; use OCP\IUser; +use OCP\Server; +use Psr\Clock\ClockInterface; +use Psr\Log\LoggerInterface; /** * Make the old files_external config work with the new public mount config api */ class ConfigAdapter implements IMountProvider { - - /** @var UserStoragesService */ - private $userStoragesService; - - /** @var UserGlobalStoragesService */ - private $userGlobalStoragesService; + public function __construct( + private UserStoragesService $userStoragesService, + private UserGlobalStoragesService $userGlobalStoragesService, + private ClockInterface $clock, + ) { + } /** - * @param UserStoragesService $userStoragesService - * @param UserGlobalStoragesService $userGlobalStoragesService + * @param class-string $class + * @return class-string<IObjectStore> + * @throws \InvalidArgumentException + * @psalm-taint-escape callable */ - public function __construct( - UserStoragesService $userStoragesService, - UserGlobalStoragesService $userGlobalStoragesService - ) { - $this->userStoragesService = $userStoragesService; - $this->userGlobalStoragesService = $userGlobalStoragesService; + private function validateObjectStoreClassString(string $class): string { + if (!\is_subclass_of($class, IObjectStore::class)) { + throw new \InvalidArgumentException('Invalid object store'); + } + return $class; } /** * Process storage ready for mounting * - * @param StorageConfig $storage - * @param IUser $user - * @throws \OCP\AppFramework\QueryException + * @throws QueryException */ - private function prepareStorageConfig(StorageConfig &$storage, IUser $user) { + private function prepareStorageConfig(StorageConfig &$storage, IUser $user): void { foreach ($storage->getBackendOptions() as $option => $value) { - $storage->setBackendOption($option, \OCA\Files_External\MountConfig::substitutePlaceholdersInConfig($value, $user->getUID())); + $storage->setBackendOption($option, MountConfig::substitutePlaceholdersInConfig($value, $user->getUID())); } $objectStore = $storage->getBackendOption('objectstore'); if ($objectStore) { - $objectClass = $objectStore['class']; - if (!is_subclass_of($objectClass, '\OCP\Files\ObjectStore\IObjectStore')) { - throw new \InvalidArgumentException('Invalid object store'); - } + $objectClass = $this->validateObjectStoreClassString($objectStore['class']); $storage->setBackendOption('objectstore', new $objectClass($objectStore)); } @@ -93,10 +77,12 @@ class ConfigAdapter implements IMountProvider { * Construct the storage implementation * * @param StorageConfig $storageConfig - * @return Storage */ - private function constructStorage(StorageConfig $storageConfig) { + private function constructStorage(StorageConfig $storageConfig): IStorage { $class = $storageConfig->getBackend()->getStorageClass(); + if (!is_a($class, IConstructableStorage::class, true)) { + Server::get(LoggerInterface::class)->warning('Building a storage not implementing IConstructableStorage is deprecated since 31.0.0', ['class' => $class]); + } $storage = new $class($storageConfig->getBackendOptions()); // auth mechanism should fire first @@ -109,9 +95,7 @@ class ConfigAdapter implements IMountProvider { /** * Get all mountpoints applicable for the user * - * @param \OCP\IUser $user - * @param \OCP\Files\Storage\IStorageFactory $loader - * @return \OCP\Files\Mount\IMountPoint[] + * @return IMountPoint[] */ public function getMountsForUser(IUser $user, IStorageFactory $loader) { $this->userStoragesService->setUser($user); @@ -130,11 +114,11 @@ class ConfigAdapter implements IMountProvider { }, $storageConfigs); - \OC\Files\Cache\Storage::getGlobalCache()->loadForStorageIds(array_map(function (Storage\IStorage $storage) { + Storage::getGlobalCache()->loadForStorageIds(array_map(function (IStorage $storage) { return $storage->getId(); }, $storages)); - $availableStorages = array_map(function (Storage\IStorage $storage, StorageConfig $storageConfig) { + $availableStorages = array_map(function (IStorage $storage, StorageConfig $storageConfig): IStorage { try { $availability = $storage->getAvailability(); if (!$availability['available'] && !Availability::shouldRecheck($availability)) { @@ -149,13 +133,17 @@ class ConfigAdapter implements IMountProvider { return $storage; }, $storages, $storageConfigs); - $mounts = array_map(function (StorageConfig $storageConfig, Storage\IStorage $storage) use ($user, $loader) { - if ($storageConfig->getType() === StorageConfig::MOUNT_TYPE_PERSONAl) { + $mounts = array_map(function (StorageConfig $storageConfig, IStorage $storage) use ($user, $loader) { + $storage->setOwner($user->getUID()); + if ($storageConfig->getType() === StorageConfig::MOUNT_TYPE_PERSONAL) { return new PersonalMount( $this->userStoragesService, $storageConfig, $storageConfig->getId(), - $storage, + new KnownMtime([ + 'storage' => $storage, + 'clock' => $this->clock, + ]), '/' . $user->getUID() . '/files' . $storageConfig->getMountPoint(), null, $loader, @@ -163,7 +151,7 @@ class ConfigAdapter implements IMountProvider { $storageConfig->getId() ); } else { - return new ExternalMountPoint( + return new SystemMountPoint( $storageConfig, $storage, '/' . $user->getUID() . '/files' . $storageConfig->getMountPoint(), diff --git a/apps/files_external/lib/Config/ExternalMountPoint.php b/apps/files_external/lib/Config/ExternalMountPoint.php index 090e1c77cdf..97569ed2913 100644 --- a/apps/files_external/lib/Config/ExternalMountPoint.php +++ b/apps/files_external/lib/Config/ExternalMountPoint.php @@ -1,43 +1,34 @@ <?php + /** - * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> - * - * @author Julius Härtl <jus@bitgrid.net> - * @author Robin Appelman <robin@icewind.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Config; use OC\Files\Mount\MountPoint; -use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\Lib\Auth\Password\SessionCredentials; +use OCA\Files_External\Lib\StorageConfig; class ExternalMountPoint extends MountPoint { - /** @var StorageConfig */ - protected $storageConfig; - - public function __construct(StorageConfig $storageConfig, $storage, $mountpoint, $arguments = null, $loader = null, $mountOptions = null, $mountId = null) { - $this->storageConfig = $storageConfig; + public function __construct( + protected StorageConfig $storageConfig, + $storage, + $mountpoint, + $arguments = null, + $loader = null, + $mountOptions = null, + $mountId = null, + ) { parent::__construct($storage, $mountpoint, $arguments, $loader, $mountOptions, $mountId, ConfigAdapter::class); } public function getMountType() { return ($this->storageConfig->getAuthMechanism() instanceof SessionCredentials) ? 'external-session' : 'external'; } + + public function getStorageConfig(): StorageConfig { + return $this->storageConfig; + } } diff --git a/apps/files_external/lib/Config/IConfigHandler.php b/apps/files_external/lib/Config/IConfigHandler.php index cd7ca4d1050..9e8283cc58b 100644 --- a/apps/files_external/lib/Config/IConfigHandler.php +++ b/apps/files_external/lib/Config/IConfigHandler.php @@ -1,24 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Config; diff --git a/apps/files_external/lib/Config/SimpleSubstitutionTrait.php b/apps/files_external/lib/Config/SimpleSubstitutionTrait.php index 4f15d285032..85a76054fa8 100644 --- a/apps/files_external/lib/Config/SimpleSubstitutionTrait.php +++ b/apps/files_external/lib/Config/SimpleSubstitutionTrait.php @@ -1,26 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Julius Härtl <jus@bitgrid.net> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Config; diff --git a/apps/files_external/lib/Config/SystemMountPoint.php b/apps/files_external/lib/Config/SystemMountPoint.php new file mode 100644 index 00000000000..af0bf792140 --- /dev/null +++ b/apps/files_external/lib/Config/SystemMountPoint.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files_External\Config; + +use OCP\Files\Mount\IShareOwnerlessMount; +use OCP\Files\Mount\ISystemMountPoint; + +class SystemMountPoint extends ExternalMountPoint implements ISystemMountPoint, IShareOwnerlessMount { +} diff --git a/apps/files_external/lib/Config/UserContext.php b/apps/files_external/lib/Config/UserContext.php index a4ff27e42b5..fb5c79a9329 100644 --- a/apps/files_external/lib/Config/UserContext.php +++ b/apps/files_external/lib/Config/UserContext.php @@ -1,26 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Julius Härtl <jus@bitgrid.net> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Config; @@ -33,26 +15,15 @@ use OCP\Share\IManager as ShareManager; class UserContext { - /** @var IUserSession */ - private $session; - - /** @var ShareManager */ - private $shareManager; - - /** @var IRequest */ - private $request; - /** @var string */ private $userId; - /** @var IUserManager */ - private $userManager; - - public function __construct(IUserSession $session, ShareManager $manager, IRequest $request, IUserManager $userManager) { - $this->session = $session; - $this->shareManager = $manager; - $this->request = $request; - $this->userManager = $userManager; + public function __construct( + private IUserSession $session, + private ShareManager $shareManager, + private IRequest $request, + private IUserManager $userManager, + ) { } public function getSession(): IUserSession { @@ -67,7 +38,7 @@ class UserContext { if ($this->userId !== null) { return $this->userId; } - if ($this->session && $this->session->getUser() !== null) { + if ($this->session->getUser() !== null) { return $this->session->getUser()->getUID(); } try { diff --git a/apps/files_external/lib/Config/UserPlaceholderHandler.php b/apps/files_external/lib/Config/UserPlaceholderHandler.php index b2ced2ecf6a..d158e6923c1 100644 --- a/apps/files_external/lib/Config/UserPlaceholderHandler.php +++ b/apps/files_external/lib/Config/UserPlaceholderHandler.php @@ -1,26 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Julius Härtl <jus@bitgrid.net> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Config; diff --git a/apps/files_external/lib/ConfigLexicon.php b/apps/files_external/lib/ConfigLexicon.php new file mode 100644 index 00000000000..154f76c1989 --- /dev/null +++ b/apps/files_external/lib/ConfigLexicon.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files_External; + +use OCP\Config\Lexicon\Entry; +use OCP\Config\Lexicon\ILexicon; +use OCP\Config\Lexicon\Strictness; +use OCP\Config\ValueType; + +/** + * Config Lexicon for files_sharing. + * + * Please Add & Manage your Config Keys in that file and keep the Lexicon up to date! + * + * {@see ILexicon} + */ +class ConfigLexicon implements ILexicon { + public const ALLOW_USER_MOUNTING = 'allow_user_mounting'; + public const USER_MOUNTING_BACKENDS = 'user_mounting_backends'; + + public function getStrictness(): Strictness { + return Strictness::NOTICE; + } + + public function getAppConfigs(): array { + return [ + new Entry(self::ALLOW_USER_MOUNTING, ValueType::BOOL, false, 'allow users to mount their own external filesystems', true), + new Entry(self::USER_MOUNTING_BACKENDS, ValueType::STRING, '', 'list of mounting backends available for users', true), + ]; + } + + public function getUserConfigs(): array { + return []; + } +} diff --git a/apps/files_external/lib/Controller/AjaxController.php b/apps/files_external/lib/Controller/AjaxController.php index db23ecd709d..5cee6422530 100644 --- a/apps/files_external/lib/Controller/AjaxController.php +++ b/apps/files_external/lib/Controller/AjaxController.php @@ -1,51 +1,25 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Martin Mattel <martin.mattel@diemattels.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> - * @author Ross Nicoll <jrn@jrn.me.uk> - * - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Controller; use OCA\Files_External\Lib\Auth\Password\GlobalAuth; use OCA\Files_External\Lib\Auth\PublicKey\RSA; use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IGroupManager; +use OCP\IL10N; use OCP\IRequest; use OCP\IUserSession; class AjaxController extends Controller { - /** @var RSA */ - private $rsaMechanism; - /** @var GlobalAuth */ - private $globalAuth; - /** @var IUserSession */ - private $userSession; - /** @var IGroupManager */ - private $groupManager; - /** * @param string $appName * @param IRequest $request @@ -54,17 +28,16 @@ class AjaxController extends Controller { * @param IUserSession $userSession * @param IGroupManager $groupManager */ - public function __construct($appName, - IRequest $request, - RSA $rsaMechanism, - GlobalAuth $globalAuth, - IUserSession $userSession, - IGroupManager $groupManager) { + public function __construct( + $appName, + IRequest $request, + private RSA $rsaMechanism, + private GlobalAuth $globalAuth, + private IUserSession $userSession, + private IGroupManager $groupManager, + private IL10N $l10n, + ) { parent::__construct($appName, $request); - $this->rsaMechanism = $rsaMechanism; - $this->globalAuth = $globalAuth; - $this->userSession = $userSession; - $this->groupManager = $groupManager; } /** @@ -82,39 +55,53 @@ class AjaxController extends Controller { /** * Generates an SSH public/private key pair. * - * @NoAdminRequired * @param int $keyLength */ + #[NoAdminRequired] public function getSshKeys($keyLength = 1024) { $key = $this->generateSshKeys($keyLength); - return new JSONResponse( - ['data' => [ + return new JSONResponse([ + 'data' => [ 'private_key' => $key['privatekey'], 'public_key' => $key['publickey'] ], - 'status' => 'success' - ]); + 'status' => 'success', + ]); } /** - * @NoAdminRequired - * * @param string $uid * @param string $user * @param string $password - * @return bool + * @return JSONResponse */ - public function saveGlobalCredentials($uid, $user, $password) { + #[NoAdminRequired] + #[PasswordConfirmationRequired(strict: true)] + public function saveGlobalCredentials($uid, $user, $password): JSONResponse { $currentUser = $this->userSession->getUser(); + if ($currentUser === null) { + return new JSONResponse([ + 'status' => 'error', + 'message' => $this->l10n->t('You are not logged in'), + ], Http::STATUS_UNAUTHORIZED); + } // Non-admins can only edit their own credentials - $allowedToEdit = ($this->groupManager->isAdmin($currentUser->getUID()) || $currentUser->getUID() === $uid); + // Admin can edit global credentials + $allowedToEdit = $uid === '' + ? $this->groupManager->isAdmin($currentUser->getUID()) + : $currentUser->getUID() === $uid; if ($allowedToEdit) { $this->globalAuth->saveAuth($uid, $user, $password); - return true; - } else { - return false; + return new JSONResponse([ + 'status' => 'success', + ]); } + + return new JSONResponse([ + 'status' => 'success', + 'message' => $this->l10n->t('Permission denied'), + ], Http::STATUS_FORBIDDEN); } } diff --git a/apps/files_external/lib/Controller/ApiController.php b/apps/files_external/lib/Controller/ApiController.php index 40539d0bbca..49547357e6b 100644 --- a/apps/files_external/lib/Controller/ApiController.php +++ b/apps/files_external/lib/Controller/ApiController.php @@ -3,61 +3,35 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Jesús Macias <jmacias@solidgear.es> - * @author Joas Schilling <coding@schilljs.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Controller; use OCA\Files_External\Lib\StorageConfig; +use OCA\Files_External\ResponseDefinitions; use OCA\Files_External\Service\UserGlobalStoragesService; use OCA\Files_External\Service\UserStoragesService; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCSController; +use OCP\Constants; use OCP\IRequest; -use OCP\IUserSession; +/** + * @psalm-import-type Files_ExternalMount from ResponseDefinitions + */ class ApiController extends OCSController { - /** @var IUserSession */ - private $userSession; - /** @var UserGlobalStoragesService */ - private $userGlobalStoragesService; - /** @var UserStoragesService */ - private $userStoragesService; - public function __construct( string $appName, IRequest $request, - IUserSession $userSession, - UserGlobalStoragesService $userGlobalStorageService, - UserStoragesService $userStorageService + private UserGlobalStoragesService $userGlobalStoragesService, + private UserStoragesService $userStoragesService, ) { parent::__construct($appName, $request); - - $this->userSession = $userSession; - $this->userGlobalStoragesService = $userGlobalStorageService; - $this->userStoragesService = $userStorageService; } /** @@ -66,7 +40,7 @@ class ApiController extends OCSController { * @param string $mountPoint mount point name, relative to the data dir * @param StorageConfig $mountConfig mount config to format * - * @return array entry + * @return Files_ExternalMount */ private function formatMount(string $mountPoint, StorageConfig $mountConfig): array { // split path from mount point @@ -77,32 +51,34 @@ class ApiController extends OCSController { $isSystemMount = $mountConfig->getType() === StorageConfig::MOUNT_TYPE_ADMIN; - $permissions = \OCP\Constants::PERMISSION_READ; + $permissions = Constants::PERMISSION_READ; // personal mounts can be deleted if (!$isSystemMount) { - $permissions |= \OCP\Constants::PERMISSION_DELETE; + $permissions |= Constants::PERMISSION_DELETE; } $entry = [ + 'id' => $mountConfig->getId(), + 'type' => 'dir', 'name' => basename($mountPoint), 'path' => $path, - 'type' => 'dir', - 'backend' => $mountConfig->getBackend()->getText(), - 'scope' => $isSystemMount ? 'system' : 'personal', 'permissions' => $permissions, - 'id' => $mountConfig->getId(), + 'scope' => $isSystemMount ? 'system' : 'personal', + 'backend' => $mountConfig->getBackend()->getText(), 'class' => $mountConfig->getBackend()->getIdentifier(), + 'config' => $mountConfig->jsonSerialize(true), ]; return $entry; } /** - * @NoAdminRequired + * Get the mount points visible for this user * - * Returns the mount points visible for this user. + * @return DataResponse<Http::STATUS_OK, list<Files_ExternalMount>, array{}> * - * @return DataResponse share information + * 200: User mounts returned */ + #[NoAdminRequired] public function getUserMounts(): DataResponse { $entries = []; $mountPoints = []; diff --git a/apps/files_external/lib/Controller/GlobalStoragesController.php b/apps/files_external/lib/Controller/GlobalStoragesController.php index 89939640acc..e7274c9cfb6 100644 --- a/apps/files_external/lib/Controller/GlobalStoragesController.php +++ b/apps/files_external/lib/Controller/GlobalStoragesController.php @@ -1,42 +1,23 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Joas Schilling <coding@schilljs.com> - * @author Juan Pablo Villafáñez <jvillafanez@solidgear.es> - * @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 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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Controller; use OCA\Files_External\NotFoundException; use OCA\Files_External\Service\GlobalStoragesService; use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\DataResponse; use OCP\IConfig; use OCP\IGroupManager; use OCP\IL10N; -use OCP\ILogger; use OCP\IRequest; use OCP\IUserSession; +use Psr\Log\LoggerInterface; /** * Global storages controller @@ -49,7 +30,7 @@ class GlobalStoragesController extends StoragesController { * @param IRequest $request request object * @param IL10N $l10n l10n service * @param GlobalStoragesService $globalStoragesService storage service - * @param ILogger $logger + * @param LoggerInterface $logger * @param IUserSession $userSession * @param IGroupManager $groupManager * @param IConfig $config @@ -59,10 +40,10 @@ class GlobalStoragesController extends StoragesController { IRequest $request, IL10N $l10n, GlobalStoragesService $globalStoragesService, - ILogger $logger, + LoggerInterface $logger, IUserSession $userSession, IGroupManager $groupManager, - IConfig $config + IConfig $config, ) { parent::__construct( $AppName, @@ -90,6 +71,7 @@ class GlobalStoragesController extends StoragesController { * * @return DataResponse */ + #[PasswordConfirmationRequired(strict: true)] public function create( $mountPoint, $backend, @@ -98,7 +80,7 @@ class GlobalStoragesController extends StoragesController { $mountOptions, $applicableUsers, $applicableGroups, - $priority + $priority, ) { $canCreateNewLocalStorage = $this->config->getSystemValue('files_external_allow_create_new_local', true); if (!$canCreateNewLocalStorage && $backend === 'local') { @@ -134,7 +116,7 @@ class GlobalStoragesController extends StoragesController { $this->updateStorageStatus($newStorage); return new DataResponse( - $this->formatStorageForUI($newStorage), + $newStorage->jsonSerialize(true), Http::STATUS_CREATED ); } @@ -145,16 +127,16 @@ class GlobalStoragesController extends StoragesController { * @param int $id storage id * @param string $mountPoint storage mount point * @param string $backend backend identifier - * @param string $authMechanism authentication mechansim identifier + * @param string $authMechanism authentication mechanism identifier * @param array $backendOptions backend-specific options * @param array $mountOptions mount-specific options * @param array $applicableUsers users for which to mount the storage * @param array $applicableGroups groups for which to mount the storage * @param int $priority priority - * @param bool $testOnly whether to storage should only test the connection or do more things * * @return DataResponse */ + #[PasswordConfirmationRequired(strict: true)] public function update( $id, $mountPoint, @@ -165,7 +147,6 @@ class GlobalStoragesController extends StoragesController { $applicableUsers, $applicableGroups, $priority, - $testOnly = true ) { $storage = $this->createStorage( $mountPoint, @@ -198,10 +179,10 @@ class GlobalStoragesController extends StoragesController { ); } - $this->updateStorageStatus($storage, $testOnly); + $this->updateStorageStatus($storage); return new DataResponse( - $this->formatStorageForUI($storage), + $storage->jsonSerialize(true), Http::STATUS_OK ); } diff --git a/apps/files_external/lib/Controller/StoragesController.php b/apps/files_external/lib/Controller/StoragesController.php index b82d2a4a4c7..df3a4528054 100644 --- a/apps/files_external/lib/Controller/StoragesController.php +++ b/apps/files_external/lib/Controller/StoragesController.php @@ -1,90 +1,35 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Jesús Macias <jmacias@solidgear.es> - * @author Joas Schilling <coding@schilljs.com> - * @author Juan Pablo Villafáñez <jvillafanez@solidgear.es> - * @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 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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Controller; use OCA\Files_External\Lib\Auth\AuthMechanism; use OCA\Files_External\Lib\Backend\Backend; -use OCA\Files_External\Lib\DefinitionParameter; 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\StoragesService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\DataResponse; use OCP\Files\StorageNotAvailableException; use OCP\IConfig; use OCP\IGroupManager; use OCP\IL10N; -use OCP\ILogger; use OCP\IRequest; use OCP\IUserSession; +use Psr\Log\LoggerInterface; /** * Base class for storages controllers */ abstract class StoragesController extends Controller { - - /** - * L10N service - * - * @var IL10N - */ - protected $l10n; - - /** - * Storages service - * - * @var StoragesService - */ - protected $service; - - /** - * @var ILogger - */ - protected $logger; - - /** - * @var IUserSession - */ - protected $userSession; - - /** - * @var IGroupManager - */ - protected $groupManager; - - /** - * @var IConfig - */ - protected $config; - /** * Creates a new storages controller. * @@ -92,25 +37,19 @@ abstract class StoragesController extends Controller { * @param IRequest $request request object * @param IL10N $l10n l10n service * @param StoragesService $storagesService storage service - * @param ILogger $logger + * @param LoggerInterface $logger */ public function __construct( $AppName, IRequest $request, - IL10N $l10n, - StoragesService $storagesService, - ILogger $logger, - IUserSession $userSession, - IGroupManager $groupManager, - IConfig $config + protected IL10N $l10n, + protected StoragesService $service, + protected LoggerInterface $logger, + protected IUserSession $userSession, + protected IGroupManager $groupManager, + protected IConfig $config, ) { parent::__construct($AppName, $request); - $this->l10n = $l10n; - $this->service = $storagesService; - $this->logger = $logger; - $this->userSession = $userSession; - $this->groupManager = $groupManager; - $this->config = $config; } /** @@ -135,7 +74,7 @@ abstract class StoragesController extends Controller { $mountOptions = null, $applicableUsers = null, $applicableGroups = null, - $priority = null + $priority = null, ) { $canCreateNewLocalStorage = $this->config->getSystemValue('files_external_allow_create_new_local', true); if (!$canCreateNewLocalStorage && $backend === 'local') { @@ -159,7 +98,7 @@ abstract class StoragesController extends Controller { $priority ); } catch (\InvalidArgumentException $e) { - $this->logger->logException($e); + $this->logger->error($e->getMessage(), ['exception' => $e]); return new DataResponse( [ 'message' => $this->l10n->t('Invalid backend or authentication mechanism class') @@ -274,9 +213,8 @@ abstract class StoragesController extends Controller { * on whether the remote storage is available or not. * * @param StorageConfig $storage storage configuration - * @param bool $testOnly whether to storage should only test the connection or do more things */ - protected function updateStorageStatus(StorageConfig &$storage, $testOnly = true) { + protected function updateStorageStatus(StorageConfig &$storage) { try { $this->manipulateStorageConfig($storage); @@ -284,22 +222,20 @@ abstract class StoragesController extends Controller { $backend = $storage->getBackend(); // update status (can be time-consuming) $storage->setStatus( - \OCA\Files_External\MountConfig::getBackendStatus( + MountConfig::getBackendStatus( $backend->getStorageClass(), $storage->getBackendOptions(), - false, - $testOnly ) ); } catch (InsufficientDataForMeaningfulAnswerException $e) { - $status = $e->getCode() ? $e->getCode() : StorageNotAvailableException::STATUS_INDETERMINATE; + $status = $e->getCode() ?: StorageNotAvailableException::STATUS_INDETERMINATE; $storage->setStatus( - $status, + (int)$status, $this->l10n->t('Insufficient data: %s', [$e->getMessage()]) ); } catch (StorageNotAvailableException $e) { $storage->setStatus( - $e->getCode(), + (int)$e->getCode(), $this->l10n->t('%s', [$e->getMessage()]) ); } catch (\Exception $e) { @@ -317,7 +253,7 @@ abstract class StoragesController extends Controller { * @return DataResponse */ public function index() { - $storages = $this->formatStoragesForUI($this->service->getStorages()); + $storages = array_map(static fn ($storage) => $storage->jsonSerialize(true), $this->service->getStorages()); return new DataResponse( $storages, @@ -325,42 +261,18 @@ abstract class StoragesController extends Controller { ); } - protected function formatStoragesForUI(array $storages): array { - return array_map(function ($storage) { - return $this->formatStorageForUI($storage); - }, $storages); - } - - protected function formatStorageForUI(StorageConfig $storage): StorageConfig { - /** @var DefinitionParameter[] $parameters */ - $parameters = array_merge($storage->getBackend()->getParameters(), $storage->getAuthMechanism()->getParameters()); - - $options = $storage->getBackendOptions(); - foreach ($options as $key => $value) { - foreach ($parameters as $parameter) { - if ($parameter->getName() === $key && $parameter->getType() === DefinitionParameter::VALUE_PASSWORD) { - $storage->setBackendOption($key, DefinitionParameter::UNMODIFIED_PLACEHOLDER); - break; - } - } - } - - return $storage; - } - /** * Get an external storage entry. * * @param int $id storage id - * @param bool $testOnly whether to storage should only test the connection or do more things * * @return DataResponse */ - public function show($id, $testOnly = true) { + public function show(int $id) { try { $storage = $this->service->getStorage($id); - $this->updateStorageStatus($storage, $testOnly); + $this->updateStorageStatus($storage); } catch (NotFoundException $e) { return new DataResponse( [ @@ -370,9 +282,9 @@ abstract class StoragesController extends Controller { ); } - $data = $this->formatStorageForUI($storage)->jsonSerialize(); + $data = $storage->jsonSerialize(true); $isAdmin = $this->groupManager->isAdmin($this->userSession->getUser()->getUID()); - $data['can_edit'] = $storage->getType() === StorageConfig::MOUNT_TYPE_PERSONAl || $isAdmin; + $data['can_edit'] = $storage->getType() === StorageConfig::MOUNT_TYPE_PERSONAL || $isAdmin; return new DataResponse( $data, @@ -387,7 +299,8 @@ abstract class StoragesController extends Controller { * * @return DataResponse */ - public function destroy($id) { + #[PasswordConfirmationRequired(strict: true)] + public function destroy(int $id) { try { $this->service->removeStorage($id); } catch (NotFoundException $e) { diff --git a/apps/files_external/lib/Controller/UserGlobalStoragesController.php b/apps/files_external/lib/Controller/UserGlobalStoragesController.php index 74424bce006..88a9f936401 100644 --- a/apps/files_external/lib/Controller/UserGlobalStoragesController.php +++ b/apps/files_external/lib/Controller/UserGlobalStoragesController.php @@ -1,28 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Joas Schilling <coding@schilljs.com> - * @author Juan Pablo Villafáñez <jvillafanez@solidgear.es> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Controller; @@ -35,13 +16,15 @@ use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\NotFoundException; use OCA\Files_External\Service\UserGlobalStoragesService; use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\DataResponse; use OCP\IConfig; use OCP\IGroupManager; use OCP\IL10N; -use OCP\ILogger; use OCP\IRequest; use OCP\IUserSession; +use Psr\Log\LoggerInterface; /** * User global storages controller @@ -54,7 +37,7 @@ class UserGlobalStoragesController extends StoragesController { * @param IRequest $request request object * @param IL10N $l10n l10n service * @param UserGlobalStoragesService $userGlobalStoragesService storage service - * @param ILogger $logger + * @param LoggerInterface $logger * @param IUserSession $userSession * @param IGroupManager $groupManager */ @@ -63,10 +46,10 @@ class UserGlobalStoragesController extends StoragesController { IRequest $request, IL10N $l10n, UserGlobalStoragesService $userGlobalStoragesService, - ILogger $logger, + LoggerInterface $logger, IUserSession $userSession, IGroupManager $groupManager, - IConfig $config + IConfig $config, ) { parent::__construct( $AppName, @@ -84,16 +67,16 @@ class UserGlobalStoragesController extends StoragesController { * Get all storage entries * * @return DataResponse - * - * @NoAdminRequired */ + #[NoAdminRequired] public function index() { - $storages = $this->formatStoragesForUI($this->service->getUniqueStorages()); - - // remove configuration data, this must be kept private - foreach ($storages as $storage) { + /** @var UserGlobalStoragesService */ + $service = $this->service; + $storages = array_map(function ($storage) { + // remove configuration data, this must be kept private $this->sanitizeStorage($storage); - } + return $storage->jsonSerialize(true); + }, $service->getUniqueStorages()); return new DataResponse( $storages, @@ -114,16 +97,14 @@ class UserGlobalStoragesController extends StoragesController { * Get an external storage entry. * * @param int $id storage id - * @param bool $testOnly whether to storage should only test the connection or do more things * @return DataResponse - * - * @NoAdminRequired */ - public function show($id, $testOnly = true) { + #[NoAdminRequired] + public function show($id) { try { $storage = $this->service->getStorage($id); - $this->updateStorageStatus($storage, $testOnly); + $this->updateStorageStatus($storage); } catch (NotFoundException $e) { return new DataResponse( [ @@ -135,9 +116,9 @@ class UserGlobalStoragesController extends StoragesController { $this->sanitizeStorage($storage); - $data = $this->formatStorageForUI($storage)->jsonSerialize(); + $data = $storage->jsonSerialize(true); $isAdmin = $this->groupManager->isAdmin($this->userSession->getUser()->getUID()); - $data['can_edit'] = $storage->getType() === StorageConfig::MOUNT_TYPE_PERSONAl || $isAdmin; + $data['can_edit'] = $storage->getType() === StorageConfig::MOUNT_TYPE_PERSONAL || $isAdmin; return new DataResponse( $data, @@ -151,16 +132,14 @@ class UserGlobalStoragesController extends StoragesController { * * @param int $id storage id * @param array $backendOptions backend-specific options - * @param bool $testOnly whether to storage should only test the connection or do more things * * @return DataResponse - * - * @NoAdminRequired */ + #[NoAdminRequired] + #[PasswordConfirmationRequired(strict: true)] public function update( $id, $backendOptions, - $testOnly = true ) { try { $storage = $this->service->getStorage($id); @@ -171,7 +150,7 @@ class UserGlobalStoragesController extends StoragesController { } else { return new DataResponse( [ - 'message' => $this->l10n->t('Storage with ID "%d" is not user editable', [$id]) + 'message' => $this->l10n->t('Storage with ID "%d" is not editable by non-admins', [$id]) ], Http::STATUS_FORBIDDEN ); @@ -185,11 +164,11 @@ class UserGlobalStoragesController extends StoragesController { ); } - $this->updateStorageStatus($storage, $testOnly); + $this->updateStorageStatus($storage); $this->sanitizeStorage($storage); return new DataResponse( - $this->formatStorageForUI($storage), + $storage->jsonSerialize(true), Http::STATUS_OK ); } diff --git a/apps/files_external/lib/Controller/UserStoragesController.php b/apps/files_external/lib/Controller/UserStoragesController.php index c0a460fd8e3..7b564d57f7e 100644 --- a/apps/files_external/lib/Controller/UserStoragesController.php +++ b/apps/files_external/lib/Controller/UserStoragesController.php @@ -1,30 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Juan Pablo Villafáñez <jvillafanez@solidgear.es> - * @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 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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Controller; @@ -34,13 +13,15 @@ use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\NotFoundException; use OCA\Files_External\Service\UserStoragesService; use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\DataResponse; use OCP\IConfig; use OCP\IGroupManager; use OCP\IL10N; -use OCP\ILogger; use OCP\IRequest; use OCP\IUserSession; +use Psr\Log\LoggerInterface; /** * User storages controller @@ -53,7 +34,7 @@ class UserStoragesController extends StoragesController { * @param IRequest $request request object * @param IL10N $l10n l10n service * @param UserStoragesService $userStoragesService storage service - * @param ILogger $logger + * @param LoggerInterface $logger * @param IUserSession $userSession * @param IGroupManager $groupManager */ @@ -62,10 +43,10 @@ class UserStoragesController extends StoragesController { IRequest $request, IL10N $l10n, UserStoragesService $userStoragesService, - ILogger $logger, + LoggerInterface $logger, IUserSession $userSession, IGroupManager $groupManager, - IConfig $config + IConfig $config, ) { parent::__construct( $AppName, @@ -91,10 +72,9 @@ class UserStoragesController extends StoragesController { /** * Get all storage entries * - * @NoAdminRequired - * * @return DataResponse */ + #[NoAdminRequired] public function index() { return parent::index(); } @@ -102,12 +82,11 @@ class UserStoragesController extends StoragesController { /** * Return storage * - * @NoAdminRequired - * * {@inheritdoc} */ - public function show($id, $testOnly = true) { - return parent::show($id, $testOnly); + #[NoAdminRequired] + public function show(int $id) { + return parent::show($id); } /** @@ -120,15 +99,15 @@ class UserStoragesController extends StoragesController { * @param array $mountOptions backend-specific mount options * * @return DataResponse - * - * @NoAdminRequired */ + #[NoAdminRequired] + #[PasswordConfirmationRequired(strict: true)] public function create( $mountPoint, $backend, $authMechanism, $backendOptions, - $mountOptions + $mountOptions, ) { $canCreateNewLocalStorage = $this->config->getSystemValue('files_external_allow_create_new_local', true); if (!$canCreateNewLocalStorage && $backend === 'local') { @@ -159,7 +138,7 @@ class UserStoragesController extends StoragesController { $this->updateStorageStatus($newStorage); return new DataResponse( - $this->formatStorageForUI($newStorage), + $newStorage->jsonSerialize(true), Http::STATUS_CREATED ); } @@ -173,12 +152,11 @@ class UserStoragesController extends StoragesController { * @param string $authMechanism authentication mechanism identifier * @param array $backendOptions backend-specific options * @param array $mountOptions backend-specific mount options - * @param bool $testOnly whether to storage should only test the connection or do more things * * @return DataResponse - * - * @NoAdminRequired */ + #[NoAdminRequired] + #[PasswordConfirmationRequired(strict: true)] public function update( $id, $mountPoint, @@ -186,7 +164,6 @@ class UserStoragesController extends StoragesController { $authMechanism, $backendOptions, $mountOptions, - $testOnly = true ) { $storage = $this->createStorage( $mountPoint, @@ -216,10 +193,10 @@ class UserStoragesController extends StoragesController { ); } - $this->updateStorageStatus($storage, $testOnly); + $this->updateStorageStatus($storage); return new DataResponse( - $this->formatStorageForUI($storage), + $storage->jsonSerialize(true), Http::STATUS_OK ); } @@ -227,11 +204,11 @@ class UserStoragesController extends StoragesController { /** * Delete storage * - * @NoAdminRequired - * * {@inheritdoc} */ - public function destroy($id) { + #[NoAdminRequired] + #[PasswordConfirmationRequired(strict: true)] + public function destroy(int $id) { return parent::destroy($id); } } diff --git a/apps/files_external/lib/Lib/Auth/AmazonS3/AccessKey.php b/apps/files_external/lib/Lib/Auth/AmazonS3/AccessKey.php index 7185fb511f5..c86c88a13d7 100644 --- a/apps/files_external/lib/Lib/Auth/AmazonS3/AccessKey.php +++ b/apps/files_external/lib/Lib/Auth/AmazonS3/AccessKey.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Morris Jobke <hey@morrisjobke.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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Auth\AmazonS3; diff --git a/apps/files_external/lib/Lib/Auth/AuthMechanism.php b/apps/files_external/lib/Lib/Auth/AuthMechanism.php index 210dd705723..7b0544100fb 100644 --- a/apps/files_external/lib/Lib/Auth/AuthMechanism.php +++ b/apps/files_external/lib/Lib/Auth/AuthMechanism.php @@ -1,31 +1,16 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Auth; use OCA\Files_External\Lib\FrontendDefinitionTrait; use OCA\Files_External\Lib\IdentifierTrait; +use OCA\Files_External\Lib\IFrontendDefinition; +use OCA\Files_External\Lib\IIdentifier; use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\Lib\StorageModifierTrait; use OCA\Files_External\Lib\VisibilityTrait; @@ -37,7 +22,7 @@ use OCA\Files_External\Lib\VisibilityTrait; * such as \OCP\IDB for database operations. This allows an authentication * mechanism to perform advanced operations based on provided information. * - * An authenication scheme defines the parameter interface, common to the + * An authentication scheme defines the parameter interface, common to the * storage implementation, the backend and the authentication mechanism. * A storage implementation expects parameters according to the authentication * scheme, which are provided from the authentication mechanism. @@ -50,12 +35,11 @@ use OCA\Files_External\Lib\VisibilityTrait; * - StorageModifierTrait * Object can affect storage mounting */ -class AuthMechanism implements \JsonSerializable { +class AuthMechanism implements \JsonSerializable, IIdentifier, IFrontendDefinition { /** Standard authentication schemes */ public const SCHEME_NULL = 'null'; public const SCHEME_BUILTIN = 'builtin'; public const SCHEME_PASSWORD = 'password'; - public const SCHEME_OAUTH1 = 'oauth1'; public const SCHEME_OAUTH2 = 'oauth2'; public const SCHEME_PUBLICKEY = 'publickey'; public const SCHEME_OPENSTACK = 'openstack'; diff --git a/apps/files_external/lib/Lib/Auth/Builtin.php b/apps/files_external/lib/Lib/Auth/Builtin.php index 91428156c75..8e12a6daca6 100644 --- a/apps/files_external/lib/Lib/Auth/Builtin.php +++ b/apps/files_external/lib/Lib/Auth/Builtin.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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/> - * + * 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\Lib\Auth; diff --git a/apps/files_external/lib/Lib/Auth/IUserProvided.php b/apps/files_external/lib/Lib/Auth/IUserProvided.php index f913e8e3ab5..2350d7f6db4 100644 --- a/apps/files_external/lib/Lib/Auth/IUserProvided.php +++ b/apps/files_external/lib/Lib/Auth/IUserProvided.php @@ -1,23 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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/> - * + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Auth; diff --git a/apps/files_external/lib/Lib/Auth/InvalidAuth.php b/apps/files_external/lib/Lib/Auth/InvalidAuth.php index 1d2953b11cd..2af24f1ea07 100644 --- a/apps/files_external/lib/Lib/Auth/InvalidAuth.php +++ b/apps/files_external/lib/Lib/Auth/InvalidAuth.php @@ -1,23 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud GmbH. - * - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud GmbH. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Auth; diff --git a/apps/files_external/lib/Lib/Auth/NullMechanism.php b/apps/files_external/lib/Lib/Auth/NullMechanism.php index 05b73b92fc1..8e2e5b656b2 100644 --- a/apps/files_external/lib/Lib/Auth/NullMechanism.php +++ b/apps/files_external/lib/Lib/Auth/NullMechanism.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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/> - * + * 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\Lib\Auth; diff --git a/apps/files_external/lib/Lib/Auth/OAuth1/OAuth1.php b/apps/files_external/lib/Lib/Auth/OAuth1/OAuth1.php deleted file mode 100644 index 44cdcf79523..00000000000 --- a/apps/files_external/lib/Lib/Auth/OAuth1/OAuth1.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Morris Jobke <hey@morrisjobke.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\Lib\Auth\OAuth1; - -use OCA\Files_External\Lib\Auth\AuthMechanism; -use OCA\Files_External\Lib\DefinitionParameter; -use OCP\IL10N; - -/** - * OAuth1 authentication - */ -class OAuth1 extends AuthMechanism { - public function __construct(IL10N $l) { - $this - ->setIdentifier('oauth1::oauth1') - ->setScheme(self::SCHEME_OAUTH1) - ->setText($l->t('OAuth1')) - ->addParameters([ - (new DefinitionParameter('configured', 'configured')) - ->setType(DefinitionParameter::VALUE_HIDDEN), - new DefinitionParameter('app_key', $l->t('App key')), - (new DefinitionParameter('app_secret', $l->t('App secret'))) - ->setType(DefinitionParameter::VALUE_PASSWORD), - (new DefinitionParameter('token', 'token')) - ->setType(DefinitionParameter::VALUE_HIDDEN), - (new DefinitionParameter('token_secret', 'token_secret')) - ->setType(DefinitionParameter::VALUE_HIDDEN), - ]) - ->addCustomJs('oauth1') - ; - } -} diff --git a/apps/files_external/lib/Lib/Auth/OAuth2/OAuth2.php b/apps/files_external/lib/Lib/Auth/OAuth2/OAuth2.php index 4dc2b23035a..beaf73c2344 100644 --- a/apps/files_external/lib/Lib/Auth/OAuth2/OAuth2.php +++ b/apps/files_external/lib/Lib/Auth/OAuth2/OAuth2.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Morris Jobke <hey@morrisjobke.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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Auth\OAuth2; @@ -38,12 +22,14 @@ class OAuth2 extends AuthMechanism { ->setText($l->t('OAuth2')) ->addParameters([ (new DefinitionParameter('configured', 'configured')) - ->setType(DefinitionParameter::VALUE_HIDDEN), + ->setType(DefinitionParameter::VALUE_TEXT) + ->setFlag(DefinitionParameter::FLAG_HIDDEN), new DefinitionParameter('client_id', $l->t('Client ID')), (new DefinitionParameter('client_secret', $l->t('Client secret'))) ->setType(DefinitionParameter::VALUE_PASSWORD), (new DefinitionParameter('token', 'token')) - ->setType(DefinitionParameter::VALUE_HIDDEN), + ->setType(DefinitionParameter::VALUE_PASSWORD) + ->setFlag(DefinitionParameter::FLAG_HIDDEN), ]) ->addCustomJs('oauth2') ; diff --git a/apps/files_external/lib/Lib/Auth/OpenStack/OpenStackV2.php b/apps/files_external/lib/Lib/Auth/OpenStack/OpenStackV2.php index ddc734ff1ec..3b1c9f123af 100644 --- a/apps/files_external/lib/Lib/Auth/OpenStack/OpenStackV2.php +++ b/apps/files_external/lib/Lib/Auth/OpenStack/OpenStackV2.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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/> - * + * 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\Lib\Auth\OpenStack; @@ -38,7 +21,7 @@ class OpenStackV2 extends AuthMechanism { ->setScheme(self::SCHEME_OPENSTACK) ->setText($l->t('OpenStack v2')) ->addParameters([ - new DefinitionParameter('user', $l->t('Username')), + new DefinitionParameter('user', $l->t('Login')), (new DefinitionParameter('password', $l->t('Password'))) ->setType(DefinitionParameter::VALUE_PASSWORD), new DefinitionParameter('tenant', $l->t('Tenant name')), diff --git a/apps/files_external/lib/Lib/Auth/OpenStack/OpenStackV3.php b/apps/files_external/lib/Lib/Auth/OpenStack/OpenStackV3.php index 44ea1c61155..b5d185fd374 100644 --- a/apps/files_external/lib/Lib/Auth/OpenStack/OpenStackV3.php +++ b/apps/files_external/lib/Lib/Auth/OpenStack/OpenStackV3.php @@ -3,28 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> - * - * @author Julien Lutran <julien.lutran@corp.ovh.com> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Lib\Auth\OpenStack; @@ -42,7 +22,7 @@ class OpenStackV3 extends AuthMechanism { ->setScheme(self::SCHEME_OPENSTACK) ->setText($l->t('OpenStack v3')) ->addParameters([ - new DefinitionParameter('user', $l->t('Username')), + new DefinitionParameter('user', $l->t('Login')), new DefinitionParameter('domain', $l->t('Domain')), (new DefinitionParameter('password', $l->t('Password'))) ->setType(DefinitionParameter::VALUE_PASSWORD), diff --git a/apps/files_external/lib/Lib/Auth/OpenStack/Rackspace.php b/apps/files_external/lib/Lib/Auth/OpenStack/Rackspace.php index 8bf3eba668d..b1d1068e586 100644 --- a/apps/files_external/lib/Lib/Auth/OpenStack/Rackspace.php +++ b/apps/files_external/lib/Lib/Auth/OpenStack/Rackspace.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Morris Jobke <hey@morrisjobke.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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Auth\OpenStack; @@ -37,7 +21,7 @@ class Rackspace extends AuthMechanism { ->setScheme(self::SCHEME_OPENSTACK) ->setText($l->t('Rackspace')) ->addParameters([ - new DefinitionParameter('user', $l->t('Username')), + new DefinitionParameter('user', $l->t('Login')), (new DefinitionParameter('key', $l->t('API key'))) ->setType(DefinitionParameter::VALUE_PASSWORD), ]) diff --git a/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php b/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php index dff5bf86625..916b496b506 100644 --- a/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php +++ b/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2015, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @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/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2015 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Auth\Password; @@ -37,13 +20,12 @@ use OCP\Security\ICredentialsManager; */ class GlobalAuth extends AuthMechanism { public const CREDENTIALS_IDENTIFIER = 'password::global'; + private const PWD_PLACEHOLDER = '************************'; - /** @var ICredentialsManager */ - protected $credentialsManager; - - public function __construct(IL10N $l, ICredentialsManager $credentialsManager) { - $this->credentialsManager = $credentialsManager; - + public function __construct( + IL10N $l, + protected ICredentialsManager $credentialsManager, + ) { $this ->setIdentifier('password::global') ->setVisibility(BackendService::VISIBILITY_DEFAULT) @@ -59,18 +41,28 @@ class GlobalAuth extends AuthMechanism { 'password' => '' ]; } else { + $auth['password'] = self::PWD_PLACEHOLDER; return $auth; } } public function saveAuth($uid, $user, $password) { + // Use old password if it has not changed. + if ($password === self::PWD_PLACEHOLDER) { + $auth = $this->credentialsManager->retrieve($uid, self::CREDENTIALS_IDENTIFIER); + $password = $auth['password']; + } + $this->credentialsManager->store($uid, self::CREDENTIALS_IDENTIFIER, [ 'user' => $user, 'password' => $password ]); } - public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { + /** + * @return void + */ + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { if ($storage->getType() === StorageConfig::MOUNT_TYPE_ADMIN) { $uid = ''; } elseif (is_null($user)) { diff --git a/apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php b/apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php index e6c2be70056..ce38140b6ee 100644 --- a/apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php +++ b/apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php @@ -1,33 +1,14 @@ <?php + /** - * @copyright Copyright (c) 2015, ownCloud, Inc. - * - * @author blizzz <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @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 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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2015 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Auth\Password; use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCA\Files_External\Lib\DefinitionParameter; use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\Listener\StorePasswordListener; @@ -49,36 +30,23 @@ use OCP\User\Events\UserLoggedInEvent; class LoginCredentials extends AuthMechanism { public const CREDENTIALS_IDENTIFIER = 'password::logincredentials/credentials'; - /** @var ISession */ - protected $session; - - /** @var ICredentialsManager */ - protected $credentialsManager; - - /** @var CredentialsStore */ - private $credentialsStore; - - /** @var ILDAPProviderFactory */ - private $ldapFactory; - public function __construct( IL10N $l, - ISession $session, - ICredentialsManager $credentialsManager, - CredentialsStore $credentialsStore, + protected ISession $session, + protected ICredentialsManager $credentialsManager, + private CredentialsStore $credentialsStore, IEventDispatcher $eventDispatcher, - ILDAPProviderFactory $ldapFactory + private ILDAPProviderFactory $ldapFactory, ) { - $this->session = $session; - $this->credentialsManager = $credentialsManager; - $this->credentialsStore = $credentialsStore; - $this->ldapFactory = $ldapFactory; - $this ->setIdentifier('password::logincredentials') ->setScheme(self::SCHEME_PASSWORD) ->setText($l->t('Log-in credentials, save in database')) ->addParameters([ + (new DefinitionParameter('password', $l->t('Password'))) + ->setType(DefinitionParameter::VALUE_PASSWORD) + ->setFlag(DefinitionParameter::FLAG_HIDDEN) + ->setFlag(DefinitionParameter::FLAG_OPTIONAL), ]); $eventDispatcher->addServiceListener(UserLoggedInEvent::class, StorePasswordListener::class); @@ -112,13 +80,16 @@ class LoginCredentials extends AuthMechanism { return $credentials; } - public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { + /** + * @return void + */ + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { if (!isset($user)) { throw new InsufficientDataForMeaningfulAnswerException('No login credentials saved'); } $credentials = $this->getCredentials($user); - $loginKey = $storage->getBackendOption("login_ldap_attr"); + $loginKey = $storage->getBackendOption('login_ldap_attr'); if ($loginKey) { $backend = $user->getBackend(); if ($backend instanceof IUserBackend && $backend->getBackendName() === 'LDAP') { diff --git a/apps/files_external/lib/Lib/Auth/Password/Password.php b/apps/files_external/lib/Lib/Auth/Password/Password.php index 5165a61d028..d4291148e3e 100644 --- a/apps/files_external/lib/Lib/Auth/Password/Password.php +++ b/apps/files_external/lib/Lib/Auth/Password/Password.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Morris Jobke <hey@morrisjobke.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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Auth\Password; @@ -35,9 +19,9 @@ class Password extends AuthMechanism { $this ->setIdentifier('password::password') ->setScheme(self::SCHEME_PASSWORD) - ->setText($l->t('Username and password')) + ->setText($l->t('Login and password')) ->addParameters([ - new DefinitionParameter('user', $l->t('Username')), + new DefinitionParameter('user', $l->t('Login')), (new DefinitionParameter('password', $l->t('Password'))) ->setType(DefinitionParameter::VALUE_PASSWORD), ]); diff --git a/apps/files_external/lib/Lib/Auth/Password/SessionCredentials.php b/apps/files_external/lib/Lib/Auth/Password/SessionCredentials.php index dbe5a2fdc20..8f161073771 100644 --- a/apps/files_external/lib/Lib/Auth/Password/SessionCredentials.php +++ b/apps/files_external/lib/Lib/Auth/Password/SessionCredentials.php @@ -1,35 +1,21 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Auth\Password; use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCA\Files_External\Lib\DefinitionParameter; use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; use OCA\Files_External\Lib\SessionStorageWrapper; use OCA\Files_External\Lib\StorageConfig; use OCP\Authentication\Exceptions\CredentialsUnavailableException; use OCP\Authentication\LoginCredentials\IStore as CredentialsStore; -use OCP\Files\Storage; +use OCP\Files\Storage\IStorage; +use OCP\Files\StorageAuthException; use OCP\IL10N; use OCP\IUser; @@ -38,30 +24,44 @@ use OCP\IUser; */ class SessionCredentials extends AuthMechanism { - /** @var CredentialsStore */ - private $credentialsStore; - - public function __construct(IL10N $l, CredentialsStore $credentialsStore) { - $this->credentialsStore = $credentialsStore; - + public function __construct( + IL10N $l, + private CredentialsStore $credentialsStore, + ) { $this->setIdentifier('password::sessioncredentials') ->setScheme(self::SCHEME_PASSWORD) ->setText($l->t('Log-in credentials, save in session')) - ->addParameters([]); + ->addParameters([ + (new DefinitionParameter('password', $l->t('Password'))) + ->setType(DefinitionParameter::VALUE_PASSWORD) + ->setFlag(DefinitionParameter::FLAG_HIDDEN) + ->setFlag(DefinitionParameter::FLAG_OPTIONAL), + ]); } - public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { + /** + * @return void + */ + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { try { $credentials = $this->credentialsStore->getLoginCredentials(); } catch (CredentialsUnavailableException $e) { throw new InsufficientDataForMeaningfulAnswerException('No session credentials saved'); } + if ($user === null) { + throw new StorageAuthException('Session unavailable'); + } + + if ($credentials->getUID() !== $user->getUID()) { + throw new StorageAuthException('Session credentials for storage owner not available'); + } + $storage->setBackendOption('user', $credentials->getLoginName()); $storage->setBackendOption('password', $credentials->getPassword()); } - public function wrapStorage(Storage $storage) { + public function wrapStorage(IStorage $storage): IStorage { return new SessionStorageWrapper(['storage' => $storage]); } } diff --git a/apps/files_external/lib/Lib/Auth/Password/UserGlobalAuth.php b/apps/files_external/lib/Lib/Auth/Password/UserGlobalAuth.php index 6312a3d136e..cb7165261ac 100644 --- a/apps/files_external/lib/Lib/Auth/Password/UserGlobalAuth.php +++ b/apps/files_external/lib/Lib/Auth/Password/UserGlobalAuth.php @@ -3,32 +3,13 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2019 Robin Appelman <robin@icewind.nl> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Robin Appelman <robin@icewind.nl> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Lib\Auth\Password; use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCA\Files_External\Lib\DefinitionParameter; use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\Service\BackendService; @@ -42,17 +23,15 @@ use OCP\Security\ICredentialsManager; class UserGlobalAuth extends AuthMechanism { private const CREDENTIALS_IDENTIFIER = 'password::global'; - /** @var ICredentialsManager */ - protected $credentialsManager; - - public function __construct(IL10N $l, ICredentialsManager $credentialsManager) { - $this->credentialsManager = $credentialsManager; - + public function __construct( + IL10N $l, + protected ICredentialsManager $credentialsManager, + ) { $this ->setIdentifier('password::global::user') ->setVisibility(BackendService::VISIBILITY_DEFAULT) ->setScheme(self::SCHEME_PASSWORD) - ->setText($l->t('Global credentials, user entered')); + ->setText($l->t('Global credentials, manually entered')); } public function saveBackendOptions(IUser $user, $id, $backendOptions) { @@ -61,6 +40,12 @@ class UserGlobalAuth extends AuthMechanism { if (!isset($backendOptions['user']) && !isset($backendOptions['password'])) { return; } + + if ($backendOptions['password'] === DefinitionParameter::UNMODIFIED_PLACEHOLDER) { + $oldCredentials = $this->credentialsManager->retrieve($user->getUID(), self::CREDENTIALS_IDENTIFIER); + $backendOptions['password'] = $oldCredentials['password']; + } + // make sure we're not setting any unexpected keys $credentials = [ 'user' => $backendOptions['user'], @@ -69,7 +54,10 @@ class UserGlobalAuth extends AuthMechanism { $this->credentialsManager->store($user->getUID(), self::CREDENTIALS_IDENTIFIER, $credentials); } - public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { + /** + * @return void + */ + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { if ($user === null) { throw new InsufficientDataForMeaningfulAnswerException('No credentials saved'); } diff --git a/apps/files_external/lib/Lib/Auth/Password/UserProvided.php b/apps/files_external/lib/Lib/Auth/Password/UserProvided.php index 0c8140e3c14..b158392f6eb 100644 --- a/apps/files_external/lib/Lib/Auth/Password/UserProvided.php +++ b/apps/files_external/lib/Lib/Auth/Password/UserProvided.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2015, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2015 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Auth\Password; @@ -40,19 +23,17 @@ use OCP\Security\ICredentialsManager; class UserProvided extends AuthMechanism implements IUserProvided { public const CREDENTIALS_IDENTIFIER_PREFIX = 'password::userprovided/'; - /** @var ICredentialsManager */ - protected $credentialsManager; - - public function __construct(IL10N $l, ICredentialsManager $credentialsManager) { - $this->credentialsManager = $credentialsManager; - + public function __construct( + IL10N $l, + protected ICredentialsManager $credentialsManager, + ) { $this ->setIdentifier('password::userprovided') ->setVisibility(BackendService::VISIBILITY_ADMIN) ->setScheme(self::SCHEME_PASSWORD) - ->setText($l->t('User entered, store in database')) + ->setText($l->t('Manually entered, store in database')) ->addParameters([ - (new DefinitionParameter('user', $l->t('Username'))) + (new DefinitionParameter('user', $l->t('Login'))) ->setFlag(DefinitionParameter::FLAG_USER_PROVIDED), (new DefinitionParameter('password', $l->t('Password'))) ->setType(DefinitionParameter::VALUE_PASSWORD) @@ -65,13 +46,21 @@ class UserProvided extends AuthMechanism implements IUserProvided { } public function saveBackendOptions(IUser $user, $mountId, array $options) { + if ($options['password'] === DefinitionParameter::UNMODIFIED_PLACEHOLDER) { + $oldCredentials = $this->credentialsManager->retrieve($user->getUID(), $this->getCredentialsIdentifier($mountId)); + $options['password'] = $oldCredentials['password']; + } + $this->credentialsManager->store($user->getUID(), $this->getCredentialsIdentifier($mountId), [ 'user' => $options['user'], // explicitly copy the fields we want instead of just passing the entire $options array 'password' => $options['password'] // this way we prevent users from being able to modify any other field ]); } - public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { + /** + * @return void + */ + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { if (!isset($user)) { throw new InsufficientDataForMeaningfulAnswerException('No credentials saved'); } diff --git a/apps/files_external/lib/Lib/Auth/PublicKey/RSA.php b/apps/files_external/lib/Lib/Auth/PublicKey/RSA.php index 2d17b4e9698..ad95c743d2d 100644 --- a/apps/files_external/lib/Lib/Auth/PublicKey/RSA.php +++ b/apps/files_external/lib/Lib/Auth/PublicKey/RSA.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Morris Jobke <hey@morrisjobke.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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Auth\PublicKey; @@ -36,31 +20,37 @@ use phpseclib\Crypt\RSA as RSACrypt; */ class RSA extends AuthMechanism { - /** @var IConfig */ - private $config; - - public function __construct(IL10N $l, IConfig $config) { - $this->config = $config; - + public function __construct( + IL10N $l, + private IConfig $config, + ) { $this ->setIdentifier('publickey::rsa') ->setScheme(self::SCHEME_PUBLICKEY) ->setText($l->t('RSA public key')) ->addParameters([ - new DefinitionParameter('user', $l->t('Username')), + new DefinitionParameter('user', $l->t('Login')), new DefinitionParameter('public_key', $l->t('Public key')), (new DefinitionParameter('private_key', 'private_key')) - ->setType(DefinitionParameter::VALUE_HIDDEN), + ->setType(DefinitionParameter::VALUE_PASSWORD) + ->setFlag(DefinitionParameter::FLAG_HIDDEN), ]) ->addCustomJs('public_key') ; } - public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { + /** + * @return void + */ + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { $auth = new RSACrypt(); $auth->setPassword($this->config->getSystemValue('secret', '')); if (!$auth->loadKey($storage->getBackendOption('private_key'))) { - throw new \RuntimeException('unable to load private key'); + // Add fallback routine for a time where secret was not enforced to be exists + $auth->setPassword(''); + if (!$auth->loadKey($storage->getBackendOption('private_key'))) { + throw new \RuntimeException('unable to load private key'); + } } $storage->setBackendOption('public_key_auth', $auth); } diff --git a/apps/files_external/lib/Lib/Auth/PublicKey/RSAPrivateKey.php b/apps/files_external/lib/Lib/Auth/PublicKey/RSAPrivateKey.php index 886d16965f4..8f58b71d5ac 100644 --- a/apps/files_external/lib/Lib/Auth/PublicKey/RSAPrivateKey.php +++ b/apps/files_external/lib/Lib/Auth/PublicKey/RSAPrivateKey.php @@ -1,24 +1,8 @@ <?php + /** - * @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl> - * - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Lib\Auth\PublicKey; @@ -35,18 +19,16 @@ use phpseclib\Crypt\RSA as RSACrypt; */ class RSAPrivateKey extends AuthMechanism { - /** @var IConfig */ - private $config; - - public function __construct(IL10N $l, IConfig $config) { - $this->config = $config; - + public function __construct( + IL10N $l, + private IConfig $config, + ) { $this ->setIdentifier('publickey::rsa_private') ->setScheme(self::SCHEME_PUBLICKEY) ->setText($l->t('RSA private key')) ->addParameters([ - new DefinitionParameter('user', $l->t('Username')), + new DefinitionParameter('user', $l->t('Login')), (new DefinitionParameter('password', $l->t('Password'))) ->setFlag(DefinitionParameter::FLAG_OPTIONAL) ->setType(DefinitionParameter::VALUE_PASSWORD), @@ -54,11 +36,18 @@ class RSAPrivateKey extends AuthMechanism { ]); } - public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { + /** + * @return void + */ + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { $auth = new RSACrypt(); $auth->setPassword($this->config->getSystemValue('secret', '')); if (!$auth->loadKey($storage->getBackendOption('private_key'))) { - throw new \RuntimeException('unable to load private key'); + // Add fallback routine for a time where secret was not enforced to be exists + $auth->setPassword(''); + if (!$auth->loadKey($storage->getBackendOption('private_key'))) { + throw new \RuntimeException('unable to load private key'); + } } $storage->setBackendOption('public_key_auth', $auth); } diff --git a/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php b/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php index 17492280275..26671110294 100644 --- a/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php +++ b/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php @@ -1,25 +1,8 @@ <?php /** - * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> - * - * @author Robin Appelman <robin@icewind.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Lib\Auth\SMB; @@ -30,10 +13,10 @@ use OCP\Authentication\LoginCredentials\IStore; use OCP\IL10N; class KerberosApacheAuth extends AuthMechanism { - /** @var IStore */ - private $credentialsStore; - - public function __construct(IL10N $l, IStore $credentialsStore) { + public function __construct( + IL10N $l, + private IStore $credentialsStore, + ) { $realm = new DefinitionParameter('default_realm', 'Default realm'); $realm ->setType(DefinitionParameter::VALUE_TEXT) @@ -44,7 +27,6 @@ class KerberosApacheAuth extends AuthMechanism { ->setScheme(self::SCHEME_SMB) ->setText($l->t('Kerberos ticket Apache mode')) ->addParameter($realm); - $this->credentialsStore = $credentialsStore; } public function getCredentialsStore(): IStore { diff --git a/apps/files_external/lib/Lib/Auth/SMB/KerberosAuth.php b/apps/files_external/lib/Lib/Auth/SMB/KerberosAuth.php index 44956c57ada..9210209192a 100644 --- a/apps/files_external/lib/Lib/Auth/SMB/KerberosAuth.php +++ b/apps/files_external/lib/Lib/Auth/SMB/KerberosAuth.php @@ -1,24 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> - * - * @author Robin Appelman <robin@icewind.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Lib\Auth\SMB; diff --git a/apps/files_external/lib/Lib/Backend/AmazonS3.php b/apps/files_external/lib/Lib/Backend/AmazonS3.php index 26da263f094..464b03b55e0 100644 --- a/apps/files_external/lib/Lib/Backend/AmazonS3.php +++ b/apps/files_external/lib/Lib/Backend/AmazonS3.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Backend; @@ -47,12 +30,21 @@ class AmazonS3 extends Backend { ->setFlag(DefinitionParameter::FLAG_OPTIONAL), (new DefinitionParameter('region', $l->t('Region'))) ->setFlag(DefinitionParameter::FLAG_OPTIONAL), + (new DefinitionParameter('storageClass', $l->t('Storage Class'))) + ->setFlag(DefinitionParameter::FLAG_OPTIONAL), (new DefinitionParameter('use_ssl', $l->t('Enable SSL'))) - ->setType(DefinitionParameter::VALUE_BOOLEAN), + ->setType(DefinitionParameter::VALUE_BOOLEAN) + ->setDefaultValue(true), (new DefinitionParameter('use_path_style', $l->t('Enable Path Style'))) ->setType(DefinitionParameter::VALUE_BOOLEAN), (new DefinitionParameter('legacy_auth', $l->t('Legacy (v2) authentication'))) ->setType(DefinitionParameter::VALUE_BOOLEAN), + (new DefinitionParameter('useMultipartCopy', $l->t('Enable multipart copy'))) + ->setType(DefinitionParameter::VALUE_BOOLEAN) + ->setDefaultValue(true), + (new DefinitionParameter('sse_c_key', $l->t('SSE-C encryption key'))) + ->setType(DefinitionParameter::VALUE_PASSWORD) + ->setFlag(DefinitionParameter::FLAG_OPTIONAL), ]) ->addAuthScheme(AccessKey::SCHEME_AMAZONS3_ACCESSKEY) ->addAuthScheme(AuthMechanism::SCHEME_NULL) diff --git a/apps/files_external/lib/Lib/Backend/Backend.php b/apps/files_external/lib/Lib/Backend/Backend.php index 021d208aed3..f7500ee24a4 100644 --- a/apps/files_external/lib/Lib/Backend/Backend.php +++ b/apps/files_external/lib/Lib/Backend/Backend.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Backend; @@ -26,10 +11,13 @@ use OCA\Files_External\Lib\Auth\AuthMechanism; use OCA\Files_External\Lib\DependencyTrait; use OCA\Files_External\Lib\FrontendDefinitionTrait; use OCA\Files_External\Lib\IdentifierTrait; +use OCA\Files_External\Lib\IFrontendDefinition; +use OCA\Files_External\Lib\IIdentifier; use OCA\Files_External\Lib\PriorityTrait; use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\Lib\StorageModifierTrait; use OCA\Files_External\Lib\VisibilityTrait; +use OCP\Files\Storage\IStorage; /** * Storage backend @@ -38,7 +26,7 @@ use OCA\Files_External\Lib\VisibilityTrait; * such as \OCP\IDB for database operations. This allows a backend * to perform advanced operations based on provided information. * - * An authenication scheme defines the parameter interface, common to the + * An authentication scheme defines the parameter interface, common to the * storage implementation, the backend and the authentication mechanism. * A storage implementation expects parameters according to the authentication * scheme, which are provided from the authentication mechanism. @@ -55,7 +43,7 @@ use OCA\Files_External\Lib\VisibilityTrait; * - StorageModifierTrait * Object can affect storage mounting */ -class Backend implements \JsonSerializable { +class Backend implements \JsonSerializable, IIdentifier, IFrontendDefinition { use VisibilityTrait; use FrontendDefinitionTrait; use PriorityTrait; @@ -73,7 +61,7 @@ class Backend implements \JsonSerializable { private $legacyAuthMechanism; /** - * @return string + * @return class-string<IStorage> */ public function getStorageClass() { return $this->storageClass; @@ -118,21 +106,17 @@ class Backend implements \JsonSerializable { return $this->legacyAuthMechanism; } - /** - * @param AuthMechanism $authMechanism - * @return self - */ - public function setLegacyAuthMechanism(AuthMechanism $authMechanism) { + public function setLegacyAuthMechanism(AuthMechanism $authMechanism): self { $this->legacyAuthMechanism = $authMechanism; return $this; } /** * @param callable $callback dynamic auth mechanism selection - * @return self */ - public function setLegacyAuthMechanismCallback(callable $callback) { + public function setLegacyAuthMechanismCallback(callable $callback): self { $this->legacyAuthMechanism = $callback; + return $this; } /** diff --git a/apps/files_external/lib/Lib/Backend/DAV.php b/apps/files_external/lib/Lib/Backend/DAV.php index cf16677334d..dea9e7c5e77 100644 --- a/apps/files_external/lib/Lib/Backend/DAV.php +++ b/apps/files_external/lib/Lib/Backend/DAV.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Morris Jobke <hey@morrisjobke.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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Backend; @@ -43,7 +27,8 @@ class DAV extends Backend { (new DefinitionParameter('root', $l->t('Remote subfolder'))) ->setFlag(DefinitionParameter::FLAG_OPTIONAL), (new DefinitionParameter('secure', $l->t('Secure https://'))) - ->setType(DefinitionParameter::VALUE_BOOLEAN), + ->setType(DefinitionParameter::VALUE_BOOLEAN) + ->setDefaultValue(true), ]) ->addAuthScheme(AuthMechanism::SCHEME_PASSWORD) ->setLegacyAuthMechanism($legacyAuth) diff --git a/apps/files_external/lib/Lib/Backend/FTP.php b/apps/files_external/lib/Lib/Backend/FTP.php index 587f3e68535..72a8184c9b9 100644 --- a/apps/files_external/lib/Lib/Backend/FTP.php +++ b/apps/files_external/lib/Lib/Backend/FTP.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Morris Jobke <hey@morrisjobke.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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Backend; @@ -40,10 +24,13 @@ class FTP extends Backend { ->setText($l->t('FTP')) ->addParameters([ new DefinitionParameter('host', $l->t('Host')), + (new DefinitionParameter('port', $l->t('Port'))) + ->setFlag(DefinitionParameter::FLAG_OPTIONAL), (new DefinitionParameter('root', $l->t('Remote subfolder'))) ->setFlag(DefinitionParameter::FLAG_OPTIONAL), (new DefinitionParameter('secure', $l->t('Secure ftps://'))) - ->setType(DefinitionParameter::VALUE_BOOLEAN), + ->setType(DefinitionParameter::VALUE_BOOLEAN) + ->setDefaultValue(true), ]) ->addAuthScheme(AuthMechanism::SCHEME_PASSWORD) ->setLegacyAuthMechanism($legacyAuth) diff --git a/apps/files_external/lib/Lib/Backend/InvalidBackend.php b/apps/files_external/lib/Lib/Backend/InvalidBackend.php index 3a535577447..48912c0e49e 100644 --- a/apps/files_external/lib/Lib/Backend/InvalidBackend.php +++ b/apps/files_external/lib/Lib/Backend/InvalidBackend.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud GmbH. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud GmbH. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Backend; @@ -34,21 +17,19 @@ use OCP\IUser; */ class InvalidBackend extends Backend { - /** @var string Invalid backend id */ - private $invalidId; - /** * Constructs a new InvalidBackend with the id of the invalid backend * for display purposes * * @param string $invalidId id of the backend that did not exist */ - public function __construct($invalidId) { - $this->invalidId = $invalidId; + public function __construct( + private $invalidId, + ) { $this - ->setIdentifier($invalidId) + ->setIdentifier($this->invalidId) ->setStorageClass('\OC\Files\Storage\FailedStorage') - ->setText('Unknown storage backend ' . $invalidId); + ->setText('Unknown storage backend ' . $this->invalidId); } /** @@ -60,7 +41,10 @@ class InvalidBackend extends Backend { return $this->invalidId; } - public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { + /** + * @return void + */ + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { $storage->setBackendOption('exception', new \Exception('Unknown storage backend "' . $this->invalidId . '"', StorageNotAvailableException::STATUS_ERROR)); } } diff --git a/apps/files_external/lib/Lib/Backend/LegacyBackend.php b/apps/files_external/lib/Lib/Backend/LegacyBackend.php index d00c5b0e944..9c7e5b01bc3 100644 --- a/apps/files_external/lib/Lib/Backend/LegacyBackend.php +++ b/apps/files_external/lib/Lib/Backend/LegacyBackend.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Morris Jobke <hey@morrisjobke.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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Backend; @@ -61,18 +45,14 @@ class LegacyBackend extends Backend { $placeholder = substr($placeholder, 1); } switch ($placeholder[0]) { - case '!': - $type = DefinitionParameter::VALUE_BOOLEAN; - $placeholder = substr($placeholder, 1); - break; - case '*': - $type = DefinitionParameter::VALUE_PASSWORD; - $placeholder = substr($placeholder, 1); - break; - case '#': - $type = DefinitionParameter::VALUE_HIDDEN; - $placeholder = substr($placeholder, 1); - break; + case '!': + $type = DefinitionParameter::VALUE_BOOLEAN; + $placeholder = substr($placeholder, 1); + break; + case '*': + $type = DefinitionParameter::VALUE_PASSWORD; + $placeholder = substr($placeholder, 1); + break; } $this->addParameter((new DefinitionParameter($name, $placeholder)) ->setType($type) diff --git a/apps/files_external/lib/Lib/Backend/Local.php b/apps/files_external/lib/Lib/Backend/Local.php index 88b251360d6..56940b8e83b 100644 --- a/apps/files_external/lib/Lib/Backend/Local.php +++ b/apps/files_external/lib/Lib/Backend/Local.php @@ -1,33 +1,19 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Morris Jobke <hey@morrisjobke.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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Backend; use OCA\Files_External\Lib\Auth\AuthMechanism; use OCA\Files_External\Lib\Auth\NullMechanism; use OCA\Files_External\Lib\DefinitionParameter; +use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\Service\BackendService; use OCP\IL10N; +use OCP\IUser; class Local extends Backend { public function __construct(IL10N $l, NullMechanism $legacyAuth) { @@ -45,4 +31,8 @@ class Local extends Backend { ->setLegacyAuthMechanism($legacyAuth) ; } + + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null): void { + $storage->setBackendOption('isExternal', true); + } } diff --git a/apps/files_external/lib/Lib/Backend/OwnCloud.php b/apps/files_external/lib/Lib/Backend/OwnCloud.php index 8b33b98b5c4..0c0e2c6d300 100644 --- a/apps/files_external/lib/Lib/Backend/OwnCloud.php +++ b/apps/files_external/lib/Lib/Backend/OwnCloud.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Backend; @@ -41,7 +24,8 @@ class OwnCloud extends Backend { (new DefinitionParameter('root', $l->t('Remote subfolder'))) ->setFlag(DefinitionParameter::FLAG_OPTIONAL), (new DefinitionParameter('secure', $l->t('Secure https://'))) - ->setType(DefinitionParameter::VALUE_BOOLEAN), + ->setType(DefinitionParameter::VALUE_BOOLEAN) + ->setDefaultValue(true), ]) ->addAuthScheme(AuthMechanism::SCHEME_PASSWORD) ->setLegacyAuthMechanism($legacyAuth) diff --git a/apps/files_external/lib/Lib/Backend/SFTP.php b/apps/files_external/lib/Lib/Backend/SFTP.php index c65720ea1ae..0926cf7fd93 100644 --- a/apps/files_external/lib/Lib/Backend/SFTP.php +++ b/apps/files_external/lib/Lib/Backend/SFTP.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Morris Jobke <hey@morrisjobke.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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Backend; @@ -37,6 +21,8 @@ class SFTP extends Backend { ->setText($l->t('SFTP')) ->addParameters([ new DefinitionParameter('host', $l->t('Host')), + (new DefinitionParameter('port', $l->t('Port'))) + ->setFlag(DefinitionParameter::FLAG_OPTIONAL), (new DefinitionParameter('root', $l->t('Root'))) ->setFlag(DefinitionParameter::FLAG_OPTIONAL), ]) diff --git a/apps/files_external/lib/Lib/Backend/SFTP_Key.php b/apps/files_external/lib/Lib/Backend/SFTP_Key.php index e9a64407ce2..278fae3fba7 100644 --- a/apps/files_external/lib/Lib/Backend/SFTP_Key.php +++ b/apps/files_external/lib/Lib/Backend/SFTP_Key.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Morris Jobke <hey@morrisjobke.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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Backend; diff --git a/apps/files_external/lib/Lib/Backend/SMB.php b/apps/files_external/lib/Lib/Backend/SMB.php index bf73c5b40f8..e86ad98880c 100644 --- a/apps/files_external/lib/Lib/Backend/SMB.php +++ b/apps/files_external/lib/Lib/Backend/SMB.php @@ -1,48 +1,30 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @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 Valdnet <47037905+Valdnet@users.noreply.github.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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Backend; use Icewind\SMB\BasicAuth; -use Icewind\SMB\KerberosApacheAuth; use Icewind\SMB\KerberosAuth; +use Icewind\SMB\KerberosTicket; +use Icewind\SMB\Native\NativeServer; +use Icewind\SMB\Wrapped\Server; use OCA\Files_External\Lib\Auth\AuthMechanism; use OCA\Files_External\Lib\Auth\Password\Password; use OCA\Files_External\Lib\Auth\SMB\KerberosApacheAuth as KerberosApacheAuthMechanism; use OCA\Files_External\Lib\DefinitionParameter; use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; -use OCA\Files_External\Lib\LegacyDependencyCheckPolyfill; +use OCA\Files_External\Lib\MissingDependency; +use OCA\Files_External\Lib\Storage\SystemBridge; use OCA\Files_External\Lib\StorageConfig; use OCP\IL10N; use OCP\IUser; class SMB extends Backend { - use LegacyDependencyCheckPolyfill; - public function __construct(IL10N $l, Password $legacyAuth) { $this ->setIdentifier('smb') @@ -59,20 +41,26 @@ class SMB extends Backend { (new DefinitionParameter('show_hidden', $l->t('Show hidden files'))) ->setType(DefinitionParameter::VALUE_BOOLEAN) ->setFlag(DefinitionParameter::FLAG_OPTIONAL), + (new DefinitionParameter('case_sensitive', $l->t('Case sensitive file system'))) + ->setType(DefinitionParameter::VALUE_BOOLEAN) + ->setFlag(DefinitionParameter::FLAG_OPTIONAL) + ->setDefaultValue(true) + ->setTooltip($l->t('Disabling it will allow to use a case insensitive file system, but comes with a performance penalty')), (new DefinitionParameter('check_acl', $l->t('Verify ACL access when listing files'))) ->setType(DefinitionParameter::VALUE_BOOLEAN) ->setFlag(DefinitionParameter::FLAG_OPTIONAL) - ->setTooltip($l->t("Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty")), + ->setTooltip($l->t("Check the ACL's of each file or folder inside a directory to filter out items where the account has no read permissions, comes with a performance penalty")), (new DefinitionParameter('timeout', $l->t('Timeout'))) - ->setType(DefinitionParameter::VALUE_HIDDEN) - ->setFlag(DefinitionParameter::FLAG_OPTIONAL), + ->setType(DefinitionParameter::VALUE_TEXT) + ->setFlag(DefinitionParameter::FLAG_OPTIONAL) + ->setFlag(DefinitionParameter::FLAG_HIDDEN), ]) ->addAuthScheme(AuthMechanism::SCHEME_PASSWORD) ->addAuthScheme(AuthMechanism::SCHEME_SMB) ->setLegacyAuthMechanism($legacyAuth); } - public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null): void { $auth = $storage->getAuthMechanism(); if ($auth->getScheme() === AuthMechanism::SCHEME_PASSWORD) { if (!is_string($storage->getBackendOption('user')) || !is_string($storage->getBackendOption('password'))) { @@ -94,33 +82,33 @@ class SMB extends Backend { throw new \InvalidArgumentException('invalid authentication backend'); } $credentialsStore = $auth->getCredentialsStore(); - $kerbAuth = new KerberosApacheAuth(); + $kerbAuth = new KerberosAuth(); + $kerbAuth->setTicket(KerberosTicket::fromEnv()); // check if a kerberos ticket is available, else fallback to session credentials - if ($kerbAuth->checkTicket()) { + if ($kerbAuth->getTicket()?->isValid()) { $smbAuth = $kerbAuth; } else { try { $credentials = $credentialsStore->getLoginCredentials(); - $user = $credentials->getLoginName(); + $loginName = $credentials->getLoginName(); $pass = $credentials->getPassword(); - preg_match('/(.*)@(.*)/', $user, $matches); + preg_match('/(.*)@(.*)/', $loginName, $matches); $realm = $storage->getBackendOption('default_realm'); if (empty($realm)) { $realm = 'WORKGROUP'; } if (count($matches) === 0) { - $username = $user; + $username = $loginName; $workgroup = $realm; } else { - $username = $matches[1]; - $workgroup = $matches[2]; + [, $username, $workgroup] = $matches; } $smbAuth = new BasicAuth( $username, $workgroup, $pass ); - } catch (\Exception $e) { + } catch (\Exception) { throw new InsufficientDataForMeaningfulAnswerException('No session credentials saved'); } } @@ -133,4 +121,20 @@ class SMB extends Backend { $storage->setBackendOption('auth', $smbAuth); } + + public function checkDependencies(): array { + $system = \OCP\Server::get(SystemBridge::class); + if (NativeServer::available($system)) { + return []; + } elseif (Server::available($system)) { + $missing = new MissingDependency('php-smbclient'); + $missing->setOptional(true); + $missing->setMessage('The php-smbclient library provides improved compatibility and performance for SMB storages.'); + return [$missing]; + } else { + $missing = new MissingDependency('php-smbclient'); + $missing->setMessage('Either the php-smbclient library (preferred) or the smbclient binary is required for SMB storages.'); + return [$missing, new MissingDependency('smbclient')]; + } + } } diff --git a/apps/files_external/lib/Lib/Backend/SMB_OC.php b/apps/files_external/lib/Lib/Backend/SMB_OC.php index 78c266e36d1..bcb8d0fbf16 100644 --- a/apps/files_external/lib/Lib/Backend/SMB_OC.php +++ b/apps/files_external/lib/Lib/Backend/SMB_OC.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Valdnet <47037905+Valdnet@users.noreply.github.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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Backend; @@ -46,7 +29,7 @@ class SMB_OC extends Backend { ->setText($l->t('SMB/CIFS using OC login')) ->addParameters([ new DefinitionParameter('host', $l->t('Host')), - (new DefinitionParameter('username_as_share', $l->t('Username as share'))) + (new DefinitionParameter('username_as_share', $l->t('Login as share'))) ->setType(DefinitionParameter::VALUE_BOOLEAN), (new DefinitionParameter('share', $l->t('Share'))) ->setFlag(DefinitionParameter::FLAG_OPTIONAL), @@ -60,7 +43,10 @@ class SMB_OC extends Backend { ; } - public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { + /** + * @return void + */ + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { $username_as_share = ($storage->getBackendOption('username_as_share') === true); if ($username_as_share) { diff --git a/apps/files_external/lib/Lib/Backend/Swift.php b/apps/files_external/lib/Lib/Backend/Swift.php index 8450af5f19c..37527ba3dbb 100644 --- a/apps/files_external/lib/Lib/Backend/Swift.php +++ b/apps/files_external/lib/Lib/Backend/Swift.php @@ -1,28 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Julien Lutran <julien.lutran@corp.ovh.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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Backend; diff --git a/apps/files_external/lib/Lib/Config/IAuthMechanismProvider.php b/apps/files_external/lib/Lib/Config/IAuthMechanismProvider.php index 4358b7abc4f..0c2e90a243c 100644 --- a/apps/files_external/lib/Lib/Config/IAuthMechanismProvider.php +++ b/apps/files_external/lib/Lib/Config/IAuthMechanismProvider.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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/> - * + * 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\Lib\Config; diff --git a/apps/files_external/lib/Lib/Config/IBackendProvider.php b/apps/files_external/lib/Lib/Config/IBackendProvider.php index e2b04e78c10..44c460c3138 100644 --- a/apps/files_external/lib/Lib/Config/IBackendProvider.php +++ b/apps/files_external/lib/Lib/Config/IBackendProvider.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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/> - * + * 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\Lib\Config; diff --git a/apps/files_external/lib/Lib/DefinitionParameter.php b/apps/files_external/lib/Lib/DefinitionParameter.php index 6b081d5a089..a73dd2df967 100644 --- a/apps/files_external/lib/Lib/DefinitionParameter.php +++ b/apps/files_external/lib/Lib/DefinitionParameter.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * - * @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/> - * + * 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\Lib; @@ -35,48 +19,45 @@ class DefinitionParameter implements \JsonSerializable { public const VALUE_TEXT = 0; public const VALUE_BOOLEAN = 1; public const VALUE_PASSWORD = 2; - public const VALUE_HIDDEN = 3; /** Flag constants */ public const FLAG_NONE = 0; public const FLAG_OPTIONAL = 1; public const FLAG_USER_PROVIDED = 2; - - /** @var string name of parameter */ - private $name; - - /** @var string human-readable parameter text */ - private $text; + public const FLAG_HIDDEN = 4; /** @var string human-readable parameter tooltip */ - private $tooltip = ''; + private string $tooltip = ''; /** @var int value type, see self::VALUE_* constants */ - private $type = self::VALUE_TEXT; + private int $type = self::VALUE_TEXT; /** @var int flags, see self::FLAG_* constants */ - private $flags = self::FLAG_NONE; + private int $flags = self::FLAG_NONE; /** - * @param string $name - * @param string $text + * @param string $name parameter name + * @param string $text parameter description + * @param mixed $defaultValue default value */ - public function __construct($name, $text) { - $this->name = $name; - $this->text = $text; + public function __construct( + private string $name, + private string $text, + private $defaultValue = null, + ) { } /** * @return string */ - public function getName() { + public function getName(): string { return $this->name; } /** * @return string */ - public function getText() { + public function getText(): string { return $this->text; } @@ -85,7 +66,7 @@ class DefinitionParameter implements \JsonSerializable { * * @return int */ - public function getType() { + public function getType(): int { return $this->type; } @@ -95,15 +76,31 @@ class DefinitionParameter implements \JsonSerializable { * @param int $type * @return self */ - public function setType($type) { + public function setType(int $type) { $this->type = $type; return $this; } /** + * @return mixed default value + */ + public function getDefaultValue() { + return $this->defaultValue; + } + + /** + * @param mixed $defaultValue default value + * @return self + */ + public function setDefaultValue($defaultValue) { + $this->defaultValue = $defaultValue; + return $this; + } + + /** * @return string */ - public function getTypeName() { + public function getTypeName(): string { switch ($this->type) { case self::VALUE_BOOLEAN: return 'boolean'; @@ -119,7 +116,7 @@ class DefinitionParameter implements \JsonSerializable { /** * @return int */ - public function getFlags() { + public function getFlags(): int { return $this->flags; } @@ -127,7 +124,7 @@ class DefinitionParameter implements \JsonSerializable { * @param int $flags * @return self */ - public function setFlags($flags) { + public function setFlags(int $flags) { $this->flags = $flags; return $this; } @@ -136,7 +133,7 @@ class DefinitionParameter implements \JsonSerializable { * @param int $flag * @return self */ - public function setFlag($flag) { + public function setFlag(int $flag) { $this->flags |= $flag; return $this; } @@ -145,7 +142,7 @@ class DefinitionParameter implements \JsonSerializable { * @param int $flag * @return bool */ - public function isFlagSet($flag) { + public function isFlagSet(int $flag): bool { return (bool)($this->flags & $flag); } @@ -169,15 +166,20 @@ class DefinitionParameter implements \JsonSerializable { * Serialize into JSON for client-side JS */ public function jsonSerialize(): array { - return [ + $result = [ 'value' => $this->getText(), 'flags' => $this->getFlags(), 'type' => $this->getType(), 'tooltip' => $this->getTooltip(), ]; + $defaultValue = $this->getDefaultValue(); + if ($defaultValue) { + $result['defaultValue'] = $defaultValue; + } + return $result; } - public function isOptional() { + public function isOptional(): bool { return $this->isFlagSet(self::FLAG_OPTIONAL) || $this->isFlagSet(self::FLAG_USER_PROVIDED); } @@ -188,7 +190,7 @@ class DefinitionParameter implements \JsonSerializable { * @param mixed $value Value to check * @return bool success */ - public function validateValue(&$value) { + public function validateValue(&$value): bool { switch ($this->getType()) { case self::VALUE_BOOLEAN: if (!is_bool($value)) { diff --git a/apps/files_external/lib/Lib/DependencyTrait.php b/apps/files_external/lib/Lib/DependencyTrait.php index 135f5ade296..644132b82bc 100644 --- a/apps/files_external/lib/Lib/DependencyTrait.php +++ b/apps/files_external/lib/Lib/DependencyTrait.php @@ -1,23 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Robin McCorkell <robin@mccorkell.me.uk> - * - * @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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib; diff --git a/apps/files_external/lib/Lib/FrontendDefinitionTrait.php b/apps/files_external/lib/Lib/FrontendDefinitionTrait.php index b10d3a0b276..0f280d1d486 100644 --- a/apps/files_external/lib/Lib/FrontendDefinitionTrait.php +++ b/apps/files_external/lib/Lib/FrontendDefinitionTrait.php @@ -1,25 +1,9 @@ <?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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib; @@ -29,62 +13,45 @@ namespace OCA\Files_External\Lib; trait FrontendDefinitionTrait { /** @var string human-readable mechanism name */ - private $text; + private string $text = ''; - /** @var DefinitionParameter[] parameters for mechanism */ - private $parameters = []; + /** @var array<string, DefinitionParameter> parameters for mechanism */ + private array $parameters = []; /** @var string[] custom JS */ - private $customJs = []; + private array $customJs = []; - /** - * @return string - */ - public function getText() { + public function getText(): string { return $this->text; } - /** - * @param string $text - * @return $this - */ - public function setText($text) { + public function setText(string $text): self { $this->text = $text; return $this; } - /** - * @param FrontendDefinitionTrait $a - * @param FrontendDefinitionTrait $b - * @return int - */ - public static function lexicalCompare(FrontendDefinitionTrait $a, FrontendDefinitionTrait $b) { + public static function lexicalCompare(IFrontendDefinition $a, IFrontendDefinition $b): int { return strcmp($a->getText(), $b->getText()); } /** - * @return DefinitionParameter[] + * @return array<string, DefinitionParameter> */ - public function getParameters() { + public function getParameters(): array { return $this->parameters; } /** - * @param DefinitionParameter[] $parameters - * @return self + * @param list<DefinitionParameter> $parameters */ - public function addParameters(array $parameters) { + public function addParameters(array $parameters): self { foreach ($parameters as $parameter) { $this->addParameter($parameter); } return $this; } - /** - * @param DefinitionParameter $parameter - * @return self - */ - public function addParameter(DefinitionParameter $parameter) { + public function addParameter(DefinitionParameter $parameter): self { $this->parameters[$parameter->getName()] = $parameter; return $this; } @@ -92,7 +59,7 @@ trait FrontendDefinitionTrait { /** * @return string[] */ - public function getCustomJs() { + public function getCustomJs(): array { return $this->customJs; } @@ -100,17 +67,15 @@ trait FrontendDefinitionTrait { * @param string $custom * @return self */ - public function addCustomJs($custom) { + public function addCustomJs(string $custom): self { $this->customJs[] = $custom; return $this; } /** * Serialize into JSON for client-side JS - * - * @return array */ - public function jsonSerializeDefinition() { + public function jsonSerializeDefinition(): array { $configuration = []; foreach ($this->getParameters() as $parameter) { $configuration[$parameter->getName()] = $parameter; @@ -126,11 +91,8 @@ trait FrontendDefinitionTrait { /** * Check if parameters are satisfied in a StorageConfig - * - * @param StorageConfig $storage - * @return bool */ - public function validateStorageDefinition(StorageConfig $storage) { + public function validateStorageDefinition(StorageConfig $storage): bool { foreach ($this->getParameters() as $name => $parameter) { $value = $storage->getBackendOption($name); if (!is_null($value) || !$parameter->isOptional()) { diff --git a/apps/files_external/lib/Lib/IFrontendDefinition.php b/apps/files_external/lib/Lib/IFrontendDefinition.php new file mode 100644 index 00000000000..c8b06a1c30b --- /dev/null +++ b/apps/files_external/lib/Lib/IFrontendDefinition.php @@ -0,0 +1,43 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Files_External\Lib; + +interface IFrontendDefinition { + + public function getText(): string; + + public function setText(string $text): self; + + /** + * @return array<string, DefinitionParameter> + */ + public function getParameters(): array; + + /** + * @param list<DefinitionParameter> $parameters + */ + public function addParameters(array $parameters): self; + + public function addParameter(DefinitionParameter $parameter): self; + + /** + * @return string[] + */ + public function getCustomJs(): array; + + public function addCustomJs(string $custom): self; + + /** + * Serialize into JSON for client-side JS + */ + public function jsonSerializeDefinition(): array; + + /** + * Check if parameters are satisfied in a StorageConfig + */ + public function validateStorageDefinition(StorageConfig $storage): bool; +} diff --git a/apps/files_external/lib/Lib/IIdentifier.php b/apps/files_external/lib/Lib/IIdentifier.php new file mode 100644 index 00000000000..0677409a3cf --- /dev/null +++ b/apps/files_external/lib/Lib/IIdentifier.php @@ -0,0 +1,14 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Files_External\Lib; + +interface IIdentifier { + + public function getIdentifier(): string; + + public function setIdentifier(string $identifier): self; +} diff --git a/apps/files_external/lib/Lib/IdentifierTrait.php b/apps/files_external/lib/Lib/IdentifierTrait.php index 37813795490..f5ffde32307 100644 --- a/apps/files_external/lib/Lib/IdentifierTrait.php +++ b/apps/files_external/lib/Lib/IdentifierTrait.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib; @@ -28,27 +13,17 @@ namespace OCA\Files_External\Lib; */ trait IdentifierTrait { - /** @var string */ - protected $identifier; + protected string $identifier = ''; /** @var string[] */ - protected $identifierAliases = []; - - /** @var IdentifierTrait */ - protected $deprecateTo = null; + protected array $identifierAliases = []; + protected ?IIdentifier $deprecateTo = null; - /** - * @return string - */ - public function getIdentifier() { + public function getIdentifier(): string { return $this->identifier; } - /** - * @param string $identifier - * @return $this - */ - public function setIdentifier($identifier) { + public function setIdentifier(string $identifier): self { $this->identifier = $identifier; $this->identifierAliases[] = $identifier; return $this; @@ -57,39 +32,25 @@ trait IdentifierTrait { /** * @return string[] */ - public function getIdentifierAliases() { + public function getIdentifierAliases(): array { return $this->identifierAliases; } - /** - * @param string $alias - * @return $this - */ - public function addIdentifierAlias($alias) { + public function addIdentifierAlias(string $alias): self { $this->identifierAliases[] = $alias; return $this; } - /** - * @return object|null - */ - public function getDeprecateTo() { + public function getDeprecateTo(): ?IIdentifier { return $this->deprecateTo; } - /** - * @param object $destinationObject - * @return self - */ - public function deprecateTo($destinationObject) { + public function deprecateTo(IIdentifier $destinationObject): self { $this->deprecateTo = $destinationObject; return $this; } - /** - * @return array - */ - public function jsonSerializeIdentifier() { + public function jsonSerializeIdentifier(): array { $data = [ 'identifier' => $this->identifier, 'identifierAliases' => $this->identifierAliases, diff --git a/apps/files_external/lib/Lib/InsufficientDataForMeaningfulAnswerException.php b/apps/files_external/lib/Lib/InsufficientDataForMeaningfulAnswerException.php index b2301543d8e..1e872b35072 100644 --- a/apps/files_external/lib/Lib/InsufficientDataForMeaningfulAnswerException.php +++ b/apps/files_external/lib/Lib/InsufficientDataForMeaningfulAnswerException.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Lukas Reschke <lukas@statuscode.ch> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib; @@ -38,7 +21,7 @@ class InsufficientDataForMeaningfulAnswerException extends StorageNotAvailableEx * @param \Exception|null $previous * @since 6.0.0 */ - public function __construct($message = '', $code = self::STATUS_INDETERMINATE, \Exception $previous = null) { + public function __construct($message = '', $code = self::STATUS_INDETERMINATE, ?\Exception $previous = null) { parent::__construct($message, $code, $previous); } } diff --git a/apps/files_external/lib/Lib/LegacyDependencyCheckPolyfill.php b/apps/files_external/lib/Lib/LegacyDependencyCheckPolyfill.php index 732413c78cd..f6311fae83e 100644 --- a/apps/files_external/lib/Lib/LegacyDependencyCheckPolyfill.php +++ b/apps/files_external/lib/Lib/LegacyDependencyCheckPolyfill.php @@ -1,33 +1,21 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Robin McCorkell <robin@mccorkell.me.uk> - * - * @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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib; +use OCP\Files\Storage\IStorage; + /** * Polyfill for checking dependencies using legacy Storage::checkDependencies() */ trait LegacyDependencyCheckPolyfill { /** - * @return string + * @return class-string<IStorage> */ abstract public function getStorageClass(); @@ -54,7 +42,7 @@ trait LegacyDependencyCheckPolyfill { $module = $key; $message = $value; } - $value = new MissingDependency($module, $this); + $value = new MissingDependency($module); $value->setMessage($message); } $ret[] = $value; diff --git a/apps/files_external/lib/Lib/MissingDependency.php b/apps/files_external/lib/Lib/MissingDependency.php index b740f4596b2..c2da7fcadbf 100644 --- a/apps/files_external/lib/Lib/MissingDependency.php +++ b/apps/files_external/lib/Lib/MissingDependency.php @@ -1,23 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Robin McCorkell <robin@mccorkell.me.uk> - * - * @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/> - * + * 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\Lib; @@ -26,30 +12,23 @@ namespace OCA\Files_External\Lib; */ class MissingDependency { - /** @var string */ - private $dependency; - /** @var string|null Custom message */ - private $message = null; + private ?string $message = null; + private bool $optional = false; /** * @param string $dependency */ - public function __construct($dependency) { - $this->dependency = $dependency; + public function __construct( + private readonly string $dependency, + ) { } - /** - * @return string - */ - public function getDependency() { + public function getDependency(): string { return $this->dependency; } - /** - * @return string|null - */ - public function getMessage() { + public function getMessage(): ?string { return $this->message; } @@ -61,4 +40,12 @@ class MissingDependency { $this->message = $message; return $this; } + + public function isOptional(): bool { + return $this->optional; + } + + public function setOptional(bool $optional): void { + $this->optional = $optional; + } } diff --git a/apps/files_external/lib/Lib/Notify/SMBNotifyHandler.php b/apps/files_external/lib/Lib/Notify/SMBNotifyHandler.php index 66567b397f2..2812df6ad6a 100644 --- a/apps/files_external/lib/Lib/Notify/SMBNotifyHandler.php +++ b/apps/files_external/lib/Lib/Notify/SMBNotifyHandler.php @@ -1,26 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Julius Härtl <jus@bitgrid.net> - * @author Robin Appelman <robin@icewind.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Lib\Notify; @@ -31,11 +13,6 @@ use OCP\Files\Notify\INotifyHandler; class SMBNotifyHandler implements INotifyHandler { /** - * @var \Icewind\SMB\INotifyHandler - */ - private $shareNotifyHandler; - - /** * @var string */ private $root; @@ -48,8 +25,10 @@ class SMBNotifyHandler implements INotifyHandler { * @param \Icewind\SMB\INotifyHandler $shareNotifyHandler * @param string $root */ - public function __construct(\Icewind\SMB\INotifyHandler $shareNotifyHandler, $root) { - $this->shareNotifyHandler = $shareNotifyHandler; + public function __construct( + private \Icewind\SMB\INotifyHandler $shareNotifyHandler, + $root, + ) { $this->root = str_replace('\\', '/', $root); } diff --git a/apps/files_external/lib/Lib/PersonalMount.php b/apps/files_external/lib/Lib/PersonalMount.php index b22266fce18..d9dbddd1449 100644 --- a/apps/files_external/lib/Lib/PersonalMount.php +++ b/apps/files_external/lib/Lib/PersonalMount.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Julius Härtl <jus@bitgrid.net> - * @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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib; @@ -28,40 +11,35 @@ use OC\Files\Mount\MoveableMount; use OCA\Files_External\Config\ExternalMountPoint; use OCA\Files_External\Service\UserStoragesService; use OCP\Files\Storage\IStorage; +use OCP\Files\Storage\IStorageFactory; /** * Person mount points can be moved by the user */ class PersonalMount extends ExternalMountPoint implements MoveableMount { - /** @var UserStoragesService */ - protected $storagesService; - - /** @var int */ - protected $numericStorageId; - /** * @param UserStoragesService $storagesService * @param int $storageId * @param IStorage $storage * @param string $mountpoint * @param array $arguments (optional) configuration for the storage backend - * @param \OCP\Files\Storage\IStorageFactory $loader + * @param IStorageFactory $loader * @param array $mountOptions mount specific options + * @param int $externalStorageId */ public function __construct( - UserStoragesService $storagesService, + protected UserStoragesService $storagesService, StorageConfig $storageConfig, - $storageId, + /** @var int id of the external storage (mount) (not the numeric id of the resulting storage!) */ + protected $numericExternalStorageId, $storage, $mountpoint, $arguments = null, $loader = null, $mountOptions = null, - $mountId = null + $mountId = null, ) { parent::__construct($storageConfig, $storage, $mountpoint, $arguments, $loader, $mountOptions, $mountId); - $this->storagesService = $storagesService; - $this->numericStorageId = $storageId; } /** @@ -71,7 +49,7 @@ class PersonalMount extends ExternalMountPoint implements MoveableMount { * @return bool */ public function moveMount($target) { - $storage = $this->storagesService->getStorage($this->numericStorageId); + $storage = $this->storagesService->getStorage($this->numericExternalStorageId); // remove "/$user/files" prefix $targetParts = explode('/', trim($target, '/'), 3); $storage->setMountPoint($targetParts[2]); @@ -86,7 +64,7 @@ class PersonalMount extends ExternalMountPoint implements MoveableMount { * @return bool */ public function removeMount() { - $this->storagesService->removeStorage($this->numericStorageId); + $this->storagesService->removeStorage($this->numericExternalStorageId); return true; } } diff --git a/apps/files_external/lib/Lib/PriorityTrait.php b/apps/files_external/lib/Lib/PriorityTrait.php index 355f75a833a..fad2c07e58c 100644 --- a/apps/files_external/lib/Lib/PriorityTrait.php +++ b/apps/files_external/lib/Lib/PriorityTrait.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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/> - * + * 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\Lib; @@ -47,13 +32,4 @@ trait PriorityTrait { $this->priority = $priority; return $this; } - - /** - * @param PriorityTrait $a - * @param PriorityTrait $b - * @return int - */ - public static function priorityCompare(PriorityTrait $a, PriorityTrait $b) { - return ($a->getPriority() - $b->getPriority()); - } } diff --git a/apps/files_external/lib/Lib/SessionStorageWrapper.php b/apps/files_external/lib/Lib/SessionStorageWrapper.php index 386ab4a73af..8754041b2fa 100644 --- a/apps/files_external/lib/Lib/SessionStorageWrapper.php +++ b/apps/files_external/lib/Lib/SessionStorageWrapper.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib; @@ -29,13 +14,12 @@ use OCP\Constants; * Wrap Storage in PermissionsMask for session ephemeral use */ class SessionStorageWrapper extends PermissionsMask { - /** - * @param array $arguments ['storage' => $storage] + * @param array $parameters ['storage' => $storage] */ - public function __construct($arguments) { + public function __construct(array $parameters) { // disable sharing permission - $arguments['mask'] = Constants::PERMISSION_ALL & ~Constants::PERMISSION_SHARE; - parent::__construct($arguments); + $parameters['mask'] = Constants::PERMISSION_ALL & ~Constants::PERMISSION_SHARE; + parent::__construct($parameters); } } diff --git a/apps/files_external/lib/Lib/Storage/AmazonS3.php b/apps/files_external/lib/Lib/Storage/AmazonS3.php index cfd78689fa4..5dc9e114532 100644 --- a/apps/files_external/lib/Lib/Storage/AmazonS3.php +++ b/apps/files_external/lib/Lib/Storage/AmazonS3.php @@ -1,103 +1,68 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author André Gaul <gaul@web-yard.de> - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christian Berendt <berendt@b1-systems.de> - * @author Christopher T. Johnson <ctjctj@gmail.com> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @author enoch <lanxenet@hotmail.com> - * @author Johan Björk <johanimon@gmail.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Julius Härtl <jus@bitgrid.net> - * @author Martin Mattel <martin.mattel@diemattels.at> - * @author Michael Gapczynski <GapczynskiM@gmail.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Philipp Kapfer <philipp.kapfer@gmx.at> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @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/> - * + * 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\Lib\Storage; -use Aws\Result; use Aws\S3\Exception\S3Exception; -use Aws\S3\S3Client; use Icewind\Streams\CallbackWrapper; +use Icewind\Streams\CountWrapper; use Icewind\Streams\IteratorDirectory; -use OC\Cache\CappedMemoryCache; use OC\Files\Cache\CacheEntry; use OC\Files\ObjectStore\S3ConnectionTrait; use OC\Files\ObjectStore\S3ObjectTrait; +use OC\Files\Storage\Common; +use OCP\Cache\CappedMemoryCache; use OCP\Constants; use OCP\Files\FileInfo; use OCP\Files\IMimeTypeDetector; +use OCP\ICache; use OCP\ICacheFactory; -use OCP\IMemcache; +use OCP\ITempManager; +use OCP\Server; +use Psr\Log\LoggerInterface; -class AmazonS3 extends \OC\Files\Storage\Common { +class AmazonS3 extends Common { use S3ConnectionTrait; use S3ObjectTrait; - public function needsPartFile() { + private LoggerInterface $logger; + + public function needsPartFile(): bool { return false; } - /** @var CappedMemoryCache|Result[] */ - private $objectCache; - - /** @var CappedMemoryCache|bool[] */ - private $directoryCache; + /** @var CappedMemoryCache<array|false> */ + private CappedMemoryCache $objectCache; - /** @var CappedMemoryCache|array */ - private $filesCache; + /** @var CappedMemoryCache<bool> */ + private CappedMemoryCache $directoryCache; - /** @var IMimeTypeDetector */ - private $mimeDetector; + /** @var CappedMemoryCache<array> */ + private CappedMemoryCache $filesCache; - /** @var bool|null */ - private $versioningEnabled = null; + private IMimeTypeDetector $mimeDetector; + private ?bool $versioningEnabled = null; + private ICache $memCache; - /** @var IMemcache */ - private $memCache; - - public function __construct($parameters) { + public function __construct(array $parameters) { parent::__construct($parameters); $this->parseParams($parameters); $this->id = 'amazon::external::' . md5($this->params['hostname'] . ':' . $this->params['bucket'] . ':' . $this->params['key']); $this->objectCache = new CappedMemoryCache(); $this->directoryCache = new CappedMemoryCache(); $this->filesCache = new CappedMemoryCache(); - $this->mimeDetector = \OC::$server->get(IMimeTypeDetector::class); + $this->mimeDetector = Server::get(IMimeTypeDetector::class); /** @var ICacheFactory $cacheFactory */ - $cacheFactory = \OC::$server->get(ICacheFactory::class); + $cacheFactory = Server::get(ICacheFactory::class); $this->memCache = $cacheFactory->createLocal('s3-external'); + $this->logger = Server::get(LoggerInterface::class); } - /** - * @param string $path - * @return string correctly encoded path - */ - private function normalizePath($path) { + private function normalizePath(string $path): string { $path = trim($path, '/'); if (!$path) { @@ -107,24 +72,24 @@ class AmazonS3 extends \OC\Files\Storage\Common { return $path; } - private function isRoot($path) { + private function isRoot(string $path): bool { return $path === '.'; } - private function cleanKey($path) { + private function cleanKey(string $path): string { if ($this->isRoot($path)) { return '/'; } return $path; } - private function clearCache() { + private function clearCache(): void { $this->objectCache = new CappedMemoryCache(); $this->directoryCache = new CappedMemoryCache(); $this->filesCache = new CappedMemoryCache(); } - private function invalidateCache($key) { + private function invalidateCache(string $key): void { unset($this->objectCache[$key]); $keys = array_keys($this->objectCache->getData()); $keyLength = strlen($key); @@ -144,17 +109,13 @@ class AmazonS3 extends \OC\Files\Storage\Common { unset($this->directoryCache[$key]); } - /** - * @param $key - * @return array|false - */ - private function headObject($key) { + private function headObject(string $key): array|false { if (!isset($this->objectCache[$key])) { try { $this->objectCache[$key] = $this->getConnection()->headObject([ 'Bucket' => $this->bucket, 'Key' => $key - ])->toArray(); + ] + $this->getSSECParameters())->toArray(); } catch (S3Exception $e) { if ($e->getStatusCode() >= 500) { throw $e; @@ -163,8 +124,9 @@ class AmazonS3 extends \OC\Files\Storage\Common { } } - if (is_array($this->objectCache[$key]) && !isset($this->objectCache[$key]["Key"])) { - $this->objectCache[$key]["Key"] = $key; + if (is_array($this->objectCache[$key]) && !isset($this->objectCache[$key]['Key'])) { + /** @psalm-suppress InvalidArgument Psalm doesn't understand nested arrays well */ + $this->objectCache[$key]['Key'] = $key; } return $this->objectCache[$key]; } @@ -178,11 +140,9 @@ class AmazonS3 extends \OC\Files\Storage\Common { * Implementation from flysystem-aws-s3-v3: * https://github.com/thephpleague/flysystem-aws-s3-v3/blob/8241e9cc5b28f981e0d24cdaf9867f14c7498ae4/src/AwsS3Adapter.php#L670-L694 * - * @param $path - * @return bool * @throws \Exception */ - private function doesDirectoryExist($path) { + private function doesDirectoryExist(string $path): bool { if ($path === '.' || $path === '') { return true; } @@ -224,13 +184,7 @@ class AmazonS3 extends \OC\Files\Storage\Common { return false; } - /** - * Remove a file or folder - * - * @param string $path - * @return bool - */ - protected function remove($path) { + protected function remove(string $path): bool { // remember fileType to reduce http calls $fileType = $this->filetype($path); if ($fileType === 'dir') { @@ -242,7 +196,7 @@ class AmazonS3 extends \OC\Files\Storage\Common { } } - public function mkdir($path) { + public function mkdir(string $path): bool { $path = $this->normalizePath($path); if ($this->is_dir($path)) { @@ -255,10 +209,13 @@ class AmazonS3 extends \OC\Files\Storage\Common { 'Key' => $path . '/', 'Body' => '', 'ContentType' => FileInfo::MIMETYPE_FOLDER - ]); + ] + $this->getSSECParameters()); $this->testTimeout(); } catch (S3Exception $e) { - \OC::$server->getLogger()->logException($e, ['app' => 'files_external']); + $this->logger->error($e->getMessage(), [ + 'app' => 'files_external', + 'exception' => $e, + ]); return false; } @@ -267,12 +224,12 @@ class AmazonS3 extends \OC\Files\Storage\Common { return true; } - public function file_exists($path) { + public function file_exists(string $path): bool { return $this->filetype($path) !== false; } - public function rmdir($path) { + public function rmdir(string $path): bool { $path = $this->normalizePath($path); if ($this->isRoot($path)) { @@ -287,20 +244,13 @@ class AmazonS3 extends \OC\Files\Storage\Common { return $this->batchDelete($path); } - protected function clearBucket() { + protected function clearBucket(): bool { $this->clearCache(); - try { - $this->getConnection()->clearBucket([ - "Bucket" => $this->bucket - ]); - return true; - // clearBucket() is not working with Ceph, so if it fails we try the slower approach - } catch (\Exception $e) { - return $this->batchDelete(); - } + return $this->batchDelete(); } - private function batchDelete($path = null) { + private function batchDelete(?string $path = null): bool { + // TODO explore using https://docs.aws.amazon.com/aws-sdk-php/v3/api/class-Aws.S3.BatchDelete.html $params = [ 'Bucket' => $this->bucket ]; @@ -330,13 +280,16 @@ class AmazonS3 extends \OC\Files\Storage\Common { $this->deleteObject($path); } } catch (S3Exception $e) { - \OC::$server->getLogger()->logException($e, ['app' => 'files_external']); + $this->logger->error($e->getMessage(), [ + 'app' => 'files_external', + 'exception' => $e, + ]); return false; } return true; } - public function opendir($path) { + public function opendir(string $path) { try { $content = iterator_to_array($this->getDirectoryContent($path)); return IteratorDirectory::wrap(array_map(function (array $item) { @@ -347,7 +300,7 @@ class AmazonS3 extends \OC\Files\Storage\Common { } } - public function stat($path) { + public function stat(string $path): array|false { $path = $this->normalizePath($path); if ($this->is_dir($path)) { @@ -369,11 +322,8 @@ class AmazonS3 extends \OC\Files\Storage\Common { * * When the information is already present (e.g. opendir has been called before) * this value is return. Otherwise a headObject is emitted. - * - * @param $path - * @return int|mixed */ - private function getContentLength($path) { + private function getContentLength(string $path): int { if (isset($this->filesCache[$path])) { return (int)$this->filesCache[$path]['ContentLength']; } @@ -391,11 +341,8 @@ class AmazonS3 extends \OC\Files\Storage\Common { * * When the information is already present (e.g. opendir has been called before) * this value is return. Otherwise a headObject is emitted. - * - * @param $path - * @return mixed|string */ - private function getLastModified($path) { + private function getLastModified(string $path): string { if (isset($this->filesCache[$path])) { return $this->filesCache[$path]['LastModified']; } @@ -408,7 +355,7 @@ class AmazonS3 extends \OC\Files\Storage\Common { return 'now'; } - public function is_dir($path) { + public function is_dir(string $path): bool { $path = $this->normalizePath($path); if (isset($this->filesCache[$path])) { @@ -418,12 +365,15 @@ class AmazonS3 extends \OC\Files\Storage\Common { try { return $this->doesDirectoryExist($path); } catch (S3Exception $e) { - \OC::$server->getLogger()->logException($e, ['app' => 'files_external']); + $this->logger->error($e->getMessage(), [ + 'app' => 'files_external', + 'exception' => $e, + ]); return false; } } - public function filetype($path) { + public function filetype(string $path): string|false { $path = $this->normalizePath($path); if ($this->isRoot($path)) { @@ -441,14 +391,17 @@ class AmazonS3 extends \OC\Files\Storage\Common { return 'dir'; } } catch (S3Exception $e) { - \OC::$server->getLogger()->logException($e, ['app' => 'files_external']); + $this->logger->error($e->getMessage(), [ + 'app' => 'files_external', + 'exception' => $e, + ]); return false; } return false; } - public function getPermissions($path) { + public function getPermissions(string $path): int { $type = $this->filetype($path); if (!$type) { return 0; @@ -456,7 +409,7 @@ class AmazonS3 extends \OC\Files\Storage\Common { return $type === 'dir' ? Constants::PERMISSION_ALL : Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE; } - public function unlink($path) { + public function unlink(string $path): bool { $path = $this->normalizePath($path); if ($this->is_dir($path)) { @@ -467,14 +420,17 @@ class AmazonS3 extends \OC\Files\Storage\Common { $this->deleteObject($path); $this->invalidateCache($path); } catch (S3Exception $e) { - \OC::$server->getLogger()->logException($e, ['app' => 'files_external']); + $this->logger->error($e->getMessage(), [ + 'app' => 'files_external', + 'exception' => $e, + ]); return false; } return true; } - public function fopen($path, $mode) { + public function fopen(string $path, string $mode) { $path = $this->normalizePath($path); switch ($mode) { @@ -488,16 +444,19 @@ class AmazonS3 extends \OC\Files\Storage\Common { try { return $this->readObject($path); - } catch (S3Exception $e) { - \OC::$server->getLogger()->logException($e, ['app' => 'files_external']); + } catch (\Exception $e) { + $this->logger->error($e->getMessage(), [ + 'app' => 'files_external', + 'exception' => $e, + ]); return false; } case 'w': case 'wb': - $tmpFile = \OC::$server->getTempManager()->getTemporaryFile(); + $tmpFile = Server::get(ITempManager::class)->getTemporaryFile(); $handle = fopen($tmpFile, 'w'); - return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { + return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile): void { $this->writeBack($tmpFile, $path); }); case 'a': @@ -515,21 +474,21 @@ class AmazonS3 extends \OC\Files\Storage\Common { } else { $ext = ''; } - $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); + $tmpFile = Server::get(ITempManager::class)->getTemporaryFile($ext); if ($this->file_exists($path)) { $source = $this->readObject($path); file_put_contents($tmpFile, $source); } $handle = fopen($tmpFile, $mode); - return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { + return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile): void { $this->writeBack($tmpFile, $path); }); } return false; } - public function touch($path, $mtime = null) { + public function touch(string $path, ?int $mtime = null): bool { if (is_null($mtime)) { $mtime = time(); } @@ -538,20 +497,25 @@ class AmazonS3 extends \OC\Files\Storage\Common { ]; try { - if (!$this->file_exists($path)) { - $mimeType = $this->mimeDetector->detectPath($path); - $this->getConnection()->putObject([ - 'Bucket' => $this->bucket, - 'Key' => $this->cleanKey($path), - 'Metadata' => $metadata, - 'Body' => '', - 'ContentType' => $mimeType, - 'MetadataDirective' => 'REPLACE', - ]); - $this->testTimeout(); + if ($this->file_exists($path)) { + return false; } + + $mimeType = $this->mimeDetector->detectPath($path); + $this->getConnection()->putObject([ + 'Bucket' => $this->bucket, + 'Key' => $this->cleanKey($path), + 'Metadata' => $metadata, + 'Body' => '', + 'ContentType' => $mimeType, + 'MetadataDirective' => 'REPLACE', + ] + $this->getSSECParameters()); + $this->testTimeout(); } catch (S3Exception $e) { - \OC::$server->getLogger()->logException($e, ['app' => 'files_external']); + $this->logger->error($e->getMessage(), [ + 'app' => 'files_external', + 'exception' => $e, + ]); return false; } @@ -559,65 +523,69 @@ class AmazonS3 extends \OC\Files\Storage\Common { return true; } - public function copy($path1, $path2, $isFile = null) { - $path1 = $this->normalizePath($path1); - $path2 = $this->normalizePath($path2); + public function copy(string $source, string $target, ?bool $isFile = null): bool { + $source = $this->normalizePath($source); + $target = $this->normalizePath($target); - if ($isFile === true || $this->is_file($path1)) { + if ($isFile === true || $this->is_file($source)) { try { - $this->getConnection()->copyObject([ - 'Bucket' => $this->bucket, - 'Key' => $this->cleanKey($path2), - 'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1) + $this->copyObject($source, $target, [ + 'StorageClass' => $this->storageClass, ]); $this->testTimeout(); } catch (S3Exception $e) { - \OC::$server->getLogger()->logException($e, ['app' => 'files_external']); + $this->logger->error($e->getMessage(), [ + 'app' => 'files_external', + 'exception' => $e, + ]); return false; } } else { - $this->remove($path2); + $this->remove($target); try { - $this->mkdir($path2); + $this->mkdir($target); $this->testTimeout(); } catch (S3Exception $e) { - \OC::$server->getLogger()->logException($e, ['app' => 'files_external']); + $this->logger->error($e->getMessage(), [ + 'app' => 'files_external', + 'exception' => $e, + ]); return false; } - foreach ($this->getDirectoryContent($path1) as $item) { - $source = $path1 . '/' . $item['name']; - $target = $path2 . '/' . $item['name']; - $this->copy($source, $target, $item['mimetype'] !== FileInfo::MIMETYPE_FOLDER); + foreach ($this->getDirectoryContent($source) as $item) { + $childSource = $source . '/' . $item['name']; + $childTarget = $target . '/' . $item['name']; + $this->copy($childSource, $childTarget, $item['mimetype'] !== FileInfo::MIMETYPE_FOLDER); } } - $this->invalidateCache($path2); + $this->invalidateCache($target); return true; } - public function rename($path1, $path2) { - $path1 = $this->normalizePath($path1); - $path2 = $this->normalizePath($path2); + public function rename(string $source, string $target): bool { + $source = $this->normalizePath($source); + $target = $this->normalizePath($target); - if ($this->is_file($path1)) { - if ($this->copy($path1, $path2) === false) { + if ($this->is_file($source)) { + if ($this->copy($source, $target) === false) { return false; } - if ($this->unlink($path1) === false) { - $this->unlink($path2); + if ($this->unlink($source) === false) { + $this->unlink($target); return false; } } else { - if ($this->copy($path1, $path2) === false) { + if ($this->copy($source, $target) === false) { return false; } - if ($this->rmdir($path1) === false) { - $this->rmdir($path2); + if ($this->rmdir($source) === false) { + $this->rmdir($target); return false; } } @@ -625,18 +593,18 @@ class AmazonS3 extends \OC\Files\Storage\Common { return true; } - public function test() { + public function test(): bool { $this->getConnection()->headBucket([ 'Bucket' => $this->bucket ]); return true; } - public function getId() { + public function getId(): string { return $this->id; } - public function writeBack($tmpFile, $path) { + public function writeBack(string $tmpFile, string $path): bool { try { $source = fopen($tmpFile, 'r'); $this->writeObject($path, $source, $this->mimeDetector->detectPath($path)); @@ -645,7 +613,10 @@ class AmazonS3 extends \OC\Files\Storage\Common { unlink($tmpFile); return true; } catch (S3Exception $e) { - \OC::$server->getLogger()->logException($e, ['app' => 'files_external']); + $this->logger->error($e->getMessage(), [ + 'app' => 'files_external', + 'exception' => $e, + ]); return false; } } @@ -653,11 +624,11 @@ class AmazonS3 extends \OC\Files\Storage\Common { /** * check if curl is installed */ - public static function checkDependencies() { + public static function checkDependencies(): bool { return true; } - public function getDirectoryContent($directory): \Traversable { + public function getDirectoryContent(string $directory): \Traversable { $path = $this->normalizePath($directory); if ($this->isRoot($path)) { @@ -699,7 +670,7 @@ class AmazonS3 extends \OC\Files\Storage\Common { 'mimetype' => $this->mimeDetector->detectPath($object['Key']), 'mtime' => strtotime($object['LastModified']), 'storage_mtime' => strtotime($object['LastModified']), - 'etag' => $object['ETag'], + 'etag' => trim($object['ETag'], '"'), 'permissions' => Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE, 'size' => (int)($object['Size'] ?? $object['ContentLength']), ]; @@ -747,14 +718,14 @@ class AmazonS3 extends \OC\Files\Storage\Common { return $result->get('Status') === 'Enabled'; } catch (S3Exception $s3Exception) { // This is needed for compatibility with Storj gateway which does not support versioning yet - if ($s3Exception->getAwsErrorCode() === 'NotImplemented') { + if ($s3Exception->getAwsErrorCode() === 'NotImplemented' || $s3Exception->getAwsErrorCode() === 'AccessDenied') { return false; } throw $s3Exception; } } - public function hasUpdated($path, $time) { + public function hasUpdated(string $path, int $time): bool { // for files we can get the proper mtime if ($path !== '' && $object = $this->headObject($path)) { $stat = $this->objectToMetaData($object); @@ -766,4 +737,24 @@ class AmazonS3 extends \OC\Files\Storage\Common { return true; } } + + public function writeStream(string $path, $stream, ?int $size = null): int { + if ($size === null) { + $size = 0; + // track the number of bytes read from the input stream to return as the number of written bytes. + $stream = CountWrapper::wrap($stream, function (int $writtenSize) use (&$size): void { + $size = $writtenSize; + }); + } + + if (!is_resource($stream)) { + throw new \InvalidArgumentException('Invalid stream provided'); + } + + $path = $this->normalizePath($path); + $this->writeObject($path, $stream, $this->mimeDetector->detectPath($path)); + $this->invalidateCache($path); + + return $size; + } } diff --git a/apps/files_external/lib/Lib/Storage/FTP.php b/apps/files_external/lib/Lib/Storage/FTP.php index d424ffe3cdd..944964de7a6 100644 --- a/apps/files_external/lib/Lib/Storage/FTP.php +++ b/apps/files_external/lib/Lib/Storage/FTP.php @@ -1,23 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2021 Robin Appelman <robin@icewind.nl> - * - * @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/> - * + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Storage; @@ -28,7 +13,11 @@ use OC\Files\Storage\Common; use OC\Files\Storage\PolyFill\CopyDirectory; use OCP\Constants; use OCP\Files\FileInfo; +use OCP\Files\IMimeTypeDetector; use OCP\Files\StorageNotAvailableException; +use OCP\ITempManager; +use OCP\Server; +use Psr\Log\LoggerInterface; class FTP extends Common { use CopyDirectory; @@ -44,23 +33,23 @@ class FTP extends Common { /** @var FtpConnection|null */ private $connection; - public function __construct($params) { - if (isset($params['host']) && isset($params['user']) && isset($params['password'])) { - $this->host = $params['host']; - $this->username = $params['user']; - $this->password = $params['password']; - if (isset($params['secure'])) { - if (is_string($params['secure'])) { - $this->secure = ($params['secure'] === 'true'); + public function __construct(array $parameters) { + if (isset($parameters['host']) && isset($parameters['user']) && isset($parameters['password'])) { + $this->host = $parameters['host']; + $this->username = $parameters['user']; + $this->password = $parameters['password']; + if (isset($parameters['secure'])) { + if (is_string($parameters['secure'])) { + $this->secure = ($parameters['secure'] === 'true'); } else { - $this->secure = (bool)$params['secure']; + $this->secure = (bool)$parameters['secure']; } } else { $this->secure = false; } - $this->root = isset($params['root']) ? '/' . ltrim($params['root']) : '/'; - $this->port = $params['port'] ?? 21; - $this->utf8Mode = isset($params['utf8']) && $params['utf8']; + $this->root = isset($parameters['root']) ? '/' . ltrim($parameters['root']) : '/'; + $this->port = $parameters['port'] ?? 21; + $this->utf8Mode = isset($parameters['utf8']) && $parameters['utf8']; } else { throw new \Exception('Creating ' . self::class . ' storage failed, required parameters not set'); } @@ -81,11 +70,11 @@ class FTP extends Common { $this->password ); } catch (\Exception $e) { - throw new StorageNotAvailableException("Failed to create ftp connection", 0, $e); + throw new StorageNotAvailableException('Failed to create ftp connection', 0, $e); } if ($this->utf8Mode) { if (!$this->connection->setUtf8Mode()) { - throw new StorageNotAvailableException("Could not set UTF-8 mode"); + throw new StorageNotAvailableException('Could not set UTF-8 mode'); } } } @@ -93,15 +82,15 @@ class FTP extends Common { return $this->connection; } - public function getId() { + public function getId(): string { return 'ftp::' . $this->username . '@' . $this->host . '/' . $this->root; } - protected function buildPath($path) { + protected function buildPath(string $path): string { return rtrim($this->root . '/' . $path, '/'); } - public static function checkDependencies() { + public static function checkDependencies(): array|bool { if (function_exists('ftp_login')) { return true; } else { @@ -109,27 +98,28 @@ class FTP extends Common { } } - public function filemtime($path) { + public function filemtime(string $path): int|false { $result = $this->getConnection()->mdtm($this->buildPath($path)); if ($result === -1) { if ($this->is_dir($path)) { $list = $this->getConnection()->mlsd($this->buildPath($path)); if (!$list) { - \OC::$server->getLogger()->warning("Unable to get last modified date for ftp folder ($path), failed to list folder contents"); + Server::get(LoggerInterface::class)->warning("Unable to get last modified date for ftp folder ($path), failed to list folder contents"); return time(); } $currentDir = current(array_filter($list, function ($item) { return $item['type'] === 'cdir'; })); if ($currentDir) { - $time = \DateTime::createFromFormat('YmdHis', $currentDir['modify']); + [$modify] = explode('.', $currentDir['modify'] ?? '', 2); + $time = \DateTime::createFromFormat('YmdHis', $modify); if ($time === false) { throw new \Exception("Invalid date format for directory: $currentDir"); } return $time->getTimestamp(); } else { - \OC::$server->getLogger()->warning("Unable to get last modified date for ftp folder ($path), folder contents doesn't include current folder"); + Server::get(LoggerInterface::class)->warning("Unable to get last modified date for ftp folder ($path), folder contents doesn't include current folder"); return time(); } } else { @@ -140,7 +130,7 @@ class FTP extends Common { } } - public function filesize($path) { + public function filesize(string $path): false|int|float { $result = $this->getConnection()->size($this->buildPath($path)); if ($result === -1) { return false; @@ -149,7 +139,7 @@ class FTP extends Common { } } - public function rmdir($path) { + public function rmdir(string $path): bool { if ($this->is_dir($path)) { $result = $this->getConnection()->rmdir($this->buildPath($path)); // recursive rmdir support depends on the ftp server @@ -165,11 +155,7 @@ class FTP extends Common { } } - /** - * @param string $path - * @return bool - */ - private function recursiveRmDir($path): bool { + private function recursiveRmDir(string $path): bool { $contents = $this->getDirectoryContent($path); $result = true; foreach ($contents as $content) { @@ -184,7 +170,7 @@ class FTP extends Common { return $result; } - public function test() { + public function test(): bool { try { return $this->getConnection()->systype() !== false; } catch (\Exception $e) { @@ -192,7 +178,7 @@ class FTP extends Common { } } - public function stat($path) { + public function stat(string $path): array|false { if (!$this->file_exists($path)) { return false; } @@ -202,14 +188,14 @@ class FTP extends Common { ]; } - public function file_exists($path) { + public function file_exists(string $path): bool { if ($path === '' || $path === '.' || $path === '/') { return true; } return $this->filetype($path) !== false; } - public function unlink($path) { + public function unlink(string $path): bool { switch ($this->filetype($path)) { case 'dir': return $this->rmdir($path); @@ -220,20 +206,20 @@ class FTP extends Common { } } - public function opendir($path) { + public function opendir(string $path) { $files = $this->getConnection()->nlist($this->buildPath($path)); return IteratorDirectory::wrap($files); } - public function mkdir($path) { + public function mkdir(string $path): bool { if ($this->is_dir($path)) { return false; } return $this->getConnection()->mkdir($this->buildPath($path)) !== false; } - public function is_dir($path) { - if ($path === "") { + public function is_dir(string $path): bool { + if ($path === '') { return true; } if ($this->getConnection()->chdir($this->buildPath($path)) === true) { @@ -244,11 +230,11 @@ class FTP extends Common { } } - public function is_file($path) { + public function is_file(string $path): bool { return $this->filesize($path) !== false; } - public function filetype($path) { + public function filetype(string $path): string|false { if ($this->is_dir($path)) { return 'dir'; } elseif ($this->is_file($path)) { @@ -258,7 +244,7 @@ class FTP extends Common { } } - public function fopen($path, $mode) { + public function fopen(string $path, string $mode) { $useExisting = true; switch ($mode) { case 'r': @@ -269,7 +255,7 @@ class FTP extends Common { case 'wb': case 'wb+': $useExisting = false; - // no break + // no break case 'a': case 'ab': case 'r+': @@ -288,10 +274,10 @@ class FTP extends Common { if (!$this->isCreatable(dirname($path))) { return false; } - $tmpFile = \OC::$server->getTempManager()->getTemporaryFile(); + $tmpFile = Server::get(ITempManager::class)->getTemporaryFile(); } $source = fopen($tmpFile, $mode); - return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $path) { + return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $path): void { $this->writeStream($path, fopen($tmpFile, 'r')); unlink($tmpFile); }); @@ -299,9 +285,9 @@ class FTP extends Common { return false; } - public function writeStream(string $path, $stream, int $size = null): int { + public function writeStream(string $path, $stream, ?int $size = null): int { if ($size === null) { - $stream = CountWrapper::wrap($stream, function ($writtenSize) use (&$size) { + $stream = CountWrapper::wrap($stream, function ($writtenSize) use (&$size): void { $size = $writtenSize; }); } @@ -324,7 +310,7 @@ class FTP extends Common { return $stream; } - public function touch($path, $mtime = null) { + public function touch(string $path, ?int $mtime = null): bool { if ($this->file_exists($path)) { return false; } else { @@ -333,14 +319,14 @@ class FTP extends Common { } } - public function rename($path1, $path2) { - $this->unlink($path2); - return $this->getConnection()->rename($this->buildPath($path1), $this->buildPath($path2)); + public function rename(string $source, string $target): bool { + $this->unlink($target); + return $this->getConnection()->rename($this->buildPath($source), $this->buildPath($target)); } - public function getDirectoryContent($directory): \Traversable { + public function getDirectoryContent(string $directory): \Traversable { $files = $this->getConnection()->mlsd($this->buildPath($directory)); - $mimeTypeDetector = \OC::$server->getMimeTypeDetector(); + $mimeTypeDetector = Server::get(IMimeTypeDetector::class); foreach ($files as $file) { $name = $file['name']; @@ -355,10 +341,11 @@ class FTP extends Common { $data = []; $data['mimetype'] = $isDir ? FileInfo::MIMETYPE_FOLDER : $mimeTypeDetector->detectPath($name); - $data['mtime'] = \DateTime::createFromFormat('YmdGis', $file['modify'])->getTimestamp(); - if ($data['mtime'] === false) { - $data['mtime'] = time(); - } + + // strip fractional seconds + [$modify] = explode('.', $file['modify'], 2); + $mtime = \DateTime::createFromFormat('YmdGis', $modify); + $data['mtime'] = $mtime === false ? time() : $mtime->getTimestamp(); if ($isDir) { $data['size'] = -1; //unknown } elseif (isset($file['size'])) { diff --git a/apps/files_external/lib/Lib/Storage/FtpConnection.php b/apps/files_external/lib/Lib/Storage/FtpConnection.php index bc4be18e42e..a064bf9b100 100644 --- a/apps/files_external/lib/Lib/Storage/FtpConnection.php +++ b/apps/files_external/lib/Lib/Storage/FtpConnection.php @@ -1,24 +1,10 @@ <?php declare(strict_types=1); + /** - * @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Lib\Storage; @@ -27,8 +13,7 @@ namespace OCA\Files_External\Lib\Storage; * Low level wrapper around the ftp functions that smooths over some difference between servers */ class FtpConnection { - /** @var resource|\FTP\Connection */ - private $connection; + private \FTP\Connection $connection; public function __construct(bool $secure, string $hostname, int $port, string $username, string $password) { if ($secure) { @@ -38,11 +23,11 @@ class FtpConnection { } if ($connection === false) { - throw new \Exception("Failed to connect to ftp"); + throw new \Exception('Failed to connect to ftp'); } if (ftp_login($connection, $username, $password) === false) { - throw new \Exception("Failed to connect to login to ftp"); + throw new \Exception('Failed to connect to login to ftp'); } ftp_pasv($connection, true); @@ -50,14 +35,11 @@ class FtpConnection { } public function __destruct() { - if ($this->connection) { - ftp_close($this->connection); - } - $this->connection = null; + ftp_close($this->connection); } public function setUtf8Mode(): bool { - $response = ftp_raw($this->connection, "OPTS UTF8 ON"); + $response = ftp_raw($this->connection, 'OPTS UTF8 ON'); return substr($response[0], 0, 3) === '200'; } @@ -85,12 +67,18 @@ class FtpConnection { return @ftp_rmdir($this->connection, $path); } - public function rename(string $path1, string $path2) { - return @ftp_rename($this->connection, $path1, $path2); + public function rename(string $source, string $target) { + return @ftp_rename($this->connection, $source, $target); } - public function mdtm(string $path) { - return @ftp_mdtm($this->connection, $path); + public function mdtm(string $path): int { + $result = @ftp_mdtm($this->connection, $path); + + // filezilla doesn't like empty path with mdtm + if ($result === -1 && $path === '') { + $result = @ftp_mdtm($this->connection, '/'); + } + return $result; } public function size(string $path) { @@ -104,7 +92,7 @@ class FtpConnection { public function nlist(string $path) { $files = @ftp_nlist($this->connection, $path); return array_map(function ($name) { - if (strpos($name, '/') !== false) { + if (str_contains($name, '/')) { $name = basename($name); } return $name; @@ -116,7 +104,7 @@ class FtpConnection { if ($files !== false) { return array_map(function ($file) { - if (strpos($file['name'], '/') !== false) { + if (str_contains($file['name'], '/')) { $file['name'] = basename($file['name']); } return $file; @@ -162,13 +150,13 @@ class FtpConnection { $parsedDate = (new \DateTime()) ->setTimestamp(strtotime("$month $day $time")); - $tomorrow = (new \DateTime())->add(new \DateInterval("P1D")); + $tomorrow = (new \DateTime())->add(new \DateInterval('P1D')); // since the provided date doesn't include the year, we either set it to the correct year // or when the date would otherwise be in the future (by more then 1 day to account for timezone errors) // we use last year if ($parsedDate > $tomorrow) { - $parsedDate = $parsedDate->sub(new \DateInterval("P1Y")); + $parsedDate = $parsedDate->sub(new \DateInterval('P1Y')); } $formattedDate = $parsedDate diff --git a/apps/files_external/lib/Lib/Storage/OwnCloud.php b/apps/files_external/lib/Lib/Storage/OwnCloud.php index d5926008f62..12c305de750 100644 --- a/apps/files_external/lib/Lib/Storage/OwnCloud.php +++ b/apps/files_external/lib/Lib/Storage/OwnCloud.php @@ -1,33 +1,13 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @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 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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Storage; +use OC\Files\Storage\DAV; use OCP\Files\Storage\IDisableEncryptionStorage; use Sabre\DAV\Client; @@ -38,20 +18,20 @@ use Sabre\DAV\Client; * http://%host/%context/remote.php/webdav/%root * */ -class OwnCloud extends \OC\Files\Storage\DAV implements IDisableEncryptionStorage { +class OwnCloud extends DAV implements IDisableEncryptionStorage { public const OC_URL_SUFFIX = 'remote.php/webdav'; - public function __construct($params) { + public function __construct(array $parameters) { // extract context path from host if specified // (owncloud install path on host) - $host = $params['host']; + $host = $parameters['host']; // strip protocol - if (substr($host, 0, 8) === "https://") { + if (substr($host, 0, 8) === 'https://') { $host = substr($host, 8); - $params['secure'] = true; - } elseif (substr($host, 0, 7) === "http://") { + $parameters['secure'] = true; + } elseif (substr($host, 0, 7) === 'http://') { $host = substr($host, 7); - $params['secure'] = false; + $parameters['secure'] = false; } $contextPath = ''; $hostSlashPos = strpos($host, '/'); @@ -60,24 +40,24 @@ class OwnCloud extends \OC\Files\Storage\DAV implements IDisableEncryptionStorag $host = substr($host, 0, $hostSlashPos); } - if (substr($contextPath, -1) !== '/') { + if (!str_ends_with($contextPath, '/')) { $contextPath .= '/'; } - if (isset($params['root'])) { - $root = '/' . ltrim($params['root'], '/'); + if (isset($parameters['root'])) { + $root = '/' . ltrim($parameters['root'], '/'); } else { $root = '/'; } - $params['host'] = $host; - $params['root'] = $contextPath . self::OC_URL_SUFFIX . $root; - $params['authType'] = Client::AUTH_BASIC; + $parameters['host'] = $host; + $parameters['root'] = $contextPath . self::OC_URL_SUFFIX . $root; + $parameters['authType'] = Client::AUTH_BASIC; - parent::__construct($params); + parent::__construct($parameters); } - public function needsPartFile() { + public function needsPartFile(): bool { return false; } } diff --git a/apps/files_external/lib/Lib/Storage/SFTP.php b/apps/files_external/lib/Lib/Storage/SFTP.php index e46f60d0be4..a2f5bafcca1 100644 --- a/apps/files_external/lib/Lib/Storage/SFTP.php +++ b/apps/files_external/lib/Lib/Storage/SFTP.php @@ -1,50 +1,30 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Andreas Fischer <bantu@owncloud.com> - * @author Bart Visscher <bartv@thisnet.nl> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author hkjolhede <hkjolhede@gmail.com> - * @author Joas Schilling <coding@schilljs.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Lennart Rosam <lennart.rosam@medien-systempartner.de> - * @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 Ross Nicoll <jrn@jrn.me.uk> - * @author SA <stephen@mthosting.net> - * @author Senorsen <senorsen.zhang@gmail.com> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Storage; +use Icewind\Streams\CallbackWrapper; +use Icewind\Streams\CountWrapper; use Icewind\Streams\IteratorDirectory; use Icewind\Streams\RetryWrapper; +use OC\Files\Storage\Common; +use OC\Files\View; +use OCP\Cache\CappedMemoryCache; +use OCP\Constants; +use OCP\Files\FileInfo; +use OCP\Files\IMimeTypeDetector; +use OCP\Server; use phpseclib\Net\SFTP\Stream; /** * Uses phpseclib's Net\SFTP class and the Net\SFTP\Stream stream wrapper to * provide access to SFTP servers. */ -class SFTP extends \OC\Files\Storage\Common { +class SFTP extends Common { private $host; private $user; private $root; @@ -56,14 +36,19 @@ class SFTP extends \OC\Files\Storage\Common { * @var \phpseclib\Net\SFTP */ protected $client; + private CappedMemoryCache $knownMTimes; + + private IMimeTypeDetector $mimeTypeDetector; + + public const COPY_CHUNK_SIZE = 8 * 1024 * 1024; /** * @param string $host protocol://server:port * @return array [$server, $port] */ - private function splitHost($host) { + private function splitHost(string $host): array { $input = $host; - if (strpos($host, '://') === false) { + if (!str_contains($host, '://')) { // add a protocol to fix parse_url behavior with ipv6 $host = 'http://' . $host; } @@ -78,28 +63,25 @@ class SFTP extends \OC\Files\Storage\Common { } } - /** - * {@inheritdoc} - */ - public function __construct($params) { + public function __construct(array $parameters) { // Register sftp:// Stream::register(); - $parsedHost = $this->splitHost($params['host']); + $parsedHost = $this->splitHost($parameters['host']); $this->host = $parsedHost[0]; $this->port = $parsedHost[1]; - if (!isset($params['user'])) { + if (!isset($parameters['user'])) { throw new \UnexpectedValueException('no authentication parameters specified'); } - $this->user = $params['user']; + $this->user = $parameters['user']; - if (isset($params['public_key_auth'])) { - $this->auth[] = $params['public_key_auth']; + if (isset($parameters['public_key_auth'])) { + $this->auth[] = $parameters['public_key_auth']; } - if (isset($params['password']) && $params['password'] !== '') { - $this->auth[] = $params['password']; + if (isset($parameters['password']) && $parameters['password'] !== '') { + $this->auth[] = $parameters['password']; } if ($this->auth === []) { @@ -107,10 +89,14 @@ class SFTP extends \OC\Files\Storage\Common { } $this->root - = isset($params['root']) ? $this->cleanPath($params['root']) : '/'; + = isset($parameters['root']) ? $this->cleanPath($parameters['root']) : '/'; $this->root = '/' . ltrim($this->root, '/'); $this->root = rtrim($this->root, '/') . '/'; + + $this->knownMTimes = new CappedMemoryCache(); + + $this->mimeTypeDetector = Server::get(IMimeTypeDetector::class); } /** @@ -119,7 +105,7 @@ class SFTP extends \OC\Files\Storage\Common { * @return \phpseclib\Net\SFTP connected client instance * @throws \Exception when the connection failed */ - public function getConnection() { + public function getConnection(): \phpseclib\Net\SFTP { if (!is_null($this->client)) { return $this->client; } @@ -153,10 +139,7 @@ class SFTP extends \OC\Files\Storage\Common { return $this->client; } - /** - * {@inheritdoc} - */ - public function test() { + public function test(): bool { if ( !isset($this->host) || !isset($this->user) @@ -166,10 +149,7 @@ class SFTP extends \OC\Files\Storage\Common { return $this->getConnection()->nlist() !== false; } - /** - * {@inheritdoc} - */ - public function getId() { + public function getId(): string { $id = 'sftp::' . $this->user . '@' . $this->host; if ($this->port !== 22) { $id .= ':' . $this->port; @@ -181,56 +161,38 @@ class SFTP extends \OC\Files\Storage\Common { return $id; } - /** - * @return string - */ - public function getHost() { + public function getHost(): string { return $this->host; } - /** - * @return string - */ - public function getRoot() { + public function getRoot(): string { return $this->root; } - /** - * @return mixed - */ - public function getUser() { + public function getUser(): string { return $this->user; } - /** - * @param string $path - * @return string - */ - private function absPath($path) { + private function absPath(string $path): string { return $this->root . $this->cleanPath($path); } - /** - * @return string|false - */ - private function hostKeysPath() { + private function hostKeysPath(): string|false { try { - $storage_view = \OCP\Files::getStorage('files_external'); - if ($storage_view) { - return \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . - $storage_view->getAbsolutePath('') . - 'ssh_hostKeys'; + $userId = \OC_User::getUser(); + if ($userId === false) { + return false; } + + $view = new View('/' . $userId . '/files_external'); + + return $view->getLocalFile('ssh_hostKeys'); } catch (\Exception $e) { } return false; } - /** - * @param $keys - * @return bool - */ - protected function writeHostKeys($keys) { + protected function writeHostKeys(array $keys): bool { try { $keyPath = $this->hostKeysPath(); if ($keyPath && file_exists($keyPath)) { @@ -246,10 +208,7 @@ class SFTP extends \OC\Files\Storage\Common { return false; } - /** - * @return array - */ - protected function readHostKeys() { + protected function readHostKeys(): array { try { $keyPath = $this->hostKeysPath(); if (file_exists($keyPath)) { @@ -258,7 +217,7 @@ class SFTP extends \OC\Files\Storage\Common { $lines = file($keyPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); if ($lines) { foreach ($lines as $line) { - $hostKeyArray = explode("::", $line, 2); + $hostKeyArray = explode('::', $line, 2); if (count($hostKeyArray) === 2) { $hosts[] = $hostKeyArray[0]; $keys[] = $hostKeyArray[1]; @@ -272,10 +231,7 @@ class SFTP extends \OC\Files\Storage\Common { return []; } - /** - * {@inheritdoc} - */ - public function mkdir($path) { + public function mkdir(string $path): bool { try { return $this->getConnection()->mkdir($this->absPath($path)); } catch (\Exception $e) { @@ -283,10 +239,7 @@ class SFTP extends \OC\Files\Storage\Common { } } - /** - * {@inheritdoc} - */ - public function rmdir($path) { + public function rmdir(string $path): bool { try { $result = $this->getConnection()->delete($this->absPath($path), true); // workaround: stray stat cache entry when deleting empty folders @@ -298,10 +251,7 @@ class SFTP extends \OC\Files\Storage\Common { } } - /** - * {@inheritdoc} - */ - public function opendir($path) { + public function opendir(string $path) { try { $list = $this->getConnection()->nlist($this->absPath($path)); if ($list === false) { @@ -321,20 +271,17 @@ class SFTP extends \OC\Files\Storage\Common { } } - /** - * {@inheritdoc} - */ - public function filetype($path) { + public function filetype(string $path): string|false { try { $stat = $this->getConnection()->stat($this->absPath($path)); if (!is_array($stat) || !array_key_exists('type', $stat)) { return false; } - if ((int) $stat['type'] === NET_SFTP_TYPE_REGULAR) { + if ((int)$stat['type'] === NET_SFTP_TYPE_REGULAR) { return 'file'; } - if ((int) $stat['type'] === NET_SFTP_TYPE_DIRECTORY) { + if ((int)$stat['type'] === NET_SFTP_TYPE_DIRECTORY) { return 'dir'; } } catch (\Exception $e) { @@ -342,10 +289,7 @@ class SFTP extends \OC\Files\Storage\Common { return false; } - /** - * {@inheritdoc} - */ - public function file_exists($path) { + public function file_exists(string $path): bool { try { return $this->getConnection()->stat($this->absPath($path)) !== false; } catch (\Exception $e) { @@ -353,10 +297,7 @@ class SFTP extends \OC\Files\Storage\Common { } } - /** - * {@inheritdoc} - */ - public function unlink($path) { + public function unlink(string $path): bool { try { return $this->getConnection()->delete($this->absPath($path), true); } catch (\Exception $e) { @@ -364,27 +305,35 @@ class SFTP extends \OC\Files\Storage\Common { } } - /** - * {@inheritdoc} - */ - public function fopen($path, $mode) { + public function fopen(string $path, string $mode) { + $path = $this->cleanPath($path); try { $absPath = $this->absPath($path); + $connection = $this->getConnection(); switch ($mode) { case 'r': case 'rb': - if (!$this->file_exists($path)) { + $stat = $this->stat($path); + if (!$stat) { return false; } SFTPReadStream::register(); - $context = stream_context_create(['sftp' => ['session' => $this->getConnection()]]); + $context = stream_context_create(['sftp' => ['session' => $connection, 'size' => $stat['size']]]); $handle = fopen('sftpread://' . trim($absPath, '/'), 'r', false, $context); return RetryWrapper::wrap($handle); case 'w': case 'wb': SFTPWriteStream::register(); - $context = stream_context_create(['sftp' => ['session' => $this->getConnection()]]); - return fopen('sftpwrite://' . trim($absPath, '/'), 'w', false, $context); + // the SFTPWriteStream doesn't go through the "normal" methods so it doesn't clear the stat cache. + $connection->_remove_from_stat_cache($absPath); + $context = stream_context_create(['sftp' => ['session' => $connection]]); + $fh = fopen('sftpwrite://' . trim($absPath, '/'), 'w', false, $context); + if ($fh) { + $fh = CallbackWrapper::wrap($fh, null, null, function () use ($path): void { + $this->knownMTimes->set($path, time()); + }); + } + return $fh; case 'a': case 'ab': case 'r+': @@ -395,7 +344,7 @@ class SFTP extends \OC\Files\Storage\Common { case 'x+': case 'c': case 'c+': - $context = stream_context_create(['sftp' => ['session' => $this->getConnection()]]); + $context = stream_context_create(['sftp' => ['session' => $connection]]); $handle = fopen($this->constructUrl($path), $mode, false, $context); return RetryWrapper::wrap($handle); } @@ -404,38 +353,29 @@ class SFTP extends \OC\Files\Storage\Common { return false; } - /** - * {@inheritdoc} - */ - public function touch($path, $mtime = null) { + public function touch(string $path, ?int $mtime = null): bool { try { if (!is_null($mtime)) { return false; } if (!$this->file_exists($path)) { - $this->getConnection()->put($this->absPath($path), ''); + return $this->getConnection()->put($this->absPath($path), ''); } else { return false; } } catch (\Exception $e) { return false; } - return true; } /** - * @param string $path - * @param string $target * @throws \Exception */ - public function getFile($path, $target) { + public function getFile(string $path, string $target): void { $this->getConnection()->get($path, $target); } - /** - * {@inheritdoc} - */ - public function rename($source, $target) { + public function rename(string $source, string $target): bool { try { if ($this->file_exists($target)) { $this->unlink($target); @@ -450,30 +390,134 @@ class SFTP extends \OC\Files\Storage\Common { } /** - * {@inheritdoc} + * @return array{mtime: int, size: int, ctime: int}|false */ - public function stat($path) { + public function stat(string $path): array|false { try { + $path = $this->cleanPath($path); $stat = $this->getConnection()->stat($this->absPath($path)); - $mtime = $stat ? $stat['mtime'] : -1; - $size = $stat ? $stat['size'] : 0; + $mtime = isset($stat['mtime']) ? (int)$stat['mtime'] : -1; + $size = isset($stat['size']) ? (int)$stat['size'] : 0; + + // the mtime can't be less than when we last touched it + if ($knownMTime = $this->knownMTimes->get($path)) { + $mtime = max($mtime, $knownMTime); + } - return ['mtime' => $mtime, 'size' => $size, 'ctime' => -1]; + return [ + 'mtime' => $mtime, + 'size' => $size, + 'ctime' => -1 + ]; } catch (\Exception $e) { return false; } } - /** - * @param string $path - * @return string - */ - public function constructUrl($path) { + public function constructUrl(string $path): string { // Do not pass the password here. We want to use the Net_SFTP object // supplied via stream context or fail. We only supply username and // hostname because this might show up in logs (they are not used). $url = 'sftp://' . urlencode($this->user) . '@' . $this->host . ':' . $this->port . $this->root . $path; return $url; } + + public function file_put_contents(string $path, mixed $data): int|float|false { + /** @psalm-suppress InternalMethod */ + $result = $this->getConnection()->put($this->absPath($path), $data); + if ($result) { + return strlen($data); + } else { + return false; + } + } + + public function writeStream(string $path, $stream, ?int $size = null): int { + if ($size === null) { + $stream = CountWrapper::wrap($stream, function (int $writtenSize) use (&$size): void { + $size = $writtenSize; + }); + if (!$stream) { + throw new \Exception('Failed to wrap stream'); + } + } + /** @psalm-suppress InternalMethod */ + $result = $this->getConnection()->put($this->absPath($path), $stream); + fclose($stream); + if ($result) { + if ($size === null) { + throw new \Exception('Failed to get written size from sftp storage wrapper'); + } + return $size; + } else { + throw new \Exception('Failed to write steam to sftp storage'); + } + } + + public function copy(string $source, string $target): bool { + if ($this->is_dir($source) || $this->is_dir($target)) { + return parent::copy($source, $target); + } else { + $absSource = $this->absPath($source); + $absTarget = $this->absPath($target); + + $connection = $this->getConnection(); + $size = $connection->size($absSource); + if ($size === false) { + return false; + } + for ($i = 0; $i < $size; $i += self::COPY_CHUNK_SIZE) { + /** @psalm-suppress InvalidArgument */ + $chunk = $connection->get($absSource, false, $i, self::COPY_CHUNK_SIZE); + if ($chunk === false) { + return false; + } + /** @psalm-suppress InternalMethod */ + if (!$connection->put($absTarget, $chunk, \phpseclib\Net\SFTP::SOURCE_STRING, $i)) { + return false; + } + } + return true; + } + } + + public function getPermissions(string $path): int { + $stat = $this->getConnection()->stat($this->absPath($path)); + if (!$stat) { + return 0; + } + if ($stat['type'] === NET_SFTP_TYPE_DIRECTORY) { + return Constants::PERMISSION_ALL; + } else { + return Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE; + } + } + + public function getMetaData(string $path): ?array { + $stat = $this->getConnection()->stat($this->absPath($path)); + if (!$stat) { + return null; + } + + if ($stat['type'] === NET_SFTP_TYPE_DIRECTORY) { + $stat['permissions'] = Constants::PERMISSION_ALL; + } else { + $stat['permissions'] = Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE; + } + + if ($stat['type'] === NET_SFTP_TYPE_DIRECTORY) { + $stat['size'] = -1; + $stat['mimetype'] = FileInfo::MIMETYPE_FOLDER; + } else { + $stat['mimetype'] = $this->mimeTypeDetector->detectPath($path); + } + + $stat['etag'] = $this->getETag($path); + $stat['storage_mtime'] = $stat['mtime']; + $stat['name'] = basename($path); + + $keys = ['size', 'mtime', 'mimetype', 'etag', 'storage_mtime', 'permissions', 'name']; + return array_intersect_key($stat, array_flip($keys)); + } } diff --git a/apps/files_external/lib/Lib/Storage/SFTPReadStream.php b/apps/files_external/lib/Lib/Storage/SFTPReadStream.php index 06ede120f5a..7dedbd7035a 100644 --- a/apps/files_external/lib/Lib/Storage/SFTPReadStream.php +++ b/apps/files_external/lib/Lib/Storage/SFTPReadStream.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Robin Appelman <robin@icewind.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Lib\Storage; @@ -36,7 +18,7 @@ class SFTPReadStream implements File { /** @var \phpseclib\Net\SFTP */ private $sftp; - /** @var resource */ + /** @var string */ private $handle; /** @var int */ @@ -49,6 +31,8 @@ class SFTPReadStream implements File { private $eof = false; private $buffer = ''; + private bool $pendingRead = false; + private int $size = 0; public static function register($protocol = 'sftpread') { if (in_array($protocol, stream_get_wrappers(), true)) { @@ -60,11 +44,9 @@ class SFTPReadStream implements File { /** * Load the source from the stream context and return the context options * - * @param string $name - * @return array * @throws \BadMethodCallException */ - protected function loadContext($name) { + protected function loadContext(string $name) { $context = stream_context_get_options($this->context); if (isset($context[$name])) { $context = $context[$name]; @@ -76,6 +58,9 @@ class SFTPReadStream implements File { } else { throw new \BadMethodCallException('Invalid context, session not set'); } + if (isset($context['size'])) { + $this->size = $context['size']; + } return $context; } @@ -119,7 +104,25 @@ class SFTPReadStream implements File { } public function stream_seek($offset, $whence = SEEK_SET) { - return false; + switch ($whence) { + case SEEK_SET: + $this->seekTo($offset); + break; + case SEEK_CUR: + $this->seekTo($this->readPosition + $offset); + break; + case SEEK_END: + $this->seekTo($this->size + $offset); + break; + } + return true; + } + + private function seekTo(int $offset): void { + $this->internalPosition = $offset; + $this->readPosition = $offset; + $this->buffer = ''; + $this->request_chunk(256 * 1024); } public function stream_tell() { @@ -137,20 +140,23 @@ class SFTPReadStream implements File { $data = substr($this->buffer, 0, $count); $this->buffer = substr($this->buffer, $count); - if ($this->buffer === false) { - $this->buffer = ''; - } $this->readPosition += strlen($data); return $data; } - private function request_chunk($size) { + private function request_chunk(int $size) { + if ($this->pendingRead) { + $this->sftp->_get_sftp_packet(); + } + $packet = pack('Na*N3', strlen($this->handle), $this->handle, $this->internalPosition / 4294967296, $this->internalPosition, $size); + $this->pendingRead = true; return $this->sftp->_send_sftp_packet(NET_SFTP_READ, $packet); } private function read_chunk() { + $this->pendingRead = false; $response = $this->sftp->_get_sftp_packet(); switch ($this->sftp->packet_type) { @@ -199,8 +205,13 @@ class SFTPReadStream implements File { } public function stream_close() { + // we still have a read request incoming that needs to be handled before we can close + if ($this->pendingRead) { + $this->sftp->_get_sftp_packet(); + } if (!$this->sftp->_close_handle($this->handle)) { return false; } + return true; } } diff --git a/apps/files_external/lib/Lib/Storage/SFTPWriteStream.php b/apps/files_external/lib/Lib/Storage/SFTPWriteStream.php index b1518809f09..d64e89b5462 100644 --- a/apps/files_external/lib/Lib/Storage/SFTPWriteStream.php +++ b/apps/files_external/lib/Lib/Storage/SFTPWriteStream.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Robin Appelman <robin@icewind.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Lib\Storage; @@ -36,7 +18,7 @@ class SFTPWriteStream implements File { /** @var \phpseclib\Net\SFTP */ private $sftp; - /** @var resource */ + /** @var string */ private $handle; /** @var int */ @@ -60,11 +42,9 @@ class SFTPWriteStream implements File { /** * Load the source from the stream context and return the context options * - * @param string $name - * @return array * @throws \BadMethodCallException */ - protected function loadContext($name) { + protected function loadContext(string $name) { $context = stream_context_get_options($this->context); if (isset($context[$name])) { $context = $context[$name]; @@ -180,5 +160,6 @@ class SFTPWriteStream implements File { if (!$this->sftp->_close_handle($this->handle)) { return false; } + return true; } } diff --git a/apps/files_external/lib/Lib/Storage/SMB.php b/apps/files_external/lib/Lib/Storage/SMB.php index c2555f87b93..8f8750864e1 100644 --- a/apps/files_external/lib/Lib/Storage/SMB.php +++ b/apps/files_external/lib/Lib/Storage/SMB.php @@ -1,39 +1,11 @@ <?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 Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Juan Pablo Villafañez <jvillafanez@solidgear.es> - * @author Juan Pablo Villafáñez <jvillafanez@solidgear.es> - * @author Julius Härtl <jus@bitgrid.net> - * @author Michael Gapczynski <GapczynskiM@gmail.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Philipp Kapfer <philipp.kapfer@gmx.at> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Roland Tapken <roland@bitarbeiter.net> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @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/> - * + * 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\Lib\Storage; use Icewind\SMB\ACL; @@ -51,22 +23,24 @@ use Icewind\SMB\IFileInfo; use Icewind\SMB\Native\NativeServer; use Icewind\SMB\Options; use Icewind\SMB\ServerFactory; -use Icewind\SMB\System; +use Icewind\SMB\Wrapped\Server; use Icewind\Streams\CallbackWrapper; use Icewind\Streams\IteratorDirectory; -use OC\Cache\CappedMemoryCache; use OC\Files\Filesystem; use OC\Files\Storage\Common; use OCA\Files_External\Lib\Notify\SMBNotifyHandler; +use OCP\Cache\CappedMemoryCache; use OCP\Constants; use OCP\Files\EntityTooLargeException; +use OCP\Files\IMimeTypeDetector; use OCP\Files\Notify\IChange; use OCP\Files\Notify\IRenameChange; use OCP\Files\NotPermittedException; use OCP\Files\Storage\INotifyStorage; use OCP\Files\StorageAuthException; use OCP\Files\StorageNotAvailableException; -use OCP\ILogger; +use OCP\ITempManager; +use Psr\Log\LoggerInterface; class SMB extends Common implements INotifyStorage { /** @@ -84,91 +58,93 @@ class SMB extends Common implements INotifyStorage { */ protected $root; - /** - * @var IFileInfo[] - */ - protected $statCache; + /** @var CappedMemoryCache<IFileInfo> */ + protected CappedMemoryCache $statCache; - /** @var ILogger */ + /** @var LoggerInterface */ protected $logger; /** @var bool */ protected $showHidden; + private bool $caseSensitive; + /** @var bool */ protected $checkAcl; - public function __construct($params) { - if (!isset($params['host'])) { + public function __construct(array $parameters) { + if (!isset($parameters['host'])) { throw new \Exception('Invalid configuration, no host provided'); } - if (isset($params['auth'])) { - $auth = $params['auth']; - } elseif (isset($params['user']) && isset($params['password']) && isset($params['share'])) { - [$workgroup, $user] = $this->splitUser($params['user']); - $auth = new BasicAuth($user, $workgroup, $params['password']); + if (isset($parameters['auth'])) { + $auth = $parameters['auth']; + } elseif (isset($parameters['user']) && isset($parameters['password']) && isset($parameters['share'])) { + [$workgroup, $user] = $this->splitUser($parameters['user']); + $auth = new BasicAuth($user, $workgroup, $parameters['password']); } else { throw new \Exception('Invalid configuration, no credentials provided'); } - if (isset($params['logger'])) { - $this->logger = $params['logger']; + if (isset($parameters['logger'])) { + if (!$parameters['logger'] instanceof LoggerInterface) { + throw new \Exception( + 'Invalid logger. Got ' + . get_class($parameters['logger']) + . ' Expected ' . LoggerInterface::class + ); + } + $this->logger = $parameters['logger']; } else { - $this->logger = \OC::$server->getLogger(); + $this->logger = \OCP\Server::get(LoggerInterface::class); } $options = new Options(); - if (isset($params['timeout'])) { - $timeout = (int)$params['timeout']; + if (isset($parameters['timeout'])) { + $timeout = (int)$parameters['timeout']; if ($timeout > 0) { $options->setTimeout($timeout); } } - $serverFactory = new ServerFactory($options); - $this->server = $serverFactory->createServer($params['host'], $auth); - $this->share = $this->server->getShare(trim($params['share'], '/')); + $system = \OCP\Server::get(SystemBridge::class); + $serverFactory = new ServerFactory($options, $system); + $this->server = $serverFactory->createServer($parameters['host'], $auth); + $this->share = $this->server->getShare(trim($parameters['share'], '/')); - $this->root = $params['root'] ?? '/'; + $this->root = $parameters['root'] ?? '/'; $this->root = '/' . ltrim($this->root, '/'); $this->root = rtrim($this->root, '/') . '/'; - $this->showHidden = isset($params['show_hidden']) && $params['show_hidden']; - $this->checkAcl = isset($params['check_acl']) && $params['check_acl']; + $this->showHidden = isset($parameters['show_hidden']) && $parameters['show_hidden']; + $this->caseSensitive = (bool)($parameters['case_sensitive'] ?? true); + $this->checkAcl = isset($parameters['check_acl']) && $parameters['check_acl']; $this->statCache = new CappedMemoryCache(); - parent::__construct($params); + parent::__construct($parameters); } - private function splitUser($user) { - if (strpos($user, '/')) { + private function splitUser(string $user): array { + if (str_contains($user, '/')) { return explode('/', $user, 2); - } elseif (strpos($user, '\\')) { + } elseif (str_contains($user, '\\')) { return explode('\\', $user); - } else { - return [null, $user]; } + + return [null, $user]; } - /** - * @return string - */ - public function getId() { + public function getId(): string { // FIXME: double slash to keep compatible with the old storage ids, // failure to do so will lead to creation of a new storage id and // loss of shares from the storage return 'smb::' . $this->server->getAuth()->getUsername() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root; } - /** - * @param string $path - * @return string - */ - protected function buildPath($path) { + protected function buildPath(string $path): string { return Filesystem::normalizePath($this->root . '/' . $path, true, false, true); } - protected function relativePath($fullPath) { + protected function relativePath(string $fullPath): ?string { if ($fullPath === $this->root) { return ''; } elseif (substr($fullPath, 0, strlen($this->root)) === $this->root) { @@ -179,11 +155,11 @@ class SMB extends Common implements INotifyStorage { } /** - * @param string $path - * @return IFileInfo * @throws StorageAuthException + * @throws \OCP\Files\NotFoundException + * @throws \OCP\Files\ForbiddenException */ - protected function getFileInfo($path) { + protected function getFileInfo(string $path): IFileInfo { try { $path = $this->buildPath($path); $cached = $this->statCache[$path] ?? null; @@ -196,34 +172,36 @@ class SMB extends Common implements INotifyStorage { } } catch (ConnectException $e) { $this->throwUnavailable($e); + } catch (NotFoundException $e) { + throw new \OCP\Files\NotFoundException($e->getMessage(), 0, $e); } catch (ForbiddenException $e) { // with php-smbclient, this exception is thrown when the provided password is invalid. // Possible is also ForbiddenException with a different error code, so we check it. if ($e->getCode() === 1) { $this->throwUnavailable($e); } - throw $e; + throw new \OCP\Files\ForbiddenException($e->getMessage(), false, $e); } } /** - * @param \Exception $e - * @return never * @throws StorageAuthException */ - protected function throwUnavailable(\Exception $e) { - $this->logger->logException($e, ['message' => 'Error while getting file info']); + protected function throwUnavailable(\Exception $e): never { + $this->logger->error('Error while getting file info', ['exception' => $e]); throw new StorageAuthException($e->getMessage(), $e); } /** * get the acl from fileinfo that is relevant for the configured user - * - * @param IFileInfo $file - * @return ACL|null */ private function getACL(IFileInfo $file): ?ACL { - $acls = $file->getAcls(); + try { + $acls = $file->getAcls(); + } catch (Exception $e) { + $this->logger->warning('Error while getting file acls', ['exception' => $e]); + return null; + } foreach ($acls as $user => $acl) { [, $user] = $this->splitUser($user); // strip domain if ($user === $this->server->getAuth()->getUsername()) { @@ -235,11 +213,10 @@ class SMB extends Common implements INotifyStorage { } /** - * @param string $path * @return \Generator<IFileInfo> * @throws StorageNotAvailableException */ - protected function getFolderContents($path): iterable { + protected function getFolderContents(string $path): iterable { try { $path = ltrim($this->buildPath($path), '/'); try { @@ -277,22 +254,20 @@ class SMB extends Common implements INotifyStorage { yield $file; } } catch (ForbiddenException $e) { - $this->logger->logException($e, ['level' => ILogger::DEBUG, 'message' => 'Hiding forbidden entry ' . $file->getName()]); + $this->logger->debug($e->getMessage(), ['exception' => $e]); } catch (NotFoundException $e) { - $this->logger->logException($e, ['level' => ILogger::DEBUG, 'message' => 'Hiding not found entry ' . $file->getName()]); + $this->logger->debug('Hiding forbidden entry ' . $file->getName(), ['exception' => $e]); } } } catch (ConnectException $e) { - $this->logger->logException($e, ['message' => 'Error while getting folder content']); - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + $this->logger->error('Error while getting folder content', ['exception' => $e]); + throw new StorageNotAvailableException($e->getMessage(), (int)$e->getCode(), $e); + } catch (NotFoundException $e) { + throw new \OCP\Files\NotFoundException($e->getMessage(), 0, $e); } } - /** - * @param IFileInfo $info - * @return array - */ - protected function formatInfo($info) { + protected function formatInfo(IFileInfo $info): array { $result = [ 'size' => $info->getSize(), 'mtime' => $info->getMTime(), @@ -310,12 +285,17 @@ class SMB extends Common implements INotifyStorage { * * @param string $source the old name of the path * @param string $target the new name of the path - * @return bool true if the rename is successful, false otherwise */ - public function rename($source, $target, $retry = true) { + public function rename(string $source, string $target, bool $retry = true): bool { if ($this->isRootDir($source) || $this->isRootDir($target)) { return false; } + if ($this->caseSensitive === false + && mb_strtolower($target) === mb_strtolower($source) + ) { + // Forbid changing case only on case-insensitive file system + return false; + } $absoluteSource = $this->buildPath($source); $absoluteTarget = $this->buildPath($target); @@ -324,39 +304,39 @@ class SMB extends Common implements INotifyStorage { } catch (AlreadyExistsException $e) { if ($retry) { $this->remove($target); - $result = $this->share->rename($absoluteSource, $absoluteTarget, false); + $result = $this->share->rename($absoluteSource, $absoluteTarget); } else { - $this->logger->logException($e, ['level' => ILogger::WARN]); + $this->logger->warning($e->getMessage(), ['exception' => $e]); return false; } } catch (InvalidArgumentException $e) { if ($retry) { $this->remove($target); - $result = $this->share->rename($absoluteSource, $absoluteTarget, false); + $result = $this->share->rename($absoluteSource, $absoluteTarget); } else { - $this->logger->logException($e, ['level' => ILogger::WARN]); + $this->logger->warning($e->getMessage(), ['exception' => $e]); return false; } } catch (\Exception $e) { - $this->logger->logException($e, ['level' => ILogger::WARN]); + $this->logger->warning($e->getMessage(), ['exception' => $e]); return false; } unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]); return $result; } - public function stat($path, $retry = true) { + public function stat(string $path, bool $retry = true): array|false { try { $result = $this->formatInfo($this->getFileInfo($path)); - } catch (ForbiddenException $e) { + } catch (\OCP\Files\ForbiddenException $e) { return false; - } catch (NotFoundException $e) { + } catch (\OCP\Files\NotFoundException $e) { return false; } catch (TimedOutException $e) { if ($retry) { return $this->stat($path, false); } else { - throw $e; + throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); } } if ($this->remoteIsShare() && $this->isRootDir($path)) { @@ -367,10 +347,8 @@ class SMB extends Common implements INotifyStorage { /** * get the best guess for the modification time of the share - * - * @return int */ - private function shareMTime() { + private function shareMTime(): int { $highestMTime = 0; $files = $this->share->dir($this->root); foreach ($files as $fileInfo) { @@ -389,28 +367,19 @@ class SMB extends Common implements INotifyStorage { /** * Check if the path is our root dir (not the smb one) - * - * @param string $path the path - * @return bool */ - private function isRootDir($path) { + private function isRootDir(string $path): bool { return $path === '' || $path === '/' || $path === '.'; } /** * Check if our root points to a smb share - * - * @return bool true if our root points to a share false otherwise */ - private function remoteIsShare() { + private function remoteIsShare(): bool { return $this->share->getName() && (!$this->root || $this->root === '/'); } - /** - * @param string $path - * @return bool - */ - public function unlink($path) { + public function unlink(string $path): bool { if ($this->isRootDir($path)) { return false; } @@ -429,48 +398,43 @@ class SMB extends Common implements INotifyStorage { } catch (ForbiddenException $e) { return false; } catch (ConnectException $e) { - $this->logger->logException($e, ['message' => 'Error while deleting file']); - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + $this->logger->error('Error while deleting file', ['exception' => $e]); + throw new StorageNotAvailableException($e->getMessage(), (int)$e->getCode(), $e); } } /** * check if a file or folder has been updated since $time - * - * @param string $path - * @param int $time - * @return bool */ - public function hasUpdated($path, $time) { + public function hasUpdated(string $path, int $time): bool { if (!$path and $this->root === '/') { // mtime doesn't work for shares, but giving the nature of the backend, // doing a full update is still just fast enough return true; } else { $actualTime = $this->filemtime($path); - return $actualTime > $time; + return $actualTime > $time || $actualTime === 0; } } /** - * @param string $path - * @param string $mode - * @return resource|bool + * @return resource|false */ - public function fopen($path, $mode) { + public function fopen(string $path, string $mode) { $fullPath = $this->buildPath($path); try { switch ($mode) { case 'r': case 'rb': if (!$this->file_exists($path)) { + $this->logger->warning('Failed to open ' . $path . ' on ' . $this->getId() . ', file doesn\'t exist.'); return false; } return $this->share->read($fullPath); case 'w': case 'wb': $source = $this->share->write($fullPath); - return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) { + return CallBackWrapper::wrap($source, null, null, function () use ($fullPath): void { unset($this->statCache[$fullPath]); }); case 'a': @@ -491,18 +455,20 @@ class SMB extends Common implements INotifyStorage { } if ($this->file_exists($path)) { if (!$this->isUpdatable($path)) { + $this->logger->warning('Failed to open ' . $path . ' on ' . $this->getId() . ', file not updatable.'); return false; } $tmpFile = $this->getCachedFile($path); } else { if (!$this->isCreatable(dirname($path))) { + $this->logger->warning('Failed to open ' . $path . ' on ' . $this->getId() . ', parent directory not writable.'); return false; } - $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); + $tmpFile = \OCP\Server::get(ITempManager::class)->getTemporaryFile($ext); } $source = fopen($tmpFile, $mode); $share = $this->share; - return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) { + return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share): void { unset($this->statCache[$fullPath]); $share->put($tmpFile, $fullPath); unlink($tmpFile); @@ -510,24 +476,27 @@ class SMB extends Common implements INotifyStorage { } return false; } catch (NotFoundException $e) { + $this->logger->warning('Failed to open ' . $path . ' on ' . $this->getId() . ', not found.', ['exception' => $e]); return false; } catch (ForbiddenException $e) { + $this->logger->warning('Failed to open ' . $path . ' on ' . $this->getId() . ', forbidden.', ['exception' => $e]); return false; } catch (OutOfSpaceException $e) { - throw new EntityTooLargeException("not enough available space to create file", 0, $e); + $this->logger->warning('Failed to open ' . $path . ' on ' . $this->getId() . ', out of space.', ['exception' => $e]); + throw new EntityTooLargeException('not enough available space to create file', 0, $e); } catch (ConnectException $e) { - $this->logger->logException($e, ['message' => 'Error while opening file']); - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + $this->logger->error('Error while opening file ' . $path . ' on ' . $this->getId(), ['exception' => $e]); + throw new StorageNotAvailableException($e->getMessage(), (int)$e->getCode(), $e); } } - public function rmdir($path) { + public function rmdir(string $path): bool { if ($this->isRootDir($path)) { return false; } try { - $this->statCache = []; + $this->statCache = new CappedMemoryCache(); $content = $this->share->dir($this->buildPath($path)); foreach ($content as $file) { if ($file->isDirectory()) { @@ -543,12 +512,12 @@ class SMB extends Common implements INotifyStorage { } catch (ForbiddenException $e) { return false; } catch (ConnectException $e) { - $this->logger->logException($e, ['message' => 'Error while removing folder']); - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + $this->logger->error('Error while removing folder', ['exception' => $e]); + throw new StorageNotAvailableException($e->getMessage(), (int)$e->getCode(), $e); } } - public function touch($path, $mtime = null) { + public function touch(string $path, ?int $mtime = null): bool { try { if (!$this->file_exists($path)) { $fh = $this->share->write($this->buildPath($path)); @@ -557,29 +526,26 @@ class SMB extends Common implements INotifyStorage { } return false; } catch (OutOfSpaceException $e) { - throw new EntityTooLargeException("not enough available space to create file", 0, $e); + throw new EntityTooLargeException('not enough available space to create file', 0, $e); } catch (ConnectException $e) { - $this->logger->logException($e, ['message' => 'Error while creating file']); - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + $this->logger->error('Error while creating file', ['exception' => $e]); + throw new StorageNotAvailableException($e->getMessage(), (int)$e->getCode(), $e); } } - public function getMetaData($path) { + public function getMetaData(string $path): ?array { try { $fileInfo = $this->getFileInfo($path); - } catch (NotFoundException $e) { - return null; - } catch (ForbiddenException $e) { + } catch (\OCP\Files\NotFoundException $e) { return null; - } - if (!$fileInfo) { + } catch (\OCP\Files\ForbiddenException $e) { return null; } return $this->getMetaDataFromFileInfo($fileInfo); } - private function getMetaDataFromFileInfo(IFileInfo $fileInfo) { + private function getMetaDataFromFileInfo(IFileInfo $fileInfo): array { $permissions = Constants::PERMISSION_READ + Constants::PERMISSION_SHARE; if ( @@ -596,7 +562,7 @@ class SMB extends Common implements INotifyStorage { if ($fileInfo->isDirectory()) { $data['mimetype'] = 'httpd/unix-directory'; } else { - $data['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($fileInfo->getPath()); + $data['mimetype'] = \OCP\Server::get(IMimeTypeDetector::class)->detectPath($fileInfo->getPath()); } $data['mtime'] = $fileInfo->getMTime(); if ($fileInfo->isDirectory()) { @@ -612,7 +578,7 @@ class SMB extends Common implements INotifyStorage { return $data; } - public function opendir($path) { + public function opendir(string $path) { try { $files = $this->getFolderContents($path); } catch (NotFoundException $e) { @@ -627,7 +593,7 @@ class SMB extends Common implements INotifyStorage { return IteratorDirectory::wrap($names); } - public function getDirectoryContent($directory): \Traversable { + public function getDirectoryContent(string $directory): \Traversable { try { $files = $this->getFolderContents($directory); foreach ($files as $file) { @@ -640,73 +606,84 @@ class SMB extends Common implements INotifyStorage { } } - public function filetype($path) { + public function filetype(string $path): string|false { try { return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file'; - } catch (NotFoundException $e) { + } catch (\OCP\Files\NotFoundException $e) { return false; - } catch (ForbiddenException $e) { + } catch (\OCP\Files\ForbiddenException $e) { return false; } } - public function mkdir($path) { + public function mkdir(string $path): bool { $path = $this->buildPath($path); try { $this->share->mkdir($path); return true; } catch (ConnectException $e) { - $this->logger->logException($e, ['message' => 'Error while creating folder']); - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + $this->logger->error('Error while creating folder', ['exception' => $e]); + throw new StorageNotAvailableException($e->getMessage(), (int)$e->getCode(), $e); } catch (Exception $e) { return false; } } - public function file_exists($path) { + public function file_exists(string $path): bool { try { + // Case sensitive filesystem doesn't matter for root directory + if ($this->caseSensitive === false && $path !== '') { + $filename = basename($path); + $siblings = $this->getDirectoryContent(dirname($path)); + foreach ($siblings as $sibling) { + if ($sibling['name'] === $filename) { + return true; + } + } + return false; + } $this->getFileInfo($path); return true; - } catch (NotFoundException $e) { + } catch (\OCP\Files\NotFoundException $e) { return false; - } catch (ForbiddenException $e) { + } catch (\OCP\Files\ForbiddenException $e) { return false; } catch (ConnectException $e) { - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + throw new StorageNotAvailableException($e->getMessage(), (int)$e->getCode(), $e); } } - public function isReadable($path) { + public function isReadable(string $path): bool { try { $info = $this->getFileInfo($path); return $this->showHidden || !$info->isHidden(); - } catch (NotFoundException $e) { + } catch (\OCP\Files\NotFoundException $e) { return false; - } catch (ForbiddenException $e) { + } catch (\OCP\Files\ForbiddenException $e) { return false; } } - public function isUpdatable($path) { + public function isUpdatable(string $path): bool { try { $info = $this->getFileInfo($path); // following windows behaviour for read-only folders: they can be written into // (https://support.microsoft.com/en-us/kb/326549 - "cause" section) return ($this->showHidden || !$info->isHidden()) && (!$info->isReadOnly() || $info->isDirectory()); - } catch (NotFoundException $e) { + } catch (\OCP\Files\NotFoundException $e) { return false; - } catch (ForbiddenException $e) { + } catch (\OCP\Files\ForbiddenException $e) { return false; } } - public function isDeletable($path) { + public function isDeletable(string $path): bool { try { $info = $this->getFileInfo($path); return ($this->showHidden || !$info->isHidden()) && !$info->isReadOnly(); - } catch (NotFoundException $e) { + } catch (\OCP\Files\NotFoundException $e) { return false; - } catch (ForbiddenException $e) { + } catch (\OCP\Files\ForbiddenException $e) { return false; } } @@ -714,28 +691,25 @@ class SMB extends Common implements INotifyStorage { /** * check if smbclient is installed */ - public static function checkDependencies() { - return ( - (bool)\OC_Helper::findBinaryPath('smbclient') - || NativeServer::available(new System()) - ) ? true : ['smbclient']; + public static function checkDependencies(): array|bool { + $system = \OCP\Server::get(SystemBridge::class); + return Server::available($system) || NativeServer::available($system) ?: ['smbclient']; } - /** - * Test a storage for availability - * - * @return bool - */ - public function test() { + public function test(): bool { try { return parent::test(); + } catch (StorageAuthException $e) { + return false; + } catch (ForbiddenException $e) { + return false; } catch (Exception $e) { - $this->logger->logException($e); + $this->logger->error($e->getMessage(), ['exception' => $e]); return false; } } - public function listen($path, callable $callback) { + public function listen(string $path, callable $callback): void { $this->notify($path)->listen(function (IChange $change) use ($callback) { if ($change instanceof IRenameChange) { return $callback($change->getType(), $change->getPath(), $change->getTargetPath()); @@ -745,7 +719,7 @@ class SMB extends Common implements INotifyStorage { }); } - public function notify($path) { + public function notify(string $path): SMBNotifyHandler { $path = '/' . ltrim($path, '/'); $shareNotifyHandler = $this->share->notify($this->buildPath($path)); return new SMBNotifyHandler($shareNotifyHandler, $this->root); diff --git a/apps/files_external/lib/Lib/Storage/StreamWrapper.php b/apps/files_external/lib/Lib/Storage/StreamWrapper.php index dc203399646..1272b9d4d8a 100644 --- a/apps/files_external/lib/Lib/Storage/StreamWrapper.php +++ b/apps/files_external/lib/Lib/Storage/StreamWrapper.php @@ -1,44 +1,23 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bart Visscher <bartv@thisnet.nl> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @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/> - * + * SPDX-FileCopyrightText: 2020-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Storage; -abstract class StreamWrapper extends \OC\Files\Storage\Common { +use OC\Files\Storage\Common; + +abstract class StreamWrapper extends Common { - /** - * @param string $path - * @return string|null - */ - abstract public function constructUrl($path); + abstract public function constructUrl(string $path): ?string; - public function mkdir($path) { + public function mkdir(string $path): bool { return mkdir($this->constructUrl($path)); } - public function rmdir($path) { + public function rmdir(string $path): bool { if ($this->is_dir($path) && $this->isDeletable($path)) { $dh = $this->opendir($path); if (!is_resource($dh)) { @@ -60,19 +39,19 @@ abstract class StreamWrapper extends \OC\Files\Storage\Common { } } - public function opendir($path) { + public function opendir(string $path) { return opendir($this->constructUrl($path)); } - public function filetype($path) { + public function filetype(string $path): string|false { return @filetype($this->constructUrl($path)); } - public function file_exists($path) { + public function file_exists(string $path): bool { return file_exists($this->constructUrl($path)); } - public function unlink($path) { + public function unlink(string $path): bool { $url = $this->constructUrl($path); $success = unlink($url); // normally unlink() is supposed to do this implicitly, @@ -81,11 +60,11 @@ abstract class StreamWrapper extends \OC\Files\Storage\Common { return $success; } - public function fopen($path, $mode) { + public function fopen(string $path, string $mode) { return fopen($this->constructUrl($path), $mode); } - public function touch($path, $mtime = null) { + public function touch(string $path, ?int $mtime = null): bool { if ($this->file_exists($path)) { if (is_null($mtime)) { $fh = $this->fopen($path, 'a'); @@ -102,26 +81,19 @@ abstract class StreamWrapper extends \OC\Files\Storage\Common { } } - /** - * @param string $path - * @param string $target - */ - public function getFile($path, $target) { + public function getFile(string $path, string $target): bool { return copy($this->constructUrl($path), $target); } - /** - * @param string $target - */ - public function uploadFile($path, $target) { + public function uploadFile(string $path, string $target): bool { return copy($path, $this->constructUrl($target)); } - public function rename($path1, $path2) { - return rename($this->constructUrl($path1), $this->constructUrl($path2)); + public function rename(string $source, string $target): bool { + return rename($this->constructUrl($source), $this->constructUrl($target)); } - public function stat($path) { + public function stat(string $path): array|false { return stat($this->constructUrl($path)); } } diff --git a/apps/files_external/lib/Lib/Storage/Swift.php b/apps/files_external/lib/Lib/Storage/Swift.php index fb93c6cdce2..e80570f14ba 100644 --- a/apps/files_external/lib/Lib/Storage/Swift.php +++ b/apps/files_external/lib/Lib/Storage/Swift.php @@ -3,60 +3,37 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Bart Visscher <bartv@thisnet.nl> - * @author Benjamin Liles <benliles@arch.tamu.edu> - * @author Christian Berendt <berendt@b1-systems.de> - * @author Christopher Bartz <bartz@dkrz.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Felix Moeller <mail@felixmoeller.de> - * @author Joas Schilling <coding@schilljs.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Martin Mattel <martin.mattel@diemattels.at> - * @author Michael Zamot <michael@zamot.io> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Philipp Kapfer <philipp.kapfer@gmx.at> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Tim Dettrick <t.dettrick@uq.edu.au> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib\Storage; use GuzzleHttp\Psr7\Uri; use Icewind\Streams\CallbackWrapper; use Icewind\Streams\IteratorDirectory; +use OC\Files\Filesystem; use OC\Files\ObjectStore\SwiftFactory; +use OC\Files\Storage\Common; +use OCP\Cache\CappedMemoryCache; use OCP\Files\IMimeTypeDetector; +use OCP\Files\StorageAuthException; use OCP\Files\StorageBadConfigException; +use OCP\Files\StorageNotAvailableException; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\ITempManager; +use OCP\Server; use OpenStack\Common\Error\BadResponseError; +use OpenStack\ObjectStore\v1\Models\Container; use OpenStack\ObjectStore\v1\Models\StorageObject; use Psr\Log\LoggerInterface; -class Swift extends \OC\Files\Storage\Common { +class Swift extends Common { /** @var SwiftFactory */ private $connectionFactory; /** - * @var \OpenStack\ObjectStore\v1\Models\Container + * @var Container */ private $container; /** @@ -84,15 +61,11 @@ class Swift extends \OC\Files\Storage\Common { * \OpenCloud\OpenStack\ObjectStorage\Resource\DataObject for existing * paths and path to false for not existing paths. * - * @var \OCP\ICache + * @var ICache */ private $objectCache; - /** - * @param string $path - * @return mixed|string - */ - private function normalizePath(string $path) { + private function normalizePath(string $path): string { $path = trim($path, '/'); if (!$path) { @@ -107,28 +80,21 @@ class Swift extends \OC\Files\Storage\Common { public const SUBCONTAINER_FILE = '.subcontainers'; /** - * translate directory path to container name - * - * @param string $path - * @return string - */ - - /** * Fetches an object from the API. * If the object is cached already or a * failed "doesn't exist" response was cached, * that one will be returned. * - * @param string $path - * @return StorageObject|bool object - * or false if the object did not exist - * @throws \OCP\Files\StorageAuthException - * @throws \OCP\Files\StorageNotAvailableException + * @return StorageObject|false object + * or false if the object did not exist + * @throws StorageAuthException + * @throws StorageNotAvailableException */ - private function fetchObject(string $path) { - if ($this->objectCache->hasKey($path)) { + private function fetchObject(string $path): StorageObject|false { + $cached = $this->objectCache->get($path); + if ($cached !== null) { // might be "false" if object did not exist from last check - return $this->objectCache->get($path); + return $cached; } try { $object = $this->getContainer()->getObject($path); @@ -138,7 +104,7 @@ class Swift extends \OC\Files\Storage\Common { } catch (BadResponseError $e) { // Expected response is "404 Not Found", so only log if it isn't if ($e->getResponse()->getStatusCode() !== 404) { - \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), [ + Server::get(LoggerInterface::class)->error($e->getMessage(), [ 'exception' => $e, 'app' => 'files_external', ]); @@ -151,67 +117,65 @@ class Swift extends \OC\Files\Storage\Common { /** * Returns whether the given path exists. * - * @param string $path - * * @return bool true if the object exist, false otherwise - * @throws \OCP\Files\StorageAuthException - * @throws \OCP\Files\StorageNotAvailableException + * @throws StorageAuthException + * @throws StorageNotAvailableException */ - private function doesObjectExist($path) { + private function doesObjectExist(string $path): bool { return $this->fetchObject($path) !== false; } - public function __construct($params) { - if ((empty($params['key']) and empty($params['password'])) - or (empty($params['user']) && empty($params['userid'])) or empty($params['bucket']) - or empty($params['region']) + public function __construct(array $parameters) { + if ((empty($parameters['key']) and empty($parameters['password'])) + or (empty($parameters['user']) && empty($parameters['userid'])) or empty($parameters['bucket']) + or empty($parameters['region']) ) { - throw new StorageBadConfigException("API Key or password, Username, Bucket and Region have to be configured."); + throw new StorageBadConfigException('API Key or password, Login, Bucket and Region have to be configured.'); } - $user = $params['user']; - $this->id = 'swift::' . $user . md5($params['bucket']); + $user = $parameters['user']; + $this->id = 'swift::' . $user . md5($parameters['bucket']); - $bucketUrl = new Uri($params['bucket']); + $bucketUrl = new Uri($parameters['bucket']); if ($bucketUrl->getHost()) { - $params['bucket'] = basename($bucketUrl->getPath()); - $params['endpoint_url'] = (string)$bucketUrl->withPath(dirname($bucketUrl->getPath())); + $parameters['bucket'] = basename($bucketUrl->getPath()); + $parameters['endpoint_url'] = (string)$bucketUrl->withPath(dirname($bucketUrl->getPath())); } - if (empty($params['url'])) { - $params['url'] = 'https://identity.api.rackspacecloud.com/v2.0/'; + if (empty($parameters['url'])) { + $parameters['url'] = 'https://identity.api.rackspacecloud.com/v2.0/'; } - if (empty($params['service_name'])) { - $params['service_name'] = 'cloudFiles'; + if (empty($parameters['service_name'])) { + $parameters['service_name'] = 'cloudFiles'; } - $params['autocreate'] = true; + $parameters['autocreate'] = true; - if (isset($params['domain'])) { - $params['user'] = [ - 'name' => $params['user'], - 'password' => $params['password'], + if (isset($parameters['domain'])) { + $parameters['user'] = [ + 'name' => $parameters['user'], + 'password' => $parameters['password'], 'domain' => [ - 'name' => $params['domain'], + 'name' => $parameters['domain'], ] ]; } - $this->params = $params; + $this->params = $parameters; // FIXME: private class... - $this->objectCache = new \OC\Cache\CappedMemoryCache(); + $this->objectCache = new CappedMemoryCache(); $this->connectionFactory = new SwiftFactory( - \OC::$server->getMemCacheFactory()->createDistributed('swift/'), + Server::get(ICacheFactory::class)->createDistributed('swift/'), $this->params, - \OC::$server->get(LoggerInterface::class) + Server::get(LoggerInterface::class) ); $this->objectStore = new \OC\Files\ObjectStore\Swift($this->params, $this->connectionFactory); - $this->bucket = $params['bucket']; - $this->mimeDetector = \OC::$server->get(IMimeTypeDetector::class); + $this->bucket = $parameters['bucket']; + $this->mimeDetector = Server::get(IMimeTypeDetector::class); } - public function mkdir($path) { + public function mkdir(string $path): bool { $path = $this->normalizePath($path); if ($this->is_dir($path)) { @@ -232,7 +196,7 @@ class Swift extends \OC\Files\Storage\Common { // with all properties $this->objectCache->remove($path); } catch (BadResponseError $e) { - \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), [ + Server::get(LoggerInterface::class)->error($e->getMessage(), [ 'exception' => $e, 'app' => 'files_external', ]); @@ -242,7 +206,7 @@ class Swift extends \OC\Files\Storage\Common { return true; } - public function file_exists($path) { + public function file_exists(string $path): bool { $path = $this->normalizePath($path); if ($path !== '.' && $this->is_dir($path)) { @@ -252,7 +216,7 @@ class Swift extends \OC\Files\Storage\Common { return $this->doesObjectExist($path); } - public function rmdir($path) { + public function rmdir(string $path): bool { $path = $this->normalizePath($path); if (!$this->is_dir($path) || !$this->isDeletable($path)) { @@ -260,8 +224,8 @@ class Swift extends \OC\Files\Storage\Common { } $dh = $this->opendir($path); - while ($file = readdir($dh)) { - if (\OC\Files\Filesystem::isIgnoredDir($file)) { + while (($file = readdir($dh)) !== false) { + if (Filesystem::isIgnoredDir($file)) { continue; } @@ -276,7 +240,7 @@ class Swift extends \OC\Files\Storage\Common { $this->objectStore->deleteObject($path . '/'); $this->objectCache->remove($path . '/'); } catch (BadResponseError $e) { - \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), [ + Server::get(LoggerInterface::class)->error($e->getMessage(), [ 'exception' => $e, 'app' => 'files_external', ]); @@ -286,7 +250,7 @@ class Swift extends \OC\Files\Storage\Common { return true; } - public function opendir($path) { + public function opendir(string $path) { $path = $this->normalizePath($path); if ($path === '.') { @@ -295,7 +259,7 @@ class Swift extends \OC\Files\Storage\Common { $path .= '/'; } -// $path = str_replace('%23', '#', $path); // the prefix is sent as a query param, so revert the encoding of # + // $path = str_replace('%23', '#', $path); // the prefix is sent as a query param, so revert the encoding of # try { $files = []; @@ -314,7 +278,7 @@ class Swift extends \OC\Files\Storage\Common { return IteratorDirectory::wrap($files); } catch (\Exception $e) { - \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), [ + Server::get(LoggerInterface::class)->error($e->getMessage(), [ 'exception' => $e, 'app' => 'files_external', ]); @@ -322,9 +286,8 @@ class Swift extends \OC\Files\Storage\Common { } } - public function stat($path) { + public function stat(string $path): array|false { $path = $this->normalizePath($path); - if ($path === '.') { $path = ''; } elseif ($this->is_dir($path)) { @@ -337,32 +300,33 @@ class Swift extends \OC\Files\Storage\Common { return false; } } catch (BadResponseError $e) { - \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), [ + Server::get(LoggerInterface::class)->error($e->getMessage(), [ 'exception' => $e, 'app' => 'files_external', ]); return false; } - $dateTime = $object->lastModified ? \DateTime::createFromFormat(\DateTime::RFC1123, $object->lastModified) : false; - $mtime = $dateTime ? $dateTime->getTimestamp() : null; - $objectMetadata = $object->getMetadata(); - if (isset($objectMetadata['timestamp'])) { - $mtime = $objectMetadata['timestamp']; + $mtime = null; + if (!empty($object->lastModified)) { + $dateTime = \DateTime::createFromFormat(\DateTime::RFC1123, $object->lastModified); + if ($dateTime !== false) { + $mtime = $dateTime->getTimestamp(); + } } - if (!empty($mtime)) { - $mtime = floor($mtime); + if (is_numeric($object->getMetadata()['timestamp'] ?? null)) { + $mtime = (float)$object->getMetadata()['timestamp']; } - $stat = []; - $stat['size'] = (int)$object->contentLength; - $stat['mtime'] = $mtime; - $stat['atime'] = time(); - return $stat; + return [ + 'size' => (int)$object->contentLength, + 'mtime' => isset($mtime) ? (int)floor($mtime) : null, + 'atime' => time(), + ]; } - public function filetype($path) { + public function filetype(string $path) { $path = $this->normalizePath($path); if ($path !== '.' && $this->doesObjectExist($path)) { @@ -378,7 +342,7 @@ class Swift extends \OC\Files\Storage\Common { } } - public function unlink($path) { + public function unlink(string $path): bool { $path = $this->normalizePath($path); if ($this->is_dir($path)) { @@ -391,7 +355,7 @@ class Swift extends \OC\Files\Storage\Common { $this->objectCache->remove($path . '/'); } catch (BadResponseError $e) { if ($e->getResponse()->getStatusCode() !== 404) { - \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), [ + Server::get(LoggerInterface::class)->error($e->getMessage(), [ 'exception' => $e, 'app' => 'files_external', ]); @@ -402,7 +366,7 @@ class Swift extends \OC\Files\Storage\Common { return true; } - public function fopen($path, $mode) { + public function fopen(string $path, string $mode) { $path = $this->normalizePath($path); switch ($mode) { @@ -415,7 +379,7 @@ class Swift extends \OC\Files\Storage\Common { try { return $this->objectStore->readObject($path); } catch (BadResponseError $e) { - \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), [ + Server::get(LoggerInterface::class)->error($e->getMessage(), [ 'exception' => $e, 'app' => 'files_external', ]); @@ -435,7 +399,7 @@ class Swift extends \OC\Files\Storage\Common { } else { $ext = ''; } - $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); + $tmpFile = Server::get(ITempManager::class)->getTemporaryFile($ext); // Fetch existing file if required if ($mode[0] !== 'w' && $this->file_exists($path)) { if ($mode[0] === 'x') { @@ -446,13 +410,13 @@ class Swift extends \OC\Files\Storage\Common { file_put_contents($tmpFile, $source); } $handle = fopen($tmpFile, $mode); - return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { + return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile): void { $this->writeBack($tmpFile, $path); }); } } - public function touch($path, $mtime = null) { + public function touch(string $path, ?int $mtime = null): bool { $path = $this->normalizePath($path); if (is_null($mtime)) { $mtime = time(); @@ -482,27 +446,27 @@ class Swift extends \OC\Files\Storage\Common { } } - public function copy($path1, $path2) { - $path1 = $this->normalizePath($path1); - $path2 = $this->normalizePath($path2); + public function copy(string $source, string $target): bool { + $source = $this->normalizePath($source); + $target = $this->normalizePath($target); - $fileType = $this->filetype($path1); + $fileType = $this->filetype($source); if ($fileType) { // make way - $this->unlink($path2); + $this->unlink($target); } if ($fileType === 'file') { try { - $source = $this->fetchObject($path1); - $source->copy([ - 'destination' => $this->bucket . '/' . $path2 + $sourceObject = $this->fetchObject($source); + $sourceObject->copy([ + 'destination' => $this->bucket . '/' . $target ]); // invalidate target object to force repopulation on fetch - $this->objectCache->remove($path2); - $this->objectCache->remove($path2 . '/'); + $this->objectCache->remove($target); + $this->objectCache->remove($target . '/'); } catch (BadResponseError $e) { - \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), [ + Server::get(LoggerInterface::class)->error($e->getMessage(), [ 'exception' => $e, 'app' => 'files_external', ]); @@ -510,29 +474,29 @@ class Swift extends \OC\Files\Storage\Common { } } elseif ($fileType === 'dir') { try { - $source = $this->fetchObject($path1 . '/'); - $source->copy([ - 'destination' => $this->bucket . '/' . $path2 . '/' + $sourceObject = $this->fetchObject($source . '/'); + $sourceObject->copy([ + 'destination' => $this->bucket . '/' . $target . '/' ]); // invalidate target object to force repopulation on fetch - $this->objectCache->remove($path2); - $this->objectCache->remove($path2 . '/'); + $this->objectCache->remove($target); + $this->objectCache->remove($target . '/'); } catch (BadResponseError $e) { - \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), [ + Server::get(LoggerInterface::class)->error($e->getMessage(), [ 'exception' => $e, 'app' => 'files_external', ]); return false; } - $dh = $this->opendir($path1); - while ($file = readdir($dh)) { - if (\OC\Files\Filesystem::isIgnoredDir($file)) { + $dh = $this->opendir($source); + while (($file = readdir($dh)) !== false) { + if (Filesystem::isIgnoredDir($file)) { continue; } - $source = $path1 . '/' . $file; - $target = $path2 . '/' . $file; + $source = $source . '/' . $file; + $target = $target . '/' . $file; $this->copy($source, $target); } } else { @@ -543,22 +507,22 @@ class Swift extends \OC\Files\Storage\Common { return true; } - public function rename($path1, $path2) { - $path1 = $this->normalizePath($path1); - $path2 = $this->normalizePath($path2); + public function rename(string $source, string $target): bool { + $source = $this->normalizePath($source); + $target = $this->normalizePath($target); - $fileType = $this->filetype($path1); + $fileType = $this->filetype($source); if ($fileType === 'dir' || $fileType === 'file') { // copy - if ($this->copy($path1, $path2) === false) { + if ($this->copy($source, $target) === false) { return false; } // cleanup - if ($this->unlink($path1) === false) { + if ($this->unlink($source) === false) { throw new \Exception('failed to remove original'); - $this->unlink($path2); + $this->unlink($target); return false; } @@ -568,18 +532,18 @@ class Swift extends \OC\Files\Storage\Common { return false; } - public function getId() { + public function getId(): string { return $this->id; } /** * Returns the initialized object store container. * - * @return \OpenStack\ObjectStore\v1\Models\Container - * @throws \OCP\Files\StorageAuthException - * @throws \OCP\Files\StorageNotAvailableException + * @return Container + * @throws StorageAuthException + * @throws StorageNotAvailableException */ - public function getContainer() { + public function getContainer(): Container { if (is_null($this->container)) { $this->container = $this->connectionFactory->getContainer(); @@ -590,7 +554,7 @@ class Swift extends \OC\Files\Storage\Common { return $this->container; } - public function writeBack($tmpFile, $path) { + public function writeBack(string $tmpFile, string $path): void { $fileData = fopen($tmpFile, 'r'); $this->objectStore->writeObject($path, $fileData, $this->mimeDetector->detectPath($path)); // invalidate target object to force repopulation on fetch @@ -598,7 +562,7 @@ class Swift extends \OC\Files\Storage\Common { unlink($tmpFile); } - public function hasUpdated($path, $time) { + public function hasUpdated(string $path, int $time): bool { if ($this->is_file($path)) { return parent::hasUpdated($path, $time); } @@ -623,7 +587,7 @@ class Swift extends \OC\Files\Storage\Common { /** * check if curl is installed */ - public static function checkDependencies() { + public static function checkDependencies(): bool { return true; } } diff --git a/apps/files_external/lib/Lib/Storage/SystemBridge.php b/apps/files_external/lib/Lib/Storage/SystemBridge.php new file mode 100644 index 00000000000..80449b2744b --- /dev/null +++ b/apps/files_external/lib/Lib/Storage/SystemBridge.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Robin Appelman <robin@icewind.nl> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files_External\Lib\Storage; + +use Icewind\SMB\System; +use OCP\IBinaryFinder; + +/** + * Bridge the NC and SMB binary finding logic + */ +class SystemBridge extends System { + public function __construct( + private IBinaryFinder $binaryFinder, + ) { + } + + protected function getBinaryPath(string $binary): ?string { + $path = $this->binaryFinder->findBinaryPath($binary); + return $path !== false ? $path : null; + } +} diff --git a/apps/files_external/lib/Lib/StorageConfig.php b/apps/files_external/lib/Lib/StorageConfig.php index 26ba483b00e..2cb82d3790a 100644 --- a/apps/files_external/lib/Lib/StorageConfig.php +++ b/apps/files_external/lib/Lib/StorageConfig.php @@ -1,42 +1,27 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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 Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @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/> - * + * 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\Lib; +use OC\Files\Filesystem; use OCA\Files_External\Lib\Auth\AuthMechanism; use OCA\Files_External\Lib\Auth\IUserProvided; use OCA\Files_External\Lib\Backend\Backend; +use OCA\Files_External\ResponseDefinitions; /** * External storage configuration + * + * @psalm-import-type Files_ExternalStorageConfig from ResponseDefinitions */ class StorageConfig implements \JsonSerializable { public const MOUNT_TYPE_ADMIN = 1; + public const MOUNT_TYPE_PERSONAL = 2; + /** @deprecated use MOUNT_TYPE_PERSONAL (full uppercase) instead */ public const MOUNT_TYPE_PERSONAl = 2; /** @@ -63,7 +48,7 @@ class StorageConfig implements \JsonSerializable { /** * Backend options * - * @var array + * @var array<string, mixed> */ private $backendOptions = []; @@ -98,21 +83,21 @@ class StorageConfig implements \JsonSerializable { /** * List of users who have access to this storage * - * @var string[] + * @var list<string> */ private $applicableUsers = []; /** * List of groups that have access to this storage * - * @var string[] + * @var list<string> */ private $applicableGroups = []; /** * Mount-specific options * - * @var array + * @var array<string, mixed> */ private $mountOptions = []; @@ -126,10 +111,10 @@ class StorageConfig implements \JsonSerializable { /** * Creates a storage config * - * @param int|null $id config id or null for a new config + * @param int|string $id config id or null for a new config */ public function __construct($id = null) { - $this->id = $id; + $this->id = $id ?? -1; $this->mountOptions['enable_sharing'] = false; } @@ -147,7 +132,7 @@ class StorageConfig implements \JsonSerializable { * * @param int $id configuration id */ - public function setId($id) { + public function setId(int $id): void { $this->id = $id; } @@ -169,7 +154,7 @@ class StorageConfig implements \JsonSerializable { * @param string $mountPoint path */ public function setMountPoint($mountPoint) { - $this->mountPoint = \OC\Files\Filesystem::normalizePath($mountPoint); + $this->mountPoint = Filesystem::normalizePath($mountPoint); } /** @@ -220,7 +205,7 @@ class StorageConfig implements \JsonSerializable { foreach ($backendOptions as $key => $value) { if (isset($parameters[$key])) { switch ($parameters[$key]->getType()) { - case \OCA\Files_External\Lib\DefinitionParameter::VALUE_BOOLEAN: + case DefinitionParameter::VALUE_BOOLEAN: $value = (bool)$value; break; } @@ -261,7 +246,7 @@ class StorageConfig implements \JsonSerializable { } /** - * Sets the mount priotity + * Sets the mount priority * * @param int $priority priority */ @@ -272,7 +257,7 @@ class StorageConfig implements \JsonSerializable { /** * Returns the users for which to mount this storage * - * @return string[] applicable users + * @return list<string> applicable users */ public function getApplicableUsers() { return $this->applicableUsers; @@ -281,7 +266,7 @@ class StorageConfig implements \JsonSerializable { /** * Sets the users for which to mount this storage * - * @param string[]|null $applicableUsers applicable users + * @param list<string>|null $applicableUsers applicable users */ public function setApplicableUsers($applicableUsers) { if (is_null($applicableUsers)) { @@ -293,7 +278,7 @@ class StorageConfig implements \JsonSerializable { /** * Returns the groups for which to mount this storage * - * @return string[] applicable groups + * @return list<string> applicable groups */ public function getApplicableGroups() { return $this->applicableGroups; @@ -302,7 +287,7 @@ class StorageConfig implements \JsonSerializable { /** * Sets the groups for which to mount this storage * - * @param string[]|null $applicableGroups applicable groups + * @param list<string>|null $applicableGroups applicable groups */ public function setApplicableGroups($applicableGroups) { if (is_null($applicableGroups)) { @@ -381,14 +366,14 @@ class StorageConfig implements \JsonSerializable { } /** - * @return int self::MOUNT_TYPE_ADMIN or self::MOUNT_TYPE_PERSONAl + * @return int self::MOUNT_TYPE_ADMIN or self::MOUNT_TYPE_PERSONAL */ public function getType() { return $this->type; } /** - * @param int $type self::MOUNT_TYPE_ADMIN or self::MOUNT_TYPE_PERSONAl + * @param int $type self::MOUNT_TYPE_ADMIN or self::MOUNT_TYPE_PERSONAL */ public function setType($type) { $this->type = $type; @@ -396,12 +381,19 @@ class StorageConfig implements \JsonSerializable { /** * Serialize config to JSON + * @return Files_ExternalStorageConfig */ - public function jsonSerialize(): array { + public function jsonSerialize(bool $obfuscate = false): array { $result = []; if (!is_null($this->id)) { $result['id'] = $this->id; } + + // obfuscate sensitive data if requested + if ($obfuscate) { + $this->formatStorageForUI(); + } + $result['mountPoint'] = $this->mountPoint; $result['backend'] = $this->backend->getIdentifier(); $result['authMechanism'] = $this->authMechanism->getIdentifier(); @@ -425,7 +417,22 @@ class StorageConfig implements \JsonSerializable { $result['statusMessage'] = $this->statusMessage; } $result['userProvided'] = $this->authMechanism instanceof IUserProvided; - $result['type'] = ($this->getType() === self::MOUNT_TYPE_PERSONAl) ? 'personal': 'system'; + $result['type'] = ($this->getType() === self::MOUNT_TYPE_PERSONAL) ? 'personal': 'system'; return $result; } + + protected function formatStorageForUI(): void { + /** @var DefinitionParameter[] $parameters */ + $parameters = array_merge($this->getBackend()->getParameters(), $this->getAuthMechanism()->getParameters()); + + $options = $this->getBackendOptions(); + foreach ($options as $key => $value) { + foreach ($parameters as $parameter) { + if ($parameter->getName() === $key && $parameter->getType() === DefinitionParameter::VALUE_PASSWORD) { + $this->setBackendOption($key, DefinitionParameter::UNMODIFIED_PLACEHOLDER); + break; + } + } + } + } } diff --git a/apps/files_external/lib/Lib/StorageModifierTrait.php b/apps/files_external/lib/Lib/StorageModifierTrait.php index 48e91531cc1..4062ff1635e 100644 --- a/apps/files_external/lib/Lib/StorageModifierTrait.php +++ b/apps/files_external/lib/Lib/StorageModifierTrait.php @@ -1,28 +1,13 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Lib; -use OCP\Files\Storage; +use OCP\Files\Storage\IStorage; use OCP\Files\StorageNotAvailableException; use OCP\IUser; @@ -44,23 +29,22 @@ trait StorageModifierTrait { /** * Modify a StorageConfig parameters * - * @param StorageConfig $storage - * @param IUser $user User the storage is being used as + * @param StorageConfig &$storage + * @param ?IUser $user User the storage is being used as + * @return void * @throws InsufficientDataForMeaningfulAnswerException * @throws StorageNotAvailableException */ - public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { } /** - * Wrap a Storage if necessary + * Wrap a storage if necessary * - * @param Storage $storage - * @return Storage * @throws InsufficientDataForMeaningfulAnswerException * @throws StorageNotAvailableException */ - public function wrapStorage(Storage $storage) { + public function wrapStorage(IStorage $storage): IStorage { return $storage; } } diff --git a/apps/files_external/lib/Lib/VisibilityTrait.php b/apps/files_external/lib/Lib/VisibilityTrait.php index db616900b8e..62b26f3edb1 100644 --- a/apps/files_external/lib/Lib/VisibilityTrait.php +++ b/apps/files_external/lib/Lib/VisibilityTrait.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @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/> - * + * 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\Lib; diff --git a/apps/files_external/lib/Listener/GroupDeletedListener.php b/apps/files_external/lib/Listener/GroupDeletedListener.php index d274f35d9cf..244b3b2371f 100644 --- a/apps/files_external/lib/Listener/GroupDeletedListener.php +++ b/apps/files_external/lib/Listener/GroupDeletedListener.php @@ -2,23 +2,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2021 Robin Appelman <robin@icewind.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Listener; @@ -28,12 +13,11 @@ use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\Group\Events\GroupDeletedEvent; +/** @template-implements IEventListener<GroupDeletedEvent> */ class GroupDeletedListener implements IEventListener { - /** @var DBConfigService */ - private $config; - - public function __construct(DBConfigService $config) { - $this->config = $config; + public function __construct( + private DBConfigService $config, + ) { } public function handle(Event $event): void { diff --git a/apps/files_external/lib/Listener/LoadAdditionalListener.php b/apps/files_external/lib/Listener/LoadAdditionalListener.php new file mode 100644 index 00000000000..6ba917759c3 --- /dev/null +++ b/apps/files_external/lib/Listener/LoadAdditionalListener.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Files_External\Listener; + +use OCA\Files\Event\LoadAdditionalScriptsEvent; +use OCA\Files_External\AppInfo\Application; +use OCA\Files_External\ConfigLexicon; +use OCP\AppFramework\Services\IInitialState; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\IAppConfig; +use OCP\Util; + +/** + * @template-implements IEventListener<LoadAdditionalScriptsEvent> + */ +class LoadAdditionalListener implements IEventListener { + + public function __construct( + private readonly IAppConfig $appConfig, + private IInitialState $initialState, + ) { + } + + public function handle(Event $event): void { + if (!($event instanceof LoadAdditionalScriptsEvent)) { + return; + } + + $allowUserMounting = $this->appConfig->getValueBool('files_external', ConfigLexicon::ALLOW_USER_MOUNTING); + $this->initialState->provideInitialState('allowUserMounting', $allowUserMounting); + + Util::addInitScript(Application::APP_ID, 'init'); + } +} diff --git a/apps/files_external/lib/Listener/StorePasswordListener.php b/apps/files_external/lib/Listener/StorePasswordListener.php index bd0c4dc1ffd..8580176b014 100644 --- a/apps/files_external/lib/Listener/StorePasswordListener.php +++ b/apps/files_external/lib/Listener/StorePasswordListener.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2020, Morris Jobke <hey@morrisjobke.de> - * - * @author Joas Schilling <coding@schilljs.com> - * @author Morris Jobke <hey@morrisjobke.de> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Listener; @@ -33,12 +15,11 @@ use OCP\Security\ICredentialsManager; use OCP\User\Events\PasswordUpdatedEvent; use OCP\User\Events\UserLoggedInEvent; +/** @template-implements IEventListener<PasswordUpdatedEvent|UserLoggedInEvent> */ class StorePasswordListener implements IEventListener { - /** @var ICredentialsManager */ - private $credentialsManager; - - public function __construct(ICredentialsManager $credentialsManager) { - $this->credentialsManager = $credentialsManager; + public function __construct( + private ICredentialsManager $credentialsManager, + ) { } public function handle(Event $event): void { @@ -50,19 +31,27 @@ class StorePasswordListener implements IEventListener { return; } - $stored = $this->credentialsManager->retrieve($event->getUser()->getUID(), LoginCredentials::CREDENTIALS_IDENTIFIER); - $update = isset($stored['password']) && $stored['password'] !== $event->getPassword(); - if (!$update && $event instanceof UserLoggedInEvent) { - $update = isset($stored['user']) && $stored['user'] !== $event->getLoginName(); + $storedCredentials = $this->credentialsManager->retrieve($event->getUser()->getUID(), LoginCredentials::CREDENTIALS_IDENTIFIER); + + if (!$storedCredentials) { + return; } - if ($stored && $update) { - $credentials = [ - 'user' => $event->getLoginName(), - 'password' => $event->getPassword() - ]; + $newCredentials = $storedCredentials; + $shouldUpdate = false; + + if (($storedCredentials['password'] ?? null) !== $event->getPassword() && $event->getPassword() !== null) { + $shouldUpdate = true; + $newCredentials['password'] = $event->getPassword(); + } + + if ($event instanceof UserLoggedInEvent && ($storedCredentials['user'] ?? null) !== $event->getLoginName()) { + $shouldUpdate = true; + $newCredentials['user'] = $event->getLoginName(); + } - $this->credentialsManager->store($event->getUser()->getUID(), LoginCredentials::CREDENTIALS_IDENTIFIER, $credentials); + if ($shouldUpdate) { + $this->credentialsManager->store($event->getUser()->getUID(), LoginCredentials::CREDENTIALS_IDENTIFIER, $newCredentials); } } } diff --git a/apps/files_external/lib/Listener/UserDeletedListener.php b/apps/files_external/lib/Listener/UserDeletedListener.php index 1417119b317..337fd12f311 100644 --- a/apps/files_external/lib/Listener/UserDeletedListener.php +++ b/apps/files_external/lib/Listener/UserDeletedListener.php @@ -2,23 +2,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2021 Robin Appelman <robin@icewind.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Listener; @@ -28,12 +13,11 @@ use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\User\Events\UserDeletedEvent; +/** @template-implements IEventListener<UserDeletedEvent> */ class UserDeletedListener implements IEventListener { - /** @var DBConfigService */ - private $config; - - public function __construct(DBConfigService $config) { - $this->config = $config; + public function __construct( + private DBConfigService $config, + ) { } public function handle(Event $event): void { diff --git a/apps/files_external/lib/Migration/DummyUserSession.php b/apps/files_external/lib/Migration/DummyUserSession.php index e1b2b500188..1ebf0e1ec4f 100644 --- a/apps/files_external/lib/Migration/DummyUserSession.php +++ b/apps/files_external/lib/Migration/DummyUserSession.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Greta Doci <gretadoci@gmail.com> - * @author Joas Schilling <coding@schilljs.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @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/> - * + * 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\Migration; @@ -29,10 +12,7 @@ use OCP\IUserSession; class DummyUserSession implements IUserSession { - /** - * @var IUser - */ - private $user; + private ?IUser $user = null; public function login($uid, $password) { } @@ -44,6 +24,10 @@ class DummyUserSession implements IUserSession { $this->user = $user; } + public function setVolatileActiveUser(?IUser $user): void { + $this->user = $user; + } + public function getUser() { return $this->user; } diff --git a/apps/files_external/lib/Migration/Version1011Date20200630192246.php b/apps/files_external/lib/Migration/Version1011Date20200630192246.php index 413c84b4bab..c87b1cfbc8b 100644 --- a/apps/files_external/lib/Migration/Version1011Date20200630192246.php +++ b/apps/files_external/lib/Migration/Version1011Date20200630192246.php @@ -3,33 +3,14 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Vincent Petry <vincent@nextcloud.com> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Migration; use Closure; -use OCP\DB\Types; use OCP\DB\ISchemaWrapper; +use OCP\DB\Types; use OCP\Migration\IOutput; use OCP\Migration\SimpleMigrationStep; diff --git a/apps/files_external/lib/Migration/Version1015Date20211104103506.php b/apps/files_external/lib/Migration/Version1015Date20211104103506.php index 1b4d9ee97c9..6027c795cdf 100644 --- a/apps/files_external/lib/Migration/Version1015Date20211104103506.php +++ b/apps/files_external/lib/Migration/Version1015Date20211104103506.php @@ -3,25 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net> - * - * @author Julius Härtl <jus@bitgrid.net> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Migration; @@ -37,14 +20,10 @@ use Psr\Log\LoggerInterface; class Version1015Date20211104103506 extends SimpleMigrationStep { - /** @var IDBConnection */ - private $connection; - /** @var LoggerInterface */ - private $logger; - - public function __construct(IDBConnection $connection, LoggerInterface $logger) { - $this->connection = $connection; - $this->logger = $logger; + public function __construct( + private IDBConnection $connection, + private LoggerInterface $logger, + ) { } public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { @@ -81,7 +60,7 @@ class Version1015Date20211104103506 extends SimpleMigrationStep { /** * @throws Exception - * @return \OCP\DB\IResult|int + * @return IResult|int */ private function getS3Mounts() { $qb = $this->connection->getQueryBuilder(); diff --git a/apps/files_external/lib/Migration/Version1016Date20220324154536.php b/apps/files_external/lib/Migration/Version1016Date20220324154536.php index a2a7ecaff17..fb2cccfdd80 100644 --- a/apps/files_external/lib/Migration/Version1016Date20220324154536.php +++ b/apps/files_external/lib/Migration/Version1016Date20220324154536.php @@ -2,24 +2,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com> - * - * @author Joas Schilling <coding@schilljs.com> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Migration; diff --git a/apps/files_external/lib/Migration/Version22000Date20210216084416.php b/apps/files_external/lib/Migration/Version22000Date20210216084416.php index 1f56b7f8abb..c4878e602c0 100644 --- a/apps/files_external/lib/Migration/Version22000Date20210216084416.php +++ b/apps/files_external/lib/Migration/Version22000Date20210216084416.php @@ -3,25 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2021 Roeland Jago Douma <roeland@famdouma.nl> - * - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Migration; diff --git a/apps/files_external/lib/MountConfig.php b/apps/files_external/lib/MountConfig.php index a95ea01add9..5637ee71ec1 100644 --- a/apps/files_external/lib/MountConfig.php +++ b/apps/files_external/lib/MountConfig.php @@ -1,44 +1,13 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Andreas Fischer <bantu@owncloud.com> - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Bart Visscher <bartv@thisnet.nl> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Frank Karlitschek <frank@karlitschek.de> - * @author Jesús Macias <jmacias@solidgear.es> - * @author Joas Schilling <coding@schilljs.com> - * @author Juan Pablo Villafáñez <jvillafanez@solidgear.es> - * @author Julius Härtl <jus@bitgrid.net> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Michael Gapczynski <GapczynskiM@gmail.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Philipp Kapfer <philipp.kapfer@gmx.at> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @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/> - * + * 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; +use OC\Files\Storage\Common; use OCA\Files_External\Config\IConfigHandler; use OCA\Files_External\Config\UserContext; use OCA\Files_External\Lib\Backend\Backend; @@ -46,9 +15,15 @@ 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\AppFramework\QueryException; use OCP\Files\StorageNotAvailableException; -use OCP\IUser; +use OCP\IConfig; +use OCP\IL10N; +use OCP\Security\ISecureRandom; +use OCP\Server; +use OCP\Util; use phpseclib\Crypt\AES; +use Psr\Log\LoggerInterface; /** * Class to configure mount.json globally and for users @@ -64,33 +39,23 @@ class MountConfig { // whether to skip backend test (for unit tests, as this static class is not mockable) public static $skipTest = false; - /** @var UserGlobalStoragesService */ - private $userGlobalStorageService; - /** @var UserStoragesService */ - private $userStorageService; - /** @var GlobalStoragesService */ - private $globalStorageService; - public function __construct( - UserGlobalStoragesService $userGlobalStorageService, - UserStoragesService $userStorageService, - GlobalStoragesService $globalStorageService + private UserGlobalStoragesService $userGlobalStorageService, + private UserStoragesService $userStorageService, + private GlobalStoragesService $globalStorageService, ) { - $this->userGlobalStorageService = $userGlobalStorageService; - $this->userStorageService = $userStorageService; - $this->globalStorageService = $globalStorageService; } /** * @param mixed $input * @param string|null $userId * @return mixed - * @throws \OCP\AppFramework\QueryException + * @throws QueryException * @since 16.0.0 */ - public static function substitutePlaceholdersInConfig($input, string $userId = null) { + public static function substitutePlaceholdersInConfig($input, ?string $userId = null) { /** @var BackendService $backendService */ - $backendService = \OC::$server->get(BackendService::class); + $backendService = Server::get(BackendService::class); /** @var IConfigHandler[] $handlers */ $handlers = $backendService->getConfigHandlers(); foreach ($handlers as $handler) { @@ -111,7 +76,7 @@ class MountConfig { * @return int see self::STATUS_* * @throws \Exception */ - public static function getBackendStatus($class, $options, $isPersonal, $testOnly = true) { + public static function getBackendStatus($class, $options) { if (self::$skipTest) { return StorageNotAvailableException::STATUS_SUCCESS; } @@ -124,11 +89,11 @@ class MountConfig { } if (class_exists($class)) { try { - /** @var \OC\Files\Storage\Common $storage */ + /** @var Common $storage */ $storage = new $class($options); try { - $result = $storage->test($isPersonal, $testOnly); + $result = $storage->test(); $storage->setAvailability($result); if ($result) { return StorageNotAvailableException::STATUS_SUCCESS; @@ -138,7 +103,7 @@ class MountConfig { throw $e; } } catch (\Exception $exception) { - \OC::$server->getLogger()->logException($exception, ['app' => 'files_external']); + Server::get(LoggerInterface::class)->error($exception->getMessage(), ['exception' => $exception, 'app' => 'files_external']); throw $exception; } } @@ -150,17 +115,17 @@ class MountConfig { * TODO: move into AppFramework along with templates * * @param Backend[] $backends - * @return string */ - public static function dependencyMessage($backends) { - $l = \OC::$server->getL10N('files_external'); + public static function dependencyMessage(array $backends): string { + $l = Util::getL10N('files_external'); $message = ''; $dependencyGroups = []; foreach ($backends as $backend) { foreach ($backend->checkDependencies() as $dependency) { - if ($message = $dependency->getMessage()) { - $message .= '<p>' . $message . '</p>'; + $dependencyMessage = $dependency->getMessage(); + if ($dependencyMessage !== null) { + $message .= '<p>' . $dependencyMessage . '</p>'; } else { $dependencyGroups[$dependency->getDependency()][] = $backend; } @@ -168,7 +133,7 @@ class MountConfig { } foreach ($dependencyGroups as $module => $dependants) { - $backends = implode(', ', array_map(function ($backend) { + $backends = implode(', ', array_map(function (Backend $backend): string { return '"' . $backend->getText() . '"'; }, $dependants)); $message .= '<p>' . MountConfig::getSingleDependencyMessage($l, $module, $backends) . '</p>'; @@ -179,13 +144,8 @@ class MountConfig { /** * Returns a dependency missing message - * - * @param \OCP\IL10N $l - * @param string $module - * @param string $backend - * @return string */ - private static function getSingleDependencyMessage(\OCP\IL10N $l, $module, $backend) { + private static function getSingleDependencyMessage(IL10N $l, string $module, string $backend): string { switch (strtolower($module)) { case 'curl': return $l->t('The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it.', [$backend]); @@ -235,7 +195,7 @@ class MountConfig { */ private static function encryptPassword($password) { $cipher = self::getCipher(); - $iv = \OC::$server->getSecureRandom()->generate(16); + $iv = Server::get(ISecureRandom::class)->generate(16); $cipher->setIV($iv); return base64_encode($iv . $cipher->encrypt($password)); } @@ -262,7 +222,7 @@ class MountConfig { */ private static function getCipher() { $cipher = new AES(AES::MODE_CBC); - $cipher->setKey(\OC::$server->getConfig()->getSystemValue('passwordsalt', null)); + $cipher->setKey(Server::get(IConfig::class)->getSystemValue('passwordsalt', null)); return $cipher; } @@ -281,8 +241,8 @@ class MountConfig { 'a' => $config['authMechanism'], 'm' => $config['mountpoint'], 'o' => $config['options'], - 'p' => isset($config['priority']) ? $config['priority'] : -1, - 'mo' => isset($config['mountOptions']) ? $config['mountOptions'] : [], + 'p' => $config['priority'] ?? -1, + 'mo' => $config['mountOptions'] ?? [], ] ); return hash('md5', $data); diff --git a/apps/files_external/lib/NotFoundException.php b/apps/files_external/lib/NotFoundException.php index 8f7063cc989..411a2212513 100644 --- a/apps/files_external/lib/NotFoundException.php +++ b/apps/files_external/lib/NotFoundException.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Joas Schilling <coding@schilljs.com> - * @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/> - * + * 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; diff --git a/apps/files_external/lib/ResponseDefinitions.php b/apps/files_external/lib/ResponseDefinitions.php new file mode 100644 index 00000000000..26a0965f1fc --- /dev/null +++ b/apps/files_external/lib/ResponseDefinitions.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files_External; + +/** + * @psalm-type Files_ExternalStorageConfig = array{ + * applicableGroups?: list<string>, + * applicableUsers?: list<string>, + * authMechanism: string, + * backend: string, + * backendOptions: array<string, mixed>, + * id?: int, + * mountOptions?: array<string, mixed>, + * mountPoint: string, + * priority?: int, + * status?: int, + * statusMessage?: string, + * type: 'personal'|'system', + * userProvided: bool, + * } + * + * @psalm-type Files_ExternalMount = array{ + * name: string, + * path: string, + * type: 'dir', + * backend: string, + * scope: 'system'|'personal', + * permissions: int, + * id: int, + * class: string, + * config: Files_ExternalStorageConfig, + * } + */ +class ResponseDefinitions { +} diff --git a/apps/files_external/lib/Service/BackendService.php b/apps/files_external/lib/Service/BackendService.php index 2cfc1d48b2e..3a688ee66e6 100644 --- a/apps/files_external/lib/Service/BackendService.php +++ b/apps/files_external/lib/Service/BackendService.php @@ -1,39 +1,23 @@ <?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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Service; use OCA\Files_External\Config\IConfigHandler; +use OCA\Files_External\ConfigLexicon; 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 OCA\Files_External\Lib\MissingDependency; use OCP\EventDispatcher\GenericEvent; -use OCP\IConfig; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IAppConfig; +use OCP\Server; /** * Service class to manage backend definitions @@ -51,9 +35,6 @@ class BackendService { /** Priority constants for PriorityTrait */ public const PRIORITY_DEFAULT = 100; - /** @var IConfig */ - protected $config; - /** @var bool */ private $userMountingAllowed = true; @@ -77,21 +58,12 @@ class BackendService { private $configHandlers = []; - /** - * @param IConfig $config - */ public function __construct( - IConfig $config + protected IAppConfig $appConfig, ) { - $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', '') - ); + $this->userMountingAllowed = $appConfig->getValueBool('files_external', ConfigLexicon::ALLOW_USER_MOUNTING); + $this->userMountingBackends = explode(',', $appConfig->getValueString('files_external', ConfigLexicon::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 === ['']) { @@ -112,7 +84,7 @@ class BackendService { private function callForRegistrations() { static $eventSent = false; if (!$eventSent) { - \OC::$server->getEventDispatcher()->dispatch( + Server::get(IEventDispatcher::class)->dispatch( 'OCA\\Files_External::loadAdditionalBackends', new GenericEvent() ); @@ -217,7 +189,8 @@ class BackendService { */ public function getAvailableBackends() { return array_filter($this->getBackends(), function ($backend) { - return !$backend->checkDependencies(); + $missing = array_filter($backend->checkDependencies(), fn (MissingDependency $dependency) => !$dependency->isOptional()); + return count($missing) === 0; }); } @@ -286,8 +259,8 @@ class BackendService { * @return bool */ protected function isAllowedUserBackend(Backend $backend) { - if ($this->userMountingAllowed && - array_intersect($backend->getIdentifierAliases(), $this->userMountingBackends) + if ($this->userMountingAllowed + && array_intersect($backend->getIdentifierAliases(), $this->userMountingBackends) ) { return true; } diff --git a/apps/files_external/lib/Service/DBConfigService.php b/apps/files_external/lib/Service/DBConfigService.php index f6446eea363..41ec4512d70 100644 --- a/apps/files_external/lib/Service/DBConfigService.php +++ b/apps/files_external/lib/Service/DBConfigService.php @@ -1,29 +1,9 @@ <?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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Service; @@ -37,38 +17,21 @@ use OCP\Security\ICrypto; */ class DBConfigService { public const MOUNT_TYPE_ADMIN = 1; + public const MOUNT_TYPE_PERSONAL = 2; + /** @deprecated use MOUNT_TYPE_PERSONAL (full uppercase) instead */ 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 __construct( + private IDBConnection $connection, + private ICrypto $crypto, + ) { } - /** - * @param int $mountId - * @return array - */ - public function getMountById($mountId) { + 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') @@ -135,7 +98,7 @@ class DBConfigService { ) ) ->groupBy(['a.mount_id']); - $stmt = $query->execute(); + $stmt = $query->executeQuery(); $result = $stmt->fetchAll(); $stmt->closeCursor(); @@ -238,7 +201,7 @@ class DBConfigService { 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))); + $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_PERSONAL, IQueryBuilder::PARAM_INT))); return $this->getMountsFromQuery($query); } @@ -266,7 +229,7 @@ class DBConfigService { 'priority' => $builder->createNamedParameter($priority, IQueryBuilder::PARAM_INT), 'type' => $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT) ]); - $query->execute(); + $query->executeStatement(); return $query->getLastInsertId(); } @@ -279,19 +242,22 @@ class DBConfigService { $builder = $this->connection->getQueryBuilder(); $query = $builder->delete('external_mounts') ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))); - $query->execute(); + $query->executeStatement(); + $builder = $this->connection->getQueryBuilder(); $query = $builder->delete('external_applicable') ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))); - $query->execute(); + $query->executeStatement(); + $builder = $this->connection->getQueryBuilder(); $query = $builder->delete('external_config') ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))); - $query->execute(); + $query->executeStatement(); + $builder = $this->connection->getQueryBuilder(); $query = $builder->delete('external_options') ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))); - $query->execute(); + $query->executeStatement(); } /** @@ -305,7 +271,7 @@ class DBConfigService { ->set('mount_point', $builder->createNamedParameter($newMountPoint)) ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))); - $query->execute(); + $query->executeStatement(); } /** @@ -319,7 +285,7 @@ class DBConfigService { ->set('auth_backend', $builder->createNamedParameter($newAuthBackend)) ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))); - $query->execute(); + $query->executeStatement(); } /** @@ -345,7 +311,7 @@ class DBConfigService { ->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(); + $query->executeStatement(); } } @@ -368,7 +334,7 @@ class DBConfigService { ->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(); + $query->executeStatement(); } } @@ -397,11 +363,11 @@ class DBConfigService { $query = $query->andWhere($builder->expr()->eq('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))); } - $query->execute(); + $query->executeStatement(); } private function getMountsFromQuery(IQueryBuilder $query) { - $result = $query->execute(); + $result = $query->executeQuery(); $mounts = $result->fetchAll(); $uniqueMounts = []; foreach ($mounts as $mount) { @@ -452,7 +418,7 @@ class DBConfigService { ->from($table) ->where($builder->expr()->in('mount_id', $placeHolders)); - $result = $query->execute(); + $result = $query->executeQuery(); $rows = $result->fetchAll(); $result->closeCursor(); diff --git a/apps/files_external/lib/Service/GlobalStoragesService.php b/apps/files_external/lib/Service/GlobalStoragesService.php index 509d4fde8d9..5b1a9f41e48 100644 --- a/apps/files_external/lib/Service/GlobalStoragesService.php +++ b/apps/files_external/lib/Service/GlobalStoragesService.php @@ -1,36 +1,15 @@ <?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/> - * + * 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\Service; use OC\Files\Filesystem; use OCA\Files_External\Lib\StorageConfig; +use OCA\Files_External\MountConfig; /** * Service class to manage global external storage @@ -52,7 +31,7 @@ class GlobalStoragesService extends StoragesService { $this->triggerApplicableHooks( $signal, $storage->getMountPoint(), - \OCA\Files_External\MountConfig::MOUNT_TYPE_USER, + MountConfig::MOUNT_TYPE_USER, ['all'] ); return; @@ -61,13 +40,13 @@ class GlobalStoragesService extends StoragesService { $this->triggerApplicableHooks( $signal, $storage->getMountPoint(), - \OCA\Files_External\MountConfig::MOUNT_TYPE_USER, + MountConfig::MOUNT_TYPE_USER, $applicableUsers ); $this->triggerApplicableHooks( $signal, $storage->getMountPoint(), - \OCA\Files_External\MountConfig::MOUNT_TYPE_GROUP, + MountConfig::MOUNT_TYPE_GROUP, $applicableGroups ); } @@ -101,7 +80,7 @@ class GlobalStoragesService extends StoragesService { $this->triggerApplicableHooks( Filesystem::signal_delete_mount, $oldStorage->getMountPoint(), - \OCA\Files_External\MountConfig::MOUNT_TYPE_USER, + MountConfig::MOUNT_TYPE_USER, ['all'] ); } @@ -110,7 +89,7 @@ class GlobalStoragesService extends StoragesService { $this->triggerApplicableHooks( Filesystem::signal_delete_mount, $oldStorage->getMountPoint(), - \OCA\Files_External\MountConfig::MOUNT_TYPE_USER, + MountConfig::MOUNT_TYPE_USER, $userDeletions ); @@ -118,7 +97,7 @@ class GlobalStoragesService extends StoragesService { $this->triggerApplicableHooks( Filesystem::signal_delete_mount, $oldStorage->getMountPoint(), - \OCA\Files_External\MountConfig::MOUNT_TYPE_GROUP, + MountConfig::MOUNT_TYPE_GROUP, $groupDeletions ); @@ -126,7 +105,7 @@ class GlobalStoragesService extends StoragesService { $this->triggerApplicableHooks( Filesystem::signal_create_mount, $newStorage->getMountPoint(), - \OCA\Files_External\MountConfig::MOUNT_TYPE_USER, + MountConfig::MOUNT_TYPE_USER, $userAdditions ); @@ -134,7 +113,7 @@ class GlobalStoragesService extends StoragesService { $this->triggerApplicableHooks( Filesystem::signal_create_mount, $newStorage->getMountPoint(), - \OCA\Files_External\MountConfig::MOUNT_TYPE_GROUP, + MountConfig::MOUNT_TYPE_GROUP, $groupAdditions ); @@ -146,7 +125,7 @@ class GlobalStoragesService extends StoragesService { $this->triggerApplicableHooks( Filesystem::signal_create_mount, $newStorage->getMountPoint(), - \OCA\Files_External\MountConfig::MOUNT_TYPE_USER, + MountConfig::MOUNT_TYPE_USER, ['all'] ); } @@ -155,7 +134,7 @@ class GlobalStoragesService extends StoragesService { /** * Get the visibility type for this controller, used in validation * - * @return string BackendService::VISIBILITY_* constants + * @return int BackendService::VISIBILITY_* constants */ public function getVisibilityType() { return BackendService::VISIBILITY_ADMIN; diff --git a/apps/files_external/lib/Service/ImportLegacyStoragesService.php b/apps/files_external/lib/Service/ImportLegacyStoragesService.php index c17b6fe8fa0..7d9840e9f5e 100644 --- a/apps/files_external/lib/Service/ImportLegacyStoragesService.php +++ b/apps/files_external/lib/Service/ImportLegacyStoragesService.php @@ -1,24 +1,8 @@ <?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/> - * + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Service; diff --git a/apps/files_external/lib/Service/LegacyStoragesService.php b/apps/files_external/lib/Service/LegacyStoragesService.php index 325e6255691..9f199a89b3f 100644 --- a/apps/files_external/lib/Service/LegacyStoragesService.php +++ b/apps/files_external/lib/Service/LegacyStoragesService.php @@ -1,33 +1,16 @@ <?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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Service; use OCA\Files_External\Lib\StorageConfig; -use OCP\ILogger; +use OCA\Files_External\MountConfig; +use OCP\Server; +use Psr\Log\LoggerInterface; /** * Read mount config from legacy mount.json @@ -57,7 +40,7 @@ abstract class LegacyStoragesService { &$storageConfig, $mountType, $applicable, - $storageOptions + $storageOptions, ) { $backend = $this->backendService->getBackend($storageOptions['backend']); if (!$backend) { @@ -82,13 +65,13 @@ abstract class LegacyStoragesService { $storageOptions['priority'] = $backend->getPriority(); } $storageConfig->setPriority($storageOptions['priority']); - if ($mountType === \OCA\Files_External\MountConfig::MOUNT_TYPE_USER) { + if ($mountType === MountConfig::MOUNT_TYPE_USER) { $applicableUsers = $storageConfig->getApplicableUsers(); if ($applicable !== 'all') { $applicableUsers[] = $applicable; $storageConfig->setApplicableUsers($applicableUsers); } - } elseif ($mountType === \OCA\Files_External\MountConfig::MOUNT_TYPE_GROUP) { + } elseif ($mountType === MountConfig::MOUNT_TYPE_GROUP) { $applicableGroups = $storageConfig->getApplicableGroups(); $applicableGroups[] = $applicable; $storageConfig->setApplicableGroups($applicableGroups); @@ -143,13 +126,13 @@ abstract class LegacyStoragesService { $parts = explode('/', ltrim($rootMountPath, '/'), 3); if (count($parts) < 3) { // something went wrong, skip - \OC::$server->getLogger()->error('Could not parse mount point "' . $rootMountPath . '"', ['app' => 'files_external']); + 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']); + $storageOptions['options'] = MountConfig::decryptPasswords($storageOptions['options']); if (!isset($storageOptions['backend'])) { $storageOptions['backend'] = $storageOptions['class']; // legacy compat } @@ -167,7 +150,7 @@ abstract class LegacyStoragesService { // 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); + $configId = MountConfig::makeConfigHash($storageOptions); if (isset($storagesWithConfigHash[$configId])) { $currentStorage = $storagesWithConfigHash[$configId]; } @@ -191,10 +174,9 @@ abstract class LegacyStoragesService { } } catch (\UnexpectedValueException $e) { // don't die if a storage backend doesn't exist - \OC::$server->getLogger()->logException($e, [ - 'message' => 'Could not load storage.', - 'level' => ILogger::ERROR, + Server::get(LoggerInterface::class)->error('Could not load storage.', [ 'app' => 'files_external', + 'exception' => $e, ]); } } diff --git a/apps/files_external/lib/Service/StoragesService.php b/apps/files_external/lib/Service/StoragesService.php index 489192dbdc2..a12a8fc245a 100644 --- a/apps/files_external/lib/Service/StoragesService.php +++ b/apps/files_external/lib/Service/StoragesService.php @@ -1,37 +1,13 @@ <?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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Service; +use OC\Files\Cache\Storage; use OC\Files\Filesystem; use OCA\Files_External\Lib\Auth\AuthMechanism; use OCA\Files_External\Lib\Auth\InvalidAuth; @@ -44,44 +20,27 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\IUserMountCache; use OCP\Files\Events\InvalidateMountCacheEvent; use OCP\Files\StorageNotAvailableException; -use OCP\ILogger; +use OCP\Server; +use OCP\Util; +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 DBConfigService $dbConfig * @param IUserMountCache $userMountCache * @param IEventDispatcher $eventDispatcher */ public function __construct( - BackendService $backendService, - DBConfigService $dbConfigService, - IUserMountCache $userMountCache, - IEventDispatcher $eventDispatcher + protected BackendService $backendService, + protected DBConfigService $dbConfig, + protected IUserMountCache $userMountCache, + protected IEventDispatcher $eventDispatcher, ) { - $this->backendService = $backendService; - $this->dbConfig = $dbConfigService; - $this->userMountCache = $userMountCache; - $this->eventDispatcher = $eventDispatcher; } protected function readDBConfig() { @@ -119,17 +78,15 @@ abstract class StoragesService { return $config; } catch (\UnexpectedValueException $e) { // don't die if a storage backend doesn't exist - \OC::$server->getLogger()->logException($e, [ - 'message' => 'Could not load storage.', - 'level' => ILogger::ERROR, + Server::get(LoggerInterface::class)->error('Could not load storage.', [ 'app' => 'files_external', + 'exception' => $e, ]); return null; } catch (\InvalidArgumentException $e) { - \OC::$server->getLogger()->logException($e, [ - 'message' => 'Could not load storage.', - 'level' => ILogger::ERROR, + Server::get(LoggerInterface::class)->error('Could not load storage.', [ 'app' => 'files_external', + 'exception' => $e, ]); return null; } @@ -162,7 +119,7 @@ abstract class StoragesService { * @return StorageConfig * @throws NotFoundException if the storage with the given id was not found */ - public function getStorage($id) { + public function getStorage(int $id) { $mount = $this->dbConfig->getMountById($id); if (!is_array($mount)) { @@ -231,7 +188,7 @@ abstract class StoragesService { /** * Get the visibility type for this controller, used in validation * - * @return string BackendService::VISIBILITY_* constants + * @return int BackendService::VISIBILITY_* constants */ abstract public function getVisibilityType(); @@ -310,7 +267,7 @@ abstract class StoragesService { $mountOptions = null, $applicableUsers = null, $applicableGroups = null, - $priority = null + $priority = null, ) { $backend = $this->backendService->getBackend($backendIdentifier); if (!$backend) { @@ -345,14 +302,14 @@ abstract class StoragesService { * Triggers the given hook signal for all the applicables given * * @param string $signal signal - * @param string $mountPoint hook mount pount param + * @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( + Util::emitHook( Filesystem::CLASSNAME, $signal, [ @@ -478,7 +435,7 @@ abstract class StoragesService { * * @throws NotFoundException if no storage was found with the given id */ - public function removeStorage($id) { + public function removeStorage(int $id) { $existingMount = $this->dbConfig->getMountById($id); if (!is_array($existingMount)) { @@ -491,7 +448,7 @@ abstract class StoragesService { $this->triggerHooks($deletedStorage, Filesystem::signal_delete_mount); // delete oc_storages entries and oc_filecache - \OC\Files\Cache\Storage::cleanByMountId($id); + Storage::cleanByMountId($id); } /** @@ -510,6 +467,7 @@ abstract class StoragesService { $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; diff --git a/apps/files_external/lib/Service/UserGlobalStoragesService.php b/apps/files_external/lib/Service/UserGlobalStoragesService.php index 2eda36e9242..aaa59c85d62 100644 --- a/apps/files_external/lib/Service/UserGlobalStoragesService.php +++ b/apps/files_external/lib/Service/UserGlobalStoragesService.php @@ -1,25 +1,9 @@ <?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/> - * + * 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; @@ -37,9 +21,6 @@ use OCP\IUserSession; class UserGlobalStoragesService extends GlobalStoragesService { use UserTrait; - /** @var IGroupManager */ - protected $groupManager; - /** * @param BackendService $backendService * @param DBConfigService $dbConfig @@ -52,13 +33,12 @@ class UserGlobalStoragesService extends GlobalStoragesService { BackendService $backendService, DBConfigService $dbConfig, IUserSession $userSession, - IGroupManager $groupManager, + protected IGroupManager $groupManager, IUserMountCache $userMountCache, - IEventDispatcher $eventDispatcher + IEventDispatcher $eventDispatcher, ) { parent::__construct($backendService, $dbConfig, $userMountCache, $eventDispatcher); $this->userSession = $userSession; - $this->groupManager = $groupManager; } /** @@ -183,7 +163,7 @@ class UserGlobalStoragesService extends GlobalStoragesService { * @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) { + public function getAllStoragesForUser(?IUser $user = null) { if (is_null($user)) { $user = $this->getUser(); } diff --git a/apps/files_external/lib/Service/UserStoragesService.php b/apps/files_external/lib/Service/UserStoragesService.php index b09b37b40cc..9d4192734b6 100644 --- a/apps/files_external/lib/Service/UserStoragesService.php +++ b/apps/files_external/lib/Service/UserStoragesService.php @@ -1,37 +1,16 @@ <?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/> - * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Service; use OC\Files\Filesystem; use OCA\Files_External\Lib\StorageConfig; +use OCA\Files_External\MountConfig; use OCA\Files_External\NotFoundException; - use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\IUserMountCache; use OCP\IUserSession; @@ -57,7 +36,7 @@ class UserStoragesService extends StoragesService { DBConfigService $dbConfig, IUserSession $userSession, IUserMountCache $userMountCache, - IEventDispatcher $eventDispatcher + IEventDispatcher $eventDispatcher, ) { $this->userSession = $userSession; parent::__construct($backendService, $dbConfig, $userMountCache, $eventDispatcher); @@ -81,7 +60,7 @@ class UserStoragesService extends StoragesService { $this->triggerApplicableHooks( $signal, $storage->getMountPoint(), - \OCA\Files_External\MountConfig::MOUNT_TYPE_USER, + MountConfig::MOUNT_TYPE_USER, [$user] ); } @@ -103,7 +82,7 @@ class UserStoragesService extends StoragesService { } protected function getType() { - return DBConfigService::MOUNT_TYPE_PERSONAl; + return DBConfigService::MOUNT_TYPE_PERSONAL; } /** @@ -127,6 +106,9 @@ class UserStoragesService extends StoragesService { * @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); } @@ -134,13 +116,19 @@ class UserStoragesService extends StoragesService { /** * Get the visibility type for this controller, used in validation * - * @return string BackendService::VISIBILITY_* constants + * @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; + 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); } } diff --git a/apps/files_external/lib/Service/UserTrait.php b/apps/files_external/lib/Service/UserTrait.php index 260f20cd03b..679066283a5 100644 --- a/apps/files_external/lib/Service/UserTrait.php +++ b/apps/files_external/lib/Service/UserTrait.php @@ -1,25 +1,9 @@ <?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/> - * + * 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\Service; diff --git a/apps/files_external/lib/Settings/Admin.php b/apps/files_external/lib/Settings/Admin.php index 116738b3f42..9af0f3c61c1 100644 --- a/apps/files_external/lib/Settings/Admin.php +++ b/apps/files_external/lib/Settings/Admin.php @@ -1,29 +1,13 @@ <?php + /** - * @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Settings; use OCA\Files_External\Lib\Auth\Password\GlobalAuth; +use OCA\Files_External\MountConfig; use OCA\Files_External\Service\BackendService; use OCA\Files_External\Service\GlobalStoragesService; use OCP\AppFramework\Http\TemplateResponse; @@ -32,28 +16,12 @@ use OCP\Settings\ISettings; class Admin implements ISettings { - /** @var IManager */ - private $encryptionManager; - - /** @var GlobalStoragesService */ - private $globalStoragesService; - - /** @var BackendService */ - private $backendService; - - /** @var GlobalAuth */ - private $globalAuth; - public function __construct( - IManager $encryptionManager, - GlobalStoragesService $globalStoragesService, - BackendService $backendService, - GlobalAuth $globalAuth + private IManager $encryptionManager, + private GlobalStoragesService $globalStoragesService, + private BackendService $backendService, + private GlobalAuth $globalAuth, ) { - $this->encryptionManager = $encryptionManager; - $this->globalStoragesService = $globalStoragesService; - $this->backendService = $backendService; - $this->globalAuth = $globalAuth; } /** @@ -66,7 +34,7 @@ class Admin implements ISettings { 'storages' => $this->globalStoragesService->getStorages(), 'backends' => $this->backendService->getAvailableBackends(), 'authMechanisms' => $this->backendService->getAuthMechanisms(), - 'dependencies' => \OCA\Files_External\MountConfig::dependencyMessage($this->backendService->getBackends()), + 'dependencies' => MountConfig::dependencyMessage($this->backendService->getBackends()), 'allowUserMounting' => $this->backendService->isUserMountingAllowed(), 'globalCredentials' => $this->globalAuth->getAuth(''), 'globalCredentialsUid' => '', @@ -84,8 +52,8 @@ class Admin implements ISettings { /** * @return int whether the form should be rather on the top or bottom of - * the admin section. The forms are arranged in ascending order of the - * priority values. It is required to return a value between 0 and 100. + * the admin section. The forms are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. * * E.g.: 70 */ diff --git a/apps/files_external/lib/Settings/Personal.php b/apps/files_external/lib/Settings/Personal.php index c27cb0fce26..8478badb842 100644 --- a/apps/files_external/lib/Settings/Personal.php +++ b/apps/files_external/lib/Settings/Personal.php @@ -1,29 +1,13 @@ <?php + /** - * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Robin Appelman <robin@icewind.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Settings; use OCA\Files_External\Lib\Auth\Password\GlobalAuth; +use OCA\Files_External\MountConfig; use OCA\Files_External\Service\BackendService; use OCA\Files_External\Service\UserGlobalStoragesService; use OCP\AppFramework\Http\TemplateResponse; @@ -33,33 +17,13 @@ use OCP\Settings\ISettings; class Personal implements ISettings { - /** @var IManager */ - private $encryptionManager; - - /** @var UserGlobalStoragesService */ - private $userGlobalStoragesService; - - /** @var BackendService */ - private $backendService; - - /** @var GlobalAuth */ - private $globalAuth; - - /** @var IUserSession */ - private $userSession; - public function __construct( - IManager $encryptionManager, - UserGlobalStoragesService $userGlobalStoragesService, - BackendService $backendService, - GlobalAuth $globalAuth, - IUserSession $userSession + private IManager $encryptionManager, + private UserGlobalStoragesService $userGlobalStoragesService, + private BackendService $backendService, + private GlobalAuth $globalAuth, + private IUserSession $userSession, ) { - $this->encryptionManager = $encryptionManager; - $this->userGlobalStoragesService = $userGlobalStoragesService; - $this->backendService = $backendService; - $this->globalAuth = $globalAuth; - $this->userSession = $userSession; } /** @@ -74,7 +38,7 @@ class Personal implements ISettings { 'storages' => $this->userGlobalStoragesService->getStorages(), 'backends' => $this->backendService->getAvailableBackends(), 'authMechanisms' => $this->backendService->getAuthMechanisms(), - 'dependencies' => \OCA\Files_External\MountConfig::dependencyMessage($this->backendService->getBackends()), + 'dependencies' => MountConfig::dependencyMessage($this->backendService->getBackends()), 'allowUserMounting' => $this->backendService->isUserMountingAllowed(), 'globalCredentials' => $this->globalAuth->getAuth($uid), 'globalCredentialsUid' => $uid, @@ -92,8 +56,8 @@ class Personal implements ISettings { /** * @return int whether the form should be rather on the top or bottom of - * the admin section. The forms are arranged in ascending order of the - * priority values. It is required to return a value between 0 and 100. + * the admin section. The forms are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. * * E.g.: 70 */ diff --git a/apps/files_external/lib/Settings/PersonalSection.php b/apps/files_external/lib/Settings/PersonalSection.php index b1ca6bb16dc..c6eb1c6b889 100644 --- a/apps/files_external/lib/Settings/PersonalSection.php +++ b/apps/files_external/lib/Settings/PersonalSection.php @@ -1,25 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> - * - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Settings; @@ -30,25 +13,13 @@ use OCP\IURLGenerator; use OCP\IUserSession; class PersonalSection extends Section { - /** @var IUserSession */ - private $userSession; - - /** @var UserGlobalStoragesService */ - private $userGlobalStoragesService; - - /** @var BackendService */ - private $backendService; - public function __construct( IURLGenerator $url, IL10N $l, - IUserSession $userSession, - UserGlobalStoragesService $userGlobalStoragesService, - BackendService $backendService + private IUserSession $userSession, + private UserGlobalStoragesService $userGlobalStoragesService, + private BackendService $backendService, ) { parent::__construct($url, $l); - $this->userSession = $userSession; - $this->userGlobalStoragesService = $userGlobalStoragesService; - $this->backendService = $backendService; } } diff --git a/apps/files_external/lib/Settings/Section.php b/apps/files_external/lib/Settings/Section.php index 9123cbe26c9..cf3b73472d7 100644 --- a/apps/files_external/lib/Settings/Section.php +++ b/apps/files_external/lib/Settings/Section.php @@ -1,26 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Joas Schilling <coding@schilljs.com> - * @author szaimen <szaimen@e.mail.de> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Settings; @@ -29,18 +11,14 @@ use OCP\IURLGenerator; use OCP\Settings\IIconSection; class Section implements IIconSection { - /** @var IL10N */ - private $l; - /** @var IURLGenerator */ - private $url; - /** * @param IURLGenerator $url * @param IL10N $l */ - public function __construct(IURLGenerator $url, IL10N $l) { - $this->url = $url; - $this->l = $l; + public function __construct( + private IURLGenerator $url, + private IL10N $l, + ) { } /** @@ -65,8 +43,8 @@ class Section implements IIconSection { /** * @return int whether the form should be rather on the top or bottom of - * the settings navigation. The sections are arranged in ascending order of - * the priority values. It is required to return a value between 0 and 99. + * the settings navigation. The sections are arranged in ascending order of + * the priority values. It is required to return a value between 0 and 99. * * E.g.: 70 */ |