Completed
Push — master ( 2addf7...4174c5 )
by
unknown
56:08 queued 28:07
created
lib/private/OCM/Model/OCMProvider.php 1 patch
Indentation   +265 added lines, -265 removed lines patch added patch discarded remove patch
@@ -19,269 +19,269 @@
 block discarded – undo
19 19
  * @since 28.0.0
20 20
  */
21 21
 class OCMProvider implements ICapabilityAwareOCMProvider {
22
-	private bool $enabled = false;
23
-	private string $apiVersion = '';
24
-	private string $inviteAcceptDialog = '';
25
-	private array $capabilities = [];
26
-	private string $endPoint = '';
27
-	/** @var IOCMResource[] */
28
-	private array $resourceTypes = [];
29
-	private ?Signatory $signatory = null;
30
-
31
-	public function __construct(
32
-		private readonly string $provider = '',
33
-	) {
34
-	}
35
-
36
-	/**
37
-	 * @param bool $enabled
38
-	 *
39
-	 * @return $this
40
-	 */
41
-	public function setEnabled(bool $enabled): static {
42
-		$this->enabled = $enabled;
43
-
44
-		return $this;
45
-	}
46
-
47
-	/**
48
-	 * @return bool
49
-	 */
50
-	public function isEnabled(): bool {
51
-		return $this->enabled;
52
-	}
53
-
54
-	/**
55
-	 * @param string $apiVersion
56
-	 *
57
-	 * @return $this
58
-	 */
59
-	public function setApiVersion(string $apiVersion): static {
60
-		$this->apiVersion = $apiVersion;
61
-
62
-		return $this;
63
-	}
64
-
65
-	/**
66
-	 * @return string
67
-	 */
68
-	public function getApiVersion(): string {
69
-		return $this->apiVersion;
70
-	}
71
-
72
-	/**
73
-	 * returns the invite accept dialog
74
-	 *
75
-	 * @return string
76
-	 * @since 32.0.0
77
-	 */
78
-	public function getInviteAcceptDialog(): string {
79
-		return $this->inviteAcceptDialog;
80
-	}
81
-
82
-	/**
83
-	 * set the invite accept dialog
84
-	 *
85
-	 * @param string $inviteAcceptDialog
86
-	 *
87
-	 * @return $this
88
-	 * @since 32.0.0
89
-	 */
90
-	public function setInviteAcceptDialog(string $inviteAcceptDialog): static {
91
-		$this->inviteAcceptDialog = $inviteAcceptDialog;
92
-
93
-		return $this;
94
-	}
95
-
96
-	/**
97
-	 * @param string $endPoint
98
-	 *
99
-	 * @return $this
100
-	 */
101
-	public function setEndPoint(string $endPoint): static {
102
-		$this->endPoint = $endPoint;
103
-
104
-		return $this;
105
-	}
106
-
107
-	/**
108
-	 * @return string
109
-	 */
110
-	public function getEndPoint(): string {
111
-		return $this->endPoint;
112
-	}
113
-
114
-	/**
115
-	 * @return string
116
-	 */
117
-	public function getProvider(): string {
118
-		return $this->provider;
119
-	}
120
-
121
-	/**
122
-	 * @param array $capabilities
123
-	 *
124
-	 * @return $this
125
-	 */
126
-	public function setCapabilities(array $capabilities): static {
127
-		foreach ($capabilities as $value) {
128
-			if (!in_array($value, $this->capabilities)) {
129
-				array_push($this->capabilities, $value);
130
-			}
131
-		}
132
-
133
-		return $this;
134
-	}
135
-
136
-	/**
137
-	 * @return array
138
-	 */
139
-	public function getCapabilities(): array {
140
-		return $this->capabilities;
141
-	}
142
-	/**
143
-	 * create a new resource to later add it with {@see IOCMProvider::addResourceType()}
144
-	 * @return IOCMResource
145
-	 */
146
-	public function createNewResourceType(): IOCMResource {
147
-		return new OCMResource();
148
-	}
149
-
150
-	/**
151
-	 * @param IOCMResource $resource
152
-	 *
153
-	 * @return $this
154
-	 */
155
-	public function addResourceType(IOCMResource $resource): static {
156
-		$this->resourceTypes[] = $resource;
157
-
158
-		return $this;
159
-	}
160
-
161
-	/**
162
-	 * @param IOCMResource[] $resourceTypes
163
-	 *
164
-	 * @return $this
165
-	 */
166
-	public function setResourceTypes(array $resourceTypes): static {
167
-		$this->resourceTypes = $resourceTypes;
168
-
169
-		return $this;
170
-	}
171
-
172
-	/**
173
-	 * @return IOCMResource[]
174
-	 */
175
-	public function getResourceTypes(): array {
176
-		return $this->resourceTypes;
177
-	}
178
-
179
-	/**
180
-	 * @param string $resourceName
181
-	 * @param string $protocol
182
-	 *
183
-	 * @return string
184
-	 * @throws OCMArgumentException
185
-	 */
186
-	public function extractProtocolEntry(string $resourceName, string $protocol): string {
187
-		foreach ($this->getResourceTypes() as $resource) {
188
-			if ($resource->getName() === $resourceName) {
189
-				$entry = $resource->getProtocols()[$protocol] ?? null;
190
-				if (is_null($entry)) {
191
-					throw new OCMArgumentException('protocol not found');
192
-				}
193
-
194
-				return (string)$entry;
195
-			}
196
-		}
197
-
198
-		throw new OCMArgumentException('resource not found');
199
-	}
200
-
201
-	public function setSignatory(Signatory $signatory): void {
202
-		$this->signatory = $signatory;
203
-	}
204
-
205
-	public function getSignatory(): ?Signatory {
206
-		return $this->signatory;
207
-	}
208
-
209
-	/**
210
-	 * import data from an array
211
-	 *
212
-	 * @param array $data
213
-	 *
214
-	 * @return OCMProvider&static
215
-	 * @throws OCMProviderException in case a descent provider cannot be generated from data
216
-	 */
217
-	public function import(array $data): static {
218
-		$this->setEnabled(is_bool($data['enabled'] ?? '') ? $data['enabled'] : false)
219
-			// Fall back to old apiVersion for Nextcloud 30 compatibility
220
-			->setApiVersion((string)($data['version'] ?? $data['apiVersion'] ?? ''))
221
-			->setEndPoint($data['endPoint'] ?? '');
222
-
223
-		$resources = [];
224
-		foreach (($data['resourceTypes'] ?? []) as $resourceData) {
225
-			$resource = new OCMResource();
226
-			$resources[] = $resource->import($resourceData);
227
-		}
228
-		$this->setResourceTypes($resources);
229
-		$this->setInviteAcceptDialog($data['inviteAcceptDialog'] ?? '');
230
-		$this->setCapabilities($data['capabilities'] ?? []);
231
-
232
-		if (isset($data['publicKey'])) {
233
-			// import details about the remote request signing public key, if available
234
-			$signatory = new Signatory();
235
-			$signatory->setKeyId($data['publicKey']['keyId'] ?? '');
236
-			$signatory->setPublicKey($data['publicKey']['publicKeyPem'] ?? '');
237
-			if ($signatory->getKeyId() !== '' && $signatory->getPublicKey() !== '') {
238
-				$this->setSignatory($signatory);
239
-			}
240
-		}
241
-
242
-		if (!$this->looksValid()) {
243
-			throw new OCMProviderException('remote provider does not look valid');
244
-		}
245
-
246
-		return $this;
247
-	}
248
-
249
-
250
-	/**
251
-	 * @return bool
252
-	 */
253
-	private function looksValid(): bool {
254
-		return ($this->getApiVersion() !== '' && $this->getEndPoint() !== '');
255
-	}
256
-
257
-	/**
258
-	 * @since 28.0.0
259
-	 */
260
-	public function jsonSerialize(): array {
261
-		$resourceTypes = [];
262
-		foreach ($this->getResourceTypes() as $res) {
263
-			$resourceTypes[] = $res->jsonSerialize();
264
-		}
265
-
266
-		$response = [
267
-			'enabled' => $this->isEnabled(),
268
-			'apiVersion' => '1.0-proposal1', // deprecated, but keep it to stay compatible with old version
269
-			'version' => $this->getApiVersion(), // informative but real version
270
-			'endPoint' => $this->getEndPoint(),
271
-			'publicKey' => $this->getSignatory()?->jsonSerialize(),
272
-			'provider' => $this->getProvider(),
273
-			'resourceTypes' => $resourceTypes
274
-		];
275
-
276
-		$capabilities = $this->getCapabilities();
277
-		if ($capabilities) {
278
-			$response['capabilities'] = $capabilities;
279
-		}
280
-		$inviteAcceptDialog = $this->getInviteAcceptDialog();
281
-		if ($inviteAcceptDialog !== '') {
282
-			$response['inviteAcceptDialog'] = $inviteAcceptDialog;
283
-		}
284
-		return $response;
285
-
286
-	}
22
+    private bool $enabled = false;
23
+    private string $apiVersion = '';
24
+    private string $inviteAcceptDialog = '';
25
+    private array $capabilities = [];
26
+    private string $endPoint = '';
27
+    /** @var IOCMResource[] */
28
+    private array $resourceTypes = [];
29
+    private ?Signatory $signatory = null;
30
+
31
+    public function __construct(
32
+        private readonly string $provider = '',
33
+    ) {
34
+    }
35
+
36
+    /**
37
+     * @param bool $enabled
38
+     *
39
+     * @return $this
40
+     */
41
+    public function setEnabled(bool $enabled): static {
42
+        $this->enabled = $enabled;
43
+
44
+        return $this;
45
+    }
46
+
47
+    /**
48
+     * @return bool
49
+     */
50
+    public function isEnabled(): bool {
51
+        return $this->enabled;
52
+    }
53
+
54
+    /**
55
+     * @param string $apiVersion
56
+     *
57
+     * @return $this
58
+     */
59
+    public function setApiVersion(string $apiVersion): static {
60
+        $this->apiVersion = $apiVersion;
61
+
62
+        return $this;
63
+    }
64
+
65
+    /**
66
+     * @return string
67
+     */
68
+    public function getApiVersion(): string {
69
+        return $this->apiVersion;
70
+    }
71
+
72
+    /**
73
+     * returns the invite accept dialog
74
+     *
75
+     * @return string
76
+     * @since 32.0.0
77
+     */
78
+    public function getInviteAcceptDialog(): string {
79
+        return $this->inviteAcceptDialog;
80
+    }
81
+
82
+    /**
83
+     * set the invite accept dialog
84
+     *
85
+     * @param string $inviteAcceptDialog
86
+     *
87
+     * @return $this
88
+     * @since 32.0.0
89
+     */
90
+    public function setInviteAcceptDialog(string $inviteAcceptDialog): static {
91
+        $this->inviteAcceptDialog = $inviteAcceptDialog;
92
+
93
+        return $this;
94
+    }
95
+
96
+    /**
97
+     * @param string $endPoint
98
+     *
99
+     * @return $this
100
+     */
101
+    public function setEndPoint(string $endPoint): static {
102
+        $this->endPoint = $endPoint;
103
+
104
+        return $this;
105
+    }
106
+
107
+    /**
108
+     * @return string
109
+     */
110
+    public function getEndPoint(): string {
111
+        return $this->endPoint;
112
+    }
113
+
114
+    /**
115
+     * @return string
116
+     */
117
+    public function getProvider(): string {
118
+        return $this->provider;
119
+    }
120
+
121
+    /**
122
+     * @param array $capabilities
123
+     *
124
+     * @return $this
125
+     */
126
+    public function setCapabilities(array $capabilities): static {
127
+        foreach ($capabilities as $value) {
128
+            if (!in_array($value, $this->capabilities)) {
129
+                array_push($this->capabilities, $value);
130
+            }
131
+        }
132
+
133
+        return $this;
134
+    }
135
+
136
+    /**
137
+     * @return array
138
+     */
139
+    public function getCapabilities(): array {
140
+        return $this->capabilities;
141
+    }
142
+    /**
143
+     * create a new resource to later add it with {@see IOCMProvider::addResourceType()}
144
+     * @return IOCMResource
145
+     */
146
+    public function createNewResourceType(): IOCMResource {
147
+        return new OCMResource();
148
+    }
149
+
150
+    /**
151
+     * @param IOCMResource $resource
152
+     *
153
+     * @return $this
154
+     */
155
+    public function addResourceType(IOCMResource $resource): static {
156
+        $this->resourceTypes[] = $resource;
157
+
158
+        return $this;
159
+    }
160
+
161
+    /**
162
+     * @param IOCMResource[] $resourceTypes
163
+     *
164
+     * @return $this
165
+     */
166
+    public function setResourceTypes(array $resourceTypes): static {
167
+        $this->resourceTypes = $resourceTypes;
168
+
169
+        return $this;
170
+    }
171
+
172
+    /**
173
+     * @return IOCMResource[]
174
+     */
175
+    public function getResourceTypes(): array {
176
+        return $this->resourceTypes;
177
+    }
178
+
179
+    /**
180
+     * @param string $resourceName
181
+     * @param string $protocol
182
+     *
183
+     * @return string
184
+     * @throws OCMArgumentException
185
+     */
186
+    public function extractProtocolEntry(string $resourceName, string $protocol): string {
187
+        foreach ($this->getResourceTypes() as $resource) {
188
+            if ($resource->getName() === $resourceName) {
189
+                $entry = $resource->getProtocols()[$protocol] ?? null;
190
+                if (is_null($entry)) {
191
+                    throw new OCMArgumentException('protocol not found');
192
+                }
193
+
194
+                return (string)$entry;
195
+            }
196
+        }
197
+
198
+        throw new OCMArgumentException('resource not found');
199
+    }
200
+
201
+    public function setSignatory(Signatory $signatory): void {
202
+        $this->signatory = $signatory;
203
+    }
204
+
205
+    public function getSignatory(): ?Signatory {
206
+        return $this->signatory;
207
+    }
208
+
209
+    /**
210
+     * import data from an array
211
+     *
212
+     * @param array $data
213
+     *
214
+     * @return OCMProvider&static
215
+     * @throws OCMProviderException in case a descent provider cannot be generated from data
216
+     */
217
+    public function import(array $data): static {
218
+        $this->setEnabled(is_bool($data['enabled'] ?? '') ? $data['enabled'] : false)
219
+            // Fall back to old apiVersion for Nextcloud 30 compatibility
220
+            ->setApiVersion((string)($data['version'] ?? $data['apiVersion'] ?? ''))
221
+            ->setEndPoint($data['endPoint'] ?? '');
222
+
223
+        $resources = [];
224
+        foreach (($data['resourceTypes'] ?? []) as $resourceData) {
225
+            $resource = new OCMResource();
226
+            $resources[] = $resource->import($resourceData);
227
+        }
228
+        $this->setResourceTypes($resources);
229
+        $this->setInviteAcceptDialog($data['inviteAcceptDialog'] ?? '');
230
+        $this->setCapabilities($data['capabilities'] ?? []);
231
+
232
+        if (isset($data['publicKey'])) {
233
+            // import details about the remote request signing public key, if available
234
+            $signatory = new Signatory();
235
+            $signatory->setKeyId($data['publicKey']['keyId'] ?? '');
236
+            $signatory->setPublicKey($data['publicKey']['publicKeyPem'] ?? '');
237
+            if ($signatory->getKeyId() !== '' && $signatory->getPublicKey() !== '') {
238
+                $this->setSignatory($signatory);
239
+            }
240
+        }
241
+
242
+        if (!$this->looksValid()) {
243
+            throw new OCMProviderException('remote provider does not look valid');
244
+        }
245
+
246
+        return $this;
247
+    }
248
+
249
+
250
+    /**
251
+     * @return bool
252
+     */
253
+    private function looksValid(): bool {
254
+        return ($this->getApiVersion() !== '' && $this->getEndPoint() !== '');
255
+    }
256
+
257
+    /**
258
+     * @since 28.0.0
259
+     */
260
+    public function jsonSerialize(): array {
261
+        $resourceTypes = [];
262
+        foreach ($this->getResourceTypes() as $res) {
263
+            $resourceTypes[] = $res->jsonSerialize();
264
+        }
265
+
266
+        $response = [
267
+            'enabled' => $this->isEnabled(),
268
+            'apiVersion' => '1.0-proposal1', // deprecated, but keep it to stay compatible with old version
269
+            'version' => $this->getApiVersion(), // informative but real version
270
+            'endPoint' => $this->getEndPoint(),
271
+            'publicKey' => $this->getSignatory()?->jsonSerialize(),
272
+            'provider' => $this->getProvider(),
273
+            'resourceTypes' => $resourceTypes
274
+        ];
275
+
276
+        $capabilities = $this->getCapabilities();
277
+        if ($capabilities) {
278
+            $response['capabilities'] = $capabilities;
279
+        }
280
+        $inviteAcceptDialog = $this->getInviteAcceptDialog();
281
+        if ($inviteAcceptDialog !== '') {
282
+            $response['inviteAcceptDialog'] = $inviteAcceptDialog;
283
+        }
284
+        return $response;
285
+
286
+    }
287 287
 }
Please login to merge, or discard this patch.
apps/cloud_federation_api/lib/Controller/RequestHandlerController.php 2 patches
Indentation   +512 added lines, -512 removed lines patch added patch discarded remove patch
@@ -59,516 +59,516 @@
 block discarded – undo
59 59
  */
60 60
 #[OpenAPI(scope: OpenAPI::SCOPE_FEDERATION)]
61 61
 class RequestHandlerController extends Controller {
62
-	public function __construct(
63
-		string $appName,
64
-		IRequest $request,
65
-		private LoggerInterface $logger,
66
-		private IUserManager $userManager,
67
-		private IGroupManager $groupManager,
68
-		private IURLGenerator $urlGenerator,
69
-		private ICloudFederationProviderManager $cloudFederationProviderManager,
70
-		private Config $config,
71
-		private IEventDispatcher $dispatcher,
72
-		private FederatedInviteMapper $federatedInviteMapper,
73
-		private readonly AddressHandler $addressHandler,
74
-		private readonly IAppConfig $appConfig,
75
-		private ICloudFederationFactory $factory,
76
-		private ICloudIdManager $cloudIdManager,
77
-		private readonly ISignatureManager $signatureManager,
78
-		private readonly OCMSignatoryManager $signatoryManager,
79
-		private ITimeFactory $timeFactory,
80
-	) {
81
-		parent::__construct($appName, $request);
82
-	}
83
-
84
-	/**
85
-	 * Add share
86
-	 *
87
-	 * @param string $shareWith The user who the share will be shared with
88
-	 * @param string $name The resource name (e.g. document.odt)
89
-	 * @param string|null $description Share description
90
-	 * @param string $providerId Resource UID on the provider side
91
-	 * @param string $owner Provider specific UID of the user who owns the resource
92
-	 * @param string|null $ownerDisplayName Display name of the user who shared the item
93
-	 * @param string|null $sharedBy Provider specific UID of the user who shared the resource
94
-	 * @param string|null $sharedByDisplayName Display name of the user who shared the resource
95
-	 * @param array{name: list<string>, options: array<string, mixed>} $protocol e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]]
96
-	 * @param string $shareType 'group' or 'user' share
97
-	 * @param string $resourceType 'file', 'calendar',...
98
-	 *
99
-	 * @return JSONResponse<Http::STATUS_CREATED, CloudFederationAPIAddShare, array{}>|JSONResponse<Http::STATUS_BAD_REQUEST, CloudFederationAPIValidationError, array{}>|JSONResponse<Http::STATUS_NOT_IMPLEMENTED, CloudFederationAPIError, array{}>
100
-	 *
101
-	 * 201: The notification was successfully received. The display name of the recipient might be returned in the body
102
-	 * 400: Bad request due to invalid parameters, e.g. when `shareWith` is not found or required properties are missing
103
-	 * 501: Share type or the resource type is not supported
104
-	 */
105
-	#[PublicPage]
106
-	#[NoCSRFRequired]
107
-	#[BruteForceProtection(action: 'receiveFederatedShare')]
108
-	public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
109
-		try {
110
-			// if request is signed and well signed, no exception are thrown
111
-			// if request is not signed and host is known for not supporting signed request, no exception are thrown
112
-			$signedRequest = $this->getSignedRequest();
113
-			$this->confirmSignedOrigin($signedRequest, 'owner', $owner);
114
-		} catch (IncomingRequestException $e) {
115
-			$this->logger->warning('incoming request exception', ['exception' => $e]);
116
-			return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
117
-		}
118
-
119
-		// check if all required parameters are set
120
-		if (
121
-			$shareWith === null
122
-			|| $name === null
123
-			|| $providerId === null
124
-			|| $resourceType === null
125
-			|| $shareType === null
126
-			|| !is_array($protocol)
127
-			|| !isset($protocol['name'])
128
-			|| !isset($protocol['options'])
129
-			|| !is_array($protocol['options'])
130
-			|| !isset($protocol['options']['sharedSecret'])
131
-		) {
132
-			return new JSONResponse(
133
-				[
134
-					'message' => 'Missing arguments',
135
-					'validationErrors' => [],
136
-				],
137
-				Http::STATUS_BAD_REQUEST
138
-			);
139
-		}
140
-
141
-		$supportedShareTypes = $this->config->getSupportedShareTypes($resourceType);
142
-		if (!in_array($shareType, $supportedShareTypes)) {
143
-			return new JSONResponse(
144
-				['message' => 'Share type "' . $shareType . '" not implemented'],
145
-				Http::STATUS_NOT_IMPLEMENTED
146
-			);
147
-		}
148
-
149
-		$cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
150
-		$shareWith = $cloudId->getUser();
151
-
152
-		if ($shareType === 'user') {
153
-			$shareWith = $this->mapUid($shareWith);
154
-
155
-			if (!$this->userManager->userExists($shareWith)) {
156
-				$response = new JSONResponse(
157
-					[
158
-						'message' => 'User "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl(),
159
-						'validationErrors' => [],
160
-					],
161
-					Http::STATUS_BAD_REQUEST
162
-				);
163
-				$response->throttle();
164
-				return $response;
165
-			}
166
-		}
167
-
168
-		if ($shareType === 'group') {
169
-			if (!$this->groupManager->groupExists($shareWith)) {
170
-				$response = new JSONResponse(
171
-					[
172
-						'message' => 'Group "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl(),
173
-						'validationErrors' => [],
174
-					],
175
-					Http::STATUS_BAD_REQUEST
176
-				);
177
-				$response->throttle();
178
-				return $response;
179
-			}
180
-		}
181
-
182
-		// if no explicit display name is given, we use the uid as display name
183
-		$ownerDisplayName = $ownerDisplayName === null ? $owner : $ownerDisplayName;
184
-		$sharedByDisplayName = $sharedByDisplayName === null ? $sharedBy : $sharedByDisplayName;
185
-
186
-		// sharedBy* parameter is optional, if nothing is set we assume that it is the same user as the owner
187
-		if ($sharedBy === null) {
188
-			$sharedBy = $owner;
189
-			$sharedByDisplayName = $ownerDisplayName;
190
-		}
191
-
192
-		try {
193
-			$provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
194
-			$share = $this->factory->getCloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, '', $shareType, $resourceType);
195
-			$share->setProtocol($protocol);
196
-			$provider->shareReceived($share);
197
-		} catch (ProviderDoesNotExistsException|ProviderCouldNotAddShareException $e) {
198
-			return new JSONResponse(
199
-				['message' => $e->getMessage()],
200
-				Http::STATUS_NOT_IMPLEMENTED
201
-			);
202
-		} catch (\Exception $e) {
203
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
204
-			return new JSONResponse(
205
-				[
206
-					'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
207
-					'validationErrors' => [],
208
-				],
209
-				Http::STATUS_BAD_REQUEST
210
-			);
211
-		}
212
-
213
-		$responseData = ['recipientDisplayName' => ''];
214
-		if ($shareType === 'user') {
215
-			$user = $this->userManager->get($shareWith);
216
-			if ($user) {
217
-				$responseData = [
218
-					'recipientDisplayName' => $user->getDisplayName(),
219
-					'recipientUserId' => $user->getUID(),
220
-				];
221
-			}
222
-		}
223
-
224
-		return new JSONResponse($responseData, Http::STATUS_CREATED);
225
-	}
226
-
227
-	/**
228
-	 * Inform the sender that an invitation was accepted to start sharing
229
-	 *
230
-	 * Inform about an accepted invitation so the user on the sender provider's side
231
-	 * can initiate the OCM share creation. To protect the identity of the parties,
232
-	 * for shares created following an OCM invitation, the user id MAY be hashed,
233
-	 * and recipients implementing the OCM invitation workflow MAY refuse to process
234
-	 * shares coming from unknown parties.
235
-	 * @link https://cs3org.github.io/OCM-API/docs.html?branch=v1.1.0&repo=OCM-API&user=cs3org#/paths/~1invite-accepted/post
236
-	 *
237
-	 * @param string $recipientProvider The address of the recipent's provider
238
-	 * @param string $token The token used for the invitation
239
-	 * @param string $userID The userID of the recipient at the recipient's provider
240
-	 * @param string $email The email address of the recipient
241
-	 * @param string $name The display name of the recipient
242
-	 *
243
-	 * @return JSONResponse<Http::STATUS_OK, array{userID: string, email: string, name: string}, array{}>|JSONResponse<Http::STATUS_FORBIDDEN|Http::STATUS_BAD_REQUEST|Http::STATUS_CONFLICT, array{message: string, error: true}, array{}>
244
-	 *
245
-	 * Note: Not implementing 404 Invitation token does not exist, instead using 400
246
-	 * 200: Invitation accepted
247
-	 * 400: Invalid token
248
-	 * 403: Invitation token does not exist
249
-	 * 409: User is already known by the OCM provider
250
-	 */
251
-	#[PublicPage]
252
-	#[NoCSRFRequired]
253
-	#[BruteForceProtection(action: 'inviteAccepted')]
254
-	public function inviteAccepted(string $recipientProvider, string $token, string $userID, string $email, string $name): JSONResponse {
255
-		$this->logger->debug('Processing share invitation for ' . $userID . ' with token ' . $token . ' and email ' . $email . ' and name ' . $name);
256
-
257
-		$updated = $this->timeFactory->getTime();
258
-
259
-		if ($token === '') {
260
-			$response = new JSONResponse(['message' => 'Invalid or non existing token', 'error' => true], Http::STATUS_BAD_REQUEST);
261
-			$response->throttle();
262
-			return $response;
263
-		}
264
-
265
-		try {
266
-			$invitation = $this->federatedInviteMapper->findByToken($token);
267
-		} catch (DoesNotExistException) {
268
-			$response = ['message' => 'Invalid or non existing token', 'error' => true];
269
-			$status = Http::STATUS_BAD_REQUEST;
270
-			$response = new JSONResponse($response, $status);
271
-			$response->throttle();
272
-			return $response;
273
-		}
274
-
275
-		if ($invitation->isAccepted() === true) {
276
-			$response = ['message' => 'Invite already accepted', 'error' => true];
277
-			$status = Http::STATUS_CONFLICT;
278
-			return new JSONResponse($response, $status);
279
-		}
280
-
281
-		if ($invitation->getExpiredAt() !== null && $updated > $invitation->getExpiredAt()) {
282
-			$response = ['message' => 'Invitation expired', 'error' => true];
283
-			$status = Http::STATUS_BAD_REQUEST;
284
-			return new JSONResponse($response, $status);
285
-		}
286
-		$localUser = $this->userManager->get($invitation->getUserId());
287
-		if ($localUser === null) {
288
-			$response = ['message' => 'Invalid or non existing token', 'error' => true];
289
-			$status = Http::STATUS_BAD_REQUEST;
290
-			$response = new JSONResponse($response, $status);
291
-			$response->throttle();
292
-			return $response;
293
-		}
294
-
295
-		$sharedFromEmail = $localUser->getEMailAddress();
296
-		if ($sharedFromEmail === null) {
297
-			$response = ['message' => 'Invalid or non existing token', 'error' => true];
298
-			$status = Http::STATUS_BAD_REQUEST;
299
-			$response = new JSONResponse($response, $status);
300
-			$response->throttle();
301
-			return $response;
302
-		}
303
-		$sharedFromDisplayName = $localUser->getDisplayName();
304
-
305
-		$response = ['userID' => $localUser->getUID(), 'email' => $sharedFromEmail, 'name' => $sharedFromDisplayName];
306
-		$status = Http::STATUS_OK;
307
-
308
-		$invitation->setAccepted(true);
309
-		$invitation->setRecipientEmail($email);
310
-		$invitation->setRecipientName($name);
311
-		$invitation->setRecipientProvider($recipientProvider);
312
-		$invitation->setRecipientUserId($userID);
313
-		$invitation->setAcceptedAt($updated);
314
-		$invitation = $this->federatedInviteMapper->update($invitation);
315
-
316
-		$event = new FederatedInviteAcceptedEvent($invitation);
317
-		$this->dispatcher->dispatchTyped($event);
318
-
319
-		return new JSONResponse($response, $status);
320
-	}
321
-
322
-	/**
323
-	 * Send a notification about an existing share
324
-	 *
325
-	 * @param string $notificationType Notification type, e.g. SHARE_ACCEPTED
326
-	 * @param string $resourceType calendar, file, contact,...
327
-	 * @param string|null $providerId ID of the share
328
-	 * @param array<string, mixed>|null $notification The actual payload of the notification
329
-	 *
330
-	 * @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{}>
331
-	 *
332
-	 * 201: The notification was successfully received
333
-	 * 400: Bad request due to invalid parameters, e.g. when `type` is invalid or missing
334
-	 * 403: Getting resource is not allowed
335
-	 * 501: The resource type is not supported
336
-	 */
337
-	#[NoCSRFRequired]
338
-	#[PublicPage]
339
-	#[BruteForceProtection(action: 'receiveFederatedShareNotification')]
340
-	public function receiveNotification($notificationType, $resourceType, $providerId, ?array $notification) {
341
-		// check if all required parameters are set
342
-		if (
343
-			$notificationType === null
344
-			|| $resourceType === null
345
-			|| $providerId === null
346
-			|| !is_array($notification)
347
-		) {
348
-			return new JSONResponse(
349
-				[
350
-					'message' => 'Missing arguments',
351
-					'validationErrors' => [],
352
-				],
353
-				Http::STATUS_BAD_REQUEST
354
-			);
355
-		}
356
-
357
-		try {
358
-			// if request is signed and well signed, no exception are thrown
359
-			// if request is not signed and host is known for not supporting signed request, no exception are thrown
360
-			$signedRequest = $this->getSignedRequest();
361
-			$this->confirmNotificationIdentity($signedRequest, $resourceType, $notification);
362
-		} catch (IncomingRequestException $e) {
363
-			$this->logger->warning('incoming request exception', ['exception' => $e]);
364
-			return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
365
-		}
366
-
367
-		try {
368
-			$provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
369
-			$result = $provider->notificationReceived($notificationType, $providerId, $notification);
370
-		} catch (ProviderDoesNotExistsException $e) {
371
-			return new JSONResponse(
372
-				[
373
-					'message' => $e->getMessage(),
374
-					'validationErrors' => [],
375
-				],
376
-				Http::STATUS_BAD_REQUEST
377
-			);
378
-		} catch (ShareNotFound $e) {
379
-			$response = new JSONResponse(
380
-				[
381
-					'message' => $e->getMessage(),
382
-					'validationErrors' => [],
383
-				],
384
-				Http::STATUS_BAD_REQUEST
385
-			);
386
-			$response->throttle();
387
-			return $response;
388
-		} catch (ActionNotSupportedException $e) {
389
-			return new JSONResponse(
390
-				['message' => $e->getMessage()],
391
-				Http::STATUS_NOT_IMPLEMENTED
392
-			);
393
-		} catch (BadRequestException $e) {
394
-			return new JSONResponse($e->getReturnMessage(), Http::STATUS_BAD_REQUEST);
395
-		} catch (AuthenticationFailedException $e) {
396
-			$response = new JSONResponse(['message' => 'RESOURCE_NOT_FOUND'], Http::STATUS_FORBIDDEN);
397
-			$response->throttle();
398
-			return $response;
399
-		} catch (\Exception $e) {
400
-			$this->logger->warning('incoming notification exception', ['exception' => $e]);
401
-			return new JSONResponse(
402
-				[
403
-					'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
404
-					'validationErrors' => [],
405
-				],
406
-				Http::STATUS_BAD_REQUEST
407
-			);
408
-		}
409
-
410
-		return new JSONResponse($result, Http::STATUS_CREATED);
411
-	}
412
-
413
-	/**
414
-	 * map login name to internal LDAP UID if a LDAP backend is in use
415
-	 *
416
-	 * @param string $uid
417
-	 * @return string mixed
418
-	 */
419
-	private function mapUid($uid) {
420
-		// FIXME this should be a method in the user management instead
421
-		$this->logger->debug('shareWith before, ' . $uid, ['app' => $this->appName]);
422
-		Util::emitHook(
423
-			'\OCA\Files_Sharing\API\Server2Server',
424
-			'preLoginNameUsedAsUserName',
425
-			['uid' => &$uid]
426
-		);
427
-		$this->logger->debug('shareWith after, ' . $uid, ['app' => $this->appName]);
428
-
429
-		return $uid;
430
-	}
431
-
432
-
433
-	/**
434
-	 * returns signed request if available.
435
-	 * throw an exception:
436
-	 * - if request is signed, but wrongly signed
437
-	 * - if request is not signed but instance is configured to only accept signed ocm request
438
-	 *
439
-	 * @return IIncomingSignedRequest|null null if remote does not (and never did) support signed request
440
-	 * @throws IncomingRequestException
441
-	 */
442
-	private function getSignedRequest(): ?IIncomingSignedRequest {
443
-		try {
444
-			$signedRequest = $this->signatureManager->getIncomingSignedRequest($this->signatoryManager);
445
-			$this->logger->debug('signed request available', ['signedRequest' => $signedRequest]);
446
-			return $signedRequest;
447
-		} catch (SignatureNotFoundException|SignatoryNotFoundException $e) {
448
-			$this->logger->debug('remote does not support signed request', ['exception' => $e]);
449
-			// remote does not support signed request.
450
-			// currently we still accept unsigned request until lazy appconfig
451
-			// core.enforce_signed_ocm_request is set to true (default: false)
452
-			if ($this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_ENFORCED, lazy: true)) {
453
-				$this->logger->notice('ignored unsigned request', ['exception' => $e]);
454
-				throw new IncomingRequestException('Unsigned request');
455
-			}
456
-		} catch (SignatureException $e) {
457
-			$this->logger->warning('wrongly signed request', ['exception' => $e]);
458
-			throw new IncomingRequestException('Invalid signature');
459
-		}
460
-		return null;
461
-	}
462
-
463
-
464
-	/**
465
-	 * confirm that the value related to $key entry from the payload is in format userid@hostname
466
-	 * and compare hostname with the origin of the signed request.
467
-	 *
468
-	 * If request is not signed, we still verify that the hostname from the extracted value does,
469
-	 * actually, not support signed request
470
-	 *
471
-	 * @param IIncomingSignedRequest|null $signedRequest
472
-	 * @param string $key entry from data available in data
473
-	 * @param string $value value itself used in case request is not signed
474
-	 *
475
-	 * @throws IncomingRequestException
476
-	 */
477
-	private function confirmSignedOrigin(?IIncomingSignedRequest $signedRequest, string $key, string $value): void {
478
-		if ($signedRequest === null) {
479
-			$instance = $this->getHostFromFederationId($value);
480
-			try {
481
-				$this->signatureManager->getSignatory($instance);
482
-				throw new IncomingRequestException('instance is supposed to sign its request');
483
-			} catch (SignatoryNotFoundException) {
484
-				return;
485
-			}
486
-		}
487
-
488
-		$body = json_decode($signedRequest->getBody(), true) ?? [];
489
-		$entry = trim($body[$key] ?? '', '@');
490
-		if ($this->getHostFromFederationId($entry) !== $signedRequest->getOrigin()) {
491
-			throw new IncomingRequestException('share initiation (' . $signedRequest->getOrigin() . ') from different instance (' . $entry . ') [key=' . $key . ']');
492
-		}
493
-	}
494
-
495
-	/**
496
-	 *  confirm identity of the remote instance on notification, based on the share token.
497
-	 *
498
-	 *  If request is not signed, we still verify that the hostname from the extracted value does,
499
-	 *  actually, not support signed request
500
-	 *
501
-	 * @param IIncomingSignedRequest|null $signedRequest
502
-	 * @param string $resourceType
503
-	 * @param string $sharedSecret
504
-	 *
505
-	 * @throws IncomingRequestException
506
-	 * @throws BadRequestException
507
-	 */
508
-	private function confirmNotificationIdentity(
509
-		?IIncomingSignedRequest $signedRequest,
510
-		string $resourceType,
511
-		array $notification,
512
-	): void {
513
-		$sharedSecret = $notification['sharedSecret'] ?? '';
514
-		if ($sharedSecret === '') {
515
-			throw new BadRequestException(['sharedSecret']);
516
-		}
517
-
518
-		try {
519
-			$provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
520
-			if ($provider instanceof ISignedCloudFederationProvider) {
521
-				$identity = $provider->getFederationIdFromSharedSecret($sharedSecret, $notification);
522
-			} else {
523
-				$this->logger->debug('cloud federation provider {provider} does not implements ISignedCloudFederationProvider', ['provider' => $provider::class]);
524
-				return;
525
-			}
526
-		} catch (\Exception $e) {
527
-			throw new IncomingRequestException($e->getMessage());
528
-		}
529
-
530
-		$this->confirmNotificationEntry($signedRequest, $identity);
531
-	}
532
-
533
-
534
-	/**
535
-	 * @param IIncomingSignedRequest|null $signedRequest
536
-	 * @param string $entry
537
-	 *
538
-	 * @return void
539
-	 * @throws IncomingRequestException
540
-	 */
541
-	private function confirmNotificationEntry(?IIncomingSignedRequest $signedRequest, string $entry): void {
542
-		$instance = $this->getHostFromFederationId($entry);
543
-		if ($signedRequest === null) {
544
-			try {
545
-				$this->signatureManager->getSignatory($instance);
546
-				throw new IncomingRequestException('instance is supposed to sign its request');
547
-			} catch (SignatoryNotFoundException) {
548
-				return;
549
-			}
550
-		} elseif ($instance !== $signedRequest->getOrigin()) {
551
-			throw new IncomingRequestException('remote instance ' . $instance . ' not linked to origin ' . $signedRequest->getOrigin());
552
-		}
553
-	}
554
-
555
-	/**
556
-	 * @param string $entry
557
-	 * @return string
558
-	 * @throws IncomingRequestException
559
-	 */
560
-	private function getHostFromFederationId(string $entry): string {
561
-		if (!str_contains($entry, '@')) {
562
-			throw new IncomingRequestException('entry ' . $entry . ' does not contain @');
563
-		}
564
-		$rightPart = substr($entry, strrpos($entry, '@') + 1);
565
-
566
-		// in case the full scheme is sent; getting rid of it
567
-		$rightPart = $this->addressHandler->removeProtocolFromUrl($rightPart);
568
-		try {
569
-			return $this->signatureManager->extractIdentityFromUri('https://' . $rightPart);
570
-		} catch (IdentityNotFoundException) {
571
-			throw new IncomingRequestException('invalid host within federation id: ' . $entry);
572
-		}
573
-	}
62
+    public function __construct(
63
+        string $appName,
64
+        IRequest $request,
65
+        private LoggerInterface $logger,
66
+        private IUserManager $userManager,
67
+        private IGroupManager $groupManager,
68
+        private IURLGenerator $urlGenerator,
69
+        private ICloudFederationProviderManager $cloudFederationProviderManager,
70
+        private Config $config,
71
+        private IEventDispatcher $dispatcher,
72
+        private FederatedInviteMapper $federatedInviteMapper,
73
+        private readonly AddressHandler $addressHandler,
74
+        private readonly IAppConfig $appConfig,
75
+        private ICloudFederationFactory $factory,
76
+        private ICloudIdManager $cloudIdManager,
77
+        private readonly ISignatureManager $signatureManager,
78
+        private readonly OCMSignatoryManager $signatoryManager,
79
+        private ITimeFactory $timeFactory,
80
+    ) {
81
+        parent::__construct($appName, $request);
82
+    }
83
+
84
+    /**
85
+     * Add share
86
+     *
87
+     * @param string $shareWith The user who the share will be shared with
88
+     * @param string $name The resource name (e.g. document.odt)
89
+     * @param string|null $description Share description
90
+     * @param string $providerId Resource UID on the provider side
91
+     * @param string $owner Provider specific UID of the user who owns the resource
92
+     * @param string|null $ownerDisplayName Display name of the user who shared the item
93
+     * @param string|null $sharedBy Provider specific UID of the user who shared the resource
94
+     * @param string|null $sharedByDisplayName Display name of the user who shared the resource
95
+     * @param array{name: list<string>, options: array<string, mixed>} $protocol e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]]
96
+     * @param string $shareType 'group' or 'user' share
97
+     * @param string $resourceType 'file', 'calendar',...
98
+     *
99
+     * @return JSONResponse<Http::STATUS_CREATED, CloudFederationAPIAddShare, array{}>|JSONResponse<Http::STATUS_BAD_REQUEST, CloudFederationAPIValidationError, array{}>|JSONResponse<Http::STATUS_NOT_IMPLEMENTED, CloudFederationAPIError, array{}>
100
+     *
101
+     * 201: The notification was successfully received. The display name of the recipient might be returned in the body
102
+     * 400: Bad request due to invalid parameters, e.g. when `shareWith` is not found or required properties are missing
103
+     * 501: Share type or the resource type is not supported
104
+     */
105
+    #[PublicPage]
106
+    #[NoCSRFRequired]
107
+    #[BruteForceProtection(action: 'receiveFederatedShare')]
108
+    public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
109
+        try {
110
+            // if request is signed and well signed, no exception are thrown
111
+            // if request is not signed and host is known for not supporting signed request, no exception are thrown
112
+            $signedRequest = $this->getSignedRequest();
113
+            $this->confirmSignedOrigin($signedRequest, 'owner', $owner);
114
+        } catch (IncomingRequestException $e) {
115
+            $this->logger->warning('incoming request exception', ['exception' => $e]);
116
+            return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
117
+        }
118
+
119
+        // check if all required parameters are set
120
+        if (
121
+            $shareWith === null
122
+            || $name === null
123
+            || $providerId === null
124
+            || $resourceType === null
125
+            || $shareType === null
126
+            || !is_array($protocol)
127
+            || !isset($protocol['name'])
128
+            || !isset($protocol['options'])
129
+            || !is_array($protocol['options'])
130
+            || !isset($protocol['options']['sharedSecret'])
131
+        ) {
132
+            return new JSONResponse(
133
+                [
134
+                    'message' => 'Missing arguments',
135
+                    'validationErrors' => [],
136
+                ],
137
+                Http::STATUS_BAD_REQUEST
138
+            );
139
+        }
140
+
141
+        $supportedShareTypes = $this->config->getSupportedShareTypes($resourceType);
142
+        if (!in_array($shareType, $supportedShareTypes)) {
143
+            return new JSONResponse(
144
+                ['message' => 'Share type "' . $shareType . '" not implemented'],
145
+                Http::STATUS_NOT_IMPLEMENTED
146
+            );
147
+        }
148
+
149
+        $cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
150
+        $shareWith = $cloudId->getUser();
151
+
152
+        if ($shareType === 'user') {
153
+            $shareWith = $this->mapUid($shareWith);
154
+
155
+            if (!$this->userManager->userExists($shareWith)) {
156
+                $response = new JSONResponse(
157
+                    [
158
+                        'message' => 'User "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl(),
159
+                        'validationErrors' => [],
160
+                    ],
161
+                    Http::STATUS_BAD_REQUEST
162
+                );
163
+                $response->throttle();
164
+                return $response;
165
+            }
166
+        }
167
+
168
+        if ($shareType === 'group') {
169
+            if (!$this->groupManager->groupExists($shareWith)) {
170
+                $response = new JSONResponse(
171
+                    [
172
+                        'message' => 'Group "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl(),
173
+                        'validationErrors' => [],
174
+                    ],
175
+                    Http::STATUS_BAD_REQUEST
176
+                );
177
+                $response->throttle();
178
+                return $response;
179
+            }
180
+        }
181
+
182
+        // if no explicit display name is given, we use the uid as display name
183
+        $ownerDisplayName = $ownerDisplayName === null ? $owner : $ownerDisplayName;
184
+        $sharedByDisplayName = $sharedByDisplayName === null ? $sharedBy : $sharedByDisplayName;
185
+
186
+        // sharedBy* parameter is optional, if nothing is set we assume that it is the same user as the owner
187
+        if ($sharedBy === null) {
188
+            $sharedBy = $owner;
189
+            $sharedByDisplayName = $ownerDisplayName;
190
+        }
191
+
192
+        try {
193
+            $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
194
+            $share = $this->factory->getCloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, '', $shareType, $resourceType);
195
+            $share->setProtocol($protocol);
196
+            $provider->shareReceived($share);
197
+        } catch (ProviderDoesNotExistsException|ProviderCouldNotAddShareException $e) {
198
+            return new JSONResponse(
199
+                ['message' => $e->getMessage()],
200
+                Http::STATUS_NOT_IMPLEMENTED
201
+            );
202
+        } catch (\Exception $e) {
203
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
204
+            return new JSONResponse(
205
+                [
206
+                    'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
207
+                    'validationErrors' => [],
208
+                ],
209
+                Http::STATUS_BAD_REQUEST
210
+            );
211
+        }
212
+
213
+        $responseData = ['recipientDisplayName' => ''];
214
+        if ($shareType === 'user') {
215
+            $user = $this->userManager->get($shareWith);
216
+            if ($user) {
217
+                $responseData = [
218
+                    'recipientDisplayName' => $user->getDisplayName(),
219
+                    'recipientUserId' => $user->getUID(),
220
+                ];
221
+            }
222
+        }
223
+
224
+        return new JSONResponse($responseData, Http::STATUS_CREATED);
225
+    }
226
+
227
+    /**
228
+     * Inform the sender that an invitation was accepted to start sharing
229
+     *
230
+     * Inform about an accepted invitation so the user on the sender provider's side
231
+     * can initiate the OCM share creation. To protect the identity of the parties,
232
+     * for shares created following an OCM invitation, the user id MAY be hashed,
233
+     * and recipients implementing the OCM invitation workflow MAY refuse to process
234
+     * shares coming from unknown parties.
235
+     * @link https://cs3org.github.io/OCM-API/docs.html?branch=v1.1.0&repo=OCM-API&user=cs3org#/paths/~1invite-accepted/post
236
+     *
237
+     * @param string $recipientProvider The address of the recipent's provider
238
+     * @param string $token The token used for the invitation
239
+     * @param string $userID The userID of the recipient at the recipient's provider
240
+     * @param string $email The email address of the recipient
241
+     * @param string $name The display name of the recipient
242
+     *
243
+     * @return JSONResponse<Http::STATUS_OK, array{userID: string, email: string, name: string}, array{}>|JSONResponse<Http::STATUS_FORBIDDEN|Http::STATUS_BAD_REQUEST|Http::STATUS_CONFLICT, array{message: string, error: true}, array{}>
244
+     *
245
+     * Note: Not implementing 404 Invitation token does not exist, instead using 400
246
+     * 200: Invitation accepted
247
+     * 400: Invalid token
248
+     * 403: Invitation token does not exist
249
+     * 409: User is already known by the OCM provider
250
+     */
251
+    #[PublicPage]
252
+    #[NoCSRFRequired]
253
+    #[BruteForceProtection(action: 'inviteAccepted')]
254
+    public function inviteAccepted(string $recipientProvider, string $token, string $userID, string $email, string $name): JSONResponse {
255
+        $this->logger->debug('Processing share invitation for ' . $userID . ' with token ' . $token . ' and email ' . $email . ' and name ' . $name);
256
+
257
+        $updated = $this->timeFactory->getTime();
258
+
259
+        if ($token === '') {
260
+            $response = new JSONResponse(['message' => 'Invalid or non existing token', 'error' => true], Http::STATUS_BAD_REQUEST);
261
+            $response->throttle();
262
+            return $response;
263
+        }
264
+
265
+        try {
266
+            $invitation = $this->federatedInviteMapper->findByToken($token);
267
+        } catch (DoesNotExistException) {
268
+            $response = ['message' => 'Invalid or non existing token', 'error' => true];
269
+            $status = Http::STATUS_BAD_REQUEST;
270
+            $response = new JSONResponse($response, $status);
271
+            $response->throttle();
272
+            return $response;
273
+        }
274
+
275
+        if ($invitation->isAccepted() === true) {
276
+            $response = ['message' => 'Invite already accepted', 'error' => true];
277
+            $status = Http::STATUS_CONFLICT;
278
+            return new JSONResponse($response, $status);
279
+        }
280
+
281
+        if ($invitation->getExpiredAt() !== null && $updated > $invitation->getExpiredAt()) {
282
+            $response = ['message' => 'Invitation expired', 'error' => true];
283
+            $status = Http::STATUS_BAD_REQUEST;
284
+            return new JSONResponse($response, $status);
285
+        }
286
+        $localUser = $this->userManager->get($invitation->getUserId());
287
+        if ($localUser === null) {
288
+            $response = ['message' => 'Invalid or non existing token', 'error' => true];
289
+            $status = Http::STATUS_BAD_REQUEST;
290
+            $response = new JSONResponse($response, $status);
291
+            $response->throttle();
292
+            return $response;
293
+        }
294
+
295
+        $sharedFromEmail = $localUser->getEMailAddress();
296
+        if ($sharedFromEmail === null) {
297
+            $response = ['message' => 'Invalid or non existing token', 'error' => true];
298
+            $status = Http::STATUS_BAD_REQUEST;
299
+            $response = new JSONResponse($response, $status);
300
+            $response->throttle();
301
+            return $response;
302
+        }
303
+        $sharedFromDisplayName = $localUser->getDisplayName();
304
+
305
+        $response = ['userID' => $localUser->getUID(), 'email' => $sharedFromEmail, 'name' => $sharedFromDisplayName];
306
+        $status = Http::STATUS_OK;
307
+
308
+        $invitation->setAccepted(true);
309
+        $invitation->setRecipientEmail($email);
310
+        $invitation->setRecipientName($name);
311
+        $invitation->setRecipientProvider($recipientProvider);
312
+        $invitation->setRecipientUserId($userID);
313
+        $invitation->setAcceptedAt($updated);
314
+        $invitation = $this->federatedInviteMapper->update($invitation);
315
+
316
+        $event = new FederatedInviteAcceptedEvent($invitation);
317
+        $this->dispatcher->dispatchTyped($event);
318
+
319
+        return new JSONResponse($response, $status);
320
+    }
321
+
322
+    /**
323
+     * Send a notification about an existing share
324
+     *
325
+     * @param string $notificationType Notification type, e.g. SHARE_ACCEPTED
326
+     * @param string $resourceType calendar, file, contact,...
327
+     * @param string|null $providerId ID of the share
328
+     * @param array<string, mixed>|null $notification The actual payload of the notification
329
+     *
330
+     * @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{}>
331
+     *
332
+     * 201: The notification was successfully received
333
+     * 400: Bad request due to invalid parameters, e.g. when `type` is invalid or missing
334
+     * 403: Getting resource is not allowed
335
+     * 501: The resource type is not supported
336
+     */
337
+    #[NoCSRFRequired]
338
+    #[PublicPage]
339
+    #[BruteForceProtection(action: 'receiveFederatedShareNotification')]
340
+    public function receiveNotification($notificationType, $resourceType, $providerId, ?array $notification) {
341
+        // check if all required parameters are set
342
+        if (
343
+            $notificationType === null
344
+            || $resourceType === null
345
+            || $providerId === null
346
+            || !is_array($notification)
347
+        ) {
348
+            return new JSONResponse(
349
+                [
350
+                    'message' => 'Missing arguments',
351
+                    'validationErrors' => [],
352
+                ],
353
+                Http::STATUS_BAD_REQUEST
354
+            );
355
+        }
356
+
357
+        try {
358
+            // if request is signed and well signed, no exception are thrown
359
+            // if request is not signed and host is known for not supporting signed request, no exception are thrown
360
+            $signedRequest = $this->getSignedRequest();
361
+            $this->confirmNotificationIdentity($signedRequest, $resourceType, $notification);
362
+        } catch (IncomingRequestException $e) {
363
+            $this->logger->warning('incoming request exception', ['exception' => $e]);
364
+            return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
365
+        }
366
+
367
+        try {
368
+            $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
369
+            $result = $provider->notificationReceived($notificationType, $providerId, $notification);
370
+        } catch (ProviderDoesNotExistsException $e) {
371
+            return new JSONResponse(
372
+                [
373
+                    'message' => $e->getMessage(),
374
+                    'validationErrors' => [],
375
+                ],
376
+                Http::STATUS_BAD_REQUEST
377
+            );
378
+        } catch (ShareNotFound $e) {
379
+            $response = new JSONResponse(
380
+                [
381
+                    'message' => $e->getMessage(),
382
+                    'validationErrors' => [],
383
+                ],
384
+                Http::STATUS_BAD_REQUEST
385
+            );
386
+            $response->throttle();
387
+            return $response;
388
+        } catch (ActionNotSupportedException $e) {
389
+            return new JSONResponse(
390
+                ['message' => $e->getMessage()],
391
+                Http::STATUS_NOT_IMPLEMENTED
392
+            );
393
+        } catch (BadRequestException $e) {
394
+            return new JSONResponse($e->getReturnMessage(), Http::STATUS_BAD_REQUEST);
395
+        } catch (AuthenticationFailedException $e) {
396
+            $response = new JSONResponse(['message' => 'RESOURCE_NOT_FOUND'], Http::STATUS_FORBIDDEN);
397
+            $response->throttle();
398
+            return $response;
399
+        } catch (\Exception $e) {
400
+            $this->logger->warning('incoming notification exception', ['exception' => $e]);
401
+            return new JSONResponse(
402
+                [
403
+                    'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
404
+                    'validationErrors' => [],
405
+                ],
406
+                Http::STATUS_BAD_REQUEST
407
+            );
408
+        }
409
+
410
+        return new JSONResponse($result, Http::STATUS_CREATED);
411
+    }
412
+
413
+    /**
414
+     * map login name to internal LDAP UID if a LDAP backend is in use
415
+     *
416
+     * @param string $uid
417
+     * @return string mixed
418
+     */
419
+    private function mapUid($uid) {
420
+        // FIXME this should be a method in the user management instead
421
+        $this->logger->debug('shareWith before, ' . $uid, ['app' => $this->appName]);
422
+        Util::emitHook(
423
+            '\OCA\Files_Sharing\API\Server2Server',
424
+            'preLoginNameUsedAsUserName',
425
+            ['uid' => &$uid]
426
+        );
427
+        $this->logger->debug('shareWith after, ' . $uid, ['app' => $this->appName]);
428
+
429
+        return $uid;
430
+    }
431
+
432
+
433
+    /**
434
+     * returns signed request if available.
435
+     * throw an exception:
436
+     * - if request is signed, but wrongly signed
437
+     * - if request is not signed but instance is configured to only accept signed ocm request
438
+     *
439
+     * @return IIncomingSignedRequest|null null if remote does not (and never did) support signed request
440
+     * @throws IncomingRequestException
441
+     */
442
+    private function getSignedRequest(): ?IIncomingSignedRequest {
443
+        try {
444
+            $signedRequest = $this->signatureManager->getIncomingSignedRequest($this->signatoryManager);
445
+            $this->logger->debug('signed request available', ['signedRequest' => $signedRequest]);
446
+            return $signedRequest;
447
+        } catch (SignatureNotFoundException|SignatoryNotFoundException $e) {
448
+            $this->logger->debug('remote does not support signed request', ['exception' => $e]);
449
+            // remote does not support signed request.
450
+            // currently we still accept unsigned request until lazy appconfig
451
+            // core.enforce_signed_ocm_request is set to true (default: false)
452
+            if ($this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_ENFORCED, lazy: true)) {
453
+                $this->logger->notice('ignored unsigned request', ['exception' => $e]);
454
+                throw new IncomingRequestException('Unsigned request');
455
+            }
456
+        } catch (SignatureException $e) {
457
+            $this->logger->warning('wrongly signed request', ['exception' => $e]);
458
+            throw new IncomingRequestException('Invalid signature');
459
+        }
460
+        return null;
461
+    }
462
+
463
+
464
+    /**
465
+     * confirm that the value related to $key entry from the payload is in format userid@hostname
466
+     * and compare hostname with the origin of the signed request.
467
+     *
468
+     * If request is not signed, we still verify that the hostname from the extracted value does,
469
+     * actually, not support signed request
470
+     *
471
+     * @param IIncomingSignedRequest|null $signedRequest
472
+     * @param string $key entry from data available in data
473
+     * @param string $value value itself used in case request is not signed
474
+     *
475
+     * @throws IncomingRequestException
476
+     */
477
+    private function confirmSignedOrigin(?IIncomingSignedRequest $signedRequest, string $key, string $value): void {
478
+        if ($signedRequest === null) {
479
+            $instance = $this->getHostFromFederationId($value);
480
+            try {
481
+                $this->signatureManager->getSignatory($instance);
482
+                throw new IncomingRequestException('instance is supposed to sign its request');
483
+            } catch (SignatoryNotFoundException) {
484
+                return;
485
+            }
486
+        }
487
+
488
+        $body = json_decode($signedRequest->getBody(), true) ?? [];
489
+        $entry = trim($body[$key] ?? '', '@');
490
+        if ($this->getHostFromFederationId($entry) !== $signedRequest->getOrigin()) {
491
+            throw new IncomingRequestException('share initiation (' . $signedRequest->getOrigin() . ') from different instance (' . $entry . ') [key=' . $key . ']');
492
+        }
493
+    }
494
+
495
+    /**
496
+     *  confirm identity of the remote instance on notification, based on the share token.
497
+     *
498
+     *  If request is not signed, we still verify that the hostname from the extracted value does,
499
+     *  actually, not support signed request
500
+     *
501
+     * @param IIncomingSignedRequest|null $signedRequest
502
+     * @param string $resourceType
503
+     * @param string $sharedSecret
504
+     *
505
+     * @throws IncomingRequestException
506
+     * @throws BadRequestException
507
+     */
508
+    private function confirmNotificationIdentity(
509
+        ?IIncomingSignedRequest $signedRequest,
510
+        string $resourceType,
511
+        array $notification,
512
+    ): void {
513
+        $sharedSecret = $notification['sharedSecret'] ?? '';
514
+        if ($sharedSecret === '') {
515
+            throw new BadRequestException(['sharedSecret']);
516
+        }
517
+
518
+        try {
519
+            $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
520
+            if ($provider instanceof ISignedCloudFederationProvider) {
521
+                $identity = $provider->getFederationIdFromSharedSecret($sharedSecret, $notification);
522
+            } else {
523
+                $this->logger->debug('cloud federation provider {provider} does not implements ISignedCloudFederationProvider', ['provider' => $provider::class]);
524
+                return;
525
+            }
526
+        } catch (\Exception $e) {
527
+            throw new IncomingRequestException($e->getMessage());
528
+        }
529
+
530
+        $this->confirmNotificationEntry($signedRequest, $identity);
531
+    }
532
+
533
+
534
+    /**
535
+     * @param IIncomingSignedRequest|null $signedRequest
536
+     * @param string $entry
537
+     *
538
+     * @return void
539
+     * @throws IncomingRequestException
540
+     */
541
+    private function confirmNotificationEntry(?IIncomingSignedRequest $signedRequest, string $entry): void {
542
+        $instance = $this->getHostFromFederationId($entry);
543
+        if ($signedRequest === null) {
544
+            try {
545
+                $this->signatureManager->getSignatory($instance);
546
+                throw new IncomingRequestException('instance is supposed to sign its request');
547
+            } catch (SignatoryNotFoundException) {
548
+                return;
549
+            }
550
+        } elseif ($instance !== $signedRequest->getOrigin()) {
551
+            throw new IncomingRequestException('remote instance ' . $instance . ' not linked to origin ' . $signedRequest->getOrigin());
552
+        }
553
+    }
554
+
555
+    /**
556
+     * @param string $entry
557
+     * @return string
558
+     * @throws IncomingRequestException
559
+     */
560
+    private function getHostFromFederationId(string $entry): string {
561
+        if (!str_contains($entry, '@')) {
562
+            throw new IncomingRequestException('entry ' . $entry . ' does not contain @');
563
+        }
564
+        $rightPart = substr($entry, strrpos($entry, '@') + 1);
565
+
566
+        // in case the full scheme is sent; getting rid of it
567
+        $rightPart = $this->addressHandler->removeProtocolFromUrl($rightPart);
568
+        try {
569
+            return $this->signatureManager->extractIdentityFromUri('https://' . $rightPart);
570
+        } catch (IdentityNotFoundException) {
571
+            throw new IncomingRequestException('invalid host within federation id: ' . $entry);
572
+        }
573
+    }
574 574
 }
