wip: spam protection

This commit is contained in:
paulwer 2023-12-18 17:21:15 +01:00
parent 5d70daaaaa
commit 7245de8c4c

View File

@ -25,6 +25,7 @@ use App\Utils\TempFile;
use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\SavesDocuments; use App\Utils\Traits\SavesDocuments;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Cache;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
@ -38,6 +39,7 @@ class IngresEmailEngine implements ShouldQueue
private IngresEmail $email; private IngresEmail $email;
private ?Company $company; private ?Company $company;
private ?bool $isUnknownRecipent = null;
private array $globalBlacklist = []; private array $globalBlacklist = [];
function __constructor(IngresEmail $email) function __constructor(IngresEmail $email)
{ {
@ -45,19 +47,28 @@ class IngresEmailEngine implements ShouldQueue
} }
/** /**
* if there is not a company with an matching mailbox, we do nothing * if there is not a company with an matching mailbox, we do nothing
* reuse this method to add more mail-parsing behaviors
*/ */
public function handle() public function handle()
{ {
if ($this->isInvalidOrBlocked())
return;
$this->isUnknownRecipent = true;
// Expense Mailbox => will create an expense // Expense Mailbox => will create an expense
foreach ($this->email->to as $expense_mailbox) { foreach ($this->email->to as $expense_mailbox) {
$this->company = MultiDB::findAndSetDbByExpenseMailbox($expense_mailbox); $this->company = MultiDB::findAndSetDbByExpenseMailbox($expense_mailbox);
if (!$this->company || !$this->validateExpenseActive()) if (!$this->company)
continue;
$this->isUnknownRecipent = false;
if (!$this->validateExpenseActive())
continue; continue;
$this->createExpense(); $this->createExpense();
} }
// TODO reuse this method to add more mail-parsing behaviors $this->saveMeta();
} }
// MAIN-PROCESSORS // MAIN-PROCESSORS
@ -95,30 +106,102 @@ class IngresEmailEngine implements ShouldQueue
return $expense; return $expense;
} }
// HELPERS // SPAM Protection
private function isInvalidOrBlocked()
{
// invalid email
if (!filter_var($this->email->from, FILTER_VALIDATE_EMAIL)) {
nlog('[IngressMailEngine] E-Mail blocked, because from e-mail has the wrong format: ' . $this->email->from);
return true;
}
$parts = explode('@', $this->email->from);
$domain = array_pop($parts);
// global blacklist
if (in_array($domain, $this->globalBlacklist)) {
nlog('[IngressMailEngine] E-Mail blocked, because the domain was found on globalBlocklist: ' . $this->email->from);
return true;
}
if (Cache::has('ingresEmailBlockedSender:' . $this->email->from)) { // was marked as blocked before, so we block without any console output
return true;
}
// sender occured in more than 500 emails in the last 12 hours
$senderMailCountTotal = Cache::get('ingresEmailSender:' . $this->email->from, 0);
if ($senderMailCountTotal >= 5000) {
nlog('[IngressMailEngine] E-Mail blocked permanent, because the sender sended more than ' . $senderMailCountTotal . ' emails in the last 12 hours: ' . $this->email->from);
$this->blockSender();
return true;
}
if ($senderMailCountTotal >= 1000) {
nlog('[IngressMailEngine] E-Mail blocked, because the sender sended more than ' . $senderMailCountTotal . ' emails in the last 12 hours: ' . $this->email->from);
$this->saveMeta();
return true;
}
// sender sended more than 50 emails to the wrong mailbox in the last 6 hours
$senderMailCountUnknownRecipent = Cache::get('ingresEmailSenderUnknownRecipent:' . $this->email->from, 0);
if ($senderMailCountUnknownRecipent >= 50) {
nlog('[IngressMailEngine] E-Mail blocked, because the sender sended more than ' . $senderMailCountUnknownRecipent . ' emails to the wrong mailbox in the last 6 hours: ' . $this->email->from);
$this->saveMeta();
return true;
}
// wrong recipent occurs in more than 100 emails in the last 12 hours, so the processing is blocked
foreach ($this->email->to as $recipent) {
$mailCountUnknownRecipent = Cache::get('ingresEmailUnknownRecipent:' . $recipent, 0); // @turbo124 maybe use many to save resources in case of spam with multiple to addresses each time
if ($mailCountUnknownRecipent >= 100) {
nlog('[IngressMailEngine] E-Mail blocked, because anyone sended more than ' . $mailCountUnknownRecipent . ' emails to the wrong mailbox in the last 12 hours. Current sender was blocked as well: ' . $this->email->from);
$this->blockSender();
return true;
}
}
return false;
}
private function blockSender()
{
Cache::add('ingresEmailBlockedSender:' . $this->email->from, true, now()->addHours(12));
$this->saveMeta();
// TODO: ignore, when known sender
// TODO: handle external blocking
}
private function saveMeta()
{
// save cache
Cache::add('ingresEmailSender:' . $this->email->from, 0, now()->addHours(12));
Cache::increment('ingresEmailSender:' . $this->email->from);
if ($this->isUnknownRecipent) {
Cache::add('ingresEmailSenderUnknownRecipent:' . $this->email->from, 0, now()->addHours(6));
Cache::increment('ingresEmailSenderUnknownRecipent:' . $this->email->from); // we save the sender, to may block him
foreach ($this->email->to as $recipent) {
Cache::add('ingresEmailUnknownRecipent:' . $recipent, 0, now()->addHours(12));
Cache::increment('ingresEmailUnknownRecipent:' . $recipent); // we save the sender, to may block him
}
}
}
// PARSING
private function processHtmlBodyToDocument() private function processHtmlBodyToDocument()
{ {
if (!$this->email->body_document && property_exists($this->email, "body")) { if (!$this->email->body_document && property_exists($this->email, "body")) {
$this->email->body_document = TempFile::UploadedFileFromRaw($this->email->body, "E-Mail.html", "text/html"); $this->email->body_document = TempFile::UploadedFileFromRaw($this->email->body, "E-Mail.html", "text/html");
} }
} }
// HELPERS
private function validateExpenseActive() private function validateExpenseActive()
{ {
return $this->company?->expense_mailbox_active ?: false; return $this->company?->expense_mailbox_active ?: false;
} }
private function validateExpenseSender() private function validateExpenseSender()
{ {
// invalid email
if (!filter_var($this->email->from, FILTER_VALIDATE_EMAIL))
return false;
$parts = explode('@', $this->email->from); $parts = explode('@', $this->email->from);
$domain = array_pop($parts); $domain = array_pop($parts);
// global blacklist
if (in_array($domain, $this->globalBlacklist))
return false;
// whitelists // whitelists
$email_whitelist = explode(",", $this->company->expense_mailbox_whitelist_emails); $email_whitelist = explode(",", $this->company->expense_mailbox_whitelist_emails);
if (in_array($this->email->from, $email_whitelist)) if (in_array($this->email->from, $email_whitelist))