diff options
author | Alexander Piskun <13381981+bigcat88@users.noreply.github.com> | 2024-07-12 23:29:55 +0300 |
---|---|---|
committer | Alexander Piskun <bigcat88@icloud.com> | 2024-07-16 19:36:43 +0300 |
commit | 696ece2f52f5c78987c48fa569b07ec0d4ac0660 (patch) | |
tree | d90479c8bfbe10a1fd8bc77b325340dc33b3fbf1 | |
parent | a2ded2005086df9fc8c05beb68452c2cee571203 (diff) | |
download | nextcloud-server-696ece2f52f5c78987c48fa569b07ec0d4ac0660.tar.gz nextcloud-server-696ece2f52f5c78987c48fa569b07ec0d4ac0660.zip |
feat: webhooks_listeners app support for sending direct requests to ExApps using AppAPI.
Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
-rw-r--r-- | apps/webhook_listeners/lib/BackgroundJobs/WebhookCall.php | 35 | ||||
-rw-r--r-- | apps/webhook_listeners/lib/Db/WebhookListener.php | 5 | ||||
-rw-r--r-- | 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 9c9a4bb6dbe..f7c111f41e8 100644 --- a/apps/webhook_listeners/lib/BackgroundJobs/WebhookCall.php +++ b/apps/webhook_listeners/lib/BackgroundJobs/WebhookCall.php @@ -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]); } } } diff --git a/apps/webhook_listeners/lib/Db/WebhookListener.php b/apps/webhook_listeners/lib/Db/WebhookListener.php index a59549d0c4a..8053fc318dc 100644 --- a/apps/webhook_listeners/lib/Db/WebhookListener.php +++ b/apps/webhook_listeners/lib/Db/WebhookListener.php @@ -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; + } } diff --git a/build/stubs/app_api.php b/build/stubs/app_api.php index 1ab63499b77..04a6042dffd 100644 --- a/build/stubs/app_api.php +++ b/build/stubs/app_api.php @@ -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; + } + } } |