11<?php
2+
23/**
34 * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
45 * SPDX-License-Identifier: AGPL-3.0-or-later
56 */
7+
68namespace OCA \CloudFederationAPI \Controller ;
79
810use NCU \Federation \ISignedCloudFederationProvider ;
1517use NCU \Security \Signature \ISignatureManager ;
1618use OC \OCM \OCMSignatoryManager ;
1719use OCA \CloudFederationAPI \Config ;
20+ use OCA \CloudFederationAPI \Db \FederatedInviteMapper ;
21+ use OCA \CloudFederationAPI \Events \FederatedInviteAcceptedEvent ;
1822use OCA \CloudFederationAPI \ResponseDefinitions ;
1923use OCA \FederatedFileSharing \AddressHandler ;
2024use OCP \AppFramework \Controller ;
25+ use OCP \AppFramework \Db \DoesNotExistException ;
2126use OCP \AppFramework \Http ;
2227use OCP \AppFramework \Http \Attribute \BruteForceProtection ;
2328use OCP \AppFramework \Http \Attribute \NoCSRFRequired ;
2429use OCP \AppFramework \Http \Attribute \OpenAPI ;
2530use OCP \AppFramework \Http \Attribute \PublicPage ;
2631use OCP \AppFramework \Http \JSONResponse ;
32+ use OCP \AppFramework \Utility \ITimeFactory ;
33+ use OCP \EventDispatcher \IEventDispatcher ;
2734use OCP \Federation \Exceptions \ActionNotSupportedException ;
2835use OCP \Federation \Exceptions \AuthenticationFailedException ;
2936use OCP \Federation \Exceptions \BadRequestException ;
@@ -61,12 +68,15 @@ public function __construct(
6168 private IURLGenerator $ urlGenerator ,
6269 private ICloudFederationProviderManager $ cloudFederationProviderManager ,
6370 private Config $ config ,
71+ private IEventDispatcher $ dispatcher ,
72+ private FederatedInviteMapper $ federatedInviteMapper ,
6473 private readonly AddressHandler $ addressHandler ,
6574 private readonly IAppConfig $ appConfig ,
6675 private ICloudFederationFactory $ factory ,
6776 private ICloudIdManager $ cloudIdManager ,
6877 private readonly ISignatureManager $ signatureManager ,
6978 private readonly OCMSignatoryManager $ signatoryManager ,
79+ private ITimeFactory $ timeFactory ,
7080 ) {
7181 parent ::__construct ($ appName , $ request );
7282 }
@@ -107,7 +117,8 @@ public function addShare($shareWith, $name, $description, $providerId, $owner, $
107117 }
108118
109119 // check if all required parameters are set
110- if ($ shareWith === null ||
120+ if (
121+ $ shareWith === null ||
111122 $ name === null ||
112123 $ providerId === null ||
113124 $ resourceType === null ||
@@ -213,6 +224,101 @@ public function addShare($shareWith, $name, $description, $providerId, $owner, $
213224 return new JSONResponse ($ responseData , Http::STATUS_CREATED );
214225 }
215226
227+ /**
228+ * Inform the sender that an invitation was accepted to start sharing
229+ *
230+ * Inform about an accepted invitation so the user on the sender provider's side
231+ * can initiate the OCM share creation. To protect the identity of the parties,
232+ * for shares created following an OCM invitation, the user id MAY be hashed,
233+ * and recipients implementing the OCM invitation workflow MAY refuse to process
234+ * shares coming from unknown parties.
235+ * @link https://cs3org.github.io/OCM-API/docs.html?branch=v1.1.0&repo=OCM-API&user=cs3org#/paths/~1invite-accepted/post
236+ *
237+ * @param string $recipientProvider The address of the recipent's provider
238+ * @param string $token The token used for the invitation
239+ * @param string $userId The userId of the recipient at the recipient's provider
240+ * @param string $email The email address of the recipient
241+ * @param string $name The display name of the recipient
242+ *
243+ * @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{}>
244+ *
245+ * Note: Not implementing 404 Invitation token does not exist, instead using 400
246+ * 200: Invitation accepted
247+ * 400: Invalid token
248+ * 403: Invitation token does not exist
249+ * 409: User is already known by the OCM provider
250+ */
251+ #[PublicPage]
252+ #[NoCSRFRequired]
253+ #[BruteForceProtection(action: 'inviteAccepted ' )]
254+ public function inviteAccepted (string $ recipientProvider , string $ token , string $ userId , string $ email , string $ name ): JSONResponse {
255+ $ this ->logger ->debug ('Processing share invitation for ' . $ userId . ' with token ' . $ token . ' and email ' . $ email . ' and name ' . $ name );
256+
257+ $ updated = $ this ->timeFactory ->getTime ();
258+
259+ if ($ token === '' ) {
260+ $ response = new JSONResponse (['message ' => 'Invalid or non existing token ' , 'error ' => true ], Http::STATUS_BAD_REQUEST );
261+ $ response ->throttle ();
262+ return $ response ;
263+ }
264+
265+ try {
266+ $ invitation = $ this ->federatedInviteMapper ->findByToken ($ token );
267+ } catch (DoesNotExistException ) {
268+ $ response = ['message ' => 'Invalid or non existing token ' , 'error ' => true ];
269+ $ status = Http::STATUS_BAD_REQUEST ;
270+ $ response = new JSONResponse ($ response , $ status );
271+ $ response ->throttle ();
272+ return $ response ;
273+ }
274+
275+ if ($ invitation ->isAccepted () === true ) {
276+ $ response = ['message ' => 'Invite already accepted ' , 'error ' => true ];
277+ $ status = Http::STATUS_CONFLICT ;
278+ return new JSONResponse ($ response , $ status );
279+ }
280+
281+ if ($ invitation ->getExpiredAt () !== null && $ updated > $ invitation ->getExpiredAt ()) {
282+ $ response = ['message ' => 'Invitation expired ' , 'error ' => true ];
283+ $ status = Http::STATUS_BAD_REQUEST ;
284+ return new JSONResponse ($ response , $ status );
285+ }
286+ $ localUser = $ this ->userManager ->get ($ invitation ->getUserId ());
287+ if ($ localUser === null ) {
288+ $ response = ['message ' => 'Invalid or non existing token ' , 'error ' => true ];
289+ $ status = Http::STATUS_BAD_REQUEST ;
290+ $ response = new JSONResponse ($ response , $ status );
291+ $ response ->throttle ();
292+ return $ response ;
293+ }
294+
295+ $ sharedFromEmail = $ localUser ->getPrimaryEMailAddress ();
296+ if ($ sharedFromEmail === null ) {
297+ $ response = ['message ' => 'Invalid or non existing token ' , 'error ' => true ];
298+ $ status = Http::STATUS_BAD_REQUEST ;
299+ $ response = new JSONResponse ($ response , $ status );
300+ $ response ->throttle ();
301+ return $ response ;
302+ }
303+ $ sharedFromDisplayName = $ localUser ->getDisplayName ();
304+
305+ $ response = ['userID ' => $ localUser ->getUID (), 'email ' => $ sharedFromEmail , 'name ' => $ sharedFromDisplayName ];
306+ $ status = Http::STATUS_OK ;
307+
308+ $ invitation ->setAccepted (true );
309+ $ invitation ->setRecipientEmail ($ email );
310+ $ invitation ->setRecipientName ($ name );
311+ $ invitation ->setRecipientProvider ($ recipientProvider );
312+ $ invitation ->setRecipientUserId ($ userId );
313+ $ invitation ->setAcceptedAt ($ updated );
314+ $ invitation = $ this ->federatedInviteMapper ->update ($ invitation );
315+
316+ $ event = new FederatedInviteAcceptedEvent ($ invitation );
317+ $ this ->dispatcher ->dispatchTyped ($ event );
318+
319+ return new JSONResponse ($ response , $ status );
320+ }
321+
216322 /**
217323 * Send a notification about an existing share
218324 *
@@ -233,7 +339,8 @@ public function addShare($shareWith, $name, $description, $providerId, $owner, $
233339 #[BruteForceProtection(action: 'receiveFederatedShareNotification ' )]
234340 public function receiveNotification ($ notificationType , $ resourceType , $ providerId , ?array $ notification ) {
235341 // check if all required parameters are set
236- if ($ notificationType === null ||
342+ if (
343+ $ notificationType === null ||
237344 $ resourceType === null ||
238345 $ providerId === null ||
239346 !is_array ($ notification )
0 commit comments