Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117738603
D5862.1775155802.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
6 KB
Referenced Files
None
Subscribers
None
D5862.1775155802.diff
View Options
diff --git a/doc/Policies/RATELIMIT.md b/doc/Policies/RATELIMIT.md
new file mode 100644
--- /dev/null
+++ b/doc/Policies/RATELIMIT.md
@@ -0,0 +1,40 @@
+# Rate Limit Policy
+
+Mail submission requests are rate limited. For this we configure mail exchanger (Postfix)
+to check the sender's address in the policy service, which is part of the Kolab Cockpit and
+uses HTTP API.
+
+## Rules
+
+Here's the rules applied to a submission (in order):
+
+1. Mail from soft-deleted or suspended senders is put on HOLD.
+2. Whitelisted senders are NOT rate limited (see the Whitelists section below).
+3. Accounts with 100% discount are NOT rate limited.
+4. Accounts with positive balance and any payments are NOT rate limited.
+5. If a sender or all users in an account, in last 60 minutes:
+ a) sent at least `RATELIMIT_MAX_MESSAGES` (default: 10) messages or
+ b) sent messages to at least `RATELIMIT_MAX_RECIPIENTS` (default: 250) recipients,
+ sumbission if DEFER-ed.
+
+## Automatic suspending
+
+A sender (or the whole account) created in last two months gets suspended if the submission rate
+is exceeded too much. Limits are:
+
+- count of messages in last 60 minutes: (`RATELIMIT_MAX_MESSAGES * RATELIMIT_SUSPEND_FACTOR`)
+- count of recipients in last 60 minutes: (`RATELIMIT_MAX_RECIPIENTS * RATELIMIT_SUSPEND_FACTOR`)
+
+## Whitelists
+
+There is an exceptions list for users and domains. Mail from such senders is NOT rate limited.
+The whitelist can be managed in command line using these three commands:
+
+```
+$ php artisan policy:ratelimit:whitelist:create {object}
+$ php artisan policy:ratelimit:whitelist:delete {object}
+$ php artisan policy:ratelimit:whitelist:read
+```
+
+There is an additional static whitelist available in configuration (`RATELIMIT_WHITELIST`).
+This list accepts full email addresses, no domains.
diff --git a/src/app/Policy/RateLimit.php b/src/app/Policy/RateLimit.php
--- a/src/app/Policy/RateLimit.php
+++ b/src/app/Policy/RateLimit.php
@@ -99,11 +99,6 @@
return new Response(Response::ACTION_DUNNO);
}
- // user nor domain whitelisted, continue scrutinizing the request
- sort($recipients);
- $recipientCount = count($recipients);
- $recipientHash = hash('sha256', implode(',', $recipients));
-
// Retrieve the wallet to get to the owner
$wallet = $user->wallet();
@@ -114,6 +109,11 @@
$owner = $wallet->owner;
+ // user nor domain whitelisted, continue scrutinizing the request
+ sort($recipients);
+ $recipientCount = count($recipients);
+ $recipientHash = hash('sha256', implode(',', $recipients));
+
// find or create the request
$request = self::where('recipient_hash', $recipientHash)
->where('user_id', $user->id)
@@ -149,19 +149,23 @@
->exists();
if ($isPayer) {
- return new Response();
+ return new Response(Response::ACTION_DUNNO);
}
}
+ $max_messages = config('app.ratelimit_max_messages');
+ $max_recipients = config('app.ratelimit_max_recipients');
+ $suspend_factor = config('app.ratelimit_suspend_factor');
+
+ $ageThreshold = Carbon::now()->subMonthsWithoutOverflow(2);
+
// Examine the rates at which the owner (or its users) is sending
$ownerRates = self::where('owner_id', $owner->id)
->where('updated_at', '>=', Carbon::now()->subHour());
- if (($count = $ownerRates->count()) >= 10) {
- // automatically suspend (recursively) if 2.5 times over the original limit and younger than two months
- $ageThreshold = Carbon::now()->subMonthsWithoutOverflow(2);
-
- if ($count >= 25 && $owner->created_at > $ageThreshold) {
+ if (($count = $ownerRates->count()) >= $max_messages) {
+ // automatically suspend (recursively) if X times over the original limit and younger than two months
+ if ($count >= $max_messages * $suspend_factor && $owner->created_at > $ageThreshold) {
$owner->suspendAccount();
}
@@ -172,11 +176,9 @@
);
}
- if (($recipientCount = $ownerRates->sum('recipient_count')) >= 100) {
- // automatically suspend if 2.5 times over the original limit and younger than two months
- $ageThreshold = Carbon::now()->subMonthsWithoutOverflow(2);
-
- if ($recipientCount >= 250 && $owner->created_at > $ageThreshold) {
+ if (($recipientCount = $ownerRates->sum('recipient_count')) >= $max_recipients) {
+ // automatically suspend if X times over the original limit and younger than two months
+ if ($recipientCount >= $max_recipients * $suspend_factor && $owner->created_at > $ageThreshold) {
$owner->suspendAccount();
}
@@ -192,11 +194,9 @@
$userRates = self::where('user_id', $user->id)
->where('updated_at', '>=', Carbon::now()->subHour());
- if (($count = $userRates->count()) >= 10) {
- // automatically suspend if 2.5 times over the original limit and younger than two months
- $ageThreshold = Carbon::now()->subMonthsWithoutOverflow(2);
-
- if ($count >= 25 && $user->created_at > $ageThreshold) {
+ if (($count = $userRates->count()) >= $max_messages) {
+ // automatically suspend if X times over the original limit and younger than two months
+ if ($count >= $max_messages * $suspend_factor && $user->created_at > $ageThreshold) {
$user->suspend();
}
@@ -207,11 +207,9 @@
);
}
- if (($recipientCount = $userRates->sum('recipient_count')) >= 100) {
- // automatically suspend if 2.5 times over the original limit
- $ageThreshold = Carbon::now()->subMonthsWithoutOverflow(2);
-
- if ($recipientCount >= 250 && $user->created_at > $ageThreshold) {
+ if (($recipientCount = $userRates->sum('recipient_count')) >= $max_recipients) {
+ // automatically suspend if X times over the original limit
+ if ($recipientCount >= $max_recipients * $suspend_factor && $user->created_at > $ageThreshold) {
$user->suspend();
}
diff --git a/src/config/app.php b/src/config/app.php
--- a/src/config/app.php
+++ b/src/config/app.php
@@ -275,6 +275,9 @@
'woat_ns2' => env('WOAT_NS2', 'ns02.' . env('APP_DOMAIN')),
'ratelimit_whitelist' => explode(',', env('RATELIMIT_WHITELIST', '')),
+ 'ratelimit_max_messages' => (int) env('RATELIMIT_MAX_MESSAGES', 10),
+ 'ratelimit_max_recipients' => (int) env('RATELIMIT_MAX_RECIPIENTS', 100),
+ 'ratelimit_suspend_factor' => (float) env('RATELIMIT_SUSPEND_FACTOR', 2.5),
'companion_download_link' => env(
'COMPANION_DOWNLOAD_LINK',
"https://mirror.apheleia-it.ch/pub/companion-app-beta.apk"
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Apr 2, 6:50 PM (19 h, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18820352
Default Alt Text
D5862.1775155802.diff (6 KB)
Attached To
Mode
D5862: Ratelimit configuration
Attached
Detach File
Event Timeline