aboutsummaryrefslogtreecommitdiffstats
path: root/apps/federation/lib
diff options
context:
space:
mode:
Diffstat (limited to 'apps/federation/lib')
-rw-r--r--apps/federation/lib/AppInfo/Application.php76
-rw-r--r--apps/federation/lib/BackgroundJob/GetSharedSecret.php191
-rw-r--r--apps/federation/lib/BackgroundJob/RequestSharedSecret.php184
-rw-r--r--apps/federation/lib/Command/SyncFederationAddressBooks.php50
-rw-r--r--apps/federation/lib/Controller/OCSAuthAPIController.php187
-rw-r--r--apps/federation/lib/Controller/SettingsController.php175
-rw-r--r--apps/federation/lib/DAV/FedAuth.php34
-rw-r--r--apps/federation/lib/DbHandler.php212
-rw-r--r--apps/federation/lib/Hooks.php51
-rw-r--r--apps/federation/lib/Listener/SabrePluginAuthInitListener.php38
-rw-r--r--apps/federation/lib/Middleware/AddServerMiddleware.php88
-rw-r--r--apps/federation/lib/Migration/Version1010Date20200630191302.php66
-rw-r--r--apps/federation/lib/Settings/Admin.php52
-rw-r--r--apps/federation/lib/SyncFederationAddressBooks.php96
-rw-r--r--apps/federation/lib/SyncJob.php57
-rw-r--r--apps/federation/lib/TrustedServers.php246
16 files changed, 645 insertions, 1158 deletions
diff --git a/apps/federation/lib/AppInfo/Application.php b/apps/federation/lib/AppInfo/Application.php
index ea8116e4353..358e3f68d50 100644
--- a/apps/federation/lib/AppInfo/Application.php
+++ b/apps/federation/lib/AppInfo/Application.php
@@ -1,82 +1,32 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Björn Schießle <bjoern@schiessle.org>
- * @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\AppInfo;
-use OCA\Federation\DAV\FedAuth;
-use OCA\Federation\Hooks;
-use OCA\Federation\Middleware\AddServerMiddleware;
+use OCA\DAV\Events\SabrePluginAuthInitEvent;
+use OCA\Federation\Listener\SabrePluginAuthInitListener;
use OCP\AppFramework\App;
-use OCP\SabrePluginEvent;
-use OCP\Util;
-use Sabre\DAV\Auth\Plugin;
-use Sabre\DAV\Server;
+use OCP\AppFramework\Bootstrap\IBootContext;
+use OCP\AppFramework\Bootstrap\IBootstrap;
+use OCP\AppFramework\Bootstrap\IRegistrationContext;
-class Application extends App {
+class Application extends App implements IBootstrap {
/**
* @param array $urlParams
*/
public function __construct($urlParams = []) {
parent::__construct('federation', $urlParams);
- $this->registerMiddleware();
}
- private function registerMiddleware() {
- $container = $this->getContainer();
- $container->registerAlias('AddServerMiddleware', AddServerMiddleware::class);
- $container->registerMiddleWare('AddServerMiddleware');
+ public function register(IRegistrationContext $context): void {
+ $context->registerEventListener(SabrePluginAuthInitEvent::class, SabrePluginAuthInitListener::class);
}
- /**
- * listen to federated_share_added hooks to auto-add new servers to the
- * list of trusted servers.
- */
- public function registerHooks() {
-
- $container = $this->getContainer();
- $hooksManager = $container->query(Hooks::class);
-
- Util::connectHook(
- 'OCP\Share',
- 'federated_share_added',
- $hooksManager,
- 'addServerHook'
- );
-
- $dispatcher = $container->getServer()->getEventDispatcher();
- $dispatcher->addListener('OCA\DAV\Connector\Sabre::authInit', function($event) use($container) {
- if ($event instanceof SabrePluginEvent) {
- $server = $event->getServer();
- if ($server instanceof Server) {
- $authPlugin = $server->getPlugin('auth');
- if ($authPlugin instanceof Plugin) {
- $authPlugin->addBackend($container->query(FedAuth::class));
- }
- }
- }
- });
+ public function boot(IBootContext $context): void {
}
-
}
diff --git a/apps/federation/lib/BackgroundJob/GetSharedSecret.php b/apps/federation/lib/BackgroundJob/GetSharedSecret.php
index 92bb31e369e..dc57db9fd62 100644
--- a/apps/federation/lib/BackgroundJob/GetSharedSecret.php
+++ b/apps/federation/lib/BackgroundJob/GetSharedSecret.php
@@ -1,139 +1,63 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @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 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: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
-
-
namespace OCA\Federation\BackgroundJob;
use GuzzleHttp\Exception\ClientException;
-use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
-use GuzzleHttp\Ring\Exception\RingException;
-use OC\BackgroundJob\JobList;
-use OC\BackgroundJob\Job;
-use OCA\Federation\DbHandler;
use OCA\Federation\TrustedServers;
use OCP\AppFramework\Http;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
+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 DbHandler */
- private $dbHandler;
-
- /** @var IDiscoveryService */
- private $ocsDiscoveryService;
-
- /** @var ILogger */
- private $logger;
-
- /** @var ITimeFactory */
- private $timeFactory;
-
- /** @var bool */
- protected $retainJob = false;
-
- private $format = '?format=json';
-
- 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 DbHandler $dbHandler
- * @param IDiscoveryService $ocsDiscoveryService
- * @param ITimeFactory $timeFactory
- */
public function __construct(
IClientService $httpClientService,
- IURLGenerator $urlGenerator,
- IJobList $jobList,
- TrustedServers $trustedServers,
- ILogger $logger,
- DbHandler $dbHandler,
- IDiscoveryService $ocsDiscoveryService,
- ITimeFactory $timeFactory
+ private IURLGenerator $urlGenerator,
+ private IJobList $jobList,
+ private TrustedServers $trustedServers,
+ private LoggerInterface $logger,
+ private IDiscoveryService $ocsDiscoveryService,
+ ITimeFactory $timeFactory,
+ private IConfig $config,
) {
- $this->logger = $logger;
+ parent::__construct($timeFactory);
$this->httpClient = $httpClientService->newClient();
- $this->jobList = $jobList;
- $this->urlGenerator = $urlGenerator;
- $this->dbHandler = $dbHandler;
- $this->ocsDiscoveryService = $ocsDiscoveryService;
- $this->trustedServers = $trustedServers;
- $this->timeFactory = $timeFactory;
}
/**
- * run the job, then remove it from the joblist
- *
- * @param JobList $jobList
- * @param ILogger|null $logger
+ * Run the job, then remove it from the joblist
*/
- public function execute($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);
@@ -143,20 +67,14 @@ class GetSharedSecret extends Job {
}
}
- /**
- * call execute() method of parent
- *
- * @param JobList $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) {
$target = $argument['url'];
- $created = isset($argument['created']) ? (int)$argument['created'] : $this->timeFactory->getTime();
- $currentTime = $this->timeFactory->getTime();
+ $created = isset($argument['created']) ? (int)$argument['created'] : $this->time->getTime();
+ $currentTime = $this->time->getTime();
$source = $this->urlGenerator->getAbsoluteURL('/');
$source = rtrim($source, '/');
$token = $argument['token'];
@@ -164,50 +82,52 @@ 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, '/') . $this->format;
+ $url = rtrim($target, '/') . '/' . trim($endPoint, '/');
$result = null;
try {
$result = $this->httpClient->get(
$url,
[
- 'query' =>
- [
- 'url' => $source,
- 'token' => $token
- ],
+ 'query' => [
+ 'url' => $source,
+ 'token' => $token,
+ 'format' => 'json',
+ ],
'timeout' => 3,
'connect_timeout' => 3,
+ 'verify' => !$this->config->getSystemValue('sharing.federation.allowSelfSignedCertificates', false),
]
);
$status = $result->getStatusCode();
-
} 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->info('Could not connect to ' . $target, ['app' => 'federation']);
- } catch (RingException $e) {
- $status = -1; // There is no status code if we could not connect
- $this->logger->info('Could not connect to ' . $target, ['app' => 'federation']);
- } catch (\Exception $e) {
+ $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
@@ -216,9 +136,6 @@ class GetSharedSecret extends Job {
&& $status !== Http::STATUS_FORBIDDEN
) {
$this->retainJob = true;
- } else {
- // reset token if we received a valid response
- $this->dbHandler->addToken($target, '');
}
if ($status === Http::STATUS_OK && $result instanceof IResponse) {
@@ -226,28 +143,26 @@ 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',
- ['app' => 'federation']
+ 'remote server "' . $target . '"" does not return a valid shared secret. Received data: ' . $body
);
$this->trustedServers->setServerStatus($target, TrustedServers::STATUS_FAILURE);
}
}
-
}
/**
- * 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->timeFactory->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 ad7504da7ad..4d57d1f6aef 100644
--- a/apps/federation/lib/BackgroundJob/RequestSharedSecret.php
+++ b/apps/federation/lib/BackgroundJob/RequestSharedSecret.php
@@ -1,52 +1,27 @@
<?php
-/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @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 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/>
- *
- */
+declare(strict_types=1);
+/**
+ * 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;
-
use GuzzleHttp\Exception\ClientException;
-use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
-use GuzzleHttp\Ring\Exception\RingException;
-use OC\BackgroundJob\JobList;
-use OC\BackgroundJob\Job;
-use OCA\Federation\DbHandler;
use OCA\Federation\TrustedServers;
use OCP\AppFramework\Http;
use OCP\AppFramework\Utility\ITimeFactory;
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
@@ -56,85 +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 DbHandler */
- private $dbHandler;
-
- /** @var TrustedServers */
- private $trustedServers;
-
- /** @var IDiscoveryService */
- private $ocsDiscoveryService;
-
- /** @var ILogger */
- private $logger;
+ protected bool $retainJob = false;
- /** @var ITimeFactory */
- private $timeFactory;
+ private string $defaultEndPoint = '/ocs/v2.php/apps/federation/api/v1/request-shared-secret';
- /** @var bool */
- protected $retainJob = false;
+ /** @var int 30 day = 2592000sec */
+ private int $maxLifespan = 2592000;
- private $format = '?format=json';
-
- 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 DbHandler $dbHandler
- * @param IDiscoveryService $ocsDiscoveryService
- * @param ILogger $logger
- * @param ITimeFactory $timeFactory
- */
public function __construct(
IClientService $httpClientService,
- IURLGenerator $urlGenerator,
- IJobList $jobList,
- TrustedServers $trustedServers,
- DbHandler $dbHandler,
- 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->dbHandler = $dbHandler;
- $this->logger = $logger;
- $this->ocsDiscoveryService = $ocsDiscoveryService;
- $this->trustedServers = $trustedServers;
- $this->timeFactory = $timeFactory;
}
/**
* run the job, then remove it from the joblist
- *
- * @param JobList $jobList
- * @param ILogger|null $logger
*/
- public function execute($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);
@@ -145,20 +73,21 @@ class RequestSharedSecret extends Job {
}
/**
- * call execute() method of parent
- *
- * @param JobList $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->timeFactory->getTime();
- $currentTime = $this->timeFactory->getTime();
+ $created = isset($argument['created']) ? (int)$argument['created'] : $this->time->getTime();
+ $currentTime = $this->time->getTime();
$source = $this->urlGenerator->getAbsoluteURL('/');
$source = rtrim($source, '/');
$token = $argument['token'];
@@ -166,16 +95,17 @@ 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
- $url = rtrim($target, '/') . '/' . trim($endPoint, '/') . $this->format;
+ // make sure that we have a well formatted url
+ $url = rtrim($target, '/') . '/' . trim($endPoint, '/');
try {
$result = $this->httpClient->post(
@@ -184,64 +114,60 @@ class RequestSharedSecret extends Job {
'body' => [
'url' => $source,
'token' => $token,
+ 'format' => 'json',
],
'timeout' => 3,
'connect_timeout' => 3,
+ 'verify' => !$this->config->getSystemValue('sharing.federation.allowSelfSignedCertificates', false),
]
);
$status = $result->getStatusCode();
-
} 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']);
- } catch (RingException $e) {
- $status = -1; // There is no status code if we could not connect
- $this->logger->info('Could not connect to ' . $target, ['app' => 'federation']);
- } catch (\Exception $e) {
+ $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;
}
-
- if ($status === Http::STATUS_FORBIDDEN) {
- // clear token if remote server refuses to ask for shared secret
- $this->dbHandler->addToken($target, '');
- }
-
}
/**
* 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->timeFactory->getTime();
+ $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 3b1f95b0502..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 Björn Schießle <bjoern@schiessle.org>
- * @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,19 +26,12 @@ 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) {
-
+ 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 {
$progress->advance();
}
diff --git a/apps/federation/lib/Controller/OCSAuthAPIController.php b/apps/federation/lib/Controller/OCSAuthAPIController.php
index a1284a4e3ad..16b401be251 100644
--- a/apps/federation/lib/Controller/OCSAuthAPIController.php
+++ b/apps/federation/lib/Controller/OCSAuthAPIController.php
@@ -1,44 +1,28 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Björn Schießle <bjoern@schiessle.org>
- * @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
@@ -47,103 +31,74 @@ use OCP\Security\ISecureRandom;
*
* @package OCA\Federation\Controller
*/
-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
- */
+#[OpenAPI(scope: OpenAPI::SCOPE_FEDERATION)]
+class OCSAuthAPIController extends OCSController {
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();
}
@@ -152,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();
}
@@ -167,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,15 +156,13 @@ class OCSAuthAPIController extends OCSController{
$sharedSecret = $this->secureRandom->generate(32);
$this->trustedServers->addSharedSecret($url, $sharedSecret);
- // reset token after the exchange of the shared secret was successful
- $this->dbHandler->addToken($url, '');
- 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 6e64200dc8c..27341eba815 100644
--- a/apps/federation/lib/Controller/SettingsController.php
+++ b/apps/federation/lib/Controller/SettingsController.php
@@ -1,124 +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 OC\HintException;
+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\AppFramework\OCS\OCSException;
+use OCP\AppFramework\OCS\OCSNotFoundException;
+use OCP\AppFramework\OCSController;
use OCP\IL10N;
use OCP\IRequest;
-
-
-class SettingsController extends Controller {
-
- /** @var IL10N */
- private $l;
-
- /** @var TrustedServers */
- private $trustedServers;
-
- /**
- * @param string $AppName
- * @param IRequest $request
- * @param IL10N $l10n
- * @param TrustedServers $trustedServers
- */
- public function __construct($AppName,
- IRequest $request,
- IL10N $l10n,
- TrustedServers $trustedServers
+use Psr\Log\LoggerInterface;
+
+class SettingsController extends OCSController {
+ public function __construct(
+ string $AppName,
+ IRequest $request,
+ private IL10N $l,
+ private TrustedServers $trustedServers,
+ private LoggerInterface $logger,
) {
parent::__construct($AppName, $request);
- $this->l = $l10n;
- $this->trustedServers = $trustedServers;
}
/**
- * add server to the list of trusted Nextclouds
+ * 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{}>
*
- * @param string $url
- * @return DataResponse
- * @throws HintException
+ * 200: Server added successfully
+ * 404: Server not found at the given URL
+ * 409: Server is already in the list of trusted servers
*/
- public function addServer($url) {
- $this->checkServer($url);
- $id = $this->trustedServers->addServer($url);
-
- return new DataResponse(
- [
- 'url' => $url,
- 'id' => $id,
- 'message' => (string) $this->l->t('Added to the list of trusted servers')
- ]
- );
+ #[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{}>
*
- * @param int $id
- * @return DataResponse
+ * 200: Server removed successfully
+ * 404: Server not found at the given ID
*/
- public function removeServer($id) {
- $this->trustedServers->removeServer($id);
- return new DataResponse();
+ #[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]));
+ }
+
+ 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);
+ }
}
/**
- * enable/disable to automatically add servers to the list of trusted servers
- * once a federated share was created and accepted successfully
+ * List all trusted servers
*
- * @param bool $autoAddServers
+ * @return DataResponse<Http::STATUS_OK, list<array{id: int, status: int, url: string}>, array{}>
+ *
+ * 200: List of trusted servers
*/
- public function autoAddServers($autoAddServers) {
- $this->trustedServers->setAutoAddServers($autoAddServers);
+ #[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
- *
- * @param string $url
- * @return bool
- * @throws HintException
+ * Check if the server should be added to the list of trusted servers or not.
*/
- 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 511888f7683..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 abfb4c2f1b9..877663b058a 100644
--- a/apps/federation/lib/DbHandler.php
+++ b/apps/federation/lib/DbHandler.php
@@ -1,128 +1,93 @@
<?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 Robin Appelman <robin@icewind.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 OC\HintException;
+use OCP\DB\Exception as DBException;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\HintException;
use OCP\IDBConnection;
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 (int)$this->connection->lastInsertId('*PREFIX*'.$this->dbTable);
+ return $query->getLastInsertId();
}
$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);
- $query->execute();
- $result = $query->execute()->fetchAll();
+ ->setParameter('id', $id, IQueryBuilder::PARAM_INT);
+
+ $qResult = $query->executeQuery();
+ $result = $qResult->fetchAll();
+ $qResult->closeCursor();
if (empty($result)) {
throw new \Exception('No Server found with ID: ' . $id);
@@ -132,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();
@@ -167,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)
@@ -180,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();
@@ -209,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)
@@ -222,89 +176,73 @@ 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) {
$normalized = substr($url, strlen('https://'));
- } else if (strpos($url, 'http://') === 0) {
+ } elseif (strpos($url, 'http://') === 0) {
$normalized = substr($url, strlen('http://'));
}
@@ -314,23 +252,17 @@ 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/Hooks.php b/apps/federation/lib/Hooks.php
deleted file mode 100644
index 6601b9c0eb7..00000000000
--- a/apps/federation/lib/Hooks.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Björn Schießle <bjoern@schiessle.org>
- *
- * @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;
-
-
-
-class Hooks {
-
- /** @var TrustedServers */
- private $trustedServers;
-
- public function __construct(TrustedServers $trustedServers) {
- $this->trustedServers = $trustedServers;
- }
-
- /**
- * add servers to the list of trusted servers once a federated share was established
- *
- * @param array $params
- */
- public function addServerHook($params) {
- if (
- $this->trustedServers->getAutoAddServers() === true &&
- $this->trustedServers->isTrustedServer($params['server']) === false
- ) {
- $this->trustedServers->addServer($params['server']);
- }
- }
-
-}
diff --git a/apps/federation/lib/Listener/SabrePluginAuthInitListener.php b/apps/federation/lib/Listener/SabrePluginAuthInitListener.php
new file mode 100644
index 00000000000..514a893fb39
--- /dev/null
+++ b/apps/federation/lib/Listener/SabrePluginAuthInitListener.php
@@ -0,0 +1,38 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Federation\Listener;
+
+use OCA\DAV\Events\SabrePluginAuthInitEvent;
+use OCA\Federation\DAV\FedAuth;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use Sabre\DAV\Auth\Plugin;
+
+/**
+ * @since 20.0.0
+ * @template-implements IEventListener<SabrePluginAuthInitEvent>
+ */
+class SabrePluginAuthInitListener implements IEventListener {
+ public function __construct(
+ private FedAuth $fedAuth,
+ ) {
+ }
+
+ public function handle(Event $event): void {
+ if (!($event instanceof SabrePluginAuthInitEvent)) {
+ return;
+ }
+
+ $server = $event->getServer();
+ $authPlugin = $server->getPlugin('auth');
+ if ($authPlugin instanceof Plugin) {
+ $authPlugin->addBackend($this->fedAuth);
+ }
+ }
+}
diff --git a/apps/federation/lib/Middleware/AddServerMiddleware.php b/apps/federation/lib/Middleware/AddServerMiddleware.php
deleted file mode 100644
index 247cc958833..00000000000
--- a/apps/federation/lib/Middleware/AddServerMiddleware.php
+++ /dev/null
@@ -1,88 +0,0 @@
-<?php
-/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @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 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 OC\HintException;
-use OCA\Federation\Controller\SettingsController;
-use OCP\AppFramework\Controller;
-use OCP\AppFramework\Http;
-use OCP\AppFramework\Http\JSONResponse;
-use OCP\AppFramework\Middleware;
-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->error($exception->getMessage(), ['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
new file mode 100644
index 00000000000..c1a7c38cfc7
--- /dev/null
+++ b/apps/federation/lib/Migration/Version1010Date20200630191302.php
@@ -0,0 +1,66 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * 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\ISchemaWrapper;
+use OCP\DB\Types;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version1010Date20200630191302 extends SimpleMigrationStep {
+
+ /**
+ * @param IOutput $output
+ * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param array $options
+ * @return null|ISchemaWrapper
+ */
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ if (!$schema->hasTable('trusted_servers')) {
+ $table = $schema->createTable('trusted_servers');
+ $table->addColumn('id', Types::INTEGER, [
+ 'autoincrement' => true,
+ 'notnull' => true,
+ 'length' => 4,
+ ]);
+ $table->addColumn('url', Types::STRING, [
+ 'notnull' => true,
+ 'length' => 512,
+ ]);
+ $table->addColumn('url_hash', Types::STRING, [
+ 'notnull' => true,
+ 'default' => '',
+ ]);
+ $table->addColumn('token', Types::STRING, [
+ 'notnull' => false,
+ 'length' => 128,
+ ]);
+ $table->addColumn('shared_secret', Types::STRING, [
+ 'notnull' => false,
+ 'length' => 256,
+ ]);
+ $table->addColumn('status', Types::INTEGER, [
+ 'notnull' => true,
+ 'length' => 4,
+ 'default' => 2,
+ ]);
+ $table->addColumn('sync_token', Types::STRING, [
+ 'notnull' => false,
+ 'length' => 512,
+ ]);
+ $table->setPrimaryKey(['id']);
+ $table->addUniqueIndex(['url_hash'], 'url_hash');
+ }
+ return $schema;
+ }
+}
diff --git a/apps/federation/lib/Settings/Admin.php b/apps/federation/lib/Settings/Admin.php
index eccb3237f7d..5cf5346bb85 100644
--- a/apps/federation/lib/Settings/Admin.php
+++ b/apps/federation/lib/Settings/Admin.php
@@ -1,39 +1,21 @@
<?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;
use OCA\Federation\TrustedServers;
use OCP\AppFramework\Http\TemplateResponse;
-use OCP\Settings\ISettings;
-
-class Admin implements ISettings {
-
- /** @var TrustedServers */
- private $trustedServers;
-
- public function __construct(TrustedServers $trustedServers) {
- $this->trustedServers = $trustedServers;
+use OCP\IL10N;
+use OCP\Settings\IDelegatedSettings;
+
+class Admin implements IDelegatedSettings {
+ public function __construct(
+ private TrustedServers $trustedServers,
+ private IL10N $l,
+ ) {
}
/**
@@ -42,7 +24,6 @@ class Admin implements ISettings {
public function getForm() {
$parameters = [
'trustedServers' => $this->trustedServers->getServers(),
- 'autoAddServers' => $this->trustedServers->getAutoAddServers(),
];
return new TemplateResponse('federation', 'settings-admin', $parameters, '');
@@ -57,8 +38,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
*/
@@ -66,4 +47,11 @@ class Admin implements ISettings {
return 30;
}
+ public function getName(): ?string {
+ return $this->l->t('Trusted servers');
+ }
+
+ public function getAuthorizedAppConfig(): array {
+ return []; // Handled by custom controller
+ }
}
diff --git a/apps/federation/lib/SyncFederationAddressBooks.php b/apps/federation/lib/SyncFederationAddressBooks.php
index b5cd9a574c4..d11f92b76ef 100644
--- a/apps/federation/lib/SyncFederationAddressBooks.php
+++ b/apps/federation/lib/SyncFederationAddressBooks.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Björn Schießle <bjoern@schiessle.org>
- * @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;
@@ -28,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;
}
@@ -58,34 +29,63 @@ class SyncFederationAddressBooks {
* @param \Closure $callback
*/
public function syncThemAll(\Closure $callback) {
-
$trustedServers = $this->dbHandler->getAllServer();
foreach ($trustedServers as $trustedServer) {
$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
+ '{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 0aa1d61affb..b802dfa9308 100644
--- a/apps/federation/lib/SyncJob.php
+++ b/apps/federation/lib/SyncJob.php
@@ -1,55 +1,34 @@
<?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: 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 OCA\Federation\AppInfo\Application;
-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->error("Error while syncing $url : " . $ex->getMessage(), ['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 79cf86ab67b..3d15cfac448 100644
--- a/apps/federation/lib/TrustedServers.php
+++ b/apps/federation/lib/TrustedServers.php
@@ -1,122 +1,62 @@
<?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>
- * @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\HintException;
+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 {
/** after a user list was exchanged at least once successfully */
- const STATUS_OK = 1;
+ public const STATUS_OK = 1;
/** waiting for shared secret or initial user list exchange */
- const STATUS_PENDING = 2;
+ public const STATUS_PENDING = 2;
/** something went wrong, misconfigured server, software bug,... user interaction needed */
- const STATUS_FAILURE = 3;
+ public const STATUS_FAILURE = 3;
/** remote server revoked access */
- const STATUS_ACCESS_REVOKED = 4;
-
- /** @var dbHandler */
- private $dbHandler;
-
- /** @var IClientService */
- private $httpClientService;
-
- /** @var ILogger */
- private $logger;
+ public const STATUS_ACCESS_REVOKED = 4;
- /** @var IJobList */
- private $jobList;
+ /** @var list<array{id: int, url: string, url_hash: string, shared_secret: ?string, status: int, sync_token: ?string}>|null */
+ private ?array $trustedServersCache = null;
- /** @var ISecureRandom */
- private $secureRandom;
-
- /** @var IConfig */
- private $config;
-
- /** @var EventDispatcherInterface */
- private $dispatcher;
-
- /** @var ITimeFactory */
- private $timeFactory;
-
- /**
- * @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) {
$token = $this->secureRandom->generate(16);
$this->dbHandler->addToken($url, $token);
$this->jobList->add(
- 'OCA\Federation\BackgroundJob\RequestSharedSecret',
+ RequestSharedSecret::class,
[
'url' => $url,
'token' => $token,
@@ -129,104 +69,88 @@ class TrustedServers {
}
/**
- * enable/disable to automatically add servers to the list of trusted servers
- * once a federated share was created and accepted successfully
- *
- * @param bool $status
+ * Get shared secret for the given server
*/
- public function setAutoAddServers($status) {
- $value = $status ? '1' : '0';
- $this->config->setAppValue('federation', 'autoAddServers', $value);
+ public function getSharedSecret(string $url): string {
+ return $this->dbHandler->getSharedSecret($url);
}
/**
- * return if we automatically add servers to the list of trusted servers
- * once a federated share was created and accepted successfully
- *
- * @return bool
+ * Add shared secret for the given server
*/
- public function getAutoAddServers() {
- $value = $this->config->getAppValue('federation', 'autoAddServers', '0');
- return $value === '1';
+ public function addSharedSecret(string $url, string $sharedSecret): void {
+ $this->dbHandler->addSharedSecret($url, $sharedSecret);
}
/**
- * get shared secret for the given server
- *
- * @param string $url
- * @return string
+ * Remove server from the list of trusted servers
*/
- public function getSharedSecret($url) {
- return $this->dbHandler->getSharedSecret($url);
- }
+ public function removeServer(int $id): void {
+ $server = $this->dbHandler->getServerById($id);
+ $this->dbHandler->removeServer($id);
+ $this->dispatcher->dispatchTyped(new TrustedServerRemovedEvent($server['url_hash']));
- /**
- * add shared secret for the given server
- *
- * @param string $url
- * @param $sharedSecret
- */
- public function addSharedSecret($url, $sharedSecret) {
- $this->dbHandler->addSharedSecret($url, $sharedSecret);
}
/**
- * remove server from the list of trusted servers
+ * Get all trusted servers
*
- * @param int $id
+ * @return list<array{id: int, url: string, url_hash: string, shared_secret: ?string, status: int, sync_token: ?string}>
+ * @throws \Exception
*/
- public function removeServer($id) {
- $server = $this->dbHandler->getServerById($id);
- $this->dbHandler->removeServer($id);
- $event = new GenericEvent($server['url_hash']);
- $this->dispatcher->dispatch('OCP\Federation\TrustedServerEvent::remove', $event);
+ public function getServers(): ?array {
+ if ($this->trustedServersCache === null) {
+ $this->trustedServersCache = $this->dbHandler->getAllServer();
+ }
+ return $this->trustedServersCache;
}
/**
- * get all trusted servers
+ * Get a trusted server
*
- * @return array
+ * @return 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 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
- *
- * @param string $url
- * @return bool
+ * 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(
@@ -234,28 +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) {
- $this->logger->debug('No Nextcloud server: ' . $e->getMessage());
+ $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', '>=')) {
@@ -267,19 +194,14 @@ 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
) {
-
return $url;
-
}
return 'https://' . $url;