diff --git a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php index a243d286c71b2..8d7887fd6a477 100644 --- a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php +++ b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php @@ -37,8 +37,6 @@ 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 +66,6 @@ public function __construct( private ICloudIdManager $cloudIdManager, private readonly ISignatureManager $signatureManager, private readonly OCMSignatoryManager $signatoryManager, - private readonly IProviderFactory $shareProviderFactory, ) { parent::__construct($appName, $request); } @@ -238,7 +235,7 @@ public function receiveNotification($notificationType, $resourceType, $providerI // 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'] ?? ''); + $this->confirmNotificationIdentity($signedRequest, $resourceType, $notification['sharedSecret'] ?? ''); } catch (IncomingRequestException $e) { $this->logger->warning('incoming request exception', ['exception' => $e]); return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST); @@ -387,42 +384,52 @@ private function confirmSignedOrigin(?IIncomingSignedRequest $signedRequest, str } } - /** - * 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 $resourceType * @param string $token * * @throws IncomingRequestException + * @throws BadRequestException */ - private function confirmShareOrigin(?IIncomingSignedRequest $signedRequest, string $token): void { + private function confirmNotificationIdentity(?IIncomingSignedRequest $signedRequest, string $resourceType, string $token): void { if ($token === '') { 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]); + $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType); + $identities = $provider->getFederationIdFromToken($token); + } catch (\Exception $e) { + throw new IncomingRequestException($e->getMessage()); + } + + if (!is_array($identities)) { + $identities = [$identities]; + } + + $confirmed = false; + foreach ($identities as $identity) { 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; + $this->confirmNotificationEntry($signedRequest, $identity); + $confirmed = true; + continue; + } catch (IncomingRequestException $e) { + $this->logger->notice('could not confirm notification entry', ['exception' => $e, 'identity' => $identity, $signedRequest => $signedRequest]); } } + + if (!$confirmed) { + throw new IncomingRequestException('cannot confirm identity'); + } } + /** * @param IIncomingSignedRequest|null $signedRequest * @param string $entry @@ -430,7 +437,7 @@ private function confirmShareOrigin(?IIncomingSignedRequest $signedRequest, stri * @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 { diff --git a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php index 029e94eeef842..d97ffe823ada6 100644 --- a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php +++ b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php @@ -5,6 +5,8 @@ */ namespace OCA\FederatedFileSharing\OCM; +use NCU\Security\Signature\Exceptions\IncomingRequestException; +use NCU\Security\Signature\IIncomingSignedRequest; use OC\AppFramework\Http; use OC\Files\Filesystem; use OCA\FederatedFileSharing\AddressHandler; @@ -37,6 +39,7 @@ 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; @@ -63,6 +66,7 @@ public function __construct( private Manager $externalShareManager, private LoggerInterface $logger, private IFilenameValidator $filenameValidator, + private readonly IProviderFactory $shareProviderFactory, ) { } @@ -747,4 +751,18 @@ public function getUserDisplayName(string $userId): string { return $slaveService->getUserDisplayName($this->cloudIdManager->removeProtocolFromUrl($userId), false); } + + /** + * @inheritDoc + * + * @param string $token + * @return string|array + */ + public function getFederationIdFromToken(string $token): string|array { + $provider = $this->shareProviderFactory->getProviderForType(IShare::TYPE_REMOTE); + $share = $provider->getShareByToken($token); + + // in case of reshare, notification comes from the instance that owns the share + return [$share->getSharedWith(), $share->getShareOwner()]; + } } diff --git a/lib/public/Federation/ICloudFederationProvider.php b/lib/public/Federation/ICloudFederationProvider.php index 067ceba160eb8..7576a024b1453 100644 --- a/lib/public/Federation/ICloudFederationProvider.php +++ b/lib/public/Federation/ICloudFederationProvider.php @@ -67,4 +67,12 @@ public function notificationReceived($notificationType, $providerId, array $noti * @since 14.0.0 */ public function getSupportedShareTypes(); + + /** + * get federationId (one or multiple) in direct relation (recipient, author) of a share token + * + * @param string $token + * @return string|array + */ + public function getFederationIdFromToken(string $token): string|array; }