Completed
Push — master ( 1ae57f...0c3ebb )
by Joas
24:07 queued 26s
created
apps/cloud_federation_api/lib/Config.php 1 patch
Indentation   +20 added lines, -20 removed lines patch added patch discarded remove patch
@@ -17,25 +17,25 @@
 block discarded – undo
17 17
  */
18 18
 class Config {
19 19
 
20
-	public function __construct(
21
-		private ICloudFederationProviderManager $cloudFederationProviderManager,
22
-		private LoggerInterface $logger,
23
-	) {
24
-	}
20
+    public function __construct(
21
+        private ICloudFederationProviderManager $cloudFederationProviderManager,
22
+        private LoggerInterface $logger,
23
+    ) {
24
+    }
25 25
 
26
-	/**
27
-	 * get a list of supported share types
28
-	 *
29
-	 * @param string $resourceType
30
-	 * @return array
31
-	 */
32
-	public function getSupportedShareTypes($resourceType) {
33
-		try {
34
-			$provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
35
-			return $provider->getSupportedShareTypes();
36
-		} catch (\Exception $e) {
37
-			$this->logger->error('Failed to create federation provider', ['exception' => $e]);
38
-			return [];
39
-		}
40
-	}
26
+    /**
27
+     * get a list of supported share types
28
+     *
29
+     * @param string $resourceType
30
+     * @return array
31
+     */
32
+    public function getSupportedShareTypes($resourceType) {
33
+        try {
34
+            $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
35
+            return $provider->getSupportedShareTypes();
36
+        } catch (\Exception $e) {
37
+            $this->logger->error('Failed to create federation provider', ['exception' => $e]);
38
+            return [];
39
+        }
40
+    }
41 41
 }
Please login to merge, or discard this patch.
apps/cloud_federation_api/lib/Controller/RequestHandlerController.php 2 patches
Indentation   +412 added lines, -412 removed lines patch added patch discarded remove patch
@@ -52,416 +52,416 @@
 block discarded – undo
52 52
  */
53 53
 #[OpenAPI(scope: OpenAPI::SCOPE_FEDERATION)]
54 54
 class RequestHandlerController extends Controller {
55
-	public function __construct(
56
-		string $appName,
57
-		IRequest $request,
58
-		private LoggerInterface $logger,
59
-		private IUserManager $userManager,
60
-		private IGroupManager $groupManager,
61
-		private IURLGenerator $urlGenerator,
62
-		private ICloudFederationProviderManager $cloudFederationProviderManager,
63
-		private Config $config,
64
-		private readonly AddressHandler $addressHandler,
65
-		private readonly IAppConfig $appConfig,
66
-		private ICloudFederationFactory $factory,
67
-		private ICloudIdManager $cloudIdManager,
68
-		private readonly ISignatureManager $signatureManager,
69
-		private readonly OCMSignatoryManager $signatoryManager,
70
-	) {
71
-		parent::__construct($appName, $request);
72
-	}
73
-
74
-	/**
75
-	 * Add share
76
-	 *
77
-	 * @param string $shareWith The user who the share will be shared with
78
-	 * @param string $name The resource name (e.g. document.odt)
79
-	 * @param string|null $description Share description
80
-	 * @param string $providerId Resource UID on the provider side
81
-	 * @param string $owner Provider specific UID of the user who owns the resource
82
-	 * @param string|null $ownerDisplayName Display name of the user who shared the item
83
-	 * @param string|null $sharedBy Provider specific UID of the user who shared the resource
84
-	 * @param string|null $sharedByDisplayName Display name of the user who shared the resource
85
-	 * @param array{name: list<string>, options: array<string, mixed>} $protocol e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]]
86
-	 * @param string $shareType 'group' or 'user' share
87
-	 * @param string $resourceType 'file', 'calendar',...
88
-	 *
89
-	 * @return JSONResponse<Http::STATUS_CREATED, CloudFederationAPIAddShare, array{}>|JSONResponse<Http::STATUS_BAD_REQUEST, CloudFederationAPIValidationError, array{}>|JSONResponse<Http::STATUS_NOT_IMPLEMENTED, CloudFederationAPIError, array{}>
90
-	 *
91
-	 * 201: The notification was successfully received. The display name of the recipient might be returned in the body
92
-	 * 400: Bad request due to invalid parameters, e.g. when `shareWith` is not found or required properties are missing
93
-	 * 501: Share type or the resource type is not supported
94
-	 */
95
-	#[PublicPage]
96
-	#[NoCSRFRequired]
97
-	#[BruteForceProtection(action: 'receiveFederatedShare')]
98
-	public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
99
-		try {
100
-			// if request is signed and well signed, no exception are thrown
101
-			// if request is not signed and host is known for not supporting signed request, no exception are thrown
102
-			$signedRequest = $this->getSignedRequest();
103
-			$this->confirmSignedOrigin($signedRequest, 'owner', $owner);
104
-		} catch (IncomingRequestException $e) {
105
-			$this->logger->warning('incoming request exception', ['exception' => $e]);
106
-			return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
107
-		}
108
-
109
-		// check if all required parameters are set
110
-		if ($shareWith === null ||
111
-			$name === null ||
112
-			$providerId === null ||
113
-			$resourceType === null ||
114
-			$shareType === null ||
115
-			!is_array($protocol) ||
116
-			!isset($protocol['name']) ||
117
-			!isset($protocol['options']) ||
118
-			!is_array($protocol['options']) ||
119
-			!isset($protocol['options']['sharedSecret'])
120
-		) {
121
-			return new JSONResponse(
122
-				[
123
-					'message' => 'Missing arguments',
124
-					'validationErrors' => [],
125
-				],
126
-				Http::STATUS_BAD_REQUEST
127
-			);
128
-		}
129
-
130
-		$supportedShareTypes = $this->config->getSupportedShareTypes($resourceType);
131
-		if (!in_array($shareType, $supportedShareTypes)) {
132
-			return new JSONResponse(
133
-				['message' => 'Share type "' . $shareType . '" not implemented'],
134
-				Http::STATUS_NOT_IMPLEMENTED
135
-			);
136
-		}
137
-
138
-		$cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
139
-		$shareWith = $cloudId->getUser();
140
-
141
-		if ($shareType === 'user') {
142
-			$shareWith = $this->mapUid($shareWith);
143
-
144
-			if (!$this->userManager->userExists($shareWith)) {
145
-				$response = new JSONResponse(
146
-					[
147
-						'message' => 'User "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl(),
148
-						'validationErrors' => [],
149
-					],
150
-					Http::STATUS_BAD_REQUEST
151
-				);
152
-				$response->throttle();
153
-				return $response;
154
-			}
155
-		}
156
-
157
-		if ($shareType === 'group') {
158
-			if (!$this->groupManager->groupExists($shareWith)) {
159
-				$response = new JSONResponse(
160
-					[
161
-						'message' => 'Group "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl(),
162
-						'validationErrors' => [],
163
-					],
164
-					Http::STATUS_BAD_REQUEST
165
-				);
166
-				$response->throttle();
167
-				return $response;
168
-			}
169
-		}
170
-
171
-		// if no explicit display name is given, we use the uid as display name
172
-		$ownerDisplayName = $ownerDisplayName === null ? $owner : $ownerDisplayName;
173
-		$sharedByDisplayName = $sharedByDisplayName === null ? $sharedBy : $sharedByDisplayName;
174
-
175
-		// sharedBy* parameter is optional, if nothing is set we assume that it is the same user as the owner
176
-		if ($sharedBy === null) {
177
-			$sharedBy = $owner;
178
-			$sharedByDisplayName = $ownerDisplayName;
179
-		}
180
-
181
-		try {
182
-			$provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
183
-			$share = $this->factory->getCloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, '', $shareType, $resourceType);
184
-			$share->setProtocol($protocol);
185
-			$provider->shareReceived($share);
186
-		} catch (ProviderDoesNotExistsException|ProviderCouldNotAddShareException $e) {
187
-			return new JSONResponse(
188
-				['message' => $e->getMessage()],
189
-				Http::STATUS_NOT_IMPLEMENTED
190
-			);
191
-		} catch (\Exception $e) {
192
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
193
-			return new JSONResponse(
194
-				[
195
-					'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
196
-					'validationErrors' => [],
197
-				],
198
-				Http::STATUS_BAD_REQUEST
199
-			);
200
-		}
201
-
202
-		$responseData = ['recipientDisplayName' => ''];
203
-		if ($shareType === 'user') {
204
-			$user = $this->userManager->get($shareWith);
205
-			if ($user) {
206
-				$responseData = [
207
-					'recipientDisplayName' => $user->getDisplayName(),
208
-					'recipientUserId' => $user->getUID(),
209
-				];
210
-			}
211
-		}
212
-
213
-		return new JSONResponse($responseData, Http::STATUS_CREATED);
214
-	}
215
-
216
-	/**
217
-	 * Send a notification about an existing share
218
-	 *
219
-	 * @param string $notificationType Notification type, e.g. SHARE_ACCEPTED
220
-	 * @param string $resourceType calendar, file, contact,...
221
-	 * @param string|null $providerId ID of the share
222
-	 * @param array<string, mixed>|null $notification The actual payload of the notification
223
-	 *
224
-	 * @return JSONResponse<Http::STATUS_CREATED, array<string, mixed>, array{}>|JSONResponse<Http::STATUS_BAD_REQUEST, CloudFederationAPIValidationError, array{}>|JSONResponse<Http::STATUS_FORBIDDEN|Http::STATUS_NOT_IMPLEMENTED, CloudFederationAPIError, array{}>
225
-	 *
226
-	 * 201: The notification was successfully received
227
-	 * 400: Bad request due to invalid parameters, e.g. when `type` is invalid or missing
228
-	 * 403: Getting resource is not allowed
229
-	 * 501: The resource type is not supported
230
-	 */
231
-	#[NoCSRFRequired]
232
-	#[PublicPage]
233
-	#[BruteForceProtection(action: 'receiveFederatedShareNotification')]
234
-	public function receiveNotification($notificationType, $resourceType, $providerId, ?array $notification) {
235
-		// check if all required parameters are set
236
-		if ($notificationType === null ||
237
-			$resourceType === null ||
238
-			$providerId === null ||
239
-			!is_array($notification)
240
-		) {
241
-			return new JSONResponse(
242
-				[
243
-					'message' => 'Missing arguments',
244
-					'validationErrors' => [],
245
-				],
246
-				Http::STATUS_BAD_REQUEST
247
-			);
248
-		}
249
-
250
-		try {
251
-			// if request is signed and well signed, no exception are thrown
252
-			// if request is not signed and host is known for not supporting signed request, no exception are thrown
253
-			$signedRequest = $this->getSignedRequest();
254
-			$this->confirmNotificationIdentity($signedRequest, $resourceType, $notification);
255
-		} catch (IncomingRequestException $e) {
256
-			$this->logger->warning('incoming request exception', ['exception' => $e]);
257
-			return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
258
-		}
259
-
260
-		try {
261
-			$provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
262
-			$result = $provider->notificationReceived($notificationType, $providerId, $notification);
263
-		} catch (ProviderDoesNotExistsException $e) {
264
-			return new JSONResponse(
265
-				[
266
-					'message' => $e->getMessage(),
267
-					'validationErrors' => [],
268
-				],
269
-				Http::STATUS_BAD_REQUEST
270
-			);
271
-		} catch (ShareNotFound $e) {
272
-			$response = new JSONResponse(
273
-				[
274
-					'message' => $e->getMessage(),
275
-					'validationErrors' => [],
276
-				],
277
-				Http::STATUS_BAD_REQUEST
278
-			);
279
-			$response->throttle();
280
-			return $response;
281
-		} catch (ActionNotSupportedException $e) {
282
-			return new JSONResponse(
283
-				['message' => $e->getMessage()],
284
-				Http::STATUS_NOT_IMPLEMENTED
285
-			);
286
-		} catch (BadRequestException $e) {
287
-			return new JSONResponse($e->getReturnMessage(), Http::STATUS_BAD_REQUEST);
288
-		} catch (AuthenticationFailedException $e) {
289
-			$response = new JSONResponse(['message' => 'RESOURCE_NOT_FOUND'], Http::STATUS_FORBIDDEN);
290
-			$response->throttle();
291
-			return $response;
292
-		} catch (\Exception $e) {
293
-			$this->logger->warning('incoming notification exception', ['exception' => $e]);
294
-			return new JSONResponse(
295
-				[
296
-					'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
297
-					'validationErrors' => [],
298
-				],
299
-				Http::STATUS_BAD_REQUEST
300
-			);
301
-		}
302
-
303
-		return new JSONResponse($result, Http::STATUS_CREATED);
304
-	}
305
-
306
-	/**
307
-	 * map login name to internal LDAP UID if a LDAP backend is in use
308
-	 *
309
-	 * @param string $uid
310
-	 * @return string mixed
311
-	 */
312
-	private function mapUid($uid) {
313
-		// FIXME this should be a method in the user management instead
314
-		$this->logger->debug('shareWith before, ' . $uid, ['app' => $this->appName]);
315
-		Util::emitHook(
316
-			'\OCA\Files_Sharing\API\Server2Server',
317
-			'preLoginNameUsedAsUserName',
318
-			['uid' => &$uid]
319
-		);
320
-		$this->logger->debug('shareWith after, ' . $uid, ['app' => $this->appName]);
321
-
322
-		return $uid;
323
-	}
324
-
325
-
326
-	/**
327
-	 * returns signed request if available.
328
-	 * throw an exception:
329
-	 * - if request is signed, but wrongly signed
330
-	 * - if request is not signed but instance is configured to only accept signed ocm request
331
-	 *
332
-	 * @return IIncomingSignedRequest|null null if remote does not (and never did) support signed request
333
-	 * @throws IncomingRequestException
334
-	 */
335
-	private function getSignedRequest(): ?IIncomingSignedRequest {
336
-		try {
337
-			$signedRequest = $this->signatureManager->getIncomingSignedRequest($this->signatoryManager);
338
-			$this->logger->debug('signed request available', ['signedRequest' => $signedRequest]);
339
-			return $signedRequest;
340
-		} catch (SignatureNotFoundException|SignatoryNotFoundException $e) {
341
-			$this->logger->debug('remote does not support signed request', ['exception' => $e]);
342
-			// remote does not support signed request.
343
-			// currently we still accept unsigned request until lazy appconfig
344
-			// core.enforce_signed_ocm_request is set to true (default: false)
345
-			if ($this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_ENFORCED, lazy: true)) {
346
-				$this->logger->notice('ignored unsigned request', ['exception' => $e]);
347
-				throw new IncomingRequestException('Unsigned request');
348
-			}
349
-		} catch (SignatureException $e) {
350
-			$this->logger->warning('wrongly signed request', ['exception' => $e]);
351
-			throw new IncomingRequestException('Invalid signature');
352
-		}
353
-		return null;
354
-	}
355
-
356
-
357
-	/**
358
-	 * confirm that the value related to $key entry from the payload is in format userid@hostname
359
-	 * and compare hostname with the origin of the signed request.
360
-	 *
361
-	 * If request is not signed, we still verify that the hostname from the extracted value does,
362
-	 * actually, not support signed request
363
-	 *
364
-	 * @param IIncomingSignedRequest|null $signedRequest
365
-	 * @param string $key entry from data available in data
366
-	 * @param string $value value itself used in case request is not signed
367
-	 *
368
-	 * @throws IncomingRequestException
369
-	 */
370
-	private function confirmSignedOrigin(?IIncomingSignedRequest $signedRequest, string $key, string $value): void {
371
-		if ($signedRequest === null) {
372
-			$instance = $this->getHostFromFederationId($value);
373
-			try {
374
-				$this->signatureManager->getSignatory($instance);
375
-				throw new IncomingRequestException('instance is supposed to sign its request');
376
-			} catch (SignatoryNotFoundException) {
377
-				return;
378
-			}
379
-		}
380
-
381
-		$body = json_decode($signedRequest->getBody(), true) ?? [];
382
-		$entry = trim($body[$key] ?? '', '@');
383
-		if ($this->getHostFromFederationId($entry) !== $signedRequest->getOrigin()) {
384
-			throw new IncomingRequestException('share initiation (' . $signedRequest->getOrigin() . ') from different instance (' . $entry . ') [key=' . $key . ']');
385
-		}
386
-	}
387
-
388
-	/**
389
-	 *  confirm identity of the remote instance on notification, based on the share token.
390
-	 *
391
-	 *  If request is not signed, we still verify that the hostname from the extracted value does,
392
-	 *  actually, not support signed request
393
-	 *
394
-	 * @param IIncomingSignedRequest|null $signedRequest
395
-	 * @param string $resourceType
396
-	 * @param string $sharedSecret
397
-	 *
398
-	 * @throws IncomingRequestException
399
-	 * @throws BadRequestException
400
-	 */
401
-	private function confirmNotificationIdentity(
402
-		?IIncomingSignedRequest $signedRequest,
403
-		string $resourceType,
404
-		array $notification,
405
-	): void {
406
-		$sharedSecret = $notification['sharedSecret'] ?? '';
407
-		if ($sharedSecret === '') {
408
-			throw new BadRequestException(['sharedSecret']);
409
-		}
410
-
411
-		try {
412
-			$provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
413
-			if ($provider instanceof ISignedCloudFederationProvider) {
414
-				$identity = $provider->getFederationIdFromSharedSecret($sharedSecret, $notification);
415
-			} else {
416
-				$this->logger->debug('cloud federation provider {provider} does not implements ISignedCloudFederationProvider', ['provider' => $provider::class]);
417
-				return;
418
-			}
419
-		} catch (\Exception $e) {
420
-			throw new IncomingRequestException($e->getMessage());
421
-		}
422
-
423
-		$this->confirmNotificationEntry($signedRequest, $identity);
424
-	}
425
-
426
-
427
-	/**
428
-	 * @param IIncomingSignedRequest|null $signedRequest
429
-	 * @param string $entry
430
-	 *
431
-	 * @return void
432
-	 * @throws IncomingRequestException
433
-	 */
434
-	private function confirmNotificationEntry(?IIncomingSignedRequest $signedRequest, string $entry): void {
435
-		$instance = $this->getHostFromFederationId($entry);
436
-		if ($signedRequest === null) {
437
-			try {
438
-				$this->signatureManager->getSignatory($instance);
439
-				throw new IncomingRequestException('instance is supposed to sign its request');
440
-			} catch (SignatoryNotFoundException) {
441
-				return;
442
-			}
443
-		} elseif ($instance !== $signedRequest->getOrigin()) {
444
-			throw new IncomingRequestException('remote instance ' . $instance . ' not linked to origin ' . $signedRequest->getOrigin());
445
-		}
446
-	}
447
-
448
-	/**
449
-	 * @param string $entry
450
-	 * @return string
451
-	 * @throws IncomingRequestException
452
-	 */
453
-	private function getHostFromFederationId(string $entry): string {
454
-		if (!str_contains($entry, '@')) {
455
-			throw new IncomingRequestException('entry ' . $entry . ' does not contain @');
456
-		}
457
-		$rightPart = substr($entry, strrpos($entry, '@') + 1);
458
-
459
-		// in case the full scheme is sent; getting rid of it
460
-		$rightPart = $this->addressHandler->removeProtocolFromUrl($rightPart);
461
-		try {
462
-			return $this->signatureManager->extractIdentityFromUri('https://' . $rightPart);
463
-		} catch (IdentityNotFoundException) {
464
-			throw new IncomingRequestException('invalid host within federation id: ' . $entry);
465
-		}
466
-	}
55
+    public function __construct(
56
+        string $appName,
57
+        IRequest $request,
58
+        private LoggerInterface $logger,
59
+        private IUserManager $userManager,
60
+        private IGroupManager $groupManager,
61
+        private IURLGenerator $urlGenerator,
62
+        private ICloudFederationProviderManager $cloudFederationProviderManager,
63
+        private Config $config,
64
+        private readonly AddressHandler $addressHandler,
65
+        private readonly IAppConfig $appConfig,
66
+        private ICloudFederationFactory $factory,
67
+        private ICloudIdManager $cloudIdManager,
68
+        private readonly ISignatureManager $signatureManager,
69
+        private readonly OCMSignatoryManager $signatoryManager,
70
+    ) {
71
+        parent::__construct($appName, $request);
72
+    }
73
+
74
+    /**
75
+     * Add share
76
+     *
77
+     * @param string $shareWith The user who the share will be shared with
78
+     * @param string $name The resource name (e.g. document.odt)
79
+     * @param string|null $description Share description
80
+     * @param string $providerId Resource UID on the provider side
81
+     * @param string $owner Provider specific UID of the user who owns the resource
82
+     * @param string|null $ownerDisplayName Display name of the user who shared the item
83
+     * @param string|null $sharedBy Provider specific UID of the user who shared the resource
84
+     * @param string|null $sharedByDisplayName Display name of the user who shared the resource
85
+     * @param array{name: list<string>, options: array<string, mixed>} $protocol e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]]
86
+     * @param string $shareType 'group' or 'user' share
87
+     * @param string $resourceType 'file', 'calendar',...
88
+     *
89
+     * @return JSONResponse<Http::STATUS_CREATED, CloudFederationAPIAddShare, array{}>|JSONResponse<Http::STATUS_BAD_REQUEST, CloudFederationAPIValidationError, array{}>|JSONResponse<Http::STATUS_NOT_IMPLEMENTED, CloudFederationAPIError, array{}>
90
+     *
91
+     * 201: The notification was successfully received. The display name of the recipient might be returned in the body
92
+     * 400: Bad request due to invalid parameters, e.g. when `shareWith` is not found or required properties are missing
93
+     * 501: Share type or the resource type is not supported
94
+     */
95
+    #[PublicPage]
96
+    #[NoCSRFRequired]
97
+    #[BruteForceProtection(action: 'receiveFederatedShare')]
98
+    public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
99
+        try {
100
+            // if request is signed and well signed, no exception are thrown
101
+            // if request is not signed and host is known for not supporting signed request, no exception are thrown
102
+            $signedRequest = $this->getSignedRequest();
103
+            $this->confirmSignedOrigin($signedRequest, 'owner', $owner);
104
+        } catch (IncomingRequestException $e) {
105
+            $this->logger->warning('incoming request exception', ['exception' => $e]);
106
+            return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
107
+        }
108
+
109
+        // check if all required parameters are set
110
+        if ($shareWith === null ||
111
+            $name === null ||
112
+            $providerId === null ||
113
+            $resourceType === null ||
114
+            $shareType === null ||
115
+            !is_array($protocol) ||
116
+            !isset($protocol['name']) ||
117
+            !isset($protocol['options']) ||
118
+            !is_array($protocol['options']) ||
119
+            !isset($protocol['options']['sharedSecret'])
120
+        ) {
121
+            return new JSONResponse(
122
+                [
123
+                    'message' => 'Missing arguments',
124
+                    'validationErrors' => [],
125
+                ],
126
+                Http::STATUS_BAD_REQUEST
127
+            );
128
+        }
129
+
130
+        $supportedShareTypes = $this->config->getSupportedShareTypes($resourceType);
131
+        if (!in_array($shareType, $supportedShareTypes)) {
132
+            return new JSONResponse(
133
+                ['message' => 'Share type "' . $shareType . '" not implemented'],
134
+                Http::STATUS_NOT_IMPLEMENTED
135
+            );
136
+        }
137
+
138
+        $cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
139
+        $shareWith = $cloudId->getUser();
140
+
141
+        if ($shareType === 'user') {
142
+            $shareWith = $this->mapUid($shareWith);
143
+
144
+            if (!$this->userManager->userExists($shareWith)) {
145
+                $response = new JSONResponse(
146
+                    [
147
+                        'message' => 'User "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl(),
148
+                        'validationErrors' => [],
149
+                    ],
150
+                    Http::STATUS_BAD_REQUEST
151
+                );
152
+                $response->throttle();
153
+                return $response;
154
+            }
155
+        }
156
+
157
+        if ($shareType === 'group') {
158
+            if (!$this->groupManager->groupExists($shareWith)) {
159
+                $response = new JSONResponse(
160
+                    [
161
+                        'message' => 'Group "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl(),
162
+                        'validationErrors' => [],
163
+                    ],
164
+                    Http::STATUS_BAD_REQUEST
165
+                );
166
+                $response->throttle();
167
+                return $response;
168
+            }
169
+        }
170
+
171
+        // if no explicit display name is given, we use the uid as display name
172
+        $ownerDisplayName = $ownerDisplayName === null ? $owner : $ownerDisplayName;
173
+        $sharedByDisplayName = $sharedByDisplayName === null ? $sharedBy : $sharedByDisplayName;
174
+
175
+        // sharedBy* parameter is optional, if nothing is set we assume that it is the same user as the owner
176
+        if ($sharedBy === null) {
177
+            $sharedBy = $owner;
178
+            $sharedByDisplayName = $ownerDisplayName;
179
+        }
180
+
181
+        try {
182
+            $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
183
+            $share = $this->factory->getCloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, '', $shareType, $resourceType);
184
+            $share->setProtocol($protocol);
185
+            $provider->shareReceived($share);
186
+        } catch (ProviderDoesNotExistsException|ProviderCouldNotAddShareException $e) {
187
+            return new JSONResponse(
188
+                ['message' => $e->getMessage()],
189
+                Http::STATUS_NOT_IMPLEMENTED
190
+            );
191
+        } catch (\Exception $e) {
192
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
193
+            return new JSONResponse(
194
+                [
195
+                    'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
196
+                    'validationErrors' => [],
197
+                ],
198
+                Http::STATUS_BAD_REQUEST
199
+            );
200
+        }
201
+
202
+        $responseData = ['recipientDisplayName' => ''];
203
+        if ($shareType === 'user') {
204
+            $user = $this->userManager->get($shareWith);
205
+            if ($user) {
206
+                $responseData = [
207
+                    'recipientDisplayName' => $user->getDisplayName(),
208
+                    'recipientUserId' => $user->getUID(),
209
+                ];
210
+            }
211
+        }
212
+
213
+        return new JSONResponse($responseData, Http::STATUS_CREATED);
214
+    }
215
+
216
+    /**
217
+     * Send a notification about an existing share
218
+     *
219
+     * @param string $notificationType Notification type, e.g. SHARE_ACCEPTED
220
+     * @param string $resourceType calendar, file, contact,...
221
+     * @param string|null $providerId ID of the share
222
+     * @param array<string, mixed>|null $notification The actual payload of the notification
223
+     *
224
+     * @return JSONResponse<Http::STATUS_CREATED, array<string, mixed>, array{}>|JSONResponse<Http::STATUS_BAD_REQUEST, CloudFederationAPIValidationError, array{}>|JSONResponse<Http::STATUS_FORBIDDEN|Http::STATUS_NOT_IMPLEMENTED, CloudFederationAPIError, array{}>
225
+     *
226
+     * 201: The notification was successfully received
227
+     * 400: Bad request due to invalid parameters, e.g. when `type` is invalid or missing
228
+     * 403: Getting resource is not allowed
229
+     * 501: The resource type is not supported
230
+     */
231
+    #[NoCSRFRequired]
232
+    #[PublicPage]
233
+    #[BruteForceProtection(action: 'receiveFederatedShareNotification')]
234
+    public function receiveNotification($notificationType, $resourceType, $providerId, ?array $notification) {
235
+        // check if all required parameters are set
236
+        if ($notificationType === null ||
237
+            $resourceType === null ||
238
+            $providerId === null ||
239
+            !is_array($notification)
240
+        ) {
241
+            return new JSONResponse(
242
+                [
243
+                    'message' => 'Missing arguments',
244
+                    'validationErrors' => [],
245
+                ],
246
+                Http::STATUS_BAD_REQUEST
247
+            );
248
+        }
249
+
250
+        try {
251
+            // if request is signed and well signed, no exception are thrown
252
+            // if request is not signed and host is known for not supporting signed request, no exception are thrown
253
+            $signedRequest = $this->getSignedRequest();
254
+            $this->confirmNotificationIdentity($signedRequest, $resourceType, $notification);
255
+        } catch (IncomingRequestException $e) {
256
+            $this->logger->warning('incoming request exception', ['exception' => $e]);
257
+            return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
258
+        }
259
+
260
+        try {
261
+            $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
262
+            $result = $provider->notificationReceived($notificationType, $providerId, $notification);
263
+        } catch (ProviderDoesNotExistsException $e) {
264
+            return new JSONResponse(
265
+                [
266
+                    'message' => $e->getMessage(),
267
+                    'validationErrors' => [],
268
+                ],
269
+                Http::STATUS_BAD_REQUEST
270
+            );
271
+        } catch (ShareNotFound $e) {
272
+            $response = new JSONResponse(
273
+                [
274
+                    'message' => $e->getMessage(),
275
+                    'validationErrors' => [],
276
+                ],
277
+                Http::STATUS_BAD_REQUEST
278
+            );
279
+            $response->throttle();
280
+            return $response;
281
+        } catch (ActionNotSupportedException $e) {
282
+            return new JSONResponse(
283
+                ['message' => $e->getMessage()],
284
+                Http::STATUS_NOT_IMPLEMENTED
285
+            );
286
+        } catch (BadRequestException $e) {
287
+            return new JSONResponse($e->getReturnMessage(), Http::STATUS_BAD_REQUEST);
288
+        } catch (AuthenticationFailedException $e) {
289
+            $response = new JSONResponse(['message' => 'RESOURCE_NOT_FOUND'], Http::STATUS_FORBIDDEN);
290
+            $response->throttle();
291
+            return $response;
292
+        } catch (\Exception $e) {
293
+            $this->logger->warning('incoming notification exception', ['exception' => $e]);
294
+            return new JSONResponse(
295
+                [
296
+                    'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
297
+                    'validationErrors' => [],
298
+                ],
299
+                Http::STATUS_BAD_REQUEST
300
+            );
301
+        }
302
+
303
+        return new JSONResponse($result, Http::STATUS_CREATED);
304
+    }
305
+
306
+    /**
307
+     * map login name to internal LDAP UID if a LDAP backend is in use
308
+     *
309
+     * @param string $uid
310
+     * @return string mixed
311
+     */
312
+    private function mapUid($uid) {
313
+        // FIXME this should be a method in the user management instead
314
+        $this->logger->debug('shareWith before, ' . $uid, ['app' => $this->appName]);
315
+        Util::emitHook(
316
+            '\OCA\Files_Sharing\API\Server2Server',
317
+            'preLoginNameUsedAsUserName',
318
+            ['uid' => &$uid]
319
+        );
320
+        $this->logger->debug('shareWith after, ' . $uid, ['app' => $this->appName]);
321
+
322
+        return $uid;
323
+    }
324
+
325
+
326
+    /**
327
+     * returns signed request if available.
328
+     * throw an exception:
329
+     * - if request is signed, but wrongly signed
330
+     * - if request is not signed but instance is configured to only accept signed ocm request
331
+     *
332
+     * @return IIncomingSignedRequest|null null if remote does not (and never did) support signed request
333
+     * @throws IncomingRequestException
334
+     */
335
+    private function getSignedRequest(): ?IIncomingSignedRequest {
336
+        try {
337
+            $signedRequest = $this->signatureManager->getIncomingSignedRequest($this->signatoryManager);
338
+            $this->logger->debug('signed request available', ['signedRequest' => $signedRequest]);
339
+            return $signedRequest;
340
+        } catch (SignatureNotFoundException|SignatoryNotFoundException $e) {
341
+            $this->logger->debug('remote does not support signed request', ['exception' => $e]);
342
+            // remote does not support signed request.
343
+            // currently we still accept unsigned request until lazy appconfig
344
+            // core.enforce_signed_ocm_request is set to true (default: false)
345
+            if ($this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_ENFORCED, lazy: true)) {
346
+                $this->logger->notice('ignored unsigned request', ['exception' => $e]);
347
+                throw new IncomingRequestException('Unsigned request');
348
+            }
349
+        } catch (SignatureException $e) {
350
+            $this->logger->warning('wrongly signed request', ['exception' => $e]);
351
+            throw new IncomingRequestException('Invalid signature');
352
+        }
353
+        return null;
354
+    }
355
+
356
+
357
+    /**
358
+     * confirm that the value related to $key entry from the payload is in format userid@hostname
359
+     * and compare hostname with the origin of the signed request.
360
+     *
361
+     * If request is not signed, we still verify that the hostname from the extracted value does,
362
+     * actually, not support signed request
363
+     *
364
+     * @param IIncomingSignedRequest|null $signedRequest
365
+     * @param string $key entry from data available in data
366
+     * @param string $value value itself used in case request is not signed
367
+     *
368
+     * @throws IncomingRequestException
369
+     */
370
+    private function confirmSignedOrigin(?IIncomingSignedRequest $signedRequest, string $key, string $value): void {
371
+        if ($signedRequest === null) {
372
+            $instance = $this->getHostFromFederationId($value);
373
+            try {
374
+                $this->signatureManager->getSignatory($instance);
375
+                throw new IncomingRequestException('instance is supposed to sign its request');
376
+            } catch (SignatoryNotFoundException) {
377
+                return;
378
+            }
379
+        }
380
+
381
+        $body = json_decode($signedRequest->getBody(), true) ?? [];
382
+        $entry = trim($body[$key] ?? '', '@');
383
+        if ($this->getHostFromFederationId($entry) !== $signedRequest->getOrigin()) {
384
+            throw new IncomingRequestException('share initiation (' . $signedRequest->getOrigin() . ') from different instance (' . $entry . ') [key=' . $key . ']');
385
+        }
386
+    }
387
+
388
+    /**
389
+     *  confirm identity of the remote instance on notification, based on the share token.
390
+     *
391
+     *  If request is not signed, we still verify that the hostname from the extracted value does,
392
+     *  actually, not support signed request
393
+     *
394
+     * @param IIncomingSignedRequest|null $signedRequest
395
+     * @param string $resourceType
396
+     * @param string $sharedSecret
397
+     *
398
+     * @throws IncomingRequestException
399
+     * @throws BadRequestException
400
+     */
401
+    private function confirmNotificationIdentity(
402
+        ?IIncomingSignedRequest $signedRequest,
403
+        string $resourceType,
404
+        array $notification,
405
+    ): void {
406
+        $sharedSecret = $notification['sharedSecret'] ?? '';
407
+        if ($sharedSecret === '') {
408
+            throw new BadRequestException(['sharedSecret']);
409
+        }
410
+
411
+        try {
412
+            $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
413
+            if ($provider instanceof ISignedCloudFederationProvider) {
414
+                $identity = $provider->getFederationIdFromSharedSecret($sharedSecret, $notification);
415
+            } else {
416
+                $this->logger->debug('cloud federation provider {provider} does not implements ISignedCloudFederationProvider', ['provider' => $provider::class]);
417
+                return;
418
+            }
419
+        } catch (\Exception $e) {
420
+            throw new IncomingRequestException($e->getMessage());
421
+        }
422
+
423
+        $this->confirmNotificationEntry($signedRequest, $identity);
424
+    }
425
+
426
+
427
+    /**
428
+     * @param IIncomingSignedRequest|null $signedRequest
429
+     * @param string $entry
430
+     *
431
+     * @return void
432
+     * @throws IncomingRequestException
433
+     */
434
+    private function confirmNotificationEntry(?IIncomingSignedRequest $signedRequest, string $entry): void {
435
+        $instance = $this->getHostFromFederationId($entry);
436
+        if ($signedRequest === null) {
437
+            try {
438
+                $this->signatureManager->getSignatory($instance);
439
+                throw new IncomingRequestException('instance is supposed to sign its request');
440
+            } catch (SignatoryNotFoundException) {
441
+                return;
442
+            }
443
+        } elseif ($instance !== $signedRequest->getOrigin()) {
444
+            throw new IncomingRequestException('remote instance ' . $instance . ' not linked to origin ' . $signedRequest->getOrigin());
445
+        }
446
+    }
447
+
448
+    /**
449
+     * @param string $entry
450
+     * @return string
451
+     * @throws IncomingRequestException
452
+     */
453
+    private function getHostFromFederationId(string $entry): string {
454
+        if (!str_contains($entry, '@')) {
455
+            throw new IncomingRequestException('entry ' . $entry . ' does not contain @');
456
+        }
457
+        $rightPart = substr($entry, strrpos($entry, '@') + 1);
458
+
459
+        // in case the full scheme is sent; getting rid of it
460
+        $rightPart = $this->addressHandler->removeProtocolFromUrl($rightPart);
461
+        try {
462
+            return $this->signatureManager->extractIdentityFromUri('https://' . $rightPart);
463
+        } catch (IdentityNotFoundException) {
464
+            throw new IncomingRequestException('invalid host within federation id: ' . $entry);
465
+        }
466
+    }
467 467
 }
