From c393fdaa9b17c6fcc64f47a963a9da9f5ee67f36 Mon Sep 17 00:00:00 2001 From: paulwer Date: Sat, 16 Dec 2023 16:27:40 +0100 Subject: [PATCH] handling of temporary files + wip init of webhooks processing --- .../Mail/Webhook/BaseWebhookHandler.php | 6 +- .../Webhook/Mailgun/MailgunWebhookHandler.php | 26 +++- .../Postmark/PostmarkWebhookHandler.php | 23 ++-- .../Mailgun/ProcessMailgunInboundWebhook.php | 112 ++---------------- .../ProcessPostmarkInboundWebhook.php | 110 ++--------------- app/Libraries/MultiDB.php | 21 ++++ app/Utils/TempFile.php | 75 +++++++++++- 7 files changed, 154 insertions(+), 219 deletions(-) diff --git a/app/Helpers/Mail/Webhook/BaseWebhookHandler.php b/app/Helpers/Mail/Webhook/BaseWebhookHandler.php index e8ae7fe3d871..8d2f2f4cc784 100644 --- a/app/Helpers/Mail/Webhook/BaseWebhookHandler.php +++ b/app/Helpers/Mail/Webhook/BaseWebhookHandler.php @@ -13,10 +13,11 @@ namespace App\Helpers\Mail\Webhook; use App\Factory\ExpenseFactory; use App\Models\Company; +use App\Utils\TempFile; use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\SavesDocuments; -interface BaseWebhookHandler +abstract class BaseWebhookHandler { use GeneratesCounter; use SavesDocuments; @@ -36,7 +37,8 @@ interface BaseWebhookHandler $expense->private_notes = $plain_message; $expense->date = $date; - // TODO: add html_message as document to the expense + // add html_message as document to the expense + $documents[] = TempFile::UploadedFileFromRaw($html_message, "E-Mail.html", "text/html"); $this->saveDocuments($documents, $expense); diff --git a/app/Helpers/Mail/Webhook/Mailgun/MailgunWebhookHandler.php b/app/Helpers/Mail/Webhook/Mailgun/MailgunWebhookHandler.php index 6c2391ea3ab3..e3efdc9986a0 100644 --- a/app/Helpers/Mail/Webhook/Mailgun/MailgunWebhookHandler.php +++ b/app/Helpers/Mail/Webhook/Mailgun/MailgunWebhookHandler.php @@ -12,11 +12,33 @@ namespace App\Helpers\Mail\Webhook\Maigun; use App\Helpers\Mail\Webhook\BaseWebhookHandler; +use App\Utils\TempFile; -interface MailgunWebhookHandler extends BaseWebhookHandler +class MailgunWebhookHandler extends BaseWebhookHandler { - public function process() + public function process($data) { + $from = $data["sender"]; + $subject = $data["subject"]; + $plain_message = $data["body-plain"]; + $html_message = $data["body-html"]; + $date = now(); // TODO + + // parse documents as UploadedFile from webhook-data + $documents = []; + foreach ($data["Attachments"] as $attachment) { + $documents[] = TempFile::UploadedFileFromRaw($attachment["Content"], $attachment["Name"], $attachment["ContentType"]); + } + + return $this->createExpense( + $from, + $subject, + $plain_message, + $html_message, + $date, + $documents, + ); + } } diff --git a/app/Helpers/Mail/Webhook/Postmark/PostmarkWebhookHandler.php b/app/Helpers/Mail/Webhook/Postmark/PostmarkWebhookHandler.php index e64064191845..1cceb5a69753 100644 --- a/app/Helpers/Mail/Webhook/Postmark/PostmarkWebhookHandler.php +++ b/app/Helpers/Mail/Webhook/Postmark/PostmarkWebhookHandler.php @@ -11,10 +11,10 @@ namespace App\Helpers\Mail\Webhook\Postmark; -use App\Factory\ExpenseFactory; use App\Helpers\Mail\Webhook\BaseWebhookHandler; +use App\Utils\TempFile; -interface PostmarkWebhookHandler extends BaseWebhookHandler +class PostmarkWebhookHandler extends BaseWebhookHandler { // { // "FromName": "Postmarkapp Support", @@ -104,15 +104,20 @@ interface PostmarkWebhookHandler extends BaseWebhookHandler $plain_message = $data["TextBody"]; $html_message = $data["HtmlBody"]; $date = $data["Date"]; // TODO - $attachments = $data["Attachments"]; // TODO + + // parse documents as UploadedFile from webhook-data + $documents = []; + foreach ($data["Attachments"] as $attachment) { + $documents[] = TempFile::UploadedFileFromRaw($attachment["Content"], $attachment["Name"], $attachment["ContentType"]); + } return $this->createExpense( - $from, // from - $subject, // subject - $plain_message, // plain_message - $html_message, // html_message - $date, // date - $attachments, // attachments + $from, + $subject, + $plain_message, + $html_message, + $date, + $documents, ); } diff --git a/app/Jobs/Mailgun/ProcessMailgunInboundWebhook.php b/app/Jobs/Mailgun/ProcessMailgunInboundWebhook.php index 5640d72c1392..603814263f38 100644 --- a/app/Jobs/Mailgun/ProcessMailgunInboundWebhook.php +++ b/app/Jobs/Mailgun/ProcessMailgunInboundWebhook.php @@ -9,27 +9,16 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\Jobs\Mailgun; +namespace App\Jobs\PostMark; -use App\DataMapper\Analytics\Mail\EmailBounce; -use App\DataMapper\Analytics\Mail\EmailSpam; -use App\Jobs\Util\SystemLogger; +use App\Helpers\Mail\Webhook\Maigun\MailgunWebhookHandler; use App\Libraries\MultiDB; -use App\Models\CreditInvitation; -use App\Models\Expense; -use App\Models\InvoiceInvitation; -use App\Models\PurchaseOrderInvitation; -use App\Models\QuoteInvitation; -use App\Models\RecurringInvoiceInvitation; use App\Models\SystemLog; -use App\Notifications\Ninja\EmailSpamNotification; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Postmark\PostmarkClient; -use Turbo124\Beacon\Facades\LightLogs; class ProcessMailgunInboundWebhook implements ShouldQueue { @@ -82,99 +71,20 @@ class ProcessMailgunInboundWebhook implements ShouldQueue */ public function handle() { - MultiDB::findAndSetDbByCompanyKey($this->request['Tag']); - // match companies if (array_key_exists('ToFull', $this->request)) throw new \Exception('invalid body'); - $toEmails = []; - foreach ($this->request['ToFull'] as $toEmailEntry) - $toEmails[] = $toEmailEntry['Email']; + foreach ($this->request['ToFull'] as $toEmailEntry) { + $toEmail = $toEmailEntry['Email']; - // create expense for each company - $expense = new Expense(); + $company = MultiDB::findAndSetDbByExpenseMailbox($toEmail); + if (!$company) { + nlog('unknown Expense Mailbox occured while handling an inbound email from postmark: ' . $toEmail); + continue; + } - $expense->company_id; + (new MailgunWebhookHandler())->process($this->request); + } } - // { - // "FromName": "Postmarkapp Support", - // "MessageStream": "inbound", - // "From": "support@postmarkapp.com", - // "FromFull": { - // "Email": "support@postmarkapp.com", - // "Name": "Postmarkapp Support", - // "MailboxHash": "" - // }, - // "To": "\"Firstname Lastname\" ", - // "ToFull": [ - // { - // "Email": "yourhash+SampleHash@inbound.postmarkapp.com", - // "Name": "Firstname Lastname", - // "MailboxHash": "SampleHash" - // } - // ], - // "Cc": "\"First Cc\" , secondCc@postmarkapp.com>", - // "CcFull": [ - // { - // "Email": "firstcc@postmarkapp.com", - // "Name": "First Cc", - // "MailboxHash": "" - // }, - // { - // "Email": "secondCc@postmarkapp.com", - // "Name": "", - // "MailboxHash": "" - // } - // ], - // "Bcc": "\"First Bcc\" , secondbcc@postmarkapp.com>", - // "BccFull": [ - // { - // "Email": "firstbcc@postmarkapp.com", - // "Name": "First Bcc", - // "MailboxHash": "" - // }, - // { - // "Email": "secondbcc@postmarkapp.com", - // "Name": "", - // "MailboxHash": "" - // } - // ], - // "OriginalRecipient": "yourhash+SampleHash@inbound.postmarkapp.com", - // "Subject": "Test subject", - // "MessageID": "73e6d360-66eb-11e1-8e72-a8904824019b", - // "ReplyTo": "replyto@postmarkapp.com", - // "MailboxHash": "SampleHash", - // "Date": "Fri, 1 Aug 2014 16:45:32 -04:00", - // "TextBody": "This is a test text body.", - // "HtmlBody": "

This is a test html body.<\/p><\/body><\/html>", - // "StrippedTextReply": "This is the reply text", - // "Tag": "TestTag", - // "Headers": [ - // { - // "Name": "X-Header-Test", - // "Value": "" - // }, - // { - // "Name": "X-Spam-Status", - // "Value": "No" - // }, - // { - // "Name": "X-Spam-Score", - // "Value": "-0.1" - // }, - // { - // "Name": "X-Spam-Tests", - // "Value": "DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,SPF_PASS" - // } - // ], - // "Attachments": [ - // { - // "Name": "test.txt", - // "Content": "VGhpcyBpcyBhdHRhY2htZW50IGNvbnRlbnRzLCBiYXNlLTY0IGVuY29kZWQu", - // "ContentType": "text/plain", - // "ContentLength": 45 - // } - // ] - // } } diff --git a/app/Jobs/PostMark/ProcessPostmarkInboundWebhook.php b/app/Jobs/PostMark/ProcessPostmarkInboundWebhook.php index 2d2355b234bc..6e54fcdf05a3 100644 --- a/app/Jobs/PostMark/ProcessPostmarkInboundWebhook.php +++ b/app/Jobs/PostMark/ProcessPostmarkInboundWebhook.php @@ -11,25 +11,14 @@ namespace App\Jobs\PostMark; -use App\DataMapper\Analytics\Mail\EmailBounce; -use App\DataMapper\Analytics\Mail\EmailSpam; -use App\Jobs\Util\SystemLogger; +use App\Helpers\Mail\Webhook\Postmark\PostmarkWebhookHandler; use App\Libraries\MultiDB; -use App\Models\CreditInvitation; -use App\Models\Expense; -use App\Models\InvoiceInvitation; -use App\Models\PurchaseOrderInvitation; -use App\Models\QuoteInvitation; -use App\Models\RecurringInvoiceInvitation; use App\Models\SystemLog; -use App\Notifications\Ninja\EmailSpamNotification; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Postmark\PostmarkClient; -use Turbo124\Beacon\Facades\LightLogs; class ProcessPostmarkInboundWebhook implements ShouldQueue { @@ -82,99 +71,20 @@ class ProcessPostmarkInboundWebhook implements ShouldQueue */ public function handle() { - MultiDB::findAndSetDbByCompanyKey($this->request['Tag']); - // match companies if (array_key_exists('ToFull', $this->request)) throw new \Exception('invalid body'); - $toEmails = []; - foreach ($this->request['ToFull'] as $toEmailEntry) - $toEmails[] = $toEmailEntry['Email']; + foreach ($this->request['ToFull'] as $toEmailEntry) { + $toEmail = $toEmailEntry['Email']; - // create expense for each company - $expense = new Expense(); + $company = MultiDB::findAndSetDbByExpenseMailbox($toEmail); + if (!$company) { + nlog('unknown Expense Mailbox occured while handling an inbound email from postmark: ' . $toEmail); + continue; + } - $expense->company_id; + (new PostmarkWebhookHandler())->process($this->request); + } } - // { - // "FromName": "Postmarkapp Support", - // "MessageStream": "inbound", - // "From": "support@postmarkapp.com", - // "FromFull": { - // "Email": "support@postmarkapp.com", - // "Name": "Postmarkapp Support", - // "MailboxHash": "" - // }, - // "To": "\"Firstname Lastname\" ", - // "ToFull": [ - // { - // "Email": "yourhash+SampleHash@inbound.postmarkapp.com", - // "Name": "Firstname Lastname", - // "MailboxHash": "SampleHash" - // } - // ], - // "Cc": "\"First Cc\" , secondCc@postmarkapp.com>", - // "CcFull": [ - // { - // "Email": "firstcc@postmarkapp.com", - // "Name": "First Cc", - // "MailboxHash": "" - // }, - // { - // "Email": "secondCc@postmarkapp.com", - // "Name": "", - // "MailboxHash": "" - // } - // ], - // "Bcc": "\"First Bcc\" , secondbcc@postmarkapp.com>", - // "BccFull": [ - // { - // "Email": "firstbcc@postmarkapp.com", - // "Name": "First Bcc", - // "MailboxHash": "" - // }, - // { - // "Email": "secondbcc@postmarkapp.com", - // "Name": "", - // "MailboxHash": "" - // } - // ], - // "OriginalRecipient": "yourhash+SampleHash@inbound.postmarkapp.com", - // "Subject": "Test subject", - // "MessageID": "73e6d360-66eb-11e1-8e72-a8904824019b", - // "ReplyTo": "replyto@postmarkapp.com", - // "MailboxHash": "SampleHash", - // "Date": "Fri, 1 Aug 2014 16:45:32 -04:00", - // "TextBody": "This is a test text body.", - // "HtmlBody": "

This is a test html body.<\/p><\/body><\/html>", - // "StrippedTextReply": "This is the reply text", - // "Tag": "TestTag", - // "Headers": [ - // { - // "Name": "X-Header-Test", - // "Value": "" - // }, - // { - // "Name": "X-Spam-Status", - // "Value": "No" - // }, - // { - // "Name": "X-Spam-Score", - // "Value": "-0.1" - // }, - // { - // "Name": "X-Spam-Tests", - // "Value": "DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,SPF_PASS" - // } - // ], - // "Attachments": [ - // { - // "Name": "test.txt", - // "Content": "VGhpcyBpcyBhdHRhY2htZW50IGNvbnRlbnRzLCBiYXNlLTY0IGVuY29kZWQu", - // "ContentType": "text/plain", - // "ContentLength": 45 - // } - // ] - // } } diff --git a/app/Libraries/MultiDB.php b/app/Libraries/MultiDB.php index f58841800c7b..cef54abeb347 100644 --- a/app/Libraries/MultiDB.php +++ b/app/Libraries/MultiDB.php @@ -513,6 +513,27 @@ class MultiDB return false; } + public static function findAndSetDbByExpenseMailbox($expense_mailbox) + { + if (!config('ninja.db.multi_db_enabled')) { + return Company::where("expense_mailbox", $expense_mailbox)->first(); + } + + $current_db = config('database.default'); + + foreach (self::$dbs as $db) { + if ($company = Company::on($db)->where("expense_mailbox", $expense_mailbox)->first()) { + self::setDb($db); + + return $company; + } + } + + self::setDB($current_db); + + return false; + } + public static function findAndSetDbByInvitation($entity, $invitation_key) { $class = 'App\Models\\' . ucfirst(Str::camel($entity)) . 'Invitation'; diff --git a/app/Utils/TempFile.php b/app/Utils/TempFile.php index 9d7fe59ff3d9..0e8062b457ad 100644 --- a/app/Utils/TempFile.php +++ b/app/Utils/TempFile.php @@ -11,27 +11,92 @@ namespace App\Utils; +use Illuminate\Http\File; +use Illuminate\Http\UploadedFile; +use Illuminate\Support\Arr; + class TempFile { - public static function path($url) :string + public static function path($url): string { - $temp_path = @tempnam(sys_get_temp_dir().'/'.sha1(time()), basename($url)); + $temp_path = @tempnam(sys_get_temp_dir() . '/' . sha1(time()), basename($url)); copy($url, $temp_path); return $temp_path; } /* Downloads a file to temp storage and returns the path - used for mailers */ - public static function filePath($data, $filename) :string + public static function filePath($data, $filename): string { - $dir_hash = sys_get_temp_dir().'/'.sha1(microtime()); + $dir_hash = sys_get_temp_dir() . '/' . sha1(microtime()); mkdir($dir_hash); - $file_path = $dir_hash.'/'.$filename; + $file_path = $dir_hash . '/' . $filename; file_put_contents($file_path, $data); return $file_path; } + + /* create a tmp file from a base64 string: https://gist.github.com/waska14/8b3bcebfad1f86f7fcd3b82927576e38*/ + public static function UploadedFileFromBase64(string $base64File): UploadedFile + { + // Get file data base64 string + $fileData = base64_decode(Arr::last(explode(',', $base64File))); + + // Create temp file and get its absolute path + $tempFile = tmpfile(); + $tempFilePath = stream_get_meta_data($tempFile)['uri']; + + // Save file data in file + file_put_contents($tempFilePath, $fileData); + + $tempFileObject = new File($tempFilePath); + $file = new UploadedFile( + $tempFileObject->getPathname(), + $tempFileObject->getFilename(), + $tempFileObject->getMimeType(), + 0, + true // Mark it as test, since the file isn't from real HTTP POST. + ); + + // Close this file after response is sent. + // Closing the file will cause to remove it from temp director! + app()->terminating(function () use ($tempFile) { + fclose($tempFile); + }); + + // return UploadedFile object + return $file; + } + + /* create a tmp file from a raw string: https://gist.github.com/waska14/8b3bcebfad1f86f7fcd3b82927576e38*/ + public static function UploadedFileFromRaw(string $fileData, string|null $fileName = null, string|null $mimeType = null): UploadedFile + { + // Create temp file and get its absolute path + $tempFile = tmpfile(); + $tempFilePath = stream_get_meta_data($tempFile)['uri']; + + // Save file data in file + file_put_contents($tempFilePath, $fileData); + + $tempFileObject = new File($tempFilePath); + $file = new UploadedFile( + $tempFileObject->getPathname(), + $fileName ?: $tempFileObject->getFilename(), + $mimeType ?: $tempFileObject->getMimeType(), + 0, + true // Mark it as test, since the file isn't from real HTTP POST. + ); + + // Close this file after response is sent. + // Closing the file will cause to remove it from temp director! + app()->terminating(function () use ($tempFile) { + fclose($tempFile); + }); + + // return UploadedFile object + return $file; + } }