BookStackApp / BookStack

A platform to create documentation/wiki content built with PHP & Laravel
https://www.bookstackapp.com/
MIT License
15.34k stars 1.91k forks source link

Scoped Webhooks #5025

Open DanielGordonIT opened 5 months ago

DanielGordonIT commented 5 months ago

Describe the feature you'd like

The ability to bind a webhook in scope to either a single book or shelf (and all child books), or multiple books or shelves (and all child books), instead of being global.

This is really only applicable to certain classes of actions, like _create, _update, _move, _delete, the comment events. The event types checked below could feasibly be scoped, the unchecked ones are global by default pretty much. image

Describe the benefits this would bring to existing BookStack users

This allows more granularity and for better control. In my use case, I don't ever need a webhook to tell me if ANY page has been edited, but I do have needs to know if pages in specific books are edited. Different books are scoped to different teams, but creating a webhook to update specific teams about which pages in their books are updated is difficult, as webhooks see everything. The current webhook implementation is good if everything is global, and there is only one main team (which is supposed to see everything).

Can the goal of this request already be achieved via other means?

Technically, yes. I posted a solution in #5009 that allows you to lock a specific webhook to only fire correctly (it'll still try to send the webhook, but with malformed output and will fail safely. Probably, anyway.) given a specific book ID and webhook name combo.

<?php

use BookStack\Activity\Models\Loggable;
use BookStack\Activity\Models\Webhook;
use BookStack\Activity\Tools\WebhookFormatter;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use BookStack\Users\Models\User;

function selectiveFormat(array $defaultWebhookData): array
{
    $book_id = $defaultWebhookData['related_item']['book_id'];
    $webhook_name = $defaultWebhookData['webhook_name']
    if ( !(
        ($book_id == 1 && $webhook_name == "Team 1 Webhook") ||
        ($book_id == 2 && $webhook_name == "Team 2 Webhook") ||
        ($book_id == 3 && $webhook_name == "Team 3 Webhook") ||
        ($book_id == 4 && $webhook_name == "Team 4 Webhook")
    )
    ) {
        // If it's a teams webhook, but the name and book ID don't match, then just return nothing and cause a 400 error.
        return [];
    }
    return null;

}

Theme::listen(ThemeEvents::WEBHOOK_CALL_BEFORE, function (
    string $event,
    Webhook $webhook,
    string|Loggable $detail,
    User $initiator,
    int $initTime,
) {
    if (str_starts_with($webhook->endpoint, 'https://teams.webhook.whatever')) {
        $defaultData = WebhookFormatter::getDefault($event, $webhook, $detail, $initiator, $initTime);
        return selectiveFormat($defaultData->format());
    }
    return null;
});

This is adapted from https://www.bookstackapp.com/hacks/pushover-webhooks/.

Have you searched for an existing open/closed issue?

How long have you been using BookStack?

3 months to 1 year

Additional context

No response

PascalLeroi commented 3 months ago

This would be pretty much perfect for specific updates in channels that only are interested in certain topics :)

Lumrenion commented 2 months ago

Users can watch books, chapters and pages, which is implemented really well. So I as a user can be notified via email when a page inside a specific book was updated. A similar behaviour for webhooks would be very powerful, so I as an admin can send a notification to e.g. a specific Zulip/Slack/Discord channel when Book A was updated, and to a different channel when Book B was updated.