You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2023/02/23 01:10:25 UTC

[james-project] branch master updated: JAMES-3885 Migrate users filters

This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new bae6c512d4 JAMES-3885 Migrate users filters
bae6c512d4 is described below

commit bae6c512d4f021e7ecfa7edb4322d0ca926fa364
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Wed Feb 22 11:15:58 2023 +0700

    JAMES-3885 Migrate users filters
---
 .../docs/modules/ROOT/pages/operate/webadmin.adoc  |   1 +
 .../james/modules/data/CassandraJmapModule.java    |   9 +-
 .../james/modules/data/MemoryDataJmapModule.java   |   5 +
 .../impl/FilterUsernameChangeTaskStep.java         |  62 +++++++++++
 .../impl/FilterUsernameChangeTaskStepTest.java     | 113 +++++++++++++++++++++
 .../MemoryUsernameChangeIntegrationTest.java       |  62 ++++++++++-
 src/site/markdown/server/manage-webadmin.md        |   1 +
 7 files changed, 250 insertions(+), 3 deletions(-)

diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
index 616489147c..229b6f2ad3 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
@@ -633,6 +633,7 @@ a task].
 Implemented migration steps are:
 
  - `ForwardUsernameChangeTaskStep`: creates forward from old user to new user and migrates existing forwards
+ - `FilterUsernameChangeTaskStep`: migrates users filtering rules
  - `DelegationUsernameChangeTaskStep`: migrates delegations where the impacted user is either delegatee or delegator
 
 Response codes:
diff --git a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java
index 9fd16bf28f..77e64336b9 100644
--- a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java
+++ b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java
@@ -27,6 +27,7 @@ import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTOModule;
 import org.apache.james.jmap.api.access.AccessTokenRepository;
 import org.apache.james.jmap.api.filtering.FilteringManagement;
 import org.apache.james.jmap.api.filtering.impl.EventSourcingFilteringManagement;
+import org.apache.james.jmap.api.filtering.impl.FilterUsernameChangeTaskStep;
 import org.apache.james.jmap.api.identity.CustomIdentityDAO;
 import org.apache.james.jmap.api.projections.EmailQueryView;
 import org.apache.james.jmap.api.projections.MessageFastViewProjection;
@@ -50,6 +51,7 @@ import org.apache.james.jmap.cassandra.upload.CassandraUploadRepository;
 import org.apache.james.jmap.cassandra.upload.UploadConfiguration;
 import org.apache.james.jmap.cassandra.upload.UploadDAO;
 import org.apache.james.jmap.cassandra.upload.UploadModule;
+import org.apache.james.user.api.UsernameChangeTaskStep;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Scopes;
@@ -97,7 +99,12 @@ public class CassandraJmapModule extends AbstractModule {
         cassandraDataDefinitions.addBinding().toInstance(CassandraPushSubscriptionModule.MODULE);
         cassandraDataDefinitions.addBinding().toInstance(CassandraCustomIdentityModule.MODULE());
 
-        Multibinder<EventDTOModule<? extends Event, ? extends EventDTO>> eventDTOModuleBinder = Multibinder.newSetBinder(binder(), new TypeLiteral<EventDTOModule<? extends Event, ? extends EventDTO>>() {});
+        Multibinder<EventDTOModule<? extends Event, ? extends EventDTO>> eventDTOModuleBinder = Multibinder.newSetBinder(binder(), new TypeLiteral<>() {});
         eventDTOModuleBinder.addBinding().toInstance(FilteringRuleSetDefineDTOModules.FILTERING_RULE_SET_DEFINED);
+
+
+        Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class)
+            .addBinding()
+            .to(FilterUsernameChangeTaskStep.class);
     }
 }
diff --git a/server/container/guice/memory/src/main/java/org/apache/james/modules/data/MemoryDataJmapModule.java b/server/container/guice/memory/src/main/java/org/apache/james/modules/data/MemoryDataJmapModule.java
index f60c3919e5..55ba982be9 100644
--- a/server/container/guice/memory/src/main/java/org/apache/james/modules/data/MemoryDataJmapModule.java
+++ b/server/container/guice/memory/src/main/java/org/apache/james/modules/data/MemoryDataJmapModule.java
@@ -23,6 +23,7 @@ import org.apache.james.core.healthcheck.HealthCheck;
 import org.apache.james.jmap.api.access.AccessTokenRepository;
 import org.apache.james.jmap.api.filtering.FilteringManagement;
 import org.apache.james.jmap.api.filtering.impl.EventSourcingFilteringManagement;
