mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-20 11:19:16 +00:00
Split webhook message sending into different models.
This commit is contained in:
@@ -100,6 +100,8 @@ class WebhookMessageGenerator
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Webhook $webhook
|
* @param Webhook $webhook
|
||||||
|
*
|
||||||
|
* @throws FireflyException
|
||||||
*/
|
*/
|
||||||
private function runWebhook(Webhook $webhook): void
|
private function runWebhook(Webhook $webhook): void
|
||||||
{
|
{
|
||||||
@@ -124,6 +126,7 @@ class WebhookMessageGenerator
|
|||||||
'trigger' => config('firefly.webhooks.triggers')[$webhook->trigger],
|
'trigger' => config('firefly.webhooks.triggers')[$webhook->trigger],
|
||||||
'url' => $webhook->url,
|
'url' => $webhook->url,
|
||||||
'uuid' => $uuid->toString(),
|
'uuid' => $uuid->toString(),
|
||||||
|
'version' => 0,
|
||||||
'response' => config('firefly.webhooks.responses')[$webhook->response],
|
'response' => config('firefly.webhooks.responses')[$webhook->response],
|
||||||
'content' => [],
|
'content' => [],
|
||||||
];
|
];
|
||||||
@@ -131,6 +134,8 @@ class WebhookMessageGenerator
|
|||||||
switch ($webhook->response) {
|
switch ($webhook->response) {
|
||||||
default:
|
default:
|
||||||
throw new FireflyException(sprintf('Cannot handle this webhook response (%d)', $webhook->response));
|
throw new FireflyException(sprintf('Cannot handle this webhook response (%d)', $webhook->response));
|
||||||
|
case Webhook::RESPONSE_NONE:
|
||||||
|
$message['content'] = [];
|
||||||
case Webhook::RESPONSE_TRANSACTIONS:
|
case Webhook::RESPONSE_TRANSACTIONS:
|
||||||
$transformer = new TransactionGroupTransformer;
|
$transformer = new TransactionGroupTransformer;
|
||||||
$message['content'] = $transformer->transformObject($transactionGroup);
|
$message['content'] = $transformer->transformObject($transactionGroup);
|
||||||
@@ -177,7 +182,6 @@ class WebhookMessageGenerator
|
|||||||
$webhookMessage->errored = false;
|
$webhookMessage->errored = false;
|
||||||
$webhookMessage->uuid = $message['uuid'];
|
$webhookMessage->uuid = $message['uuid'];
|
||||||
$webhookMessage->message = $message;
|
$webhookMessage->message = $message;
|
||||||
$webhookMessage->logs = null;
|
|
||||||
$webhookMessage->save();
|
$webhookMessage->save();
|
||||||
|
|
||||||
return $webhookMessage;
|
return $webhookMessage;
|
||||||
|
@@ -23,6 +23,7 @@ namespace FireflyIII\Handlers\Events;
|
|||||||
|
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use FireflyIII\Models\WebhookAttempt;
|
||||||
use FireflyIII\Models\WebhookMessage;
|
use FireflyIII\Models\WebhookMessage;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use GuzzleHttp\Exception\ClientException;
|
use GuzzleHttp\Exception\ClientException;
|
||||||
@@ -41,49 +42,62 @@ class WebhookEventHandler
|
|||||||
{
|
{
|
||||||
$max = (int)config('firefly.webhooks.max_attempts');
|
$max = (int)config('firefly.webhooks.max_attempts');
|
||||||
$max = 0 === $max ? 3 : $max;
|
$max = 0 === $max ? 3 : $max;
|
||||||
$messages = WebhookMessage::where('sent', 0)
|
$messages = WebhookMessage
|
||||||
->where('attempts', '<=', $max)
|
::where('webhook_messages.sent', 0)
|
||||||
->get();
|
->where('webhook_messages.errored', 0)
|
||||||
Log::debug(sprintf('Going to send %d webhook message(s)', $messages->count()));
|
->get(['webhook_messages.*']);
|
||||||
|
Log::debug(sprintf('Found %d webhook message(s) to be send.', $messages->count()));
|
||||||
/** @var WebhookMessage $message */
|
/** @var WebhookMessage $message */
|
||||||
foreach ($messages as $message) {
|
foreach ($messages as $message) {
|
||||||
$this->sendMessage($message);
|
$count = $message->webhookAttempts()->count();
|
||||||
|
if ($count >= 3) {
|
||||||
|
Log::info('No send message.');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// TODO needs its own handler.
|
||||||
|
$this->sendMessageV0($message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param WebhookMessage $message
|
* @param WebhookMessage $message
|
||||||
*/
|
*/
|
||||||
private function sendMessage(WebhookMessage $message): void
|
private function sendMessageV0(WebhookMessage $message): void
|
||||||
{
|
{
|
||||||
Log::debug(sprintf('Trying to send webhook message #%d', $message->id));
|
Log::debug(sprintf('Trying to send webhook message #%d', $message->id));
|
||||||
try {
|
try {
|
||||||
$json = json_encode($message->message, JSON_THROW_ON_ERROR);
|
$json = json_encode($message->message, JSON_THROW_ON_ERROR);
|
||||||
} catch (JsonException $e) {
|
} catch (JsonException $e) {
|
||||||
$message->attempts++;
|
$attempt = new WebhookAttempt;
|
||||||
$message->logs[] = sprintf('%s: %s', date('Y-m-d H:i:s'), sprintf('Json error: %s', $e->getMessage()));
|
$attempt->webhookMessage()->associate($message);
|
||||||
$message->save();
|
$attempt->status_code = 0;
|
||||||
|
$attempt->logs = sprintf('Json error: %s', $e->getMessage());
|
||||||
|
$attempt->save();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$user = $message->webhook->user;
|
// signature v0 is generated using the following structure:
|
||||||
try {
|
// The signed_payload string is created by concatenating:
|
||||||
$token = $user->generateAccessToken();
|
// The timestamp (as a string)
|
||||||
} catch (Exception $e) {
|
// The character .
|
||||||
$message->attempts++;
|
// The character .
|
||||||
$message->logs[] = sprintf('%s: %s', date('Y-m-d H:i:s'), sprintf('Could not generate token: %s', $e->getMessage()));
|
// The actual JSON payload (i.e., the request body)
|
||||||
$message->save();
|
$timestamp = time();
|
||||||
|
$payload = sprintf('%s.%s', $timestamp, $json);
|
||||||
|
$signature = hash_hmac('sha3-256', $payload, $message->webhook->secret, false);
|
||||||
|
|
||||||
|
// signature string:
|
||||||
|
// header included in each signed event contains a timestamp and one or more signatures.
|
||||||
|
// The timestamp is prefixed by t=, and each signature is prefixed by a scheme.
|
||||||
|
// Schemes start with v, followed by an integer. Currently, the only valid live signature scheme is v0.
|
||||||
|
$signatureString = sprintf('t=%s,v0=%s', $timestamp, $signature);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$accessToken = app('preferences')->getForUser($user, 'access_token', $token);
|
|
||||||
$signature = hash_hmac('sha3-256', $json, $accessToken->data, false);
|
|
||||||
$options = [
|
$options = [
|
||||||
'body' => $json,
|
'body' => $json,
|
||||||
'headers' => [
|
'headers' => [
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
'Signature' => $signature,
|
'Signature' => $signatureString,
|
||||||
'connect_timeout' => 3.14,
|
'connect_timeout' => 3.14,
|
||||||
'User-Agent' => sprintf('FireflyIII/%s', config('firefly.version')),
|
'User-Agent' => sprintf('FireflyIII/%s', config('firefly.version')),
|
||||||
'timeout' => 10,
|
'timeout' => 10,
|
||||||
@@ -101,10 +115,15 @@ class WebhookEventHandler
|
|||||||
$message->errored = true;
|
$message->errored = true;
|
||||||
$message->sent = false;
|
$message->sent = false;
|
||||||
}
|
}
|
||||||
$message->attempts++;
|
|
||||||
$message->logs = $logs;
|
|
||||||
$message->save();
|
$message->save();
|
||||||
|
|
||||||
|
$attempt = new WebhookAttempt;
|
||||||
|
$attempt->webhookMessage()->associate($message);
|
||||||
|
$attempt->status_code = $res->getStatusCode();
|
||||||
|
$attempt->logs = '';
|
||||||
|
$attempt->response = (string)$res->getBody();
|
||||||
|
$attempt->save();
|
||||||
|
|
||||||
Log::debug(sprintf('Webhook message #%d was sent. Status code %d', $message->id, $res->getStatusCode()));
|
Log::debug(sprintf('Webhook message #%d was sent. Status code %d', $message->id, $res->getStatusCode()));
|
||||||
Log::debug(sprintf('Webhook request body size: %d bytes', strlen($json)));
|
Log::debug(sprintf('Webhook request body size: %d bytes', strlen($json)));
|
||||||
Log::debug(sprintf('Response body: %s', $res->getBody()));
|
Log::debug(sprintf('Response body: %s', $res->getBody()));
|
||||||
|
@@ -832,6 +832,7 @@ return [
|
|||||||
'responses' => [
|
'responses' => [
|
||||||
200 => 'RESPONSE_TRANSACTIONS',
|
200 => 'RESPONSE_TRANSACTIONS',
|
||||||
210 => 'RESPONSE_ACCOUNTS',
|
210 => 'RESPONSE_ACCOUNTS',
|
||||||
|
220 => 'RESPONSE_NONE',
|
||||||
],
|
],
|
||||||
'deliveries' => [
|
'deliveries' => [
|
||||||
300 => 'DELIVERY_JSON',
|
300 => 'DELIVERY_JSON',
|
||||||
|
@@ -119,13 +119,13 @@ class ChangesForV550 extends Migration
|
|||||||
$table->softDeletes();
|
$table->softDeletes();
|
||||||
$table->integer('user_id', false, true);
|
$table->integer('user_id', false, true);
|
||||||
$table->string('title', 512)->index();
|
$table->string('title', 512)->index();
|
||||||
|
$table->string('secret', 32)->index();
|
||||||
$table->boolean('active')->default(true);
|
$table->boolean('active')->default(true);
|
||||||
$table->unsignedSmallInteger('trigger', false);
|
$table->unsignedSmallInteger('trigger', false);
|
||||||
$table->unsignedSmallInteger('response', false);
|
$table->unsignedSmallInteger('response', false);
|
||||||
$table->unsignedSmallInteger('delivery', false);
|
$table->unsignedSmallInteger('delivery', false);
|
||||||
$table->string('url', 512)->index();
|
$table->string('url', 1024);
|
||||||
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||||
$table->unique(['user_id', 'trigger', 'response', 'delivery', 'url']);
|
|
||||||
$table->unique(['user_id', 'title']);
|
$table->unique(['user_id', 'title']);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -137,15 +137,31 @@ class ChangesForV550 extends Migration
|
|||||||
$table->increments('id');
|
$table->increments('id');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
$table->softDeletes();
|
$table->softDeletes();
|
||||||
$table->integer('webhook_id', false, true);
|
|
||||||
$table->boolean('sent')->default(false);
|
$table->boolean('sent')->default(false);
|
||||||
$table->boolean('errored')->default(false);
|
$table->boolean('errored')->default(false);
|
||||||
$table->unsignedTinyInteger('attempts')->default(0);
|
|
||||||
|
$table->integer('webhook_id', false, true);
|
||||||
$table->string('uuid', 64);
|
$table->string('uuid', 64);
|
||||||
$table->longText('message');
|
$table->longText('message');
|
||||||
$table->longText('logs')->nullable();
|
|
||||||
$table->foreign('webhook_id')->references('id')->on('webhooks')->onDelete('cascade');
|
$table->foreign('webhook_id')->references('id')->on('webhooks')->onDelete('cascade');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Schema::create(
|
||||||
|
'webhook_attempts',
|
||||||
|
static function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
$table->integer('webhook_message_id', false, true);
|
||||||
|
$table->unsignedSmallInteger('status_code')->default(0);
|
||||||
|
|
||||||
|
$table->longText('logs')->nullable();
|
||||||
|
$table->longText('response')->nullable();
|
||||||
|
|
||||||
|
$table->foreign('webhook_message_id')->references('id')->on('webhook_messages')->onDelete('cascade');
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user