aboutsummaryrefslogtreecommitdiffstats
path: root/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/cloud_federation_api/lib/Controller/RequestHandlerController.php')
-rw-r--r--apps/cloud_federation_api/lib/Controller/RequestHandlerController.php111
1 files changed, 109 insertions, 2 deletions
diff --git a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php
index cbd66f52382..23fe509c081 100644
--- a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php
+++ b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php
@@ -1,8 +1,10 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
+
namespace OCA\CloudFederationAPI\Controller;
use NCU\Federation\ISignedCloudFederationProvider;
@@ -15,15 +17,20 @@ use NCU\Security\Signature\IIncomingSignedRequest;
use NCU\Security\Signature\ISignatureManager;
use OC\OCM\OCMSignatoryManager;
use OCA\CloudFederationAPI\Config;
+use OCA\CloudFederationAPI\Db\FederatedInviteMapper;
+use OCA\CloudFederationAPI\Events\FederatedInviteAcceptedEvent;
use OCA\CloudFederationAPI\ResponseDefinitions;
use OCA\FederatedFileSharing\AddressHandler;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Db\DoesNotExistException;
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\JSONResponse;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\Federation\Exceptions\ActionNotSupportedException;
use OCP\Federation\Exceptions\AuthenticationFailedException;
use OCP\Federation\Exceptions\BadRequestException;
@@ -61,12 +68,15 @@ class RequestHandlerController extends Controller {
private IURLGenerator $urlGenerator,
private ICloudFederationProviderManager $cloudFederationProviderManager,
private Config $config,
+ private IEventDispatcher $dispatcher,
+ private FederatedInviteMapper $federatedInviteMapper,
private readonly AddressHandler $addressHandler,
private readonly IAppConfig $appConfig,
private ICloudFederationFactory $factory,
private ICloudIdManager $cloudIdManager,
private readonly ISignatureManager $signatureManager,
private readonly OCMSignatoryManager $signatoryManager,
+ private ITimeFactory $timeFactory,
) {
parent::__construct($appName, $request);
}
@@ -107,7 +117,8 @@ class RequestHandlerController extends Controller {
}
// check if all required parameters are set
- if ($shareWith === null ||
+ if (
+ $shareWith === null ||
$name === null ||
$providerId === null ||
$resourceType === null ||
@@ -214,6 +225,101 @@ class RequestHandlerController extends Controller {
}
/**
+ * Inform the sender that an invitation was accepted to start sharing
+ *
+ * Inform about an accepted invitation so the user on the sender provider's side
+ * can initiate the OCM share creation. To protect the identity of the parties,
+ * for shares created following an OCM invitation, the user id MAY be hashed,
+ * and recipients implementing the OCM invitation workflow MAY refuse to process
+ * shares coming from unknown parties.
+ * @link https://cs3org.github.io/OCM-API/docs.html?branch=v1.1.0&repo=OCM-API&user=cs3org#/paths/~1invite-accepted/post
+ *
+ * @param string $recipientProvider The address of the recipent's provider
+ * @param string $token The token used for the invitation
+ * @param string $userId The userId of the recipient at the recipient's provider
+ * @param string $email The email address of the recipient
+ * @param string $name The display name of the recipient
+ *
+ * @return JSONResponse<Http::STATUS_OK, array{userID: string, email: string, name: string}, array{}>|JSONResponse<Http::STATUS_FORBIDDEN|Http::STATUS_BAD_REQUEST|Http::STATUS_CONFLICT, array{message: string, error: true}, array{}>
+ *
+ * Note: Not implementing 404 Invitation token does not exist, instead using 400
+ * 200: Invitation accepted
+ * 400: Invalid token
+ * 403: Invitation token does not exist
+ * 409: User is already known by the OCM provider
+ */
+ #[PublicPage]
+ #[NoCSRFRequired]
+ #[BruteForceProtection(action: 'inviteAccepted')]
+ public function inviteAccepted(string $recipientProvider, string $token, string $userId, string $email, string $name): JSONResponse {
+ $this->logger->debug('Processing share invitation for ' . $userId . ' with token ' . $token . ' and email ' . $email . ' and name ' . $name);
+
+ $updated = $this->timeFactory->getTime();
+
+ if ($token === '') {
+ $response = new JSONResponse(['message' => 'Invalid or non existing token', 'error' => true], Http::STATUS_BAD_REQUEST);
+ $response->throttle();
+ return $response;
+ }
+
+ try {
+ $invitation = $this->federatedInviteMapper->findByToken($token);
+ } catch (DoesNotExistException) {
+ $response = ['message' => 'Invalid or non existing token', 'error' => true];
+ $status = Http::STATUS_BAD_REQUEST;
+ $response = new JSONResponse($response, $status);
+ $response->throttle();
+ return $response;
+ }
+
+ if ($invitation->isAccepted() === true) {
+ $response = ['message' => 'Invite already accepted', 'error' => true];
+ $status = Http::STATUS_CONFLICT;
+ return new JSONResponse($response, $status);
+ }
+
+ if ($invitation->getExpiredAt() !== null && $updated > $invitation->getExpiredAt()) {
+ $response = ['message' => 'Invitation expired', 'error' => true];
+ $status = Http::STATUS_BAD_REQUEST;
+ return new JSONResponse($response, $status);
+ }
+ $localUser = $this->userManager->get($invitation->getUserId());
+ if ($localUser === null) {
+ $response = ['message' => 'Invalid or non existing token', 'error' => true];
+ $status = Http::STATUS_BAD_REQUEST;
+ $response = new JSONResponse($response, $status);
+ $response->throttle();
+ return $response;
+ }
+
+ $sharedFromEmail = $localUser->getEMailAddress();
+ if ($sharedFromEmail === null) {
+ $response = ['message' => 'Invalid or non existing token', 'error' => true];
+ $status = Http::STATUS_BAD_REQUEST;
+ $response = new JSONResponse($response, $status);
+ $response->throttle();
+ return $response;
+ }
+ $sharedFromDisplayName = $localUser->getDisplayName();
+
+ $response = ['userID' => $localUser->getUID(), 'email' => $sharedFromEmail, 'name' => $sharedFromDisplayName];
+ $status = Http::STATUS_OK;
+
+ $invitation->setAccepted(true);
+ $invitation->setRecipientEmail($email);
+ $invitation->setRecipientName($name);
+ $invitation->setRecipientProvider($recipientProvider);
+ $invitation->setRecipientUserId($userId);
+ $invitation->setAcceptedAt($updated);
+ $invitation = $this->federatedInviteMapper->update($invitation);
+
+ $event = new FederatedInviteAcceptedEvent($invitation);
+ $this->dispatcher->dispatchTyped($event);
+
+ return new JSONResponse($response, $status);
+ }
+
+ /**
* Send a notification about an existing share
*
* @param string $notificationType Notification type, e.g. SHARE_ACCEPTED
@@ -233,7 +339,8 @@ class RequestHandlerController extends Controller {
#[BruteForceProtection(action: 'receiveFederatedShareNotification')]
public function receiveNotification($notificationType, $resourceType, $providerId, ?array $notification) {
// check if all required parameters are set
- if ($notificationType === null ||
+ if (
+ $notificationType === null ||
$resourceType === null ||
$providerId === null ||
!is_array($notification)