diff --git a/docs/modules/ROOT/pages/tmail-backend/configure/rate-limiting.adoc b/docs/modules/ROOT/pages/tmail-backend/configure/rate-limiting.adoc
index d1b479a02e..dab5812138 100644
--- a/docs/modules/ROOT/pages/tmail-backend/configure/rate-limiting.adoc
+++ b/docs/modules/ROOT/pages/tmail-backend/configure/rate-limiting.adoc
@@ -2,33 +2,3 @@
:navtitle: Rate limiting
See xref:tmail-backend/features/tmailRateLimiting.adoc[this page] for feature explanation.
-
-== Apply rate limiting plans
-An administrator needs to rely on the `EnforceRateLimitingPlan` mailet in order to apply rate limiting plans.
-
-Configure mailets to apply rate limiting to senders:
-
-....
-
- some-prefix
- TransitLimitations
- tooMuchMails
-
-
- some-prefix
- RelayLimitations
- tooMuchMails
-
-....
-
-Configure mailet to apply rate limiting to recipients:
-
-....
-
- some-prefix
- DeliveryLimitations
- tooMuchMails
-
-....
-
-Exceeded emails will be transferred to `tooMuchMails` processor. Please customize this mailet depends on your will.
diff --git a/docs/modules/ROOT/pages/tmail-backend/features/tmailRateLimiting.adoc b/docs/modules/ROOT/pages/tmail-backend/features/tmailRateLimiting.adoc
index 964cb527df..30fdfdc06d 100644
--- a/docs/modules/ROOT/pages/tmail-backend/features/tmailRateLimiting.adoc
+++ b/docs/modules/ROOT/pages/tmail-backend/features/tmailRateLimiting.adoc
@@ -3,20 +3,14 @@
Manage email rate limiting plans and apply them to your users!
-TMail Rate Limiting enable the administrator to manage rate limiting plans and apply those plans to users:
+TMail Rate Limiting enable the administrator to manage rate limiting of users:
-- Administrator can manage rate limiting plans
-- Administrator can manage which rate limiting plans apply to which users
-- Administrator can configure mailet configuration, so it can retrieve rate limiting plans and apply to according users
+- Administrator can manage rate limiting for each user
+- Administrator can configure mailet configuration, so it can retrieve rate limiting and apply to according users
== Manage rate limiting plans via webadmin
-The administrator can manage rate limiting plans using xref:tmail-backend/webadmin.adoc#_rate_limiting[Rate Limiting Plan Management Routes]:
+The administrator can manage rate limiting using xref:tmail-backend/webadmin.adoc#_rate_limiting[Rate Limiting Plan Management Routes]:
-- Creating/updating/getting rate limiting plans
-- Attaching/getting/removing plan of a user. Getting list of users belonging to a plan.
-
-The administrator only can apply a plan to a user.
-
-Because of limited number of plans, we do not support removing rate limiting plans.
+- Updating/getting rate limiting for users.
diff --git a/docs/modules/ROOT/pages/tmail-backend/webadmin.adoc b/docs/modules/ROOT/pages/tmail-backend/webadmin.adoc
index 1c28ed45c3..486a1b60d5 100644
--- a/docs/modules/ROOT/pages/tmail-backend/webadmin.adoc
+++ b/docs/modules/ROOT/pages/tmail-backend/webadmin.adoc
@@ -191,279 +191,6 @@ Return codes:
- `400` Invalid request
- `404` User does not exist
-=== Create a new plan
-Allow to create a new rate limiting plan.
-....
-curl -XPOST http://ip:port/rate-limit-plans/{RateLimitingPlanName}
- -H "Content-Type: application/json"
- -d '{
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400,
- "count": 1000,
- "size": 4096
- }
- ],
- "relayLimits": [{
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "deliveryLimits": [{
- "name": "deliveryMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }]
-}'
-....
-
-Return planId of created plan:
-....
-{
- "planId": "6b427706-11de-4674-a4e7-166983d9119e"
-}
-....
-Return codes:
-
-- `201` The plan created successfully
-- `400` Invalid request
-
-=== Edit a plan
-
-....
-curl -XPUT http://ip:port/rate-limit-plans/{RateLimitingPlanId}
- -H "Content-Type: application/json"
- -d '{
- "transitLimits": [
- {
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400
- "count": 1000,
- "size": 4096
- }
- ],
- "relayLimits": [
- {
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }
- ],
- "deliveryLimits": [
- {
- "name": "deliveryMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }
- ]
-}'
-....
-
-Allow to update an existing plan.
-
-Return codes:
-
-- `204` The plan updated successfully
-- `400` Invalid request
-- `404` Plan does not exist
-
-=== Get a plan
-
-....
-curl -XGET http://ip:port/rate-limit-plans/{RateLimitingPlanId}
-....
-
-Return a plan:
-....
-{
- "planId": "65b94d87-b077-4994-bc82-ab87c4e68313",
- "planName": "oldPlanName",
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400,
- "count": 1000,
- "size": 4096
- }
- ],
- "relayLimits": [{
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "deliveryLimits": [{
- "name": "deliveryMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }]
-}
-....
-
-Return codes:
-
-- `200` Get the plan successfully
-- `400` Invalid request
-- `404` Plan does not exist
-
-=== Get all plans
-
-....
-curl -XGET http://ip:port/rate-limit-plans
-....
-
-Return all existing plans:
-....
-[{
- "planId": "524acec6-7910-4137-b862-7ec1ab048404",
- "planName": "plan1",
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400,
- "count": 1000,
- "size": 4096
- }
- ],
- "relayLimits": [{
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "deliveryLimits": [{
- "name": "deliveryMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }]
- },
- {
- "planId": "2fc6b2d7-9b62-42f0-aa8a-5ab62168e0c5",
- "planName": "plan2",
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400,
- "count": 1000,
- "size": 4096
- }
- ],
- "relayLimits": [{
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "deliveryLimits": [{
- "name": "deliveryMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }]
- }
-]
-....
-
-Return codes:
-
-- `200` Get all plans successfully
-
-=== Attach a plan to a user
-....
-curl -XPUT http://ip:port/users/{username}/rate-limit-plans/{planId}
-....
-
-Attach a rate limiting plan to a user. This also can be used to reattach a new plan to that user.
-
-Return codes:
-
-- `204` Attached the plan to the user successfully
-- `400` Invalid request
-- `404` Either plan or user is not found
-
-=== Get list of users belonging to a plan
-....
-curl -XGET http://ip:port/rate-limit-plans/{planId}/users
-....
-
-Return users belong to a plan:
-....
-[
- "bob@linagora.com",
- "andre@linagora.com"
-]
-....
-
-Return codes:
-
-- `200` Get all users belong to that plan successfully
-- `400` Invalid request
-- `404` Plan is not found
-
-=== Get plan of a user
-....
-curl -XGET http://ip:port/users/{username}/rate-limit-plans
-....
-
-Return rate limiting planId attached to that user:
-....
-{
- "planId": "02242f08-515c-4170-945e-64afa991f149"
-}
-....
-
-Return codes:
-
-- `200` Get plan of that user successfully
-- `400` Invalid request
-- `404` Either user is not found or that user does not have a plan.
-
-=== Revoke plan of a user
-....
-curl -XDELETE http://ip:port/users/{username}/rate-limit-plans
-....
-
-Revoke the plan attached to that user.
-
-Return codes:
-
-- `204` Revoke plan of that user successfully
-- `400` Invalid request
-- `404` User is not found
-
== Domain contacts
=== Create a contact
diff --git a/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedServer.java b/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedServer.java
index f2c3533400..5cc3a4eb08 100644
--- a/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedServer.java
+++ b/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedServer.java
@@ -211,7 +211,7 @@
import com.linagora.tmail.team.TeamMailboxModule;
import com.linagora.tmail.webadmin.EmailAddressContactRoutesModule;
import com.linagora.tmail.webadmin.OidcBackchannelLogoutRoutesModule;
-import com.linagora.tmail.webadmin.RateLimitPlanRoutesModule;
+import com.linagora.tmail.webadmin.RateLimitsRoutesModule;
import com.linagora.tmail.webadmin.TeamMailboxRoutesModule;
import com.linagora.tmail.webadmin.archival.InboxArchivalTaskModule;
import com.linagora.tmail.webadmin.cleanup.MailboxesCleanupModule;
@@ -257,7 +257,7 @@ protected void configure() {
new MailboxRoutesModule(),
new MailQueueRoutesModule(),
new MailRepositoriesRoutesModule(),
- new RateLimitPlanRoutesModule(),
+ new RateLimitsRoutesModule(),
new TeamMailboxModule(),
new TeamMailboxRoutesModule(),
new SieveRoutesModule(),
diff --git a/tmail-backend/apps/memory/src/main/java/com/linagora/tmail/james/app/MemoryServer.java b/tmail-backend/apps/memory/src/main/java/com/linagora/tmail/james/app/MemoryServer.java
index 86a75b9ce7..ca94fc0af3 100644
--- a/tmail-backend/apps/memory/src/main/java/com/linagora/tmail/james/app/MemoryServer.java
+++ b/tmail-backend/apps/memory/src/main/java/com/linagora/tmail/james/app/MemoryServer.java
@@ -114,7 +114,7 @@
import com.linagora.tmail.team.TeamMailboxModule;
import com.linagora.tmail.webadmin.EmailAddressContactRoutesModule;
import com.linagora.tmail.webadmin.OidcBackchannelLogoutRoutesModule;
-import com.linagora.tmail.webadmin.RateLimitPlanRoutesModule;
+import com.linagora.tmail.webadmin.RateLimitsRoutesModule;
import com.linagora.tmail.webadmin.TeamMailboxRoutesModule;
import com.linagora.tmail.webadmin.archival.InboxArchivalTaskModule;
import com.linagora.tmail.webadmin.cleanup.MailboxesCleanupModule;
@@ -179,7 +179,7 @@ public class MemoryServer {
new TMailScanningQuotaSearcherModule(),
new MemoryRateLimiterModule(),
new MemoryRateLimitingModule(),
- new RateLimitPlanRoutesModule(),
+ new RateLimitsRoutesModule(),
new MemoryEmailAddressContactModule(),
new EmailAddressContactRoutesModule(),
new MemoryLabelRepositoryModule(),
diff --git a/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailServer.java b/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailServer.java
index 3b6074a0ff..b9dbf94e87 100644
--- a/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailServer.java
+++ b/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailServer.java
@@ -187,7 +187,7 @@
import com.linagora.tmail.team.TeamMailboxModule;
import com.linagora.tmail.webadmin.EmailAddressContactRoutesModule;
import com.linagora.tmail.webadmin.OidcBackchannelLogoutRoutesModule;
-import com.linagora.tmail.webadmin.RateLimitPlanRoutesModule;
+import com.linagora.tmail.webadmin.RateLimitsRoutesModule;
import com.linagora.tmail.webadmin.TeamMailboxRoutesModule;
import com.linagora.tmail.webadmin.archival.InboxArchivalTaskModule;
import com.linagora.tmail.webadmin.cleanup.MailboxesCleanupModule;
@@ -278,7 +278,7 @@ public static GuiceJamesServer createServer(PostgresTmailConfiguration configura
new MailQueueRoutesModule(),
new MailRepositoriesRoutesModule(),
new MessagesRoutesModule(),
- new RateLimitPlanRoutesModule(),
+ new RateLimitsRoutesModule(),
new ReIndexingModule(),
new SieveRoutesModule(),
new TeamMailboxModule(),
@@ -356,7 +356,7 @@ public static GuiceJamesServer createServer(PostgresTmailConfiguration configura
.with(new TeamMailboxModule(),
new TMailMailboxSortOrderProviderModule(),
new PostgresRateLimitingModule(),
- new RateLimitPlanRoutesModule(),
+ new RateLimitsRoutesModule(),
new EmailAddressContactRoutesModule(),
new PostgresLabelRepositoryModule(),
new PostgresJmapSettingsRepositoryModule(),
diff --git a/tmail-backend/integration-tests/pom.xml b/tmail-backend/integration-tests/pom.xml
index db50bacc16..01c5d3c978 100644
--- a/tmail-backend/integration-tests/pom.xml
+++ b/tmail-backend/integration-tests/pom.xml
@@ -48,10 +48,6 @@
webadmin/distributed-webadmin-integration-tests
webadmin/postgres-webadmin-integration-tests
- rate-limiter/rate-limiter-integration-tests-common
- rate-limiter/distributed-rate-limiter-integration-tests
- rate-limiter/postgres-rate-limiter-integration-tests
-
rspamd/rspamd-integration-tests-common
rspamd/distributed-rspamd-integration-tests
rspamd/postgres-rspamd-integration-tests
@@ -89,11 +85,6 @@
webadmin-integration-tests-common
${project.version}
-
- ${project.groupId}
- rate-limiter-integration-tests-common
- ${project.version}
-
${project.groupId}
rspamd-integration-tests-common
diff --git a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/pom.xml b/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/pom.xml
deleted file mode 100644
index 5bfde42a29..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/pom.xml
+++ /dev/null
@@ -1,138 +0,0 @@
-
-
-
-
-
- com.linagora.tmail
- integration-tests
- 1.0.0-SNAPSHOT
- ../../pom.xml
-
- 4.0.0
- distributed-rate-limiter-integration-tests
- Twake Mail :: Integration Tests :: Rate Limiter :: Distributed
-
-
-
- ${project.groupId}
- distributed
- test
-
-
- ${project.groupId}
- rate-limiter-integration-tests-common
- test
-
-
- ${project.groupId}
- distributed
- test
- test-jar
-
-
- ${project.groupId}
- smtp-extensions
-
-
- ${project.groupId}
- tmail-guice-distributed
- test
- test-jar
-
-
- ${project.groupId}
- tmail-guice-jmap
- test
- test-jar
-
-
- ${project.groupId}
- webadmin-integration-tests-common
- test
-
-
- ${james.groupId}
- apache-james-backends-cassandra
- test
- test-jar
-
-
- ${james.groupId}
- apache-james-backends-opensearch
- test
- test-jar
-
-
- ${james.groupId}
- apache-james-backends-rabbitmq
- test
- test-jar
-
-
- ${james.groupId}
- apache-james-backends-redis
- test
- test-jar
-
-
- ${james.groupId}
- blob-s3
- test
- test-jar
-
-
- ${james.groupId}
- blob-s3-guice
- test
- test-jar
-
-
- ${james.groupId}
- james-server-cassandra-app
- test
- test-jar
-
-
- ${james.groupId}
- james-server-distributed-app
- test
- test-jar
-
-
- ${james.groupId}
- james-server-guice-common
- test
- test-jar
-
-
- ${james.groupId}
- james-server-guice-jmap
- test
- test-jar
-
-
- ${james.groupId}
- james-server-rate-limiter-redis
- test
- test-jar
-
-
-
-
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/java/com/linagora/tmail/integration/distributed/DistributedRateLimitingPlanIntegrationTest.java b/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/java/com/linagora/tmail/integration/distributed/DistributedRateLimitingPlanIntegrationTest.java
deleted file mode 100644
index bd678a70a9..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/java/com/linagora/tmail/integration/distributed/DistributedRateLimitingPlanIntegrationTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.integration.distributed;
-
-import org.apache.james.JamesServerBuilder;
-import org.apache.james.JamesServerExtension;
-import org.apache.james.backends.redis.RedisExtension;
-import org.apache.james.mailrepository.api.MailRepositoryUrl;
-import org.apache.james.modules.AwsS3BlobStoreExtension;
-import org.apache.james.rate.limiter.redis.RedisRateLimiterModule;
-import org.junit.jupiter.api.extension.RegisterExtension;
-
-import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
-import com.linagora.tmail.integration.RateLimitingPlanIntegrationContract;
-import com.linagora.tmail.james.app.CassandraExtension;
-import com.linagora.tmail.james.app.DistributedJamesConfiguration;
-import com.linagora.tmail.james.app.DistributedServer;
-import com.linagora.tmail.james.app.DockerOpenSearchExtension;
-import com.linagora.tmail.james.app.EventBusKeysChoice;
-import com.linagora.tmail.james.app.RabbitMQExtension;
-import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
-
-public class DistributedRateLimitingPlanIntegrationTest implements RateLimitingPlanIntegrationContract {
- private static final MailRepositoryUrl ERROR_REPOSITORY = MailRepositoryUrl.from("cassandra://var/mail/error/");
-
- @Override
- public MailRepositoryUrl getErrorRepository() {
- return ERROR_REPOSITORY;
- }
-
- @RegisterExtension
- static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
- DistributedJamesConfiguration.builder()
- .workingDirectory(tmpDir)
- .configurationFromClasspath()
- .blobStore(BlobStoreConfiguration.builder()
- .s3()
- .noSecondaryS3BlobStore()
- .disableCache()
- .deduplication()
- .noCryptoConfig()
- .disableSingleSave())
- .eventBusKeysChoice(EventBusKeysChoice.REDIS)
- .build())
- .extension(new DockerOpenSearchExtension())
- .extension(new CassandraExtension())
- .extension(new RabbitMQExtension())
- .extension(new AwsS3BlobStoreExtension())
- .extension(new RedisExtension())
- .server(configuration -> DistributedServer.createServer(configuration)
- .overrideWith(new RedisRateLimiterModule())
- .overrideWith(new LinagoraTestJMAPServerModule()))
- .build();
-}
diff --git a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/dnsservice.xml b/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/dnsservice.xml
deleted file mode 100644
index 6e4fbd2efb..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/dnsservice.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
- true
- false
- 50000
-
diff --git a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/domainlist.xml b/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/domainlist.xml
deleted file mode 100644
index fe17431a1e..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/domainlist.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
- false
- false
-
diff --git a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/fakemailrepositorystore.xml b/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/fakemailrepositorystore.xml
deleted file mode 100644
index 2d19a802da..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/fakemailrepositorystore.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
- file
-
-
-
-
-
diff --git a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/imapserver.xml b/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/imapserver.xml
deleted file mode 100644
index 3434dbce39..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/imapserver.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
-
-
-
-
- imapserver
- 0.0.0.0:0
- 200
-
-
- classpath://keystore
- james72laBalle
- org.bouncycastle.jce.provider.BouncyCastleProvider
-
- 0
- 0
- false
- false
-
-
- imapserver-ssl
- 0.0.0.0:0
- 200
-
-
- classpath://keystore
- james72laBalle
- org.bouncycastle.jce.provider.BouncyCastleProvider
-
- 0
- 0
- false
-
-
diff --git a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/keystore b/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/keystore
deleted file mode 100644
index 536a6c792b..0000000000
Binary files a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/keystore and /dev/null differ
diff --git a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/listeners.xml b/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/listeners.xml
deleted file mode 100644
index 003e852851..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/listeners.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
- org.apache.james.mailbox.cassandra.MailboxOperationLoggingListener
-
-
- org.apache.james.jmap.event.PopulateEmailQueryViewListener
- true
-
-
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/mailetcontainer.xml b/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/mailetcontainer.xml
deleted file mode 100644
index b347e4d72c..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/mailetcontainer.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
-
-
-
- postmaster@localhost
-
-
- 5
-
-
-
-
- transport
-
-
-
-
- TransitLimitations
-
-
-
-
- rrt-error
-
-
-
-
-
-
- local-delivery
-
-
- outgoing
- 5000, 100000, 500000
- 3
- 0
- 10
- true
- bounces
-
-
- error
-
-
-
-
- DeliveryLimitations
-
-
-
-
-
-
- false
-
-
-
-
-
- cassandra://var/mail/error
-
-
-
-
-
- cassandra://var/mail/rrt-error/
-
-
-
-
-
-
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/mailrepositorystore.xml b/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/mailrepositorystore.xml
deleted file mode 100644
index 626e0a2bad..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/mailrepositorystore.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
- cassandra
-
-
-
-
diff --git a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/pop3server.xml b/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/pop3server.xml
deleted file mode 100644
index 6defc24721..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/pop3server.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
diff --git a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/rabbitmq.properties b/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/rabbitmq.properties
deleted file mode 100644
index 0db6f33686..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/rabbitmq.properties
+++ /dev/null
@@ -1,20 +0,0 @@
-######################################################################
-# As a subpart of Twake Mail, this file is edited by Linagora. #
-# #
-# https://twake-mail.com/ #
-# https://linagora.com #
-# #
-# This file is subject to The Affero Gnu Public License #
-# version 3. #
-# #
-# https://www.gnu.org/licenses/agpl-3.0.en.html #
-# #
-# This program is distributed in the hope that it will be #
-# useful, but WITHOUT ANY WARRANTY; without even the implied #
-# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR #
-# PURPOSE. See the GNU Affero General Public License for #
-# more details. #
-######################################################################
-
-uri=amqp://james:james@rabbitmq_host:5672
-management.uri=http://james:james@rabbitmq_host:15672/api/
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/smtpserver.xml b/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/smtpserver.xml
deleted file mode 100644
index 78ba994d15..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/smtpserver.xml
+++ /dev/null
@@ -1,105 +0,0 @@
-
-
-
-
-
-
- smtpserver-global
- 0.0.0.0:0
- 200
-
- file://conf/keystore
- james72laBalle
- org.bouncycastle.jce.provider.BouncyCastleProvider
- SunX509
-
- 360
- 0
- 0
- false
- false
- 0
- true
- Apache JAMES awesome SMTP Server
-
-
-
-
- false
-
-
- smtpserver-TLS
- 0.0.0.0:0
- 200
-
- file://conf/keystore
- james72laBalle
- org.bouncycastle.jce.provider.BouncyCastleProvider
- SunX509
-
- 360
- 0
- 0
-
- true
-
- false
- 0
- true
- Apache JAMES awesome SMTP Server
-
-
-
-
- false
-
-
- smtpserver-authenticated
- 0.0.0.0:0
- 200
-
- file://conf/keystore
- james72laBalle
- org.bouncycastle.jce.provider.BouncyCastleProvider
- SunX509
-
- 360
- 0
- 0
-
- true
-
- false
- 0
- true
- Apache JAMES awesome SMTP Server
-
-
-
-
- false
-
-
-
-
diff --git a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/webadmin.properties b/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/webadmin.properties
deleted file mode 100644
index 78a176aabd..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/distributed-rate-limiter-integration-tests/src/test/resources/webadmin.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-# This template file can be used as example for James Server configuration
-# DO NOT USE IT AS SUCH AND ADAPT IT TO YOUR NEEDS
-
-# Read https://james.apache.org/server/config-webadmin.html for further details
-
-enabled=true
-port=0
-host=127.0.0.1
-
-extensions.routes=org.apache.james.webadmin.dropwizard.MetricsRoutes
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/pom.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/pom.xml
deleted file mode 100644
index a8472a21be..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/pom.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
- com.linagora.tmail
- integration-tests
- 1.0.0-SNAPSHOT
- ../../pom.xml
-
- 4.0.0
- postgres-rate-limiter-integration-tests
- Twake Mail :: Integration Tests :: Rate Limiter :: Postgres
-
-
-
- ${project.groupId}
- blobid-list
- test
-
-
- ${project.groupId}
- postgres
- test
-
-
- ${project.groupId}
- postgres
- test-jar
- test
-
-
- ${project.groupId}
- tmail-guice-distributed
- test-jar
- test
-
-
- ${project.groupId}
- tmail-guice-jmap
- test
- test-jar
-
-
- ${project.groupId}
- rate-limiter-integration-tests-common
- test
-
-
- ${james.groupId}
- apache-james-backends-postgres
- test-jar
- test
-
-
- ${james.groupId}
- apache-james-backends-rabbitmq
- test
- test-jar
-
-
- ${james.groupId}
- apache-james-backends-redis
- test
- test-jar
-
-
- org.testcontainers
- postgresql
- test
-
-
-
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRateLimitingPlanIntegrationTest.java b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRateLimitingPlanIntegrationTest.java
deleted file mode 100644
index 67a1db2ed9..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRateLimitingPlanIntegrationTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.integration.postgres;
-
-import org.apache.james.JamesServerBuilder;
-import org.apache.james.JamesServerExtension;
-import org.apache.james.PostgresJamesConfiguration;
-import org.apache.james.SearchConfiguration;
-import org.apache.james.backends.postgres.PostgresExtension;
-import org.apache.james.backends.redis.RedisExtension;
-import org.apache.james.mailrepository.api.MailRepositoryUrl;
-import org.apache.james.rate.limiter.redis.RedisRateLimiterModule;
-import org.junit.jupiter.api.extension.RegisterExtension;
-
-import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
-import com.linagora.tmail.integration.RateLimitingPlanIntegrationContract;
-import com.linagora.tmail.james.app.PostgresTmailConfiguration;
-import com.linagora.tmail.james.app.PostgresTmailServer;
-import com.linagora.tmail.james.app.RabbitMQExtension;
-import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
-
-public class PostgresRateLimitingPlanIntegrationTest implements RateLimitingPlanIntegrationContract {
- private static final MailRepositoryUrl ERROR_REPOSITORY = MailRepositoryUrl.from("postgres://var/mail/error/");
-
- @Override
- public MailRepositoryUrl getErrorRepository() {
- return ERROR_REPOSITORY;
- }
-
- @RegisterExtension
- static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
- PostgresTmailConfiguration.builder()
- .workingDirectory(tmpDir)
- .configurationFromClasspath()
- .searchConfiguration(SearchConfiguration.scanning())
- .eventBusImpl(PostgresJamesConfiguration.EventBusImpl.RABBITMQ)
- .blobStore(BlobStoreConfiguration.builder()
- .postgres()
- .disableCache()
- .deduplication()
- .noCryptoConfig()
- .disableSingleSave())
- .build())
- .extension(PostgresExtension.empty())
- .extension(new RabbitMQExtension())
- .extension(new RedisExtension())
- .server(configuration -> PostgresTmailServer.createServer(configuration)
- .overrideWith(new LinagoraTestJMAPServerModule())
- .overrideWith(new RedisRateLimiterModule()))
- .build();
-}
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/dnsservice.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/dnsservice.xml
deleted file mode 100644
index 6e4fbd2efb..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/dnsservice.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
- true
- false
- 50000
-
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/domainlist.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/domainlist.xml
deleted file mode 100644
index fe17431a1e..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/domainlist.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
- false
- false
-
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/fakemailrepositorystore.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/fakemailrepositorystore.xml
deleted file mode 100644
index 2d19a802da..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/fakemailrepositorystore.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
- file
-
-
-
-
-
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/imapserver.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/imapserver.xml
deleted file mode 100644
index 3434dbce39..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/imapserver.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
-
-
-
-
- imapserver
- 0.0.0.0:0
- 200
-
-
- classpath://keystore
- james72laBalle
- org.bouncycastle.jce.provider.BouncyCastleProvider
-
- 0
- 0
- false
- false
-
-
- imapserver-ssl
- 0.0.0.0:0
- 200
-
-
- classpath://keystore
- james72laBalle
- org.bouncycastle.jce.provider.BouncyCastleProvider
-
- 0
- 0
- false
-
-
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/keystore b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/keystore
deleted file mode 100644
index 536a6c792b..0000000000
Binary files a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/keystore and /dev/null differ
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/listeners.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/listeners.xml
deleted file mode 100644
index 74e79d927f..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/listeners.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
- org.apache.james.jmap.event.PopulateEmailQueryViewListener
- true
-
-
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/mailetcontainer.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/mailetcontainer.xml
deleted file mode 100644
index e06ea9b4d8..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/mailetcontainer.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
- postmaster@localhost
-
-
- 5
-
-
-
-
- transport
-
-
-
-
- TransitLimitations
-
-
-
-
- rrt-error
-
-
-
-
-
-
- local-delivery
-
-
- outgoing
- 5000, 100000, 500000
- 3
- 0
- 10
- true
- bounces
-
-
- error
-
-
-
-
- DeliveryLimitations
-
-
-
-
-
-
- false
-
-
-
-
-
- postgres://var/mail/error
-
-
-
-
-
- postgres://var/mail/rrt-error/
-
-
-
-
-
-
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/mailrepositorystore.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/mailrepositorystore.xml
deleted file mode 100644
index 573ec24ad3..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/mailrepositorystore.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
- postgres
-
-
-
-
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/managesieveserver.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/managesieveserver.xml
deleted file mode 100644
index f136a432b8..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/managesieveserver.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/pop3server.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/pop3server.xml
deleted file mode 100644
index 2a62f6f789..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/pop3server.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/rabbitmq.properties b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/rabbitmq.properties
deleted file mode 100644
index 25d0dd6a97..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/rabbitmq.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-uri=amqp://james:james@rabbitmq_host:5672
-management.uri=http://james:james@rabbitmq_host:15672/api/
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/smtpserver.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/smtpserver.xml
deleted file mode 100644
index 07178db100..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/smtpserver.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
-
-
-
- smtpserver-global
- 0.0.0.0:0
- 200
-
- file://conf/keystore
- james72laBalle
- org.bouncycastle.jce.provider.BouncyCastleProvider
- SunX509
-
- 360
- 0
- 0
- false
-
- never
- false
- true
-
- 0
- true
- Apache JAMES awesome SMTP Server
-
-
-
-
- false
-
-
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/usersrepository.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/usersrepository.xml
deleted file mode 100644
index f8c8a25872..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/usersrepository.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
- true
- SHA-1
-
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/webadmin.properties b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/webadmin.properties
deleted file mode 100644
index 3386a14238..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/webadmin.properties
+++ /dev/null
@@ -1,25 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-# This template file can be used as example for James Server configuration
-# DO NOT USE IT AS SUCH AND ADAPT IT TO YOUR NEEDS
-
-# Read https://james.apache.org/server/config-webadmin.html for further details
-
-enabled=true
-port=0
-host=127.0.0.1
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/rate-limiter-integration-tests-common/pom.xml b/tmail-backend/integration-tests/rate-limiter/rate-limiter-integration-tests-common/pom.xml
deleted file mode 100644
index 1d5e59a3d8..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/rate-limiter-integration-tests-common/pom.xml
+++ /dev/null
@@ -1,86 +0,0 @@
-
-
-
-
-
- integration-tests
- com.linagora.tmail
- 1.0.0-SNAPSHOT
- ../../pom.xml
-
- 4.0.0
-
- rate-limiter-integration-tests-common
- Twake Mail :: Integration Tests :: Rate Limiter :: Common
-
-
-
- ${james.groupId}
- james-server-guice-common
-
-
- ${james.groupId}
- james-server-guice-imap
-
-
- ${james.groupId}
- james-server-guice-mailbox
-
-
- ${james.groupId}
- james-server-guice-smtp
-
-
- ${james.groupId}
- james-server-guice-webadmin
-
-
- ${james.groupId}
- james-server-mailets-integration-testing
-
-
- ${james.groupId}
- james-server-testing
-
-
- ${james.groupId}
- james-server-webadmin-core
- test-jar
-
-
-
-
-
-
- net.alchim31.maven
- scala-maven-plugin
-
-
- io.github.evis
- scalafix-maven-plugin_2.13
-
- ${project.parent.parent.parent.basedir}/.scalafix.conf
-
-
-
-
-
-
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/rate-limiter-integration-tests-common/src/main/scala/com/linagora/tmail/integration/RateLimitingPlanIntegrationContract.scala b/tmail-backend/integration-tests/rate-limiter/rate-limiter-integration-tests-common/src/main/scala/com/linagora/tmail/integration/RateLimitingPlanIntegrationContract.scala
deleted file mode 100644
index f36a00fe50..0000000000
--- a/tmail-backend/integration-tests/rate-limiter/rate-limiter-integration-tests-common/src/main/scala/com/linagora/tmail/integration/RateLimitingPlanIntegrationContract.scala
+++ /dev/null
@@ -1,270 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.integration
-
-import java.util.stream.IntStream
-
-import com.linagora.tmail.integration.RateLimitingPlanIntegrationContract.{DOMAIN, RECIPIENT1, RECIPIENT2, RECIPIENT3, SENDER1, SENDER2}
-import io.restassured.RestAssured
-import io.restassured.RestAssured.{`given`, requestSpecification}
-import io.restassured.http.ContentType.JSON
-import org.apache.http.HttpStatus.{SC_CREATED, SC_NO_CONTENT}
-import org.apache.james.GuiceJamesServer
-import org.apache.james.core.{Domain, Username}
-import org.apache.james.mailets.configuration.Constants.{LOCALHOST_IP, PASSWORD, awaitAtMostOneMinute}
-import org.apache.james.mailrepository.api.MailRepositoryUrl
-import org.apache.james.modules.protocols.{ImapGuiceProbe, SmtpGuiceProbe}
-import org.apache.james.utils.TestIMAPClient.INBOX
-import org.apache.james.utils.{DataProbeImpl, MailRepositoryProbeImpl, SMTPMessageSender, TestIMAPClient, WebAdminGuiceProbe}
-import org.apache.james.webadmin.WebAdminUtils
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.jupiter.api.{BeforeEach, Test}
-
-object RateLimitingPlanIntegrationContract {
- val DOMAIN: Domain = Domain.of("domain.tld")
- val SENDER1: Username = Username.fromLocalPartWithDomain("sender1", DOMAIN)
- val SENDER2: Username = Username.fromLocalPartWithDomain("sender2", DOMAIN)
- val RECIPIENT1: Username = Username.fromLocalPartWithDomain("recipient1", DOMAIN)
- val RECIPIENT2: Username = Username.fromLocalPartWithDomain("recipient2", DOMAIN)
- val RECIPIENT3: Username = Username.fromLocalPartWithDomain("recipient3", DOMAIN)
-}
-
-trait RateLimitingPlanIntegrationContract {
-
- def getErrorRepository: MailRepositoryUrl
-
- @BeforeEach
- def setUp(server: GuiceJamesServer): Unit = {
- server.getProbe(classOf[DataProbeImpl])
- .fluent()
- .addDomain(DOMAIN.asString())
- .addUser(SENDER1.asString(), PASSWORD)
- .addUser(SENDER2.asString(), PASSWORD)
- .addUser(RECIPIENT1.asString(), PASSWORD)
- .addUser(RECIPIENT2.asString(), PASSWORD)
- .addUser(RECIPIENT3.asString(), PASSWORD)
-
- requestSpecification = WebAdminUtils.buildRequestSpecification(server.getProbe(classOf[WebAdminGuiceProbe]).getWebAdminPort)
- .build
- RestAssured.enableLoggingOfRequestAndResponseIfValidationFails()
- }
-
- private def messageSender(server: GuiceJamesServer): SMTPMessageSender =
- new SMTPMessageSender(DOMAIN.asString())
- .connect(LOCALHOST_IP, server.getProbe(classOf[SmtpGuiceProbe]).getSmtpPort)
-
- private def testImapClient(server: GuiceJamesServer): TestIMAPClient =
- new TestIMAPClient().connect(LOCALHOST_IP, server.getProbe(classOf[ImapGuiceProbe]).getImapPort)
-
- private def createNewPlan(): String = {
- val json: String =
- """{
- | "transitLimits": [
- | {
- | "name": "receivedMailsPerHour",
- | "periodInSeconds": 3600,
- | "count": 1,
- | "size": 2048
- | }
- | ],
- | "relayLimits": [
- | {
- | "name": "relayMailsPerHour",
- | "periodInSeconds": 3600,
- | "count": 1,
- | "size": 2048
- | }
- | ],
- | "deliveryLimits": [
- | {
- | "name": "deliveryMailsPerHour",
- | "periodInSeconds": 3600,
- | "count": 1,
- | "size": 2048
- | }
- | ]
- |}""".stripMargin
-
- createNewPlan(json, "planTest")
- }
-
- private def createNewPlan(limitJson: String, planName: String): String =
- given().basePath("/rate-limit-plans/" + planName)
- .body(limitJson)
- .when()
- .post()
- .`then`()
- .statusCode(SC_CREATED)
- .contentType(JSON)
- .extract()
- .jsonPath()
- .get("planId").asInstanceOf[String]
-
-
- private def attachPlanToUser(planId: String, username: Username): Unit =
- given().basePath(String.format("/users/%s/rate-limit-plans/%s", username.asString(), planId))
- .put()
- .`then`()
- .statusCode(SC_NO_CONTENT)
-
- @Test
- def senderShouldSentEmailWhenRateLimitPlanIsAcceptable(server: GuiceJamesServer): Unit = {
- attachPlanToUser(createNewPlan(), SENDER1)
-
- messageSender(server).authenticate(SENDER1.asString(), PASSWORD)
- .sendMessageWithHeaders(SENDER1.asString, RECIPIENT1.asString, "From: " + SENDER1.asString() + "\r\n\r\nsubject: test123\r\rcontent 123\r.\r")
-
- val imapClient: TestIMAPClient = testImapClient(server)
- imapClient.login(RECIPIENT1.asString(), PASSWORD)
- .select(INBOX)
- .awaitMessage(awaitAtMostOneMinute)
-
- assertThat(imapClient.readFirstMessage).contains("content 123")
- }
-
- @Test
- def senderShouldNotSentEmailWhenRateLimitPlanIsExceeded(server: GuiceJamesServer): Unit = {
- attachPlanToUser(createNewPlan(), SENDER1)
-
- // accept
- messageSender(server).authenticate(SENDER1.asString(), PASSWORD)
- .sendMessageWithHeaders(SENDER1.asString, RECIPIENT1.asString, "From: " + SENDER1.asString() + "\r\n\r\nsubject: test1\r\rcontent 1\r.\r")
-
- testImapClient(server).login(RECIPIENT1.asString(), PASSWORD)
- .select(INBOX)
- .awaitMessage(awaitAtMostOneMinute)
-
- // exceed
- messageSender(server).authenticate(SENDER1.asString(), PASSWORD)
- .sendMessageWithHeaders(SENDER1.asString, RECIPIENT2.asString, "From: " + SENDER1.asString() + "\r\n\r\nsubject: test2\r\rcontent 2\r.\r")
-
- awaitAtMostOneMinute.until(() => server.getProbe(classOf[MailRepositoryProbeImpl])
- .getRepositoryMailCount(getErrorRepository) == 1)
-
- assertThat(testImapClient(server)
- .login(RECIPIENT2, PASSWORD)
- .getMessageCount(INBOX))
- .isEqualTo(0)
- }
-
- @Test
- def recipientShouldReceivedEmailWhenRateLimitPlanIsAcceptable(server: GuiceJamesServer): Unit = {
- attachPlanToUser(createNewPlan(), RECIPIENT1)
-
- messageSender(server).authenticate(SENDER1.asString(), PASSWORD)
- .sendMessageWithHeaders(SENDER1.asString, RECIPIENT1.asString, "From: " + SENDER1.asString() + "\r\n\r\nsubject: test123\r\rcontent 123\r.\r")
-
- val imapClient: TestIMAPClient = testImapClient(server)
- imapClient.login(RECIPIENT1.asString(), PASSWORD)
- .select(INBOX)
- .awaitMessage(awaitAtMostOneMinute)
-
- assertThat(imapClient.readFirstMessage).contains("content 123")
- }
-
- @Test
- def recipientShouldNotReceivedEmailWhenRateLimitPlanIsExceeded(server: GuiceJamesServer): Unit = {
- attachPlanToUser(createNewPlan(), RECIPIENT1)
- // accept
- messageSender(server).authenticate(SENDER1.asString(), PASSWORD)
- .sendMessageWithHeaders(SENDER1.asString, RECIPIENT1.asString, "From: " + SENDER1.asString() + "\r\n\r\nsubject: test1\r\rcontent 1\r.\r")
-
- testImapClient(server).login(RECIPIENT1.asString(), PASSWORD)
- .select(INBOX)
- .awaitMessage(awaitAtMostOneMinute)
-
- // exceed
- messageSender(server).authenticate(SENDER1.asString(), PASSWORD)
- .sendMessageWithHeaders(SENDER1.asString, RECIPIENT1.asString, "From: " + SENDER1.asString() + "\r\n\r\nsubject: test2\r\rcontent 2\r.\r")
-
- awaitAtMostOneMinute.until(() => server.getProbe(classOf[MailRepositoryProbeImpl])
- .getRepositoryMailCount(getErrorRepository) == 1)
-
- assertThat(testImapClient(server)
- .login(RECIPIENT1, PASSWORD)
- .getMessageCount(INBOX))
- .isEqualTo(1)
- }
-
- @Test
- def differentPlansApplyToDifferentUsersShouldRateLimitPerSender(server: GuiceJamesServer): Unit = {
- val planA: String = createNewPlan(
- """{
- | "transitLimits": [
- | {
- | "name": "receivedMailsPerHour",
- | "periodInSeconds": 3600,
- | "count": 1,
- | "size": 2048
- | }
- | ]
- |}""".stripMargin, "planA")
-
- val countLimitPlanB: Int = 5
- val planB: String = createNewPlan(
- s"""{
- | "transitLimits": [
- | {
- | "name": "receivedMailsPerHour",
- | "periodInSeconds": 3600,
- | "count": $countLimitPlanB,
- | "size": 2000048
- | }
- | ]
- |}""".stripMargin, "planB")
-
- attachPlanToUser(planA, SENDER1)
- attachPlanToUser(planB, SENDER2)
-
- // Checking sender1 with planA
- messageSender(server).authenticate(SENDER1.asString(), PASSWORD)
- .sendMessageWithHeaders(SENDER1.asString, RECIPIENT1.asString, "From: " + SENDER1.asString() + "\r\n\r\nsubject: test1\r\rcontent 1\r.\r")
-
- testImapClient(server).login(RECIPIENT1.asString(), PASSWORD)
- .select(INBOX)
- .awaitMessage(awaitAtMostOneMinute)
-
- messageSender(server).authenticate(SENDER1.asString(), PASSWORD)
- .sendMessageWithHeaders(SENDER1.asString, RECIPIENT2.asString, "From: " + SENDER1.asString() + "\r\n\r\nsubject: test2\r\rcontent 2\r.\r")
-
- awaitAtMostOneMinute.until(() => server.getProbe(classOf[MailRepositoryProbeImpl])
- .getRepositoryMailCount(getErrorRepository) == 1)
-
- assertThat(testImapClient(server)
- .login(RECIPIENT2, PASSWORD)
- .getMessageCount(INBOX))
- .isEqualTo(0)
-
- // Checking sender2 with planB
- IntStream.range(0, countLimitPlanB)
- .forEach(index =>
- messageSender(server).authenticate(SENDER2.asString(), PASSWORD)
- .sendMessageWithHeaders(SENDER2.asString, RECIPIENT3.asString, s"""From: ${SENDER2.asString}\r\n\r\nsubject: test3+$index\r\rcontent 3\r.\r"""))
-
- awaitAtMostOneMinute.until(() => testImapClient(server)
- .login(RECIPIENT3, PASSWORD)
- .getMessageCount(INBOX) == countLimitPlanB)
-
- messageSender(server).authenticate(SENDER2.asString(), PASSWORD)
- .sendMessageWithHeaders(SENDER2.asString, RECIPIENT3.asString, s"""From: ${SENDER2.asString}\r\n\r\nsubject: test4\r\rcontent 4\r.\r""")
- awaitAtMostOneMinute.until(() => server.getProbe(classOf[MailRepositoryProbeImpl])
- .getRepositoryMailCount(getErrorRepository) == 2)
- }
-
-}
diff --git a/tmail-backend/integration-tests/webadmin/distributed-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/distributed/DistributedUsernameChangeIntegrationTest.java b/tmail-backend/integration-tests/webadmin/distributed-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/distributed/DistributedUsernameChangeIntegrationTest.java
index d17d112e08..cc6b28119b 100644
--- a/tmail-backend/integration-tests/webadmin/distributed-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/distributed/DistributedUsernameChangeIntegrationTest.java
+++ b/tmail-backend/integration-tests/webadmin/distributed-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/distributed/DistributedUsernameChangeIntegrationTest.java
@@ -41,7 +41,6 @@
import com.google.inject.multibindings.Multibinder;
import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
import com.linagora.tmail.integration.UsernameChangeIntegrationContract;
-import com.linagora.tmail.integration.probe.RateLimitingProbe;
import com.linagora.tmail.james.app.CassandraExtension;
import com.linagora.tmail.james.app.DistributedEncryptedMailboxModule;
import com.linagora.tmail.james.app.DistributedJamesConfiguration;
@@ -94,9 +93,6 @@ public class DistributedUsernameChangeIntegrationTest extends UsernameChangeInte
.overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class)
.addBinding()
.to(JmapGuiceKeystoreManagerProbe.class))
- .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class)
- .addBinding()
- .to(RateLimitingProbe.class))
.overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class)
.addBinding()
.to(JmapGuiceLabelProbe.class))
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresUsernameChangeIntegrationTest.java b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresUsernameChangeIntegrationTest.java
index b1d4967214..891fa8c84b 100644
--- a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresUsernameChangeIntegrationTest.java
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresUsernameChangeIntegrationTest.java
@@ -45,7 +45,6 @@
import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
import com.linagora.tmail.integration.UsernameChangeIntegrationContract;
-import com.linagora.tmail.integration.probe.RateLimitingProbe;
import com.linagora.tmail.james.app.DockerOpenSearchExtension;
import com.linagora.tmail.james.app.PostgresEncryptedMailboxModule;
import com.linagora.tmail.james.app.PostgresTmailConfiguration;
@@ -89,7 +88,6 @@ public class PostgresUsernameChangeIntegrationTest extends UsernameChangeIntegra
.overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceContactAutocompleteProbe.class))
.overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapSettingsProbe.class))
.overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceLabelProbe.class))
- .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(RateLimitingProbe.class))
.overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceKeystoreManagerProbe.class)))
.extension(PostgresExtension.empty())
.extension(new ClockExtension())
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresWebAdminBase.java b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresWebAdminBase.java
index 64de2a7e60..f2b027fb45 100644
--- a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresWebAdminBase.java
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresWebAdminBase.java
@@ -35,7 +35,6 @@
import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
-import com.linagora.tmail.integration.probe.RateLimitingProbe;
import com.linagora.tmail.james.app.PostgresTmailConfiguration;
import com.linagora.tmail.james.app.PostgresTmailServer;
import com.linagora.tmail.james.common.probe.JmapGuiceContactAutocompleteProbe;
@@ -67,7 +66,6 @@ public class PostgresWebAdminBase {
.overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceContactAutocompleteProbe.class))
.overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapSettingsProbe.class))
.overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceLabelProbe.class))
- .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(RateLimitingProbe.class))
.overrideWith(overrideWithModule))
.extension(PostgresExtension.empty())
.extension(new ClockExtension());
diff --git a/tmail-backend/integration-tests/webadmin/webadmin-integration-tests-common/pom.xml b/tmail-backend/integration-tests/webadmin/webadmin-integration-tests-common/pom.xml
index da43e83df5..1605fbf976 100644
--- a/tmail-backend/integration-tests/webadmin/webadmin-integration-tests-common/pom.xml
+++ b/tmail-backend/integration-tests/webadmin/webadmin-integration-tests-common/pom.xml
@@ -38,15 +38,6 @@
${project.groupId}
team-mailboxes
-
- ${project.groupId}
- tmail-rate-limiter-api
-
-
- ${project.groupId}
- tmail-rate-limiter-api
- test-jar
-
${james.groupId}
james-server-guice-common
diff --git a/tmail-backend/integration-tests/webadmin/webadmin-integration-tests-common/src/main/java/com/linagora/tmail/integration/UsernameChangeIntegrationContract.java b/tmail-backend/integration-tests/webadmin/webadmin-integration-tests-common/src/main/java/com/linagora/tmail/integration/UsernameChangeIntegrationContract.java
index ba857b2ce4..74cf2b1b21 100644
--- a/tmail-backend/integration-tests/webadmin/webadmin-integration-tests-common/src/main/java/com/linagora/tmail/integration/UsernameChangeIntegrationContract.java
+++ b/tmail-backend/integration-tests/webadmin/webadmin-integration-tests-common/src/main/java/com/linagora/tmail/integration/UsernameChangeIntegrationContract.java
@@ -42,7 +42,6 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import com.linagora.tmail.integration.probe.RateLimitingProbe;
import com.linagora.tmail.james.common.probe.JmapGuiceContactAutocompleteProbe;
import com.linagora.tmail.james.common.probe.JmapGuiceKeystoreManagerProbe;
import com.linagora.tmail.james.common.probe.JmapGuiceLabelProbe;
@@ -52,8 +51,6 @@
import com.linagora.tmail.james.jmap.model.DisplayName;
import com.linagora.tmail.james.jmap.model.Label;
import com.linagora.tmail.james.jmap.model.LabelCreationRequest;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanId;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanRepositoryContract;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
@@ -138,28 +135,6 @@ void shouldMigratePGPPublicKeys(GuiceJamesServer server) throws Exception {
.isEmpty();
}
- @Test
- void shouldAdaptRateLimiting(GuiceJamesServer server) {
- RateLimitingProbe rateLimitingProbe = server.getProbe(RateLimitingProbe.class);
- RateLimitingPlanId planId = rateLimitingProbe.createPlan(RateLimitingPlanRepositoryContract.CREATION_REQUEST()).id();
-
- rateLimitingProbe.applyPlan(ALICE, planId);
-
- String taskId = webAdminApi
- .queryParam("action", "rename")
- .post("/users/" + ALICE.asString() + "/rename/" + BOB.asString())
- .jsonPath()
- .get("taskId");
-
- webAdminApi.get("/tasks/" + taskId + "/await")
- .then()
- .statusCode(HttpStatus.SC_OK)
- .body("additionalInformation.status.RateLimitingPlanUsernameChangeTaskStep", Matchers.is("DONE"));
-
- assertThat(rateLimitingProbe.listUsersOfAPlan(planId))
- .containsExactly(BOB);
- }
-
@Test
void shouldMigrateLabels(GuiceJamesServer server) {
JmapGuiceLabelProbe labelProbe = server.getProbe(JmapGuiceLabelProbe.class);
diff --git a/tmail-backend/integration-tests/webadmin/webadmin-integration-tests-common/src/main/java/com/linagora/tmail/integration/probe/RateLimitingProbe.java b/tmail-backend/integration-tests/webadmin/webadmin-integration-tests-common/src/main/java/com/linagora/tmail/integration/probe/RateLimitingProbe.java
deleted file mode 100644
index 90115f05fe..0000000000
--- a/tmail-backend/integration-tests/webadmin/webadmin-integration-tests-common/src/main/java/com/linagora/tmail/integration/probe/RateLimitingProbe.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.integration.probe;
-
-import java.util.List;
-
-import jakarta.inject.Inject;
-
-import org.apache.james.core.Username;
-import org.apache.james.utils.GuiceProbe;
-
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlan;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanCreateRequest;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanId;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanRepository;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanUserRepository;
-
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-
-public class RateLimitingProbe implements GuiceProbe {
- private final RateLimitingPlanRepository planRepository;
- private final RateLimitingPlanUserRepository planUserRepository;
-
- @Inject
- public RateLimitingProbe(RateLimitingPlanRepository planRepository, RateLimitingPlanUserRepository planUserRepository) {
- this.planRepository = planRepository;
- this.planUserRepository = planUserRepository;
- }
-
- public RateLimitingPlan createPlan(RateLimitingPlanCreateRequest request) {
- return Mono.from(planRepository.create(request)).block();
- }
-
- public void applyPlan(Username username, RateLimitingPlanId planId) {
- Mono.from(planUserRepository.applyPlan(username, planId)).block();
- }
-
- public List listUsersOfAPlan(RateLimitingPlanId planId) {
- return Flux.from(planUserRepository.listUsers(planId))
- .collectList()
- .block();
- }
-}
diff --git a/tmail-backend/mailets/pom.xml b/tmail-backend/mailets/pom.xml
index f90697ab1c..f27218cbcf 100644
--- a/tmail-backend/mailets/pom.xml
+++ b/tmail-backend/mailets/pom.xml
@@ -116,17 +116,6 @@
james-server-queue-api
test
-
- ${james.groupId}
- james-server-rate-limiter-redis
- test
-
-
- ${james.groupId}
- james-server-rate-limiter-redis
- test
- test-jar
-
${james.groupId}
james-server-testing
diff --git a/tmail-backend/mailets/src/main/scala/com/linagora/tmail/mailets/EnforceRateLimitingPlan.scala b/tmail-backend/mailets/src/main/scala/com/linagora/tmail/mailets/EnforceRateLimitingPlan.scala
deleted file mode 100644
index 8f77795ae4..0000000000
--- a/tmail-backend/mailets/src/main/scala/com/linagora/tmail/mailets/EnforceRateLimitingPlan.scala
+++ /dev/null
@@ -1,166 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.mailets
-
-import java.time.Duration
-import java.time.temporal.ChronoUnit
-
-import com.google.common.annotations.VisibleForTesting
-import com.google.common.base.Preconditions
-import com.google.common.collect.ImmutableList
-import com.linagora.tmail.mailets.EnforceRateLimitingPlan.{ACCEPTABLE_OPERATIONS, LIMIT_PER_RECIPIENTS_OPERATIONS, LIMIT_PER_SENDER_OPERATIONS}
-import com.linagora.tmail.rate.limiter.api.OperationLimitations.{DELIVERY_LIMITATIONS_NAME, RELAY_LIMITATIONS_NAME, TRANSIT_LIMITATIONS_NAME}
-import com.linagora.tmail.rate.limiter.api.{CacheRateLimitingPlan, OperationLimitations, RateLimitingPlanId, RateLimitingPlanNotFoundException, RateLimitingPlanRepository, RateLimitingPlanUserRepository}
-import jakarta.inject.Inject
-import org.apache.james.core.{MailAddress, Username}
-import org.apache.james.lifecycle.api.LifecycleUtil
-import org.apache.james.metrics.api.GaugeRegistry
-import org.apache.james.rate.limiter.api.{AcceptableRate, RateExceeded, RateLimiterFactory, RateLimitingResult, Rule, Rules}
-import org.apache.james.transport.mailets.ConfigurationOps.DurationOps
-import org.apache.james.transport.mailets.KeyPrefix
-import org.apache.james.util.DurationParser
-import org.apache.mailet.Mail
-import org.apache.mailet.base.GenericMailet
-import reactor.core.scala.publisher.{SFlux, SMono}
-
-import scala.jdk.CollectionConverters._
-import scala.util.Using
-
-object EnforceRateLimitingPlan {
- val LIMIT_PER_SENDER_OPERATIONS: Set[String] = Set(TRANSIT_LIMITATIONS_NAME, RELAY_LIMITATIONS_NAME)
- val LIMIT_PER_RECIPIENTS_OPERATIONS: Set[String] = Set(DELIVERY_LIMITATIONS_NAME)
- val ACCEPTABLE_OPERATIONS: Set[String] = LIMIT_PER_SENDER_OPERATIONS ++ LIMIT_PER_RECIPIENTS_OPERATIONS
-}
-
-class EnforceRateLimitingPlan @Inject()(planRepository: RateLimitingPlanRepository,
- planUserRepository: RateLimitingPlanUserRepository,
- rateLimiterFactory: RateLimiterFactory,
- gaugeRegistry: GaugeRegistry) extends GenericMailet {
-
- private var operationLimitation: String = _
- private var exceededProcessor: String = _
- private var rateLimitSender: Boolean = _
- private var rateLimitRecipient: Boolean = _
- private var planRateLimiterResolver: PlanRateLimiterResolver = _
- private var planStore: RateLimitingPlanRepository = _
-
- override def init(): Unit = {
- exceededProcessor = getInitParameter("exceededProcessor", Mail.ERROR)
- operationLimitation = getInitParameter("operationLimitation")
- rateLimitSender = LIMIT_PER_SENDER_OPERATIONS.contains(operationLimitation)
- rateLimitRecipient = LIMIT_PER_RECIPIENTS_OPERATIONS.contains(operationLimitation)
- Preconditions.checkArgument(operationLimitation != null && operationLimitation.nonEmpty, "`operationLimitation` is compulsory".asInstanceOf[Object])
- Preconditions.checkArgument(ACCEPTABLE_OPERATIONS.contains(operationLimitation), s"`operationLimitation` must be [${String.join(", ", ACCEPTABLE_OPERATIONS.asJava)}]".asInstanceOf[Object])
-
- planStore = parseCacheExpiration()
- .map(duration => new CacheRateLimitingPlan(planRepository, duration, gaugeRegistry, Some(operationLimitation)))
- .getOrElse(planRepository)
-
- planRateLimiterResolver = PlanRateLimiterResolver(
- rateLimiterFactory = rateLimiterFactory,
- keyPrefix = Option(getInitParameter("keyPrefix")).map(KeyPrefix),
- precision = getMailetConfig.getDuration("precision"))
- }
-
- @VisibleForTesting
- def parseCacheExpiration(): Option[Duration] = Option(getInitParameter("cacheExpiration"))
- .map(string => DurationParser.parse(string, ChronoUnit.SECONDS))
- .map(duration => {
- Preconditions.checkArgument(!duration.isZero && !duration.isNegative, "`cacheExpiration` must be positive".asInstanceOf[Object])
- duration
- })
-
- override def service(mail: Mail): Unit =
- if (rateLimitSender) {
- mail.getMaybeSender.asOptional()
- .ifPresent(sender => applyRateLimiterPerSender(mail, Username.fromMailAddress(sender)))
- } else if (rateLimitRecipient && !mail.getRecipients.isEmpty) {
- applyRateLimiterPerRecipient(mail)
- }
-
- private def applyRateLimiter(mail: Mail, username: Username): SMono[RateLimitingResult] =
- SMono.fromPublisher(planUserRepository.getPlanByUser(username))
- .flatMap(retrieveRateLimiter)
- .flatMapMany(SFlux.fromIterable)
- .flatMap(_.rateLimit(username, mail))
- .fold[RateLimitingResult](AcceptableRate)((a, b) => a.merge(b))
- .onErrorResume {
- case _: RateLimitingPlanNotFoundException => SMono.just[RateLimitingResult](AcceptableRate)
- }
-
- private def retrieveRateLimiter(id: RateLimitingPlanId): SMono[Seq[TmailPlanRateLimiter]] =
- SMono.fromPublisher(planStore.get(id))
- .flatMapIterable(_.operationLimitations)
- .filter(_.asString().equals(operationLimitation))
- .next()
- .map(planRateLimiterResolver.extractRateLimiters(id, _))
-
- private def applyRateLimiterPerSender(mail: Mail, username: Username): Unit = {
- val rateLimitingResult: RateLimitingResult = applyRateLimiter(mail, username).block()
- if (rateLimitingResult.equals(RateExceeded)) {
- mail.setState(exceededProcessor)
- }
- }
-
- private def applyRateLimiterPerRecipient(mail: Mail): Unit = {
- val rateLimitResults: Seq[(MailAddress, RateLimitingResult)] = SFlux.fromIterable(mail.getRecipients.asScala)
- .flatMap(recipient => applyRateLimiter(mail, Username.fromMailAddress(recipient))
- .map(rateLimitingResult => recipient -> rateLimitingResult))
- .collectSeq()
- .block()
-
- val rateLimitedRecipients: Seq[MailAddress] = rateLimitResults.filter(_._2.equals(RateExceeded)).map(_._1)
- val acceptableRecipients: Seq[MailAddress] = rateLimitResults.filter(_._2.equals(AcceptableRate)).map(_._1)
-
- (acceptableRecipients, rateLimitedRecipients) match {
- case (acceptable, _) if acceptable.isEmpty => mail.setState(exceededProcessor)
- case (_, exceeded) if exceeded.isEmpty => // do nothing
- case _ =>
- mail.setRecipients(ImmutableList.copyOf(acceptableRecipients.asJava))
-
- Using(mail.duplicate())(newMail => {
- newMail.setRecipients(ImmutableList.copyOf(rateLimitedRecipients.asJava))
- getMailetContext.sendMail(newMail, exceededProcessor)
- })(LifecycleUtil.dispose(_))
- }
- }
-}
-
-case class PlanRateLimiterResolver(rateLimiterFactory: RateLimiterFactory,
- keyPrefix: Option[KeyPrefix],
- precision: Option[Duration]) {
-
- def extractRateLimiters(planId: RateLimitingPlanId, operationLimitations: OperationLimitations): Seq[TmailPlanRateLimiter] =
- aggregateRule(operationLimitations)
- .map(pair => TmailPlanRateLimiter(
- rateLimiter = rateLimiterFactory.withSpecification(pair._2, precision),
- limitTypeName = pair._1,
- keyPrefix = keyPrefix,
- planId = planId,
- operationLimitationName = operationLimitations.asString()))
- .toSeq
-
- private def aggregateRule(operationLimitations: OperationLimitations): Map[String, Rules] =
- operationLimitations.rateLimitations()
- .flatMap(rateLimitation => rateLimitation.limits.value
- .map(limit => limit -> rateLimitation.period))
- .groupBy(_._1.asString())
- .map(pair => pair._1 -> Rules(pair._2.map(pair => Rule(pair._1.allowedQuantity(), pair._2))))
-
-}
diff --git a/tmail-backend/mailets/src/main/scala/com/linagora/tmail/mailets/TmailPlanRateLimiter.scala b/tmail-backend/mailets/src/main/scala/com/linagora/tmail/mailets/TmailPlanRateLimiter.scala
deleted file mode 100644
index be3b5ca9b4..0000000000
--- a/tmail-backend/mailets/src/main/scala/com/linagora/tmail/mailets/TmailPlanRateLimiter.scala
+++ /dev/null
@@ -1,71 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.mailets
-
-import com.linagora.tmail.rate.limiter.api.LimitTypes.{COUNT, SIZE}
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanId
-import eu.timepit.refined.auto._
-import org.apache.james.core.Username
-import org.apache.james.rate.limiter.api.Increment.Increment
-import org.apache.james.rate.limiter.api.{AcceptableRate, Increment, RateLimiter, RateLimitingKey, RateLimitingResult}
-import org.apache.james.transport.mailets.KeyPrefix
-import org.apache.mailet.Mail
-import org.reactivestreams.Publisher
-import reactor.core.scala.publisher.SMono
-
-case class TmailPlanRateLimiter(rateLimiter: RateLimiter,
- keyPrefix: Option[KeyPrefix] = None,
- limitTypeName: String,
- planId: RateLimitingPlanId,
- operationLimitationName: String) {
-
- def rateLimit(username: Username, mail: Mail): Publisher[RateLimitingResult] =
- LimitTypeUtils.extractQuantity(mail, limitTypeName)
- .map(increment => rateLimiter.rateLimit(
- key = RateLimitingPlanKey(
- keyPrefix,
- limitTypeName,
- planId,
- operationLimitationName,
- username),
- increaseQuantity = increment))
- .getOrElse(SMono.just[RateLimitingResult](AcceptableRate))
-}
-
-case class RateLimitingPlanKey(keyPrefix: Option[KeyPrefix],
- limitTypeName: String,
- planId: RateLimitingPlanId,
- operationLimitationName: String,
- username: Username) extends RateLimitingKey {
- override def asString(): String = s"${
- keyPrefix.map(prefix => prefix.value + "_")
- .getOrElse("")
- }${planId.value.toString}_${operationLimitationName}_${limitTypeName}_${username.asString()}"
-}
-
-object LimitTypeUtils {
- def extractQuantity(mail: Mail, limitTypeName: String): Option[Increment] =
- limitTypeName match {
- case COUNT => Some(1)
- case SIZE => Increment.validate(mail.getMessageSize.toInt) match {
- case Right(size) => Some(size)
- case Left(e) => throw e
- }
- }
-}
diff --git a/tmail-backend/mailets/src/test/scala/com/linagora/tmail/mailets/EnforceRateLimitingPlanTest.scala b/tmail-backend/mailets/src/test/scala/com/linagora/tmail/mailets/EnforceRateLimitingPlanTest.scala
deleted file mode 100644
index e04cdde327..0000000000
--- a/tmail-backend/mailets/src/test/scala/com/linagora/tmail/mailets/EnforceRateLimitingPlanTest.scala
+++ /dev/null
@@ -1,728 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.mailets
-
-import java.time.Duration
-import java.util
-import java.util.stream.IntStream
-
-import com.codahale.metrics.MetricRegistry
-import com.linagora.tmail.mailets.EnforceRateLimitingPlanTest.{USER1, USER2}
-import com.linagora.tmail.rate.limiter.api.memory.MemoryRateLimitingPlanUserRepository
-import com.linagora.tmail.rate.limiter.api.{DeliveryLimitations, InMemoryRateLimitingPlanRepository, LimitTypes, OperationLimitationsType, RateLimitation, RateLimitingPlan, RateLimitingPlanCreateRequest, RateLimitingPlanRepository, RateLimitingPlanUserRepository, RelayLimitations, TransitLimitations}
-import eu.timepit.refined.auto._
-import org.apache.james.backends.redis.{DockerRedis, RedisClientFactory, RedisExtension, StandaloneRedisConfiguration}
-import org.apache.james.core.Username
-import org.apache.james.metrics.api.NoopGaugeRegistry
-import org.apache.james.metrics.dropwizard.DropWizardGaugeRegistry
-import org.apache.james.rate.limiter.redis.RedisRateLimiterFactory
-import org.apache.james.server.core.filesystem.FileSystemImpl
-import org.apache.james.util.Size.parse
-import org.apache.mailet.base.test.{FakeMail, FakeMailContext, FakeMailetConfig}
-import org.apache.mailet.{Mail, MailetConfig}
-import org.assertj.core.api.Assertions.{assertThat, assertThatCode, assertThatThrownBy}
-import org.assertj.core.api.SoftAssertions.assertSoftly
-import org.junit.jupiter.api.extension.ExtendWith
-import org.junit.jupiter.api.{BeforeEach, Nested, Test}
-import reactor.core.scala.publisher.SMono
-
-object EnforceRateLimitingPlanTest {
- val USER1: Username = Username.of("user1@domain.tld")
- val USER2: Username = Username.of("user2@domain.tld")
-}
-
-@ExtendWith(Array(classOf[RedisExtension]))
-class EnforceRateLimitingPlanTest {
-
- var rateLimitationPlanRepository: RateLimitingPlanRepository = _
- var rateLimitingPlanUserRepository: RateLimitingPlanUserRepository = _
- var redisRateLimiterFactory: RedisRateLimiterFactory = _
-
- def testee(mailetConfig: MailetConfig): EnforceRateLimitingPlan = {
- val mailet: EnforceRateLimitingPlan = new EnforceRateLimitingPlan(rateLimitationPlanRepository, rateLimitingPlanUserRepository, redisRateLimiterFactory,
- new NoopGaugeRegistry())
- mailet.init(mailetConfig)
- mailet
- }
-
- @BeforeEach
- def setup(redis: DockerRedis): Unit = {
- val redisConfiguration: StandaloneRedisConfiguration = StandaloneRedisConfiguration.from(redis.redisURI().toString)
- rateLimitationPlanRepository = new InMemoryRateLimitingPlanRepository
- rateLimitingPlanUserRepository = new MemoryRateLimitingPlanUserRepository
- redisRateLimiterFactory = new RedisRateLimiterFactory(redisConfiguration, new RedisClientFactory(FileSystemImpl.forTesting, redisConfiguration))
-
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(rateLimitationPlanRepository.create(RateLimitingPlanCreateRequest(
- name = "PaidPlan",
- operationLimitations = OperationLimitationsType.liftOrThrow(Seq(
- TransitLimitations(Seq(
- RateLimitation(name = "transit limit 1",
- period = Duration.ofSeconds(5),
- limits = LimitTypes.from(Map(("count", 1)))))),
- RelayLimitations(Seq(
- RateLimitation(name = "relay limit 1",
- period = Duration.ofSeconds(5),
- limits = LimitTypes.from(Map(("count", 1)))))),
- DeliveryLimitations(Seq(
- RateLimitation(name = "delivery limit 1",
- period = Duration.ofSeconds(5),
- limits = LimitTypes.from(Map(("count", 1)))))))))))
- .block()
-
- SMono.fromPublisher(rateLimitingPlanUserRepository.applyPlan(USER1, rateLimitingPlan.id)).block()
- SMono.fromPublisher(rateLimitingPlanUserRepository.applyPlan(USER2, rateLimitingPlan.id)).block()
- }
-
- @Nested
- class PerSenderLimit {
- var mailetConfig: FakeMailetConfig.Builder = _
- var mailet: EnforceRateLimitingPlan = _
-
- @BeforeEach
- def setUpSender(): Unit = {
- mailetConfig = FakeMailetConfig.builder()
- .mailetName("EnforceRateLimitingPlan")
- .setProperty("operationLimitation", "TransitLimitations")
- .setProperty("precision", "1s")
- mailet = testee(mailetConfig.build())
- }
-
- @Test
- def shouldFlowToTheIntendedProcessor(): Unit = {
- val mailet: EnforceRateLimitingPlan = testee(mailetConfig
- .setProperty("exceededProcessor", "tooMuchMails")
- .build())
-
- val mail1: Mail = FakeMail.builder()
- .name("mail1")
- .sender(USER1.asString())
- .recipients("rcpt1@linagora.com")
- .state("transport")
- .build()
-
- val mail2: Mail = FakeMail.builder()
- .name("mail2")
- .sender(USER1.asString())
- .recipients("rcpt2@linagora.com")
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("tooMuchMails")
- })
- }
-
- @Test
- def shouldNotAppliedToRecipientDoesNotInPlan(): Unit = {
- val sender: String = "notInPlan@domain.tld"
-
- val mail1: Mail = FakeMail.builder()
- .name("mail1")
- .sender(sender)
- .recipients("rcpt1@linagora.com")
- .state("transport")
- .build()
-
- val mail2: Mail = FakeMail.builder()
- .name("mail2")
- .sender(sender)
- .recipients("rcpt2@linagora.com")
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("transport")
- })
- }
-
- @Test
- def shouldBeAppliedPerSender(): Unit = {
- val mail1: Mail = FakeMail.builder()
- .name("mail1")
- .sender(USER1.asString())
- .recipients("rcpt1@linagora.com")
- .state("transport")
- .build()
-
- val mail2: Mail = FakeMail.builder()
- .name("mail2")
- .sender(USER2.asString())
- .recipients("rcpt2@linagora.com")
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("transport")
- })
- }
-
- @Test
- def shouldRateLimitSizeOfEmails(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(rateLimitationPlanRepository.create(RateLimitingPlanCreateRequest(
- name = "LimitPlan",
- operationLimitations = OperationLimitationsType.liftOrThrow(Seq(
- TransitLimitations(Seq(RateLimitation(
- name = "transit limit 1",
- period = Duration.ofSeconds(100),
- limits = LimitTypes.from(Map(("size", parse("100K").asBytes())))))))))))
- .block()
-
- val username: Username = Username.of("user3@domain.tld")
- SMono.fromPublisher(rateLimitingPlanUserRepository.applyPlan(username, rateLimitingPlan.id)).block()
- mailet = testee(mailetConfig.build())
-
- val mail1: Mail = FakeMail.builder()
- .name("mail1")
- .sender(username.asString())
- .recipients("rcpt1@linagora.com")
- .size(50 * 1024)
- .state("transport")
- .build()
-
- val mail2: Mail = FakeMail.builder()
- .name("mail2")
- .sender(username.asString())
- .recipients("rcpt1@linagora.com")
- .size(51 * 1024)
- .state("transport")
- .build()
-
- val mail3: Mail = FakeMail.builder()
- .name("mail3")
- .sender(username.asString())
- .recipients("rcpt1@linagora.com")
- .size(49 * 1024)
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
- mailet.service(mail3)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("error")
- softly.assertThat(mail3.getState).isEqualTo("transport")
- })
- }
-
- @Test
- def severalLimitsShouldBeApplied(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(rateLimitationPlanRepository.create(RateLimitingPlanCreateRequest(
- name = "LimitPlan",
- operationLimitations = OperationLimitationsType.liftOrThrow(Seq(
- TransitLimitations(Seq(RateLimitation(
- name = "transit limit 1",
- period = Duration.ofSeconds(100),
- limits = LimitTypes.from(Map(("count", 3), ("size", parse("100K").asBytes())))))))))))
- .block()
-
- val username: Username = Username.of("user3@domain.tld")
- SMono.fromPublisher(rateLimitingPlanUserRepository.applyPlan(username, rateLimitingPlan.id)).block()
- mailet = testee(mailetConfig.build())
-
- val mail1: Mail = FakeMail.builder()
- .name("mail1")
- .sender(username.asString())
- .recipients("rcpt1@linagora.com")
- .size(50 * 1024)
- .state("transport")
- .build()
-
- // Will exceed the size rate limit
- val mail2: Mail = FakeMail.builder()
- .name("mail2")
- .sender(username.asString())
- .recipients("rcpt2@linagora.com")
- .size(60 * 1024)
- .state("transport")
- .build()
-
- val mail3: Mail = FakeMail.builder()
- .name("mail4")
- .sender(username.asString())
- .recipients("rcpt3@linagora.com")
- .size(1)
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
- mailet.service(mail3)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("error")
- softly.assertThat(mail3.getState).isEqualTo("transport")
- })
- }
-
- @Test
- def shouldNotAppliedWhenOperationLimitationNotFound(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(rateLimitationPlanRepository.create(RateLimitingPlanCreateRequest(
- name = "LimitPlan",
- operationLimitations = OperationLimitationsType.liftOrThrow(Seq(
- DeliveryLimitations(Seq(RateLimitation(
- name = "delivery limit 1",
- period = Duration.ofSeconds(100),
- limits = LimitTypes.from(Map(("count", 1)))))))))))
- .block()
-
- val username: Username = Username.of("user3@domain.tld")
- SMono.fromPublisher(rateLimitingPlanUserRepository.applyPlan(username, rateLimitingPlan.id)).block()
-
- val mail1: Mail = FakeMail.builder()
- .name("mail1")
- .sender(username.asString())
- .recipients("rcpt1@linagora.com")
- .state("transport")
- .build()
-
- val mail2: Mail = FakeMail.builder()
- .name("mail2")
- .sender(username.asString())
- .recipients("rcpt2@linagora.com")
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("transport")
- })
- }
-
- @Test
- def relayLimitationsShouldRateLimitPerSender(): Unit = {
- val mailet: EnforceRateLimitingPlan = testee(FakeMailetConfig.builder()
- .mailetName("EnforceRateLimitingPlan")
- .setProperty("operationLimitation", "RelayLimitations")
- .setProperty("precision", "1s")
- .build())
-
- val mail1: Mail = FakeMail.builder()
- .name("mail1")
- .sender(USER1.asString())
- .recipients("rcpt1@linagora.com")
- .state("transport")
- .build()
-
- val mail2: Mail = FakeMail.builder()
- .name("mail2")
- .sender(USER1.asString())
- .recipients("rcpt2@linagora.com")
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("error")
- })
- }
- }
-
- @Nested
- class PerRecipientLimit {
- var mailetConfig: FakeMailetConfig.Builder = _
- var mailet: EnforceRateLimitingPlan = _
-
- @BeforeEach
- def setUpSender(): Unit = {
- mailetConfig = FakeMailetConfig.builder()
- .mailetName("EnforceRateLimitingPlan")
- .setProperty("operationLimitation", "DeliveryLimitations")
- .setProperty("precision", "1s")
- mailet = testee(mailetConfig.build())
- }
-
- @Test
- def shouldFlowToTheIntendedProcessor(): Unit = {
- val mailet: EnforceRateLimitingPlan = testee(mailetConfig
- .setProperty("exceededProcessor", "tooMuchMails")
- .build())
-
- val mail1: Mail = FakeMail.builder()
- .name("mail")
- .sender("sender1@domain.tld")
- .recipients(USER1.asString())
- .state("transport")
- .build()
-
- val mail2: Mail = FakeMail.builder()
- .name("mail2")
- .sender("sender1@domain.tld")
- .recipients(USER1.asString())
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("tooMuchMails")
- })
- }
-
- @Test
- def shouldNotAppliedToRecipientDoesNotInPlan(): Unit = {
- val recipient: String = "notInPlan@domain.tld"
-
- val mail1: Mail = FakeMail.builder()
- .name("mail")
- .sender("sender1@domain.tld")
- .recipients(recipient)
- .state("transport")
- .build()
-
- val mail2: Mail = FakeMail.builder()
- .name("mail2")
- .sender("sender1@domain.tld")
- .recipients(recipient)
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("transport")
- })
- }
-
- @Test
- def shouldBeAppliedPerRecipient(): Unit = {
- val mailet: EnforceRateLimitingPlan = testee(mailetConfig.build())
-
- val mail1: Mail = FakeMail.builder()
- .name("mail")
- .sender("sender1@domain.tld")
- .recipients(USER1.asString())
- .state("transport")
- .build()
-
- val mail2: Mail = FakeMail.builder()
- .name("mail2")
- .sender("sender1@domain.tld")
- .recipients(USER2.asString())
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("transport")
- })
- }
-
- @Test
- def shouldNotBeAppliedWhenDoNotHaveRecipient(): Unit = {
- val mail: Mail = FakeMail.builder()
- .name("mail")
- .sender("sender1@domain.tld")
- .state("transport")
- .build()
-
- mailet.service(mail)
- assertThat(mail.getState).isEqualTo("transport")
- }
-
- @Test
- def shouldNotBeAppliedPerSender(): Unit = {
- val mail1: Mail = FakeMail.builder()
- .name("mail")
- .sender("sender1@domain.tld")
- .recipients(USER1.asString())
- .state("transport")
- .build()
-
- val mail2: Mail = FakeMail.builder()
- .name("mail2")
- .sender("sender2@domain.tld")
- .recipients(USER1.asString())
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("error")
- })
- }
-
- @Test
- def shouldRateLimitSizeOfEmails(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(rateLimitationPlanRepository.create(RateLimitingPlanCreateRequest(
- name = "LimitPlan",
- operationLimitations = OperationLimitationsType.liftOrThrow(Seq(
- DeliveryLimitations(Seq(RateLimitation(
- name = "transit limit 1",
- period = Duration.ofSeconds(100),
- limits = LimitTypes.from(Map(("size", parse("100K").asBytes())))))))))))
- .block()
-
- val username: Username = Username.of("user3@domain.tld")
- SMono.fromPublisher(rateLimitingPlanUserRepository.applyPlan(username, rateLimitingPlan.id)).block()
-
- val mail1: Mail = FakeMail.builder()
- .name("mail1")
- .sender("sender@domain.tld")
- .recipients(username.asString())
- .size(50 * 1024)
- .state("transport")
- .build()
-
- val mail2: Mail = FakeMail.builder()
- .name("mail2")
- .sender("sender@domain.tld")
- .recipients(username.asString())
- .size(51 * 1024)
- .state("transport")
- .build()
-
- val mail3: Mail = FakeMail.builder()
- .name("mail3")
- .sender("sender@domain.tld")
- .recipients(username.asString())
- .size(49 * 1024)
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
- mailet.service(mail3)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("error")
- softly.assertThat(mail3.getState).isEqualTo("transport")
- })
- }
-
- @Test
- def shouldRateLimitPerRecipient(): Unit = {
- val mailetContext: FakeMailContext = FakeMailContext.defaultContext
- val mailet: EnforceRateLimitingPlan = testee(mailetConfig
- .mailetContext(mailetContext).build())
-
- val mail1: Mail = FakeMail.builder()
- .name("mail")
- .sender("sender1@domain.tld")
- .recipients(USER1.asString())
- .state("transport")
- .build()
-
- val mail2: Mail = FakeMail.builder()
- .name("mail")
- .sender("sender1@domain.tld")
- .recipients(USER1.asString(), USER2.asString())
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("transport")
- })
-
- val sentMails: util.List[FakeMailContext.SentMail] = mailetContext.getSentMails
- assertThat(sentMails).hasSize(1)
- assertSoftly(softly => {
- softly.assertThat(sentMails.get(0).getState).isEqualTo("error")
- softly.assertThat(sentMails.get(0).getRecipients).containsExactlyInAnyOrder(USER1.asMailAddress())
- })
- }
-
- @Test
- def shouldRateLimitedWhenAllRecipientsExceeded(): Unit = {
- val mail1: Mail = FakeMail.builder()
- .name("mail")
- .sender("sender1@domain.tld")
- .recipients(USER1.asString(), USER2.asString())
- .state("transport")
- .build()
-
- val mail2: Mail = FakeMail.builder()
- .name("mail")
- .sender("sender1@domain.tld")
- .recipients(USER1.asString(), USER2.asString())
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("error")
- })
- }
-
- @Test
- def shouldNotAppliedWhenOperationLimitationNotFound(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(rateLimitationPlanRepository.create(RateLimitingPlanCreateRequest(
- name = "LimitPlan",
- operationLimitations = OperationLimitationsType.liftOrThrow(Seq(
- TransitLimitations(Seq(RateLimitation(
- name = "transit limit 1",
- period = Duration.ofSeconds(100),
- limits = LimitTypes.from(Map(("count", 1)))))))))))
- .block()
-
- val username: Username = Username.of("user3@domain.tld")
- SMono.fromPublisher(rateLimitingPlanUserRepository.applyPlan(username, rateLimitingPlan.id)).block()
-
- val mail1: Mail = FakeMail.builder()
- .name("mail")
- .sender("sender1@domain.tld")
- .recipients(username.asString())
- .state("transport")
- .build()
-
- val mail2: Mail = FakeMail.builder()
- .name("mail2")
- .sender("sender1@domain.tld")
- .recipients(username.asString())
- .state("transport")
- .build()
-
- mailet.service(mail1)
- mailet.service(mail2)
-
- assertSoftly(softly => {
- softly.assertThat(mail1.getState).isEqualTo("transport")
- softly.assertThat(mail2.getState).isEqualTo("transport")
- })
- }
- }
-
- @Nested
- class Configuration {
- @Test
- def shouldFailWhenNoOperationLimitation(): Unit = {
- assertThatThrownBy(() => testee(FakeMailetConfig.builder()
- .mailetName("EnforceRateLimitingPlan")
- .build()))
- .isInstanceOf(classOf[IllegalArgumentException])
- }
-
- @Test
- def shouldFailWhenInvalidOperationLimitation(): Unit = {
- assertThatThrownBy(() => testee(FakeMailetConfig.builder()
- .mailetName("EnforceRateLimitingPlan")
- .setProperty("operationLimitation", "badOperation")
- .build()))
- .isInstanceOf(classOf[IllegalArgumentException])
- }
-
- @Test
- def shouldNotFailWhenValidOperationLimitation(): Unit = {
- assertThatCode(() => testee(FakeMailetConfig.builder()
- .mailetName("EnforceRateLimitingPlan")
- .setProperty("operationLimitation", "TransitLimitations")
- .build()))
- .doesNotThrowAnyException()
- }
-
- @Test
- def cacheExpirationShouldSupportUnits(): Unit = {
- assertThat(testee(FakeMailetConfig.builder()
- .mailetName("EnforceRateLimitingPlan")
- .setProperty("operationLimitation", "TransitLimitations")
- .setProperty("cacheExpiration", "2h")
- .build()).parseCacheExpiration())
- .isEqualTo(Some(Duration.ofHours(2)))
- }
-
-
- @Test
- def cacheExpirationWithNoUnitShouldDefaultToSeconds(): Unit = {
- assertThat(testee(FakeMailetConfig.builder()
- .mailetName("EnforceRateLimitingPlan")
- .setProperty("operationLimitation", "TransitLimitations")
- .setProperty("cacheExpiration", "2")
- .build()).parseCacheExpiration())
- .isEqualTo(Some(Duration.ofSeconds(2)))
- }
-
- @Test
- def shouldFailWhenBadCacheExpiration(): Unit = {
- assertThatThrownBy(() => testee(FakeMailetConfig.builder()
- .mailetName("EnforceRateLimitingPlan")
- .setProperty("operationLimitation", "TransitLimitations")
- .setProperty("cacheExpiration", "bad")
- .build()).parseCacheExpiration())
- .isInstanceOf(classOf[IllegalArgumentException])
- }
- }
-
- @Test
- def planShouldBeCachedWhenConfigurationIsProvided(): Unit = {
- val metricRegistry: MetricRegistry = new MetricRegistry()
- val gaugeRegistry: DropWizardGaugeRegistry = new DropWizardGaugeRegistry(metricRegistry)
-
- val mailet: EnforceRateLimitingPlan = new EnforceRateLimitingPlan(rateLimitationPlanRepository, rateLimitingPlanUserRepository, redisRateLimiterFactory,
- gaugeRegistry)
-
- mailet.init(FakeMailetConfig.builder()
- .mailetName("EnforceRateLimitingPlan")
- .setProperty("operationLimitation", "TransitLimitations")
- .setProperty("precision", "1s")
- .setProperty("cacheExpiration", "2m")
- .build())
-
- val sentCount: Int = 10;
- IntStream.range(0, sentCount)
- .forEach(index => {
- val mail: Mail = FakeMail.builder()
- .name("mail" + index)
- .sender(USER1.asString())
- .recipients("rcpt1@linagora.com")
- .state("transport")
- .build()
- mailet.service(mail)
- })
- assertThat(metricRegistry.getGauges.get("TransitLimitations.rate_limiting_plan.cache.get.hitCount").getValue)
- .isEqualTo(java.lang.Long.valueOf(sentCount - 1))
- }
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-api/pom.xml b/tmail-backend/rate-limiter/rate-limiter-api/pom.xml
index e58a7c202d..792325d446 100644
--- a/tmail-backend/rate-limiter/rate-limiter-api/pom.xml
+++ b/tmail-backend/rate-limiter/rate-limiter-api/pom.xml
@@ -33,63 +33,18 @@
Twake Mail :: Rate Limiter :: Api
-
- ${james.groupId}
- apache-mailet-api
-
-
- ${james.groupId}
- james-core
-
${james.groupId}
james-server-data-api
-
- ${james.groupId}
- james-server-rate-limiter
-
-
- ${james.groupId}
- metrics-api
-
${james.groupId}
testing-base
test
-
- com.github.ben-manes.caffeine
- caffeine
-
com.google.inject
guice
-
- io.projectreactor
- reactor-scala-extensions_${scala.base}
-
-
- eu.timepit
- refined_${scala.base}
-
-
-
-
-
- net.alchim31.maven
- scala-maven-plugin
-
-
- io.github.evis
- scalafix-maven-plugin_2.13
-
- ${project.parent.parent.basedir}/.scalafix.conf
-
-
-
-
-
\ No newline at end of file
diff --git a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/GetAllRateLimitPlanResponseDTO.java b/tmail-backend/rate-limiter/rate-limiter-api/src/main/java/com/linagora/tmail/rate/limiter/api/memory/MemoryRateLimitingModule.java
similarity index 60%
rename from tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/GetAllRateLimitPlanResponseDTO.java
rename to tmail-backend/rate-limiter/rate-limiter-api/src/main/java/com/linagora/tmail/rate/limiter/api/memory/MemoryRateLimitingModule.java
index cf03978ed5..9b6c18f6b1 100644
--- a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/GetAllRateLimitPlanResponseDTO.java
+++ b/tmail-backend/rate-limiter/rate-limiter-api/src/main/java/com/linagora/tmail/rate/limiter/api/memory/MemoryRateLimitingModule.java
@@ -16,16 +16,24 @@
* more details. *
********************************************************************/
-package com.linagora.tmail.webadmin.model;
+package com.linagora.tmail.rate.limiter.api.memory;
-import java.util.List;
+import org.apache.james.user.api.UsernameChangeTaskStep;
-import com.fasterxml.jackson.annotation.JsonValue;
+import com.google.inject.AbstractModule;
+import com.google.inject.Scopes;
+import com.google.inject.multibindings.Multibinder;
+import com.linagora.tmail.rate.limiter.api.RateLimitingRepository;
+import com.linagora.tmail.rate.limiter.api.RateLimitingUsernameChangeTaskStep;
-public record GetAllRateLimitPlanResponseDTO(List list) {
+public class MemoryRateLimitingModule extends AbstractModule {
@Override
- @JsonValue
- public List list() {
- return list;
+ protected void configure() {
+ bind(MemoryRateLimitingRepository.class).in(Scopes.SINGLETON);
+ bind(RateLimitingRepository.class).to(MemoryRateLimitingRepository.class);
+
+ Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class)
+ .addBinding()
+ .to(RateLimitingUsernameChangeTaskStep.class);
}
}
diff --git a/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/CacheRateLimitingPlan.scala b/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/CacheRateLimitingPlan.scala
deleted file mode 100644
index 173d4049b3..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/CacheRateLimitingPlan.scala
+++ /dev/null
@@ -1,81 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api
-
-import java.lang
-import java.time.Duration
-import java.util.concurrent.Executor
-
-import com.github.benmanes.caffeine.cache.{AsyncCacheLoader, AsyncLoadingCache, Caffeine}
-import org.apache.james.metrics.api.GaugeRegistry
-import org.reactivestreams.Publisher
-import reactor.core.publisher.Mono
-import reactor.core.scala.publisher.SMono
-import reactor.core.scheduler.Schedulers
-
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.jdk.FutureConverters._
-
-class CacheRateLimitingPlan(repository: RateLimitingPlanRepository, expireDuration: Duration, gaugeRegistry: GaugeRegistry,
- gaugePrefix: Option[String] = None) extends RateLimitingPlanRepository {
-
- private val cacheLoaderGet: AsyncCacheLoader[RateLimitingPlanId, RateLimitingPlan] =
- (key: RateLimitingPlanId, executor: Executor) => Mono.from(repository.get(key))
- .subscribeOn(Schedulers.fromExecutor(executor))
- .toFuture
-
- private val gaugePrefixValue: String = gaugePrefix.map(_ + ".").getOrElse("")
-
- private val cacheGet: AsyncLoadingCache[RateLimitingPlanId, RateLimitingPlan] = {
- val loadingCache: AsyncLoadingCache[RateLimitingPlanId, RateLimitingPlan] = Caffeine.newBuilder()
- .expireAfterWrite(expireDuration)
- .recordStats()
- .buildAsync[RateLimitingPlanId, RateLimitingPlan](cacheLoaderGet)
-
- gaugeRegistry.register(gaugePrefixValue + "rate_limiting_plan.cache.get.hitRate", () => loadingCache.synchronous().stats().hitRate())
- .register(gaugePrefixValue + "rate_limiting_plan.cache.get.missCount", () => loadingCache.synchronous().stats().missCount())
- .register(gaugePrefixValue + "rate_limiting_plan.cache.get.hitCount", () => loadingCache.synchronous().stats().hitCount())
- .register(gaugePrefixValue + "rate_limiting_plan.cache.get.size", () => loadingCache.synchronous().estimatedSize())
- loadingCache
- }
-
- override def create(creationRequest: RateLimitingPlanCreateRequest): Publisher[RateLimitingPlan] =
- SMono.fromPublisher(repository.create(creationRequest))
- .doOnNext(_ => invalidCache())
-
- override def update(resetRequest: RateLimitingPlanResetRequest): Publisher[Unit] =
- SMono.fromPublisher(repository.update(resetRequest))
- .`then`(SMono.fromCallable(() => invalidCache()))
-
- override def get(id: RateLimitingPlanId): Publisher[RateLimitingPlan] =
- SMono.fromFuture(cacheGet.get(id).asScala)
-
- override def planExists(id: RateLimitingPlanId): Publisher[lang.Boolean] =
- SMono.fromFuture(cacheGet.get(id).asScala)
- .hasElement
- .onErrorResume {
- case _: RateLimitingPlanNotFoundException => SMono.just(false)
- case error => SMono.error(error)
- }
- .map(_.booleanValue())
-
- override def list(): Publisher[RateLimitingPlan] = repository.list()
-
- private def invalidCache(): Unit = cacheGet.synchronous().invalidateAll()
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/RateLimitation.scala b/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/RateLimitation.scala
deleted file mode 100644
index b6ec30b4bf..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/RateLimitation.scala
+++ /dev/null
@@ -1,198 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api
-
-import java.time.Duration
-import java.util.UUID
-
-import com.linagora.tmail.rate.limiter.api.LimitTypes.{COUNT, LimitTypes, SIZE}
-import com.linagora.tmail.rate.limiter.api.OperationLimitations.{DELIVERY_LIMITATIONS_NAME, RELAY_LIMITATIONS_NAME, TRANSIT_LIMITATIONS_NAME}
-import com.linagora.tmail.rate.limiter.api.OperationLimitationsType.OperationLimitationsType
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanName.RateLimitingPlanName
-import eu.timepit.refined
-import eu.timepit.refined.api.Refined
-import eu.timepit.refined.collection.NonEmpty
-import org.apache.james.core.Username
-import org.apache.james.rate.limiter.api.AllowedQuantity
-import org.apache.james.rate.limiter.api.AllowedQuantity.AllowedQuantity
-
-object LimitTypes {
- val SIZE: String = "size"
- val COUNT: String = "count"
-
- type LimitTypes = Set[LimitType] Refined NonEmpty
-
- def validate(value: Set[LimitType]): Either[IllegalArgumentException, LimitTypes] =
- refined.refineV[NonEmpty](value) match {
- case Right(value) => Right(value)
- case Left(_) => Left(new IllegalArgumentException("value should not be empty"))
- }
-
- def liftOrThrow(value: Set[LimitType]): LimitTypes =
- validate(value) match {
- case Right(value) => value
- case Left(error) => throw error
- }
-
- def from(limitTypeAndAllowedQuantity: Map[String, Long]): LimitTypes = {
- val limitTypes: Set[LimitType] = limitTypeAndAllowedQuantity
- .map(map => LimitType.liftOrThrow(map._1, map._2))
- .toSet
- liftOrThrow(limitTypes)
- }
-
- def fromMutableMap(limitTypeAndAllowedQuantity: scala.collection.mutable.Map[String, Long]): LimitTypes =
- from(limitTypeAndAllowedQuantity.toMap)
-}
-
-object LimitType {
- def liftOrThrow(limitTypeName: String, allowedQuantity: Long): LimitType =
- from(limitTypeName, allowedQuantity) match {
- case Right(value) => value
- case Left(error) => throw error
- }
-
- def from(limitTypeName: String, allowedQuantity: Long): Either[IllegalArgumentException, LimitType] = {
- limitTypeName match {
- case SIZE => AllowedQuantity.validate(allowedQuantity).map(Size)
- case COUNT => AllowedQuantity.validate(allowedQuantity).map(Count)
- case _ => Left(new IllegalArgumentException(s"`$limitTypeName` is invalid"))
- }
- }
-}
-
-sealed trait LimitType {
- def asString(): String
-
- def allowedQuantity(): AllowedQuantity
-}
-
-case class Count(quantity: AllowedQuantity) extends LimitType {
- override def asString(): String = COUNT
-
- override def allowedQuantity(): AllowedQuantity = quantity
-}
-
-case class Size(quantity: AllowedQuantity) extends LimitType {
- override def asString(): String = SIZE
-
- override def allowedQuantity(): AllowedQuantity = quantity
-}
-
-case class RateLimitation(name: String, period: Duration, limits: LimitTypes) {
- def limitsValue: Set[LimitType] = limits.value
-}
-
-object RateLimitingPlanId {
- def generate: RateLimitingPlanId = RateLimitingPlanId(UUID.randomUUID())
-
- def parse(string: String): RateLimitingPlanId = RateLimitingPlanId(UUID.fromString(string))
-}
-
-case class RateLimitingPlanId(value: UUID) {
- def serialize(): String = value.toString
-}
-
-object RateLimitingPlanName {
- type RateLimitingPlanName = String Refined NonEmpty
-
- def liftOrThrow(value: String): RateLimitingPlanName =
- refined.refineV[NonEmpty](value) match {
- case Right(value) => value
- case Left(_) => throw new IllegalArgumentException("Rate limiting plan name should not be empty")
- }
-}
-
-object OperationLimitationsType {
- type OperationLimitationsType = Seq[OperationLimitations] Refined NonEmpty
-
- def liftOrThrow(value: Seq[OperationLimitations]): OperationLimitationsType =
- refined.refineV[NonEmpty](value) match {
- case Right(value) => value
- case Left(_) => throw new IllegalArgumentException("value should not be empty")
- }
-}
-
-object OperationLimitations {
- val TRANSIT_LIMITATIONS_NAME: String = "TransitLimitations"
- val RELAY_LIMITATIONS_NAME: String = "RelayLimitations"
- val DELIVERY_LIMITATIONS_NAME: String = "DeliveryLimitations"
-
- def liftOrThrow(operationName: String, rateLimitations: Seq[RateLimitation]): OperationLimitations =
- from(operationName, rateLimitations) match {
- case Right(value) => value
- case Left(error) => throw error
- }
-
- def from(operationName: String, rateLimitations: Seq[RateLimitation]): Either[IllegalArgumentException, OperationLimitations] =
- operationName match {
- case TRANSIT_LIMITATIONS_NAME => Right(TransitLimitations(rateLimitations))
- case RELAY_LIMITATIONS_NAME => Right(RelayLimitations(rateLimitations))
- case DELIVERY_LIMITATIONS_NAME => Right(DeliveryLimitations(rateLimitations))
- case _ => Left(new IllegalArgumentException(s"`$operationName` is invalid"))
- }
-}
-
-sealed trait OperationLimitations {
- def asString(): String
-
- def rateLimitations(): Seq[RateLimitation]
-}
-
-case class TransitLimitations(value: Seq[RateLimitation]) extends OperationLimitations {
- override def asString(): String = TRANSIT_LIMITATIONS_NAME
-
- override def rateLimitations(): Seq[RateLimitation] = value
-}
-
-case class RelayLimitations(value: Seq[RateLimitation]) extends OperationLimitations {
- override def asString(): String = RELAY_LIMITATIONS_NAME
-
- override def rateLimitations(): Seq[RateLimitation] = value
-}
-
-case class DeliveryLimitations(value: Seq[RateLimitation]) extends OperationLimitations {
- override def asString(): String = DELIVERY_LIMITATIONS_NAME
-
- override def rateLimitations(): Seq[RateLimitation] = value
-}
-
-object RateLimitingPlan {
- def from(createRequest: RateLimitingPlanCreateRequest): RateLimitingPlan =
- RateLimitingPlan(id = RateLimitingPlanId.generate, name = createRequest.name, operationLimitations = createRequest.operationLimitations.value)
-
- def update(origin: RateLimitingPlan, resetRequest: RateLimitingPlanResetRequest): RateLimitingPlan =
- origin.copy(name = resetRequest.name, operationLimitations = resetRequest.operationLimitations.value)
-}
-
-case class RateLimitingPlan(id: RateLimitingPlanId, name: RateLimitingPlanName, operationLimitations: Seq[OperationLimitations])
-
-case class RateLimitingPlanCreateRequest(name: RateLimitingPlanName, operationLimitations: OperationLimitationsType) {
- def operationLimitationsValue: Seq[OperationLimitations] = operationLimitations.value
-
- def nameValue: String = name.value
-}
-
-case class RateLimitingPlanResetRequest(id: RateLimitingPlanId, name: RateLimitingPlanName, operationLimitations: OperationLimitationsType) {
- def operationLimitationsValue: Seq[OperationLimitations] = operationLimitations.value
-
- def nameValue: String = name.value
-}
-
-case class UsernameToRateLimitingPlanId(username: Username, rateLimitingPlanId: RateLimitingPlanId)
diff --git a/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanRepository.scala b/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanRepository.scala
deleted file mode 100644
index b6e07206f4..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanRepository.scala
+++ /dev/null
@@ -1,67 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api
-
-import org.reactivestreams.Publisher
-import reactor.core.scala.publisher.{SFlux, SMono}
-
-import scala.collection.concurrent.Map
-
-trait RateLimitingPlanRepository {
-
- def create(creationRequest: RateLimitingPlanCreateRequest): Publisher[RateLimitingPlan]
-
- def update(resetRequest: RateLimitingPlanResetRequest): Publisher[Unit]
-
- def get(id: RateLimitingPlanId): Publisher[RateLimitingPlan]
-
- def planExists(id: RateLimitingPlanId): Publisher[java.lang.Boolean]
-
- def list(): Publisher[RateLimitingPlan]
-}
-
-case class RateLimitingPlanNotFoundException() extends RuntimeException
-
-class InMemoryRateLimitingPlanRepository() extends RateLimitingPlanRepository {
-
- val rateLimitingPlanStore: Map[RateLimitingPlanId, RateLimitingPlan] = scala.collection.concurrent.TrieMap()
-
- override def create(creationRequest: RateLimitingPlanCreateRequest): Publisher[RateLimitingPlan] =
- SMono.fromCallable(() => {
- val rateLimitingPlan: RateLimitingPlan = RateLimitingPlan.from(creationRequest)
- rateLimitingPlanStore.put(rateLimitingPlan.id, rateLimitingPlan)
- rateLimitingPlan
- })
-
- override def update(resetRequest: RateLimitingPlanResetRequest): Publisher[Unit] =
- SMono.justOrEmpty(rateLimitingPlanStore.get(resetRequest.id))
- .switchIfEmpty(SMono.error(new RateLimitingPlanNotFoundException))
- .map(rateLimitingPlan => rateLimitingPlanStore.update(resetRequest.id, RateLimitingPlan.update(rateLimitingPlan, resetRequest)))
-
- override def get(id: RateLimitingPlanId): Publisher[RateLimitingPlan] =
- SMono.justOrEmpty(rateLimitingPlanStore.get(id))
- .switchIfEmpty(SMono.error(new RateLimitingPlanNotFoundException))
-
-
- override def list(): Publisher[RateLimitingPlan] =
- SFlux.fromIterable(rateLimitingPlanStore)
- .map(_._2)
-
- override def planExists(id: RateLimitingPlanId): Publisher[java.lang.Boolean] = SMono.just(rateLimitingPlanStore.contains(id))
-}
\ No newline at end of file
diff --git a/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanUserRepository.scala b/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanUserRepository.scala
deleted file mode 100644
index f2170eac50..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanUserRepository.scala
+++ /dev/null
@@ -1,32 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api
-
-import org.apache.james.core.Username
-import org.reactivestreams.Publisher
-
-trait RateLimitingPlanUserRepository {
- def applyPlan(username: Username, planId: RateLimitingPlanId): Publisher[Unit]
-
- def revokePlan(username: Username): Publisher[Unit]
-
- def listUsers(planId: RateLimitingPlanId): Publisher[Username]
-
- def getPlanByUser(username: Username): Publisher[RateLimitingPlanId]
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanUsernameChangeTaskStep.scala b/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanUsernameChangeTaskStep.scala
deleted file mode 100644
index 851626f8c9..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanUsernameChangeTaskStep.scala
+++ /dev/null
@@ -1,48 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api
-
-import jakarta.inject.Inject
-import org.apache.james.core.Username
-import org.apache.james.user.api.UsernameChangeTaskStep
-import org.apache.james.user.api.UsernameChangeTaskStep.StepName
-import org.reactivestreams.Publisher
-import reactor.core.scala.publisher.SMono
-
-class RateLimitingPlanUsernameChangeTaskStep @Inject() (val repository: RateLimitingPlanUserRepository) extends UsernameChangeTaskStep {
- override def name(): StepName = new StepName("RateLimitingPlanUsernameChangeTaskStep")
-
- override def priority(): Int = 7
-
- override def changeUsername(oldUsername: Username, newUsername: Username): Publisher[Void] = {
- SMono(repository.getPlanByUser(newUsername))
- .onErrorResume {
- case _: RateLimitingPlanNotFoundException => migratePlan(oldUsername, newUsername)
- }
- .`then`(SMono(repository.revokePlan(oldUsername)))
- .`then`()
- }
-
- private def migratePlan(oldUsername: Username, newUsername: Username) =
- SMono(repository.getPlanByUser(oldUsername))
- .onErrorResume {
- case _: RateLimitingPlanNotFoundException => SMono.empty
- }
- .flatMap(planId => SMono(repository.applyPlan(newUsername, planId)))
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/memory/MemoryRateLimitingModule.scala b/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/memory/MemoryRateLimitingModule.scala
deleted file mode 100644
index dd70389cbb..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/memory/MemoryRateLimitingModule.scala
+++ /dev/null
@@ -1,46 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.memory
-
-import com.google.inject.multibindings.Multibinder
-import com.google.inject.{AbstractModule, Scopes}
-import com.linagora.tmail.rate.limiter.api.{InMemoryRateLimitingPlanRepository, RateLimitingPlanRepository, RateLimitingPlanUserRepository, RateLimitingPlanUsernameChangeTaskStep, RateLimitingRepository, RateLimitingUsernameChangeTaskStep}
-import org.apache.james.user.api.UsernameChangeTaskStep
-
-class MemoryRateLimitingModule() extends AbstractModule {
-
- override def configure(): Unit = {
- bind(classOf[MemoryRateLimitingPlanUserRepository]).in(Scopes.SINGLETON)
- bind(classOf[InMemoryRateLimitingPlanRepository]).in(Scopes.SINGLETON)
-
- bind(classOf[RateLimitingPlanUserRepository]).to(classOf[MemoryRateLimitingPlanUserRepository])
- bind(classOf[RateLimitingPlanRepository]).to(classOf[InMemoryRateLimitingPlanRepository])
-
- Multibinder.newSetBinder(binder(), classOf[UsernameChangeTaskStep])
- .addBinding()
- .to(classOf[RateLimitingPlanUsernameChangeTaskStep])
-
- bind(classOf[MemoryRateLimitingRepository]).in(Scopes.SINGLETON)
- bind(classOf[RateLimitingRepository]).to(classOf[MemoryRateLimitingRepository])
-
- Multibinder.newSetBinder(binder(), classOf[UsernameChangeTaskStep])
- .addBinding()
- .to(classOf[RateLimitingUsernameChangeTaskStep])
- }
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/memory/MemoryRateLimitingPlanUserRepository.scala b/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/memory/MemoryRateLimitingPlanUserRepository.scala
deleted file mode 100644
index 2c80e0182d..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-api/src/main/scala/com/linagora/tmail/rate/limiter/api/memory/MemoryRateLimitingPlanUserRepository.scala
+++ /dev/null
@@ -1,52 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.memory
-
-import com.google.common.base.Preconditions
-import com.linagora.tmail.rate.limiter.api.{RateLimitingPlanId, RateLimitingPlanNotFoundException, RateLimitingPlanUserRepository}
-import org.apache.james.core.Username
-import reactor.core.scala.publisher.{SFlux, SMono}
-
-import scala.collection.concurrent.Map
-
-class MemoryRateLimitingPlanUserRepository extends RateLimitingPlanUserRepository {
- val table: Map[Username, RateLimitingPlanId] = scala.collection.concurrent.TrieMap()
-
- override def applyPlan(username: Username, planId: RateLimitingPlanId): SMono[Unit] = {
- Preconditions.checkNotNull(username)
- Preconditions.checkNotNull(planId)
- SMono.fromCallable(() => table.put(username, planId)).`then`()
- }
-
- override def revokePlan(username: Username): SMono[Unit] = {
- Preconditions.checkNotNull(username)
- SMono.fromCallable(() => table.remove(username)).`then`()
- }
-
- override def listUsers(planId: RateLimitingPlanId): SFlux[Username] = {
- Preconditions.checkNotNull(planId)
- SFlux.fromIterable(table.filter(usernameToPlanId => usernameToPlanId._2.equals(planId)).keys)
- }
-
- override def getPlanByUser(username: Username): SMono[RateLimitingPlanId] = {
- Preconditions.checkNotNull(username)
- SMono.justOrEmpty(table.get(username))
- .switchIfEmpty(SMono.error(RateLimitingPlanNotFoundException()))
- }
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/CacheInMemoryRateLimitingPlanRepositoryTest.scala b/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/CacheInMemoryRateLimitingPlanRepositoryTest.scala
deleted file mode 100644
index 8013bd3610..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/CacheInMemoryRateLimitingPlanRepositoryTest.scala
+++ /dev/null
@@ -1,36 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api
-
-import java.time.Duration
-
-import org.apache.james.metrics.api.NoopGaugeRegistry
-import org.junit.jupiter.api.BeforeEach
-
-class CacheInMemoryRateLimitingPlanRepositoryTest extends RateLimitingPlanRepositoryContract {
- var repository: RateLimitingPlanRepository = _
-
- override def testee: RateLimitingPlanRepository = repository
-
- @BeforeEach
- def beforeEach(): Unit = {
- val inMemoryRepository: RateLimitingPlanRepository = new InMemoryRateLimitingPlanRepository()
- repository = new CacheRateLimitingPlan(inMemoryRepository, Duration.ofMinutes(2), new NoopGaugeRegistry)
- }
-}
\ No newline at end of file
diff --git a/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanRepositoryContract.scala b/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanRepositoryContract.scala
deleted file mode 100644
index a07689682f..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanRepositoryContract.scala
+++ /dev/null
@@ -1,227 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api
-
-import java.time.Duration
-
-import com.linagora.tmail.rate.limiter.api.LimitTypes.LimitTypes
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanRepositoryContract.{CREATION_REQUEST, CREATION_REQUEST_WITH_MULTI_OPERATIONS, RESET_REQUEST}
-import eu.timepit.refined.auto._
-import org.apache.james.rate.limiter.api.AllowedQuantity
-import org.assertj.core.api.Assertions.{assertThat, assertThatCode, assertThatThrownBy}
-import org.assertj.core.api.SoftAssertions
-import org.junit.jupiter.api.{BeforeEach, Test}
-import reactor.core.scala.publisher.{SFlux, SMono}
-
-import scala.jdk.CollectionConverters._
-
-case class TestPJ(name: String, limits: LimitTypes)
-
-object RateLimitingPlanRepositoryContract {
- def getAllowedQuantity(value: Long): AllowedQuantity.AllowedQuantity = AllowedQuantity.validate(value).toOption.get
-
- val COUNT: Count = Count(getAllowedQuantity(1))
- val SIZE: Size = Size(getAllowedQuantity(10000))
- val LIMIT_TYPES: LimitTypes = LimitTypes.liftOrThrow(Set(COUNT, SIZE))
- val RATE_LIMITATION: RateLimitation = RateLimitation("name1", Duration.ofMinutes(1), LIMIT_TYPES)
- val TRANSIT_LIMITS: TransitLimitations = TransitLimitations(Seq(RATE_LIMITATION))
- val RELAY_LIMITS: RelayLimitations = RelayLimitations(Seq(RATE_LIMITATION))
-
- val CREATION_REQUEST: RateLimitingPlanCreateRequest = RateLimitingPlanCreateRequest(
- name = "name1",
- operationLimitations = OperationLimitationsType.liftOrThrow(Seq(TRANSIT_LIMITS)))
- val RESET_REQUEST: RateLimitingPlanResetRequest = RateLimitingPlanResetRequest(
- id = RateLimitingPlanId.generate,
- name = "new name",
- operationLimitations = OperationLimitationsType.liftOrThrow(Seq(TRANSIT_LIMITS)))
-
- val CREATION_REQUEST_WITH_MULTI_OPERATIONS: RateLimitingPlanCreateRequest = RateLimitingPlanCreateRequest(
- name = "complex_plan",
- operationLimitations = OperationLimitationsType.liftOrThrow(Seq(
- TransitLimitations(Seq(
- RateLimitation(name = "limit1",
- period = Duration.ofMinutes(1),
- limits = LimitTypes.liftOrThrow(Set(Count(getAllowedQuantity(1)), Size(getAllowedQuantity(10000))))),
- RateLimitation(name = "limit2",
- period = Duration.ofMinutes(2),
- limits = LimitTypes.liftOrThrow(Set(Count(getAllowedQuantity(2)), Size(getAllowedQuantity(20000))))))),
- RelayLimitations(Seq(
- RateLimitation(name = "limit3",
- period = Duration.ofMinutes(3),
- limits = LimitTypes.liftOrThrow(Set(Count(getAllowedQuantity(3)), Size(getAllowedQuantity(30000))))),
- RateLimitation(name = "limit4",
- period = Duration.ofMinutes(4),
- limits = LimitTypes.liftOrThrow(Set(Count(getAllowedQuantity(4)), Size(getAllowedQuantity(40000))))))))))
-}
-
-trait RateLimitingPlanRepositoryContract {
- def testee: RateLimitingPlanRepository
-
- @Test
- def createShouldReturnRateLimitingPlan(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(testee.create(CREATION_REQUEST))
- .block()
-
- SoftAssertions.assertSoftly(softly => {
- softly.assertThat(rateLimitingPlan.id).isNotNull
- softly.assertThat(rateLimitingPlan.operationLimitations.asJava)
- .containsExactlyInAnyOrderElementsOf(CREATION_REQUEST.operationLimitations.value.asJava)
- softly.assertThat(rateLimitingPlan.name).isEqualTo(CREATION_REQUEST.name)
- })
- }
-
- @Test
- def createShouldStoreEntry(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(testee.create(CREATION_REQUEST))
- .block()
-
- assertThat(SMono.fromPublisher(testee.get(rateLimitingPlan.id))
- .block())
- .isEqualTo(rateLimitingPlan)
- }
-
- @Test
- def createShouldReturnDifferentEntry(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(testee.create(CREATION_REQUEST))
- .block()
-
- assertThat(SMono.fromPublisher(testee.create(CREATION_REQUEST))
- .block())
- .isNotEqualTo(rateLimitingPlan)
- }
-
- @Test
- def createShouldWorkWhenHaveSeveralOperationLimitations(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(testee.create(CREATION_REQUEST_WITH_MULTI_OPERATIONS))
- .block()
-
- assertThat(SFlux.fromPublisher(testee.list()).collectSeq().block().asJava)
- .containsExactlyInAnyOrder(rateLimitingPlan)
- }
-
- @Test
- def updateShouldThrowWhenIdNotFound(): Unit = {
- assertThatThrownBy(() => SMono.fromPublisher(testee.update(RESET_REQUEST)).block())
- .isInstanceOf(classOf[RateLimitingPlanNotFoundException])
- }
-
- @Test
- def updateShouldNotThrowWhenIdExists(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(testee.create(CREATION_REQUEST))
- .block()
-
- assertThatCode(() => SMono.fromPublisher(testee.update(RESET_REQUEST.copy(id = rateLimitingPlan.id))).block())
- .doesNotThrowAnyException()
- }
-
- @Test
- def updateShouldModifyEntry(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(testee.create(CREATION_REQUEST))
- .block()
-
- SMono.fromPublisher(testee.update(RESET_REQUEST.copy(id = rateLimitingPlan.id))).block()
-
- assertThat(SMono.fromPublisher(testee.get(rateLimitingPlan.id)).block())
- .isEqualTo(RateLimitingPlan(
- id = rateLimitingPlan.id,
- name = RESET_REQUEST.name,
- operationLimitations = RESET_REQUEST.operationLimitations))
- }
-
- @Test
- def updateShouldNotModifyAnotherEntry(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(testee.create(CREATION_REQUEST))
- .block()
- val rateLimitingPlan2: RateLimitingPlan = SMono.fromPublisher(testee.create(CREATION_REQUEST))
- .block()
-
- SMono.fromPublisher(testee.update(RESET_REQUEST.copy(id = rateLimitingPlan.id))).block()
-
- assertThat(SMono.fromPublisher(testee.get(rateLimitingPlan2.id))
- .block())
- .isEqualTo(rateLimitingPlan2)
- }
-
- @Test
- def getShouldThrowWhenIdNotFound(): Unit = {
- assertThatThrownBy(() => SMono.fromPublisher(testee.get(RateLimitingPlanId.generate)).block())
- .isInstanceOf(classOf[RateLimitingPlanNotFoundException])
- }
-
- @Test
- def getShouldReturnEntryWhenIdExists(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(testee.create(CREATION_REQUEST))
- .block()
-
- assertThat(SMono.fromPublisher(testee.get(rateLimitingPlan.id)).block())
- .isEqualTo(rateLimitingPlan)
- }
-
- @Test
- def planExistsShouldReturnFalseByDefault(): Unit = {
- assertThat(SMono.fromPublisher(testee.planExists(RateLimitingPlanId.generate)).block())
- .isEqualTo(false)
- }
-
- @Test
- def planExistsShouldReturnTrueWhenPlanExists(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(testee.create(CREATION_REQUEST))
- .block()
-
- assertThat(SMono.fromPublisher(testee.planExists(rateLimitingPlan.id)).block())
- .isEqualTo(true)
- }
-
- @Test
- def listShouldReturnEmptyByDefault(): Unit = {
- assertThat(SFlux.fromPublisher(testee.list()).collectSeq().block().asJava)
- .isEmpty()
- }
-
- @Test
- def listShouldReturnStoredEntriesWhenHasSingleElement(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(testee.create(CREATION_REQUEST))
- .block()
-
- assertThat(SFlux.fromPublisher(testee.list()).collectSeq().block().asJava)
- .containsExactlyInAnyOrder(rateLimitingPlan)
- }
-
- @Test
- def listShouldReturnStoredEntriesWhenHasSeveralElement(): Unit = {
- val rateLimitingPlan: RateLimitingPlan = SMono.fromPublisher(testee.create(CREATION_REQUEST))
- .block()
- val rateLimitingPlan2: RateLimitingPlan = SMono.fromPublisher(testee.create(CREATION_REQUEST_WITH_MULTI_OPERATIONS))
- .block()
-
- assertThat(SFlux.fromPublisher(testee.list()).collectSeq().block().asJava)
- .containsExactlyInAnyOrder(rateLimitingPlan, rateLimitingPlan2)
- }
-}
-
-class InMemoryRateLimitingPlanRepositoryTest extends RateLimitingPlanRepositoryContract {
- var inMemoryRateLimitationPlanRepository: InMemoryRateLimitingPlanRepository = _
-
- override def testee: RateLimitingPlanRepository = inMemoryRateLimitationPlanRepository
-
- @BeforeEach
- def beforeEach(): Unit = {
- inMemoryRateLimitationPlanRepository = new InMemoryRateLimitingPlanRepository();
- }
-}
\ No newline at end of file
diff --git a/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanUserRepositoryContract.scala b/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanUserRepositoryContract.scala
deleted file mode 100644
index d431a75169..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanUserRepositoryContract.scala
+++ /dev/null
@@ -1,147 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api
-
-import java.util.UUID
-
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanUserRepositoryContract.{ALICE, BOB, PLAN_ID_1, PLAN_ID_2}
-import org.apache.james.core.Username
-import org.assertj.core.api.Assertions.{assertThat, assertThatThrownBy}
-import org.assertj.core.api.SoftAssertions
-import org.junit.jupiter.api.Test
-import reactor.core.scala.publisher.{SFlux, SMono}
-
-import scala.jdk.CollectionConverters._
-
-object RateLimitingPlanUserRepositoryContract {
- val BOB: Username = Username.of("BOB@domain1.tld")
- val ALICE: Username = Username.of("ALICE@domain2.tld")
- val PLAN_ID_1: RateLimitingPlanId = RateLimitingPlanId(UUID.randomUUID())
- val PLAN_ID_2: RateLimitingPlanId = RateLimitingPlanId(UUID.randomUUID())
-}
-
-trait RateLimitingPlanUserRepositoryContract {
- def testee: RateLimitingPlanUserRepository
-
- @Test
- def applyPlanShouldSucceed(): Unit = {
- SMono.fromPublisher(testee.applyPlan(BOB, PLAN_ID_1)).block()
-
- assertThat(SMono.fromPublisher(testee.getPlanByUser(BOB)).block()).isEqualTo(PLAN_ID_1)
- }
-
- @Test
- def applyPlanWhenManyUsersHaveSamePlanShouldSucceed(): Unit = {
- SMono.fromPublisher(testee.applyPlan(BOB, PLAN_ID_1)).block()
- SMono.fromPublisher(testee.applyPlan(ALICE, PLAN_ID_1)).block()
-
- SoftAssertions.assertSoftly(softly => {
- softly.assertThat(SMono.fromPublisher(testee.getPlanByUser(BOB)).block().value).isEqualTo(PLAN_ID_1.value)
- softly.assertThat(SMono.fromPublisher(testee.getPlanByUser(ALICE)).block().value).isEqualTo(PLAN_ID_1.value)
- })
- }
-
- @Test
- def applyPlanShouldOverridePreviousPlan(): Unit = {
- SMono.fromPublisher(testee.applyPlan(BOB, PLAN_ID_1)).block()
- SMono.fromPublisher(testee.applyPlan(BOB, PLAN_ID_2)).block()
-
- assertThat(SMono.fromPublisher(testee.getPlanByUser(BOB)).block()).isEqualTo(PLAN_ID_2)
- }
-
- @Test
- def applyPlanShouldThrowWhenUsernameIsNull(): Unit = {
- assertThatThrownBy(() => SMono.fromPublisher(testee.applyPlan(null, PLAN_ID_1)).block())
- .isInstanceOf(classOf[NullPointerException])
- }
-
- @Test
- def applyPlanShouldThrowWhenPlanIdIsNull(): Unit = {
- assertThatThrownBy(() => SMono.fromPublisher(testee.applyPlan(BOB, null)).block())
- .isInstanceOf(classOf[NullPointerException])
- }
-
- @Test
- def revokePlanShouldCleanTheRecord(): Unit = {
- SMono.fromPublisher(testee.applyPlan(BOB, PLAN_ID_1)).block()
- SMono.fromPublisher(testee.revokePlan(BOB)).block()
-
- assertThatThrownBy(() => SMono.fromPublisher(testee.getPlanByUser(BOB)).block())
- .isInstanceOf(classOf[RateLimitingPlanNotFoundException])
- }
-
- @Test
- def insertSettingsAfterDeletingSettingsShouldWorkWell(): Unit = {
- // This test simulates a scenario where the user record exists but has no settings yet.
- SMono.fromPublisher(testee.applyPlan(BOB, PLAN_ID_1)).block()
- SMono.fromPublisher(testee.revokePlan(BOB)).block()
-
- SMono.fromPublisher(testee.applyPlan(BOB, PLAN_ID_2)).block()
-
- assertThat(SMono.fromPublisher(testee.getPlanByUser(BOB)).block()).isEqualTo(PLAN_ID_2)
- }
-
- @Test
- def revokePlanShouldThrowWhenUsernameIsNull(): Unit = {
- assertThatThrownBy(() => SMono.fromPublisher(testee.revokePlan(null)).block())
- .isInstanceOf(classOf[NullPointerException])
- }
-
- @Test
- def getPlanByUserShouldSucceed(): Unit = {
- SMono.fromPublisher(testee.applyPlan(BOB, PLAN_ID_1)).block()
- SMono.fromPublisher(testee.applyPlan(ALICE, PLAN_ID_2)).block()
-
- SoftAssertions.assertSoftly(softly => {
- softly.assertThat(SMono.fromPublisher(testee.getPlanByUser(BOB)).block().value).isEqualTo(PLAN_ID_1.value)
- softly.assertThat(SMono.fromPublisher(testee.getPlanByUser(ALICE)).block().value).isEqualTo(PLAN_ID_2.value)
- })
- }
-
- @Test
- def getPlanByUserWhenNotFoundShouldThrowException(): Unit = {
- assertThatThrownBy(() => SMono.fromPublisher(testee.getPlanByUser(BOB)).block())
- .isInstanceOf(classOf[RateLimitingPlanNotFoundException])
- }
-
- @Test
- def getPlanByUserShouldThrowWhenUsernameIsNull(): Unit = {
- assertThatThrownBy(() => SMono.fromPublisher(testee.getPlanByUser(null)).block())
- .isInstanceOf(classOf[NullPointerException])
- }
-
- @Test
- def listUserShouldSucceed(): Unit = {
- SMono.fromPublisher(testee.applyPlan(BOB, PLAN_ID_1)).block()
- SMono.fromPublisher(testee.applyPlan(ALICE, PLAN_ID_1)).block()
-
- assertThat(SFlux.fromPublisher(testee.listUsers(PLAN_ID_1)).collectSeq().block().asJava).containsExactlyInAnyOrder(BOB, ALICE)
- }
-
- @Test
- def listUserShouldReturnEmptyByDefault(): Unit = {
- assertThat(SFlux.fromPublisher(testee.listUsers(PLAN_ID_1)).collectSeq().block().asJava).isEmpty()
- }
-
- @Test
- def listUserShouldThrowWhenPlanIdIsNull(): Unit = {
- assertThatThrownBy(() => SFlux.fromPublisher(testee.listUsers(null)).collectSeq().block())
- .isInstanceOf(classOf[NullPointerException])
- }
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanUsernameChangeTaskStepTest.scala b/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanUsernameChangeTaskStepTest.scala
deleted file mode 100644
index 11e512d4ef..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/RateLimitingPlanUsernameChangeTaskStepTest.scala
+++ /dev/null
@@ -1,118 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api
-
-import java.time.Duration
-
-import com.linagora.tmail.rate.limiter.api.LimitTypes.LimitTypes
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanUserRepositoryContract.{ALICE, BOB}
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanUsernameChangeTaskStepTest.{CREATION_REQUEST, CREATION_REQUEST_2}
-import com.linagora.tmail.rate.limiter.api.memory.MemoryRateLimitingPlanUserRepository
-import eu.timepit.refined.auto._
-import org.apache.james.rate.limiter.api.AllowedQuantity
-import org.assertj.core.api.Assertions.{assertThat, assertThatThrownBy}
-import org.junit.jupiter.api.{BeforeEach, Test}
-import reactor.core.scala.publisher.SMono
-
-object RateLimitingPlanUsernameChangeTaskStepTest {
- val COUNT: Count = Count(AllowedQuantity.validate(1).toOption.get)
- val SIZE: Size = Size(AllowedQuantity.validate(10000).toOption.get)
- val LIMIT_TYPES: LimitTypes = LimitTypes.liftOrThrow(Set(COUNT, SIZE))
- val RATE_LIMITATION: RateLimitation = RateLimitation("name1", Duration.ofMinutes(1), LIMIT_TYPES)
- val TRANSIT_LIMITS: TransitLimitations = TransitLimitations(Seq(RATE_LIMITATION))
- val RELAY_LIMITS: RelayLimitations = RelayLimitations(Seq(RATE_LIMITATION))
- val CREATION_REQUEST: RateLimitingPlanCreateRequest = RateLimitingPlanCreateRequest(
- name = "plan1",
- operationLimitations = OperationLimitationsType.liftOrThrow(Seq(TRANSIT_LIMITS)))
- val CREATION_REQUEST_2: RateLimitingPlanCreateRequest = RateLimitingPlanCreateRequest(
- name = "plan2",
- operationLimitations = OperationLimitationsType.liftOrThrow(Seq(TRANSIT_LIMITS)))
-}
-
-class RateLimitingPlanUsernameChangeTaskStepTest {
-
- var rateLimitingPlanRepository: RateLimitingPlanRepository = _
- var rateLimitingPlanUserRepository: RateLimitingPlanUserRepository = _
- var testee: RateLimitingPlanUsernameChangeTaskStep = _
-
- @BeforeEach
- def beforeEach(): Unit = {
- rateLimitingPlanRepository = new InMemoryRateLimitingPlanRepository()
- rateLimitingPlanUserRepository = new MemoryRateLimitingPlanUserRepository()
- testee = new RateLimitingPlanUsernameChangeTaskStep(rateLimitingPlanUserRepository)
- }
-
- @Test
- def shouldMigrateRateLimitingPlans(): Unit = {
- val planId: RateLimitingPlanId = SMono.fromPublisher(rateLimitingPlanRepository.create(CREATION_REQUEST))
- .map(_.id)
- .block()
- SMono.fromPublisher(rateLimitingPlanUserRepository.applyPlan(ALICE, planId)).block()
-
- SMono.fromPublisher(testee.changeUsername(ALICE, BOB)).block()
-
- assertThat(SMono.fromPublisher(rateLimitingPlanUserRepository.getPlanByUser(BOB)).block())
- .isEqualTo(planId)
- }
-
- @Test
- def shouldRevokeOldUserRateLimitingPlans(): Unit = {
- val planId: RateLimitingPlanId = SMono.fromPublisher(rateLimitingPlanRepository.create(CREATION_REQUEST))
- .map(_.id)
- .block()
- SMono.fromPublisher(rateLimitingPlanUserRepository.applyPlan(ALICE, planId)).block()
-
- SMono.fromPublisher(testee.changeUsername(ALICE, BOB)).block()
-
- assertThatThrownBy(() => SMono.fromPublisher(rateLimitingPlanUserRepository.getPlanByUser(ALICE)).block())
- .isInstanceOf(classOf[RateLimitingPlanNotFoundException])
- }
-
- @Test
- def shouldNotOverrideNewUserRateLimitingPlans(): Unit = {
- val planId: RateLimitingPlanId = SMono.fromPublisher(rateLimitingPlanRepository.create(CREATION_REQUEST))
- .map(_.id)
- .block()
- SMono.fromPublisher(rateLimitingPlanUserRepository.applyPlan(ALICE, planId)).block()
-
- val planId2: RateLimitingPlanId = SMono.fromPublisher(rateLimitingPlanRepository.create(CREATION_REQUEST_2))
- .map(_.id)
- .block()
- SMono.fromPublisher(rateLimitingPlanUserRepository.applyPlan(BOB, planId2)).block()
-
- SMono.fromPublisher(testee.changeUsername(ALICE, BOB)).block()
-
- assertThat(SMono.fromPublisher(rateLimitingPlanUserRepository.getPlanByUser(BOB)).block())
- .isEqualTo(planId2)
- assertThatThrownBy(() => SMono.fromPublisher(rateLimitingPlanUserRepository.getPlanByUser(ALICE)).block())
- .isInstanceOf(classOf[RateLimitingPlanNotFoundException])
- }
-
- @Test
- def migrateShouldSucceedWhenOldUserHasNoPlan(): Unit = {
- SMono.fromPublisher(testee.changeUsername(ALICE, BOB)).block()
-
- assertThatThrownBy(() => SMono.fromPublisher(rateLimitingPlanUserRepository.getPlanByUser(ALICE)).block())
- .isInstanceOf(classOf[RateLimitingPlanNotFoundException])
-
- assertThatThrownBy(() => SMono.fromPublisher(rateLimitingPlanUserRepository.getPlanByUser(BOB)).block())
- .isInstanceOf(classOf[RateLimitingPlanNotFoundException])
- }
-
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/memory/MemoryRateLimitingPlanUserRepositoryTest.scala b/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/memory/MemoryRateLimitingPlanUserRepositoryTest.scala
deleted file mode 100644
index 5489e3a3b9..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-api/src/test/scala/com/linagora/tmail/rate/limiter/api/memory/MemoryRateLimitingPlanUserRepositoryTest.scala
+++ /dev/null
@@ -1,27 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.memory
-
-import com.linagora.tmail.rate.limiter.api.{RateLimitingPlanUserRepository, RateLimitingPlanUserRepositoryContract}
-
-class MemoryRateLimitingPlanUserRepositoryTest extends RateLimitingPlanUserRepositoryContract {
- val repository: RateLimitingPlanUserRepository = new MemoryRateLimitingPlanUserRepository
-
- override def testee: RateLimitingPlanUserRepository = repository
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-cassandra/pom.xml b/tmail-backend/rate-limiter/rate-limiter-cassandra/pom.xml
index b7c204564b..069588ff39 100644
--- a/tmail-backend/rate-limiter/rate-limiter-cassandra/pom.xml
+++ b/tmail-backend/rate-limiter/rate-limiter-cassandra/pom.xml
@@ -67,19 +67,4 @@
guice
-
-
-
- net.alchim31.maven
- scala-maven-plugin
-
-
- io.github.evis
- scalafix-maven-plugin_2.13
-
- ${project.parent.parent.basedir}/.scalafix.conf
-
-
-
-
\ No newline at end of file
diff --git a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitationDTO.java b/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/java/com/linagora/tmail/rate/limiter/api/cassandra/module/CassandraRateLimitingModule.java
similarity index 56%
rename from tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitationDTO.java
rename to tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/java/com/linagora/tmail/rate/limiter/api/cassandra/module/CassandraRateLimitingModule.java
index 3b689549e6..c461069b2b 100644
--- a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitationDTO.java
+++ b/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/java/com/linagora/tmail/rate/limiter/api/cassandra/module/CassandraRateLimitingModule.java
@@ -16,23 +16,25 @@
* more details. *
********************************************************************/
-package com.linagora.tmail.webadmin.model;
+package com.linagora.tmail.rate.limiter.api.cassandra.module;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.james.user.api.UsernameChangeTaskStep;
-public record RateLimitationDTO(String name,
- Long periodInSeconds,
- Long count,
- Long size) {
- @JsonCreator
- public RateLimitationDTO(@JsonProperty(value = "name", required = true) String name,
- @JsonProperty(value = "periodInSeconds", required = true) Long periodInSeconds,
- @JsonProperty(value = "count", required = true) Long count,
- @JsonProperty(value = "size", required = true) Long size) {
- this.name = name;
- this.periodInSeconds = periodInSeconds;
- this.count = count;
- this.size = size;
+import com.google.inject.AbstractModule;
+import com.google.inject.Scopes;
+import com.google.inject.multibindings.Multibinder;
+import com.linagora.tmail.rate.limiter.api.RateLimitingRepository;
+import com.linagora.tmail.rate.limiter.api.RateLimitingUsernameChangeTaskStep;
+import com.linagora.tmail.rate.limiter.api.cassandra.CassandraRateLimitingRepository;
+
+public class CassandraRateLimitingModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(CassandraRateLimitingRepository.class).in(Scopes.SINGLETON);
+ bind(RateLimitingRepository.class).to(CassandraRateLimitingRepository.class);
+
+ Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class)
+ .addBinding()
+ .to(RateLimitingUsernameChangeTaskStep.class);
}
}
diff --git a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/CassandraRateLimitingPlanRepository.scala b/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/CassandraRateLimitingPlanRepository.scala
deleted file mode 100644
index 8dc124ce6c..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/CassandraRateLimitingPlanRepository.scala
+++ /dev/null
@@ -1,72 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.cassandra
-
-import com.google.common.base.Preconditions
-import com.linagora.tmail.rate.limiter.api.cassandra.dao.{CassandraRateLimitPlanDAO, RateLimitingPlanEntry}
-import com.linagora.tmail.rate.limiter.api.{RateLimitingPlan, RateLimitingPlanCreateRequest, RateLimitingPlanId, RateLimitingPlanName, RateLimitingPlanNotFoundException, RateLimitingPlanRepository, RateLimitingPlanResetRequest}
-import jakarta.inject.Inject
-import org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY
-import org.reactivestreams.Publisher
-import reactor.core.scala.publisher.{SFlux, SMono}
-
-class CassandraRateLimitingPlanRepository @Inject()(cassandraRateLimitPlanDAO: CassandraRateLimitPlanDAO) extends RateLimitingPlanRepository {
-
- override def create(creationRequest: RateLimitingPlanCreateRequest): Publisher[RateLimitingPlan] =
- SMono.fromCallable(() => RateLimitingPlanId.generate)
- .flatMap(planId => create(planId, RateLimitingPlanEntry.from(planId, creationRequest)))
- .flatMap(planId => SMono.fromPublisher(get(planId)))
-
- private def create(planId: RateLimitingPlanId, insertEntries: Seq[RateLimitingPlanEntry]): SMono[RateLimitingPlanId] =
- SFlux.fromIterable(insertEntries)
- .flatMap(insertEntry => cassandraRateLimitPlanDAO.insert(insertEntry))
- .`then`()
- .`then`(SMono.just(planId))
-
- override def update(resetRequest: RateLimitingPlanResetRequest): Publisher[Unit] =
- SMono.fromPublisher(cassandraRateLimitPlanDAO.planExists(resetRequest.id))
- .filter(exists => exists)
- .switchIfEmpty(SMono.error(new RateLimitingPlanNotFoundException))
- .flatMap(_ => cassandraRateLimitPlanDAO.delete(resetRequest.id))
- .`then`(create(resetRequest.id, RateLimitingPlanEntry.from(resetRequest)))
- .`then`()
-
- override def planExists(id: RateLimitingPlanId): Publisher[java.lang.Boolean] = cassandraRateLimitPlanDAO.planExists(id).map(boolean2Boolean)
-
- override def get(id: RateLimitingPlanId): Publisher[RateLimitingPlan] =
- cassandraRateLimitPlanDAO.list(id)
- .collectSeq()
- .filter(_.nonEmpty)
- .switchIfEmpty(SMono.error(new RateLimitingPlanNotFoundException))
- .map(convertEntriesToRateLimitingPlan)
-
- override def list(): Publisher[RateLimitingPlan] =
- cassandraRateLimitPlanDAO.list()
- .groupBy(planEntry => planEntry.planId)
- .flatMap(planEntryGroupFlux => planEntryGroupFlux.collectSeq(), DEFAULT_CONCURRENCY)
- .map(convertEntriesToRateLimitingPlan)
-
- private def convertEntriesToRateLimitingPlan(entries: Seq[RateLimitingPlanEntry]): RateLimitingPlan = {
- Preconditions.checkArgument(entries.nonEmpty)
- RateLimitingPlan(id = RateLimitingPlanId(entries.head.planId),
- name = RateLimitingPlanName.liftOrThrow(entries.head.planName),
- operationLimitations = entries.map(_.operationLimitations))
- }
-}
-
diff --git a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/CassandraRateLimitingPlanUserRepository.scala b/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/CassandraRateLimitingPlanUserRepository.scala
deleted file mode 100644
index 3a006c6449..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/CassandraRateLimitingPlanUserRepository.scala
+++ /dev/null
@@ -1,52 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.cassandra
-
-import com.google.common.base.Preconditions
-import com.linagora.tmail.rate.limiter.api.cassandra.dao.CassandraRateLimitPlanUserDAO
-import com.linagora.tmail.rate.limiter.api.{RateLimitingPlanId, RateLimitingPlanNotFoundException, RateLimitingPlanUserRepository}
-import jakarta.inject.Inject
-import org.apache.james.core.Username
-import reactor.core.scala.publisher.{SFlux, SMono}
-
-class CassandraRateLimitingPlanUserRepository @Inject()(dao: CassandraRateLimitPlanUserDAO) extends RateLimitingPlanUserRepository {
- override def applyPlan(username: Username, planId: RateLimitingPlanId): SMono[Unit] = {
- Preconditions.checkNotNull(username)
- Preconditions.checkNotNull(planId)
- dao.insertRecord(username, planId).`then`()
- }
-
- override def revokePlan(username: Username): SMono[Unit] = {
- Preconditions.checkNotNull(username)
- dao.clearPlanId(username).`then`()
- }
-
- override def listUsers(planId: RateLimitingPlanId): SFlux[Username] = {
- Preconditions.checkNotNull(planId)
- dao.getAllRecord
- .filter(_.rateLimitingPlanId.equals(planId))
- .map(_.username)
- }
-
- override def getPlanByUser(username: Username): SMono[RateLimitingPlanId] = {
- Preconditions.checkNotNull(username)
- dao.getPlanId(username)
- .switchIfEmpty(SMono.error(RateLimitingPlanNotFoundException()))
- }
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/dao/CassandraRateLimitPlanDAO.scala b/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/dao/CassandraRateLimitPlanDAO.scala
deleted file mode 100644
index 62a5adb3e0..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/dao/CassandraRateLimitPlanDAO.scala
+++ /dev/null
@@ -1,132 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.cassandra.dao
-
-import java.time.Duration
-import java.util
-import java.util.UUID
-
-import com.datastax.oss.driver.api.core.CqlSession
-import com.datastax.oss.driver.api.core.`type`.{DataTypes, TupleType}
-import com.datastax.oss.driver.api.core.cql.{PreparedStatement, Row}
-import com.datastax.oss.driver.api.core.data.TupleValue
-import com.datastax.oss.driver.api.querybuilder.QueryBuilder.{bindMarker, deleteFrom, insertInto, selectFrom}
-import com.linagora.tmail.rate.limiter.api.cassandra.table.CassandraRateLimitPlanHeaderEntry.{RATE_LIMITATION_DURATION_INDEX, RATE_LIMITATION_NAME_INDEX, RATE_LIMITS_INDEX}
-import com.linagora.tmail.rate.limiter.api.cassandra.table.CassandraRateLimitPlanTable.{OPERATION_LIMITATION_NAME, PLAN_ID, PLAN_NAME, RATE_LIMITATIONS, TABLE_NAME}
-import com.linagora.tmail.rate.limiter.api.{LimitTypes, OperationLimitations, RateLimitation, RateLimitingPlanCreateRequest, RateLimitingPlanId, RateLimitingPlanResetRequest}
-import jakarta.inject.Inject
-import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor
-import reactor.core.scala.publisher.{SFlux, SMono}
-
-import scala.jdk.CollectionConverters._
-
-object RateLimitingPlanEntry {
- def from(planId: RateLimitingPlanId, createRequest: RateLimitingPlanCreateRequest): Seq[RateLimitingPlanEntry] =
- createRequest.operationLimitations.value
- .map(operationLimitations => RateLimitingPlanEntry(
- planId = planId.value,
- planName = createRequest.name.value,
- operationLimitations = operationLimitations))
-
- def from(resetRequest: RateLimitingPlanResetRequest): Seq[RateLimitingPlanEntry] =
- resetRequest.operationLimitations.value
- .map(operationLimitations => RateLimitingPlanEntry(
- planId = resetRequest.id.value,
- planName = resetRequest.name.value,
- operationLimitations = operationLimitations))
-}
-
-case class RateLimitingPlanEntry(planId: UUID,
- planName: String,
- operationLimitations: OperationLimitations)
-
-class CassandraRateLimitPlanDAO @Inject()(session: CqlSession) {
- private val executor: CassandraAsyncExecutor = new CassandraAsyncExecutor(session)
- private val rateLimitationsTuple: TupleType = DataTypes.tupleOf(DataTypes.TEXT, DataTypes.BIGINT, DataTypes.frozenMapOf(DataTypes.TEXT, DataTypes.BIGINT))
-
- private val insertStatement: PreparedStatement = session.prepare(insertInto(TABLE_NAME)
- .value(PLAN_ID, bindMarker(PLAN_ID))
- .value(PLAN_NAME, bindMarker(PLAN_NAME))
- .value(OPERATION_LIMITATION_NAME, bindMarker(OPERATION_LIMITATION_NAME))
- .value(RATE_LIMITATIONS, bindMarker(RATE_LIMITATIONS))
- .build())
-
- private val deleteStatement: PreparedStatement = session.prepare(deleteFrom(TABLE_NAME)
- .whereColumn(PLAN_ID).isEqualTo(bindMarker(PLAN_ID))
- .build())
-
- private val selectStatement: PreparedStatement = session.prepare(selectFrom(TABLE_NAME)
- .all()
- .whereColumn(PLAN_ID).isEqualTo(bindMarker(PLAN_ID))
- .build())
-
- private val selectAllStatement: PreparedStatement = session.prepare(selectFrom(TABLE_NAME).all().build())
-
- def insert(insertEntry: RateLimitingPlanEntry): SMono[Void] =
- SMono.fromPublisher(executor.executeVoid(insertStatement
- .bind().setUuid(PLAN_ID, insertEntry.planId)
- .setString(PLAN_NAME, insertEntry.planName)
- .setString(OPERATION_LIMITATION_NAME, insertEntry.operationLimitations.asString())
- .setList(RATE_LIMITATIONS, toTupleList(insertEntry.operationLimitations.rateLimitations()), classOf[TupleValue])))
-
- def delete(planId: RateLimitingPlanId): SMono[Void] =
- SMono.fromPublisher(executor.executeVoid(deleteStatement.bind()
- .setUuid(PLAN_ID, planId.value)))
-
- def list(planId: RateLimitingPlanId): SFlux[RateLimitingPlanEntry] =
- SFlux.fromPublisher(executor.executeRows(selectStatement
- .bind().setUuid(PLAN_ID, planId.value)))
- .map(readRow)
-
- def planExists(planId: RateLimitingPlanId): SMono[Boolean] =
- SMono.fromPublisher(executor.executeReturnExists(selectStatement
- .bind().setUuid(PLAN_ID, planId.value)))
- .map(_.booleanValue())
-
- def list(): SFlux[RateLimitingPlanEntry] =
- SFlux.fromPublisher(executor.executeRows(selectAllStatement.bind()))
- .map(readRow)
-
- private def readRow(row: Row): RateLimitingPlanEntry = {
- val rateLimitations: Seq[RateLimitation] = row.getList(RATE_LIMITATIONS, classOf[TupleValue]).asScala
- .map(tupleData => {
- val limits: Map[String, Long] = tupleData.getMap(RATE_LIMITS_INDEX, classOf[String], classOf[java.lang.Long])
- .asScala.map(pair => pair._1 -> pair._2.toLong)
- .toMap
- RateLimitation(
- name = tupleData.getString(RATE_LIMITATION_NAME_INDEX),
- period = Duration.ofMillis(tupleData.getLong(RATE_LIMITATION_DURATION_INDEX)),
- limits = LimitTypes.from(limits))
- }).toSeq
-
- RateLimitingPlanEntry(
- planId = row.getUuid(PLAN_ID),
- planName = row.getString(PLAN_NAME),
- operationLimitations = OperationLimitations.liftOrThrow(row.getString(OPERATION_LIMITATION_NAME), rateLimitations))
- }
-
- private def toTupleList(rateLimitations: Seq[RateLimitation]): java.util.List[TupleValue] =
- rateLimitations.map(rateLimitation => {
- val limits: util.Map[String, Long] = rateLimitation.limits.value
- .map(limitType => limitType.asString() -> limitType.allowedQuantity().value)
- .toMap.asJava
- rateLimitationsTuple.newValue(rateLimitation.name, rateLimitation.period.toMillis, limits)
- }).asJava
-
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/dao/CassandraRateLimitPlanUserDAO.scala b/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/dao/CassandraRateLimitPlanUserDAO.scala
deleted file mode 100644
index 213a71de4c..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/dao/CassandraRateLimitPlanUserDAO.scala
+++ /dev/null
@@ -1,81 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.cassandra.dao
-
-import com.datastax.oss.driver.api.core.CqlSession
-import com.datastax.oss.driver.api.core.`type`.codec.TypeCodecs
-import com.datastax.oss.driver.api.core.cql.Row
-import com.datastax.oss.driver.api.querybuilder.QueryBuilder.{bindMarker, insertInto, selectFrom, update}
-import com.linagora.tmail.rate.limiter.api.{RateLimitingPlanId, UsernameToRateLimitingPlanId}
-import com.linagora.tmail.user.cassandra.TMailCassandraUsersRepositoryDataDefinition.{RATE_LIMITING_PLAN_ID, TABLE_NAME, USER}
-import jakarta.inject.Inject
-import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor
-import org.apache.james.core.Username
-import reactor.core.scala.publisher.{SFlux, SMono}
-
-class CassandraRateLimitPlanUserDAO @Inject()(session: CqlSession) {
- private val executor = new CassandraAsyncExecutor(session)
-
- private val insertStatement = session.prepare(insertInto(TABLE_NAME)
- .value(USER, bindMarker(USER))
- .value(RATE_LIMITING_PLAN_ID, bindMarker(RATE_LIMITING_PLAN_ID))
- .build())
-
- private val selectOnePlanIdStatement = session.prepare(selectFrom(TABLE_NAME).column(RATE_LIMITING_PLAN_ID)
- .whereColumn(USER).isEqualTo(bindMarker(USER))
- .build())
-
- private val selectAllStatement = session.prepare(selectFrom(TABLE_NAME)
- .columns(USER, RATE_LIMITING_PLAN_ID)
- .build())
-
- private val clearPlanIdOfUser = session.prepare(update(TABLE_NAME)
- .setColumn(RATE_LIMITING_PLAN_ID, bindMarker(RATE_LIMITING_PLAN_ID))
- .whereColumn(USER).isEqualTo(bindMarker(USER))
- .build())
-
- def insertRecord(username: Username, rateLimitingPlanId: RateLimitingPlanId): SMono[Void] =
- SMono.fromPublisher(executor.executeVoid(insertStatement.bind()
- .setString(USER, username.asString)
- .setUuid(RATE_LIMITING_PLAN_ID, rateLimitingPlanId.value)))
-
- def getPlanId(username: Username): SMono[RateLimitingPlanId] =
- SMono.fromPublisher(executor.executeSingleRow(selectOnePlanIdStatement.bind()
- .setString(USER, username.asString))
- .filter(associatedPlanIdExists)
- .map(this.readPlanId))
-
- def getAllRecord: SFlux[UsernameToRateLimitingPlanId] =
- SFlux.fromPublisher(executor.executeRows(selectAllStatement.bind())
- .filter(associatedPlanIdExists)
- .map(this.readRow))
-
- def clearPlanId(username: Username): SMono[Void] =
- SMono.fromPublisher(executor.executeVoid(clearPlanIdOfUser.bind()
- .set(USER, username.asString, TypeCodecs.TEXT)
- .setToNull(RATE_LIMITING_PLAN_ID)))
-
- private def readPlanId(row: Row): RateLimitingPlanId = RateLimitingPlanId(row.getUuid(RATE_LIMITING_PLAN_ID))
-
- private def readRow(row: Row): UsernameToRateLimitingPlanId = UsernameToRateLimitingPlanId(Username.of(row.getString(USER)),
- RateLimitingPlanId(row.getUuid(RATE_LIMITING_PLAN_ID)))
-
- private def associatedPlanIdExists(row: Row): Boolean =
- row.getUuid(RATE_LIMITING_PLAN_ID) != null
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/module/CassandraRateLimitingModule.scala b/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/module/CassandraRateLimitingModule.scala
deleted file mode 100644
index 418870b1fe..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/module/CassandraRateLimitingModule.scala
+++ /dev/null
@@ -1,52 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.cassandra.module
-
-import com.google.inject.multibindings.Multibinder
-import com.google.inject.{AbstractModule, Scopes}
-import com.linagora.tmail.rate.limiter.api.cassandra.dao.{CassandraRateLimitPlanDAO, CassandraRateLimitPlanUserDAO}
-import com.linagora.tmail.rate.limiter.api.cassandra.table.CassandraRateLimitPlanTable
-import com.linagora.tmail.rate.limiter.api.cassandra.{CassandraRateLimitingPlanRepository, CassandraRateLimitingPlanUserRepository, CassandraRateLimitingRepository}
-import com.linagora.tmail.rate.limiter.api.{RateLimitingPlanRepository, RateLimitingPlanUserRepository, RateLimitingPlanUsernameChangeTaskStep, RateLimitingRepository, RateLimitingUsernameChangeTaskStep}
-import org.apache.james.backends.cassandra.components.CassandraDataDefinition
-import org.apache.james.user.api.UsernameChangeTaskStep
-
-class CassandraRateLimitingModule() extends AbstractModule {
- override def configure(): Unit = {
- bind(classOf[CassandraRateLimitPlanDAO]).in(Scopes.SINGLETON)
- bind(classOf[CassandraRateLimitPlanUserDAO]).in(Scopes.SINGLETON)
-
- bind(classOf[RateLimitingPlanUserRepository]).to(classOf[CassandraRateLimitingPlanUserRepository])
- bind(classOf[RateLimitingPlanRepository]).to(classOf[CassandraRateLimitingPlanRepository])
-
- val multibinder = Multibinder.newSetBinder(binder, classOf[CassandraDataDefinition])
- multibinder.addBinding().toInstance(CassandraRateLimitPlanTable.MODULE)
-
- Multibinder.newSetBinder(binder(), classOf[UsernameChangeTaskStep])
- .addBinding()
- .to(classOf[RateLimitingPlanUsernameChangeTaskStep])
-
- bind(classOf[CassandraRateLimitingRepository]).in(Scopes.SINGLETON)
- bind(classOf[RateLimitingRepository]).to(classOf[CassandraRateLimitingRepository])
-
- Multibinder.newSetBinder(binder(), classOf[UsernameChangeTaskStep])
- .addBinding()
- .to(classOf[RateLimitingUsernameChangeTaskStep])
- }
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/table/CassandraRateLimitPlanTable.scala b/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/table/CassandraRateLimitPlanTable.scala
deleted file mode 100644
index 01b0c21e64..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/main/scala/com/linagora/tmail/rate/limiter/api/cassandra/table/CassandraRateLimitPlanTable.scala
+++ /dev/null
@@ -1,49 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.cassandra.table
-
-import com.datastax.oss.driver.api.core.`type`.DataTypes
-import org.apache.james.backends.cassandra.components.CassandraDataDefinition
-
-object CassandraRateLimitPlanTable {
- val TABLE_NAME: String = "rate_limit_plan"
- val PLAN_ID: String = "plan_id"
- val PLAN_NAME: String = "plan_name"
- val OPERATION_LIMITATION_NAME: String = "operation_limitation_name"
- val RATE_LIMITATIONS: String = "rate_limitations"
-
- val MODULE: CassandraDataDefinition = CassandraDataDefinition.table(TABLE_NAME)
- .comment("Hold Rate Limiting Plan - Use to management.")
- .statement(statement => types => statement
- .withPartitionKey(PLAN_ID, DataTypes.UUID)
- .withStaticColumn(PLAN_NAME, DataTypes.TEXT)
- .withClusteringColumn(OPERATION_LIMITATION_NAME, DataTypes.TEXT)
- .withColumn(RATE_LIMITATIONS,
- DataTypes.frozenListOf(DataTypes.tupleOf(
- DataTypes.TEXT,
- DataTypes.BIGINT,
- DataTypes.frozenMapOf(DataTypes.TEXT, DataTypes.BIGINT)))))
- .build
-}
-
-object CassandraRateLimitPlanHeaderEntry {
- val RATE_LIMITATION_NAME_INDEX: Int = 0
- val RATE_LIMITATION_DURATION_INDEX: Int = 1
- val RATE_LIMITS_INDEX: Int = 2
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/test/java/com/linagora/tmail/rate/limiter/api/cassandra/CacheCassandraRateLimitingPlanRepositoryTest.java b/tmail-backend/rate-limiter/rate-limiter-cassandra/src/test/java/com/linagora/tmail/rate/limiter/api/cassandra/CacheCassandraRateLimitingPlanRepositoryTest.java
deleted file mode 100644
index 4cfb60e48f..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/test/java/com/linagora/tmail/rate/limiter/api/cassandra/CacheCassandraRateLimitingPlanRepositoryTest.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.cassandra;
-
-import java.time.Duration;
-import java.util.Optional;
-
-import org.apache.james.backends.cassandra.CassandraCluster;
-import org.apache.james.backends.cassandra.CassandraClusterExtension;
-import org.apache.james.backends.cassandra.components.CassandraDataDefinition;
-import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionDataDefinition;
-import org.apache.james.metrics.api.NoopGaugeRegistry;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.extension.RegisterExtension;
-
-import com.linagora.tmail.rate.limiter.api.CacheRateLimitingPlan;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanRepository;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanRepositoryContract;
-import com.linagora.tmail.rate.limiter.api.cassandra.dao.CassandraRateLimitPlanDAO;
-import com.linagora.tmail.rate.limiter.api.cassandra.table.CassandraRateLimitPlanTable;
-
-import scala.jdk.javaapi.OptionConverters;
-
-public class CacheCassandraRateLimitingPlanRepositoryTest implements RateLimitingPlanRepositoryContract {
-
- @RegisterExtension
- static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(
- CassandraDataDefinition.aggregateModules(CassandraRateLimitPlanTable.MODULE(), CassandraSchemaVersionDataDefinition.MODULE));
-
- private CacheRateLimitingPlan repository;
-
- @BeforeEach
- void setUp(CassandraCluster cassandra) {
- CassandraRateLimitPlanDAO dao = new CassandraRateLimitPlanDAO(cassandra.getConf());
- RateLimitingPlanRepository rateLimitingPlanRepository = new CassandraRateLimitingPlanRepository(dao);
- repository = new CacheRateLimitingPlan(rateLimitingPlanRepository, Duration.ofMinutes(2), new NoopGaugeRegistry(), OptionConverters.toScala(Optional.empty()));
- }
-
- @Override
- public RateLimitingPlanRepository testee() {
- return repository;
- }
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/test/java/com/linagora/tmail/rate/limiter/api/cassandra/CassandraRateLimitingPlanRepositoryTest.java b/tmail-backend/rate-limiter/rate-limiter-cassandra/src/test/java/com/linagora/tmail/rate/limiter/api/cassandra/CassandraRateLimitingPlanRepositoryTest.java
deleted file mode 100644
index 4b3ed3cd80..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/test/java/com/linagora/tmail/rate/limiter/api/cassandra/CassandraRateLimitingPlanRepositoryTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.cassandra;
-
-import org.apache.james.backends.cassandra.CassandraCluster;
-import org.apache.james.backends.cassandra.CassandraClusterExtension;
-import org.apache.james.backends.cassandra.components.CassandraDataDefinition;
-import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionDataDefinition;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.extension.RegisterExtension;
-
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanRepository;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanRepositoryContract;
-import com.linagora.tmail.rate.limiter.api.cassandra.dao.CassandraRateLimitPlanDAO;
-import com.linagora.tmail.rate.limiter.api.cassandra.table.CassandraRateLimitPlanTable;
-
-public class CassandraRateLimitingPlanRepositoryTest implements RateLimitingPlanRepositoryContract {
-
- @RegisterExtension
- static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(
- CassandraDataDefinition.aggregateModules(CassandraRateLimitPlanTable.MODULE(), CassandraSchemaVersionDataDefinition.MODULE));
-
- private CassandraRateLimitingPlanRepository repository;
-
- @BeforeEach
- void setUp(CassandraCluster cassandra) {
- CassandraRateLimitPlanDAO dao = new CassandraRateLimitPlanDAO(cassandra.getConf());
- repository = new CassandraRateLimitingPlanRepository(dao);
- }
-
- @Override
- public RateLimitingPlanRepository testee() {
- return repository;
- }
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/test/java/com/linagora/tmail/rate/limiter/api/cassandra/CassandraRateLimitingPlanUserRepositoryTest.java b/tmail-backend/rate-limiter/rate-limiter-cassandra/src/test/java/com/linagora/tmail/rate/limiter/api/cassandra/CassandraRateLimitingPlanUserRepositoryTest.java
deleted file mode 100644
index d7fbe9c26e..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-cassandra/src/test/java/com/linagora/tmail/rate/limiter/api/cassandra/CassandraRateLimitingPlanUserRepositoryTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.cassandra;
-
-import static org.apache.james.jmap.JMAPTestingConstants.BOB;
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.apache.james.backends.cassandra.CassandraCluster;
-import org.apache.james.backends.cassandra.CassandraClusterExtension;
-import org.apache.james.backends.cassandra.components.CassandraDataDefinition;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.RegisterExtension;
-
-import com.datastax.oss.driver.api.core.CqlSession;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanUserRepository;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanUserRepositoryContract;
-import com.linagora.tmail.rate.limiter.api.cassandra.dao.CassandraRateLimitPlanUserDAO;
-import com.linagora.tmail.user.cassandra.TMailCassandraUsersRepositoryDataDefinition;
-
-import reactor.core.publisher.Mono;
-
-public class CassandraRateLimitingPlanUserRepositoryTest implements RateLimitingPlanUserRepositoryContract {
- @RegisterExtension
- static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(
- CassandraDataDefinition.aggregateModules(TMailCassandraUsersRepositoryDataDefinition.MODULE));
-
- private CassandraRateLimitingPlanUserRepository repository;
-
- @BeforeEach
- void setUp(CassandraCluster cassandra) {
- CassandraRateLimitPlanUserDAO dao = new CassandraRateLimitPlanUserDAO(cassandra.getConf());
- repository = new CassandraRateLimitingPlanUserRepository(dao);
- }
-
- @Override
- public RateLimitingPlanUserRepository testee() {
- return repository;
- }
-
- @Test
- void shouldNotDeleteUserRecordWhenRevokePlanId(CassandraCluster cassandraCluster) {
- CqlSession testingSession = cassandraCluster.getConf();
-
- // Given the Bob record in the user table
- testingSession.execute(String.format("INSERT INTO user (name, realname) VALUES ('%s', '%s')", BOB.asString(), BOB.getLocalPart()));
-
- // Attach rate limiting plan for Bob
- Mono.from(repository.applyPlan(BOB, RateLimitingPlanUserRepositoryContract.PLAN_ID_1())).block();
-
- // Revoke Bob plan (e.g. as part of username change process)
- Mono.from(repository.revokePlan(BOB)).block();
-
- // Assert that the user record still exists after revoking plan, so other user associated data is not lost
- assertThat(testingSession.execute(String.format("SELECT * FROM user WHERE name = '%s'", BOB.asString()))
- .iterator()
- .next()
- .get("realname", String.class))
- .isEqualTo(BOB.getLocalPart());
- }
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-postgres/pom.xml b/tmail-backend/rate-limiter/rate-limiter-postgres/pom.xml
index 081bbcf27a..7a84d5ff8f 100644
--- a/tmail-backend/rate-limiter/rate-limiter-postgres/pom.xml
+++ b/tmail-backend/rate-limiter/rate-limiter-postgres/pom.xml
@@ -60,20 +60,4 @@
test
-
-
-
-
- net.alchim31.maven
- scala-maven-plugin
-
-
- io.github.evis
- scalafix-maven-plugin_2.13
-
- ${project.parent.parent.basedir}/.scalafix.conf
-
-
-
-
\ No newline at end of file
diff --git a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/PostgresRateLimitingPlanRepository.java b/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/PostgresRateLimitingPlanRepository.java
deleted file mode 100644
index 490086b4af..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/PostgresRateLimitingPlanRepository.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.postgres;
-
-import java.util.List;
-
-import jakarta.inject.Inject;
-
-import org.reactivestreams.Publisher;
-
-import com.google.common.base.Preconditions;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlan;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanCreateRequest;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanId;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanName;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanNotFoundException;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanRepository;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanResetRequest;
-import com.linagora.tmail.rate.limiter.api.postgres.dao.PostgresRateLimitingPlanDAO;
-
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-import scala.jdk.javaapi.CollectionConverters;
-import scala.runtime.BoxedUnit;
-
-public class PostgresRateLimitingPlanRepository implements RateLimitingPlanRepository {
- private final PostgresRateLimitingPlanDAO rateLimitingPlanRepositoryDAO;
-
- @Inject
- public PostgresRateLimitingPlanRepository(PostgresRateLimitingPlanDAO rateLimitingPlanRepositoryDAO) {
- this.rateLimitingPlanRepositoryDAO = rateLimitingPlanRepositoryDAO;
- }
-
- @Override
- public Publisher create(RateLimitingPlanCreateRequest creationRequest) {
- RateLimitingPlanId planId = RateLimitingPlanId.generate();
- return rateLimitingPlanRepositoryDAO.savePlans(RateLimitingPlanEntry.from(planId, creationRequest))
- .then(Mono.from(get(planId)));
- }
-
- @Override
- public Publisher update(RateLimitingPlanResetRequest resetRequest) {
- return rateLimitingPlanRepositoryDAO.planExists(resetRequest.id())
- .filter(exists -> exists)
- .flatMap(exists -> rateLimitingPlanRepositoryDAO.updatePlans(RateLimitingPlanEntry.from(resetRequest))
- .then(Mono.just(BoxedUnit.UNIT)))
- .switchIfEmpty(Mono.error(new RateLimitingPlanNotFoundException()));
- }
-
- @Override
- public Publisher get(RateLimitingPlanId id) {
- return rateLimitingPlanRepositoryDAO.getPlans(id)
- .collectList()
- .filter(rateLimitingPlanEntries -> !rateLimitingPlanEntries.isEmpty())
- .map(this::convertEntriesToRateLimitingPlan)
- .switchIfEmpty(Mono.error(new RateLimitingPlanNotFoundException()));
- }
-
- @Override
- public Publisher planExists(RateLimitingPlanId id) {
- return rateLimitingPlanRepositoryDAO.planExists(id);
- }
-
- @Override
- public Publisher list() {
- return rateLimitingPlanRepositoryDAO.getPlans()
- .groupBy(RateLimitingPlanEntry::planId)
- .flatMap(Flux::collectList)
- .map(this::convertEntriesToRateLimitingPlan);
- }
-
- private RateLimitingPlan convertEntriesToRateLimitingPlan(List rateLimitingPlanEntries) {
- Preconditions.checkArgument(!rateLimitingPlanEntries.isEmpty());
- return new RateLimitingPlan(new RateLimitingPlanId(rateLimitingPlanEntries.get(0).planId()),
- RateLimitingPlanName.liftOrThrow(rateLimitingPlanEntries.get(0).planName()),
- CollectionConverters.asScala(rateLimitingPlanEntries.stream().map(RateLimitingPlanEntry::operationLimitations).toList()).toSeq());
- }
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/RateLimitingPlanEntry.java b/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/RateLimitingPlanEntry.java
deleted file mode 100644
index 1af7c07d80..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/RateLimitingPlanEntry.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.postgres;
-
-import java.time.Duration;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.google.common.collect.ImmutableMap;
-import com.linagora.tmail.rate.limiter.api.LimitType;
-import com.linagora.tmail.rate.limiter.api.OperationLimitations;
-import com.linagora.tmail.rate.limiter.api.RateLimitation;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanCreateRequest;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanId;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanResetRequest;
-import com.linagora.tmail.rate.limiter.api.postgres.dao.PostgresRateLimitingDAOUtils;
-
-import scala.jdk.javaapi.CollectionConverters;
-
-public record RateLimitingPlanEntry(UUID planId,
- String planName,
- String operationLimitationName,
- List rateLimitationsDTOS) {
-
- static List from(RateLimitingPlanId rateLimitingPlanId, RateLimitingPlanCreateRequest createRequest) {
- return CollectionConverters.asJava(createRequest.operationLimitationsValue()).stream()
- .map(limitations -> new RateLimitingPlanEntry(rateLimitingPlanId.value(),
- createRequest.nameValue(),
- limitations.asString(),
- RateLimitationsDTO.listFrom(CollectionConverters.asJava(limitations.rateLimitations()))))
- .toList();
- }
-
- static List from(RateLimitingPlanResetRequest resetRequest) {
- return CollectionConverters.asJava(resetRequest.operationLimitationsValue()).stream()
- .map(limitations -> new RateLimitingPlanEntry(resetRequest.id().value(),
- resetRequest.nameValue(),
- limitations.asString(),
- RateLimitationsDTO.listFrom(CollectionConverters.asJava(limitations.rateLimitations()))))
- .toList();
- }
-
- public record RateLimitationsDTO(@JsonProperty("rateLimitationName") String rateLimitationName,
- @JsonProperty("rateLimitationPeriod") Long rateLimitationPeriod,
- @JsonProperty("limitMap") Map limitMap) {
- public static RateLimitationsDTO from(RateLimitation rateLimitation) {
- return new RateLimitationsDTO(rateLimitation.name(),
- rateLimitation.period().toMillis(),
- CollectionConverters.asJava(rateLimitation.limitsValue()).stream()
- .collect(ImmutableMap.toImmutableMap(LimitType::asString,
- PostgresRateLimitingDAOUtils::getQuantity)));
- }
-
- public static List listFrom(List rateLimitations) {
- return rateLimitations.stream()
- .map(RateLimitationsDTO::from)
- .toList();
- }
-
- public RateLimitation asRateLimitation() {
- return new RateLimitation(rateLimitationName(),
- Duration.ofMillis(rateLimitationPeriod()),
- PostgresRateLimitingDAOUtils.getLimitType(limitMap()));
- }
- }
-
- public OperationLimitations operationLimitations() {
- return OperationLimitations.liftOrThrow(
- operationLimitationName(),
- CollectionConverters.asScala(rateLimitationsDTOS().stream()
- .map(RateLimitationsDTO::asRateLimitation)
- .toList()).toSeq());
- }
-
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/dao/PostgresRateLimitingPlanDAO.java b/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/dao/PostgresRateLimitingPlanDAO.java
deleted file mode 100644
index a497a29a01..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/dao/PostgresRateLimitingPlanDAO.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.postgres.dao;
-
-import static com.linagora.tmail.rate.limiter.api.postgres.table.PostgresRateLimitPlanModule.PostgresRateLimitPlanTable.OPERATION_LIMITATION_NAME;
-import static com.linagora.tmail.rate.limiter.api.postgres.table.PostgresRateLimitPlanModule.PostgresRateLimitPlanTable.PLAN_ID;
-import static com.linagora.tmail.rate.limiter.api.postgres.table.PostgresRateLimitPlanModule.PostgresRateLimitPlanTable.PLAN_NAME;
-import static com.linagora.tmail.rate.limiter.api.postgres.table.PostgresRateLimitPlanModule.PostgresRateLimitPlanTable.RATE_LIMITATIONS;
-import static com.linagora.tmail.rate.limiter.api.postgres.table.PostgresRateLimitPlanModule.PostgresRateLimitPlanTable.TABLE_NAME;
-
-import java.util.List;
-import java.util.function.Function;
-
-import jakarta.inject.Inject;
-
-import org.apache.james.backends.postgres.utils.PostgresExecutor;
-import org.jooq.JSON;
-import org.jooq.Record;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanId;
-import com.linagora.tmail.rate.limiter.api.postgres.RateLimitingPlanEntry;
-import com.linagora.tmail.rate.limiter.api.postgres.RateLimitingPlanEntry.RateLimitationsDTO;
-
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-
-public class PostgresRateLimitingPlanDAO {
- private final PostgresExecutor postgresExecutor;
- private final ObjectMapper objectMapper;
-
- @Inject
- public PostgresRateLimitingPlanDAO(PostgresExecutor postgresExecutor) {
- this.postgresExecutor = postgresExecutor;
- objectMapper = new ObjectMapper();
- }
-
- public Mono savePlans(List planEntryList) {
- return postgresExecutor.executeVoid(dslContext -> Mono.from(dslContext.batch(planEntryList
- .stream()
- .map(planEntry -> dslContext.insertInto(TABLE_NAME)
- .set(PLAN_ID, planEntry.planId())
- .set(PLAN_NAME, planEntry.planName())
- .set(OPERATION_LIMITATION_NAME, planEntry.operationLimitationName())
- .set(RATE_LIMITATIONS, toJSON(planEntry.rateLimitationsDTOS())))
- .toList())));
- }
-
- public Mono updatePlans(List planEntryList) {
- return postgresExecutor.executeVoid(dslContext -> Mono.from(dslContext.batch(planEntryList
- .stream()
- .map(planEntry -> dslContext.update(TABLE_NAME)
- .set(PLAN_NAME, planEntry.planName())
- .set(OPERATION_LIMITATION_NAME, planEntry.operationLimitationName())
- .set(RATE_LIMITATIONS, toJSON(planEntry.rateLimitationsDTOS()))
- .where(PLAN_ID.eq(planEntry.planId())))
- .toList())));
- }
-
- public Flux getPlans(RateLimitingPlanId planId) {
- return postgresExecutor.executeRows(dslContext -> Flux.from(dslContext.selectFrom(TABLE_NAME)
- .where(PLAN_ID.eq(planId.value()))))
- .map(recordRateLimitingPlanEntryFunction());
- }
-
- public Flux getPlans() {
- return postgresExecutor.executeRows(dslContext -> Flux.from(dslContext.selectFrom(TABLE_NAME)))
- .map(recordRateLimitingPlanEntryFunction());
- }
-
- private Function recordRateLimitingPlanEntryFunction() {
- return record -> new RateLimitingPlanEntry(record.get(PLAN_ID),
- record.get(PLAN_NAME),
- record.get(OPERATION_LIMITATION_NAME),
- toRateLimitationsDTOList(record.get(RATE_LIMITATIONS)));
- }
-
- public Mono planExists(RateLimitingPlanId id) {
- return postgresExecutor.executeExists(dslContext -> dslContext.select(PLAN_ID)
- .from(TABLE_NAME)
- .where(PLAN_ID.eq(id.value())));
- }
-
- private JSON toJSON(List rateLimitations) {
- try {
- return JSON.json(objectMapper.writeValueAsString(rateLimitations));
- } catch (JsonProcessingException e) {
- throw new RuntimeException(e);
- }
- }
-
- private List toRateLimitationsDTOList(JSON json) {
- try {
- return objectMapper.readValue(json.data(), new TypeReference>() {});
- } catch (JsonProcessingException e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/OperationLimitationsDTO.java b/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/module/PostgresRateLimitingModule.java
similarity index 57%
rename from tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/OperationLimitationsDTO.java
rename to tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/module/PostgresRateLimitingModule.java
index 124d886bc4..82d26c8c69 100644
--- a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/OperationLimitationsDTO.java
+++ b/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/module/PostgresRateLimitingModule.java
@@ -16,25 +16,25 @@
* more details. *
********************************************************************/
-package com.linagora.tmail.webadmin.model;
+package com.linagora.tmail.rate.limiter.api.postgres.module;
-import java.util.List;
+import org.apache.james.user.api.UsernameChangeTaskStep;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonValue;
-import com.google.common.base.Preconditions;
+import com.google.inject.AbstractModule;
+import com.google.inject.Scopes;
+import com.google.inject.multibindings.Multibinder;
+import com.linagora.tmail.rate.limiter.api.RateLimitingRepository;
+import com.linagora.tmail.rate.limiter.api.RateLimitingUsernameChangeTaskStep;
+import com.linagora.tmail.rate.limiter.api.postgres.PostgresRateLimitingRepository;
-public class OperationLimitationsDTO {
- private final List rateLimitationDTOList;
+public class PostgresRateLimitingModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(PostgresRateLimitingRepository.class).in(Scopes.SINGLETON);
+ bind(RateLimitingRepository.class).to(PostgresRateLimitingRepository.class);
- @JsonCreator
- public OperationLimitationsDTO(List rateLimitationDTOList) {
- Preconditions.checkArgument(!rateLimitationDTOList.isEmpty(), "Operation limitation arrays must have at least one entry.");
- this.rateLimitationDTOList = rateLimitationDTOList;
- }
-
- @JsonValue
- public List getRateLimitationDTOList() {
- return rateLimitationDTOList;
+ Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class)
+ .addBinding()
+ .to(RateLimitingUsernameChangeTaskStep.class);
}
}
diff --git a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/table/PostgresRateLimitPlanModule.java b/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/table/PostgresRateLimitPlanModule.java
deleted file mode 100644
index f14a129ee6..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/java/com/linagora/tmail/rate/limiter/api/postgres/table/PostgresRateLimitPlanModule.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.postgres.table;
-
-import static com.linagora.tmail.rate.limiter.api.postgres.table.PostgresRateLimitPlanModule.PostgresRateLimitPlanTable.PLAN_ID_INDEX;
-import static com.linagora.tmail.rate.limiter.api.postgres.table.PostgresRateLimitPlanModule.PostgresRateLimitPlanTable.TABLE;
-
-import java.util.UUID;
-
-import org.apache.james.backends.postgres.PostgresDataDefinition;
-import org.apache.james.backends.postgres.PostgresIndex;
-import org.apache.james.backends.postgres.PostgresTable;
-import org.jooq.Field;
-import org.jooq.JSON;
-import org.jooq.Record;
-import org.jooq.Table;
-import org.jooq.impl.DSL;
-import org.jooq.impl.SQLDataType;
-
-public interface PostgresRateLimitPlanModule {
- interface PostgresRateLimitPlanTable {
- Table TABLE_NAME = DSL.table("rate_limit_plan");
-
- Field PLAN_ID = DSL.field("plan_id", SQLDataType.UUID.notNull());
- Field PLAN_NAME = DSL.field("plan_name", SQLDataType.VARCHAR.notNull());
- Field OPERATION_LIMITATION_NAME = DSL.field("operation_limitation_name", SQLDataType.VARCHAR.notNull());
- Field RATE_LIMITATIONS = DSL.field("rate_limitations", SQLDataType.JSON.notNull());
-
- PostgresTable TABLE = PostgresTable.name(TABLE_NAME.getName())
- .createTableStep(((dsl, tableName) -> dsl.createTableIfNotExists(tableName)
- .column(PLAN_ID)
- .column(PLAN_NAME)
- .column(OPERATION_LIMITATION_NAME)
- .column(RATE_LIMITATIONS)
- .primaryKey(PLAN_ID, OPERATION_LIMITATION_NAME)))
- .disableRowLevelSecurity()
- .build();
-
- PostgresIndex PLAN_ID_INDEX = PostgresIndex.name("rate_limit_plan_plan_id_index")
- .createIndexStep((dsl, indexName) -> dsl.createIndexIfNotExists(indexName)
- .on(TABLE_NAME, PLAN_ID));
- }
-
- PostgresDataDefinition MODULE = PostgresDataDefinition.builder()
- .addTable(TABLE)
- .addIndex(PLAN_ID_INDEX)
- .build();
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/scala/com/linagora/tmail/rate/limiter/api/postgres/dao/PostgresRateLimitPlanUserDAO.scala b/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/scala/com/linagora/tmail/rate/limiter/api/postgres/dao/PostgresRateLimitPlanUserDAO.scala
deleted file mode 100644
index a7ff6ac59f..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/scala/com/linagora/tmail/rate/limiter/api/postgres/dao/PostgresRateLimitPlanUserDAO.scala
+++ /dev/null
@@ -1,57 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.postgres.dao
-
-import java.util.UUID
-
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanId
-import com.linagora.tmail.user.postgres.TMailPostgresUserDataDefinition.PostgresUserTable.{RATE_LIMITING_PLAN_ID, TABLE_NAME, USERNAME}
-import jakarta.inject.Inject
-import org.apache.james.backends.postgres.utils.PostgresExecutor
-import org.apache.james.core.Username
-import reactor.core.publisher.{Flux, Mono}
-
-class PostgresRateLimitPlanUserDAO @Inject()(postgresExecutor: PostgresExecutor) {
- def insert(username: Username, rateLimitingPlanId: RateLimitingPlanId): Mono[Void] =
- postgresExecutor.executeVoid(dsl => Mono.from(dsl.insertInto(TABLE_NAME)
- .set(USERNAME, username.asString())
- .set(RATE_LIMITING_PLAN_ID, rateLimitingPlanId.value)
- .onConflict(USERNAME)
- .doUpdate()
- .set(RATE_LIMITING_PLAN_ID, rateLimitingPlanId.value)))
-
- def getPlanIdByUser(username: Username): Mono[RateLimitingPlanId] =
- postgresExecutor.executeRow(dsl => Mono.from(dsl.select(RATE_LIMITING_PLAN_ID)
- .from(TABLE_NAME)
- .where(USERNAME.eq(username.asString()))))
- .filter(_.get(RATE_LIMITING_PLAN_ID) != null)
- .map(record => RateLimitingPlanId(record.get(RATE_LIMITING_PLAN_ID)))
-
- def getUsersByPlanId(planId: RateLimitingPlanId): Flux[Username] =
- postgresExecutor.executeRows(dsl => Flux.from(dsl.select(USERNAME)
- .from(TABLE_NAME)
- .where(RATE_LIMITING_PLAN_ID.eq(planId.value))))
- .filter(_.get(USERNAME) != null)
- .map(record => Username.of(record.get(USERNAME)))
-
- def clearPlan(username: Username): Mono[Void] =
- postgresExecutor.executeVoid(dsl => Mono.from(dsl.update(TABLE_NAME)
- .set(RATE_LIMITING_PLAN_ID, null.asInstanceOf[UUID])
- .where(USERNAME.eq(username.asString()))))
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/scala/com/linagora/tmail/rate/limiter/api/postgres/dao/PostgresRateLimitingDAOUtils.scala b/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/scala/com/linagora/tmail/rate/limiter/api/postgres/dao/PostgresRateLimitingDAOUtils.scala
deleted file mode 100644
index 3c9eb78f42..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/scala/com/linagora/tmail/rate/limiter/api/postgres/dao/PostgresRateLimitingDAOUtils.scala
+++ /dev/null
@@ -1,37 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.postgres.dao
-
-import com.linagora.tmail.rate.limiter.api.LimitTypes.LimitTypes
-import com.linagora.tmail.rate.limiter.api.{LimitType, LimitTypes}
-
-import scala.jdk.CollectionConverters._
-
-object PostgresRateLimitingDAOUtils {
- def getQuantity(limitType: LimitType): Long = {
- limitType.allowedQuantity().value.longValue
- }
-
- def getLimitType(limitTypeAndAllowedQuantity: java.util.Map[String, java.lang.Long]): LimitTypes = {
- val limitTypes = limitTypeAndAllowedQuantity.asScala
- .map(map => LimitType.liftOrThrow(map._1, map._2.toLong))
- .toSet
- LimitTypes.liftOrThrow(limitTypes)
- }
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/scala/com/linagora/tmail/rate/limiter/api/postgres/module/PostgresRateLimitingModule.scala b/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/scala/com/linagora/tmail/rate/limiter/api/postgres/module/PostgresRateLimitingModule.scala
deleted file mode 100644
index b4f71abbeb..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/scala/com/linagora/tmail/rate/limiter/api/postgres/module/PostgresRateLimitingModule.scala
+++ /dev/null
@@ -1,52 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.postgres.module
-
-import com.google.inject.multibindings.Multibinder
-import com.google.inject.{AbstractModule, Scopes}
-import com.linagora.tmail.rate.limiter.api.postgres.repository.PostgresRateLimitingPlanUserRepository
-import com.linagora.tmail.rate.limiter.api.postgres.table.PostgresRateLimitPlanModule
-import com.linagora.tmail.rate.limiter.api.postgres.{PostgresRateLimitingPlanRepository, PostgresRateLimitingRepository}
-import com.linagora.tmail.rate.limiter.api.{RateLimitingPlanRepository, RateLimitingPlanUserRepository, RateLimitingPlanUsernameChangeTaskStep, RateLimitingRepository, RateLimitingUsernameChangeTaskStep}
-import org.apache.james.backends.postgres.PostgresDataDefinition
-import org.apache.james.user.api.UsernameChangeTaskStep
-
-class PostgresRateLimitingModule() extends AbstractModule {
- override def configure(): Unit = {
- bind(classOf[PostgresRateLimitingPlanUserRepository]).in(Scopes.SINGLETON)
- bind(classOf[PostgresRateLimitingPlanRepository]).in(Scopes.SINGLETON)
-
- bind(classOf[RateLimitingPlanUserRepository]).to(classOf[PostgresRateLimitingPlanUserRepository])
- bind(classOf[RateLimitingPlanRepository]).to(classOf[PostgresRateLimitingPlanRepository])
-
- val postgresRateLimitingDataDefinitions = Multibinder.newSetBinder(binder, classOf[PostgresDataDefinition])
- postgresRateLimitingDataDefinitions.addBinding().toInstance(PostgresRateLimitPlanModule.MODULE)
-
- Multibinder.newSetBinder(binder(), classOf[UsernameChangeTaskStep])
- .addBinding()
- .to(classOf[RateLimitingPlanUsernameChangeTaskStep])
-
- bind(classOf[PostgresRateLimitingRepository]).in(Scopes.SINGLETON)
- bind(classOf[RateLimitingRepository]).to(classOf[PostgresRateLimitingRepository])
-
- Multibinder.newSetBinder(binder(), classOf[UsernameChangeTaskStep])
- .addBinding()
- .to(classOf[RateLimitingUsernameChangeTaskStep])
- }
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/scala/com/linagora/tmail/rate/limiter/api/postgres/repository/PostgresRateLimitingPlanUserRepository.scala b/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/scala/com/linagora/tmail/rate/limiter/api/postgres/repository/PostgresRateLimitingPlanUserRepository.scala
deleted file mode 100644
index 9d79ac7d59..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-postgres/src/main/scala/com/linagora/tmail/rate/limiter/api/postgres/repository/PostgresRateLimitingPlanUserRepository.scala
+++ /dev/null
@@ -1,59 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.postgres.repository
-
-import com.google.common.base.Preconditions
-import com.linagora.tmail.rate.limiter.api.postgres.dao.PostgresRateLimitPlanUserDAO
-import com.linagora.tmail.rate.limiter.api.{RateLimitingPlanId, RateLimitingPlanNotFoundException, RateLimitingPlanUserRepository}
-import jakarta.inject.Inject
-import org.apache.james.core.Username
-import org.reactivestreams.Publisher
-import reactor.core.publisher.Mono
-import reactor.core.scala.publisher.SMono
-
-class PostgresRateLimitingPlanUserRepository @Inject()(dao: PostgresRateLimitPlanUserDAO) extends RateLimitingPlanUserRepository {
-
- override def applyPlan(username: Username, planId: RateLimitingPlanId): Publisher[Unit] = {
- Preconditions.checkNotNull(username)
- Preconditions.checkNotNull(planId)
-
- SMono(dao.insert(username, planId))
- .`then`()
- }
-
- override def revokePlan(username: Username): Publisher[Unit] = {
- Preconditions.checkNotNull(username)
-
- SMono(dao.clearPlan(username))
- .`then`()
- }
-
- override def listUsers(planId: RateLimitingPlanId): Publisher[Username] = {
- Preconditions.checkNotNull(planId)
-
- dao.getUsersByPlanId(planId)
- }
-
- override def getPlanByUser(username: Username): Publisher[RateLimitingPlanId] = {
- Preconditions.checkNotNull(username)
-
- dao.getPlanIdByUser(username)
- .switchIfEmpty(Mono.error(() => RateLimitingPlanNotFoundException()))
- }
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-postgres/src/test/java/com/linagora/tmail/rate/limiter/api/postgres/PostgresRateLimitingPlanRepositoryTest.java b/tmail-backend/rate-limiter/rate-limiter-postgres/src/test/java/com/linagora/tmail/rate/limiter/api/postgres/PostgresRateLimitingPlanRepositoryTest.java
deleted file mode 100644
index 074748a6e3..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-postgres/src/test/java/com/linagora/tmail/rate/limiter/api/postgres/PostgresRateLimitingPlanRepositoryTest.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.postgres;
-
-import org.apache.james.backends.postgres.PostgresExtension;
-import org.junit.jupiter.api.extension.RegisterExtension;
-
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanRepository;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanRepositoryContract;
-import com.linagora.tmail.rate.limiter.api.postgres.dao.PostgresRateLimitingPlanDAO;
-import com.linagora.tmail.rate.limiter.api.postgres.table.PostgresRateLimitPlanModule;
-
-public class PostgresRateLimitingPlanRepositoryTest implements RateLimitingPlanRepositoryContract {
- @RegisterExtension
- static PostgresExtension postgresExtension = PostgresExtension.withoutRowLevelSecurity(PostgresRateLimitPlanModule.MODULE);
-
- @Override
- public RateLimitingPlanRepository testee() {
- return new PostgresRateLimitingPlanRepository(new PostgresRateLimitingPlanDAO(postgresExtension.getDefaultPostgresExecutor()));
- }
-}
diff --git a/tmail-backend/rate-limiter/rate-limiter-postgres/src/test/java/com/linagora/tmail/rate/limiter/api/postgres/PostgresRateLimitingPlanUserRepositoryTest.java b/tmail-backend/rate-limiter/rate-limiter-postgres/src/test/java/com/linagora/tmail/rate/limiter/api/postgres/PostgresRateLimitingPlanUserRepositoryTest.java
deleted file mode 100644
index 434130b6fd..0000000000
--- a/tmail-backend/rate-limiter/rate-limiter-postgres/src/test/java/com/linagora/tmail/rate/limiter/api/postgres/PostgresRateLimitingPlanUserRepositoryTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.rate.limiter.api.postgres;
-
-import static org.apache.james.jmap.JMAPTestingConstants.BOB;
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.apache.james.backends.postgres.PostgresDataDefinition;
-import org.apache.james.backends.postgres.PostgresExtension;
-import org.jooq.impl.DSL;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.RegisterExtension;
-
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanUserRepository;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanUserRepositoryContract;
-import com.linagora.tmail.rate.limiter.api.postgres.dao.PostgresRateLimitPlanUserDAO;
-import com.linagora.tmail.rate.limiter.api.postgres.repository.PostgresRateLimitingPlanUserRepository;
-import com.linagora.tmail.user.postgres.TMailPostgresUserDataDefinition;
-
-import reactor.core.publisher.Mono;
-
-class PostgresRateLimitingPlanUserRepositoryTest implements RateLimitingPlanUserRepositoryContract {
- @RegisterExtension
- static PostgresExtension postgresExtension = PostgresExtension.withoutRowLevelSecurity(
- PostgresDataDefinition.aggregateModules(TMailPostgresUserDataDefinition.MODULE));
-
- private PostgresRateLimitingPlanUserRepository repository;
-
- @BeforeEach
- void setUp() {
- repository = new PostgresRateLimitingPlanUserRepository(new PostgresRateLimitPlanUserDAO(postgresExtension.getDefaultPostgresExecutor()));
- }
-
- @Override
- public RateLimitingPlanUserRepository testee() {
- return repository;
- }
-
- @Test
- void shouldNotDeleteUserRecordWhenDeleteSettings() {
- // Given the Bob record in the user table
- postgresExtension.getDefaultPostgresExecutor()
- .executeVoid(dslContext -> Mono.from(dslContext.insertInto(DSL.table("users"), DSL.field("username"), DSL.field("hashed_password"))
- .values(BOB.asString(), "hashedPassword")))
- .block();
-
- // Set Bob rate limiting plan
- Mono.from(repository.applyPlan(BOB, RateLimitingPlanUserRepositoryContract.PLAN_ID_1())).block();
-
- // Revoke Bob rate limiting plan (e.g. as part of username change process)
- Mono.from(repository.revokePlan(BOB)).block();
-
- // Assert that the user record still exists after revoke associated plan, so other user associated data is not lost
- String storedHashPassword = postgresExtension.getDefaultPostgresExecutor()
- .executeRow(dslContext -> Mono.from(dslContext.select(DSL.field("hashed_password"))
- .from(DSL.table("users"))
- .where(DSL.field("username").eq(BOB.asString()))))
- .block()
- .get("hashed_password", String.class);
- assertThat(storedHashPassword).isEqualTo("hashedPassword");
- }
-}
diff --git a/tmail-backend/webadmin/webadmin-rate-limit/pom.xml b/tmail-backend/webadmin/webadmin-rate-limit/pom.xml
index aefd6d6963..c3f831ce18 100644
--- a/tmail-backend/webadmin/webadmin-rate-limit/pom.xml
+++ b/tmail-backend/webadmin/webadmin-rate-limit/pom.xml
@@ -96,20 +96,4 @@
test
-
-
-
-
- net.alchim31.maven
- scala-maven-plugin
-
-
- io.github.evis
- scalafix-maven-plugin_2.13
-
- ${project.parent.parent.basedir}/.scalafix.conf
-
-
-
-
\ No newline at end of file
diff --git a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/RateLimitPlanUserRoutes.java b/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/RateLimitPlanUserRoutes.java
deleted file mode 100644
index f742304102..0000000000
--- a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/RateLimitPlanUserRoutes.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.webadmin;
-
-import static org.apache.james.webadmin.Constants.SEPARATOR;
-import static org.eclipse.jetty.http.HttpStatus.NOT_FOUND_404;
-
-import jakarta.inject.Inject;
-
-import org.apache.james.core.Username;
-import org.apache.james.user.api.UsersRepository;
-import org.apache.james.user.api.UsersRepositoryException;
-import org.apache.james.webadmin.Routes;
-import org.apache.james.webadmin.utils.ErrorResponder;
-import org.apache.james.webadmin.utils.JsonTransformer;
-import org.apache.james.webadmin.utils.Responses;
-
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanId;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanNotFoundException;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanRepository;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanUserRepository;
-import com.linagora.tmail.webadmin.model.RateLimitingPlanIdResponse;
-
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-import spark.Request;
-import spark.Route;
-import spark.Service;
-
-public class RateLimitPlanUserRoutes implements Routes {
- private static final String USERNAME_PARAM = ":username";
- private static final String PLAN_ID_PARAM = ":planId";
- private static final String RATE_LIMIT_BASE_PATH = SEPARATOR + "rate-limit-plans";
- private static final String USERS_PATH = SEPARATOR + "users";
- private static final String ATTACH_PLAN_TO_USER_PATH = USERS_PATH + SEPARATOR + USERNAME_PARAM + RATE_LIMIT_BASE_PATH
- + SEPARATOR + PLAN_ID_PARAM;
- private static final String GET_USERS_OF_PLAN_PATH = RATE_LIMIT_BASE_PATH + SEPARATOR + PLAN_ID_PARAM + USERS_PATH;
- private static final String REVOKE_PLAN_OF_USER_PATH = USERS_PATH + SEPARATOR + USERNAME_PARAM + RATE_LIMIT_BASE_PATH;
- private static final String GET_PLAN_OF_USER_PATH = USERS_PATH + SEPARATOR + USERNAME_PARAM + RATE_LIMIT_BASE_PATH;
-
- private final RateLimitingPlanUserRepository planUserRepository;
- private final RateLimitingPlanRepository planRepository;
- private final UsersRepository usersRepository;
- private final JsonTransformer jsonTransformer;
-
- @Inject
- public RateLimitPlanUserRoutes(RateLimitingPlanUserRepository planUserRepository, RateLimitingPlanRepository planRepository,
- UsersRepository usersRepository, JsonTransformer jsonTransformer) {
- this.planUserRepository = planUserRepository;
- this.planRepository = planRepository;
- this.usersRepository = usersRepository;
- this.jsonTransformer = jsonTransformer;
- }
-
- @Override
- public String getBasePath() {
- return RATE_LIMIT_BASE_PATH;
- }
-
- @Override
- public void define(Service service) {
- service.put(ATTACH_PLAN_TO_USER_PATH, attachPlanToUser(), jsonTransformer);
- service.get(GET_USERS_OF_PLAN_PATH, getUsersOfPlan(), jsonTransformer);
- service.get(GET_PLAN_OF_USER_PATH, getPlanOfUser(), jsonTransformer);
- service.delete(REVOKE_PLAN_OF_USER_PATH, revokePlanOfUser(), jsonTransformer);
- }
-
- private Route attachPlanToUser() {
- return (request, response) -> {
- Username username = extractUsername(request);
- RateLimitingPlanId planId = extractPlanId(request);
- userPreconditions(username);
- planIdPreconditions(planId);
- return Mono.from(planUserRepository.applyPlan(username, planId))
- .then(Mono.just(Responses.returnNoContent(response)))
- .block();
- };
- }
-
- private Route getUsersOfPlan() {
- return (request, response) -> {
- RateLimitingPlanId planId = extractPlanId(request);
- planIdPreconditions(planId);
- return Flux.from(planUserRepository.listUsers(planId))
- .map(Username::asString)
- .collectList()
- .block();
- };
- }
-
- private Route getPlanOfUser() {
- return (request, response) -> {
- Username username = extractUsername(request);
- userPreconditions(username);
- return Mono.from(planUserRepository.getPlanByUser(username))
- .map(planId -> new RateLimitingPlanIdResponse(planId.value().toString()))
- .onErrorResume(RateLimitingPlanNotFoundException.class, e -> {
- throw ErrorResponder.builder()
- .statusCode(NOT_FOUND_404)
- .type(ErrorResponder.ErrorType.NOT_FOUND)
- .message(String.format("User %s does not have a plan", username.asString()))
- .haltError();
- })
- .block();
- };
- }
-
- private Route revokePlanOfUser() {
- return (request, response) -> {
- Username username = extractUsername(request);
- userPreconditions(username);
- return Mono.from(planUserRepository.revokePlan(username))
- .then(Mono.just(Responses.returnNoContent(response)))
- .block();
- };
- }
-
- private Username extractUsername(Request request) {
- return Username.of(request.params(USERNAME_PARAM));
- }
-
- private RateLimitingPlanId extractPlanId(Request request) {
- return RateLimitingPlanId.parse(request.params(PLAN_ID_PARAM));
- }
-
- public void userPreconditions(Username username) throws UsersRepositoryException {
- if (!usersRepository.contains(username)) {
- throw ErrorResponder.builder()
- .statusCode(NOT_FOUND_404)
- .type(ErrorResponder.ErrorType.NOT_FOUND)
- .message(String.format("User %s does not exist", username.asString()))
- .haltError();
- }
- }
-
- public void planIdPreconditions(RateLimitingPlanId planId) {
- if (!Mono.from(planRepository.planExists(planId)).block()) {
- throw ErrorResponder.builder()
- .statusCode(NOT_FOUND_404)
- .type(ErrorResponder.ErrorType.NOT_FOUND)
- .message(String.format("Plan id %s does not exist", planId.value().toString()))
- .haltError();
- }
- }
-}
diff --git a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/RateLimitPlanRoutesModule.java b/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/RateLimitsRoutesModule.java
similarity index 87%
rename from tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/RateLimitPlanRoutesModule.java
rename to tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/RateLimitsRoutesModule.java
index aeb6a2c113..c1ca008bc9 100644
--- a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/RateLimitPlanRoutesModule.java
+++ b/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/RateLimitsRoutesModule.java
@@ -23,12 +23,10 @@
import com.google.inject.AbstractModule;
import com.google.inject.multibindings.Multibinder;
-public class RateLimitPlanRoutesModule extends AbstractModule {
+public class RateLimitsRoutesModule extends AbstractModule {
@Override
protected void configure() {
Multibinder routesMultibinder = Multibinder.newSetBinder(binder(), Routes.class);
routesMultibinder.addBinding().to(RateLimitsUserRoutes.class);
- routesMultibinder.addBinding().to(RateLimitPlanUserRoutes.class);
- routesMultibinder.addBinding().to(RateLimitPlanManagementRoutes.class);
}
}
diff --git a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitingPlanCreateRequestDTO.java b/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitingPlanCreateRequestDTO.java
deleted file mode 100644
index a1b0ad7972..0000000000
--- a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitingPlanCreateRequestDTO.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.webadmin.model;
-
-import java.util.HashMap;
-import java.util.Optional;
-
-public class RateLimitingPlanCreateRequestDTO extends HashMap> {
- public static final String TRANSIT_LIMIT_KEY = "transitLimits";
- public static final String DELIVERY_LIMIT_KEY = "deliveryLimits";
- public static final String RELAY_LIMIT_KEY = "relayLimits";
-}
diff --git a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitingPlanDTO.java b/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitingPlanDTO.java
deleted file mode 100644
index 456e8f86a9..0000000000
--- a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitingPlanDTO.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.webadmin.model;
-
-import java.util.Optional;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-public record RateLimitingPlanDTO(@JsonProperty("planId") String planId,
- @JsonProperty("planName") String planName,
- @JsonProperty("transitLimits") Optional transitLimits,
- @JsonProperty("relayLimits") Optional relayLimits,
- @JsonProperty("deliveryLimits") Optional deliveryLimits) {
-}
diff --git a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitingPlanIdResponse.java b/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitingPlanIdResponse.java
deleted file mode 100644
index a51964257e..0000000000
--- a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitingPlanIdResponse.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.webadmin.model;
-
-public record RateLimitingPlanIdResponse(String planId) {
-}
diff --git a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitingPlanResetRequestDTO.java b/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitingPlanResetRequestDTO.java
deleted file mode 100644
index d475b525bc..0000000000
--- a/tmail-backend/webadmin/webadmin-rate-limit/src/main/java/com/linagora/tmail/webadmin/model/RateLimitingPlanResetRequestDTO.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.webadmin.model;
-
-import static com.linagora.tmail.webadmin.model.RateLimitingPlanCreateRequestDTO.DELIVERY_LIMIT_KEY;
-import static com.linagora.tmail.webadmin.model.RateLimitingPlanCreateRequestDTO.RELAY_LIMIT_KEY;
-import static com.linagora.tmail.webadmin.model.RateLimitingPlanCreateRequestDTO.TRANSIT_LIMIT_KEY;
-
-import java.util.Optional;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-public record RateLimitingPlanResetRequestDTO(String planName,
- Optional transitLimits,
- Optional relayLimits,
- Optional deliveryLimits) {
- @JsonCreator
- public RateLimitingPlanResetRequestDTO(@JsonProperty(value = "planName", required = true) String planName,
- @JsonProperty(TRANSIT_LIMIT_KEY) Optional transitLimits,
- @JsonProperty(RELAY_LIMIT_KEY) Optional relayLimits,
- @JsonProperty(DELIVERY_LIMIT_KEY) Optional deliveryLimits) {
- this.planName = planName;
- this.transitLimits = transitLimits;
- this.relayLimits = relayLimits;
- this.deliveryLimits = deliveryLimits;
- }
-}
diff --git a/tmail-backend/webadmin/webadmin-rate-limit/src/main/scala/com/linagora/tmail/webadmin/RateLimitPlanManagementRoutes.scala b/tmail-backend/webadmin/webadmin-rate-limit/src/main/scala/com/linagora/tmail/webadmin/RateLimitPlanManagementRoutes.scala
deleted file mode 100644
index e786e575bf..0000000000
--- a/tmail-backend/webadmin/webadmin-rate-limit/src/main/scala/com/linagora/tmail/webadmin/RateLimitPlanManagementRoutes.scala
+++ /dev/null
@@ -1,168 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.webadmin
-
-import java.time.Duration
-import java.util.Optional
-
-import com.linagora.tmail.rate.limiter.api.LimitTypes.LimitTypes
-import com.linagora.tmail.rate.limiter.api.OperationLimitations.{DELIVERY_LIMITATIONS_NAME, RELAY_LIMITATIONS_NAME, TRANSIT_LIMITATIONS_NAME}
-import com.linagora.tmail.rate.limiter.api.{LimitTypes, OperationLimitations, OperationLimitationsType, RateLimitation, RateLimitingPlan, RateLimitingPlanCreateRequest, RateLimitingPlanId, RateLimitingPlanName, RateLimitingPlanNotFoundException, RateLimitingPlanRepository, RateLimitingPlanResetRequest}
-import com.linagora.tmail.webadmin.model.RateLimitingPlanCreateRequestDTO.{DELIVERY_LIMIT_KEY, RELAY_LIMIT_KEY, TRANSIT_LIMIT_KEY}
-import com.linagora.tmail.webadmin.model.{GetAllRateLimitPlanResponseDTO, OperationLimitationsDTO, RateLimitationDTO, RateLimitingPlanCreateRequestDTO, RateLimitingPlanDTO, RateLimitingPlanIdResponse, RateLimitingPlanResetRequestDTO}
-import jakarta.inject.Inject
-import org.apache.james.webadmin.Constants.SEPARATOR
-import org.apache.james.webadmin.Routes
-import org.apache.james.webadmin.utils.{ErrorResponder, JsonExtractor, JsonTransformer, Responses}
-import org.eclipse.jetty.http.HttpStatus.{CREATED_201, NOT_FOUND_404}
-import reactor.core.scala.publisher.{SFlux, SMono}
-import spark.{Request, Response, Route, Service}
-
-import scala.jdk.CollectionConverters._
-import scala.jdk.OptionConverters._
-import scala.jdk.StreamConverters._
-
-class RateLimitPlanManagementRoutes @Inject()(planRepository: RateLimitingPlanRepository,
- jsonTransformer: JsonTransformer) extends Routes {
- private val PLAN_ID_PARAM = ":planId"
- private val PLAN_NAME_PARAM = ":planName"
- private val BASE_PATH = s"${SEPARATOR}rate-limit-plans"
- private val CREATE_A_PLAN_PATH = s"$BASE_PATH$SEPARATOR$PLAN_NAME_PARAM"
- private val UPDATE_A_PLAN_PATH = s"$BASE_PATH$SEPARATOR$PLAN_ID_PARAM"
- private val GET_A_PLAN_PATH = s"$BASE_PATH$SEPARATOR$PLAN_ID_PARAM"
- private val GET_ALL_PLAN_PATH = BASE_PATH
-
- val createRequestExtractor = new JsonExtractor[RateLimitingPlanCreateRequestDTO](classOf[RateLimitingPlanCreateRequestDTO])
- val resetRequestExtractor = new JsonExtractor[RateLimitingPlanResetRequestDTO](classOf[RateLimitingPlanResetRequestDTO])
-
- override def getBasePath: String = BASE_PATH
-
- override def define(service: Service): Unit = {
- service.post(CREATE_A_PLAN_PATH, createAPlan, jsonTransformer)
- service.put(UPDATE_A_PLAN_PATH, updateAPlan, jsonTransformer)
- service.get(GET_A_PLAN_PATH, getAPlan, jsonTransformer)
- service.get(GET_ALL_PLAN_PATH, getAllPlan, jsonTransformer)
- }
-
- private def createAPlan: Route = (request: Request, response: Response) =>
- SMono.fromPublisher(planRepository.create(toCreateRequest(request)))
- .map(plan => {
- response.status(CREATED_201)
- new RateLimitingPlanIdResponse(plan.id.serialize())
- })
- .block()
-
- private def updateAPlan: Route = (request: Request, response: Response) =>
- SMono.fromPublisher(planRepository.update(toResetRequest(request)))
- .onErrorResume { case e: RateLimitingPlanNotFoundException => SMono.error(
- ErrorResponder.builder()
- .statusCode(NOT_FOUND_404)
- .`type`(ErrorResponder.ErrorType.NOT_FOUND)
- .message("Plan does not exist")
- .haltError())
- }
- .`then`(SMono.just(Responses.returnNoContent(response)))
- .block()
-
- private def getAPlan: Route = (request: Request, response: Response) =>
- SMono.fromPublisher(planRepository.get(extractRateLimitingPlanId(request)))
- .map(toRateLimitingPlanDTO)
- .onErrorResume { case e: RateLimitingPlanNotFoundException => SMono.error(
- ErrorResponder.builder()
- .statusCode(NOT_FOUND_404)
- .`type`(ErrorResponder.ErrorType.NOT_FOUND)
- .message("Plan does not exist")
- .haltError())
- }
- .block()
-
- private def getAllPlan: Route = (request: Request, response: Response) =>
- SFlux.fromPublisher(planRepository.list())
- .map(toRateLimitingPlanDTO)
- .collectSeq()
- .map(seq => new GetAllRateLimitPlanResponseDTO(seq.toList.asJava))
- .block()
-
- private def toRateLimitingPlanDTO(rateLimitingPlan: RateLimitingPlan): RateLimitingPlanDTO =
- new RateLimitingPlanDTO(rateLimitingPlan.id.serialize(),
- rateLimitingPlan.name.value,
- toOperationLimitationsDTO(rateLimitingPlan.operationLimitations, OperationLimitations.TRANSIT_LIMITATIONS_NAME),
- toOperationLimitationsDTO(rateLimitingPlan.operationLimitations, OperationLimitations.RELAY_LIMITATIONS_NAME),
- toOperationLimitationsDTO(rateLimitingPlan.operationLimitations, OperationLimitations.DELIVERY_LIMITATIONS_NAME))
-
- private def toOperationLimitationsDTO(operationLimitation: Seq[OperationLimitations], operationType: String): Optional[OperationLimitationsDTO] =
- operationLimitation.find(_.asString().equals(operationType))
- .map(transitLimitation => new OperationLimitationsDTO(transitLimitation.rateLimitations().map(toRateLimitationDTO).toList.asJava))
- .toJava
-
- private def toRateLimitationDTO(rateLimitation: RateLimitation): RateLimitationDTO =
- new RateLimitationDTO(rateLimitation.name, rateLimitation.period.getSeconds,
- extractLimitType(rateLimitation, LimitTypes.COUNT), extractLimitType(rateLimitation, LimitTypes.SIZE))
-
- private def extractLimitType(rateLimitation: RateLimitation, typeString: String): Long =
- rateLimitation.limits.value
- .filter(_.asString().equals(typeString))
- .head
- .allowedQuantity()
- .value
-
- private def toCreateRequest(request: Request): RateLimitingPlanCreateRequest = {
- val createRequestDTO = createRequestExtractor.parse(request.body())
- val combinedLimitations = java.util.stream.Stream.of(
- createRequestDTO.getOrDefault(TRANSIT_LIMIT_KEY, Optional.empty()).map(toOperationLimitation(TRANSIT_LIMITATIONS_NAME, _)),
- createRequestDTO.getOrDefault(DELIVERY_LIMIT_KEY, Optional.empty()).map(toOperationLimitation(DELIVERY_LIMITATIONS_NAME, _)),
- createRequestDTO.getOrDefault(RELAY_LIMIT_KEY, Optional.empty()).map(toOperationLimitation(RELAY_LIMITATIONS_NAME, _)))
- .filter(_.isPresent)
- .map(_.get)
- .toScala(Seq)
-
- RateLimitingPlanCreateRequest(extractRateLimitingPlanName(request), OperationLimitationsType.liftOrThrow(combinedLimitations))
- }
-
- private def toResetRequest(request: Request): RateLimitingPlanResetRequest = {
- val resetRequestDTO = resetRequestExtractor.parse(request.body())
- val combinedLimitations = java.util.stream.Stream.of(
- resetRequestDTO.transitLimits.map(toOperationLimitation(TRANSIT_LIMITATIONS_NAME, _)),
- resetRequestDTO.deliveryLimits.map(toOperationLimitation(DELIVERY_LIMITATIONS_NAME, _)),
- resetRequestDTO.relayLimits.map(toOperationLimitation(RELAY_LIMITATIONS_NAME, _)))
- .filter(_.isPresent)
- .map(_.get)
- .toScala(Seq)
-
- RateLimitingPlanResetRequest(extractRateLimitingPlanId(request), RateLimitingPlanName.liftOrThrow(resetRequestDTO.planName),
- OperationLimitationsType.liftOrThrow(combinedLimitations))
- }
-
- private def toOperationLimitation(operationName: String, dto: OperationLimitationsDTO): OperationLimitations =
- OperationLimitations.liftOrThrow(operationName, dto.getRateLimitationDTOList
- .stream()
- .map(rateLimitationDTO => toRateLimitation(rateLimitationDTO))
- .toScala(Seq))
-
- private def toRateLimitation(dto: RateLimitationDTO): RateLimitation = {
- require(dto.periodInSeconds.>=(0), "Rate limitation period must not be negative")
- RateLimitation(name = dto.name, period = Duration.ofSeconds(dto.periodInSeconds), limits = toLimitTypes(dto.count, dto.size))
- }
-
- private def toLimitTypes(count: Long, size: Long): LimitTypes = LimitTypes.from(Map((LimitTypes.COUNT, count), (LimitTypes.SIZE, size)))
-
- private def extractRateLimitingPlanId(request: Request) = RateLimitingPlanId.parse(request.params(PLAN_ID_PARAM))
-
- private def extractRateLimitingPlanName(request: Request) = RateLimitingPlanName.liftOrThrow(request.params(PLAN_NAME_PARAM))
-}
diff --git a/tmail-backend/webadmin/webadmin-rate-limit/src/test/java/com/linagora/tmail/webadmin/RateLimitPlanManagementRoutesTest.java b/tmail-backend/webadmin/webadmin-rate-limit/src/test/java/com/linagora/tmail/webadmin/RateLimitPlanManagementRoutesTest.java
deleted file mode 100644
index 9b5c046d3a..0000000000
--- a/tmail-backend/webadmin/webadmin-rate-limit/src/test/java/com/linagora/tmail/webadmin/RateLimitPlanManagementRoutesTest.java
+++ /dev/null
@@ -1,912 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.webadmin;
-
-import static io.restassured.RestAssured.given;
-import static io.restassured.http.ContentType.JSON;
-import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.eclipse.jetty.http.HttpStatus.BAD_REQUEST_400;
-import static org.eclipse.jetty.http.HttpStatus.CREATED_201;
-import static org.eclipse.jetty.http.HttpStatus.NOT_FOUND_404;
-import static org.eclipse.jetty.http.HttpStatus.NO_CONTENT_204;
-import static org.eclipse.jetty.http.HttpStatus.OK_200;
-
-import java.util.Map;
-
-import org.apache.james.webadmin.WebAdminServer;
-import org.apache.james.webadmin.WebAdminUtils;
-import org.apache.james.webadmin.utils.JsonTransformer;
-import org.assertj.core.api.SoftAssertions;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-
-import com.linagora.tmail.rate.limiter.api.InMemoryRateLimitingPlanRepository;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanRepository;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanId;
-
-import io.restassured.RestAssured;
-import reactor.core.publisher.Mono;
-
-public class RateLimitPlanManagementRoutesTest {
- private static final String CREATE_A_PLAN_PATH = "/rate-limit-plans/%s";
- private static final String UPDATE_A_PLAN_PATH = "/rate-limit-plans/%s";
- private static final String GET_A_PLAN_PATH = "/rate-limit-plans/%s";
- private static final String GET_ALL_PLAN_PATH = "/rate-limit-plans";
-
- private WebAdminServer webAdminServer;
- private RateLimitingPlanRepository planRepository;
-
- @BeforeEach
- void setUp() {
- planRepository = new InMemoryRateLimitingPlanRepository();
- RateLimitPlanManagementRoutes routes = new RateLimitPlanManagementRoutes(planRepository, new JsonTransformer());
- webAdminServer = WebAdminUtils.createWebAdminServer(routes).start();
-
- RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer).build();
- }
-
- @AfterEach
- void tearDown() {
- webAdminServer.destroy();
- }
-
- @Nested
- class CreateAPlanTest {
- @Test
- void shouldSucceed() {
- String json = """
- {
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400,
- "count": 1000,
- "size": 4096
- }
- ],
- "relayLimits": [{
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "deliveryLimits": [{
- "name": "deliveryMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }]
- }""";
-
- Map response = given()
- .body(json)
- .post(String.format(CREATE_A_PLAN_PATH, "planName1"))
- .then()
- .statusCode(CREATED_201)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(response).containsOnlyKeys("planId");
- }
-
- @Test
- void shouldSucceedWhenOnlyTransitLimits() {
- String json = """
- {
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400,
- "count": 1000,
- "size": 4096
- }
- ]
- }""";
-
- Map response = given()
- .body(json)
- .post(String.format(CREATE_A_PLAN_PATH, "planName1"))
- .then()
- .statusCode(CREATED_201)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(response).containsOnlyKeys("planId");
- }
-
- @Test
- void shouldReturnBadRequestWhenEmptyPayLoad() {
- String json = "{}";
-
- Map errors = given()
- .body(json)
- .post(String.format(CREATE_A_PLAN_PATH, "planName1"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "Invalid arguments supplied in the user request")
- .containsEntry("details", "value should not be empty");
- }
-
- @Test
- void shouldReturnBadRequestWhenAllEntryAreNull() {
- String json = """
- {
- "transitLimits": null,
- "relayLimits": null,
- "deliveryLimits": null
- }""";
-
- Map errors = given()
- .body(json)
- .post(String.format(CREATE_A_PLAN_PATH, "planName1"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "Invalid arguments supplied in the user request")
- .containsEntry("details", "value should not be empty");
- }
-
- @Test
- void shouldReturnBadRequestWhenAEntryIsEmptyArray() {
- String json = "{\"transitLimits\":[]}";
-
- Map errors = given()
- .body(json)
- .post(String.format(CREATE_A_PLAN_PATH, "planName1"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "JSON payload of the request is not valid");
- assertThat(errors.get("details").toString()).contains("Operation limitation arrays must have at least one entry.");
- }
-
- @Test
- void shouldReturnBadRequestWhenMissingRateLimitationNameField() {
- String json = """
- {
- "transitLimits": [{
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }]
- }""";
-
- Map errors = given()
- .body(json)
- .post(String.format(CREATE_A_PLAN_PATH, "planName1"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- SoftAssertions.assertSoftly(softly -> {
- softly.assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "JSON payload of the request is not valid");
- softly.assertThat(errors.get("details").toString()).contains("Missing required creator property 'name'");
- });
- }
-
- @Test
- void shouldReturnBadRequestWhenMissingRateLimitationPeriodField() {
- String json = """
- {
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "count": 100,
- "size": 2048
- }]
- }""";
-
- Map errors = given()
- .body(json)
- .post(String.format(CREATE_A_PLAN_PATH, "planName1"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- SoftAssertions.assertSoftly(softly -> {
- softly.assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "JSON payload of the request is not valid");
- softly.assertThat(errors.get("details").toString()).contains("Missing required creator property 'periodInSeconds'");
- });
- }
-
- @Test
- void shouldReturnBadRequestWhenMissingRateLimitationCountField() {
- String json = """
- {
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "size": 2048
- }]
- }""";
-
- Map errors = given()
- .body(json)
- .post(String.format(CREATE_A_PLAN_PATH, "planName1"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- SoftAssertions.assertSoftly(softly -> {
- softly.assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "JSON payload of the request is not valid");
- softly.assertThat(errors.get("details").toString()).contains("Missing required creator property 'count'");
- });
- }
-
- @Test
- void shouldReturnBadRequestWhenNegativeCount() {
- String json = """
- {
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": -100,
- "size": 2048
- }]
- }""";
-
- Map errors = given()
- .body(json)
- .post(String.format(CREATE_A_PLAN_PATH, "planName1"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "Invalid arguments supplied in the user request")
- .containsEntry("details", "Predicate failed: (-100 > 0).");
- }
-
- @Test
- void shouldReturnBadRequestWhenMissingRateLimitationSizeField() {
- String json = """
- {
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100
- }]
- }""";
-
- Map errors = given()
- .body(json)
- .post(String.format(CREATE_A_PLAN_PATH, "planName1"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- SoftAssertions.assertSoftly(softly -> {
- softly.assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "JSON payload of the request is not valid");
- softly.assertThat(errors.get("details").toString()).contains("Missing required creator property 'size'");
- });
- }
-
- @Test
- void shouldReturnBadRequestWhenNegativeSize() {
- String json = """
- {
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": -2048
- }]
- }""";
-
- Map errors = given()
- .body(json)
- .post(String.format(CREATE_A_PLAN_PATH, "planName1"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "Invalid arguments supplied in the user request")
- .containsEntry("details", "Predicate failed: (-2048 > 0).");
- }
-
- @Test
- void shouldReturnBadRequestWhenNegativePeriod() {
- String json = """
- {
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": -3600,
- "count": 100,
- "size": 2048
- }]
- }""";
-
- Map errors = given()
- .body(json)
- .post(String.format(CREATE_A_PLAN_PATH, "planName1"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "Invalid arguments supplied in the user request")
- .containsEntry("details", "requirement failed: Rate limitation period must not be negative");
- }
-
- @Test
- void shouldSucceedWhenValidPeriod() {
- String json = """
- {
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }]
- }""";
-
- given()
- .body(json)
- .post(String.format(CREATE_A_PLAN_PATH, "planName1"))
- .then()
- .statusCode(CREATED_201)
- .contentType(JSON);
- }
- }
-
- @Nested
- class UpdateAPlanTest {
- @Test
- void shouldSucceedWhenPlanExists() {
- String createPlanJson = """
- {
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400,
- "count": 1000,
- "size": 4096
- }
- ],
- "relayLimits": [{
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "deliveryLimits": [{
- "name": "deliveryMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }]
- }""";
-
- String oldPlanId = given()
- .body(createPlanJson)
- .post(String.format(CREATE_A_PLAN_PATH, "oldPlanName"))
- .then()
- .statusCode(CREATED_201)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getString("planId");
-
- String updatePlanJson = """
- {
- "planName": "newPlanName",
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400,
- "count": 1000,
- "size": 4096
- }
- ],
- "relayLimits": [{
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "deliveryLimits": null
- }""";
-
- String response = given()
- .body(updatePlanJson)
- .put(String.format(UPDATE_A_PLAN_PATH, oldPlanId))
- .then()
- .statusCode(NO_CONTENT_204)
- .contentType(JSON)
- .extract()
- .body()
- .asString();
-
- SoftAssertions.assertSoftly(softly -> {
- softly.assertThat(response).isEmpty();
- softly.assertThat(Mono.from(planRepository.get(RateLimitingPlanId.parse(oldPlanId))).block().name())
- .isEqualTo("newPlanName");
- });
- }
-
- @Test
- void shouldReturnNotFoundWhenPlanNotFound() {
- String json = """
- {
- "planName": "newPlanName",
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400,
- "count": 1000,
- "size": 4096
- }
- ],
- "relayLimits": [{
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "deliveryLimits": null
- }""";
-
- Map errors = given()
- .body(json)
- .put(String.format(UPDATE_A_PLAN_PATH, "fbeb01f9-2f88-4c0d-8542-0cf576d5081d"))
- .then()
- .statusCode(NOT_FOUND_404)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", NOT_FOUND_404)
- .containsEntry("type", "notFound")
- .containsEntry("message", "Plan does not exist");
- }
-
- @Test
- void shouldReturnBadRequestWhenMissingPlanName() {
- String json = """
- {
- "transitLimits": null,
- "relayLimits": [{
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "deliveryLimits": null
- }""";
-
- Map errors = given()
- .body(json)
- .put(String.format(UPDATE_A_PLAN_PATH, "fbeb01f9-2f88-4c0d-8542-0cf576d5081d"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "JSON payload of the request is not valid");
- }
-
- @Test
- void shouldReturnBadRequestWhenPlanNameIsEmpty() {
- String json = """
- {
- "planName": "",
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "relayLimits": null,
- "deliveryLimits": null
- }""";
-
- Map errors = given()
- .body(json)
- .put(String.format(UPDATE_A_PLAN_PATH, "fbeb01f9-2f88-4c0d-8542-0cf576d5081d"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "Invalid arguments supplied in the user request")
- .containsEntry("details", "Rate limiting plan name should not be empty");
- }
- }
-
- @Nested
- class GetAPlanTest {
- @Test
- void shouldSucceedWhenPlanExists() {
- String createPlanJson = """
- {
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400,
- "count": 1000,
- "size": 4096
- }
- ],
- "relayLimits": [{
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "deliveryLimits": [{
- "name": "deliveryMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }]
- }""";
-
- String oldPlanId = given()
- .body(createPlanJson)
- .post(String.format(CREATE_A_PLAN_PATH, "oldPlanName"))
- .then()
- .statusCode(CREATED_201)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getString("planId");
-
- String response = given()
- .get(String.format(GET_A_PLAN_PATH, oldPlanId))
- .then()
- .statusCode(OK_200)
- .contentType(JSON)
- .extract()
- .body()
- .asString();
-
- assertThatJson(response)
- .whenIgnoringPaths("planId")
- .isEqualTo("""
- {
- "planId": "18828c8d-35c3-4ac8-bfac-1d3c0588b40c",
- "planName": "oldPlanName",
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400,
- "count": 1000,
- "size": 4096
- }
- ],
- "relayLimits": [{
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "deliveryLimits": [{
- "name": "deliveryMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }]
- }""");
- }
-
- @Test
- void shouldReturnNotFoundWhenPlanNotFound() {
- Map errors = given()
- .get(String.format(GET_A_PLAN_PATH, "fbeb01f9-2f88-4c0d-8542-0cf576d5081d"))
- .then()
- .statusCode(NOT_FOUND_404)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", NOT_FOUND_404)
- .containsEntry("type", "notFound")
- .containsEntry("message", "Plan does not exist");
- }
-
- @Test
- void shouldReturnBadRequestWhenInvalidPlanId() {
- Map errors = given()
- .get(String.format(GET_A_PLAN_PATH, "invalid planId"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "Invalid arguments supplied in the user request")
- .containsEntry("details", "Invalid UUID string: invalid planId");
- }
- }
-
- @Nested
- class GetAllPlanTest {
- @Test
- void shouldReturnPlansWhenPlansExist() {
- String createPlanJson = """
- {
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400,
- "count": 1000,
- "size": 4096
- }
- ],
- "relayLimits": [{
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "deliveryLimits": [{
- "name": "deliveryMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }]
- }""";
-
- given()
- .body(createPlanJson)
- .post(String.format(CREATE_A_PLAN_PATH, "plan1"))
- .then()
- .statusCode(CREATED_201)
- .contentType(JSON);
-
- given()
- .body(createPlanJson)
- .post(String.format(CREATE_A_PLAN_PATH, "plan2"))
- .then()
- .statusCode(CREATED_201)
- .contentType(JSON);
-
- String response = given()
- .get(GET_ALL_PLAN_PATH)
- .then()
- .statusCode(OK_200)
- .contentType(JSON)
- .extract()
- .body()
- .asString();
-
- assertThatJson(response)
- .whenIgnoringPaths("[0].planId", "[0].planName", "[1].planId", "[1].planName")
- .isEqualTo("""
- [{
- "planId": "02d64e08-488c-4094-87c8-a511b242e802",
- "planName": "plan2",
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400,
- "count": 1000,
- "size": 4096
- }
- ],
- "relayLimits": [{
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "deliveryLimits": [{
- "name": "deliveryMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }]
- },
- {
- "planId": "5bb06b65-1b54-4ff4-8bad-ec5f425d9db6",
- "planName": "plan1",
- "transitLimits": [{
- "name": "receivedMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- },
- {
- "name": "receivedMailsPerDay",
- "periodInSeconds": 86400,
- "count": 1000,
- "size": 4096
- }
- ],
- "relayLimits": [{
- "name": "relayMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }],
- "deliveryLimits": [{
- "name": "deliveryMailsPerHour",
- "periodInSeconds": 3600,
- "count": 100,
- "size": 2048
- }]
- }
- ]""");
- }
-
- @Test
- void shouldReturnEmptyByDefault() {
- String response = given()
- .get(GET_ALL_PLAN_PATH)
- .then()
- .statusCode(OK_200)
- .contentType(JSON)
- .extract()
- .body()
- .asString();
-
- assertThatJson(response).isArray().isEmpty();
- }
- }
-}
diff --git a/tmail-backend/webadmin/webadmin-rate-limit/src/test/java/com/linagora/tmail/webadmin/RateLimitPlanUsersRoutesTest.java b/tmail-backend/webadmin/webadmin-rate-limit/src/test/java/com/linagora/tmail/webadmin/RateLimitPlanUsersRoutesTest.java
deleted file mode 100644
index a11763ab10..0000000000
--- a/tmail-backend/webadmin/webadmin-rate-limit/src/test/java/com/linagora/tmail/webadmin/RateLimitPlanUsersRoutesTest.java
+++ /dev/null
@@ -1,512 +0,0 @@
-/********************************************************************
- * As a subpart of Twake Mail, this file is edited by Linagora. *
- * *
- * https://twake-mail.com/ *
- * https://linagora.com *
- * *
- * This file is subject to The Affero Gnu Public License *
- * version 3. *
- * *
- * https://www.gnu.org/licenses/agpl-3.0.en.html *
- * *
- * This program is distributed in the hope that it will be *
- * useful, but WITHOUT ANY WARRANTY; without even the implied *
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *
- * PURPOSE. See the GNU Affero General Public License for *
- * more details. *
- ********************************************************************/
-
-package com.linagora.tmail.webadmin;
-
-import static io.restassured.RestAssured.given;
-import static io.restassured.http.ContentType.JSON;
-import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.eclipse.jetty.http.HttpStatus.BAD_REQUEST_400;
-import static org.eclipse.jetty.http.HttpStatus.NOT_FOUND_404;
-import static org.eclipse.jetty.http.HttpStatus.NO_CONTENT_204;
-import static org.eclipse.jetty.http.HttpStatus.OK_200;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.util.Map;
-import java.util.stream.Stream;
-
-import org.apache.james.core.Username;
-import org.apache.james.user.api.UsersRepository;
-import org.apache.james.user.api.UsersRepositoryException;
-import org.apache.james.webadmin.WebAdminServer;
-import org.apache.james.webadmin.WebAdminUtils;
-import org.apache.james.webadmin.utils.JsonTransformer;
-import org.assertj.core.api.SoftAssertions;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-
-import com.linagora.tmail.rate.limiter.api.InMemoryRateLimitingPlanRepository;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanId;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanNotFoundException;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanRepository;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanRepositoryContract;
-import com.linagora.tmail.rate.limiter.api.RateLimitingPlanUserRepository;
-import com.linagora.tmail.rate.limiter.api.memory.MemoryRateLimitingPlanUserRepository;
-
-import io.restassured.RestAssured;
-import io.restassured.path.json.JsonPath;
-import net.javacrumbs.jsonunit.core.Option;
-import reactor.core.publisher.Mono;
-
-public class RateLimitPlanUsersRoutesTest {
- private static final String ATTACH_PLAN_TO_USER_PATH = "/users/%s/rate-limit-plans/%s";
- private static final String GET_USERS_OF_PLAN_PATH = "/rate-limit-plans/%s/users";
- private static final String GET_PLAN_OF_USER_PATH = "/users/%s/rate-limit-plans";
- private static final String REVOKE_PLAN_OF_USER_PATH = "/users/%s/rate-limit-plans";
- private static final String VALID_PLAN_ID = "f5f3ce18-504a-4f32-a6f6-3ae41b096dbd";
- public static Username BOB = Username.of("bob@linagora.com");
- public static Username ANDRE = Username.of("andre@linagora.com");
-
- private static Stream usernameInvalidSource() {
- return Stream.of(
- Arguments.of("@"),
- Arguments.of("aa@aa@aa")
- );
- }
-
- private WebAdminServer webAdminServer;
- private UsersRepository usersRepository;
- private RateLimitingPlanUserRepository planUserRepository;
- private RateLimitingPlanRepository planRepository;
-
- @BeforeEach
- void setUp() {
- planRepository = new InMemoryRateLimitingPlanRepository();
- planUserRepository = new MemoryRateLimitingPlanUserRepository();
- usersRepository = mock(UsersRepository.class);
- RateLimitPlanUserRoutes rateLimitPlanUserRoutes = new RateLimitPlanUserRoutes(planUserRepository, planRepository, usersRepository, new JsonTransformer());
- webAdminServer = WebAdminUtils.createWebAdminServer(rateLimitPlanUserRoutes).start();
-
- RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer).build();
- }
-
- @AfterEach
- void tearDown() {
- webAdminServer.destroy();
- }
-
- @Nested
- class AttachPlanToUserTest {
-
- @ParameterizedTest
- @MethodSource("com.linagora.tmail.webadmin.RateLimitPlanUsersRoutesTest#usernameInvalidSource")
- void shouldReturnErrorWhenInvalidUser(String username) {
- Map errors = given()
- .put(String.format(ATTACH_PLAN_TO_USER_PATH, username, VALID_PLAN_ID))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "Invalid arguments supplied in the user request");
- }
-
- @Test
- void shouldReturnNotFoundWhenUserNotFound() {
- Map errors = given()
- .put(String.format(ATTACH_PLAN_TO_USER_PATH, BOB.asString(), VALID_PLAN_ID))
- .then()
- .statusCode(NOT_FOUND_404)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", NOT_FOUND_404)
- .containsEntry("type", "notFound")
- .containsEntry("message", "User " + BOB.asString() + " does not exist");
- }
-
- @Test
- void shouldReturnErrorWhenInvalidPlanId() {
- Map errors = given()
- .put(String.format(ATTACH_PLAN_TO_USER_PATH, BOB.asString(), "invalidUUIDString"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "Invalid arguments supplied in the user request")
- .containsEntry("details", "Invalid UUID string: invalidUUIDString");
- }
-
- @Test
- void shouldReturnNotFoundWhenPlanDoesNotExist() throws UsersRepositoryException {
- when(usersRepository.contains(BOB)).thenReturn(true);
-
- Map errors = given()
- .put(String.format(ATTACH_PLAN_TO_USER_PATH, BOB.asString(), VALID_PLAN_ID))
- .then()
- .statusCode(NOT_FOUND_404)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", NOT_FOUND_404)
- .containsEntry("type", "notFound")
- .containsEntry("message", "Plan id " + VALID_PLAN_ID + " does not exist");
- }
-
-
- @Test
- void shouldSucceedWhenPlanExists() throws UsersRepositoryException {
- when(usersRepository.contains(BOB)).thenReturn(true);
- RateLimitingPlanId planId = Mono.from(planRepository.create(RateLimitingPlanRepositoryContract.CREATION_REQUEST())).block().id();
-
- String response = given()
- .put(String.format(ATTACH_PLAN_TO_USER_PATH, BOB.asString(), planId.serialize()))
- .then()
- .statusCode(NO_CONTENT_204)
- .contentType(JSON)
- .extract()
- .body()
- .asString();
-
- SoftAssertions.assertSoftly(softly -> {
- softly.assertThat(response).isEmpty();
- softly.assertThat(Mono.from(planUserRepository.getPlanByUser(BOB)).block()).isEqualTo(planId);
- });
- }
-
- @Test
- void shouldOverridePreviousPlanWithNewPlan() throws UsersRepositoryException {
- when(usersRepository.contains(BOB)).thenReturn(true);
- RateLimitingPlanId previousPlanId = Mono.from(planRepository.create(RateLimitingPlanRepositoryContract.CREATION_REQUEST())).block().id();
- RateLimitingPlanId newPlanId = Mono.from(planRepository.create(RateLimitingPlanRepositoryContract.CREATION_REQUEST())).block().id();
-
- given()
- .put(String.format(ATTACH_PLAN_TO_USER_PATH, BOB.asString(), previousPlanId.serialize()))
- .then()
- .statusCode(NO_CONTENT_204)
- .contentType(JSON)
- .extract()
- .body()
- .asString();
-
- String response = given()
- .put(String.format(ATTACH_PLAN_TO_USER_PATH, BOB.asString(), newPlanId.serialize()))
- .then()
- .statusCode(NO_CONTENT_204)
- .contentType(JSON)
- .extract()
- .body()
- .asString();
-
- SoftAssertions.assertSoftly(softly -> {
- softly.assertThat(response).isEmpty();
- softly.assertThat(Mono.from(planUserRepository.getPlanByUser(BOB)).block()).isEqualTo(newPlanId);
- });
- }
-
- @Test
- void shouldBeIdempotent() throws UsersRepositoryException {
- when(usersRepository.contains(BOB)).thenReturn(true);
- RateLimitingPlanId planId = Mono.from(planRepository.create(RateLimitingPlanRepositoryContract.CREATION_REQUEST())).block().id();
-
- given()
- .put(String.format(ATTACH_PLAN_TO_USER_PATH, BOB.asString(), planId.serialize()))
- .then()
- .statusCode(NO_CONTENT_204)
- .contentType(JSON)
- .extract()
- .body()
- .asString();
-
- String response = given()
- .put(String.format(ATTACH_PLAN_TO_USER_PATH, BOB.asString(), planId.serialize()))
- .then()
- .statusCode(NO_CONTENT_204)
- .contentType(JSON)
- .extract()
- .body()
- .asString();
-
- SoftAssertions.assertSoftly(softly -> {
- softly.assertThat(response).isEmpty();
- softly.assertThat(Mono.from(planUserRepository.getPlanByUser(BOB)).block()).isEqualTo(planId);
- });
- }
- }
-
- @Nested
- class GetUsersOfPlan {
-
- @Test
- void shouldReturnErrorWhenInvalidPlanId() {
- Map errors = given()
- .get(String.format(GET_USERS_OF_PLAN_PATH, "invalidUUIDString"))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "Invalid arguments supplied in the user request")
- .containsEntry("details", "Invalid UUID string: invalidUUIDString");
- }
-
- @Test
- void shouldReturnNotFoundWhenPlanDoesNotExist() {
- Map errors = given()
- .get(String.format(GET_USERS_OF_PLAN_PATH, VALID_PLAN_ID))
- .then()
- .statusCode(NOT_FOUND_404)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", NOT_FOUND_404)
- .containsEntry("type", "notFound")
- .containsEntry("message", "Plan id " + VALID_PLAN_ID + " does not exist");
- }
-
-
- @Test
- void shouldReturnUsersWhenPlanExistsAndHasUsersAttached() throws UsersRepositoryException {
- when(usersRepository.contains(BOB)).thenReturn(true);
- RateLimitingPlanId planId = Mono.from(planRepository.create(RateLimitingPlanRepositoryContract.CREATION_REQUEST())).block().id();
- Mono.from(planUserRepository.applyPlan(BOB, planId)).block();
- Mono.from(planUserRepository.applyPlan(ANDRE, planId)).block();
-
- String response = given()
- .get(String.format(GET_USERS_OF_PLAN_PATH, planId.serialize()))
- .then()
- .statusCode(OK_200)
- .contentType(JSON)
- .extract()
- .body()
- .asString();
-
- assertThatJson(response)
- .when(Option.IGNORING_ARRAY_ORDER)
- .isEqualTo("[\"bob@linagora.com\", \"andre@linagora.com\"]");
- }
-
- @Test
- void shouldReturnEmptyWhenPlanExistsAndHasNonUsersAttached() throws UsersRepositoryException {
- when(usersRepository.contains(BOB)).thenReturn(true);
- RateLimitingPlanId planId = Mono.from(planRepository.create(RateLimitingPlanRepositoryContract.CREATION_REQUEST())).block().id();
-
- String response = given()
- .get(String.format(GET_USERS_OF_PLAN_PATH, planId.serialize()))
- .then()
- .statusCode(OK_200)
- .contentType(JSON)
- .extract()
- .body()
- .asString();
-
- assertThatJson(response)
- .isEqualTo("[]");
- }
- }
-
- @Nested
- class GetPlanOfUserTest {
-
- @ParameterizedTest
- @MethodSource("com.linagora.tmail.webadmin.RateLimitPlanUsersRoutesTest#usernameInvalidSource")
- void shouldReturnErrorWhenInvalidUser(String username) {
- Map errors = given()
- .get(String.format(GET_PLAN_OF_USER_PATH, username))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "Invalid arguments supplied in the user request");
- }
-
- @Test
- void shouldReturnNotFoundWhenUserNotFound() {
- Map errors = given()
- .get(String.format(GET_PLAN_OF_USER_PATH, BOB.asString()))
- .then()
- .statusCode(NOT_FOUND_404)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", NOT_FOUND_404)
- .containsEntry("type", "notFound")
- .containsEntry("message", "User " + BOB.asString() + " does not exist");
- }
-
- @Test
- void shouldReturnNotFoundWhenUserDoesNotHaveAPlan() throws UsersRepositoryException {
- when(usersRepository.contains(BOB)).thenReturn(true);
-
- Map errors = given()
- .get(String.format(GET_PLAN_OF_USER_PATH, BOB.asString()))
- .then()
- .statusCode(NOT_FOUND_404)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", NOT_FOUND_404)
- .containsEntry("type", "notFound")
- .containsEntry("message", "User " + BOB.asString() + " does not have a plan");
- }
-
- @Test
- void shouldReturnPlanIdWhenUserHasAPlan() throws UsersRepositoryException {
- when(usersRepository.contains(BOB)).thenReturn(true);
- RateLimitingPlanId planId = Mono.from(planRepository.create(RateLimitingPlanRepositoryContract.CREATION_REQUEST())).block().id();
- Mono.from(planUserRepository.applyPlan(BOB, planId)).block();
-
- JsonPath jsonPath = given()
- .get(String.format(GET_PLAN_OF_USER_PATH, BOB.asString()))
- .then()
- .statusCode(OK_200)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath();
-
- assertThat(jsonPath.getString("planId")).isEqualTo(planId.serialize());
- }
- }
-
- @Nested
- class RevokePlanOfUser {
-
- @ParameterizedTest
- @MethodSource("com.linagora.tmail.webadmin.RateLimitPlanUsersRoutesTest#usernameInvalidSource")
- void shouldReturnErrorWhenInvalidUser(String username) {
- Map errors = given()
- .delete(String.format(REVOKE_PLAN_OF_USER_PATH, username))
- .then()
- .statusCode(BAD_REQUEST_400)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", BAD_REQUEST_400)
- .containsEntry("type", "InvalidArgument")
- .containsEntry("message", "Invalid arguments supplied in the user request");
- }
-
- @Test
- void shouldReturnNotFoundWhenUserNotFound() {
- Map errors = given()
- .delete(String.format(REVOKE_PLAN_OF_USER_PATH, BOB.asString()))
- .then()
- .statusCode(NOT_FOUND_404)
- .contentType(JSON)
- .extract()
- .body()
- .jsonPath()
- .getMap(".");
-
- assertThat(errors)
- .containsEntry("statusCode", NOT_FOUND_404)
- .containsEntry("type", "notFound")
- .containsEntry("message", "User " + BOB.asString() + " does not exist");
- }
-
- @Test
- void shouldRevokePlanOfUser() throws UsersRepositoryException {
- when(usersRepository.contains(BOB)).thenReturn(true);
- RateLimitingPlanId planId = Mono.from(planRepository.create(RateLimitingPlanRepositoryContract.CREATION_REQUEST())).block().id();
- Mono.from(planUserRepository.applyPlan(BOB, planId)).block();
-
- String response = given()
- .delete(String.format(REVOKE_PLAN_OF_USER_PATH, BOB.asString()))
- .then()
- .statusCode(NO_CONTENT_204)
- .contentType(JSON)
- .extract()
- .body()
- .asString();
-
- assertThat(response).isEmpty();
- assertThatThrownBy(() -> Mono.from(planUserRepository.getPlanByUser(BOB)).block())
- .isInstanceOf(RateLimitingPlanNotFoundException.class);
- }
-
- @Test
- void shouldBeIdempotent() throws UsersRepositoryException {
- when(usersRepository.contains(BOB)).thenReturn(true);
- RateLimitingPlanId planId = Mono.from(planRepository.create(RateLimitingPlanRepositoryContract.CREATION_REQUEST())).block().id();
- Mono.from(planUserRepository.applyPlan(BOB, planId)).block();
-
- given()
- .delete(String.format(REVOKE_PLAN_OF_USER_PATH, BOB.asString()))
- .then()
- .statusCode(NO_CONTENT_204)
- .contentType(JSON)
- .extract()
- .body()
- .asString();
-
- String response = given()
- .delete(String.format(REVOKE_PLAN_OF_USER_PATH, BOB.asString()))
- .then()
- .statusCode(NO_CONTENT_204)
- .contentType(JSON)
- .extract()
- .body()
- .asString();
-
- SoftAssertions.assertSoftly(softly -> {
- softly.assertThat(response).isEmpty();
- softly.assertThatThrownBy(() -> Mono.from(planUserRepository.getPlanByUser(BOB)).block())
- .isInstanceOf(RateLimitingPlanNotFoundException.class);
- });
- }
- }
-}