+import org.apache.james.jmap.api.filtering.impl.FilterUsernameChangeTaskStep;
 import org.apache.james.jmap.api.identity.CustomIdentityDAO;
 import org.apache.james.jmap.api.projections.EmailQueryView;
 import org.apache.james.jmap.api.projections.MessageFastViewProjection;
@@ -36,6 +37,7 @@ import org.apache.james.jmap.memory.upload.InMemoryUploadRepository;
 import org.apache.james.mailbox.extractor.TextExtractor;
 import org.apache.james.mailbox.store.extractor.DefaultTextExtractor;
 import org.apache.james.mailbox.store.extractor.JsoupTextExtractor;
+import org.apache.james.user.api.UsernameChangeTaskStep;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Scopes;
@@ -70,5 +72,8 @@ public class MemoryDataJmapModule extends AbstractModule {
         Multibinder.newSetBinder(binder(), HealthCheck.class)
             .addBinding()
             .to(MessageFastViewProjectionHealthCheck.class);
+        Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class)
+            .addBinding()
+            .to(FilterUsernameChangeTaskStep.class);
     }
 }
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/impl/FilterUsernameChangeTaskStep.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/impl/FilterUsernameChangeTaskStep.java
new file mode 100644
index 0000000000..822a8fa58d
--- /dev/null
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/impl/FilterUsernameChangeTaskStep.java
@@ -0,0 +1,62 @@
+/****************************************************************
+ * 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.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.api.filtering.impl;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import org.apache.james.core.Username;
+import org.apache.james.jmap.api.filtering.FilteringManagement;
+import org.apache.james.jmap.api.filtering.Version;
+import org.apache.james.user.api.UsernameChangeTaskStep;
+import org.reactivestreams.Publisher;
+
+import reactor.core.publisher.Mono;
+
+public class FilterUsernameChangeTaskStep implements UsernameChangeTaskStep {
+    public static final Optional<Version> NO_VERSION = Optional.empty();
+
+    private final FilteringManagement filteringManagement;
+
+    @Inject
+    public FilterUsernameChangeTaskStep(FilteringManagement filteringManagement) {
+        this.filteringManagement = filteringManagement;
+    }
+
+    @Override
+    public StepName name() {
+        return new StepName("FilterUsernameChangeTaskStep");
+    }
+
+    @Override
+    public int priority() {
+        return 4;
+    }
+
+    @Override
+    public Publisher<Void> changeUsername(Username oldUsername, Username newUsername) {
+        return Mono.from(filteringManagement.listRulesForUser(oldUsername))
+            .filter(rules -> !rules.getRules().isEmpty())
+            .flatMap(rules -> Mono.from(filteringManagement.defineRulesForUser(newUsername, rules.getRules(), NO_VERSION))
+                .then(Mono.from(filteringManagement.clearRulesForUser(oldUsername))))
+            .then();
+    }
+}
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/FilterUsernameChangeTaskStepTest.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/FilterUsernameChangeTaskStepTest.java
new file mode 100644
index 0000000000..6a4c89a289
--- /dev/null
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/FilterUsernameChangeTaskStepTest.java
@@ -0,0 +1,113 @@
+/****************************************************************
+ * 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.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.api.filtering.impl;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Optional;
+
+import org.apache.james.core.Username;
+import org.apache.james.eventsourcing.eventstore.EventStore;
+import org.apache.james.eventsourcing.eventstore.memory.InMemoryEventStoreExtension;
+import org.apache.james.jmap.api.filtering.Rule;
+import org.apache.james.jmap.api.filtering.Version;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import reactor.core.publisher.Mono;
+
+@ExtendWith(InMemoryEventStoreExtension.class)
+public class FilterUsernameChangeTaskStepTest {
+    private static final Username BOB = Username.of("bob");
+    private static final Username ALICE = Username.of("alice");
+    private static final Optional<Version> NO_VERSION = Optional.empty();
+
+    private static final String NAME = "a name";
+    private static final Rule.Condition CONDITION = Rule.Condition.of(Rule.Condition.Field.CC, Rule.Condition.Comparator.CONTAINS, "something");
+    private static final Rule.Action ACTION = Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("id-01"));
+    private static final Rule.Builder RULE_BUILDER = Rule.builder().name(NAME).condition(CONDITION).action(ACTION);
+    private static final Rule RULE_1 = RULE_BUILDER.id(Rule.Id.of("1")).build();
+    private static final Rule RULE_2 = RULE_BUILDER.id(Rule.Id.of("2")).build();
+
+    private FilterUsernameChangeTaskStep testee;
+    private EventSourcingFilteringManagement filteringManagement;
+
+    @BeforeEach
+    void setup(EventStore eventStore) {
+        filteringManagement = new EventSourcingFilteringManagement(eventStore);
+        testee = new FilterUsernameChangeTaskStep(filteringManagement);
+    }
+
+    @Test
+    void shouldMigrateFilters() {
+        Mono.from(filteringManagement.defineRulesForUser(BOB, NO_VERSION, RULE_1))
+            .block();
+
+        Mono.from(testee.changeUsername(BOB, ALICE))
+            .block();
+
+        assertThat(Mono.from(filteringManagement.listRulesForUser(ALICE))
+            .block().getRules())
+            .containsOnly(RULE_1);
+    }
+
+    @Test
+    void shouldRemoveFiltersFromOriginalAccount() {
+        Mono.from(filteringManagement.defineRulesForUser(BOB, NO_VERSION, RULE_1))
+            .block();
+
+        Mono.from(testee.changeUsername(BOB, ALICE))
+            .block();
+
+        assertThat(Mono.from(filteringManagement.listRulesForUser(BOB))
+            .block().getRules())
+            .isEmpty();
+    }
+
+    @Test
+    void shouldOverrideFiltersFromDestinationAccount() {
+        Mono.from(filteringManagement.defineRulesForUser(BOB, NO_VERSION, RULE_1))
+            .block();
+        Mono.from(filteringManagement.defineRulesForUser(ALICE, NO_VERSION, RULE_2))
+            .block();
+
+        Mono.from(testee.changeUsername(BOB, ALICE))
+            .block();
+
+        assertThat(Mono.from(filteringManagement.listRulesForUser(ALICE))
+            .block().getRules())
+            .containsOnly(RULE_1);
+    }
+
+    @Test
+    void shouldNotOverrideFiltersFromDestinationAccountWhenNoDataInSourceAccount() {
+        Mono.from(filteringManagement.defineRulesForUser(ALICE, NO_VERSION, RULE_2))
+            .block();
+
+        Mono.from(testee.changeUsername(BOB, ALICE))
+            .block();
+
+        assertThat(Mono.from(filteringManagement.listRulesForUser(ALICE))
+            .block().getRules())
+            .containsOnly(RULE_2);
+    }
+}
diff --git a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java
index a16b75b904..3da89cc460 100644
--- a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java
@@ -19,7 +19,6 @@
 
 package org.apache.james.webadmin.integration.memory;
 
-import static io.restassured.RestAssured.given;
 import static org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT;
 import static org.apache.james.jmap.JMAPTestingConstants.ALICE;
 import static org.apache.james.jmap.JMAPTestingConstants.ALICE_PASSWORD;
@@ -34,28 +33,66 @@ import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.Matchers.hasSize;
 
 import java.util.List;
+import java.util.Optional;
+
+import javax.inject.Inject;
 
 import org.apache.james.GuiceJamesServer;
 import org.apache.james.JamesServerBuilder;
 import org.apache.james.JamesServerExtension;
 import org.apache.james.MemoryJamesConfiguration;
 import org.apache.james.MemoryJamesServerMain;
+import org.apache.james.core.Username;
+import org.apache.james.jmap.api.filtering.FilteringManagement;
+import org.apache.james.jmap.api.filtering.Rule;
+import org.apache.james.jmap.api.filtering.Rules;
+import org.apache.james.jmap.api.filtering.Version;
 import org.apache.james.jmap.draft.JmapGuiceProbe;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.apache.james.probe.DataProbe;
 import org.apache.james.util.Port;
 import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.GuiceProbe;
 import org.apache.james.utils.WebAdminGuiceProbe;
 import org.apache.james.webadmin.WebAdminUtils;
-import org.apache.james.webadmin.routes.TasksRoutes;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
+import com.google.inject.multibindings.Multibinder;
+
 import io.restassured.RestAssured;
 import io.restassured.specification.RequestSpecification;
+import reactor.core.publisher.Mono;
 
 class MemoryUsernameChangeIntegrationTest {
+    public static final class FilterProbe implements GuiceProbe {
+        private final FilteringManagement filteringManagement;
+
+        @Inject
+        public FilterProbe(FilteringManagement filteringManagement) {
+            this.filteringManagement = filteringManagement;
+        }
+
+        public void defineRulesForUser(Username username, List<Rule> rules, Optional<Version> ifInState) {
+            Mono.from(filteringManagement.defineRulesForUser(username, rules, ifInState))
+                .block();
+        }
+
+        public Rules listRulesForUser(Username username) {
+            return Mono.from(filteringManagement.listRulesForUser(username))
+                .block();
+        }
+    }
+
+    private static final String NAME = "a name";
+    private static final Rule.Condition CONDITION = Rule.Condition.of(Rule.Condition.Field.CC, Rule.Condition.Comparator.CONTAINS, "something");
+    private static final Rule.Action ACTION = Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("id-01"));
+    private static final Rule.Builder RULE_BUILDER = Rule.builder().name(NAME).condition(CONDITION).action(ACTION);
+    private static final Rule RULE_1 = RULE_BUILDER.id(Rule.Id.of("1")).build();
+    private static final Rule RULE_2 = RULE_BUILDER.id(Rule.Id.of("2")).build();
+    private static final Optional<Version> NO_VERSION = Optional.empty();
+
     @RegisterExtension
     static JamesServerExtension jamesServerExtension = new JamesServerBuilder<MemoryJamesConfiguration>(tmpDir ->
         MemoryJamesConfiguration.builder()
@@ -64,6 +101,8 @@ class MemoryUsernameChangeIntegrationTest {
             .usersRepository(DEFAULT)
             .build())
         .server(configuration -> MemoryJamesServerMain.createServer(configuration)
+            .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class)
+                .addBinding().to(FilterProbe.class))
             .overrideWith(new TestJMAPServerModule()))
         .build();
 
@@ -126,4 +165,23 @@ class MemoryUsernameChangeIntegrationTest {
                 .body(".", hasSize(1))
                 .body("[0]", is(CEDRIC.asString()));
     }
+
+    @Test
+    void shouldAdaptFilters(GuiceJamesServer server) {
+        FilterProbe filterProbe = server.getProbe(FilterProbe.class);
+        filterProbe.defineRulesForUser(ALICE, List.of(RULE_1), NO_VERSION);
+
+        String taskId = webAdminApi
+            .queryParam("action", "rename")
+            .post("/users/" + ALICE.asString() + "/rename/" + BOB.asString())
+            .jsonPath()
+            .get("taskId");
+
+        webAdminApi.get("/tasks/" + taskId + "/await");
+
+        assertThat(filterProbe.listRulesForUser(BOB).getRules())
+            .containsOnly(RULE_1);
+        assertThat(filterProbe.listRulesForUser(ALICE).getRules())
+            .isEmpty();
+    }
 }
diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md
index 07f89767d3..2338b0e334 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -485,6 +485,7 @@ Would migrate account data from `oldUser` to `newUser`.
 Implemented migration steps are:
 
  - `ForwardUsernameChangeTaskStep`: creates forward from old user to new user and migrates existing forwards
+ - `FilterUsernameChangeTaskStep`: migrates users filtering rules
  - `DelegationUsernameChangeTaskStep`: migrates delegations where the impacted user is either delegatee or delegator
 
 Response codes:


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org