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(
private ICertificateManager $certificateManager,
private WebhookListenerMapper $mapper,
private LoggerInterface $logger,
+ private IAppManager $appManager,
ITimeFactory $timeFactory,
) {
parent::__construct($timeFactory);
$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]);
}
}
}
<?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;
+ }
+ }
}