Skip to content

Commit

Permalink
feat: Add support for headers and authentication headers in webhooks
Browse files Browse the repository at this point in the history
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
  • Loading branch information
come-nc committed Jun 3, 2024
1 parent 35fbdfa commit 7898ea4
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 17 deletions.
1 change: 1 addition & 0 deletions apps/webhooks/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
'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',
Expand Down
1 change: 1 addition & 0 deletions apps/webhooks/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
10 changes: 10 additions & 0 deletions apps/webhooks/lib/BackgroundJobs/WebhookCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace OCA\Webhooks\BackgroundJobs;

use OCA\Webhooks\Db\AuthMethod;
use OCA\Webhooks\Db\WebhookListenerMapper;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\QueuedJob;
Expand All @@ -31,7 +32,16 @@ protected function run($argument): void {
$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) {
Expand Down
9 changes: 4 additions & 5 deletions apps/webhooks/lib/Command/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@ protected function configure(): void {

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);
Expand Down
5 changes: 3 additions & 2 deletions apps/webhooks/lib/Controller/WebhooksController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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;
Expand Down Expand Up @@ -112,7 +113,7 @@ public function create(
$event,
$eventFilter,
$headers,
$authMethod,
AuthMethod::from($authMethod ?? AuthMethod::None->value),
$authData,
);
return new DataResponse($webhookListener);
Expand Down Expand Up @@ -172,7 +173,7 @@ public function update(
$event,
$eventFilter,
$headers,
$authMethod,
AuthMethod::from($authMethod ?? AuthMethod::None->value),
$authData,
);
return new DataResponse($webhookListener);
Expand Down
15 changes: 15 additions & 0 deletions apps/webhooks/lib/Db/AuthMethod.php
Original file line number Diff line number Diff line change
@@ -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';
}
34 changes: 31 additions & 3 deletions apps/webhooks/lib/Db/WebhookListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace OCA\Webhooks\Db;

use OCP\AppFramework\Db\Entity;
use OCP\Security\ICrypto;

/**
* @method void setUserId(string $userId)
Expand All @@ -34,7 +35,7 @@ class WebhookListener extends Entity implements \JsonSerializable {
/** @var array */
protected $eventFilter;

/** @var ?string */
/** @var ?array */
protected $headers;

/** @var ?string */
Expand All @@ -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');
Expand All @@ -52,7 +61,26 @@ public function __construct() {
$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 {
Expand Down
12 changes: 6 additions & 6 deletions apps/webhooks/lib/Db/WebhookListenerMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function addWebhookListener(
string $event,
?array $eventFilter,
?array $headers,
?string $authMethod,
AuthMethod $authMethod,
?array $authData,
) {
$webhookListener = WebhookListener::fromParams(
Expand All @@ -76,10 +76,10 @@ public function addWebhookListener(
'event' => $event,
'eventFilter' => $eventFilter ?? [],
'headers' => $headers,
'authMethod' => $authMethod ?? 'none',
'authData' => $authData,
'authMethod' => $authMethod->value,
]
);
$webhookListener->setAuthDataClear($authData);
return $this->insert($webhookListener);
}

Expand All @@ -92,7 +92,7 @@ public function updateWebhookListener(
string $event,
?array $eventFilter,
?array $headers,
?string $authMethod,
AuthMethod $authMethod,
?array $authData,
) {
$webhookListener = WebhookListener::fromParams(
Expand All @@ -105,10 +105,10 @@ public function updateWebhookListener(
'event' => $event,
'eventFilter' => $eventFilter ?? [],
'headers' => $headers,
'authMethod' => $authMethod,
'authData' => $authData,
'authMethod' => $authMethod->value,
]
);
$webhookListener->setAuthDataClear($authData);
return $this->update($webhookListener);
}

Expand Down
20 changes: 20 additions & 0 deletions apps/webhooks/tests/Db/WebhookListenerMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace OCA\Webhooks\Tests\Db;

use OCA\Webhooks\Db\AuthMethod;
use OCA\Webhooks\Db\WebhookListenerMapper;
use OCP\IDBConnection;
use OCP\User\Events\UserCreatedEvent;
Expand Down Expand Up @@ -51,8 +52,27 @@ public function testInsertListenerAndGetIt() {
UserCreatedEvent::class,
null,
null,
AuthMethod::None,
null,
);

$listener2 = $this->mapper->getById($listener1->getId());

$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());
Expand Down
2 changes: 1 addition & 1 deletion apps/webhooks/tests/Service/PHPMongoQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* 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;
Expand Down

0 comments on commit 7898ea4

Please sign in to comment.