Please login to merge, or discard this patch.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -141,7 +141,7 @@  discard block
 block discarded – undo
141 141
 		$supportedShareTypes = $this->config->getSupportedShareTypes($resourceType);
142 142
 		if (!in_array($shareType, $supportedShareTypes)) {
143 143
 			return new JSONResponse(
144
-				['message' => 'Share type "' . $shareType . '" not implemented'],
144
+				['message' => 'Share type "'.$shareType.'" not implemented'],
145 145
 				Http::STATUS_NOT_IMPLEMENTED
146 146
 			);
147 147
 		}
@@ -155,7 +155,7 @@  discard block
 block discarded – undo
155 155
 			if (!$this->userManager->userExists($shareWith)) {
156 156
 				$response = new JSONResponse(
157 157
 					[
158
-						'message' => 'User "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl(),
158
+						'message' => 'User "'.$shareWith.'" does not exists at '.$this->urlGenerator->getBaseUrl(),
159 159
 						'validationErrors' => [],
160 160
 					],
161 161
 					Http::STATUS_BAD_REQUEST
@@ -169,7 +169,7 @@  discard block
 block discarded – undo
169 169
 			if (!$this->groupManager->groupExists($shareWith)) {
170 170
 				$response = new JSONResponse(
171 171
 					[
172
-						'message' => 'Group "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl(),
172
+						'message' => 'Group "'.$shareWith.'" does not exists at '.$this->urlGenerator->getBaseUrl(),
173 173
 						'validationErrors' => [],
174 174
 					],
175 175
 					Http::STATUS_BAD_REQUEST
@@ -194,7 +194,7 @@  discard block
 block discarded – undo
194 194
 			$share = $this->factory->getCloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, '', $shareType, $resourceType);
195 195
 			$share->setProtocol($protocol);
196 196
 			$provider->shareReceived($share);
197
-		} catch (ProviderDoesNotExistsException|ProviderCouldNotAddShareException $e) {
197
+		} catch (ProviderDoesNotExistsException | ProviderCouldNotAddShareException $e) {
198 198
 			return new JSONResponse(
199 199
 				['message' => $e->getMessage()],
200 200
 				Http::STATUS_NOT_IMPLEMENTED
@@ -203,7 +203,7 @@  discard block
 block discarded – undo
203 203
 			$this->logger->error($e->getMessage(), ['exception' => $e]);
204 204
 			return new JSONResponse(
205 205
 				[
206
-					'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
206
+					'message' => 'Internal error at '.$this->urlGenerator->getBaseUrl(),
207 207
 					'validationErrors' => [],
208 208
 				],
209 209
 				Http::STATUS_BAD_REQUEST
@@ -252,7 +252,7 @@  discard block
 block discarded – undo
252 252
 	#[NoCSRFRequired]
253 253
 	#[BruteForceProtection(action: 'inviteAccepted')]
254 254
 	public function inviteAccepted(string $recipientProvider, string $token, string $userID, string $email, string $name): JSONResponse {
255
-		$this->logger->debug('Processing share invitation for ' . $userID . ' with token ' . $token . ' and email ' . $email . ' and name ' . $name);
255
+		$this->logger->debug('Processing share invitation for '.$userID.' with token '.$token.' and email '.$email.' and name '.$name);
256 256
 
257 257
 		$updated = $this->timeFactory->getTime();
258 258
 
@@ -400,7 +400,7 @@  discard block
 block discarded – undo
400 400
 			$this->logger->warning('incoming notification exception', ['exception' => $e]);
401 401
 			return new JSONResponse(
402 402
 				[
403
-					'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
403
+					'message' => 'Internal error at '.$this->urlGenerator->getBaseUrl(),
404 404
 					'validationErrors' => [],
405 405
 				],
406 406
 				Http::STATUS_BAD_REQUEST
@@ -418,13 +418,13 @@  discard block
 block discarded – undo
418 418
 	 */
419 419
 	private function mapUid($uid) {
420 420
 		// FIXME this should be a method in the user management instead
421
-		$this->logger->debug('shareWith before, ' . $uid, ['app' => $this->appName]);
421
+		$this->logger->debug('shareWith before, '.$uid, ['app' => $this->appName]);
422 422
 		Util::emitHook(
423 423
 			'\OCA\Files_Sharing\API\Server2Server',
424 424
 			'preLoginNameUsedAsUserName',
425 425
 			['uid' => &$uid]
426 426
 		);
427
-		$this->logger->debug('shareWith after, ' . $uid, ['app' => $this->appName]);
427
+		$this->logger->debug('shareWith after, '.$uid, ['app' => $this->appName]);
428 428
 
429 429
 		return $uid;
430 430
 	}
@@ -444,7 +444,7 @@  discard block
 block discarded – undo
444 444
 			$signedRequest = $this->signatureManager->getIncomingSignedRequest($this->signatoryManager);
445 445
 			$this->logger->debug('signed request available', ['signedRequest' => $signedRequest]);
446 446
 			return $signedRequest;
447
-		} catch (SignatureNotFoundException|SignatoryNotFoundException $e) {
447
+		} catch (SignatureNotFoundException | SignatoryNotFoundException $e) {
448 448
 			$this->logger->debug('remote does not support signed request', ['exception' => $e]);
449 449
 			// remote does not support signed request.
450 450
 			// currently we still accept unsigned request until lazy appconfig
@@ -488,7 +488,7 @@  discard block
 block discarded – undo
488 488
 		$body = json_decode($signedRequest->getBody(), true) ?? [];
489 489
 		$entry = trim($body[$key] ?? '', '@');
490 490
 		if ($this->getHostFromFederationId($entry) !== $signedRequest->getOrigin()) {
491
-			throw new IncomingRequestException('share initiation (' . $signedRequest->getOrigin() . ') from different instance (' . $entry . ') [key=' . $key . ']');
491
+			throw new IncomingRequestException('share initiation ('.$signedRequest->getOrigin().') from different instance ('.$entry.') [key='.$key.']');
492 492
 		}
493 493
 	}
494 494
 
@@ -548,7 +548,7 @@  discard block
 block discarded – undo
548 548
 				return;
549 549
 			}
550 550
 		} elseif ($instance !== $signedRequest->getOrigin()) {
551
-			throw new IncomingRequestException('remote instance ' . $instance . ' not linked to origin ' . $signedRequest->getOrigin());
551
+			throw new IncomingRequestException('remote instance '.$instance.' not linked to origin '.$signedRequest->getOrigin());
552 552
 		}
553 553
 	}
554 554
 
@@ -559,16 +559,16 @@  discard block
 block discarded – undo
559 559
 	 */
560 560
 	private function getHostFromFederationId(string $entry): string {
561 561
 		if (!str_contains($entry, '@')) {
562
-			throw new IncomingRequestException('entry ' . $entry . ' does not contain @');
562
+			throw new IncomingRequestException('entry '.$entry.' does not contain @');
563 563
 		}
564 564
 		$rightPart = substr($entry, strrpos($entry, '@') + 1);
565 565
 
566 566
 		// in case the full scheme is sent; getting rid of it
567 567
 		$rightPart = $this->addressHandler->removeProtocolFromUrl($rightPart);
568 568
 		try {
569
-			return $this->signatureManager->extractIdentityFromUri('https://' . $rightPart);
569
+			return $this->signatureManager->extractIdentityFromUri('https://'.$rightPart);
570 570
 		} catch (IdentityNotFoundException) {
571
-			throw new IncomingRequestException('invalid host within federation id: ' . $entry);
571
+			throw new IncomingRequestException('invalid host within federation id: '.$entry);
572 572
 		}
573 573
 	}
574 574
 }
Please login to merge, or discard this patch.