Repository navigation
feat(ocm): support new format of notfication payload #66049
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| namespace OCA\CloudFederationAPI\Controller; | ||
|
|
||
| use OC\AppFramework\Http\Attributes\FederationRateLimit; | ||
| use OC\Federation\SenderDomainAwareCloudFederationNotification; | ||
| use OC\OCM\OCMSignatoryManager; | ||
| use OCA\CloudFederationAPI\Config; | ||
| use OCA\CloudFederationAPI\ResponseDefinitions; | ||
|
|
@@ -270,21 +271,27 @@ public function addShare($shareWith, $name, $description, $providerId, $owner, $ | |
| * Send a notification about an existing share | ||
| * | ||
| * @param string $notificationType Notification type, e.g. SHARE_ACCEPTED | ||
| * @param string $resourceType calendar, file, contact,... | ||
| * @param string|null $resourceType calendar, file, contact,... | ||
| * @param string|null $providerId ID of the share | ||
| * @param array<string, mixed>|null $notification The actual payload of the notification | ||
| * @param string|null $senderDomain Domain of the sending server | ||
| * @param string|null $shareType Type of the recipient the notification is about, e.g. user | ||
| * | ||
| * @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{}> | ||
| * | ||
| * 201: The notification was successfully received | ||
| * 400: Bad request due to invalid parameters, e.g. when `type` is invalid or missing | ||
| * 403: Getting resource is not allowed | ||
| * 501: The resource type is not supported | ||
| * 501: The resource type or the notification type is not supported | ||
| */ | ||
| #[NoCSRFRequired] | ||
| #[PublicPage] | ||
| #[BruteForceProtection(action: 'receiveFederatedShareNotification')] | ||
| public function receiveNotification($notificationType, $resourceType, $providerId, ?array $notification) { | ||
| public function receiveNotification($notificationType, $resourceType, $providerId, ?array $notification, $senderDomain, $shareType) { | ||
| if ($senderDomain !== null) { | ||
| return $this->receiveSenderDomainNotification($notificationType, $resourceType, $providerId, $notification, $senderDomain, $shareType); | ||
| } | ||
|
|
||
| // check if all required parameters are set | ||
| if ( | ||
| $notificationType === null | ||
|
|
@@ -317,6 +324,73 @@ public function receiveNotification($notificationType, $resourceType, $providerI | |
| try { | ||
| $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType); | ||
| $result = $provider->notificationReceived($notificationType, $providerId, $notification); | ||
| } catch (\Exception $e) { | ||
| return $this->notificationExceptionResponse($e); | ||
| } | ||
|
|
||
| try { | ||
| $notificationObject = new SenderDomainAwareCloudFederationNotification(null, null); | ||
| $notificationObject->setMessage($notificationType, $resourceType, $providerId, $notification); | ||
| $notificationEvent = new OCMNotificationReceivedEvent($notificationObject); | ||
| $this->dispatcher->dispatchTyped($notificationEvent); | ||
| } catch (\Exception $e) { | ||
| $this->logger->warning('error while dispatching OCM notification received event', ['exception' => $e]); | ||
| } | ||
|
|
||
| return new JSONResponse($result, Http::STATUS_CREATED); | ||
| } | ||
|
|
||
| /** | ||
| * Send a notification with a senderDomain field to listeners of {@see OCMNotificationReceivedEvent} | ||
| * | ||
| * @param array<string, mixed>|null $notification | ||
| * @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{}> | ||
| */ | ||
| private function receiveSenderDomainNotification(?string $notificationType, ?string $resourceType, ?string $providerId, ?array $notification, string $senderDomain, ?string $shareType): JSONResponse { | ||
| // check if all required parameters are set | ||
| if ( | ||
| $notificationType === null | ||
| || $senderDomain === '' | ||
| || ($resourceType === null && $shareType === null) | ||
| ) { | ||
| return new JSONResponse( | ||
| [ | ||
| 'message' => 'Missing arguments', | ||
| 'validationErrors' => [], | ||
| ], | ||
| Http::STATUS_BAD_REQUEST | ||
| ); | ||
| } | ||
|
|
||
| try { | ||
| $signedRequest = $this->ocmDiscoveryService->getIncomingSignedRequestFromDomain($senderDomain); | ||
| } catch (IncomingRequestException $e) { | ||
| $this->logger->warning('incoming request exception', ['exception' => $e]); | ||
| return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST); | ||
| } | ||
|
|
||
| $notificationObject = new SenderDomainAwareCloudFederationNotification($signedRequest->getOrigin(), $shareType); | ||
| $notificationObject->setMessage($notificationType, $resourceType, $providerId, $notification ?? []); | ||
| $notificationEvent = new OCMNotificationReceivedEvent($notificationObject); | ||
|
|
||
| try { | ||
| $this->dispatcher->dispatchTyped($notificationEvent); | ||
| $result = $notificationEvent->getResult() ?? throw new ActionNotSupportedException($notificationType); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess this will throw if there is no listener? Perhaps that is not what we want?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. with no listener, yes, it would throw, but the exception is handled and the sender of the notification would get a 501 (Http::STATUS_NOT_IMPLEMENTED) looking at the spec for 501: https://github.com/cs3org/OCM-API/blob/v1.5.0/spec.yaml#L95 you think we should return something else, maybe 201? |
||
| } catch (\Exception $e) { | ||
| return $this->notificationExceptionResponse($e); | ||
| } | ||
|
|
||
| return new JSONResponse($result, Http::STATUS_CREATED); | ||
| } | ||
|
|
||
| /** | ||
| * Map an exception thrown while handling a notification to its response | ||
| * | ||
| * @return JSONResponse<Http::STATUS_BAD_REQUEST, CloudFederationAPIValidationError, array{}>|JSONResponse<Http::STATUS_FORBIDDEN|Http::STATUS_NOT_IMPLEMENTED, CloudFederationAPIError, array{}> | ||
| */ | ||
| private function notificationExceptionResponse(\Exception $e): JSONResponse { | ||
| try { | ||
| throw $e; | ||
| } catch (ProviderDoesNotExistsException $e) { | ||
| return new JSONResponse( | ||
| [ | ||
|
|
@@ -356,17 +430,6 @@ public function receiveNotification($notificationType, $resourceType, $providerI | |
| Http::STATUS_BAD_REQUEST | ||
| ); | ||
| } | ||
|
|
||
| try { | ||
| $notificationObject = $this->factory->getCloudFederationNotification(); | ||
| $notificationObject->setMessage($notificationType, $resourceType, $providerId, $notification); | ||
| $notificationEvent = new OCMNotificationReceivedEvent($notificationObject); | ||
| $this->dispatcher->dispatchTyped($notificationEvent); | ||
| } catch (\Exception $e) { | ||
| $this->logger->warning('error while dispatching OCM notification received event', ['exception' => $e]); | ||
| } | ||
|
|
||
| return new JSONResponse($result, Http::STATUS_CREATED); | ||
| } | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| /** | ||
| * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors | ||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||
| */ | ||
|
|
||
| namespace OC\Federation; | ||
|
|
||
| use OCP\Federation\ISenderDomainAwareCloudFederationNotification; | ||
|
|
||
| class SenderDomainAwareCloudFederationNotification extends CloudFederationNotification implements ISenderDomainAwareCloudFederationNotification { | ||
| public function __construct( | ||
| private readonly ?string $senderDomain, | ||
| private readonly ?string $shareType, | ||
| ) { | ||
| } | ||
|
|
||
| #[\Override] | ||
| public function getMessage(): array { | ||
| $message = parent::getMessage(); | ||
| if ($this->senderDomain !== null) { | ||
| $message['senderDomain'] = $this->senderDomain; | ||
| } | ||
| if ($this->shareType !== null) { | ||
| $message['shareType'] = $this->shareType; | ||
| } | ||
|
|
||
| return $message; | ||
| } | ||
|
|
||
| #[\Override] | ||
| public function getNotificationType(): string { | ||
| return (string)($this->getMessage()['notificationType'] ?? ''); | ||
| } | ||
|
|
||
| #[\Override] | ||
| public function getSenderDomain(): ?string { | ||
| return $this->senderDomain; | ||
| } | ||
|
|
||
| #[\Override] | ||
| public function getResourceType(): ?string { | ||
| return $this->getMessage()['resourceType'] ?? null; | ||
| } | ||
|
|
||
| #[\Override] | ||
| public function getShareType(): ?string { | ||
| return $this->shareType; | ||
| } | ||
|
|
||
| #[\Override] | ||
| public function getProviderId(): ?string { | ||
| $providerId = $this->getMessage()['providerId'] ?? ''; | ||
| return $providerId === '' ? null : $providerId; | ||
| } | ||
|
|
||
| #[\Override] | ||
| public function getNotification(): array { | ||
| return $this->getMessage()['notification'] ?? []; | ||
| } | ||
| } | ||
|
cristianscheid marked this conversation as resolved.
Resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| /** | ||
| * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors | ||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||
| */ | ||
|
|
||
| namespace OCP\Federation; | ||
|
|
||
| use OCP\AppFramework\Attribute\Consumable; | ||
|
|
||
| /** | ||
| * Notification received on the OCM notifications endpoint. | ||
| * | ||
| * @link https://github.com/cs3org/OCM-API/blob/v1.5.0/IETF-OCM.md#notifications | ||
| * @since 36.0.0 | ||
| */ | ||
| #[Consumable(since: '36.0.0')] | ||
| interface ISenderDomainAwareCloudFederationNotification extends ICloudFederationNotification { | ||
| /** | ||
| * Notification type, e.g. SHARE_ACCEPTED | ||
| * | ||
| * @since 36.0.0 | ||
| */ | ||
| public function getNotificationType(): string; | ||
|
|
||
| /** | ||
| * Domain of the sending server, verified against the request signature. | ||
| * Null for notifications without a senderDomain field. | ||
| * | ||
| * @since 36.0.0 | ||
| */ | ||
| public function getSenderDomain(): ?string; | ||
|
|
||
| /** | ||
| * Type of the resource the notification is about, e.g. file | ||
| * | ||
| * @since 36.0.0 | ||
| */ | ||
| public function getResourceType(): ?string; | ||
|
|
||
| /** | ||
| * Type of the recipient the notification is about, e.g. user | ||
| * | ||
| * @since 36.0.0 | ||
| */ | ||
| public function getShareType(): ?string; | ||
|
|
||
| /** | ||
| * Identifier assigned by the sending server to the underlying share | ||
| * | ||
| * @since 36.0.0 | ||
| * @deprecated 36.0.0 deprecated by the OCM specification, identify the share from {@see getNotification()} instead | ||
| */ | ||
| public function getProviderId(): ?string; | ||
|
|
||
| /** | ||
| * Additional parameters, depending on the notification type and the resource type | ||
| * | ||
| * @return array<string, mixed> | ||
| * @since 36.0.0 | ||
| */ | ||
| public function getNotification(): array; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.