diff options
Diffstat (limited to 'apps/federation/lib')
-rw-r--r-- | apps/federation/lib/AppInfo/Application.php | 28 | ||||
-rw-r--r-- | apps/federation/lib/BackgroundJob/GetSharedSecret.php | 158 | ||||
-rw-r--r-- | apps/federation/lib/BackgroundJob/RequestSharedSecret.php | 140 | ||||
-rw-r--r-- | apps/federation/lib/Command/SyncFederationAddressBooks.php | 46 | ||||
-rw-r--r-- | apps/federation/lib/Controller/OCSAuthAPIController.php | 182 | ||||
-rw-r--r-- | apps/federation/lib/Controller/SettingsController.php | 167 | ||||
-rw-r--r-- | apps/federation/lib/DAV/FedAuth.php | 34 | ||||
-rw-r--r-- | apps/federation/lib/DbHandler.php | 200 | ||||
-rw-r--r-- | apps/federation/lib/Listener/SabrePluginAuthInitListener.php | 30 | ||||
-rw-r--r-- | apps/federation/lib/Middleware/AddServerMiddleware.php | 90 | ||||
-rw-r--r-- | apps/federation/lib/Migration/Version1010Date20200630191302.php | 24 | ||||
-rw-r--r-- | apps/federation/lib/Settings/Admin.php | 48 | ||||
-rw-r--r-- | apps/federation/lib/SyncFederationAddressBooks.php | 94 | ||||
-rw-r--r-- | apps/federation/lib/SyncJob.php | 60 | ||||
-rw-r--r-- | apps/federation/lib/TrustedServers.php | 212 |
15 files changed, 500 insertions, 1013 deletions
diff --git a/apps/federation/lib/AppInfo/Application.php b/apps/federation/lib/AppInfo/Application.php index d564fb4a20b..358e3f68d50 100644 --- a/apps/federation/lib/AppInfo/Application.php +++ b/apps/federation/lib/AppInfo/Application.php @@ -1,32 +1,14 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Björn Schießle <bjoern@schiessle.org> - * @author Joas Schilling <coding@schilljs.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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\Federation\AppInfo; use OCA\DAV\Events\SabrePluginAuthInitEvent; use OCA\Federation\Listener\SabrePluginAuthInitListener; -use OCA\Federation\Middleware\AddServerMiddleware; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -42,8 +24,6 @@ class Application extends App implements IBootstrap { } public function register(IRegistrationContext $context): void { - $context->registerMiddleware(AddServerMiddleware::class); - $context->registerEventListener(SabrePluginAuthInitEvent::class, SabrePluginAuthInitListener::class); } diff --git a/apps/federation/lib/BackgroundJob/GetSharedSecret.php b/apps/federation/lib/BackgroundJob/GetSharedSecret.php index 5379a837151..dc57db9fd62 100644 --- a/apps/federation/lib/BackgroundJob/GetSharedSecret.php +++ b/apps/federation/lib/BackgroundJob/GetSharedSecret.php @@ -1,31 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @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: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Federation\BackgroundJob; @@ -39,85 +17,47 @@ use OCP\BackgroundJob\Job; use OCP\Http\Client\IClient; use OCP\Http\Client\IClientService; use OCP\Http\Client\IResponse; -use OCP\ILogger; +use OCP\IConfig; use OCP\IURLGenerator; use OCP\OCS\IDiscoveryService; +use Psr\Log\LoggerInterface; /** * Class GetSharedSecret * - * request shared secret from remote Nextcloud + * Request shared secret from remote Nextcloud * * @package OCA\Federation\Backgroundjob */ class GetSharedSecret extends Job { + private IClient $httpClient; + protected bool $retainJob = false; + private string $defaultEndPoint = '/ocs/v2.php/apps/federation/api/v1/shared-secret'; + /** 30 day = 2592000sec */ + private int $maxLifespan = 2592000; - /** @var IClient */ - private $httpClient; - - /** @var IJobList */ - private $jobList; - - /** @var IURLGenerator */ - private $urlGenerator; - - /** @var TrustedServers */ - private $trustedServers; - - /** @var IDiscoveryService */ - private $ocsDiscoveryService; - - /** @var ILogger */ - private $logger; - - /** @var bool */ - protected $retainJob = false; - - private $defaultEndPoint = '/ocs/v2.php/apps/federation/api/v1/shared-secret'; - - /** @var int 30 day = 2592000sec */ - private $maxLifespan = 2592000; - - /** - * RequestSharedSecret constructor. - * - * @param IClientService $httpClientService - * @param IURLGenerator $urlGenerator - * @param IJobList $jobList - * @param TrustedServers $trustedServers - * @param ILogger $logger - * @param IDiscoveryService $ocsDiscoveryService - * @param ITimeFactory $timeFactory - */ public function __construct( IClientService $httpClientService, - IURLGenerator $urlGenerator, - IJobList $jobList, - TrustedServers $trustedServers, - ILogger $logger, - IDiscoveryService $ocsDiscoveryService, - ITimeFactory $timeFactory + private IURLGenerator $urlGenerator, + private IJobList $jobList, + private TrustedServers $trustedServers, + private LoggerInterface $logger, + private IDiscoveryService $ocsDiscoveryService, + ITimeFactory $timeFactory, + private IConfig $config, ) { parent::__construct($timeFactory); - $this->logger = $logger; $this->httpClient = $httpClientService->newClient(); - $this->jobList = $jobList; - $this->urlGenerator = $urlGenerator; - $this->ocsDiscoveryService = $ocsDiscoveryService; - $this->trustedServers = $trustedServers; } /** - * run the job, then remove it from the joblist - * - * @param IJobList $jobList - * @param ILogger|null $logger + * Run the job, then remove it from the joblist */ - public function execute(IJobList $jobList, ILogger $logger = null) { + public function start(IJobList $jobList): void { $target = $this->argument['url']; // only execute if target is still in the list of trusted domains if ($this->trustedServers->isTrustedServer($target)) { - $this->parentExecute($jobList, $logger); + $this->parentStart($jobList); } $jobList->remove($this, $this->argument); @@ -127,14 +67,8 @@ class GetSharedSecret extends Job { } } - /** - * call execute() method of parent - * - * @param IJobList $jobList - * @param ILogger $logger - */ - protected function parentExecute($jobList, $logger = null) { - parent::execute($jobList, $logger); + protected function parentStart(IJobList $jobList): void { + parent::start($jobList); } protected function run($argument) { @@ -148,13 +82,14 @@ class GetSharedSecret extends Job { // kill job after 30 days of trying $deadline = $currentTime - $this->maxLifespan; if ($created < $deadline) { + $this->logger->warning("The job to get the shared secret job is too old and gets stopped now without retention. Setting server status of '{$target}' to failure."); $this->retainJob = false; - $this->trustedServers->setServerStatus($target,TrustedServers::STATUS_FAILURE); + $this->trustedServers->setServerStatus($target, TrustedServers::STATUS_FAILURE); return; } $endPoints = $this->ocsDiscoveryService->discover($target, 'FEDERATED_SHARING'); - $endPoint = isset($endPoints['shared-secret']) ? $endPoints['shared-secret'] : $this->defaultEndPoint; + $endPoint = $endPoints['shared-secret'] ?? $this->defaultEndPoint; // make sure that we have a well formatted url $url = rtrim($target, '/') . '/' . trim($endPoint, '/'); @@ -164,14 +99,14 @@ class GetSharedSecret extends Job { $result = $this->httpClient->get( $url, [ - 'query' => - [ - 'url' => $source, - 'token' => $token, - 'format' => 'json', - ], + 'query' => [ + 'url' => $source, + 'token' => $token, + 'format' => 'json', + ], 'timeout' => 3, 'connect_timeout' => 3, + 'verify' => !$this->config->getSystemValue('sharing.federation.allowSelfSignedCertificates', false), ] ); @@ -179,20 +114,20 @@ class GetSharedSecret extends Job { } catch (ClientException $e) { $status = $e->getCode(); if ($status === Http::STATUS_FORBIDDEN) { - $this->logger->info($target . ' refused to exchange a shared secret with you.', ['app' => 'federation']); + $this->logger->info($target . ' refused to exchange a shared secret with you.'); } else { - $this->logger->info($target . ' responded with a ' . $status . ' containing: ' . $e->getMessage(), ['app' => 'federation']); + $this->logger->info($target . ' responded with a ' . $status . ' containing: ' . $e->getMessage()); } } catch (RequestException $e) { $status = -1; // There is no status code if we could not connect - $this->logger->logException($e, [ - 'message' => 'Could not connect to ' . $target, - 'level' => ILogger::INFO, - 'app' => 'federation', + $this->logger->info('Could not connect to ' . $target, [ + 'exception' => $e, ]); } catch (\Throwable $e) { $status = Http::STATUS_INTERNAL_SERVER_ERROR; - $this->logger->logException($e, ['app' => 'federation']); + $this->logger->error($e->getMessage(), [ + 'exception' => $e, + ]); } // if we received a unexpected response we try again later @@ -208,13 +143,12 @@ class GetSharedSecret extends Job { $result = json_decode($body, true); if (isset($result['ocs']['data']['sharedSecret'])) { $this->trustedServers->addSharedSecret( - $target, - $result['ocs']['data']['sharedSecret'] + $target, + $result['ocs']['data']['sharedSecret'] ); } else { $this->logger->error( - 'remote server "' . $target . '"" does not return a valid shared secret. Received data: ' . $body, - ['app' => 'federation'] + 'remote server "' . $target . '"" does not return a valid shared secret. Received data: ' . $body ); $this->trustedServers->setServerStatus($target, TrustedServers::STATUS_FAILURE); } @@ -222,13 +156,13 @@ class GetSharedSecret extends Job { } /** - * re-add background job + * Re-add background job * * @param array $argument */ - protected function reAddJob(array $argument) { + protected function reAddJob(array $argument): void { $url = $argument['url']; - $created = isset($argument['created']) ? (int)$argument['created'] : $this->time->getTime(); + $created = $argument['created'] ?? $this->time->getTime(); $token = $argument['token']; $this->jobList->add( GetSharedSecret::class, diff --git a/apps/federation/lib/BackgroundJob/RequestSharedSecret.php b/apps/federation/lib/BackgroundJob/RequestSharedSecret.php index 2080418af61..4d57d1f6aef 100644 --- a/apps/federation/lib/BackgroundJob/RequestSharedSecret.php +++ b/apps/federation/lib/BackgroundJob/RequestSharedSecret.php @@ -1,30 +1,11 @@ <?php + +declare(strict_types=1); + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @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: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Federation\BackgroundJob; @@ -37,9 +18,10 @@ use OCP\BackgroundJob\IJobList; use OCP\BackgroundJob\Job; use OCP\Http\Client\IClient; use OCP\Http\Client\IClientService; -use OCP\ILogger; +use OCP\IConfig; use OCP\IURLGenerator; use OCP\OCS\IDiscoveryService; +use Psr\Log\LoggerInterface; /** * Class RequestSharedSecret @@ -49,74 +31,38 @@ use OCP\OCS\IDiscoveryService; * @package OCA\Federation\Backgroundjob */ class RequestSharedSecret extends Job { + private IClient $httpClient; - /** @var IClient */ - private $httpClient; - - /** @var IJobList */ - private $jobList; - - /** @var IURLGenerator */ - private $urlGenerator; - - /** @var TrustedServers */ - private $trustedServers; + protected bool $retainJob = false; - /** @var IDiscoveryService */ - private $ocsDiscoveryService; + private string $defaultEndPoint = '/ocs/v2.php/apps/federation/api/v1/request-shared-secret'; - /** @var ILogger */ - private $logger; + /** @var int 30 day = 2592000sec */ + private int $maxLifespan = 2592000; - /** @var bool */ - protected $retainJob = false; - - private $defaultEndPoint = '/ocs/v2.php/apps/federation/api/v1/request-shared-secret'; - - /** @var int 30 day = 2592000sec */ - private $maxLifespan = 2592000; - - /** - * RequestSharedSecret constructor. - * - * @param IClientService $httpClientService - * @param IURLGenerator $urlGenerator - * @param IJobList $jobList - * @param TrustedServers $trustedServers - * @param IDiscoveryService $ocsDiscoveryService - * @param ILogger $logger - * @param ITimeFactory $timeFactory - */ public function __construct( IClientService $httpClientService, - IURLGenerator $urlGenerator, - IJobList $jobList, - TrustedServers $trustedServers, - IDiscoveryService $ocsDiscoveryService, - ILogger $logger, - ITimeFactory $timeFactory + private IURLGenerator $urlGenerator, + private IJobList $jobList, + private TrustedServers $trustedServers, + private IDiscoveryService $ocsDiscoveryService, + private LoggerInterface $logger, + ITimeFactory $timeFactory, + private IConfig $config, ) { parent::__construct($timeFactory); $this->httpClient = $httpClientService->newClient(); - $this->jobList = $jobList; - $this->urlGenerator = $urlGenerator; - $this->logger = $logger; - $this->ocsDiscoveryService = $ocsDiscoveryService; - $this->trustedServers = $trustedServers; } /** * run the job, then remove it from the joblist - * - * @param IJobList $jobList - * @param ILogger|null $logger */ - public function execute(IJobList $jobList, ILogger $logger = null) { + public function start(IJobList $jobList): void { $target = $this->argument['url']; // only execute if target is still in the list of trusted domains if ($this->trustedServers->isTrustedServer($target)) { - $this->parentExecute($jobList, $logger); + $this->parentStart($jobList); } $jobList->remove($this, $this->argument); @@ -127,15 +73,17 @@ class RequestSharedSecret extends Job { } /** - * call execute() method of parent - * - * @param IJobList $jobList - * @param ILogger $logger + * Call start() method of parent + * Useful for unit tests */ - protected function parentExecute($jobList, $logger) { - parent::execute($jobList, $logger); + protected function parentStart(IJobList $jobList): void { + parent::start($jobList); } + /** + * @param array $argument + * @return void + */ protected function run($argument) { $target = $argument['url']; $created = isset($argument['created']) ? (int)$argument['created'] : $this->time->getTime(); @@ -147,15 +95,16 @@ class RequestSharedSecret extends Job { // kill job after 30 days of trying $deadline = $currentTime - $this->maxLifespan; if ($created < $deadline) { + $this->logger->warning("The job to request the shared secret job is too old and gets stopped now without retention. Setting server status of '{$target}' to failure."); $this->retainJob = false; $this->trustedServers->setServerStatus($target, TrustedServers::STATUS_FAILURE); return; } $endPoints = $this->ocsDiscoveryService->discover($target, 'FEDERATED_SHARING'); - $endPoint = isset($endPoints['shared-secret']) ? $endPoints['shared-secret'] : $this->defaultEndPoint; + $endPoint = $endPoints['shared-secret'] ?? $this->defaultEndPoint; - // make sure that we have a well formated url + // make sure that we have a well formatted url $url = rtrim($target, '/') . '/' . trim($endPoint, '/'); try { @@ -169,6 +118,7 @@ class RequestSharedSecret extends Job { ], 'timeout' => 3, 'connect_timeout' => 3, + 'verify' => !$this->config->getSystemValue('sharing.federation.allowSelfSignedCertificates', false), ] ); @@ -176,22 +126,22 @@ class RequestSharedSecret extends Job { } catch (ClientException $e) { $status = $e->getCode(); if ($status === Http::STATUS_FORBIDDEN) { - $this->logger->info($target . ' refused to ask for a shared secret.', ['app' => 'federation']); + $this->logger->info($target . ' refused to ask for a shared secret.'); } else { - $this->logger->info($target . ' responded with a ' . $status . ' containing: ' . $e->getMessage(), ['app' => 'federation']); + $this->logger->info($target . ' responded with a ' . $status . ' containing: ' . $e->getMessage()); } } catch (RequestException $e) { $status = -1; // There is no status code if we could not connect - $this->logger->info('Could not connect to ' . $target, ['app' => 'federation']); + $this->logger->info('Could not connect to ' . $target); } catch (\Throwable $e) { $status = Http::STATUS_INTERNAL_SERVER_ERROR; - $this->logger->logException($e, ['app' => 'federation']); + $this->logger->error($e->getMessage(), ['exception' => $e]); } // if we received a unexpected response we try again later if ( $status !== Http::STATUS_OK - && $status !== Http::STATUS_FORBIDDEN + && ($status !== Http::STATUS_FORBIDDEN || $this->getAttempt($argument) < 5) ) { $this->retainJob = true; } @@ -199,21 +149,25 @@ class RequestSharedSecret extends Job { /** * re-add background job - * - * @param array $argument */ - protected function reAddJob(array $argument) { + protected function reAddJob(array $argument): void { $url = $argument['url']; $created = isset($argument['created']) ? (int)$argument['created'] : $this->time->getTime(); $token = $argument['token']; + $attempt = $this->getAttempt($argument) + 1; $this->jobList->add( RequestSharedSecret::class, [ 'url' => $url, 'token' => $token, - 'created' => $created + 'created' => $created, + 'attempt' => $attempt ] ); } + + protected function getAttempt(array $argument): int { + return $argument['attempt'] ?? 0; + } } diff --git a/apps/federation/lib/Command/SyncFederationAddressBooks.php b/apps/federation/lib/Command/SyncFederationAddressBooks.php index 045c3c72009..36cb99473f7 100644 --- a/apps/federation/lib/Command/SyncFederationAddressBooks.php +++ b/apps/federation/lib/Command/SyncFederationAddressBooks.php @@ -1,46 +1,23 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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\Federation\Command; +use OCA\Federation\SyncFederationAddressBooks as SyncService; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class SyncFederationAddressBooks extends Command { - - /** @var \OCA\Federation\SyncFederationAddressBooks */ - private $syncService; - - /** - * @param \OCA\Federation\SyncFederationAddressBooks $syncService - */ - public function __construct(\OCA\Federation\SyncFederationAddressBooks $syncService) { + public function __construct( + private SyncService $syncService, + ) { parent::__construct(); - - $this->syncService = $syncService; } protected function configure() { @@ -49,15 +26,10 @@ class SyncFederationAddressBooks extends Command { ->setDescription('Synchronizes addressbooks of all federated clouds'); } - /** - * @param InputInterface $input - * @param OutputInterface $output - * @return int - */ protected function execute(InputInterface $input, OutputInterface $output): int { $progress = new ProgressBar($output); $progress->start(); - $this->syncService->syncThemAll(function ($url, $ex) use ($progress, $output) { + $this->syncService->syncThemAll(function ($url, $ex) use ($progress, $output): void { if ($ex instanceof \Exception) { $output->writeln("Error while syncing $url : " . $ex->getMessage()); } else { diff --git a/apps/federation/lib/Controller/OCSAuthAPIController.php b/apps/federation/lib/Controller/OCSAuthAPIController.php index dd9b94d0027..16b401be251 100644 --- a/apps/federation/lib/Controller/OCSAuthAPIController.php +++ b/apps/federation/lib/Controller/OCSAuthAPIController.php @@ -1,43 +1,28 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @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> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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\Federation\Controller; use OCA\Federation\DbHandler; use OCA\Federation\TrustedServers; use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\BruteForceProtection; +use OCP\AppFramework\Http\Attribute\NoCSRFRequired; +use OCP\AppFramework\Http\Attribute\OpenAPI; +use OCP\AppFramework\Http\Attribute\PublicPage; +use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCS\OCSForbiddenException; use OCP\AppFramework\OCSController; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; -use OCP\ILogger; use OCP\IRequest; +use OCP\Security\Bruteforce\IThrottler; use OCP\Security\ISecureRandom; +use Psr\Log\LoggerInterface; /** * Class OCSAuthAPI @@ -46,103 +31,74 @@ use OCP\Security\ISecureRandom; * * @package OCA\Federation\Controller */ +#[OpenAPI(scope: OpenAPI::SCOPE_FEDERATION)] class OCSAuthAPIController extends OCSController { - - /** @var ISecureRandom */ - private $secureRandom; - - /** @var IJobList */ - private $jobList; - - /** @var TrustedServers */ - private $trustedServers; - - /** @var DbHandler */ - private $dbHandler; - - /** @var ILogger */ - private $logger; - - /** @var ITimeFactory */ - private $timeFactory; - - /** - * OCSAuthAPI constructor. - * - * @param string $appName - * @param IRequest $request - * @param ISecureRandom $secureRandom - * @param IJobList $jobList - * @param TrustedServers $trustedServers - * @param DbHandler $dbHandler - * @param ILogger $logger - * @param ITimeFactory $timeFactory - */ public function __construct( - $appName, + string $appName, IRequest $request, - ISecureRandom $secureRandom, - IJobList $jobList, - TrustedServers $trustedServers, - DbHandler $dbHandler, - ILogger $logger, - ITimeFactory $timeFactory + private ISecureRandom $secureRandom, + private IJobList $jobList, + private TrustedServers $trustedServers, + private DbHandler $dbHandler, + private LoggerInterface $logger, + private ITimeFactory $timeFactory, + private IThrottler $throttler, ) { parent::__construct($appName, $request); - - $this->secureRandom = $secureRandom; - $this->jobList = $jobList; - $this->trustedServers = $trustedServers; - $this->dbHandler = $dbHandler; - $this->logger = $logger; - $this->timeFactory = $timeFactory; } /** - * @NoCSRFRequired - * @PublicPage + * Request received to ask remote server for a shared secret, for legacy end-points * - * request received to ask remote server for a shared secret, for legacy end-points + * @param string $url URL of the server + * @param string $token Token of the server + * @return DataResponse<Http::STATUS_OK, list<empty>, array{}> + * @throws OCSForbiddenException Requesting shared secret is not allowed * - * @param string $url - * @param string $token - * @return Http\DataResponse - * @throws OCSForbiddenException + * 200: Shared secret requested successfully */ - public function requestSharedSecretLegacy($url, $token) { + #[NoCSRFRequired] + #[PublicPage] + #[BruteForceProtection(action: 'federationSharedSecret')] + public function requestSharedSecretLegacy(string $url, string $token): DataResponse { return $this->requestSharedSecret($url, $token); } /** - * @NoCSRFRequired - * @PublicPage + * Create shared secret and return it, for legacy end-points * - * create shared secret and return it, for legacy end-points + * @param string $url URL of the server + * @param string $token Token of the server + * @return DataResponse<Http::STATUS_OK, array{sharedSecret: string}, array{}> + * @throws OCSForbiddenException Getting shared secret is not allowed * - * @param string $url - * @param string $token - * @return Http\DataResponse - * @throws OCSForbiddenException + * 200: Shared secret returned */ - public function getSharedSecretLegacy($url, $token) { + #[NoCSRFRequired] + #[PublicPage] + #[BruteForceProtection(action: 'federationSharedSecret')] + public function getSharedSecretLegacy(string $url, string $token): DataResponse { return $this->getSharedSecret($url, $token); } /** - * @NoCSRFRequired - * @PublicPage + * Request received to ask remote server for a shared secret * - * request received to ask remote server for a shared secret + * @param string $url URL of the server + * @param string $token Token of the server + * @return DataResponse<Http::STATUS_OK, list<empty>, array{}> + * @throws OCSForbiddenException Requesting shared secret is not allowed * - * @param string $url - * @param string $token - * @return Http\DataResponse - * @throws OCSForbiddenException + * 200: Shared secret requested successfully */ - public function requestSharedSecret($url, $token) { + #[NoCSRFRequired] + #[PublicPage] + #[BruteForceProtection(action: 'federationSharedSecret')] + public function requestSharedSecret(string $url, string $token): DataResponse { if ($this->trustedServers->isTrustedServer($url) === false) { - $this->logger->error('remote server not trusted (' . $url . ') while requesting shared secret', ['app' => 'federation']); + $this->throttler->registerAttempt('federationSharedSecret', $this->request->getRemoteAddress()); + $this->logger->error('remote server not trusted (' . $url . ') while requesting shared secret'); throw new OCSForbiddenException(); } @@ -151,8 +107,7 @@ class OCSAuthAPIController extends OCSController { $localToken = $this->dbHandler->getToken($url); if (strcmp($localToken, $token) > 0) { $this->logger->info( - 'remote server (' . $url . ') presented lower token. We will initiate the exchange of the shared secret.', - ['app' => 'federation'] + 'remote server (' . $url . ') presented lower token. We will initiate the exchange of the shared secret.' ); throw new OCSForbiddenException(); } @@ -166,31 +121,34 @@ class OCSAuthAPIController extends OCSController { ] ); - return new Http\DataResponse(); + return new DataResponse(); } /** - * @NoCSRFRequired - * @PublicPage + * Create shared secret and return it * - * create shared secret and return it + * @param string $url URL of the server + * @param string $token Token of the server + * @return DataResponse<Http::STATUS_OK, array{sharedSecret: string}, array{}> + * @throws OCSForbiddenException Getting shared secret is not allowed * - * @param string $url - * @param string $token - * @return Http\DataResponse - * @throws OCSForbiddenException + * 200: Shared secret returned */ - public function getSharedSecret($url, $token) { + #[NoCSRFRequired] + #[PublicPage] + #[BruteForceProtection(action: 'federationSharedSecret')] + public function getSharedSecret(string $url, string $token): DataResponse { if ($this->trustedServers->isTrustedServer($url) === false) { - $this->logger->error('remote server not trusted (' . $url . ') while getting shared secret', ['app' => 'federation']); + $this->throttler->registerAttempt('federationSharedSecret', $this->request->getRemoteAddress()); + $this->logger->error('remote server not trusted (' . $url . ') while getting shared secret'); throw new OCSForbiddenException(); } if ($this->isValidToken($url, $token) === false) { + $this->throttler->registerAttempt('federationSharedSecret', $this->request->getRemoteAddress()); $expectedToken = $this->dbHandler->getToken($url); $this->logger->error( - 'remote server (' . $url . ') didn\'t send a valid token (got "' . $token . '" but expected "'. $expectedToken . '") while getting shared secret', - ['app' => 'federation'] + 'remote server (' . $url . ') didn\'t send a valid token (got "' . $token . '" but expected "' . $expectedToken . '") while getting shared secret' ); throw new OCSForbiddenException(); } @@ -199,12 +157,12 @@ class OCSAuthAPIController extends OCSController { $this->trustedServers->addSharedSecret($url, $sharedSecret); - return new Http\DataResponse([ + return new DataResponse([ 'sharedSecret' => $sharedSecret ]); } - protected function isValidToken($url, $token) { + protected function isValidToken(string $url, string $token): bool { $storedToken = $this->dbHandler->getToken($url); return hash_equals($storedToken, $token); } diff --git a/apps/federation/lib/Controller/SettingsController.php b/apps/federation/lib/Controller/SettingsController.php index c60a7d31d7c..27341eba815 100644 --- a/apps/federation/lib/Controller/SettingsController.php +++ b/apps/federation/lib/Controller/SettingsController.php @@ -1,114 +1,125 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Morris Jobke <hey@morrisjobke.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: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Federation\Controller; +use OCA\Federation\Settings\Admin; use OCA\Federation\TrustedServers; -use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\ApiRoute; +use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; use OCP\AppFramework\Http\DataResponse; -use OCP\HintException; +use OCP\AppFramework\OCS\OCSException; +use OCP\AppFramework\OCS\OCSNotFoundException; +use OCP\AppFramework\OCSController; use OCP\IL10N; use OCP\IRequest; +use Psr\Log\LoggerInterface; -class SettingsController extends Controller { - - /** @var IL10N */ - private $l; +class SettingsController extends OCSController { + public function __construct( + string $AppName, + IRequest $request, + private IL10N $l, + private TrustedServers $trustedServers, + private LoggerInterface $logger, + ) { + parent::__construct($AppName, $request); + } - /** @var TrustedServers */ - private $trustedServers; /** - * @param string $AppName - * @param IRequest $request - * @param IL10N $l10n - * @param TrustedServers $trustedServers + * Add server to the list of trusted Nextcloud servers + * + * @param string $url The URL of the server to add + * @return DataResponse<Http::STATUS_OK, array{id: int, message: string, url: string}, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_CONFLICT, array{message: string}, array{}> + * + * 200: Server added successfully + * 404: Server not found at the given URL + * 409: Server is already in the list of trusted servers */ - public function __construct($AppName, - IRequest $request, - IL10N $l10n, - TrustedServers $trustedServers - ) { - parent::__construct($AppName, $request); - $this->l = $l10n; - $this->trustedServers = $trustedServers; - } + #[AuthorizedAdminSetting(settings: Admin::class)] + #[ApiRoute(verb: 'POST', url: '/trusted-servers')] + public function addServer(string $url): DataResponse { + $this->checkServer(trim($url)); + // Add the server to the list of trusted servers, all is well + $id = $this->trustedServers->addServer(trim($url)); + return new DataResponse([ + 'url' => $url, + 'id' => $id, + 'message' => $this->l->t('Added to the list of trusted servers') + ]); + } /** - * Add server to the list of trusted Nextclouds. + * Add server to the list of trusted Nextcloud servers + * + * @param int $id The ID of the trusted server to remove + * @return DataResponse<Http::STATUS_OK, array{id: int}, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{message: string}, array{}> * - * @AuthorizedAdminSetting(settings=OCA\Federation\Settings\Admin) - * @param string $url - * @return DataResponse - * @throws HintException + * 200: Server removed successfully + * 404: Server not found at the given ID */ - public function addServer($url) { - $this->checkServer($url); - $id = $this->trustedServers->addServer($url); + #[AuthorizedAdminSetting(settings: Admin::class)] + #[ApiRoute(verb: 'DELETE', url: '/trusted-servers/{id}', requirements: ['id' => '\d+'])] + public function removeServer(int $id): DataResponse { + try { + $this->trustedServers->getServer($id); + } catch (\Exception $e) { + throw new OCSNotFoundException($this->l->t('No server found with ID: %s', [$id])); + } - return new DataResponse( - [ - 'url' => $url, - 'id' => $id, - 'message' => $this->l->t('Added to the list of trusted servers') - ] - ); + try { + $this->trustedServers->removeServer($id); + return new DataResponse(['id' => $id]); + } catch (\Exception $e) { + $this->logger->error($e->getMessage(), ['e' => $e]); + throw new OCSException($this->l->t('Could not remove server'), Http::STATUS_INTERNAL_SERVER_ERROR); + } } /** - * Add server to the list of trusted Nextclouds. + * List all trusted servers + * + * @return DataResponse<Http::STATUS_OK, list<array{id: int, status: int, url: string}>, array{}> * - * @AuthorizedAdminSetting(settings=OCA\Federation\Settings\Admin) - * @param int $id - * @return DataResponse + * 200: List of trusted servers */ - public function removeServer($id) { - $this->trustedServers->removeServer($id); - return new DataResponse(); + #[AuthorizedAdminSetting(settings: Admin::class)] + #[ApiRoute(verb: 'GET', url: '/trusted-servers')] + public function getServers(): DataResponse { + $servers = $this->trustedServers->getServers(); + + // obfuscate the shared secret + $servers = array_map(function ($server) { + return [ + 'url' => $server['url'], + 'id' => $server['id'], + 'status' => $server['status'], + ]; + }, $servers); + + // return the list of trusted servers + return new DataResponse($servers); } + /** * Check if the server should be added to the list of trusted servers or not. - * - * @AuthorizedAdminSetting(settings=OCA\Federation\Settings\Admin) - * @param string $url - * @return bool - * @throws HintException */ - protected function checkServer($url) { + #[AuthorizedAdminSetting(settings: Admin::class)] + protected function checkServer(string $url): void { if ($this->trustedServers->isTrustedServer($url) === true) { - $message = 'Server is already in the list of trusted servers.'; - $hint = $this->l->t('Server is already in the list of trusted servers.'); - throw new HintException($message, $hint); + throw new OCSException($this->l->t('Server is already in the list of trusted servers.'), Http::STATUS_CONFLICT); } - if ($this->trustedServers->isOwnCloudServer($url) === false) { - $message = 'No server to federate with found'; - $hint = $this->l->t('No server to federate with found'); - throw new HintException($message, $hint); + if ($this->trustedServers->isNextcloudServer($url) === false) { + throw new OCSNotFoundException($this->l->t('No server to federate with found')); } - - return true; } } diff --git a/apps/federation/lib/DAV/FedAuth.php b/apps/federation/lib/DAV/FedAuth.php index 003bd6d2485..45bf422c104 100644 --- a/apps/federation/lib/DAV/FedAuth.php +++ b/apps/federation/lib/DAV/FedAuth.php @@ -1,48 +1,32 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Joas Schilling <coding@schilljs.com> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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\Federation\DAV; use OCA\Federation\DbHandler; +use OCP\Defaults; use Sabre\DAV\Auth\Backend\AbstractBasic; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; class FedAuth extends AbstractBasic { - /** @var DbHandler */ - private $db; - /** * FedAuth constructor. * * @param DbHandler $db */ - public function __construct(DbHandler $db) { - $this->db = $db; + public function __construct( + private DbHandler $db, + ) { $this->principalPrefix = 'principals/system/'; // setup realm - $defaults = new \OCP\Defaults(); + $defaults = new Defaults(); $this->realm = $defaults->getName(); } diff --git a/apps/federation/lib/DbHandler.php b/apps/federation/lib/DbHandler.php index 1dd0d1fc1c4..877663b058a 100644 --- a/apps/federation/lib/DbHandler.php +++ b/apps/federation/lib/DbHandler.php @@ -1,33 +1,15 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Björn Schießle <bjoern@schiessle.org> - * @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 Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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\Federation; use OC\Files\Filesystem; +use OCP\DB\Exception as DBException; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\HintException; use OCP\IDBConnection; use OCP\IL10N; @@ -35,56 +17,40 @@ use OCP\IL10N; /** * Class DbHandler * - * handles all database calls for the federation app + * Handles all database calls for the federation app + * + * @todo Port to QBMapper * * @group DB * @package OCA\Federation */ class DbHandler { + private string $dbTable = 'trusted_servers'; - /** @var IDBConnection */ - private $connection; - - /** @var IL10N */ - private $IL10N; - - /** @var string */ - private $dbTable = 'trusted_servers'; - - /** - * @param IDBConnection $connection - * @param IL10N $il10n - */ public function __construct( - IDBConnection $connection, - IL10N $il10n + private IDBConnection $connection, + private IL10N $IL10N, ) { - $this->connection = $connection; - $this->IL10N = $il10n; } /** - * add server to the list of trusted servers + * Add server to the list of trusted servers * - * @param string $url - * @return int * @throws HintException */ - public function addServer($url) { + public function addServer(string $url): int { $hash = $this->hash($url); $url = rtrim($url, '/'); $query = $this->connection->getQueryBuilder(); $query->insert($this->dbTable) - ->values( - [ - 'url' => $query->createParameter('url'), - 'url_hash' => $query->createParameter('url_hash'), - ] - ) + ->values([ + 'url' => $query->createParameter('url'), + 'url_hash' => $query->createParameter('url_hash'), + ]) ->setParameter('url', $url) ->setParameter('url_hash', $hash); - $result = $query->execute(); + $result = $query->executeStatement(); if ($result) { return $query->getLastInsertId(); @@ -93,35 +59,33 @@ class DbHandler { $message = 'Internal failure, Could not add trusted server: ' . $url; $message_t = $this->IL10N->t('Could not add server'); throw new HintException($message, $message_t); + return -1; } /** - * remove server from the list of trusted servers - * - * @param int $id + * Remove server from the list of trusted servers */ - public function removeServer($id) { + public function removeServer(int $id): void { $query = $this->connection->getQueryBuilder(); $query->delete($this->dbTable) ->where($query->expr()->eq('id', $query->createParameter('id'))) ->setParameter('id', $id); - $query->execute(); + $query->executeStatement(); } /** - * get trusted server with given ID + * Get trusted server with given ID * - * @param int $id - * @return array + * @return array{id: int, url: string, url_hash: string, token: ?string, shared_secret: ?string, status: int, sync_token: ?string} * @throws \Exception */ - public function getServerById($id) { + public function getServerById(int $id): array { $query = $this->connection->getQueryBuilder(); $query->select('*')->from($this->dbTable) ->where($query->expr()->eq('id', $query->createParameter('id'))) - ->setParameter('id', $id); + ->setParameter('id', $id, IQueryBuilder::PARAM_INT); - $qResult = $query->execute(); + $qResult = $query->executeQuery(); $result = $qResult->fetchAll(); $qResult->closeCursor(); @@ -133,34 +97,32 @@ class DbHandler { } /** - * get all trusted servers + * Get all trusted servers * - * @return array + * @return list<array{id: int, url: string, url_hash: string, shared_secret: ?string, status: int, sync_token: ?string}> + * @throws DBException */ - public function getAllServer() { + public function getAllServer(): array { $query = $this->connection->getQueryBuilder(); $query->select(['url', 'url_hash', 'id', 'status', 'shared_secret', 'sync_token']) ->from($this->dbTable); - $statement = $query->execute(); + $statement = $query->executeQuery(); $result = $statement->fetchAll(); $statement->closeCursor(); return $result; } /** - * check if server already exists in the database table - * - * @param string $url - * @return bool + * Check if server already exists in the database table */ - public function serverExists($url) { + public function serverExists(string $url): bool { $hash = $this->hash($url); $query = $this->connection->getQueryBuilder(); $query->select('url') ->from($this->dbTable) ->where($query->expr()->eq('url_hash', $query->createParameter('url_hash'))) ->setParameter('url_hash', $hash); - $statement = $query->execute(); + $statement = $query->executeQuery(); $result = $statement->fetchAll(); $statement->closeCursor(); @@ -168,12 +130,9 @@ class DbHandler { } /** - * write token to database. Token is used to exchange the secret - * - * @param string $url - * @param string $token + * Write token to database. Token is used to exchange the secret */ - public function addToken($url, $token) { + public function addToken(string $url, string $token): void { $hash = $this->hash($url); $query = $this->connection->getQueryBuilder(); $query->update($this->dbTable) @@ -181,24 +140,21 @@ class DbHandler { ->where($query->expr()->eq('url_hash', $query->createParameter('url_hash'))) ->setParameter('url_hash', $hash) ->setParameter('token', $token); - $query->execute(); + $query->executeStatement(); } /** - * get token stored in database - * - * @param string $url - * @return string + * Get token stored in database * @throws \Exception */ - public function getToken($url) { + public function getToken(string $url): string { $hash = $this->hash($url); $query = $this->connection->getQueryBuilder(); $query->select('token')->from($this->dbTable) ->where($query->expr()->eq('url_hash', $query->createParameter('url_hash'))) ->setParameter('url_hash', $hash); - $statement = $query->execute(); + $statement = $query->executeQuery(); $result = $statement->fetch(); $statement->closeCursor(); @@ -210,12 +166,9 @@ class DbHandler { } /** - * add shared Secret to database - * - * @param string $url - * @param string $sharedSecret + * Add shared Secret to database */ - public function addSharedSecret($url, $sharedSecret) { + public function addSharedSecret(string $url, string $sharedSecret): void { $hash = $this->hash($url); $query = $this->connection->getQueryBuilder(); $query->update($this->dbTable) @@ -223,84 +176,68 @@ class DbHandler { ->where($query->expr()->eq('url_hash', $query->createParameter('url_hash'))) ->setParameter('url_hash', $hash) ->setParameter('sharedSecret', $sharedSecret); - $query->execute(); + $query->executeStatement(); } /** - * get shared secret from database - * - * @param string $url - * @return string + * Get shared secret from database */ - public function getSharedSecret($url) { + public function getSharedSecret(string $url): string { $hash = $this->hash($url); $query = $this->connection->getQueryBuilder(); $query->select('shared_secret')->from($this->dbTable) ->where($query->expr()->eq('url_hash', $query->createParameter('url_hash'))) ->setParameter('url_hash', $hash); - $statement = $query->execute(); + $statement = $query->executeQuery(); $result = $statement->fetch(); $statement->closeCursor(); - return $result['shared_secret']; + return (string)$result['shared_secret']; } /** - * set server status - * - * @param string $url - * @param int $status - * @param string|null $token + * Set server status */ - public function setServerStatus($url, $status, $token = null) { + public function setServerStatus(string $url, int $status, ?string $token = null): void { $hash = $this->hash($url); $query = $this->connection->getQueryBuilder(); $query->update($this->dbTable) - ->set('status', $query->createNamedParameter($status)) - ->where($query->expr()->eq('url_hash', $query->createNamedParameter($hash))); + ->set('status', $query->createNamedParameter($status)) + ->where($query->expr()->eq('url_hash', $query->createNamedParameter($hash))); if (!is_null($token)) { $query->set('sync_token', $query->createNamedParameter($token)); } - $query->execute(); + $query->executeStatement(); } /** - * get server status - * - * @param string $url - * @return int + * Get server status */ - public function getServerStatus($url) { + public function getServerStatus(string $url): int { $hash = $this->hash($url); $query = $this->connection->getQueryBuilder(); $query->select('status')->from($this->dbTable) - ->where($query->expr()->eq('url_hash', $query->createParameter('url_hash'))) - ->setParameter('url_hash', $hash); + ->where($query->expr()->eq('url_hash', $query->createParameter('url_hash'))) + ->setParameter('url_hash', $hash); - $statement = $query->execute(); + $statement = $query->executeQuery(); $result = $statement->fetch(); $statement->closeCursor(); return (int)$result['status']; } /** - * create hash from URL - * - * @param string $url - * @return string + * Create hash from URL */ - protected function hash($url) { + protected function hash(string $url): string { $normalized = $this->normalizeUrl($url); return sha1($normalized); } /** - * normalize URL, used to create the sha1 hash - * - * @param string $url - * @return string + * Normalize URL, used to create the sha1 hash */ - protected function normalizeUrl($url) { + protected function normalizeUrl(string $url): string { $normalized = $url; if (strpos($url, 'https://') === 0) { @@ -315,20 +252,15 @@ class DbHandler { return $normalized; } - /** - * @param $username - * @param $password - * @return bool - */ - public function auth($username, $password) { + public function auth(string $username, string $password): bool { if ($username !== 'system') { return false; } $query = $this->connection->getQueryBuilder(); $query->select('url')->from($this->dbTable) - ->where($query->expr()->eq('shared_secret', $query->createNamedParameter($password))); + ->where($query->expr()->eq('shared_secret', $query->createNamedParameter($password))); - $statement = $query->execute(); + $statement = $query->executeQuery(); $result = $statement->fetch(); $statement->closeCursor(); return !empty($result); diff --git a/apps/federation/lib/Listener/SabrePluginAuthInitListener.php b/apps/federation/lib/Listener/SabrePluginAuthInitListener.php index f176f21506a..514a893fb39 100644 --- a/apps/federation/lib/Listener/SabrePluginAuthInitListener.php +++ b/apps/federation/lib/Listener/SabrePluginAuthInitListener.php @@ -3,25 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2020, Morris Jobke <hey@morrisjobke.de> - * - * @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\Federation\Listener; @@ -33,13 +16,12 @@ use Sabre\DAV\Auth\Plugin; /** * @since 20.0.0 + * @template-implements IEventListener<SabrePluginAuthInitEvent> */ class SabrePluginAuthInitListener implements IEventListener { - /** @var FedAuth */ - private $fedAuth; - - public function __construct(FedAuth $fedAuth) { - $this->fedAuth = $fedAuth; + public function __construct( + private FedAuth $fedAuth, + ) { } public function handle(Event $event): void { diff --git a/apps/federation/lib/Middleware/AddServerMiddleware.php b/apps/federation/lib/Middleware/AddServerMiddleware.php deleted file mode 100644 index de6f7786679..00000000000 --- a/apps/federation/lib/Middleware/AddServerMiddleware.php +++ /dev/null @@ -1,90 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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\Federation\Middleware; - -use OCA\Federation\Controller\SettingsController; -use OCP\AppFramework\Controller; -use OCP\AppFramework\Http; -use OCP\AppFramework\Http\JSONResponse; -use OCP\AppFramework\Middleware; -use OCP\HintException; -use OCP\IL10N; -use OCP\ILogger; - -class AddServerMiddleware extends Middleware { - - /** @var string */ - protected $appName; - - /** @var IL10N */ - protected $l; - - /** @var ILogger */ - protected $logger; - - /** - * @param string $appName - * @param IL10N $l - * @param ILogger $logger - */ - public function __construct($appName, IL10N $l, ILogger $logger) { - $this->appName = $appName; - $this->l = $l; - $this->logger = $logger; - } - - /** - * Log error message and return a response which can be displayed to the user - * - * @param Controller $controller - * @param string $methodName - * @param \Exception $exception - * @return JSONResponse - * @throws \Exception - */ - public function afterException($controller, $methodName, \Exception $exception) { - if (($controller instanceof SettingsController) === false) { - throw $exception; - } - $this->logger->logException($exception, [ - 'level' => ILogger::ERROR, - 'app' => $this->appName, - ]); - if ($exception instanceof HintException) { - $message = $exception->getHint(); - } else { - $message = $exception->getMessage(); - } - - return new JSONResponse( - ['message' => $message], - Http::STATUS_BAD_REQUEST - ); - } -} diff --git a/apps/federation/lib/Migration/Version1010Date20200630191302.php b/apps/federation/lib/Migration/Version1010Date20200630191302.php index c98d7da137a..c1a7c38cfc7 100644 --- a/apps/federation/lib/Migration/Version1010Date20200630191302.php +++ b/apps/federation/lib/Migration/Version1010Date20200630191302.php @@ -3,32 +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> - * - * @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\Federation\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/federation/lib/Settings/Admin.php b/apps/federation/lib/Settings/Admin.php index 7d4e51a124c..5cf5346bb85 100644 --- a/apps/federation/lib/Settings/Admin.php +++ b/apps/federation/lib/Settings/Admin.php @@ -1,24 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2016 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: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Federation\Settings; @@ -28,22 +12,10 @@ use OCP\IL10N; use OCP\Settings\IDelegatedSettings; class Admin implements IDelegatedSettings { - - /** @var TrustedServers */ - private $trustedServers; - - /** @var IL10N */ - private $l; - - /** - * Admin constructor. - * - * @param TrustedServers $trustedServers - * @param IL10N $l - */ - public function __construct(TrustedServers $trustedServers, IL10N $l) { - $this->trustedServers = $trustedServers; - $this->l = $l; + public function __construct( + private TrustedServers $trustedServers, + private IL10N $l, + ) { } /** @@ -66,8 +38,8 @@ class Admin implements IDelegatedSettings { /** * @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 */ @@ -76,7 +48,7 @@ class Admin implements IDelegatedSettings { } public function getName(): ?string { - return $this->l->t("Trusted servers"); + return $this->l->t('Trusted servers'); } public function getAuthorizedAppConfig(): array { diff --git a/apps/federation/lib/SyncFederationAddressBooks.php b/apps/federation/lib/SyncFederationAddressBooks.php index ace5c07065a..d11f92b76ef 100644 --- a/apps/federation/lib/SyncFederationAddressBooks.php +++ b/apps/federation/lib/SyncFederationAddressBooks.php @@ -1,27 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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\Federation; @@ -29,29 +11,17 @@ use OC\OCS\DiscoveryService; use OCA\DAV\CardDAV\SyncService; use OCP\AppFramework\Http; use OCP\OCS\IDiscoveryService; +use Psr\Log\LoggerInterface; class SyncFederationAddressBooks { + private DiscoveryService $ocsDiscoveryService; - /** @var DbHandler */ - protected $dbHandler; - - /** @var SyncService */ - private $syncService; - - /** @var DiscoveryService */ - private $ocsDiscoveryService; - - /** - * @param DbHandler $dbHandler - * @param SyncService $syncService - * @param IDiscoveryService $ocsDiscoveryService - */ - public function __construct(DbHandler $dbHandler, - SyncService $syncService, - IDiscoveryService $ocsDiscoveryService + public function __construct( + protected DbHandler $dbHandler, + private SyncService $syncService, + IDiscoveryService $ocsDiscoveryService, + private LoggerInterface $logger, ) { - $this->syncService = $syncService; - $this->dbHandler = $dbHandler; $this->ocsDiscoveryService = $ocsDiscoveryService; } @@ -64,28 +34,58 @@ class SyncFederationAddressBooks { $url = $trustedServer['url']; $callback($url, null); $sharedSecret = $trustedServer['shared_secret']; - $syncToken = $trustedServer['sync_token']; + $oldSyncToken = $trustedServer['sync_token']; $endPoints = $this->ocsDiscoveryService->discover($url, 'FEDERATED_SHARING'); - $cardDavUser = isset($endPoints['carddav-user']) ? $endPoints['carddav-user'] : 'system'; + $cardDavUser = $endPoints['carddav-user'] ?? 'system'; $addressBookUrl = isset($endPoints['system-address-book']) ? trim($endPoints['system-address-book'], '/') : 'remote.php/dav/addressbooks/system/system/system'; if (is_null($sharedSecret)) { + $this->logger->debug("Shared secret for $url is null"); continue; } $targetBookId = $trustedServer['url_hash']; - $targetPrincipal = "principals/system/system"; + $targetPrincipal = 'principals/system/system'; $targetBookProperties = [ '{DAV:}displayname' => $url ]; + try { - $newToken = $this->syncService->syncRemoteAddressBook($url, $cardDavUser, $addressBookUrl, $sharedSecret, $syncToken, $targetBookId, $targetPrincipal, $targetBookProperties); - if ($newToken !== $syncToken) { - $this->dbHandler->setServerStatus($url, TrustedServers::STATUS_OK, $newToken); + $syncToken = $oldSyncToken; + + do { + [$syncToken, $truncated] = $this->syncService->syncRemoteAddressBook( + $url, + $cardDavUser, + $addressBookUrl, + $sharedSecret, + $syncToken, + $targetBookId, + $targetPrincipal, + $targetBookProperties + ); + } while ($truncated); + + if ($syncToken !== $oldSyncToken) { + $this->dbHandler->setServerStatus($url, TrustedServers::STATUS_OK, $syncToken); + } else { + $this->logger->debug("Sync Token for $url unchanged from previous sync"); + // The server status might have been changed to a failure status in previous runs. + if ($this->dbHandler->getServerStatus($url) !== TrustedServers::STATUS_OK) { + $this->dbHandler->setServerStatus($url, TrustedServers::STATUS_OK); + } } } catch (\Exception $ex) { if ($ex->getCode() === Http::STATUS_UNAUTHORIZED) { $this->dbHandler->setServerStatus($url, TrustedServers::STATUS_ACCESS_REVOKED); + $this->logger->error("Server sync for $url failed because of revoked access.", [ + 'exception' => $ex, + ]); + } else { + $this->dbHandler->setServerStatus($url, TrustedServers::STATUS_FAILURE); + $this->logger->error("Server sync for $url failed.", [ + 'exception' => $ex, + ]); } $callback($url, $ex); } diff --git a/apps/federation/lib/SyncJob.php b/apps/federation/lib/SyncJob.php index f16d08a80d8..b802dfa9308 100644 --- a/apps/federation/lib/SyncJob.php +++ b/apps/federation/lib/SyncJob.php @@ -1,59 +1,33 @@ <?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 Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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\Federation; -use OC\BackgroundJob\TimedJob; -use OCP\ILogger; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\TimedJob; +use Psr\Log\LoggerInterface; class SyncJob extends TimedJob { - - /** @var SyncFederationAddressBooks */ - protected $syncService; - - /** @var ILogger */ - protected $logger; - - /** - * @param SyncFederationAddressBooks $syncService - * @param ILogger $logger - */ - public function __construct(SyncFederationAddressBooks $syncService, ILogger $logger) { + public function __construct( + protected SyncFederationAddressBooks $syncService, + protected LoggerInterface $logger, + ITimeFactory $timeFactory, + ) { + parent::__construct($timeFactory); // Run once a day $this->setInterval(24 * 60 * 60); - $this->syncService = $syncService; - $this->logger = $logger; + $this->setTimeSensitivity(self::TIME_INSENSITIVE); } protected function run($argument) { - $this->syncService->syncThemAll(function ($url, $ex) { + $this->syncService->syncThemAll(function ($url, $ex): void { if ($ex instanceof \Exception) { - $this->logger->logException($ex, [ - 'message' => "Error while syncing $url.", - 'level' => ILogger::INFO, - 'app' => 'fed-sync', + $this->logger->error("Error while syncing $url.", [ + 'exception' => $ex, ]); } }); diff --git a/apps/federation/lib/TrustedServers.php b/apps/federation/lib/TrustedServers.php index 57b9a505499..3d15cfac448 100644 --- a/apps/federation/lib/TrustedServers.php +++ b/apps/federation/lib/TrustedServers.php @@ -1,29 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @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\Federation; @@ -31,13 +11,14 @@ use OCA\Federation\BackgroundJob\RequestSharedSecret; use OCP\AppFramework\Http; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; +use OCP\DB\Exception; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Federation\Events\TrustedServerRemovedEvent; use OCP\HintException; use OCP\Http\Client\IClientService; use OCP\IConfig; -use OCP\ILogger; use OCP\Security\ISecureRandom; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\EventDispatcher\GenericEvent; +use Psr\Log\LoggerInterface; class TrustedServers { @@ -50,67 +31,25 @@ class TrustedServers { /** remote server revoked access */ public const STATUS_ACCESS_REVOKED = 4; - /** @var dbHandler */ - private $dbHandler; - - /** @var IClientService */ - private $httpClientService; - - /** @var ILogger */ - private $logger; - - /** @var IJobList */ - private $jobList; - - /** @var ISecureRandom */ - private $secureRandom; - - /** @var IConfig */ - private $config; - - /** @var EventDispatcherInterface */ - private $dispatcher; - - /** @var ITimeFactory */ - private $timeFactory; + /** @var list<array{id: int, url: string, url_hash: string, shared_secret: ?string, status: int, sync_token: ?string}>|null */ + private ?array $trustedServersCache = null; - /** - * @param DbHandler $dbHandler - * @param IClientService $httpClientService - * @param ILogger $logger - * @param IJobList $jobList - * @param ISecureRandom $secureRandom - * @param IConfig $config - * @param EventDispatcherInterface $dispatcher - * @param ITimeFactory $timeFactory - */ public function __construct( - DbHandler $dbHandler, - IClientService $httpClientService, - ILogger $logger, - IJobList $jobList, - ISecureRandom $secureRandom, - IConfig $config, - EventDispatcherInterface $dispatcher, - ITimeFactory $timeFactory + private DbHandler $dbHandler, + private IClientService $httpClientService, + private LoggerInterface $logger, + private IJobList $jobList, + private ISecureRandom $secureRandom, + private IConfig $config, + private IEventDispatcher $dispatcher, + private ITimeFactory $timeFactory, ) { - $this->dbHandler = $dbHandler; - $this->httpClientService = $httpClientService; - $this->logger = $logger; - $this->jobList = $jobList; - $this->secureRandom = $secureRandom; - $this->config = $config; - $this->dispatcher = $dispatcher; - $this->timeFactory = $timeFactory; } /** - * add server to the list of trusted servers - * - * @param $url - * @return int server id + * Add server to the list of trusted servers */ - public function addServer($url) { + public function addServer(string $url): int { $url = $this->updateProtocol($url); $result = $this->dbHandler->addServer($url); if ($result) { @@ -130,82 +69,88 @@ class TrustedServers { } /** - * get shared secret for the given server - * - * @param string $url - * @return string + * Get shared secret for the given server */ - public function getSharedSecret($url) { + public function getSharedSecret(string $url): string { return $this->dbHandler->getSharedSecret($url); } /** - * add shared secret for the given server - * - * @param string $url - * @param $sharedSecret + * Add shared secret for the given server */ - public function addSharedSecret($url, $sharedSecret) { + public function addSharedSecret(string $url, string $sharedSecret): void { $this->dbHandler->addSharedSecret($url, $sharedSecret); } /** - * remove server from the list of trusted servers - * - * @param int $id + * Remove server from the list of trusted servers */ - public function removeServer($id) { + public function removeServer(int $id): void { $server = $this->dbHandler->getServerById($id); $this->dbHandler->removeServer($id); - $event = new GenericEvent($server['url_hash']); - $this->dispatcher->dispatch('OCP\Federation\TrustedServerEvent::remove', $event); + $this->dispatcher->dispatchTyped(new TrustedServerRemovedEvent($server['url_hash'])); + } /** - * get all trusted servers + * Get all trusted servers * - * @return array + * @return list<array{id: int, url: string, url_hash: string, shared_secret: ?string, status: int, sync_token: ?string}> + * @throws \Exception */ - public function getServers() { - return $this->dbHandler->getAllServer(); + public function getServers(): ?array { + if ($this->trustedServersCache === null) { + $this->trustedServersCache = $this->dbHandler->getAllServer(); + } + return $this->trustedServersCache; } /** - * check if given server is a trusted Nextcloud server + * Get a trusted server * - * @param string $url - * @return bool + * @return array{id: int, url: string, url_hash: string, shared_secret: ?string, status: int, sync_token: ?string} + * @throws Exception + */ + public function getServer(int $id): ?array { + if ($this->trustedServersCache === null) { + $this->trustedServersCache = $this->dbHandler->getAllServer(); + } + + foreach ($this->trustedServersCache as $server) { + if ($server['id'] === $id) { + return $server; + } + } + + throw new \Exception('No server found with ID: ' . $id); + } + + /** + * Check if given server is a trusted Nextcloud server */ - public function isTrustedServer($url) { + public function isTrustedServer(string $url): bool { return $this->dbHandler->serverExists($url); } /** - * set server status - * - * @param string $url - * @param int $status + * Set server status */ - public function setServerStatus($url, $status) { + public function setServerStatus(string $url, int $status): void { $this->dbHandler->setServerStatus($url, $status); } /** - * @param string $url - * @return int + * Get server status */ - public function getServerStatus($url) { + public function getServerStatus(string $url): int { return $this->dbHandler->getServerStatus($url); } /** - * check if URL point to a ownCloud/Nextcloud server - * - * @param string $url - * @return bool + * Check if URL point to a ownCloud/Nextcloud server */ - public function isOwnCloudServer($url) { - $isValidOwnCloud = false; + public function isNextcloudServer(string $url): bool { + $isValidNextcloud = false; $client = $this->httpClientService->newClient(); try { $result = $client->get( @@ -213,31 +158,31 @@ class TrustedServers { [ 'timeout' => 3, 'connect_timeout' => 3, + 'verify' => !$this->config->getSystemValue('sharing.federation.allowSelfSignedCertificates', false), ] ); if ($result->getStatusCode() === Http::STATUS_OK) { - $isValidOwnCloud = $this->checkOwnCloudVersion($result->getBody()); + $body = $result->getBody(); + if (is_resource($body)) { + $body = stream_get_contents($body) ?: ''; + } + $isValidNextcloud = $this->checkNextcloudVersion($body); } } catch (\Exception $e) { - \OC::$server->getLogger()->logException($e, [ - 'message' => 'No Nextcloud server.', - 'level' => ILogger::DEBUG, - 'app' => 'federation', + $this->logger->error('No Nextcloud server.', [ + 'exception' => $e, ]); return false; } - return $isValidOwnCloud; + return $isValidNextcloud; } /** - * check if ownCloud version is >= 9.0 - * - * @param $status - * @return bool + * Check if ownCloud/Nextcloud version is >= 9.0 * @throws HintException */ - protected function checkOwnCloudVersion($status) { + protected function checkNextcloudVersion(string $status): bool { $decoded = json_decode($status, true); if (!empty($decoded) && isset($decoded['version'])) { if (!version_compare($decoded['version'], '9.0.0', '>=')) { @@ -249,12 +194,9 @@ class TrustedServers { } /** - * check if the URL contain a protocol, if not add https - * - * @param string $url - * @return string + * Check if the URL contain a protocol, if not add https */ - protected function updateProtocol($url) { + protected function updateProtocol(string $url): string { if ( strpos($url, 'https://') === 0 || strpos($url, 'http://') === 0 |