Skip to content

Commit

Permalink
Merge pull request #46477 from nextcloud/support-direct-appapi-requests
Browse files Browse the repository at this point in the history
feat: webhooks_listeners app: send direct requests to ExApps using AppAPI
  • Loading branch information
bigcat88 authored Jul 16, 2024
2 parents bd9ab63 + 54c700b commit 1f8999e
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 15 deletions.
35 changes: 32 additions & 3 deletions apps/webhook_listeners/lib/BackgroundJobs/WebhookCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,23 @@

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 IClientService $clientService,
private ICertificateManager $certificateManager,
private WebhookListenerMapper $mapper,
private LoggerInterface $logger,
private IAppManager $appManager,
ITimeFactory $timeFactory,
) {
parent::__construct($timeFactory);
Expand All @@ -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]);
}
}
}
5 changes: 5 additions & 0 deletions apps/webhook_listeners/lib/Db/WebhookListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

/**
* @method void setUserId(string $userId)
* @method ?string getAppId()
* @method string getUserId()
* @method string getHttpMethod()
* @method string getUri()
Expand Down Expand Up @@ -139,4 +140,8 @@ public function jsonSerialize(): array {
)
);
}

public function getAppId(): ?string {
return $this->appId;
}
}
101 changes: 89 additions & 12 deletions build/stubs/app_api.php
Original file line number Diff line number Diff line change
@@ -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;
}
}
}

0 comments on commit 1f8999e

Please sign in to comment.