Make sure webhook messages can be versionised later.

This commit is contained in:
James Cole
2020-12-04 20:19:52 +01:00
parent 7ee9b51b3f
commit 48d1d5c90b
10 changed files with 413 additions and 99 deletions

View File

@@ -23,7 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Generator\Webhook\WebhookMessageGenerator;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\Webhook;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
@@ -82,7 +82,8 @@ class StoredGroupEventHandler
Log::debug('StoredTransactionGroup:triggerWebhooks');
$group = $storedGroupEvent->transactionGroup;
$user = $group->user;
$engine = new WebhookMessageGenerator;
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser($user);
$engine->setTransactionGroups(new Collection([$group]));
$engine->setTrigger(Webhook::TRIGGER_STORE_TRANSACTION);

View File

@@ -23,7 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Generator\Webhook\WebhookMessageGenerator;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
@@ -122,7 +122,8 @@ class UpdatedGroupEventHandler
Log::debug('UpdatedGroupEventHandler:triggerWebhooks');
$group = $updatedGroupEvent->transactionGroup;
$user = $group->user;
$engine = new WebhookMessageGenerator;
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser($user);
$engine->setTransactionGroups(new Collection([$group]));
$engine->setTrigger(Webhook::TRIGGER_UPDATE_TRANSACTION);

View File

@@ -23,8 +23,10 @@ namespace FireflyIII\Handlers\Events;
use Exception;
use FireflyIII\Helpers\Webhook\SignatureGeneratorInterface;
use FireflyIII\Models\WebhookAttempt;
use FireflyIII\Models\WebhookMessage;
use FireflyIII\Services\Webhook\WebhookSenderInterface;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use JsonException;
@@ -36,97 +38,24 @@ use Log;
class WebhookEventHandler
{
/**
*
* Will try to send at most 3 messages so the flow doesn't get broken too much.
*/
public function sendWebhookMessages(): void
{
$max = (int)config('firefly.webhooks.max_attempts');
$max = 0 === $max ? 3 : $max;
$messages = WebhookMessage
::where('webhook_messages.sent', 0)
->where('webhook_messages.errored', 0)
->get(['webhook_messages.*']);
Log::debug(sprintf('Found %d webhook message(s) to be send.', $messages->count()));
/** @var WebhookMessage $message */
foreach ($messages as $message) {
$count = $message->webhookAttempts()->count();
if ($count >= 3) {
Log::info('No send message.');
continue;
}
// TODO needs its own handler.
$this->sendMessageV0($message);
}
->get(['webhook_messages.*'])
->filter(
function (WebhookMessage $message) {
return $message->webhookAttempts()->count() <= 2;
}
)->splice(0, 3);
Log::debug(sprintf('Found %d webhook message(s) ready to be send.', $messages->count()));
$sender =app(WebhookSenderInterface::class);
$sender->setMessages($messages);
$sender->send();
}
/**
* @param WebhookMessage $message
*/
private function sendMessageV0(WebhookMessage $message): void
{
Log::debug(sprintf('Trying to send webhook message #%d', $message->id));
try {
$json = json_encode($message->message, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
$attempt = new WebhookAttempt;
$attempt->webhookMessage()->associate($message);
$attempt->status_code = 0;
$attempt->logs = sprintf('Json error: %s', $e->getMessage());
$attempt->save();
return;
}
// signature v0 is generated using the following structure:
// The signed_payload string is created by concatenating:
// The timestamp (as a string)
// The character .
// The character .
// The actual JSON payload (i.e., the request body)
$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);
$options = [
'body' => $json,
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Signature' => $signatureString,
'connect_timeout' => 3.14,
'User-Agent' => sprintf('FireflyIII/%s', config('firefly.version')),
'timeout' => 10,
],
];
$client = new Client;
$logs = $message->logs ?? [];
try {
$res = $client->request('POST', $message->webhook->url, $options);
$message->sent = true;
} catch (ClientException|Exception $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$logs[] = sprintf('%s: %s', date('Y-m-d H:i:s'), $e->getMessage());
$message->errored = true;
$message->sent = false;
}
$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 request body size: %d bytes', strlen($json)));
Log::debug(sprintf('Response body: %s', $res->getBody()));
}
}