mirror of
https://github.com/nextcloud/server.git
synced 2024-09-19 11:07:15 +00:00
4459af41c9
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
303 lines
10 KiB
PHP
303 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
namespace OCA\WebhookListeners\Controller;
|
|
|
|
use OCA\WebhookListeners\Db\AuthMethod;
|
|
use OCA\WebhookListeners\Db\WebhookListener;
|
|
use OCA\WebhookListeners\Db\WebhookListenerMapper;
|
|
use OCA\WebhookListeners\ResponseDefinitions;
|
|
use OCA\WebhookListeners\Settings\Admin;
|
|
use OCP\AppFramework\Db\DoesNotExistException;
|
|
use OCP\AppFramework\Http;
|
|
use OCP\AppFramework\Http\Attribute\ApiRoute;
|
|
use OCP\AppFramework\Http\Attribute\AppApiAdminAccessWithoutUser;
|
|
use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
|
|
use OCP\AppFramework\Http\Attribute\OpenAPI;
|
|
use OCP\AppFramework\Http\DataResponse;
|
|
use OCP\AppFramework\OCS\OCSBadRequestException;
|
|
use OCP\AppFramework\OCS\OCSException;
|
|
use OCP\AppFramework\OCS\OCSForbiddenException;
|
|
use OCP\AppFramework\OCS\OCSNotFoundException;
|
|
use OCP\AppFramework\OCSController;
|
|
use OCP\IRequest;
|
|
use OCP\ISession;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
/**
|
|
* @psalm-import-type WebhookListenersWebhookInfo from ResponseDefinitions
|
|
*/
|
|
#[OpenAPI(scope: OpenAPI::SCOPE_ADMINISTRATION)]
|
|
class WebhooksController extends OCSController {
|
|
public function __construct(
|
|
string $appName,
|
|
IRequest $request,
|
|
private LoggerInterface $logger,
|
|
private WebhookListenerMapper $mapper,
|
|
private ?string $userId,
|
|
private ISession $session,
|
|
) {
|
|
parent::__construct($appName, $request);
|
|
}
|
|
|
|
/**
|
|
* List registered webhooks
|
|
*
|
|
* @param string|null $uri The callback URI to filter by
|
|
* @return DataResponse<Http::STATUS_OK, WebhookListenersWebhookInfo[], array{}>
|
|
* @throws OCSException Other internal error
|
|
*
|
|
* 200: Webhook registrations returned
|
|
*/
|
|
#[ApiRoute(verb: 'GET', url: '/api/v1/webhooks')]
|
|
#[AuthorizedAdminSetting(settings:Admin::class)]
|
|
#[AppApiAdminAccessWithoutUser]
|
|
public function index(?string $uri = null): DataResponse {
|
|
try {
|
|
if ($uri !== null) {
|
|
$webhookListeners = $this->mapper->getByUri($uri);
|
|
} else {
|
|
$webhookListeners = $this->mapper->getAll();
|
|
}
|
|
|
|
return new DataResponse(
|
|
array_map(
|
|
fn (WebhookListener $listener): array => $listener->jsonSerialize(),
|
|
$webhookListeners
|
|
)
|
|
);
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Error when listing webhooks', ['exception' => $e]);
|
|
throw new OCSException('An internal error occurred', Http::STATUS_INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get details on a registered webhook
|
|
*
|
|
* @param int $id id of the webhook
|
|
*
|
|
* @return DataResponse<Http::STATUS_OK, WebhookListenersWebhookInfo, array{}>
|
|
* @throws OCSNotFoundException Webhook not found
|
|
* @throws OCSException Other internal error
|
|
*
|
|
* 200: Webhook registration returned
|
|
*/
|
|
#[ApiRoute(verb: 'GET', url: '/api/v1/webhooks/{id}')]
|
|
#[AuthorizedAdminSetting(settings:Admin::class)]
|
|
#[AppApiAdminAccessWithoutUser]
|
|
public function show(int $id): DataResponse {
|
|
try {
|
|
return new DataResponse($this->mapper->getById($id)->jsonSerialize());
|
|
} catch (DoesNotExistException $e) {
|
|
throw new OCSNotFoundException($e->getMessage(), $e);
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Error when getting webhook', ['exception' => $e]);
|
|
throw new OCSException('An internal error occurred', Http::STATUS_INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register a new webhook
|
|
*
|
|
* @param string $httpMethod HTTP method to use to contact the webhook
|
|
* @param string $uri Webhook URI endpoint
|
|
* @param string $event Event class name to listen to
|
|
* @param ?array<string,mixed> $eventFilter Mongo filter to apply to the serialized data to decide if firing
|
|
* @param ?string $userIdFilter User id to filter on. The webhook will only be called by requests from this user. Empty or null means no filtering.
|
|
* @param ?array<string,string> $headers Array of headers to send
|
|
* @param "none"|"headers"|null $authMethod Authentication method to use
|
|
* @param ?array<string,mixed> $authData Array of data for authentication
|
|
*
|
|
* @return DataResponse<Http::STATUS_OK, WebhookListenersWebhookInfo, array{}>
|
|
*
|
|
* 200: Webhook registration returned
|
|
*
|
|
* @throws OCSBadRequestException Bad request
|
|
* @throws OCSForbiddenException Insufficient permissions
|
|
* @throws OCSException Other error
|
|
*/
|
|
#[ApiRoute(verb: 'POST', url: '/api/v1/webhooks')]
|
|
#[AuthorizedAdminSetting(settings:Admin::class)]
|
|
#[AppApiAdminAccessWithoutUser]
|
|
public function create(
|
|
string $httpMethod,
|
|
string $uri,
|
|
string $event,
|
|
?array $eventFilter,
|
|
?string $userIdFilter,
|
|
?array $headers,
|
|
?string $authMethod,
|
|
#[\SensitiveParameter]
|
|
?array $authData,
|
|
): DataResponse {
|
|
$appId = null;
|
|
if ($this->session->get('app_api') === true) {
|
|
$appId = $this->request->getHeader('EX-APP-ID');
|
|
}
|
|
try {
|
|
$authMethod = AuthMethod::from($authMethod ?? AuthMethod::None->value);
|
|
} catch (\ValueError $e) {
|
|
throw new OCSBadRequestException('This auth method does not exist');
|
|
}
|
|
try {
|
|
$webhookListener = $this->mapper->addWebhookListener(
|
|
$appId,
|
|
$this->userId,
|
|
$httpMethod,
|
|
$uri,
|
|
$event,
|
|
$eventFilter,
|
|
$userIdFilter,
|
|
$headers,
|
|
$authMethod,
|
|
$authData,
|
|
);
|
|
return new DataResponse($webhookListener->jsonSerialize());
|
|
} catch (\UnexpectedValueException $e) {
|
|
throw new OCSBadRequestException($e->getMessage(), $e);
|
|
} catch (\DomainException $e) {
|
|
throw new OCSForbiddenException($e->getMessage(), $e);
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Error when inserting webhook', ['exception' => $e]);
|
|
throw new OCSException('An internal error occurred', Http::STATUS_INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update an existing webhook registration
|
|
*
|
|
* @param int $id id of the webhook
|
|
* @param string $httpMethod HTTP method to use to contact the webhook
|
|
* @param string $uri Webhook URI endpoint
|
|
* @param string $event Event class name to listen to
|
|
* @param ?array<string,mixed> $eventFilter Mongo filter to apply to the serialized data to decide if firing
|
|
* @param ?string $userIdFilter User id to filter on. The webhook will only be called by requests from this user. Empty or null means no filtering.
|
|
* @param ?array<string,string> $headers Array of headers to send
|
|
* @param "none"|"headers"|null $authMethod Authentication method to use
|
|
* @param ?array<string,mixed> $authData Array of data for authentication
|
|
*
|
|
* @return DataResponse<Http::STATUS_OK, WebhookListenersWebhookInfo, array{}>
|
|
*
|
|
* 200: Webhook registration returned
|
|
*
|
|
* @throws OCSBadRequestException Bad request
|
|
* @throws OCSForbiddenException Insufficient permissions
|
|
* @throws OCSException Other error
|
|
*/
|
|
#[ApiRoute(verb: 'POST', url: '/api/v1/webhooks/{id}')]
|
|
#[AuthorizedAdminSetting(settings:Admin::class)]
|
|
#[AppApiAdminAccessWithoutUser]
|
|
public function update(
|
|
int $id,
|
|
string $httpMethod,
|
|
string $uri,
|
|
string $event,
|
|
?array $eventFilter,
|
|
?string $userIdFilter,
|
|
?array $headers,
|
|
?string $authMethod,
|
|
#[\SensitiveParameter]
|
|
?array $authData,
|
|
): DataResponse {
|
|
$appId = null;
|
|
if ($this->session->get('app_api') === true) {
|
|
$appId = $this->request->getHeader('EX-APP-ID');
|
|
}
|
|
try {
|
|
$authMethod = AuthMethod::from($authMethod ?? AuthMethod::None->value);
|
|
} catch (\ValueError $e) {
|
|
throw new OCSBadRequestException('This auth method does not exist');
|
|
}
|
|
try {
|
|
$webhookListener = $this->mapper->updateWebhookListener(
|
|
$id,
|
|
$appId,
|
|
$this->userId,
|
|
$httpMethod,
|
|
$uri,
|
|
$event,
|
|
$eventFilter,
|
|
$userIdFilter,
|
|
$headers,
|
|
$authMethod,
|
|
$authData,
|
|
);
|
|
return new DataResponse($webhookListener->jsonSerialize());
|
|
} catch (\UnexpectedValueException $e) {
|
|
throw new OCSBadRequestException($e->getMessage(), $e);
|
|
} catch (\DomainException $e) {
|
|
throw new OCSForbiddenException($e->getMessage(), $e);
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Error when updating flow with id ' . $id, ['exception' => $e]);
|
|
throw new OCSException('An internal error occurred', Http::STATUS_INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove an existing webhook registration
|
|
*
|
|
* @param int $id id of the webhook
|
|
*
|
|
* @return DataResponse<Http::STATUS_OK, bool, array{}>
|
|
*
|
|
* 200: Boolean returned whether something was deleted
|
|
*
|
|
* @throws OCSBadRequestException Bad request
|
|
* @throws OCSForbiddenException Insufficient permissions
|
|
* @throws OCSException Other error
|
|
*/
|
|
#[ApiRoute(verb: 'DELETE', url: '/api/v1/webhooks/{id}')]
|
|
#[AuthorizedAdminSetting(settings:Admin::class)]
|
|
#[AppApiAdminAccessWithoutUser]
|
|
public function destroy(int $id): DataResponse {
|
|
try {
|
|
$deleted = $this->mapper->deleteById($id);
|
|
return new DataResponse($deleted);
|
|
} catch (\UnexpectedValueException $e) {
|
|
throw new OCSBadRequestException($e->getMessage(), $e);
|
|
} catch (\DomainException $e) {
|
|
throw new OCSForbiddenException($e->getMessage(), $e);
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Error when deleting flow with id ' . $id, ['exception' => $e]);
|
|
throw new OCSException('An internal error occurred', Http::STATUS_INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove all existing webhook registration mapped to an AppAPI app id
|
|
*
|
|
* @param string $appid id of the app, as in the EX-APP-ID for creation
|
|
*
|
|
* @return DataResponse<Http::STATUS_OK, int, array{}>
|
|
*
|
|
* 200: Integer number of registrations deleted
|
|
*
|
|
* @throws OCSBadRequestException Bad request
|
|
* @throws OCSForbiddenException Insufficient permissions
|
|
* @throws OCSException Other error
|
|
*/
|
|
#[ApiRoute(verb: 'DELETE', url: '/api/v1/webhooks/byappid/{appid}')]
|
|
#[AuthorizedAdminSetting(settings:Admin::class)]
|
|
#[AppApiAdminAccessWithoutUser]
|
|
public function deleteByAppId(string $appid): DataResponse {
|
|
try {
|
|
$deletedCount = $this->mapper->deleteByAppId($appid);
|
|
return new DataResponse($deletedCount);
|
|
} catch (\UnexpectedValueException $e) {
|
|
throw new OCSBadRequestException($e->getMessage(), $e);
|
|
} catch (\DomainException $e) {
|
|
throw new OCSForbiddenException($e->getMessage(), $e);
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Error when deleting flows for app id ' . $appid, ['exception' => $e]);
|
|
throw new OCSException('An internal error occurred', Http::STATUS_INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
}
|
|
}
|