From 8d18607f8029606381489dccd6c66efdffb842f0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?C=C3=B4me=20Chilliet?= Date: Thu, 23 May 2024 11:19:27 +0200 Subject: [PATCH] feat: Add support for webhook listeners MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/composer/composer/autoload_classmap.php | 2 + lib/composer/composer/autoload_static.php | 2 + .../Bootstrap/RegistrationContext.php | 32 ++++++++++ .../WebhookEventListenerRegistration.php | 61 +++++++++++++++++++ lib/private/EventDispatcher/WebhookCaller.php | 53 ++++++++++++++++ .../Bootstrap/IRegistrationContext.php | 16 +++++ 6 files changed, 166 insertions(+) create mode 100644 lib/private/AppFramework/Bootstrap/WebhookEventListenerRegistration.php create mode 100644 lib/private/EventDispatcher/WebhookCaller.php diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 8e1408e121e..7ae7b490f7d 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -866,6 +866,7 @@ return array( 'OC\\AppFramework\\Bootstrap\\ServiceAliasRegistration' => $baseDir . '/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php', 'OC\\AppFramework\\Bootstrap\\ServiceFactoryRegistration' => $baseDir . '/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php', 'OC\\AppFramework\\Bootstrap\\ServiceRegistration' => $baseDir . '/lib/private/AppFramework/Bootstrap/ServiceRegistration.php', + 'OC\\AppFramework\\Bootstrap\\WebhookEventListenerRegistration' => $baseDir . '/lib/private/AppFramework/Bootstrap/WebhookEventListenerRegistration.php', 'OC\\AppFramework\\DependencyInjection\\DIContainer' => $baseDir . '/lib/private/AppFramework/DependencyInjection/DIContainer.php', 'OC\\AppFramework\\Http' => $baseDir . '/lib/private/AppFramework/Http.php', 'OC\\AppFramework\\Http\\Dispatcher' => $baseDir . '/lib/private/AppFramework/Http/Dispatcher.php', @@ -1382,6 +1383,7 @@ return array( 'OC\\Encryption\\Util' => $baseDir . '/lib/private/Encryption/Util.php', 'OC\\EventDispatcher\\EventDispatcher' => $baseDir . '/lib/private/EventDispatcher/EventDispatcher.php', 'OC\\EventDispatcher\\ServiceEventListener' => $baseDir . '/lib/private/EventDispatcher/ServiceEventListener.php', + 'OC\\EventDispatcher\\WebhookCaller' => $baseDir . '/lib/private/EventDispatcher/WebhookCaller.php', 'OC\\EventSource' => $baseDir . '/lib/private/EventSource.php', 'OC\\EventSourceFactory' => $baseDir . '/lib/private/EventSourceFactory.php', 'OC\\Federation\\CloudFederationFactory' => $baseDir . '/lib/private/Federation/CloudFederationFactory.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index d6939ae36ce..16b148dae5b 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -907,6 +907,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\AppFramework\\Bootstrap\\ServiceAliasRegistration' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php', 'OC\\AppFramework\\Bootstrap\\ServiceFactoryRegistration' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php', 'OC\\AppFramework\\Bootstrap\\ServiceRegistration' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Bootstrap/ServiceRegistration.php', + 'OC\\AppFramework\\Bootstrap\\WebhookEventListenerRegistration' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Bootstrap/WebhookEventListenerRegistration.php', 'OC\\AppFramework\\DependencyInjection\\DIContainer' => __DIR__ . '/../../..' . '/lib/private/AppFramework/DependencyInjection/DIContainer.php', 'OC\\AppFramework\\Http' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http.php', 'OC\\AppFramework\\Http\\Dispatcher' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/Dispatcher.php', @@ -1423,6 +1424,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Encryption\\Util' => __DIR__ . '/../../..' . '/lib/private/Encryption/Util.php', 'OC\\EventDispatcher\\EventDispatcher' => __DIR__ . '/../../..' . '/lib/private/EventDispatcher/EventDispatcher.php', 'OC\\EventDispatcher\\ServiceEventListener' => __DIR__ . '/../../..' . '/lib/private/EventDispatcher/ServiceEventListener.php', + 'OC\\EventDispatcher\\WebhookCaller' => __DIR__ . '/../../..' . '/lib/private/EventDispatcher/WebhookCaller.php', 'OC\\EventSource' => __DIR__ . '/../../..' . '/lib/private/EventSource.php', 'OC\\EventSourceFactory' => __DIR__ . '/../../..' . '/lib/private/EventSourceFactory.php', 'OC\\Federation\\CloudFederationFactory' => __DIR__ . '/../../..' . '/lib/private/Federation/CloudFederationFactory.php', diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php index df03d59ebfa..b82f30e0fb6 100644 --- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php +++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php @@ -81,6 +81,9 @@ class RegistrationContext { /** @var EventListenerRegistration[] */ private $eventListeners = []; + /** @var WebhookEventListenerRegistration[] */ + private $webhookEventListeners = []; + /** @var MiddlewareRegistration[] */ private $middlewares = []; @@ -221,6 +224,17 @@ class RegistrationContext { ); } + public function registerWebhookEventListener(string $event, string $method, string $listenerUri, array $options = [], int $priority = 0): void { + $this->context->registerWebhookEventListener( + $this->appId, + $event, + $method, + $listenerUri, + $options, + $priority, + ); + } + public function registerMiddleware(string $class, bool $global = false): void { $this->context->registerMiddleware( $this->appId, @@ -451,6 +465,10 @@ class RegistrationContext { $this->eventListeners[] = new EventListenerRegistration($appId, $event, $listener, $priority); } + public function registerWebhookEventListener(string $appId, string $event, string $method, string $listenerUri, array $options, int $priority = 0): void { + $this->webhookEventListeners[] = new WebhookEventListenerRegistration($appId, $event, $method, $listenerUri, $options, $priority); + } + /** * @psalm-param class-string $class */ @@ -674,6 +692,20 @@ class RegistrationContext { ]); } } + while (($registration = array_shift($this->webhookEventListeners)) !== null) { + try { + $eventDispatcher->addListener( + $registration->getEvent(), + $registration->getCallable(), + $registration->getPriority() + ); + } catch (Throwable $e) { + $appId = $registration->getAppId(); + $this->logger->error("Error during event webhook listener registration of $appId: " . $e->getMessage(), [ + 'exception' => $e, + ]); + } + } } /** diff --git a/lib/private/AppFramework/Bootstrap/WebhookEventListenerRegistration.php b/lib/private/AppFramework/Bootstrap/WebhookEventListenerRegistration.php new file mode 100644 index 00000000000..3b283c6029d --- /dev/null +++ b/lib/private/AppFramework/Bootstrap/WebhookEventListenerRegistration.php @@ -0,0 +1,61 @@ + + * + * @author Côme Chilliet + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Bootstrap; + +use OC\EventDispatcher\WebhookCaller; +use OCP\EventDispatcher\Event; +use OCP\Server; + +/** + * @psalm-immutable + */ +class WebhookEventListenerRegistration extends ARegistration { + public function __construct( + string $appId, + private string $event, + private string $method, + private string $uri, + private array $options, + private int $priority, + ) { + parent::__construct($appId); + } + + public function getEvent(): string { + return $this->event; + } + + public function getCallable(): callable { + return function (Event $event) { + Server::get(WebhookCaller::class)->callWebhook($event, $this->method, $this->uri, $this->options); + }; + } + + public function getPriority(): int { + return $this->priority; + } +} diff --git a/lib/private/EventDispatcher/WebhookCaller.php b/lib/private/EventDispatcher/WebhookCaller.php new file mode 100644 index 00000000000..2f0d1a2ab06 --- /dev/null +++ b/lib/private/EventDispatcher/WebhookCaller.php @@ -0,0 +1,53 @@ + + * + * @author Côme Chilliet + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\EventDispatcher; + +use OCP\EventDispatcher\Event; +use OCP\Http\Client\IClientService; + +class WebhookCaller { + public function __construct( + private IClientService $clientService, + ) { + } + + public function callWebhook( + Event $event, + string $method, + string $uri, + array $options, + ): void { + $client = $this->clientService->newClient(); + $client->request($method, $uri, $options + ['query' => ['event' => $event::class]]); + + /** + * TODO: + * Serialization of the event + * Timeout or async + */ + } +} diff --git a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php index b86f7bcd76d..17040ccfb4f 100644 --- a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php +++ b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php @@ -120,6 +120,22 @@ interface IRegistrationContext { */ public function registerEventListener(string $event, string $listener, int $priority = 0): void; + /** + * Register a webhook listener + * + * @psalm-template T of \OCP\EventDispatcher\Event + * @param class-string $event The fully-qualified class name of the Event sub class to listen for + * @param string $method The HTTP method to use (usually 'GET' or 'POST') + * @param string $listenerUri The absolute URI to contact + * @param array $options Additional options for the request, {@see \OCP\Http\Client::request()} + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + * + * @since 30.0.0 + */ + public function registerWebhookEventListener(string $event, string $method, string $listenerUri, array $options = [], int $priority = 0): void; + + /** * @param string $class * @param bool $global load this middleware also for requests of other apps? Added in Nextcloud 26 -- 2.39.5