|
|
|
|
@ -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 ||
|
|
|
|
|
@ -259,6 +247,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);
|
|
|
|
|
@ -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 {origin}', ['instance' => $instance, 'origin' => $signedRequest->getOrigin()]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|