Please login to merge, or discard this patch.
Spacing   +14 added lines, -14 removed lines patch added patch discarded remove patch
@@ -130,7 +130,7 @@  discard block
 block discarded – undo
130 130
 		$supportedShareTypes = $this->config->getSupportedShareTypes($resourceType);
131 131
 		if (!in_array($shareType, $supportedShareTypes)) {
132 132
 			return new JSONResponse(
133
-				['message' => 'Share type "' . $shareType . '" not implemented'],
133
+				['message' => 'Share type "'.$shareType.'" not implemented'],
134 134
 				Http::STATUS_NOT_IMPLEMENTED
135 135
 			);
136 136
 		}
@@ -144,7 +144,7 @@  discard block
 block discarded – undo
144 144
 			if (!$this->userManager->userExists($shareWith)) {
145 145
 				$response = new JSONResponse(
146 146
 					[
147
-						'message' => 'User "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl(),
147
+						'message' => 'User "'.$shareWith.'" does not exists at '.$this->urlGenerator->getBaseUrl(),
148 148
 						'validationErrors' => [],
149 149
 					],
150 150
 					Http::STATUS_BAD_REQUEST
@@ -158,7 +158,7 @@  discard block
 block discarded – undo
158 158
 			if (!$this->groupManager->groupExists($shareWith)) {
159 159
 				$response = new JSONResponse(
160 160
 					[
161
-						'message' => 'Group "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl(),
161
+						'message' => 'Group "'.$shareWith.'" does not exists at '.$this->urlGenerator->getBaseUrl(),
162 162
 						'validationErrors' => [],
163 163
 					],
164 164
 					Http::STATUS_BAD_REQUEST
@@ -183,7 +183,7 @@  discard block
 block discarded – undo
183 183
 			$share = $this->factory->getCloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, '', $shareType, $resourceType);
184 184
 			$share->setProtocol($protocol);
185 185
 			$provider->shareReceived($share);
186
-		} catch (ProviderDoesNotExistsException|ProviderCouldNotAddShareException $e) {
186
+		} catch (ProviderDoesNotExistsException | ProviderCouldNotAddShareException $e) {
187 187
 			return new JSONResponse(
188 188
 				['message' => $e->getMessage()],
189 189
 				Http::STATUS_NOT_IMPLEMENTED
@@ -192,7 +192,7 @@  discard block
 block discarded – undo
192 192
 			$this->logger->error($e->getMessage(), ['exception' => $e]);
193 193
 			return new JSONResponse(
194 194
 				[
195
-					'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
195
+					'message' => 'Internal error at '.$this->urlGenerator->getBaseUrl(),
196 196
 					'validationErrors' => [],
197 197
 				],
198 198
 				Http::STATUS_BAD_REQUEST
@@ -293,7 +293,7 @@  discard block
 block discarded – undo
293 293
 			$this->logger->warning('incoming notification exception', ['exception' => $e]);
294 294
 			return new JSONResponse(
295 295
 				[
296
-					'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
296
+					'message' => 'Internal error at '.$this->urlGenerator->getBaseUrl(),
297 297
 					'validationErrors' => [],
298 298
 				],
299 299
 				Http::STATUS_BAD_REQUEST
@@ -311,13 +311,13 @@  discard block
 block discarded – undo
311 311
 	 */
312 312
 	private function mapUid($uid) {
313 313
 		// FIXME this should be a method in the user management instead
314
-		$this->logger->debug('shareWith before, ' . $uid, ['app' => $this->appName]);
314
+		$this->logger->debug('shareWith before, '.$uid, ['app' => $this->appName]);
315 315
 		Util::emitHook(
316 316
 			'\OCA\Files_Sharing\API\Server2Server',
317 317
 			'preLoginNameUsedAsUserName',
318 318
 			['uid' => &$uid]
319 319
 		);
320
-		$this->logger->debug('shareWith after, ' . $uid, ['app' => $this->appName]);
320
+		$this->logger->debug('shareWith after, '.$uid, ['app' => $this->appName]);
321 321
 
322 322
 		return $uid;
323 323
 	}
@@ -337,7 +337,7 @@  discard block
 block discarded – undo
337 337
 			$signedRequest = $this->signatureManager->getIncomingSignedRequest($this->signatoryManager);
338 338
 			$this->logger->debug('signed request available', ['signedRequest' => $signedRequest]);
339 339
 			return $signedRequest;
340
-		} catch (SignatureNotFoundException|SignatoryNotFoundException $e) {
340
+		} catch (SignatureNotFoundException | SignatoryNotFoundException $e) {
341 341
 			$this->logger->debug('remote does not support signed request', ['exception' => $e]);
342 342
 			// remote does not support signed request.
343 343
 			// currently we still accept unsigned request until lazy appconfig
@@ -381,7 +381,7 @@  discard block
 block discarded – undo
381 381
 		$body = json_decode($signedRequest->getBody(), true) ?? [];
382 382
 		$entry = trim($body[$key] ?? '', '@');
383 383
 		if ($this->getHostFromFederationId($entry) !== $signedRequest->getOrigin()) {
384
-			throw new IncomingRequestException('share initiation (' . $signedRequest->getOrigin() . ') from different instance (' . $entry . ') [key=' . $key . ']');
384
+			throw new IncomingRequestException('share initiation ('.$signedRequest->getOrigin().') from different instance ('.$entry.') [key='.$key.']');
385 385
 		}
386 386
 	}
387 387
 
@@ -441,7 +441,7 @@  discard block
 block discarded – undo
441 441
 				return;
442 442
 			}
443 443
 		} elseif ($instance !== $signedRequest->getOrigin()) {
444
-			throw new IncomingRequestException('remote instance ' . $instance . ' not linked to origin ' . $signedRequest->getOrigin());
444
+			throw new IncomingRequestException('remote instance '.$instance.' not linked to origin '.$signedRequest->getOrigin());
445 445
 		}
446 446
 	}
447 447
 
@@ -452,16 +452,16 @@  discard block
 block discarded – undo
452 452
 	 */
453 453
 	private function getHostFromFederationId(string $entry): string {
454 454
 		if (!str_contains($entry, '@')) {
455
-			throw new IncomingRequestException('entry ' . $entry . ' does not contain @');
455
+			throw new IncomingRequestException('entry '.$entry.' does not contain @');
456 456
 		}
457 457
 		$rightPart = substr($entry, strrpos($entry, '@') + 1);
458 458
 
459 459
 		// in case the full scheme is sent; getting rid of it
460 460
 		$rightPart = $this->addressHandler->removeProtocolFromUrl($rightPart);
461 461
 		try {
462
-			return $this->signatureManager->extractIdentityFromUri('https://' . $rightPart);
462
+			return $this->signatureManager->extractIdentityFromUri('https://'.$rightPart);
463 463
 		} catch (IdentityNotFoundException) {
464
-			throw new IncomingRequestException('invalid host within federation id: ' . $entry);
464
+			throw new IncomingRequestException('invalid host within federation id: '.$entry);
465 465
 		}
466 466
 	}
467 467
 }
Please login to merge, or discard this patch.
apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php 1 patch
Indentation   +762 added lines, -762 removed lines patch added patch discarded remove patch
@@ -45,766 +45,766 @@
 block discarded – undo
45 45
 use SensitiveParameter;
46 46
 
47 47
 class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
48
-	/**
49
-	 * CloudFederationProvider constructor.
50
-	 */
51
-	public function __construct(
52
-		private IAppManager $appManager,
53
-		private FederatedShareProvider $federatedShareProvider,
54
-		private AddressHandler $addressHandler,
55
-		private IUserManager $userManager,
56
-		private IManager $shareManager,
57
-		private ICloudIdManager $cloudIdManager,
58
-		private IActivityManager $activityManager,
59
-		private INotificationManager $notificationManager,
60
-		private IURLGenerator $urlGenerator,
61
-		private ICloudFederationFactory $cloudFederationFactory,
62
-		private ICloudFederationProviderManager $cloudFederationProviderManager,
63
-		private IDBConnection $connection,
64
-		private IGroupManager $groupManager,
65
-		private IConfig $config,
66
-		private Manager $externalShareManager,
67
-		private LoggerInterface $logger,
68
-		private IFilenameValidator $filenameValidator,
69
-		private readonly IProviderFactory $shareProviderFactory,
70
-	) {
71
-	}
72
-
73
-	/**
74
-	 * @return string
75
-	 */
76
-	public function getShareType() {
77
-		return 'file';
78
-	}
79
-
80
-	/**
81
-	 * share received from another server
82
-	 *
83
-	 * @param ICloudFederationShare $share
84
-	 * @return string provider specific unique ID of the share
85
-	 *
86
-	 * @throws ProviderCouldNotAddShareException
87
-	 * @throws QueryException
88
-	 * @throws HintException
89
-	 * @since 14.0.0
90
-	 */
91
-	public function shareReceived(ICloudFederationShare $share) {
92
-		if (!$this->isS2SEnabled(true)) {
93
-			throw new ProviderCouldNotAddShareException('Server does not support federated cloud sharing', '', Http::STATUS_SERVICE_UNAVAILABLE);
94
-		}
95
-
96
-		$protocol = $share->getProtocol();
97
-		if ($protocol['name'] !== 'webdav') {
98
-			throw new ProviderCouldNotAddShareException('Unsupported protocol for data exchange.', '', Http::STATUS_NOT_IMPLEMENTED);
99
-		}
100
-
101
-		[$ownerUid, $remote] = $this->addressHandler->splitUserRemote($share->getOwner());
102
-		// for backward compatibility make sure that the remote url stored in the
103
-		// database ends with a trailing slash
104
-		if (!str_ends_with($remote, '/')) {
105
-			$remote = $remote . '/';
106
-		}
107
-
108
-		$token = $share->getShareSecret();
109
-		$name = $share->getResourceName();
110
-		$owner = $share->getOwnerDisplayName();
111
-		$sharedBy = $share->getSharedByDisplayName();
112
-		$shareWith = $share->getShareWith();
113
-		$remoteId = $share->getProviderId();
114
-		$sharedByFederatedId = $share->getSharedBy();
115
-		$ownerFederatedId = $share->getOwner();
116
-		$shareType = $this->mapShareTypeToNextcloud($share->getShareType());
117
-
118
-		// if no explicit information about the person who created the share was send
119
-		// we assume that the share comes from the owner
120
-		if ($sharedByFederatedId === null) {
121
-			$sharedBy = $owner;
122
-			$sharedByFederatedId = $ownerFederatedId;
123
-		}
124
-
125
-		if ($remote && $token && $name && $owner && $remoteId && $shareWith) {
126
-			if (!$this->filenameValidator->isFilenameValid($name)) {
127
-				throw new ProviderCouldNotAddShareException('The mountpoint name contains invalid characters.', '', Http::STATUS_BAD_REQUEST);
128
-			}
129
-
130
-			// FIXME this should be a method in the user management instead
131
-			if ($shareType === IShare::TYPE_USER) {
132
-				$this->logger->debug('shareWith before, ' . $shareWith, ['app' => 'files_sharing']);
133
-				Util::emitHook(
134
-					'\OCA\Files_Sharing\API\Server2Server',
135
-					'preLoginNameUsedAsUserName',
136
-					['uid' => &$shareWith]
137
-				);
138
-				$this->logger->debug('shareWith after, ' . $shareWith, ['app' => 'files_sharing']);
139
-
140
-				if (!$this->userManager->userExists($shareWith)) {
141
-					throw new ProviderCouldNotAddShareException('User does not exists', '', Http::STATUS_BAD_REQUEST);
142
-				}
143
-
144
-				\OC_Util::setupFS($shareWith);
145
-			}
146
-
147
-			if ($shareType === IShare::TYPE_GROUP && !$this->groupManager->groupExists($shareWith)) {
148
-				throw new ProviderCouldNotAddShareException('Group does not exists', '', Http::STATUS_BAD_REQUEST);
149
-			}
150
-
151
-			try {
152
-				$this->externalShareManager->addShare($remote, $token, '', $name, $owner, $shareType, false, $shareWith, $remoteId);
153
-				$shareId = Server::get(IDBConnection::class)->lastInsertId('*PREFIX*share_external');
154
-
155
-				// get DisplayName about the owner of the share
156
-				$ownerDisplayName = $this->getUserDisplayName($ownerFederatedId);
157
-
158
-				$trustedServers = null;
159
-				if ($this->appManager->isEnabledForAnyone('federation')
160
-					&& class_exists(TrustedServers::class)) {
161
-					try {
162
-						$trustedServers = Server::get(TrustedServers::class);
163
-					} catch (\Throwable $e) {
164
-						$this->logger->debug('Failed to create TrustedServers', ['exception' => $e]);
165
-					}
166
-				}
167
-
168
-
169
-				if ($shareType === IShare::TYPE_USER) {
170
-					$event = $this->activityManager->generateEvent();
171
-					$event->setApp('files_sharing')
172
-						->setType('remote_share')
173
-						->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/'), $ownerDisplayName])
174
-						->setAffectedUser($shareWith)
175
-						->setObject('remote_share', $shareId, $name);
176
-					Server::get(IActivityManager::class)->publish($event);
177
-					$this->notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $ownerDisplayName);
178
-
179
-					// If auto-accept is enabled, accept the share
180
-					if ($this->federatedShareProvider->isFederatedTrustedShareAutoAccept() && $trustedServers?->isTrustedServer($remote) === true) {
181
-						$this->externalShareManager->acceptShare($shareId, $shareWith);
182
-					}
183
-				} else {
184
-					$groupMembers = $this->groupManager->get($shareWith)->getUsers();
185
-					foreach ($groupMembers as $user) {
186
-						$event = $this->activityManager->generateEvent();
187
-						$event->setApp('files_sharing')
188
-							->setType('remote_share')
189
-							->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/'), $ownerDisplayName])
190
-							->setAffectedUser($user->getUID())
191
-							->setObject('remote_share', $shareId, $name);
192
-						Server::get(IActivityManager::class)->publish($event);
193
-						$this->notifyAboutNewShare($user->getUID(), $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $ownerDisplayName);
194
-
195
-						// If auto-accept is enabled, accept the share
196
-						if ($this->federatedShareProvider->isFederatedTrustedShareAutoAccept() && $trustedServers?->isTrustedServer($remote) === true) {
197
-							$this->externalShareManager->acceptShare($shareId, $user->getUID());
198
-						}
199
-					}
200
-				}
201
-
202
-				return $shareId;
203
-			} catch (\Exception $e) {
204
-				$this->logger->error('Server can not add remote share.', [
205
-					'app' => 'files_sharing',
206
-					'exception' => $e,
207
-				]);
208
-				throw new ProviderCouldNotAddShareException('internal server error, was not able to add share from ' . $remote, '', HTTP::STATUS_INTERNAL_SERVER_ERROR);
209
-			}
210
-		}
211
-
212
-		throw new ProviderCouldNotAddShareException('server can not add remote share, missing parameter', '', HTTP::STATUS_BAD_REQUEST);
213
-	}
214
-
215
-	/**
216
-	 * notification received from another server
217
-	 *
218
-	 * @param string $notificationType (e.g. SHARE_ACCEPTED)
219
-	 * @param string $providerId id of the share
220
-	 * @param array $notification payload of the notification
221
-	 * @return array<string> data send back to the sender
222
-	 *
223
-	 * @throws ActionNotSupportedException
224
-	 * @throws AuthenticationFailedException
225
-	 * @throws BadRequestException
226
-	 * @throws HintException
227
-	 * @since 14.0.0
228
-	 */
229
-	public function notificationReceived($notificationType, $providerId, array $notification) {
230
-		switch ($notificationType) {
231
-			case 'SHARE_ACCEPTED':
232
-				return $this->shareAccepted($providerId, $notification);
233
-			case 'SHARE_DECLINED':
234
-				return $this->shareDeclined($providerId, $notification);
235
-			case 'SHARE_UNSHARED':
236
-				return $this->unshare($providerId, $notification);
237
-			case 'REQUEST_RESHARE':
238
-				return $this->reshareRequested($providerId, $notification);
239
-			case 'RESHARE_UNDO':
240
-				return $this->undoReshare($providerId, $notification);
241
-			case 'RESHARE_CHANGE_PERMISSION':
242
-				return $this->updateResharePermissions($providerId, $notification);
243
-		}
244
-
245
-
246
-		throw new BadRequestException([$notificationType]);
247
-	}
248
-
249
-	/**
250
-	 * map OCM share type (strings) to Nextcloud internal share types (integer)
251
-	 *
252
-	 * @param string $shareType
253
-	 * @return int
254
-	 */
255
-	private function mapShareTypeToNextcloud($shareType) {
256
-		$result = IShare::TYPE_USER;
257
-		if ($shareType === 'group') {
258
-			$result = IShare::TYPE_GROUP;
259
-		}
260
-
261
-		return $result;
262
-	}
263
-
264
-	private function notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $displayName): void {
265
-		$notification = $this->notificationManager->createNotification();
266
-		$notification->setApp('files_sharing')
267
-			->setUser($shareWith)
268
-			->setDateTime(new \DateTime())
269
-			->setObject('remote_share', $shareId)
270
-			->setSubject('remote_share', [$ownerFederatedId, $sharedByFederatedId, trim($name, '/'), $displayName]);
271
-
272
-		$declineAction = $notification->createAction();
273
-		$declineAction->setLabel('decline')
274
-			->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'DELETE');
275
-		$notification->addAction($declineAction);
276
-
277
-		$acceptAction = $notification->createAction();
278
-		$acceptAction->setLabel('accept')
279
-			->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'POST');
280
-		$notification->addAction($acceptAction);
281
-
282
-		$this->notificationManager->notify($notification);
283
-	}
284
-
285
-	/**
286
-	 * process notification that the recipient accepted a share
287
-	 *
288
-	 * @param string $id
289
-	 * @param array $notification
290
-	 * @return array<string>
291
-	 * @throws ActionNotSupportedException
292
-	 * @throws AuthenticationFailedException
293
-	 * @throws BadRequestException
294
-	 * @throws HintException
295
-	 */
296
-	private function shareAccepted($id, array $notification) {
297
-		if (!$this->isS2SEnabled()) {
298
-			throw new ActionNotSupportedException('Server does not support federated cloud sharing');
299
-		}
300
-
301
-		if (!isset($notification['sharedSecret'])) {
302
-			throw new BadRequestException(['sharedSecret']);
303
-		}
304
-
305
-		$token = $notification['sharedSecret'];
306
-
307
-		$share = $this->federatedShareProvider->getShareById($id);
308
-
309
-		$this->verifyShare($share, $token);
310
-		$this->executeAcceptShare($share);
311
-		if ($share->getShareOwner() !== $share->getSharedBy()) {
312
-			[, $remote] = $this->addressHandler->splitUserRemote($share->getSharedBy());
313
-			$remoteId = $this->federatedShareProvider->getRemoteId($share);
314
-			$notification = $this->cloudFederationFactory->getCloudFederationNotification();
315
-			$notification->setMessage(
316
-				'SHARE_ACCEPTED',
317
-				'file',
318
-				$remoteId,
319
-				[
320
-					'sharedSecret' => $token,
321
-					'message' => 'Recipient accepted the re-share'
322
-				]
323
-
324
-			);
325
-			$this->cloudFederationProviderManager->sendNotification($remote, $notification);
326
-		}
327
-
328
-		return [];
329
-	}
330
-
331
-	/**
332
-	 * @param IShare $share
333
-	 * @throws ShareNotFound
334
-	 */
335
-	protected function executeAcceptShare(IShare $share) {
336
-		try {
337
-			$fileId = (int)$share->getNode()->getId();
338
-			[$file, $link] = $this->getFile($this->getCorrectUid($share), $fileId);
339
-		} catch (\Exception $e) {
340
-			throw new ShareNotFound();
341
-		}
342
-
343
-		$event = $this->activityManager->generateEvent();
344
-		$event->setApp('files_sharing')
345
-			->setType('remote_share')
346
-			->setAffectedUser($this->getCorrectUid($share))
347
-			->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_ACCEPTED, [$share->getSharedWith(), [$fileId => $file]])
348
-			->setObject('files', $fileId, $file)
349
-			->setLink($link);
350
-		$this->activityManager->publish($event);
351
-	}
352
-
353
-	/**
354
-	 * process notification that the recipient declined a share
355
-	 *
356
-	 * @param string $id
357
-	 * @param array $notification
358
-	 * @return array<string>
359
-	 * @throws ActionNotSupportedException
360
-	 * @throws AuthenticationFailedException
361
-	 * @throws BadRequestException
362
-	 * @throws ShareNotFound
363
-	 * @throws HintException
364
-	 *
365
-	 */
366
-	protected function shareDeclined($id, array $notification) {
367
-		if (!$this->isS2SEnabled()) {
368
-			throw new ActionNotSupportedException('Server does not support federated cloud sharing');
369
-		}
370
-
371
-		if (!isset($notification['sharedSecret'])) {
372
-			throw new BadRequestException(['sharedSecret']);
373
-		}
374
-
375
-		$token = $notification['sharedSecret'];
376
-
377
-		$share = $this->federatedShareProvider->getShareById($id);
378
-
379
-		$this->verifyShare($share, $token);
380
-
381
-		if ($share->getShareOwner() !== $share->getSharedBy()) {
382
-			[, $remote] = $this->addressHandler->splitUserRemote($share->getSharedBy());
383
-			$remoteId = $this->federatedShareProvider->getRemoteId($share);
384
-			$notification = $this->cloudFederationFactory->getCloudFederationNotification();
385
-			$notification->setMessage(
386
-				'SHARE_DECLINED',
387
-				'file',
388
-				$remoteId,
389
-				[
390
-					'sharedSecret' => $token,
391
-					'message' => 'Recipient declined the re-share'
392
-				]
393
-
394
-			);
395
-			$this->cloudFederationProviderManager->sendNotification($remote, $notification);
396
-		}
397
-
398
-		$this->executeDeclineShare($share);
399
-
400
-		return [];
401
-	}
402
-
403
-	/**
404
-	 * delete declined share and create a activity
405
-	 *
406
-	 * @param IShare $share
407
-	 * @throws ShareNotFound
408
-	 */
409
-	protected function executeDeclineShare(IShare $share) {
410
-		$this->federatedShareProvider->removeShareFromTable($share);
411
-
412
-		try {
413
-			$fileId = (int)$share->getNode()->getId();
414
-			[$file, $link] = $this->getFile($this->getCorrectUid($share), $fileId);
415
-		} catch (\Exception $e) {
416
-			throw new ShareNotFound();
417
-		}
418
-
419
-		$event = $this->activityManager->generateEvent();
420
-		$event->setApp('files_sharing')
421
-			->setType('remote_share')
422
-			->setAffectedUser($this->getCorrectUid($share))
423
-			->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_DECLINED, [$share->getSharedWith(), [$fileId => $file]])
424
-			->setObject('files', $fileId, $file)
425
-			->setLink($link);
426
-		$this->activityManager->publish($event);
427
-	}
428
-
429
-	/**
430
-	 * received the notification that the owner unshared a file from you
431
-	 *
432
-	 * @param string $id
433
-	 * @param array $notification
434
-	 * @return array<string>
435
-	 * @throws AuthenticationFailedException
436
-	 * @throws BadRequestException
437
-	 */
438
-	private function undoReshare($id, array $notification) {
439
-		if (!isset($notification['sharedSecret'])) {
440
-			throw new BadRequestException(['sharedSecret']);
441
-		}
442
-		$token = $notification['sharedSecret'];
443
-
444
-		$share = $this->federatedShareProvider->getShareById($id);
445
-
446
-		$this->verifyShare($share, $token);
447
-		$this->federatedShareProvider->removeShareFromTable($share);
448
-		return [];
449
-	}
450
-
451
-	/**
452
-	 * unshare file from self
453
-	 *
454
-	 * @param string $id
455
-	 * @param array $notification
456
-	 * @return array<string>
457
-	 * @throws ActionNotSupportedException
458
-	 * @throws BadRequestException
459
-	 */
460
-	private function unshare($id, array $notification) {
461
-		if (!$this->isS2SEnabled(true)) {
462
-			throw new ActionNotSupportedException('incoming shares disabled!');
463
-		}
464
-
465
-		if (!isset($notification['sharedSecret'])) {
466
-			throw new BadRequestException(['sharedSecret']);
467
-		}
468
-		$token = $notification['sharedSecret'];
469
-
470
-		$qb = $this->connection->getQueryBuilder();
471
-		$qb->select('*')
472
-			->from('share_external')
473
-			->where(
474
-				$qb->expr()->andX(
475
-					$qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
476
-					$qb->expr()->eq('share_token', $qb->createNamedParameter($token))
477
-				)
478
-			);
479
-
480
-		$result = $qb->executeQuery();
481
-		$share = $result->fetch();
482
-		$result->closeCursor();
483
-
484
-		if ($token && $id && !empty($share)) {
485
-			$remote = $this->cleanupRemote($share['remote']);
486
-
487
-			$owner = $this->cloudIdManager->getCloudId($share['owner'], $remote);
488
-			$mountpoint = $share['mountpoint'];
489
-			$user = $share['user'];
490
-
491
-			$qb = $this->connection->getQueryBuilder();
492
-			$qb->delete('share_external')
493
-				->where(
494
-					$qb->expr()->andX(
495
-						$qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
496
-						$qb->expr()->eq('share_token', $qb->createNamedParameter($token))
497
-					)
498
-				);
499
-
500
-			$qb->executeStatement();
501
-
502
-			// delete all child in case of a group share
503
-			$qb = $this->connection->getQueryBuilder();
504
-			$qb->delete('share_external')
505
-				->where($qb->expr()->eq('parent', $qb->createNamedParameter((int)$share['id'])));
506
-			$qb->executeStatement();
507
-
508
-			$ownerDisplayName = $this->getUserDisplayName($owner->getId());
509
-
510
-			if ((int)$share['share_type'] === IShare::TYPE_USER) {
511
-				if ($share['accepted']) {
512
-					$path = trim($mountpoint, '/');
513
-				} else {
514
-					$path = trim($share['name'], '/');
515
-				}
516
-				$notification = $this->notificationManager->createNotification();
517
-				$notification->setApp('files_sharing')
518
-					->setUser($share['user'])
519
-					->setObject('remote_share', (string)$share['id']);
520
-				$this->notificationManager->markProcessed($notification);
521
-
522
-				$event = $this->activityManager->generateEvent();
523
-				$event->setApp('files_sharing')
524
-					->setType('remote_share')
525
-					->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_UNSHARED, [$owner->getId(), $path, $ownerDisplayName])
526
-					->setAffectedUser($user)
527
-					->setObject('remote_share', (int)$share['id'], $path);
528
-				Server::get(IActivityManager::class)->publish($event);
529
-			}
530
-		}
531
-
532
-		return [];
533
-	}
534
-
535
-	private function cleanupRemote($remote) {
536
-		$remote = substr($remote, strpos($remote, '://') + 3);
537
-
538
-		return rtrim($remote, '/');
539
-	}
540
-
541
-	/**
542
-	 * recipient of a share request to re-share the file with another user
543
-	 *
544
-	 * @param string $id
545
-	 * @param array $notification
546
-	 * @return array<string>
547
-	 * @throws AuthenticationFailedException
548
-	 * @throws BadRequestException
549
-	 * @throws ProviderCouldNotAddShareException
550
-	 * @throws ShareNotFound
551
-	 */
552
-	protected function reshareRequested($id, array $notification) {
553
-		if (!isset($notification['sharedSecret'])) {
554
-			throw new BadRequestException(['sharedSecret']);
555
-		}
556
-		$token = $notification['sharedSecret'];
557
-
558
-		if (!isset($notification['shareWith'])) {
559
-			throw new BadRequestException(['shareWith']);
560
-		}
561
-		$shareWith = $notification['shareWith'];
562
-
563
-		if (!isset($notification['senderId'])) {
564
-			throw new BadRequestException(['senderId']);
565
-		}
566
-		$senderId = $notification['senderId'];
567
-
568
-		$share = $this->federatedShareProvider->getShareById($id);
569
-
570
-		// We have to respect the default share permissions
571
-		$permissions = $share->getPermissions() & (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
572
-		$share->setPermissions($permissions);
573
-
574
-		// don't allow to share a file back to the owner
575
-		try {
576
-			[$user, $remote] = $this->addressHandler->splitUserRemote($shareWith);
577
-			$owner = $share->getShareOwner();
578
-			$currentServer = $this->addressHandler->generateRemoteURL();
579
-			if ($this->addressHandler->compareAddresses($user, $remote, $owner, $currentServer)) {
580
-				throw new ProviderCouldNotAddShareException('Resharing back to the owner is not allowed: ' . $id);
581
-			}
582
-		} catch (\Exception $e) {
583
-			throw new ProviderCouldNotAddShareException($e->getMessage());
584
-		}
585
-
586
-		$this->verifyShare($share, $token);
587
-
588
-		// check if re-sharing is allowed
589
-		if ($share->getPermissions() & Constants::PERMISSION_SHARE) {
590
-			// the recipient of the initial share is now the initiator for the re-share
591
-			$share->setSharedBy($share->getSharedWith());
592
-			$share->setSharedWith($shareWith);
593
-			$result = $this->federatedShareProvider->create($share);
594
-			$this->federatedShareProvider->storeRemoteId((int)$result->getId(), $senderId);
595
-			return ['token' => $result->getToken(), 'providerId' => $result->getId()];
596
-		} else {
597
-			throw new ProviderCouldNotAddShareException('resharing not allowed for share: ' . $id);
598
-		}
599
-	}
600
-
601
-	/**
602
-	 * update permission of a re-share so that the share dialog shows the right
603
-	 * permission if the owner or the sender changes the permission
604
-	 *
605
-	 * @param string $id
606
-	 * @param array $notification
607
-	 * @return array<string>
608
-	 * @throws AuthenticationFailedException
609
-	 * @throws BadRequestException
610
-	 */
611
-	protected function updateResharePermissions($id, array $notification) {
612
-		throw new HintException('Updating reshares not allowed');
613
-	}
614
-
615
-	/**
616
-	 * translate OCM Permissions to Nextcloud permissions
617
-	 *
618
-	 * @param array $ocmPermissions
619
-	 * @return int
620
-	 * @throws BadRequestException
621
-	 */
622
-	protected function ocmPermissions2ncPermissions(array $ocmPermissions) {
623
-		$ncPermissions = 0;
624
-		foreach ($ocmPermissions as $permission) {
625
-			switch (strtolower($permission)) {
626
-				case 'read':
627
-					$ncPermissions += Constants::PERMISSION_READ;
628
-					break;
629
-				case 'write':
630
-					$ncPermissions += Constants::PERMISSION_CREATE + Constants::PERMISSION_UPDATE;
631
-					break;
632
-				case 'share':
633
-					$ncPermissions += Constants::PERMISSION_SHARE;
634
-					break;
635
-				default:
636
-					throw new BadRequestException(['permission']);
637
-			}
638
-		}
639
-
640
-		return $ncPermissions;
641
-	}
642
-
643
-	/**
644
-	 * update permissions in database
645
-	 *
646
-	 * @param IShare $share
647
-	 * @param int $permissions
648
-	 */
649
-	protected function updatePermissionsInDatabase(IShare $share, $permissions) {
650
-		$query = $this->connection->getQueryBuilder();
651
-		$query->update('share')
652
-			->where($query->expr()->eq('id', $query->createNamedParameter($share->getId())))
653
-			->set('permissions', $query->createNamedParameter($permissions))
654
-			->executeStatement();
655
-	}
656
-
657
-
658
-	/**
659
-	 * get file
660
-	 *
661
-	 * @param string $user
662
-	 * @param int $fileSource
663
-	 * @return array with internal path of the file and a absolute link to it
664
-	 */
665
-	private function getFile($user, $fileSource) {
666
-		\OC_Util::setupFS($user);
667
-
668
-		try {
669
-			$file = Filesystem::getPath($fileSource);
670
-		} catch (NotFoundException $e) {
671
-			$file = null;
672
-		}
673
-		$args = Filesystem::is_dir($file) ? ['dir' => $file] : ['dir' => dirname($file), 'scrollto' => $file];
674
-		$link = Util::linkToAbsolute('files', 'index.php', $args);
675
-
676
-		return [$file, $link];
677
-	}
678
-
679
-	/**
680
-	 * check if we are the initiator or the owner of a re-share and return the correct UID
681
-	 *
682
-	 * @param IShare $share
683
-	 * @return string
684
-	 */
685
-	protected function getCorrectUid(IShare $share) {
686
-		if ($this->userManager->userExists($share->getShareOwner())) {
687
-			return $share->getShareOwner();
688
-		}
689
-
690
-		return $share->getSharedBy();
691
-	}
692
-
693
-
694
-
695
-	/**
696
-	 * check if we got the right share
697
-	 *
698
-	 * @param IShare $share
699
-	 * @param string $token
700
-	 * @return bool
701
-	 * @throws AuthenticationFailedException
702
-	 */
703
-	protected function verifyShare(IShare $share, $token) {
704
-		if (
705
-			$share->getShareType() === IShare::TYPE_REMOTE &&
706
-			$share->getToken() === $token
707
-		) {
708
-			return true;
709
-		}
710
-
711
-		if ($share->getShareType() === IShare::TYPE_CIRCLE) {
712
-			try {
713
-				$knownShare = $this->shareManager->getShareByToken($token);
714
-				if ($knownShare->getId() === $share->getId()) {
715
-					return true;
716
-				}
717
-			} catch (ShareNotFound $e) {
718
-			}
719
-		}
720
-
721
-		throw new AuthenticationFailedException();
722
-	}
723
-
724
-
725
-
726
-	/**
727
-	 * check if server-to-server sharing is enabled
728
-	 *
729
-	 * @param bool $incoming
730
-	 * @return bool
731
-	 */
732
-	private function isS2SEnabled($incoming = false) {
733
-		$result = $this->appManager->isEnabledForUser('files_sharing');
734
-
735
-		if ($incoming) {
736
-			$result = $result && $this->federatedShareProvider->isIncomingServer2serverShareEnabled();
737
-		} else {
738
-			$result = $result && $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
739
-		}
740
-
741
-		return $result;
742
-	}
743
-
744
-
745
-	/**
746
-	 * get the supported share types, e.g. "user", "group", etc.
747
-	 *
748
-	 * @return array
749
-	 *
750
-	 * @since 14.0.0
751
-	 */
752
-	public function getSupportedShareTypes() {
753
-		return ['user', 'group'];
754
-	}
755
-
756
-
757
-	public function getUserDisplayName(string $userId): string {
758
-		// check if gss is enabled and available
759
-		if (!$this->appManager->isEnabledForAnyone('globalsiteselector')
760
-			|| !class_exists('\OCA\GlobalSiteSelector\Service\SlaveService')) {
761
-			return '';
762
-		}
763
-
764
-		try {
765
-			$slaveService = Server::get(SlaveService::class);
766
-		} catch (\Throwable $e) {
767
-			Server::get(LoggerInterface::class)->error(
768
-				$e->getMessage(),
769
-				['exception' => $e]
770
-			);
771
-			return '';
772
-		}
773
-
774
-		return $slaveService->getUserDisplayName($this->cloudIdManager->removeProtocolFromUrl($userId), false);
775
-	}
776
-
777
-	/**
778
-	 * @inheritDoc
779
-	 *
780
-	 * @param string $sharedSecret
781
-	 * @param array $payload
782
-	 * @return string
783
-	 */
784
-	public function getFederationIdFromSharedSecret(
785
-		#[SensitiveParameter]
786
-		string $sharedSecret,
787
-		array $payload,
788
-	): string {
789
-		$provider = $this->shareProviderFactory->getProviderForType(IShare::TYPE_REMOTE);
790
-		try {
791
-			$share = $provider->getShareByToken($sharedSecret);
792
-		} catch (ShareNotFound) {
793
-			// Maybe we're dealing with a share federated from another server
794
-			$share = $this->externalShareManager->getShareByToken($sharedSecret);
795
-			if ($share === false) {
796
-				return '';
797
-			}
798
-
799
-			return $share['user'] . '@' . $share['remote'];
800
-		}
801
-
802
-		// if uid_owner is a local account, the request comes from the recipient
803
-		// if not, request comes from the instance that owns the share and recipient is the re-sharer
804
-		if ($this->userManager->get($share->getShareOwner()) !== null) {
805
-			return $share->getSharedWith();
806
-		} else {
807
-			return $share->getShareOwner();
808
-		}
809
-	}
48
+    /**
49
+     * CloudFederationProvider constructor.
50
+     */
51
+    public function __construct(
52
+        private IAppManager $appManager,
53
+        private FederatedShareProvider $federatedShareProvider,
54
+        private AddressHandler $addressHandler,
55
+        private IUserManager $userManager,
56
+        private IManager $shareManager,
57
+        private ICloudIdManager $cloudIdManager,
58
+        private IActivityManager $activityManager,
59
+        private INotificationManager $notificationManager,
60
+        private IURLGenerator $urlGenerator,
61
+        private ICloudFederationFactory $cloudFederationFactory,
62
+        private ICloudFederationProviderManager $cloudFederationProviderManager,
63
+        private IDBConnection $connection,
64
+        private IGroupManager $groupManager,
65
+        private IConfig $config,
66
+        private Manager $externalShareManager,
67
+        private LoggerInterface $logger,
68
+        private IFilenameValidator $filenameValidator,
69
+        private readonly IProviderFactory $shareProviderFactory,
70
+    ) {
71
+    }
72
+
73
+    /**
74
+     * @return string
75
+     */
76
+    public function getShareType() {
77
+        return 'file';
78
+    }
79
+
80
+    /**
81
+     * share received from another server
82
+     *
83
+     * @param ICloudFederationShare $share
84
+     * @return string provider specific unique ID of the share
85
+     *
86
+     * @throws ProviderCouldNotAddShareException
87
+     * @throws QueryException
88
+     * @throws HintException
89
+     * @since 14.0.0
90
+     */
91
+    public function shareReceived(ICloudFederationShare $share) {
92
+        if (!$this->isS2SEnabled(true)) {
93
+            throw new ProviderCouldNotAddShareException('Server does not support federated cloud sharing', '', Http::STATUS_SERVICE_UNAVAILABLE);
94
+        }
95
+
96
+        $protocol = $share->getProtocol();
97
+        if ($protocol['name'] !== 'webdav') {
98
+            throw new ProviderCouldNotAddShareException('Unsupported protocol for data exchange.', '', Http::STATUS_NOT_IMPLEMENTED);
99
+        }
100
+
101
+        [$ownerUid, $remote] = $this->addressHandler->splitUserRemote($share->getOwner());
102
+        // for backward compatibility make sure that the remote url stored in the
103
+        // database ends with a trailing slash
104
+        if (!str_ends_with($remote, '/')) {
105
+            $remote = $remote . '/';
106
+        }
107
+
108
+        $token = $share->getShareSecret();
109
+        $name = $share->getResourceName();
110
+        $owner = $share->getOwnerDisplayName();
111
+        $sharedBy = $share->getSharedByDisplayName();
112
+        $shareWith = $share->getShareWith();
113
+        $remoteId = $share->getProviderId();
114
+        $sharedByFederatedId = $share->getSharedBy();
115
+        $ownerFederatedId = $share->getOwner();
116
+        $shareType = $this->mapShareTypeToNextcloud($share->getShareType());
117
+
118
+        // if no explicit information about the person who created the share was send
119
+        // we assume that the share comes from the owner
120
+        if ($sharedByFederatedId === null) {
121
+            $sharedBy = $owner;
122
+            $sharedByFederatedId = $ownerFederatedId;
123
+        }
124
+
125
+        if ($remote && $token && $name && $owner && $remoteId && $shareWith) {
126
+            if (!$this->filenameValidator->isFilenameValid($name)) {
127
+                throw new ProviderCouldNotAddShareException('The mountpoint name contains invalid characters.', '', Http::STATUS_BAD_REQUEST);
128
+            }
129
+
130
+            // FIXME this should be a method in the user management instead
131
+            if ($shareType === IShare::TYPE_USER) {
132
+                $this->logger->debug('shareWith before, ' . $shareWith, ['app' => 'files_sharing']);
133
+                Util::emitHook(
134
+                    '\OCA\Files_Sharing\API\Server2Server',
135
+                    'preLoginNameUsedAsUserName',
136
+                    ['uid' => &$shareWith]
137
+                );
138
+                $this->logger->debug('shareWith after, ' . $shareWith, ['app' => 'files_sharing']);
139
+
140
+                if (!$this->userManager->userExists($shareWith)) {
141
+                    throw new ProviderCouldNotAddShareException('User does not exists', '', Http::STATUS_BAD_REQUEST);
142
+                }
143
+
144
+                \OC_Util::setupFS($shareWith);
145
+            }
146
+
147
+            if ($shareType === IShare::TYPE_GROUP && !$this->groupManager->groupExists($shareWith)) {
148
+                throw new ProviderCouldNotAddShareException('Group does not exists', '', Http::STATUS_BAD_REQUEST);
149
+            }
150
+
151
+            try {
152
+                $this->externalShareManager->addShare($remote, $token, '', $name, $owner, $shareType, false, $shareWith, $remoteId);
153
+                $shareId = Server::get(IDBConnection::class)->lastInsertId('*PREFIX*share_external');
154
+
155
+                // get DisplayName about the owner of the share
156
+                $ownerDisplayName = $this->getUserDisplayName($ownerFederatedId);
157
+
158
+                $trustedServers = null;
159
+                if ($this->appManager->isEnabledForAnyone('federation')
160
+                    && class_exists(TrustedServers::class)) {
161
+                    try {
162
+                        $trustedServers = Server::get(TrustedServers::class);
163
+                    } catch (\Throwable $e) {
164
+                        $this->logger->debug('Failed to create TrustedServers', ['exception' => $e]);
165
+                    }
166
+                }
167
+
168
+
169
+                if ($shareType === IShare::TYPE_USER) {
170
+                    $event = $this->activityManager->generateEvent();
171
+                    $event->setApp('files_sharing')
172
+                        ->setType('remote_share')
173
+                        ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/'), $ownerDisplayName])
174
+                        ->setAffectedUser($shareWith)
175
+                        ->setObject('remote_share', $shareId, $name);
176
+                    Server::get(IActivityManager::class)->publish($event);
177
+                    $this->notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $ownerDisplayName);
178
+
179
+                    // If auto-accept is enabled, accept the share
180
+                    if ($this->federatedShareProvider->isFederatedTrustedShareAutoAccept() && $trustedServers?->isTrustedServer($remote) === true) {
181
+                        $this->externalShareManager->acceptShare($shareId, $shareWith);
182
+                    }
183
+                } else {
184
+                    $groupMembers = $this->groupManager->get($shareWith)->getUsers();
185
+                    foreach ($groupMembers as $user) {
186
+                        $event = $this->activityManager->generateEvent();
187
+                        $event->setApp('files_sharing')
188
+                            ->setType('remote_share')
189
+                            ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/'), $ownerDisplayName])
190
+                            ->setAffectedUser($user->getUID())
191
+                            ->setObject('remote_share', $shareId, $name);
192
+                        Server::get(IActivityManager::class)->publish($event);
193
+                        $this->notifyAboutNewShare($user->getUID(), $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $ownerDisplayName);
194
+
195
+                        // If auto-accept is enabled, accept the share
196
+                        if ($this->federatedShareProvider->isFederatedTrustedShareAutoAccept() && $trustedServers?->isTrustedServer($remote) === true) {
197
+                            $this->externalShareManager->acceptShare($shareId, $user->getUID());
198
+                        }
199
+                    }
200
+                }
201
+
202
+                return $shareId;
203
+            } catch (\Exception $e) {
204
+                $this->logger->error('Server can not add remote share.', [
205
+                    'app' => 'files_sharing',
206
+                    'exception' => $e,
207
+                ]);
208
+                throw new ProviderCouldNotAddShareException('internal server error, was not able to add share from ' . $remote, '', HTTP::STATUS_INTERNAL_SERVER_ERROR);
209
+            }
210
+        }
211
+
212
+        throw new ProviderCouldNotAddShareException('server can not add remote share, missing parameter', '', HTTP::STATUS_BAD_REQUEST);
213
+    }
214
+
215
+    /**
216
+     * notification received from another server
217
+     *
218
+     * @param string $notificationType (e.g. SHARE_ACCEPTED)
219
+     * @param string $providerId id of the share
220
+     * @param array $notification payload of the notification
221
+     * @return array<string> data send back to the sender
222
+     *
223
+     * @throws ActionNotSupportedException
224
+     * @throws AuthenticationFailedException
225
+     * @throws BadRequestException
226
+     * @throws HintException
227
+     * @since 14.0.0
228
+     */
229
+    public function notificationReceived($notificationType, $providerId, array $notification) {
230
+        switch ($notificationType) {
231
+            case 'SHARE_ACCEPTED':
232
+                return $this->shareAccepted($providerId, $notification);
233
+            case 'SHARE_DECLINED':
234
+                return $this->shareDeclined($providerId, $notification);
235
+            case 'SHARE_UNSHARED':
236
+                return $this->unshare($providerId, $notification);
237
+            case 'REQUEST_RESHARE':
238
+                return $this->reshareRequested($providerId, $notification);
239
+            case 'RESHARE_UNDO':
240
+                return $this->undoReshare($providerId, $notification);
241
+            case 'RESHARE_CHANGE_PERMISSION':
242
+                return $this->updateResharePermissions($providerId, $notification);
243
+        }
244
+
245
+
246
+        throw new BadRequestException([$notificationType]);
247
+    }
248
+
249
+    /**
250
+     * map OCM share type (strings) to Nextcloud internal share types (integer)
251
+     *
252
+     * @param string $shareType
253
+     * @return int
254
+     */
255
+    private function mapShareTypeToNextcloud($shareType) {
256
+        $result = IShare::TYPE_USER;
257
+        if ($shareType === 'group') {
258
+            $result = IShare::TYPE_GROUP;
259
+        }
260
+
261
+        return $result;
262
+    }
263
+
264
+    private function notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $displayName): void {
265
+        $notification = $this->notificationManager->createNotification();
266
+        $notification->setApp('files_sharing')
267
+            ->setUser($shareWith)
268
+            ->setDateTime(new \DateTime())
269
+            ->setObject('remote_share', $shareId)
270
+            ->setSubject('remote_share', [$ownerFederatedId, $sharedByFederatedId, trim($name, '/'), $displayName]);
271
+
272
+        $declineAction = $notification->createAction();
273
+        $declineAction->setLabel('decline')
274
+            ->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'DELETE');
275
+        $notification->addAction($declineAction);
276
+
277
+        $acceptAction = $notification->createAction();
278
+        $acceptAction->setLabel('accept')
279
+            ->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'POST');
280
+        $notification->addAction($acceptAction);
281
+
282
+        $this->notificationManager->notify($notification);
283
+    }
284
+
285
+    /**
286
+     * process notification that the recipient accepted a share
287
+     *
288
+     * @param string $id
289
+     * @param array $notification
290
+     * @return array<string>
291
+     * @throws ActionNotSupportedException
292
+     * @throws AuthenticationFailedException
293
+     * @throws BadRequestException
294
+     * @throws HintException
295
+     */
296
+    private function shareAccepted($id, array $notification) {
297
+        if (!$this->isS2SEnabled()) {
298
+            throw new ActionNotSupportedException('Server does not support federated cloud sharing');
299
+        }
300
+
301
+        if (!isset($notification['sharedSecret'])) {
302
+            throw new BadRequestException(['sharedSecret']);
303
+        }
304
+
305
+        $token = $notification['sharedSecret'];
306
+
307
+        $share = $this->federatedShareProvider->getShareById($id);
308
+
309
+        $this->verifyShare($share, $token);
310
+        $this->executeAcceptShare($share);
311
+        if ($share->getShareOwner() !== $share->getSharedBy()) {
312
+            [, $remote] = $this->addressHandler->splitUserRemote($share->getSharedBy());
313
+            $remoteId = $this->federatedShareProvider->getRemoteId($share);
314
+            $notification = $this->cloudFederationFactory->getCloudFederationNotification();
315
+            $notification->setMessage(
316
+                'SHARE_ACCEPTED',
317
+                'file',
318
+                $remoteId,
319
+                [
320
+                    'sharedSecret' => $token,
321
+                    'message' => 'Recipient accepted the re-share'
322
+                ]
323
+
324
+            );
325
+            $this->cloudFederationProviderManager->sendNotification($remote, $notification);
326
+        }
327
+
328
+        return [];
329
+    }
330
+
331
+    /**
332
+     * @param IShare $share
333
+     * @throws ShareNotFound
334
+     */
335
+    protected function executeAcceptShare(IShare $share) {
336
+        try {
337
+            $fileId = (int)$share->getNode()->getId();
338
+            [$file, $link] = $this->getFile($this->getCorrectUid($share), $fileId);
339
+        } catch (\Exception $e) {
340
+            throw new ShareNotFound();
341
+        }
342
+
343
+        $event = $this->activityManager->generateEvent();
344
+        $event->setApp('files_sharing')
345
+            ->setType('remote_share')
346
+            ->setAffectedUser($this->getCorrectUid($share))
347
+            ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_ACCEPTED, [$share->getSharedWith(), [$fileId => $file]])
348
+            ->setObject('files', $fileId, $file)
349
+            ->setLink($link);
350
+        $this->activityManager->publish($event);
351
+    }
352
+
353
+    /**
354
+     * process notification that the recipient declined a share
355
+     *
356
+     * @param string $id
357
+     * @param array $notification
358
+     * @return array<string>
359
+     * @throws ActionNotSupportedException
360
+     * @throws AuthenticationFailedException
361
+     * @throws BadRequestException
362
+     * @throws ShareNotFound
363
+     * @throws HintException
364
+     *
365
+     */
366
+    protected function shareDeclined($id, array $notification) {
367
+        if (!$this->isS2SEnabled()) {
368
+            throw new ActionNotSupportedException('Server does not support federated cloud sharing');
369
+        }
370
+
371
+        if (!isset($notification['sharedSecret'])) {
372
+            throw new BadRequestException(['sharedSecret']);
373
+        }
374
+
375
+        $token = $notification['sharedSecret'];
376
+
377
+        $share = $this->federatedShareProvider->getShareById($id);
378
+
379
+        $this->verifyShare($share, $token);
380
+
381
+        if ($share->getShareOwner() !== $share->getSharedBy()) {
382
+            [, $remote] = $this->addressHandler->splitUserRemote($share->getSharedBy());
383
+            $remoteId = $this->federatedShareProvider->getRemoteId($share);
384
+            $notification = $this->cloudFederationFactory->getCloudFederationNotification();
385
+            $notification->setMessage(
386
+                'SHARE_DECLINED',
387
+                'file',
388
+                $remoteId,
389
+                [
390
+                    'sharedSecret' => $token,
391
+                    'message' => 'Recipient declined the re-share'
392
+                ]
393
+
394
+            );
395
+            $this->cloudFederationProviderManager->sendNotification($remote, $notification);
396
+        }
397
+
398
+        $this->executeDeclineShare($share);
399
+
400
+        return [];
401
+    }
402
+
403
+    /**
404
+     * delete declined share and create a activity
405
+     *
406
+     * @param IShare $share
407
+     * @throws ShareNotFound
408
+     */
409
+    protected function executeDeclineShare(IShare $share) {
410
+        $this->federatedShareProvider->removeShareFromTable($share);
411
+
412
+        try {
413
+            $fileId = (int)$share->getNode()->getId();
414
+            [$file, $link] = $this->getFile($this->getCorrectUid($share), $fileId);
415
+        } catch (\Exception $e) {
416
+            throw new ShareNotFound();
417
+        }
418
+
419
+        $event = $this->activityManager->generateEvent();
420
+        $event->setApp('files_sharing')
421
+            ->setType('remote_share')
422
+            ->setAffectedUser($this->getCorrectUid($share))
423
+            ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_DECLINED, [$share->getSharedWith(), [$fileId => $file]])
424
+            ->setObject('files', $fileId, $file)
425
+            ->setLink($link);
426
+        $this->activityManager->publish($event);
427
+    }
428
+
429
+    /**
430
+     * received the notification that the owner unshared a file from you
431
+     *
432
+     * @param string $id
433
+     * @param array $notification
434
+     * @return array<string>
435
+     * @throws AuthenticationFailedException
436
+     * @throws BadRequestException
437
+     */
438
+    private function undoReshare($id, array $notification) {
439
+        if (!isset($notification['sharedSecret'])) {
440
+            throw new BadRequestException(['sharedSecret']);
441
+        }
442
+        $token = $notification['sharedSecret'];
443
+
444
+        $share = $this->federatedShareProvider->getShareById($id);
445
+
446
+        $this->verifyShare($share, $token);
447
+        $this->federatedShareProvider->removeShareFromTable($share);
448
+        return [];
449
+    }
450
+
451
+    /**
452
+     * unshare file from self
453
+     *
454
+     * @param string $id
455
+     * @param array $notification
456
+     * @return array<string>
457
+     * @throws ActionNotSupportedException
458
+     * @throws BadRequestException
459
+     */
460
+    private function unshare($id, array $notification) {
461
+        if (!$this->isS2SEnabled(true)) {
462
+            throw new ActionNotSupportedException('incoming shares disabled!');
463
+        }
464
+
465
+        if (!isset($notification['sharedSecret'])) {
466
+            throw new BadRequestException(['sharedSecret']);
467
+        }
468
+        $token = $notification['sharedSecret'];
469
+
470
+        $qb = $this->connection->getQueryBuilder();
471
+        $qb->select('*')
472
+            ->from('share_external')
473
+            ->where(
474
+                $qb->expr()->andX(
475
+                    $qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
476
+                    $qb->expr()->eq('share_token', $qb->createNamedParameter($token))
477
+                )
478
+            );
479
+
480
+        $result = $qb->executeQuery();
481
+        $share = $result->fetch();
482
+        $result->closeCursor();
483
+
484
+        if ($token && $id && !empty($share)) {
485
+            $remote = $this->cleanupRemote($share['remote']);
486
+
487
+            $owner = $this->cloudIdManager->getCloudId($share['owner'], $remote);
488
+            $mountpoint = $share['mountpoint'];
489
+            $user = $share['user'];
490
+
491
+            $qb = $this->connection->getQueryBuilder();
492
+            $qb->delete('share_external')
493
+                ->where(
494
+                    $qb->expr()->andX(
495
+                        $qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
496
+                        $qb->expr()->eq('share_token', $qb->createNamedParameter($token))
497
+                    )
498
+                );
499
+
500
+            $qb->executeStatement();
501
+
502
+            // delete all child in case of a group share
503
+            $qb = $this->connection->getQueryBuilder();
504
+            $qb->delete('share_external')
505
+                ->where($qb->expr()->eq('parent', $qb->createNamedParameter((int)$share['id'])));
506
+            $qb->executeStatement();
507
+
508
+            $ownerDisplayName = $this->getUserDisplayName($owner->getId());
509
+
510
+            if ((int)$share['share_type'] === IShare::TYPE_USER) {
511
+                if ($share['accepted']) {
512
+                    $path = trim($mountpoint, '/');
513
+                } else {
514
+                    $path = trim($share['name'], '/');
515
+                }
516
+                $notification = $this->notificationManager->createNotification();
517
+                $notification->setApp('files_sharing')
518
+                    ->setUser($share['user'])
519
+                    ->setObject('remote_share', (string)$share['id']);
520
+                $this->notificationManager->markProcessed($notification);
521
+
522
+                $event = $this->activityManager->generateEvent();
523
+                $event->setApp('files_sharing')
524
+                    ->setType('remote_share')
525
+                    ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_UNSHARED, [$owner->getId(), $path, $ownerDisplayName])
526
+                    ->setAffectedUser($user)
527
+                    ->setObject('remote_share', (int)$share['id'], $path);
528
+                Server::get(IActivityManager::class)->publish($event);
529
+            }
530
+        }
531
+
532
+        return [];
533
+    }
534
+
535
+    private function cleanupRemote($remote) {
536
+        $remote = substr($remote, strpos($remote, '://') + 3);
537
+
538
+        return rtrim($remote, '/');
539
+    }
540
+
541
+    /**
542
+     * recipient of a share request to re-share the file with another user
543
+     *
544
+     * @param string $id
545
+     * @param array $notification
546
+     * @return array<string>
547
+     * @throws AuthenticationFailedException
548
+     * @throws BadRequestException
549
+     * @throws ProviderCouldNotAddShareException
550
+     * @throws ShareNotFound
551
+     */
552
+    protected function reshareRequested($id, array $notification) {
553
+        if (!isset($notification['sharedSecret'])) {
554
+            throw new BadRequestException(['sharedSecret']);
555
+        }
556
+        $token = $notification['sharedSecret'];
557
+
558
+        if (!isset($notification['shareWith'])) {
559
+            throw new BadRequestException(['shareWith']);
560
+        }
561
+        $shareWith = $notification['shareWith'];
562
+
563
+        if (!isset($notification['senderId'])) {
564
+            throw new BadRequestException(['senderId']);
565
+        }
566
+        $senderId = $notification['senderId'];
567
+
568
+        $share = $this->federatedShareProvider->getShareById($id);
569
+
570
+        // We have to respect the default share permissions
571
+        $permissions = $share->getPermissions() & (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
572
+        $share->setPermissions($permissions);
573
+
574
+        // don't allow to share a file back to the owner
575
+        try {
576
+            [$user, $remote] = $this->addressHandler->splitUserRemote($shareWith);
577
+            $owner = $share->getShareOwner();
578
+            $currentServer = $this->addressHandler->generateRemoteURL();
579
+            if ($this->addressHandler->compareAddresses($user, $remote, $owner, $currentServer)) {
580
+                throw new ProviderCouldNotAddShareException('Resharing back to the owner is not allowed: ' . $id);
581
+            }
582
+        } catch (\Exception $e) {
583
+            throw new ProviderCouldNotAddShareException($e->getMessage());
584
+        }
585
+
586
+        $this->verifyShare($share, $token);
587
+
588
+        // check if re-sharing is allowed
589
+        if ($share->getPermissions() & Constants::PERMISSION_SHARE) {
590
+            // the recipient of the initial share is now the initiator for the re-share
591
+            $share->setSharedBy($share->getSharedWith());
592
+            $share->setSharedWith($shareWith);
593
+            $result = $this->federatedShareProvider->create($share);
594
+            $this->federatedShareProvider->storeRemoteId((int)$result->getId(), $senderId);
595
+            return ['token' => $result->getToken(), 'providerId' => $result->getId()];
596
+        } else {
597
+            throw new ProviderCouldNotAddShareException('resharing not allowed for share: ' . $id);
598
+        }
599
+    }
600
+
601
+    /**
602
+     * update permission of a re-share so that the share dialog shows the right
603
+     * permission if the owner or the sender changes the permission
604
+     *
605
+     * @param string $id
606
+     * @param array $notification
607
+     * @return array<string>
608
+     * @throws AuthenticationFailedException
609
+     * @throws BadRequestException
610
+     */
611
+    protected function updateResharePermissions($id, array $notification) {
612
+        throw new HintException('Updating reshares not allowed');
613
+    }
614
+
615
+    /**
616
+     * translate OCM Permissions to Nextcloud permissions
617
+     *
618
+     * @param array $ocmPermissions
619
+     * @return int
620
+     * @throws BadRequestException
621
+     */
622
+    protected function ocmPermissions2ncPermissions(array $ocmPermissions) {
623
+        $ncPermissions = 0;
624
+        foreach ($ocmPermissions as $permission) {
625
+            switch (strtolower($permission)) {
626
+                case 'read':
627
+                    $ncPermissions += Constants::PERMISSION_READ;
628
+                    break;
629
+                case 'write':
630
+                    $ncPermissions += Constants::PERMISSION_CREATE + Constants::PERMISSION_UPDATE;
631
+                    break;
632
+                case 'share':
633
+                    $ncPermissions += Constants::PERMISSION_SHARE;
634
+                    break;
635
+                default:
636
+                    throw new BadRequestException(['permission']);
637
+            }
638
+        }
639
+
640
+        return $ncPermissions;
641
+    }
642
+
643
+    /**
644
+     * update permissions in database
645
+     *
646
+     * @param IShare $share
647
+     * @param int $permissions
648
+     */
649
+    protected function updatePermissionsInDatabase(IShare $share, $permissions) {
650
+        $query = $this->connection->getQueryBuilder();
651
+        $query->update('share')
652
+            ->where($query->expr()->eq('id', $query->createNamedParameter($share->getId())))
653
+            ->set('permissions', $query->createNamedParameter($permissions))
654
+            ->executeStatement();
655
+    }
656
+
657
+
658
+    /**
659
+     * get file
660
+     *
661
+     * @param string $user
662
+     * @param int $fileSource
663
+     * @return array with internal path of the file and a absolute link to it
664
+     */
665
+    private function getFile($user, $fileSource) {
666
+        \OC_Util::setupFS($user);
667
+
668
+        try {
669
+            $file = Filesystem::getPath($fileSource);
670
+        } catch (NotFoundException $e) {
671
+            $file = null;
672
+        }
673
+        $args = Filesystem::is_dir($file) ? ['dir' => $file] : ['dir' => dirname($file), 'scrollto' => $file];
674
+        $link = Util::linkToAbsolute('files', 'index.php', $args);
675
+
676
+        return [$file, $link];
677
+    }
678
+
679
+    /**
680
+     * check if we are the initiator or the owner of a re-share and return the correct UID
681
+     *
682
+     * @param IShare $share
683
+     * @return string
684
+     */
685
+    protected function getCorrectUid(IShare $share) {
686
+        if ($this->userManager->userExists($share->getShareOwner())) {
687
+            return $share->getShareOwner();
688
+        }
689
+
690
+        return $share->getSharedBy();
691
+    }
692
+
693
+
694
+
695
+    /**
696
+     * check if we got the right share
697
+     *
698
+     * @param IShare $share
699
+     * @param string $token
700
+     * @return bool
701
+     * @throws AuthenticationFailedException
702
+     */
703
+    protected function verifyShare(IShare $share, $token) {
704
+        if (
705
+            $share->getShareType() === IShare::TYPE_REMOTE &&
706
+            $share->getToken() === $token
707
+        ) {
708
+            return true;
709
+        }
710
+
711
+        if ($share->getShareType() === IShare::TYPE_CIRCLE) {
712
+            try {
713
+                $knownShare = $this->shareManager->getShareByToken($token);
714
+                if ($knownShare->getId() === $share->getId()) {
715
+                    return true;
716
+                }
717
+            } catch (ShareNotFound $e) {
718
+            }
719
+        }
720
+
721
+        throw new AuthenticationFailedException();
722
+    }
723
+
724
+
725
+
726
+    /**
727
+     * check if server-to-server sharing is enabled
728
+     *
729
+     * @param bool $incoming
730
+     * @return bool
731
+     */
732
+    private function isS2SEnabled($incoming = false) {
733
+        $result = $this->appManager->isEnabledForUser('files_sharing');
734
+
735
+        if ($incoming) {
736
+            $result = $result && $this->federatedShareProvider->isIncomingServer2serverShareEnabled();
737
+        } else {
738
+            $result = $result && $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
739
+        }
740
+
741
+        return $result;
742
+    }
743
+
744
+
745
+    /**
746
+     * get the supported share types, e.g. "user", "group", etc.
747
+     *
748
+     * @return array
749
+     *
750
+     * @since 14.0.0
751
+     */
752
+    public function getSupportedShareTypes() {
753
+        return ['user', 'group'];
754
+    }
755
+
756
+
757
+    public function getUserDisplayName(string $userId): string {
758
+        // check if gss is enabled and available
759
+        if (!$this->appManager->isEnabledForAnyone('globalsiteselector')
760
+            || !class_exists('\OCA\GlobalSiteSelector\Service\SlaveService')) {
761
+            return '';
762
+        }
763
+
764
+        try {
765
+            $slaveService = Server::get(SlaveService::class);
766
+        } catch (\Throwable $e) {
767
+            Server::get(LoggerInterface::class)->error(
768
+                $e->getMessage(),
769
+                ['exception' => $e]
770
+            );
771
+            return '';
772
+        }
773
+
774
+        return $slaveService->getUserDisplayName($this->cloudIdManager->removeProtocolFromUrl($userId), false);
775
+    }
776
+
777
+    /**
778
+     * @inheritDoc
779
+     *
780
+     * @param string $sharedSecret
781
+     * @param array $payload
782
+     * @return string
783
+     */
784
+    public function getFederationIdFromSharedSecret(
785
+        #[SensitiveParameter]
786
+        string $sharedSecret,
787
+        array $payload,
788
+    ): string {
789
+        $provider = $this->shareProviderFactory->getProviderForType(IShare::TYPE_REMOTE);
790
+        try {
791
+            $share = $provider->getShareByToken($sharedSecret);
792
+        } catch (ShareNotFound) {
793
+            // Maybe we're dealing with a share federated from another server
794
+            $share = $this->externalShareManager->getShareByToken($sharedSecret);
795
+            if ($share === false) {
796
+                return '';
797
+            }
798
+
799
+            return $share['user'] . '@' . $share['remote'];
800
+        }
801
+
802
+        // if uid_owner is a local account, the request comes from the recipient
803
+        // if not, request comes from the instance that owns the share and recipient is the re-sharer
804
+        if ($this->userManager->get($share->getShareOwner()) !== null) {
805
+            return $share->getSharedWith();
806
+        } else {
807
+            return $share->getShareOwner();
808
+        }
809
+    }
810 810
 }
Please login to merge, or discard this patch.