From 696ece2f52f5c78987c48fa569b07ec0d4ac0660 Mon Sep 17 00:00:00 2001 From: Alexander Piskun <13381981+bigcat88@users.noreply.github.com> Date: Fri, 12 Jul 2024 23:29:55 +0300 Subject: [PATCH] feat: webhooks_listeners app support for sending direct requests to ExApps using AppAPI. Signed-off-by: Alexander Piskun --- .../lib/BackgroundJobs/WebhookCall.php | 35 +++++- .../lib/Db/WebhookListener.php | 5 + build/stubs/app_api.php | 101 +++++++++++++++--- 3 files changed, 126 insertions(+), 15 deletions(-) diff --git a/apps/webhook_listeners/lib/BackgroundJobs/WebhookCall.php b/apps/webhook_listeners/lib/BackgroundJobs/WebhookCall.php index 9c9a4bb6dbe37..f7c111f41e816 100644 --- a/apps/webhook_listeners/lib/BackgroundJobs/WebhookCall.php +++ b/apps/webhook_listeners/lib/BackgroundJobs/WebhookCall.php @@ -11,11 +11,15 @@ use OCA\WebhookListeners\Db\AuthMethod; use OCA\WebhookListeners\Db\WebhookListenerMapper; +use OCP\App\IAppManager; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\QueuedJob; use OCP\Http\Client\IClientService; use OCP\ICertificateManager; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; use Psr\Log\LoggerInterface; +use RuntimeException; class WebhookCall extends QueuedJob { public function __construct( @@ -23,6 +27,7 @@ public function __construct( private ICertificateManager $certificateManager, private WebhookListenerMapper $mapper, private LoggerInterface $logger, + private IAppManager $appManager, ITimeFactory $timeFactory, ) { parent::__construct($timeFactory); @@ -49,15 +54,39 @@ protected function run($argument): void { $options['headers'] = array_merge($options['headers'], $authHeaders); break; } - $response = $client->request($webhookListener->getHttpMethod(), $webhookListener->getUri(), $options); + $webhookUri = $webhookListener->getUri(); + $exAppId = $webhookListener->getAppId(); + if ($exAppId !== null && str_starts_with($webhookUri, "/")) { + // ExApp is awaiting a direct request to itself using AppAPI + if (!$this->appManager->isInstalled('app_api')) { + throw new RuntimeException('AppAPI is disabled or not installed.'); + } + try { + $appApiFunctions = \OCP\Server::get(\OCA\AppAPI\PublicFunctions::class); + } catch (ContainerExceptionInterface | NotFoundExceptionInterface) { + throw new RuntimeException('Could not get AppAPI public functions.'); + } + $exApp = $appApiFunctions->getExApp($exAppId); + if ($exApp === null) { + throw new RuntimeException('ExApp ' . $exAppId . ' is missing.'); + } elseif (!$exApp['enabled']) { + throw new RuntimeException('ExApp ' . $exAppId . ' is disabled.'); + } + $response = $appApiFunctions->exAppRequest($exAppId, $webhookUri, $webhookListener->getUserId(), $webhookListener->getHttpMethod(), [], $options); + if (is_array($response) && isset($response['error'])) { + throw new RuntimeException(sprintf('Error during request to ExApp(%s): %s', $exAppId, $response['error'])); + } + } else { + $response = $client->request($webhookListener->getHttpMethod(), $webhookUri, $options); + } $statusCode = $response->getStatusCode(); if ($statusCode >= 200 && $statusCode < 300) { $this->logger->debug('Webhook returned status code '.$statusCode, ['body' => $response->getBody()]); } else { - $this->logger->warning('Webhook returned unexpected status code '.$statusCode, ['body' => $response->getBody()]); + $this->logger->warning('Webhook(' . $webhookId . ') returned unexpected status code '.$statusCode, ['body' => $response->getBody()]); } } catch (\Exception $e) { - $this->logger->error('Webhook call failed: '.$e->getMessage(), ['exception' => $e]); + $this->logger->error('Webhook(' . $webhookId . ') call failed: '.$e->getMessage(), ['exception' => $e]); } } } diff --git a/apps/webhook_listeners/lib/Db/WebhookListener.php b/apps/webhook_listeners/lib/Db/WebhookListener.php index a59549d0c4a95..8053fc318dcf6 100644 --- a/apps/webhook_listeners/lib/Db/WebhookListener.php +++ b/apps/webhook_listeners/lib/Db/WebhookListener.php @@ -14,6 +14,7 @@ /** * @method void setUserId(string $userId) + * @method ?string getAppId() * @method string getUserId() * @method string getHttpMethod() * @method string getUri() @@ -139,4 +140,8 @@ public function jsonSerialize(): array { ) ); } + + public function getAppId(): ?string { + return $this->appId; + } } diff --git a/build/stubs/app_api.php b/build/stubs/app_api.php index 1ab63499b773c..04a6042dffd53 100644 --- a/build/stubs/app_api.php +++ b/build/stubs/app_api.php @@ -1,15 +1,92 @@ exAppService->getExApp($appId); + if ($exApp === null) { + return ['error' => sprintf('ExApp `%s` not found', $appId)]; + } + return $this->service->requestToExApp($exApp, $route, $userId, $method, $params, $options, $request); + } + + /** + * Async request to ExApp with AppAPI auth headers + * + * @throws \Exception if ExApp not found + */ + public function asyncExAppRequest( + string $appId, + string $route, + ?string $userId = null, + string $method = 'POST', + array $params = [], + array $options = [], + ?IRequest $request = null, + ): IPromise { + $exApp = $this->exAppService->getExApp($appId); + if ($exApp === null) { + throw new \Exception(sprintf('ExApp `%s` not found', $appId)); + } + return $this->service->requestToExAppAsync($exApp, $route, $userId, $method, $params, $options, $request); + } + + /** + * Get basic ExApp info by appid + * + * @param string $appId + * + * @return array|null ExApp info (appid, version, name, enabled) or null if no ExApp found + */ + public function getExApp(string $appId): ?array { + $exApp = $this->exAppService->getExApp($appId); + if ($exApp !== null) { + $info = $exApp->jsonSerialize(); + return [ + 'appid' => $info['appid'], + 'version' => $info['version'], + 'name' => $info['name'], + 'enabled' => $info['enabled'], + ]; + } + return null; + } + } }