]> source.dussan.org Git - nextcloud-server.git/commitdiff
feat: webhooks_listeners app support for sending direct requests to ExApps using...
authorAlexander Piskun <13381981+bigcat88@users.noreply.github.com>
Fri, 12 Jul 2024 20:29:55 +0000 (23:29 +0300)
committerAlexander Piskun <bigcat88@icloud.com>
Tue, 16 Jul 2024 16:36:43 +0000 (19:36 +0300)
Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
apps/webhook_listeners/lib/BackgroundJobs/WebhookCall.php
apps/webhook_listeners/lib/Db/WebhookListener.php
build/stubs/app_api.php

index 9c9a4bb6dbe377a22cdd122676d7d5b690d75e7b..f7c111f41e81628171b63193238e7a7a09a3d62a 100644 (file)
@@ -11,11 +11,15 @@ namespace OCA\WebhookListeners\BackgroundJobs;
 
 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 @@ class WebhookCall extends QueuedJob {
                private ICertificateManager $certificateManager,
                private WebhookListenerMapper $mapper,
                private LoggerInterface $logger,
+               private IAppManager $appManager,
                ITimeFactory $timeFactory,
        ) {
                parent::__construct($timeFactory);
@@ -49,15 +54,39 @@ class WebhookCall extends QueuedJob {
                                        $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]);
                }
        }
 }
index a59549d0c4a9598a695994a7b02a5fd61a22150e..8053fc318dcf6eee252c633b5dfa1a803e409756 100644 (file)
@@ -14,6 +14,7 @@ use OCP\Security\ICrypto;
 
 /**
  * @method void setUserId(string $userId)
+ * @method ?string getAppId()
  * @method string getUserId()
  * @method string getHttpMethod()
  * @method string getUri()
@@ -139,4 +140,8 @@ class WebhookListener extends Entity implements \JsonSerializable {
                        )
                );
        }
+
+       public function getAppId(): ?string {
+               return $this->appId;
+       }
 }
index 1ab63499b773cdd340ba57fe41fdc12533e3efcc..04a6042dffd53884f4616624a04089ab620c2e5b 100644 (file)
@@ -1,15 +1,92 @@
 <?php
 
-namespace OCA\AppAPI\Service;
-
-use OCP\IRequest;
-
-class AppAPIService {
-       /**
-        * @param IRequest $request
-        * @param bool $isDav
-        *
-        * @return bool
-        */
-       public function validateExAppRequestToNC(IRequest $request, bool $isDav = false): bool {}
+namespace OCA\AppAPI\Service {
+       use OCP\IRequest;
+
+       class AppAPIService {
+               /**
+                * @param IRequest $request
+                * @param bool $isDav
+                *
+                * @return bool
+                */
+               public function validateExAppRequestToNC(IRequest $request, bool $isDav = false): bool {}
+       }
+}
+
+namespace OCA\AppAPI {
+
+       use OCP\IRequest;
+       use OCP\Http\Client\IPromise;
+       use OCP\Http\Client\IResponse;
+
+       class PublicFunctions {
+
+               public function __construct(
+                       private readonly ExAppService  $exAppService,
+                       private readonly AppAPIService $service,
+               ) {
+               }
+
+               /**
+                * Request to ExApp with AppAPI auth headers
+                */
+               public function exAppRequest(
+                       string $appId,
+                       string $route,
+                       ?string $userId = null,
+                       string $method = 'POST',
+                       array $params = [],
+                       array $options = [],
+                       ?IRequest $request = null,
+               ): array|IResponse {
+                       $exApp = $this->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;
+               }
+       }
 }