aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/cloud_federation_api/lib/Controller/RequestHandlerController.php69
-rw-r--r--apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php35
-rw-r--r--lib/composer/composer/autoload_classmap.php1
-rw-r--r--lib/composer/composer/autoload_static.php1
-rw-r--r--lib/private/OCM/OCMDiscoveryService.php3
-rw-r--r--lib/private/Security/Signature/SignatureManager.php3
-rw-r--r--lib/unstable/Federation/ISignedCloudFederationProvider.php33
-rw-r--r--lib/unstable/Security/Signature/Model/Signatory.php4
8 files changed, 109 insertions, 40 deletions
diff --git a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php
index a243d286c71..86af7924e6f 100644
--- a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php
+++ b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php
@@ -5,6 +5,7 @@
*/
namespace OCA\CloudFederationAPI\Controller;
+use NCU\Federation\ISignedCloudFederationProvider;
use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
use NCU\Security\Signature\Exceptions\IncomingRequestException;
use NCU\Security\Signature\Exceptions\SignatoryNotFoundException;
@@ -37,8 +38,6 @@ use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\Share\Exceptions\ShareNotFound;
-use OCP\Share\IProviderFactory;
-use OCP\Share\IShare;
use OCP\Util;
use Psr\Log\LoggerInterface;
@@ -68,7 +67,6 @@ class RequestHandlerController extends Controller {
private ICloudIdManager $cloudIdManager,
private readonly ISignatureManager $signatureManager,
private readonly OCMSignatoryManager $signatoryManager,
- private readonly IProviderFactory $shareProviderFactory,
) {
parent::__construct($appName, $request);
}
@@ -234,16 +232,6 @@ class RequestHandlerController extends Controller {
#[PublicPage]
#[BruteForceProtection(action: 'receiveFederatedShareNotification')]
public function receiveNotification($notificationType, $resourceType, $providerId, ?array $notification) {
- try {
- // if request is signed and well signed, no exception are thrown
- // if request is not signed and host is known for not supporting signed request, no exception are thrown
- $signedRequest = $this->getSignedRequest();
- $this->confirmShareOrigin($signedRequest, $notification['sharedSecret'] ?? '');
- } catch (IncomingRequestException $e) {
- $this->logger->warning('incoming request exception', ['exception' => $e]);
- return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
- }
-
// check if all required parameters are set
if ($notificationType === null ||
$resourceType === null ||
@@ -260,6 +248,16 @@ class RequestHandlerController extends Controller {
}
try {
+ // if request is signed and well signed, no exception are thrown
+ // if request is not signed and host is known for not supporting signed request, no exception are thrown
+ $signedRequest = $this->getSignedRequest();
+ $this->confirmNotificationIdentity($signedRequest, $resourceType, $notification);
+ } catch (IncomingRequestException $e) {
+ $this->logger->warning('incoming request exception', ['exception' => $e]);
+ return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
+ }
+
+ try {
$provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
$result = $provider->notificationReceived($notificationType, $providerId, $notification);
} catch (ProviderDoesNotExistsException $e) {
@@ -387,42 +385,45 @@ class RequestHandlerController extends Controller {
}
}
-
/**
- * confirm that the value related to share token is in format userid@hostname
- * and compare hostname with the origin of the signed request.
+ * confirm identity of the remote instance on notification, based on the share token.
*
* If request is not signed, we still verify that the hostname from the extracted value does,
* actually, not support signed request
*
* @param IIncomingSignedRequest|null $signedRequest
- * @param string $token
+ * @param string $resourceType
+ * @param string $sharedSecret
*
* @throws IncomingRequestException
+ * @throws BadRequestException
*/
- private function confirmShareOrigin(?IIncomingSignedRequest $signedRequest, string $token): void {
- if ($token === '') {
+ private function confirmNotificationIdentity(
+ ?IIncomingSignedRequest $signedRequest,
+ string $resourceType,
+ array $notification,
+ ): void {
+ $sharedSecret = $notification['sharedSecret'] ?? '';
+ if ($sharedSecret === '') {
throw new BadRequestException(['sharedSecret']);
}
- $provider = $this->shareProviderFactory->getProviderForType(IShare::TYPE_REMOTE);
- $share = $provider->getShareByToken($token);
try {
- $this->confirmShareEntry($signedRequest, $share->getSharedWith());
- } catch (IncomingRequestException $e) {
- // notification might come from the instance that owns the share
- $this->logger->debug('could not confirm origin on sharedWith (' . $share->getSharedWIth() . '); going with shareOwner (' . $share->getShareOwner() . ')', ['exception' => $e]);
- try {
- $this->confirmShareEntry($signedRequest, $share->getShareOwner());
- } catch (IncomingRequestException $f) {
- // if both entry are failing, we log first exception as warning and second exception
- // will be logged as warning by the controller
- $this->logger->warning('could not confirm origin on sharedWith (' . $share->getSharedWIth() . '); going with shareOwner (' . $share->getShareOwner() . ')', ['exception' => $e]);
- throw $f;
+ $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
+ if ($provider instanceof ISignedCloudFederationProvider) {
+ $identity = $provider->getFederationIdFromSharedSecret($sharedSecret, $notification);
+ } else {
+ $this->logger->debug('cloud federation provider {provider} does not implements ISignedCloudFederationProvider', ['provider' => $provider::class]);
+ return;
}
+ } catch (\Exception $e) {
+ throw new IncomingRequestException($e->getMessage());
}
+
+ $this->confirmNotificationEntry($signedRequest, $identity);
}
+
/**
* @param IIncomingSignedRequest|null $signedRequest
* @param string $entry
@@ -430,7 +431,7 @@ class RequestHandlerController extends Controller {
* @return void
* @throws IncomingRequestException
*/
- private function confirmShareEntry(?IIncomingSignedRequest $signedRequest, string $entry): void {
+ private function confirmNotificationEntry(?IIncomingSignedRequest $signedRequest, string $entry): void {
$instance = $this->getHostFromFederationId($entry);
if ($signedRequest === null) {
try {
@@ -440,7 +441,7 @@ class RequestHandlerController extends Controller {
return;
}
} elseif ($instance !== $signedRequest->getOrigin()) {
- throw new IncomingRequestException('token sharedWith (' . $instance . ') not linked to origin (' . $signedRequest->getOrigin() . ')');
+ throw new IncomingRequestException('remote instance ' . $instance . ' not linked to origin ' . $signedRequest->getOrigin());
}
}
diff --git a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php
index 029e94eeef8..8821ff9834e 100644
--- a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php
+++ b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php
@@ -5,6 +5,7 @@
*/
namespace OCA\FederatedFileSharing\OCM;
+use NCU\Federation\ISignedCloudFederationProvider;
use OC\AppFramework\Http;
use OC\Files\Filesystem;
use OCA\FederatedFileSharing\AddressHandler;
@@ -21,7 +22,6 @@ use OCP\Federation\Exceptions\AuthenticationFailedException;
use OCP\Federation\Exceptions\BadRequestException;
use OCP\Federation\Exceptions\ProviderCouldNotAddShareException;
use OCP\Federation\ICloudFederationFactory;
-use OCP\Federation\ICloudFederationProvider;
use OCP\Federation\ICloudFederationProviderManager;
use OCP\Federation\ICloudFederationShare;
use OCP\Federation\ICloudIdManager;
@@ -37,11 +37,13 @@ use OCP\Notification\IManager as INotificationManager;
use OCP\Server;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
+use OCP\Share\IProviderFactory;
use OCP\Share\IShare;
use OCP\Util;
use Psr\Log\LoggerInterface;
+use SensitiveParameter;
-class CloudFederationProviderFiles implements ICloudFederationProvider {
+class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
/**
* CloudFederationProvider constructor.
*/
@@ -63,6 +65,7 @@ class CloudFederationProviderFiles implements ICloudFederationProvider {
private Manager $externalShareManager,
private LoggerInterface $logger,
private IFilenameValidator $filenameValidator,
+ private readonly IProviderFactory $shareProviderFactory,
) {
}
@@ -747,4 +750,32 @@ class CloudFederationProviderFiles implements ICloudFederationProvider {
return $slaveService->getUserDisplayName($this->cloudIdManager->removeProtocolFromUrl($userId), false);
}
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $sharedSecret
+ * @param array $payload
+ * @return string
+ */
+ public function getFederationIdFromSharedSecret(
+ #[SensitiveParameter]
+ string $sharedSecret,
+ array $payload,
+ ): string {
+ $provider = $this->shareProviderFactory->getProviderForType(IShare::TYPE_REMOTE);
+ try {
+ $share = $provider->getShareByToken($sharedSecret);
+ } catch (ShareNotFound) {
+ return '';
+ }
+
+ // if uid_owner is a local account, the request comes from the recipient
+ // if not, request comes from the instance that owns the share and recipient is the re-sharer
+ if ($this->userManager->get($share->getShareOwner()) !== null) {
+ return $share->getSharedWith();
+ } else {
+ return $share->getShareOwner();
+ }
+ }
}
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index a30eccfd838..94e6e1f1e41 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -12,6 +12,7 @@ return array(
'NCU\\Config\\Exceptions\\UnknownKeyException' => $baseDir . '/lib/unstable/Config/Exceptions/UnknownKeyException.php',
'NCU\\Config\\IUserConfig' => $baseDir . '/lib/unstable/Config/IUserConfig.php',
'NCU\\Config\\ValueType' => $baseDir . '/lib/unstable/Config/ValueType.php',
+ 'NCU\\Federation\\ISignedCloudFederationProvider' => $baseDir . '/lib/unstable/Federation/ISignedCloudFederationProvider.php',
'NCU\\Security\\Signature\\Enum\\DigestAlgorithm' => $baseDir . '/lib/unstable/Security/Signature/Enum/DigestAlgorithm.php',
'NCU\\Security\\Signature\\Enum\\SignatoryStatus' => $baseDir . '/lib/unstable/Security/Signature/Enum/SignatoryStatus.php',
'NCU\\Security\\Signature\\Enum\\SignatoryType' => $baseDir . '/lib/unstable/Security/Signature/Enum/SignatoryType.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 9ca1852a071..246934b4848 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -53,6 +53,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'NCU\\Config\\Exceptions\\UnknownKeyException' => __DIR__ . '/../../..' . '/lib/unstable/Config/Exceptions/UnknownKeyException.php',
'NCU\\Config\\IUserConfig' => __DIR__ . '/../../..' . '/lib/unstable/Config/IUserConfig.php',
'NCU\\Config\\ValueType' => __DIR__ . '/../../..' . '/lib/unstable/Config/ValueType.php',
+ 'NCU\\Federation\\ISignedCloudFederationProvider' => __DIR__ . '/../../..' . '/lib/unstable/Federation/ISignedCloudFederationProvider.php',
'NCU\\Security\\Signature\\Enum\\DigestAlgorithm' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Enum/DigestAlgorithm.php',
'NCU\\Security\\Signature\\Enum\\SignatoryStatus' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Enum/SignatoryStatus.php',
'NCU\\Security\\Signature\\Enum\\SignatoryType' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Enum/SignatoryType.php',
diff --git a/lib/private/OCM/OCMDiscoveryService.php b/lib/private/OCM/OCMDiscoveryService.php
index 55da887494a..af612416372 100644
--- a/lib/private/OCM/OCMDiscoveryService.php
+++ b/lib/private/OCM/OCMDiscoveryService.php
@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace OC\OCM;
+use GuzzleHttp\Exception\ConnectException;
use JsonException;
use OCP\AppFramework\Http;
use OCP\Http\Client\IClientService;
@@ -50,7 +51,7 @@ class OCMDiscoveryService implements IOCMDiscoveryService {
// if scheme not specified, we test both;
try {
return $this->discover('https://' . $remote, $skipCache);
- } catch (OCMProviderException) {
+ } catch (OCMProviderException|ConnectException) {
return $this->discover('http://' . $remote, $skipCache);
}
}
diff --git a/lib/private/Security/Signature/SignatureManager.php b/lib/private/Security/Signature/SignatureManager.php
index fa52bbfaa7c..91a06e29b4a 100644
--- a/lib/private/Security/Signature/SignatureManager.php
+++ b/lib/private/Security/Signature/SignatureManager.php
@@ -142,7 +142,7 @@ class SignatureManager implements ISignatureManager {
if ($ttlSignatory > 0 && $knownSignatory->getLastUpdated() < (time() - $ttlSignatory)) {
$signatory = $this->getSaneRemoteSignatory($signatoryManager, $signedRequest);
$this->updateSignatoryMetadata($signatory);
- $knownSignatory->setMetadata($signatory->getMetadata());
+ $knownSignatory->setMetadata($signatory->getMetadata() ?? []);
}
$signedRequest->setSignatory($knownSignatory);
@@ -353,6 +353,7 @@ class SignatureManager implements ISignatureManager {
$time = time();
$signatory->setCreation($time);
$signatory->setLastUpdated($time);
+ $signatory->setMetadata($signatory->getMetadata() ?? []); // trigger insert on field metadata using current or default value
$this->mapper->insert($signatory);
}
diff --git a/lib/unstable/Federation/ISignedCloudFederationProvider.php b/lib/unstable/Federation/ISignedCloudFederationProvider.php
new file mode 100644
index 00000000000..1ec50f606ae
--- /dev/null
+++ b/lib/unstable/Federation/ISignedCloudFederationProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace NCU\Federation;
+
+use OCP\Federation\ICloudFederationProvider;
+
+/**
+ * Interface ICloudFederationProvider
+ *
+ * Enable apps to create their own cloud federation provider
+ *
+ * @experimental 31.0.0
+ */
+interface ISignedCloudFederationProvider extends ICloudFederationProvider {
+
+ /**
+ * returns federationId in direct relation (as recipient or as author) of a sharedSecret
+ * the federationId must be the one at the remote end
+ *
+ * @param string $sharedSecret
+ * @param array $payload
+ *
+ * @experimental 31.0.0
+ * @return string
+ */
+ public function getFederationIdFromSharedSecret(string $sharedSecret, array $payload): string;
+}
diff --git a/lib/unstable/Security/Signature/Model/Signatory.php b/lib/unstable/Security/Signature/Model/Signatory.php
index d42be9c4544..c3a275082ae 100644
--- a/lib/unstable/Security/Signature/Model/Signatory.php
+++ b/lib/unstable/Security/Signature/Model/Signatory.php
@@ -42,7 +42,7 @@ use OCP\AppFramework\Db\Entity;
* @method void setAccount(string $account)
* @method string getAccount()
* @method void setMetadata(array $metadata)
- * @method array getMetadata()
+ * @method ?array getMetadata()
* @method void setCreation(int $creation)
* @method int getCreation()
* @method void setLastUpdated(int $creation)
@@ -59,7 +59,7 @@ class Signatory extends Entity implements JsonSerializable {
protected string $account = '';
protected int $type = 9;
protected int $status = 1;
- protected array $metadata = [];
+ protected ?array $metadata = null;
protected int $creation = 0;
protected int $lastUpdated = 0;