mirror of
https://github.com/nextcloud/server.git
synced 2026-06-11 09:42:09 -04:00
feat(webhooks): Add support for a userid filter
This allows to register a userId to filter on along with the webhooks. This webhook will then only be triggered if the given userId is the one in session. This is more efficient than filtering by user in the event filter because the listener is not even registered if the user id does not match. Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
This commit is contained in:
parent
164f4a3ea3
commit
44d707c97b
7 changed files with 42 additions and 10 deletions
|
|
@ -16,6 +16,7 @@ use OCP\AppFramework\Bootstrap\IBootContext;
|
|||
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IUserSession;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
|
|
@ -40,9 +41,10 @@ class Application extends App implements IBootstrap {
|
|||
): void {
|
||||
/** @var WebhookListenerMapper */
|
||||
$mapper = $container->get(WebhookListenerMapper::class);
|
||||
$userSession = $container->get(IUserSession::class);
|
||||
|
||||
/* Listen to all events with at least one webhook configured */
|
||||
$configuredEvents = $mapper->getAllConfiguredEvents();
|
||||
$configuredEvents = $mapper->getAllConfiguredEvents($userSession->getUser()?->getUID());
|
||||
foreach ($configuredEvents as $eventName) {
|
||||
$logger->debug("Listening to {$eventName}");
|
||||
$dispatcher->addServiceListener(
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ class WebhooksController extends OCSController {
|
|||
string $uri,
|
||||
string $event,
|
||||
?array $eventFilter,
|
||||
?string $userIdFilter,
|
||||
?array $headers,
|
||||
?string $authMethod,
|
||||
#[\SensitiveParameter]
|
||||
|
|
@ -150,6 +151,7 @@ class WebhooksController extends OCSController {
|
|||
$uri,
|
||||
$event,
|
||||
$eventFilter,
|
||||
$userIdFilter,
|
||||
$headers,
|
||||
$authMethod,
|
||||
$authData,
|
||||
|
|
@ -193,6 +195,7 @@ class WebhooksController extends OCSController {
|
|||
string $uri,
|
||||
string $event,
|
||||
?array $eventFilter,
|
||||
?string $userIdFilter,
|
||||
?array $headers,
|
||||
?string $authMethod,
|
||||
#[\SensitiveParameter]
|
||||
|
|
@ -218,6 +221,7 @@ class WebhooksController extends OCSController {
|
|||
$uri,
|
||||
$event,
|
||||
$eventFilter,
|
||||
$userIdFilter,
|
||||
$headers,
|
||||
$authMethod,
|
||||
$authData,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,12 @@ class WebhookListener extends Entity implements \JsonSerializable {
|
|||
*/
|
||||
protected $eventFilter;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* If not empty, id of the user that needs to be connected for the webhook to trigger
|
||||
*/
|
||||
protected $userIdFilter;
|
||||
|
||||
/**
|
||||
* @var ?array
|
||||
*/
|
||||
|
|
@ -90,6 +96,7 @@ class WebhookListener extends Entity implements \JsonSerializable {
|
|||
$this->addType('uri', 'string');
|
||||
$this->addType('event', 'string');
|
||||
$this->addType('eventFilter', 'json');
|
||||
$this->addType('userIdFilter', 'string');
|
||||
$this->addType('headers', 'json');
|
||||
$this->addType('authMethod', 'string');
|
||||
$this->addType('authData', 'string');
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ use OCP\IDBConnection;
|
|||
class WebhookListenerMapper extends QBMapper {
|
||||
public const TABLE_NAME = 'webhook_listeners';
|
||||
|
||||
private const EVENTS_CACHE_KEY = 'eventsUsedInWebhooks';
|
||||
private const EVENTS_CACHE_KEY_PREFIX = 'eventsUsedInWebhooks';
|
||||
|
||||
private ?ICache $cache = null;
|
||||
|
||||
|
|
@ -77,6 +77,7 @@ class WebhookListenerMapper extends QBMapper {
|
|||
string $uri,
|
||||
string $event,
|
||||
?array $eventFilter,
|
||||
?string $userIdFilter,
|
||||
?array $headers,
|
||||
AuthMethod $authMethod,
|
||||
#[\SensitiveParameter]
|
||||
|
|
@ -95,12 +96,13 @@ class WebhookListenerMapper extends QBMapper {
|
|||
'uri' => $uri,
|
||||
'event' => $event,
|
||||
'eventFilter' => $eventFilter ?? [],
|
||||
'userIdFilter' => $userIdFilter ?? '',
|
||||
'headers' => $headers,
|
||||
'authMethod' => $authMethod->value,
|
||||
]
|
||||
);
|
||||
$webhookListener->setAuthDataClear($authData);
|
||||
$this->cache?->remove(self::EVENTS_CACHE_KEY);
|
||||
$this->cache?->remove($this->buildCacheKey($userIdFilter));
|
||||
return $this->insert($webhookListener);
|
||||
}
|
||||
|
||||
|
|
@ -115,6 +117,7 @@ class WebhookListenerMapper extends QBMapper {
|
|||
string $uri,
|
||||
string $event,
|
||||
?array $eventFilter,
|
||||
?string $userIdFilter,
|
||||
?array $headers,
|
||||
AuthMethod $authMethod,
|
||||
#[\SensitiveParameter]
|
||||
|
|
@ -134,12 +137,13 @@ class WebhookListenerMapper extends QBMapper {
|
|||
'uri' => $uri,
|
||||
'event' => $event,
|
||||
'eventFilter' => $eventFilter ?? [],
|
||||
'userIdFilter' => $userIdFilter ?? '',
|
||||
'headers' => $headers,
|
||||
'authMethod' => $authMethod->value,
|
||||
]
|
||||
);
|
||||
$webhookListener->setAuthDataClear($authData);
|
||||
$this->cache?->remove(self::EVENTS_CACHE_KEY);
|
||||
$this->cache?->remove($this->buildCacheKey($userIdFilter));
|
||||
return $this->update($webhookListener);
|
||||
}
|
||||
|
||||
|
|
@ -159,11 +163,12 @@ class WebhookListenerMapper extends QBMapper {
|
|||
* @throws Exception
|
||||
* @return list<string>
|
||||
*/
|
||||
private function getAllConfiguredEventsFromDatabase(): array {
|
||||
private function getAllConfiguredEventsFromDatabase(string $userId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->selectDistinct('event')
|
||||
->from($this->getTableName());
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->in('user_id_filter', $qb->createNamedParameter(['',$userId], IQueryBuilder::PARAM_STR_ARRAY), IQueryBuilder::PARAM_STR));
|
||||
|
||||
$result = $qb->executeQuery();
|
||||
|
||||
|
|
@ -181,14 +186,15 @@ class WebhookListenerMapper extends QBMapper {
|
|||
* @throws Exception
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getAllConfiguredEvents(): array {
|
||||
$events = $this->cache?->get(self::EVENTS_CACHE_KEY);
|
||||
public function getAllConfiguredEvents(?string $userId = null): array {
|
||||
$cacheKey = $this->buildCacheKey($userId);
|
||||
$events = $this->cache?->get($cacheKey);
|
||||
if ($events !== null) {
|
||||
return json_decode($events);
|
||||
}
|
||||
$events = $this->getAllConfiguredEventsFromDatabase();
|
||||
$events = $this->getAllConfiguredEventsFromDatabase($userId ?? '');
|
||||
// cache for 5 minutes
|
||||
$this->cache?->set(self::EVENTS_CACHE_KEY, json_encode($events), 300);
|
||||
$this->cache?->set($cacheKey, json_encode($events), 300);
|
||||
return $events;
|
||||
}
|
||||
|
||||
|
|
@ -217,4 +223,8 @@ class WebhookListenerMapper extends QBMapper {
|
|||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
private function buildCacheKey(?string $userIdFilter = ''): string {
|
||||
return self::EVENTS_CACHE_KEY_PREFIX.'_'.($userIdFilter ?? '');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,10 @@ class Version1000Date20240527153425 extends SimpleMigrationStep {
|
|||
$table->addColumn('event_filter', Types::TEXT, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
$table->addColumn('user_id_filter', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 64,
|
||||
]);
|
||||
$table->addColumn('headers', Types::TEXT, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ namespace OCA\WebhookListeners;
|
|||
* uri: string,
|
||||
* event?: string,
|
||||
* eventFilter?: array<string,mixed>,
|
||||
* userIdFilter?: string,
|
||||
* headers?: array<string,string>,
|
||||
* authMethod: string,
|
||||
* authData?: array<string,mixed>,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ class WebhookListenerMapperTest extends TestCase {
|
|||
UserCreatedEvent::class,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
AuthMethod::None,
|
||||
null,
|
||||
);
|
||||
|
|
@ -72,6 +73,7 @@ class WebhookListenerMapperTest extends TestCase {
|
|||
NodeWrittenEvent::class,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
AuthMethod::None,
|
||||
null,
|
||||
);
|
||||
|
|
@ -92,6 +94,7 @@ class WebhookListenerMapperTest extends TestCase {
|
|||
NodeWrittenEvent::class,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
AuthMethod::None,
|
||||
null,
|
||||
);
|
||||
|
|
@ -111,6 +114,7 @@ class WebhookListenerMapperTest extends TestCase {
|
|||
NodeWrittenEvent::class,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
AuthMethod::Header,
|
||||
['secretHeader' => 'header'],
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue