Skip to content

Commit a831501

Browse files
committed
Webhooks: Fixed failing delete-based events
Due to queue serialization. Added a test to check a couple of delete events. Added ApiTokenFactory to support. Also made a couple of typing/doc updates while there. Related to #4373
1 parent 18979e8 commit a831501

File tree

6 files changed

+64
-23
lines changed

6 files changed

+64
-23
lines changed

app/Activity/DispatchWebhookJob.php

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,23 @@ class DispatchWebhookJob implements ShouldQueue
2424
use SerializesModels;
2525

2626
protected Webhook $webhook;
27-
protected string $event;
2827
protected User $initiator;
2928
protected int $initiatedTime;
30-
31-
/**
32-
* @var string|Loggable
33-
*/
34-
protected $detail;
29+
protected array $webhookData;
3530

3631
/**
3732
* Create a new job instance.
3833
*
3934
* @return void
4035
*/
41-
public function __construct(Webhook $webhook, string $event, $detail)
36+
public function __construct(Webhook $webhook, string $event, Loggable|string $detail)
4237
{
4338
$this->webhook = $webhook;
44-
$this->event = $event;
45-
$this->detail = $detail;
4639
$this->initiator = user();
4740
$this->initiatedTime = time();
41+
42+
$themeResponse = Theme::dispatch(ThemeEvents::WEBHOOK_CALL_BEFORE, $event, $this->webhook, $detail, $this->initiator, $this->initiatedTime);
43+
$this->webhookData = $themeResponse ?? WebhookFormatter::getDefault($event, $this->webhook, $detail, $this->initiator, $this->initiatedTime)->format();
4844
}
4945

5046
/**
@@ -54,15 +50,13 @@ public function __construct(Webhook $webhook, string $event, $detail)
5450
*/
5551
public function handle()
5652
{
57-
$themeResponse = Theme::dispatch(ThemeEvents::WEBHOOK_CALL_BEFORE, $this->event, $this->webhook, $this->detail, $this->initiator, $this->initiatedTime);
58-
$webhookData = $themeResponse ?? WebhookFormatter::getDefault($this->event, $this->webhook, $this->detail, $this->initiator, $this->initiatedTime)->format();
5953
$lastError = null;
6054

6155
try {
6256
$response = Http::asJson()
6357
->withOptions(['allow_redirects' => ['strict' => true]])
6458
->timeout($this->webhook->timeout)
65-
->post($this->webhook->endpoint, $webhookData);
59+
->post($this->webhook->endpoint, $this->webhookData);
6660
} catch (\Exception $exception) {
6761
$lastError = $exception->getMessage();
6862
Log::error("Webhook call to endpoint {$this->webhook->endpoint} failed with error \"{$lastError}\"");

app/Activity/Tools/WebhookFormatter.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,14 @@ class WebhookFormatter
1717
protected string $event;
1818
protected User $initiator;
1919
protected int $initiatedTime;
20-
21-
/**
22-
* @var string|Loggable
23-
*/
24-
protected $detail;
20+
protected string|Loggable $detail;
2521

2622
/**
2723
* @var array{condition: callable(string, Model):bool, format: callable(Model):void}[]
2824
*/
2925
protected $modelFormatters = [];
3026

31-
public function __construct(string $event, Webhook $webhook, $detail, User $initiator, int $initiatedTime)
27+
public function __construct(string $event, Webhook $webhook, string|Loggable $detail, User $initiator, int $initiatedTime)
3228
{
3329
$this->webhook = $webhook;
3430
$this->event = $event;

app/Api/ApiToken.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use BookStack\Activity\Models\Loggable;
66
use BookStack\Users\Models\User;
7+
use Illuminate\Database\Eloquent\Factories\HasFactory;
78
use Illuminate\Database\Eloquent\Model;
89
use Illuminate\Database\Eloquent\Relations\BelongsTo;
910
use Illuminate\Support\Carbon;
@@ -20,6 +21,8 @@
2021
*/
2122
class ApiToken extends Model implements Loggable
2223
{
24+
use HasFactory;
25+
2326
protected $fillable = ['name', 'expires_at'];
2427
protected $casts = [
2528
'expires_at' => 'date:Y-m-d',

app/Theming/ThemeEvents.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,12 @@ class ThemeEvents
132132
* If the listener returns a non-null value, that will be used as the POST data instead
133133
* of the system default.
134134
*
135-
* @param string $event
136-
* @param \BookStack\Activity\Models\Webhook $webhook
135+
* @param string $event
136+
* @param \BookStack\Activity\Models\Webhook $webhook
137137
* @param string|\BookStack\Activity\Models\Loggable $detail
138-
* @param \BookStack\Users\Models\User $initiator
139-
* @param int $initiatedTime
138+
* @param \BookStack\Users\Models\User $initiator
139+
* @param int $initiatedTime
140+
* @returns array|null
140141
*/
141142
const WEBHOOK_CALL_BEFORE = 'webhook_call_before';
142143
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Database\Factories\Api;
4+
5+
use BookStack\Api\ApiToken;
6+
use BookStack\Users\Models\User;
7+
use Illuminate\Database\Eloquent\Factories\Factory;
8+
use Illuminate\Support\Carbon;
9+
use Illuminate\Support\Str;
10+
11+
class ApiTokenFactory extends Factory
12+
{
13+
protected $model = ApiToken::class;
14+
15+
public function definition(): array
16+
{
17+
return [
18+
'token_id' => Str::random(10),
19+
'secret' => Str::random(12),
20+
'name' => $this->faker->name(),
21+
'expires_at' => Carbon::now()->addYear(),
22+
'created_at' => Carbon::now(),
23+
'updated_at' => Carbon::now(),
24+
'user_id' => User::factory(),
25+
];
26+
}
27+
}

tests/Actions/WebhookCallTest.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use BookStack\Activity\DispatchWebhookJob;
77
use BookStack\Activity\Models\Webhook;
88
use BookStack\Activity\Tools\ActivityLogger;
9+
use BookStack\Api\ApiToken;
10+
use BookStack\Entities\Models\PageRevision;
911
use BookStack\Users\Models\User;
1012
use Illuminate\Http\Client\Request;
1113
use Illuminate\Support\Facades\Bus;
@@ -46,6 +48,24 @@ public function test_inactive_webhook_not_called_on_event()
4648
Bus::assertNotDispatched(DispatchWebhookJob::class);
4749
}
4850

51+
public function test_webhook_runs_for_delete_actions()
52+
{
53+
$this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']);
54+
Http::fake([
55+
'*' => Http::response('', 500),
56+
]);
57+
58+
$user = $this->users->newUser();
59+
$resp = $this->asAdmin()->delete($user->getEditUrl());
60+
$resp->assertRedirect('/settings/users');
61+
62+
/** @var ApiToken $apiToken */
63+
$editor = $this->users->editor();
64+
$apiToken = ApiToken::factory()->create(['user_id' => $editor]);
65+
$resp = $this->delete($editor->getEditUrl('/api-tokens/' . $apiToken->id));
66+
$resp->assertRedirect($editor->getEditUrl('#api_tokens'));
67+
}
68+
4969
public function test_failed_webhook_call_logs_error()
5070
{
5171
$logger = $this->withTestLogger();
@@ -120,7 +140,7 @@ protected function runEvent(string $event, $detail = '', ?User $user = null)
120140
$activityLogger->add($event, $detail);
121141
}
122142

123-
protected function newWebhook(array $attrs = [], array $events = ['all']): Webhook
143+
protected function newWebhook(array $attrs, array $events): Webhook
124144
{
125145
/** @var Webhook $webhook */
126146
$webhook = Webhook::factory()->create($attrs);

0 commit comments

Comments
 (0)