mirror of
https://github.com/nextcloud/server.git
synced 2026-06-08 16:26:59 -04:00
feat: Add support for headers and authentication headers in webhooks
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
This commit is contained in:
parent
261f08e631
commit
85e0407aad
10 changed files with 93 additions and 18 deletions
|
|
@ -11,6 +11,7 @@ return array(
|
|||
'OCA\\Webhooks\\BackgroundJobs\\WebhookCall' => $baseDir . '/../lib/BackgroundJobs/WebhookCall.php',
|
||||
'OCA\\Webhooks\\Command\\Index' => $baseDir . '/../lib/Command/Index.php',
|
||||
'OCA\\Webhooks\\Controller\\WebhooksController' => $baseDir . '/../lib/Controller/WebhooksController.php',
|
||||
'OCA\\Webhooks\\Db\\AuthMethod' => $baseDir . '/../lib/Db/AuthMethod.php',
|
||||
'OCA\\Webhooks\\Db\\WebhookListener' => $baseDir . '/../lib/Db/WebhookListener.php',
|
||||
'OCA\\Webhooks\\Db\\WebhookListenerMapper' => $baseDir . '/../lib/Db/WebhookListenerMapper.php',
|
||||
'OCA\\Webhooks\\Listener\\WebhooksEventListener' => $baseDir . '/../lib/Listener/WebhooksEventListener.php',
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class ComposerStaticInitWebhooks
|
|||
'OCA\\Webhooks\\BackgroundJobs\\WebhookCall' => __DIR__ . '/..' . '/../lib/BackgroundJobs/WebhookCall.php',
|
||||
'OCA\\Webhooks\\Command\\Index' => __DIR__ . '/..' . '/../lib/Command/Index.php',
|
||||
'OCA\\Webhooks\\Controller\\WebhooksController' => __DIR__ . '/..' . '/../lib/Controller/WebhooksController.php',
|
||||
'OCA\\Webhooks\\Db\\AuthMethod' => __DIR__ . '/..' . '/../lib/Db/AuthMethod.php',
|
||||
'OCA\\Webhooks\\Db\\WebhookListener' => __DIR__ . '/..' . '/../lib/Db/WebhookListener.php',
|
||||
'OCA\\Webhooks\\Db\\WebhookListenerMapper' => __DIR__ . '/..' . '/../lib/Db/WebhookListenerMapper.php',
|
||||
'OCA\\Webhooks\\Listener\\WebhooksEventListener' => __DIR__ . '/..' . '/../lib/Listener/WebhooksEventListener.php',
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Webhooks\BackgroundJobs;
|
||||
|
||||
use OCA\Webhooks\Db\AuthMethod;
|
||||
use OCA\Webhooks\Db\WebhookListenerMapper;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\QueuedJob;
|
||||
|
|
@ -31,7 +32,16 @@ class WebhookCall extends QueuedJob {
|
|||
$client = $this->clientService->newClient();
|
||||
$options = [];
|
||||
$options['body'] = json_encode($data);
|
||||
$options['headers'] = $webhookListener->getHeaders();
|
||||
try {
|
||||
switch ($webhookListener->getAuthMethodEnum()) {
|
||||
case AuthMethod::None:
|
||||
break;
|
||||
case AuthMethod::Header:
|
||||
$authHeaders = $webhookListener->getAuthDataClear();
|
||||
$options['headers'] = array_merge($options['headers'], $authHeaders);
|
||||
break;
|
||||
}
|
||||
$response = $client->request($webhookListener->getHttpMethod(), $webhookListener->getUri(), $options);
|
||||
$statusCode = $response->getStatusCode();
|
||||
if ($statusCode >= 200 && $statusCode < 300) {
|
||||
|
|
|
|||
|
|
@ -31,11 +31,10 @@ class Index extends Base {
|
|||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
$webhookListeners = array_map(
|
||||
function (WebhookListener $listener): array {
|
||||
$data = $listener->jsonSerialize();
|
||||
$data['eventFilter'] = json_encode($data['eventFilter']);
|
||||
return $data;
|
||||
},
|
||||
fn (WebhookListener $listener): array => array_map(
|
||||
fn (string|array|null $value): ?string => (is_array($value) ? json_encode($value) : $value),
|
||||
$listener->jsonSerialize()
|
||||
),
|
||||
$this->mapper->getAll()
|
||||
);
|
||||
$this->writeTableInOutputFormat($input, $output, $webhookListeners);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ declare(strict_types=1);
|
|||
namespace OCA\Webhooks\Controller;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use OCA\Webhooks\Db\AuthMethod;
|
||||
use OCA\Webhooks\Db\WebhookListenerMapper;
|
||||
use OCP\AppFramework\Http\Attribute\ApiRoute;
|
||||
use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
|
||||
|
|
@ -112,7 +113,7 @@ class WebhooksController extends OCSController {
|
|||
$event,
|
||||
$eventFilter,
|
||||
$headers,
|
||||
$authMethod,
|
||||
AuthMethod::from($authMethod ?? AuthMethod::None->value),
|
||||
$authData,
|
||||
);
|
||||
return new DataResponse($webhookListener);
|
||||
|
|
@ -172,7 +173,7 @@ class WebhooksController extends OCSController {
|
|||
$event,
|
||||
$eventFilter,
|
||||
$headers,
|
||||
$authMethod,
|
||||
AuthMethod::from($authMethod ?? AuthMethod::None->value),
|
||||
$authData,
|
||||
);
|
||||
return new DataResponse($webhookListener);
|
||||
|
|
|
|||
15
apps/webhooks/lib/Db/AuthMethod.php
Normal file
15
apps/webhooks/lib/Db/AuthMethod.php
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Webhooks\Db;
|
||||
|
||||
enum AuthMethod: string {
|
||||
case None = 'none';
|
||||
case Header = 'header';
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ declare(strict_types=1);
|
|||
namespace OCA\Webhooks\Db;
|
||||
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\Security\ICrypto;
|
||||
|
||||
/**
|
||||
* @method void setUserId(string $userId)
|
||||
|
|
@ -34,7 +35,7 @@ class WebhookListener extends Entity implements \JsonSerializable {
|
|||
/** @var array */
|
||||
protected $eventFilter;
|
||||
|
||||
/** @var ?string */
|
||||
/** @var ?array */
|
||||
protected $headers;
|
||||
|
||||
/** @var ?string */
|
||||
|
|
@ -43,7 +44,15 @@ class WebhookListener extends Entity implements \JsonSerializable {
|
|||
/** @var ?string */
|
||||
protected $authData;
|
||||
|
||||
public function __construct() {
|
||||
private ICrypto $crypto;
|
||||
|
||||
public function __construct(
|
||||
?ICrypto $crypto = null,
|
||||
) {
|
||||
if ($crypto === null) {
|
||||
$crypto = \OCP\Server::get(ICrypto::class);
|
||||
}
|
||||
$this->crypto = $crypto;
|
||||
$this->addType('appId', 'string');
|
||||
$this->addType('userId', 'string');
|
||||
$this->addType('httpMethod', 'string');
|
||||
|
|
@ -52,7 +61,26 @@ class WebhookListener extends Entity implements \JsonSerializable {
|
|||
$this->addType('eventFilter', 'json');
|
||||
$this->addType('headers', 'json');
|
||||
$this->addType('authMethod', 'string');
|
||||
$this->addType('authData', 'json');
|
||||
$this->addType('authData', 'string');
|
||||
}
|
||||
|
||||
public function getAuthMethodEnum(): AuthMethod {
|
||||
return AuthMethod::from(parent::getAuthMethod());
|
||||
}
|
||||
|
||||
public function getAuthDataClear(): array {
|
||||
if ($this->authData === null) {
|
||||
return [];
|
||||
}
|
||||
return json_decode($this->crypto->decrypt($this->getAuthData()), associative:true, flags:JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
public function setAuthDataClear(?array $data): void {
|
||||
if ($data === null) {
|
||||
$this->setAuthData(null);
|
||||
return;
|
||||
}
|
||||
$this->setAuthData($this->crypto->encrypt(json_encode($data)));
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ class WebhookListenerMapper extends QBMapper {
|
|||
string $event,
|
||||
?array $eventFilter,
|
||||
?array $headers,
|
||||
?string $authMethod,
|
||||
AuthMethod $authMethod,
|
||||
?array $authData,
|
||||
) {
|
||||
$webhookListener = WebhookListener::fromParams(
|
||||
|
|
@ -76,10 +76,10 @@ class WebhookListenerMapper extends QBMapper {
|
|||
'event' => $event,
|
||||
'eventFilter' => $eventFilter ?? [],
|
||||
'headers' => $headers,
|
||||
'authMethod' => $authMethod ?? 'none',
|
||||
'authData' => $authData,
|
||||
'authMethod' => $authMethod->value,
|
||||
]
|
||||
);
|
||||
$webhookListener->setAuthDataClear($authData);
|
||||
return $this->insert($webhookListener);
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ class WebhookListenerMapper extends QBMapper {
|
|||
string $event,
|
||||
?array $eventFilter,
|
||||
?array $headers,
|
||||
?string $authMethod,
|
||||
AuthMethod $authMethod,
|
||||
?array $authData,
|
||||
) {
|
||||
$webhookListener = WebhookListener::fromParams(
|
||||
|
|
@ -105,10 +105,10 @@ class WebhookListenerMapper extends QBMapper {
|
|||
'event' => $event,
|
||||
'eventFilter' => $eventFilter ?? [],
|
||||
'headers' => $headers,
|
||||
'authMethod' => $authMethod,
|
||||
'authData' => $authData,
|
||||
'authMethod' => $authMethod->value,
|
||||
]
|
||||
);
|
||||
$webhookListener->setAuthDataClear($authData);
|
||||
return $this->update($webhookListener);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Webhooks\Tests\Db;
|
||||
|
||||
use OCA\Webhooks\Db\AuthMethod;
|
||||
use OCA\Webhooks\Db\WebhookListenerMapper;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\User\Events\UserCreatedEvent;
|
||||
|
|
@ -51,7 +52,7 @@ class WebhookListenerMapperTest extends TestCase {
|
|||
UserCreatedEvent::class,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
AuthMethod::None,
|
||||
null,
|
||||
);
|
||||
|
||||
|
|
@ -60,4 +61,23 @@ class WebhookListenerMapperTest extends TestCase {
|
|||
$listener1->resetUpdatedFields();
|
||||
$this->assertEquals($listener1, $listener2);
|
||||
}
|
||||
|
||||
public function testInsertListenerAndGetItWithAuthData() {
|
||||
$listener1 = $this->mapper->addWebhookListener(
|
||||
null,
|
||||
'bob',
|
||||
'POST',
|
||||
'https://webhook.example.com/endpoint',
|
||||
UserCreatedEvent::class,
|
||||
null,
|
||||
null,
|
||||
AuthMethod::Header,
|
||||
['secretHeader' => 'header'],
|
||||
);
|
||||
|
||||
$listener2 = $this->mapper->getById($listener1->getId());
|
||||
|
||||
$listener1->resetUpdatedFields();
|
||||
$this->assertEquals($listener1, $listener2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ declare(strict_types=1);
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Webhooks\Tests\Db;
|
||||
namespace OCA\Webhooks\Tests\Service;
|
||||
|
||||
use OCA\Webhooks\Service\PHPMongoQuery;
|
||||
use OCP\Files\Events\Node\NodeWrittenEvent;
|
||||
|
|
|
|||
Loading…
Reference in a new issue