mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-31 02:36:28 +00:00 
			
		
		
		
	Update webhook code.
This commit is contained in:
		| @@ -29,6 +29,7 @@ use FireflyIII\Models\Webhook; | ||||
| use FireflyIII\Rules\IsBoolean; | ||||
| use FireflyIII\Support\Request\ChecksLogin; | ||||
| use FireflyIII\Support\Request\ConvertsDataTypes; | ||||
| use FireflyIII\Support\Request\ValidatesWebhooks; | ||||
| use Illuminate\Contracts\Validation\Validator; | ||||
| use Illuminate\Foundation\Http\FormRequest; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| @@ -40,6 +41,7 @@ class CreateRequest extends FormRequest | ||||
| { | ||||
|     use ChecksLogin; | ||||
|     use ConvertsDataTypes; | ||||
|     use ValidatesWebhooks; | ||||
| 
 | ||||
|     public function getData(): array | ||||
|     { | ||||
| @@ -90,52 +92,4 @@ class CreateRequest extends FormRequest | ||||
|             'url'          => ['required', sprintf('url:%s', $validProtocols)], | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function withValidator(Validator $validator): void | ||||
|     { | ||||
|         $validator->after( | ||||
|             function (Validator $validator): void { | ||||
|                 Log::debug('Validating webhook'); | ||||
|                 if ($validator->failed()) { | ||||
|                     return; | ||||
|                 } | ||||
|                 $data      = $validator->getData(); | ||||
|                 $triggers  = $data['triggers'] ?? []; | ||||
|                 $responses = $data['responses'] ?? []; | ||||
| 
 | ||||
|                 if (0 === count($triggers) || 0 === count($responses)) { | ||||
|                     Log::debug('No trigger or response, return.'); | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 $validTriggers  = array_values(Webhook::getTriggers()); | ||||
|                 $validResponses = array_values(Webhook::getResponses()); | ||||
|                 foreach ($triggers as $trigger) { | ||||
|                     if (!in_array($trigger, $validTriggers, true)) { | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|                 foreach ($responses as $response) { | ||||
|                     if (!in_array($response, $validResponses, true)) { | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|                 // some combinations are illegal.
 | ||||
|                 foreach ($triggers as $i => $trigger) { | ||||
|                     $forbidden = config(sprintf('webhooks.forbidden_responses.%s', $trigger)); | ||||
|                     if (null === $forbidden) { | ||||
|                         $validator->errors()->add(sprintf('triggers.%d', $i), trans('validation.unknown_webhook_trigger', ['trigger' => $trigger,])); | ||||
|                         continue; | ||||
|                     } | ||||
|                     foreach ($responses as $ii => $response) { | ||||
|                         if (in_array($response, $forbidden, true)) { | ||||
|                             Log::debug(sprintf('Trigger %s and response %s are forbidden.', $trigger, $response)); | ||||
|                             $validator->errors()->add(sprintf('responses.%d', $ii), trans('validation.bad_webhook_combination', ['trigger' => $trigger, 'response' => $response,])); | ||||
|                             return; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -31,6 +31,7 @@ use FireflyIII\Models\Webhook; | ||||
| use FireflyIII\Rules\IsBoolean; | ||||
| use FireflyIII\Support\Request\ChecksLogin; | ||||
| use FireflyIII\Support\Request\ConvertsDataTypes; | ||||
| use FireflyIII\Support\Request\ValidatesWebhooks; | ||||
| use Illuminate\Contracts\Validation\Validator; | ||||
| use Illuminate\Foundation\Http\FormRequest; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| @@ -42,6 +43,7 @@ class UpdateRequest extends FormRequest | ||||
| { | ||||
|     use ChecksLogin; | ||||
|     use ConvertsDataTypes; | ||||
|     use ValidatesWebhooks; | ||||
| 
 | ||||
|     public function getData(): array | ||||
|     { | ||||
| @@ -97,54 +99,4 @@ class UpdateRequest extends FormRequest | ||||
|             'url'      => [sprintf('url:%s', $validProtocols), sprintf('uniqueExistingWebhook:%d', $webhook->id)], | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function withValidator(Validator $validator): void | ||||
|     { | ||||
|         $validator->after( | ||||
|             function (Validator $validator): void { | ||||
|                 Log::debug('Validating webhook'); | ||||
| 
 | ||||
|                 if ($validator->failed()) { | ||||
|                     return; | ||||
|                 } | ||||
|                 $data      = $validator->getData(); | ||||
|                 $triggers  = $data['triggers'] ?? []; | ||||
|                 $responses = $data['responses'] ?? []; | ||||
| 
 | ||||
|                 if (0 === count($triggers) || 0 === count($responses)) { | ||||
|                     Log::debug('No trigger or response, return.'); | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 $validTriggers  = array_values(Webhook::getTriggers()); | ||||
|                 $validResponses = array_values(Webhook::getResponses()); | ||||
|                 foreach ($triggers as $trigger) { | ||||
|                     if (!in_array($trigger, $validTriggers, true)) { | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|                 foreach ($responses as $response) { | ||||
|                     if (!in_array($response, $validResponses, true)) { | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|                 // some combinations are illegal.
 | ||||
|                 foreach ($triggers as $i => $trigger) { | ||||
|                     $forbidden = config(sprintf('webhooks.forbidden_responses.%s', $trigger)); | ||||
|                     if (null === $forbidden) { | ||||
|                         $validator->errors()->add(sprintf('triggers.%d', $i), trans('validation.unknown_webhook_trigger', ['trigger' => $trigger,])); | ||||
|                         continue; | ||||
|                     } | ||||
|                     foreach ($responses as $ii => $response) { | ||||
|                         if (in_array($response, $forbidden, true)) { | ||||
|                             Log::debug(sprintf('Trigger %s and response %s are forbidden.', $trigger, $response)); | ||||
|                             $validator->errors()->add(sprintf('responses.%d', $ii), trans('validation.bad_webhook_combination', ['trigger' => $trigger, 'response' => $response,])); | ||||
|                             return; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -29,6 +29,7 @@ namespace FireflyIII\Enums; | ||||
|  */ | ||||
| enum WebhookTrigger: int | ||||
| { | ||||
|     case ANY                       = 50; | ||||
|     case STORE_TRANSACTION         = 100; | ||||
|     case UPDATE_TRANSACTION        = 110; | ||||
|     case DESTROY_TRANSACTION       = 120; | ||||
|   | ||||
| @@ -30,11 +30,12 @@ use FireflyIII\Exceptions\FireflyException; | ||||
| use FireflyIII\Models\Budget; | ||||
| use FireflyIII\Models\BudgetLimit; | ||||
| use FireflyIII\Models\Transaction; | ||||
| use FireflyIII\Models\WebhookResponse as WebhookResponseModel; | ||||
| use FireflyIII\Models\TransactionGroup; | ||||
| use FireflyIII\Models\TransactionJournal; | ||||
| use FireflyIII\Models\Webhook; | ||||
| use FireflyIII\Models\WebhookMessage; | ||||
| use FireflyIII\Models\WebhookResponse as WebhookResponseModel; | ||||
| use FireflyIII\Models\WebhookTrigger as WebhookTriggerModel; | ||||
| use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment; | ||||
| use FireflyIII\Support\JsonApi\Enrichments\BudgetEnrichment; | ||||
| use FireflyIII\Support\JsonApi\Enrichments\BudgetLimitEnrichment; | ||||
| @@ -82,11 +83,11 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
|     private function getWebhooks(): Collection | ||||
|     { | ||||
|         return $this->user->webhooks() | ||||
|             ->leftJoin('webhook_webhook_trigger','webhook_webhook_trigger.webhook_id','webhooks.id') | ||||
|             ->leftJoin('webhook_triggers','webhook_webhook_trigger.webhook_trigger_id','webhook_triggers.id') | ||||
|             ->where('active', true) | ||||
|             ->where('webhook_triggers.title', $this->trigger->name) | ||||
|             ->get(['webhooks.*']); | ||||
|                           ->leftJoin('webhook_webhook_trigger', 'webhook_webhook_trigger.webhook_id', 'webhooks.id') | ||||
|                           ->leftJoin('webhook_triggers', 'webhook_webhook_trigger.webhook_trigger_id', 'webhook_triggers.id') | ||||
|                           ->where('active', true) | ||||
|                           ->whereIn('webhook_triggers.title', [$this->trigger->name, WebhookTrigger::ANY->name]) | ||||
|                           ->get(['webhooks.*']); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -121,23 +122,24 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
|      */ | ||||
|     private function generateMessage(Webhook $webhook, Model $model): void | ||||
|     { | ||||
|         $class        = $model::class; | ||||
|         $class = $model::class; | ||||
|         // Line is ignored because all of Firefly III's Models have an id property.
 | ||||
|         Log::debug(sprintf('Now in generateMessage(#%d, %s#%d)', $webhook->id, $class, $model->id)); | ||||
|         $uuid         = Uuid::uuid4(); | ||||
|         $uuid = Uuid::uuid4(); | ||||
|         /** @var WebhookResponseModel $response */ | ||||
|         $response     = $webhook->webhookResponses()->first(); | ||||
|         $triggers     = $this->getTriggerTitles($webhook->webhookTriggers()->get()); | ||||
|         $basicMessage = [ | ||||
|             'uuid'          => $uuid->toString(), | ||||
|             'user_id'       => 0, | ||||
|             'user_group_id' => 0, | ||||
|             'trigger'       => $this->trigger->name, | ||||
|             'response'      => $webhook->webhookResponses()->first()->title, // guess that the database is correct.
 | ||||
|             'response'      => $response->title, // guess that the database is correct.
 | ||||
|             'url'           => $webhook->url, | ||||
|             'version'       => sprintf('v%d', $this->getVersion()), | ||||
|             'content'       => [], | ||||
|         ]; | ||||
| 
 | ||||
|         // depends on the model how user_id is set:
 | ||||
|         $relevantResponse = WebhookResponse::TRANSACTIONS->name; | ||||
|         switch ($class) { | ||||
|             default: | ||||
|                 // Line is ignored because all of Firefly III's Models have an id property.
 | ||||
| @@ -149,14 +151,14 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
|                 /** @var Budget $model */ | ||||
|                 $basicMessage['user_id']       = $model->user_id; | ||||
|                 $basicMessage['user_group_id'] = $model->user_group_id; | ||||
|                 $relevantResponse = WebhookResponse::BUDGET->name; | ||||
|                 $relevantResponse              = WebhookResponse::BUDGET->name; | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case BudgetLimit::class: | ||||
|                 $basicMessage['user_id']       = $model->budget->user_id; | ||||
|                 $basicMessage['user_group_id'] = $model->budget->user_group_id; | ||||
|                 $relevantResponse = WebhookResponse::BUDGET->name; | ||||
|                 $relevantResponse              = WebhookResponse::BUDGET->name; | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
| @@ -167,21 +169,9 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
| 
 | ||||
|                 break; | ||||
|         } | ||||
|         $responseTitle = $this->getRelevantResponse($triggers, $response, $class); | ||||
| 
 | ||||
|         // then depends on the response what to put in the message:
 | ||||
|         /** @var WebhookResponseModel $webhookResponse */ | ||||
|         $webhookResponse = $webhook->webhookResponses()->first(); | ||||
|         $response = $webhookResponse->title; | ||||
|         Log::debug(sprintf('Expected response for this webhook is "%s".', $response)); | ||||
|         // if it's relevant, just switch to another.
 | ||||
|         if(WebhookResponse::RELEVANT->name === $response) { | ||||
|             // switch to whatever is actually relevant.
 | ||||
|             $response = $relevantResponse; | ||||
|             Log::debug(sprintf('Expected response for this webhook is now "%s".', $response)); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         switch ($response) { | ||||
|         switch ($responseTitle) { | ||||
|             default: | ||||
|                 Log::error(sprintf('The response code for webhook #%d is "%s" and the message generator cant handle it. Soft fail.', $webhook->id, $webhook->response)); | ||||
| 
 | ||||
| @@ -190,23 +180,23 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
|             case WebhookResponse::BUDGET->name: | ||||
|                 $basicMessage['content'] = []; | ||||
|                 if ($model instanceof Budget) { | ||||
|                     $enrichment              = new BudgetEnrichment(); | ||||
|                     $enrichment = new BudgetEnrichment(); | ||||
|                     $enrichment->setUser($model->user); | ||||
|                     $model                   = $enrichment->enrichSingle($model); | ||||
|                     $transformer             = new BudgetTransformer(); | ||||
|                     $basicMessage['content'] = $transformer->transform($model); | ||||
|                 } | ||||
|                 if ($model instanceof BudgetLimit) { | ||||
|                     $user                    = $model->budget->user; | ||||
|                     $enrichment              = new BudgetLimitEnrichment(); | ||||
|                     $user       = $model->budget->user; | ||||
|                     $enrichment = new BudgetLimitEnrichment(); | ||||
|                     $enrichment->setUser($user); | ||||
| 
 | ||||
|                     $parameters              = new ParameterBag(); | ||||
|                     $parameters = new ParameterBag(); | ||||
|                     $parameters->set('start', $model->start_date); | ||||
|                     $parameters->set('end', $model->end_date); | ||||
| 
 | ||||
|                     $model                   = $enrichment->enrichSingle($model); | ||||
|                     $transformer             = new BudgetLimitTransformer(); | ||||
|                     $model       = $enrichment->enrichSingle($model); | ||||
|                     $transformer = new BudgetLimitTransformer(); | ||||
|                     $transformer->setParameters($parameters); | ||||
|                     $basicMessage['content'] = $transformer->transform($model); | ||||
|                 } | ||||
| @@ -220,7 +210,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
| 
 | ||||
|             case WebhookResponse::TRANSACTIONS->name: | ||||
|                 /** @var TransactionGroup $model */ | ||||
|                 $transformer             = new TransactionGroupTransformer(); | ||||
|                 $transformer = new TransactionGroupTransformer(); | ||||
| 
 | ||||
|                 try { | ||||
|                     $basicMessage['content'] = $transformer->transformObject($model); | ||||
| @@ -237,13 +227,13 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
| 
 | ||||
|             case WebhookResponse::ACCOUNTS->name: | ||||
|                 /** @var TransactionGroup $model */ | ||||
|                 $accounts                = $this->collectAccounts($model); | ||||
|                 $enrichment              = new AccountEnrichment(); | ||||
|                 $accounts   = $this->collectAccounts($model); | ||||
|                 $enrichment = new AccountEnrichment(); | ||||
|                 $enrichment->setDate(null); | ||||
|                 $enrichment->setUser($model->user); | ||||
|                 $accounts                = $enrichment->enrich($accounts); | ||||
|                 $accounts = $enrichment->enrich($accounts); | ||||
|                 foreach ($accounts as $account) { | ||||
|                     $transformer               = new AccountTransformer(); | ||||
|                     $transformer = new AccountTransformer(); | ||||
|                     $transformer->setParameters(new ParameterBag()); | ||||
|                     $basicMessage['content'][] = $transformer->transform($account); | ||||
|                 } | ||||
| @@ -273,7 +263,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
| 
 | ||||
|     private function storeMessage(Webhook $webhook, array $message): void | ||||
|     { | ||||
|         $webhookMessage          = new WebhookMessage(); | ||||
|         $webhookMessage = new WebhookMessage(); | ||||
|         $webhookMessage->webhook()->associate($webhook); | ||||
|         $webhookMessage->sent    = false; | ||||
|         $webhookMessage->errored = false; | ||||
| @@ -302,4 +292,41 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
|     { | ||||
|         $this->webhooks = $webhooks; | ||||
|     } | ||||
| 
 | ||||
|     private function getRelevantResponse(array $triggers, WebhookResponseModel $response, $class): string | ||||
|     { | ||||
|         // return none if none.
 | ||||
|         if (WebhookResponse::NONE->name === $response->title) { | ||||
|             Log::debug(sprintf('Return "%s" because requested nothing.', WebhookResponse::NONE->name)); | ||||
|             return WebhookResponse::NONE->name; | ||||
|         } | ||||
| 
 | ||||
|         if (WebhookResponse::RELEVANT->name === $response->title) { | ||||
|             Log::debug('Expected response is any relevant data.'); | ||||
|             // depends on the $class
 | ||||
|             switch ($class) { | ||||
|                 case TransactionGroup::class: | ||||
|                     Log::debug(sprintf('Return "%s" because class is %s', WebhookResponse::TRANSACTIONS->name, $class)); | ||||
|                     return WebhookResponse::TRANSACTIONS->name; | ||||
|                 case Budget::class: | ||||
|                 case BudgetLimit::class: | ||||
|                     Log::debug(sprintf('Return "%s" because class is %s', WebhookResponse::BUDGET->name, $class)); | ||||
|                     return WebhookResponse::BUDGET->name; | ||||
|                 default: | ||||
|                     throw new FireflyException(sprintf('Cannot deal with "relevant" if the given object is a "%s"', $class)); | ||||
|             } | ||||
|         } | ||||
|         Log::debug(sprintf('Return response again: %s', $response->title)); | ||||
|         return $response->title; | ||||
|     } | ||||
| 
 | ||||
|     private function getTriggerTitles(Collection $collection): array | ||||
|     { | ||||
|         $return = []; | ||||
|         /** @var WebhookTriggerModel $item */ | ||||
|         foreach ($collection as $item) { | ||||
|             $return[] = $item->title; | ||||
|         } | ||||
|         return array_unique($return); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										69
									
								
								app/Support/Request/ValidatesWebhooks.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								app/Support/Request/ValidatesWebhooks.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace FireflyIII\Support\Request; | ||||
| 
 | ||||
| use FireflyIII\Enums\WebhookTrigger; | ||||
| use FireflyIII\Models\Webhook; | ||||
| use Illuminate\Contracts\Validation\Validator; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| 
 | ||||
| trait ValidatesWebhooks | ||||
| { | ||||
|     public function withValidator(Validator $validator): void | ||||
|     { | ||||
|         $validator->after( | ||||
|             function (Validator $validator): void { | ||||
|                 Log::debug('Validating webhook'); | ||||
|                 if ($validator->failed()) { | ||||
|                     return; | ||||
|                 } | ||||
|                 $data      = $validator->getData(); | ||||
|                 $triggers  = $data['triggers'] ?? []; | ||||
|                 $responses = $data['responses'] ?? []; | ||||
| 
 | ||||
|                 if (0 === count($triggers) || 0 === count($responses)) { | ||||
|                     Log::debug('No trigger or response, return.'); | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 $validTriggers  = array_values(Webhook::getTriggers()); | ||||
|                 $validResponses = array_values(Webhook::getResponses()); | ||||
|                 $containsAny = false; | ||||
|                 $count = 0; | ||||
|                 foreach ($triggers as $trigger) { | ||||
|                     if (!in_array($trigger, $validTriggers, true)) { | ||||
|                         return; | ||||
|                     } | ||||
|                     $count++; | ||||
|                     if($trigger === WebhookTrigger::ANY->name) { | ||||
|                         $containsAny = true; | ||||
|                     } | ||||
|                 } | ||||
|                 if($containsAny && $count > 1) { | ||||
|                     $validator->errors()->add('triggers.0', trans('validation.only_any_trigger')); | ||||
|                     return; | ||||
|                 } | ||||
|                 foreach ($responses as $response) { | ||||
|                     if (!in_array($response, $validResponses, true)) { | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|                 // some combinations are illegal.
 | ||||
|                 foreach ($triggers as $i => $trigger) { | ||||
|                     $forbidden = config(sprintf('webhooks.forbidden_responses.%s', $trigger)); | ||||
|                     if (null === $forbidden) { | ||||
|                         $validator->errors()->add(sprintf('triggers.%d', $i), trans('validation.unknown_webhook_trigger', ['trigger' => $trigger,])); | ||||
|                         continue; | ||||
|                     } | ||||
|                     foreach ($responses as $ii => $response) { | ||||
|                         if (in_array($response, $forbidden, true)) { | ||||
|                             Log::debug(sprintf('Trigger %s and response %s are forbidden.', $trigger, $response)); | ||||
|                             $validator->errors()->add(sprintf('responses.%d', $ii), trans('validation.bad_webhook_combination', ['trigger' => $trigger, 'response' => $response,])); | ||||
|                             return; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user