mirror of
https://github.com/nextcloud/server.git
synced 2026-04-15 22:11:17 -04:00
feat: webhooks_listeners app support for sending direct requests to ExApps using AppAPI.
Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
This commit is contained in:
parent
a2ded20050
commit
696ece2f52
3 changed files with 125 additions and 14 deletions
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,92 @@
|
|||
<?php
|
||||
|
||||
namespace OCA\AppAPI\Service;
|
||||
namespace OCA\AppAPI\Service {
|
||||
use OCP\IRequest;
|
||||
|
||||
use OCP\IRequest;
|
||||
|
||||
class AppAPIService {
|
||||
/**
|
||||
* @param IRequest $request
|
||||
* @param bool $isDav
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validateExAppRequestToNC(IRequest $request, bool $isDav = false): bool {}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue