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/05/26 10:30:45 UTC

[james-project] branch master updated (6d3033b362 -> 2a353de3fd)

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

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


    from 6d3033b362 DeletedMessagesVault API - support limit query
     new 21cbd39925 JAMES-3909 RecipientRewriteTableUserDeletionTaskStep
     new 80572f62d8 JAMES-3909 DelegationUserDeletionTaskStep
     new 8d732379d4 JAMES-3909 IdentityUserDeletionTaskStep
     new 08b5e1a8e8 JAMES-3909 PushDeleteUserDataTaskStep
     new 9e20aeef26 JAMES-3909 VacationDeleteUserTaskStep
     new 728b0ec6f0 JAMES-3909 FiltersDeleteUserDataTaskStep
     new 2a353de3fd JAMES-3909 Integration tests for data deletion

The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../james/modules/data/CassandraJmapModule.java    |   9 +
 .../modules/data/CassandraVacationModule.java      |   5 +
 .../main/java/org/apache/james/CoreDataModule.java |   7 +-
 .../java/org/apache/james/utils/DataProbeImpl.java |  16 +-
 .../james/modules/data/MemoryDataJmapModule.java   |   9 +
 .../james/modules/data/MemoryDataModule.java       |   5 +
 .../java/org/apache/james/probe/DataProbe.java     |   3 +
 .../RecipientRewriteTableUserDeletionTaskStep.java |  92 ++++++++++
 ...ep.java => DelegationUserDeletionTaskStep.java} |  27 ++-
 .../vacation/api/VacationDeleteUserTaskStep.java   |  50 +++---
 .../CassandraPushSubscriptionDAO.java              |  10 ++
 .../CassandraPushSubscriptionRepository.java       |   5 +
 .../identity/CassandraCustomIdentityDAO.scala      |   9 +
 .../filtering/FiltersDeleteUserDataTaskStep.java   |  40 ++---
 .../PushDeleteUserDataTaskStep.java                |  40 ++---
 .../PushSubscriptionRepository.java                |   2 +
 .../MemoryPushSubscriptionRepository.java          |   5 +
 .../jmap/api/identity/CustomIdentityDAO.scala      |   2 +
 .../identity/IdentityUserDeletionTaskStep.scala}   |  22 +--
 .../james/jmap/api/model/PushSubscription.scala    |   4 +
 .../memory/identity/MemoryCustomIdentityDAO.scala  |   2 +
 ...java => FiltersDeleteUserDataTaskStepTest.java} |  74 ++++----
 .../api/identity/CustomIdentityDAOContract.scala   |  27 ++-
 .../PushDeleteUserDataTaskStepTest.scala           |  63 +++++++
 .../PushSubscriptionRepositoryContract.scala       |  23 +++
 ...cala => IdentityUserDeletionTaskStepTest.scala} |  33 +++-
 ...ipientRewriteTableUserDeletionTaskStepTest.java | 137 ++++++++++++++
 .../memory/DelegationUserDeletionTaskStepTest.java |  66 +++----
 .../memory/VacationDeleteUserTaskStepTest.java     |  70 ++++++++
 .../memory/MemoryUserDeletionIntegrationTest.java  | 200 ++++++++++++++++++++-
 30 files changed, 878 insertions(+), 179 deletions(-)
 create mode 100644 server/data/data-api/src/main/java/org/apache/james/rrt/RecipientRewriteTableUserDeletionTaskStep.java
 copy server/data/data-api/src/main/java/org/apache/james/user/api/{DelegationUsernameChangeTaskStep.java => DelegationUserDeletionTaskStep.java} (59%)
 copy mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/ThreadTablePartitionKey.java => server/data/data-api/src/main/java/org/apache/james/vacation/api/VacationDeleteUserTaskStep.java (54%)
 copy mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Subject.java => server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/FiltersDeleteUserDataTaskStep.java (61%)
 copy mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MimeMessageId.java => server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStep.java (62%)
 copy server/{protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/CoreEchoMethod.scala => data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/IdentityUserDeletionTaskStep.scala} (64%)
 copy server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/{DefineRulesCommandTest.java => FiltersDeleteUserDataTaskStepTest.java} (53%)
 create mode 100644 server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStepTest.scala
 copy server/data/data-jmap/src/test/scala/org/apache/james/jmap/memory/identity/{MemoryCustomIdentityTest.scala => IdentityUserDeletionTaskStepTest.scala} (58%)
 create mode 100644 server/data/data-memory/src/test/java/org/apache/james/rrt/memory/RecipientRewriteTableUserDeletionTaskStepTest.java
 copy mailet/standard/src/test/java/org/apache/james/transport/matchers/SMTPIsAuthNetworkTest.java => server/data/data-memory/src/test/java/org/apache/james/user/memory/DelegationUserDeletionTaskStepTest.java (50%)
 create mode 100644 server/data/data-memory/src/test/java/org/apache/james/vacation/memory/VacationDeleteUserTaskStepTest.java


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


[james-project] 04/07: JAMES-3909 PushDeleteUserDataTaskStep

Posted by rc...@apache.org.
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

commit 08b5e1a8e800cdec8a443c2166651a2c5a04beb7
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri May 19 16:25:12 2023 +0700

    JAMES-3909 PushDeleteUserDataTaskStep
---
 .../CassandraPushSubscriptionDAO.java              | 10 ++++
 .../CassandraPushSubscriptionRepository.java       |  5 ++
 ...sitory.java => PushDeleteUserDataTaskStep.java} | 37 +++++++------
 .../PushSubscriptionRepository.java                |  2 +
 .../MemoryPushSubscriptionRepository.java          |  5 ++
 .../PushDeleteUserDataTaskStepTest.scala           | 63 ++++++++++++++++++++++
 .../PushSubscriptionRepositoryContract.scala       | 23 ++++++++
 7 files changed, 128 insertions(+), 17 deletions(-)

diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionDAO.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionDAO.java
index d3eb92afd7..2738600992 100644
--- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionDAO.java
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionDAO.java
@@ -73,6 +73,7 @@ public class CassandraPushSubscriptionDAO {
     private final PreparedStatement insert;
     private final PreparedStatement selectAll;
     private final PreparedStatement deleteOne;
+    private final PreparedStatement deleteAll;
 
     @Inject
     public CassandraPushSubscriptionDAO(CqlSession session, TypeStateFactory typeStateFactory) {
@@ -100,6 +101,10 @@ public class CassandraPushSubscriptionDAO {
             .whereColumn(DEVICE_CLIENT_ID).isEqualTo(bindMarker(DEVICE_CLIENT_ID))
             .build());
 
+        deleteAll = session.prepare(deleteFrom(TABLE_NAME)
+            .whereColumn(USER).isEqualTo(bindMarker(USER))
+            .build());
+
         this.typeStateFactory = typeStateFactory;
     }
 
@@ -138,6 +143,11 @@ public class CassandraPushSubscriptionDAO {
             .setString(DEVICE_CLIENT_ID, deviceClientId));
     }
 
+    public Mono<Void> deleteAll(Username username) {
+        return executor.executeVoid(deleteAll.bind()
+            .setString(USER, username.asString()));
+    }
+
     private PushSubscription toPushSubscription(Row row) {
         return PushSubscription.apply(
             PushSubscriptionId.apply(row.getUuid(ID)),
diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java
index ab901e1b62..47dd50f047 100644
--- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java
@@ -122,6 +122,11 @@ public class CassandraPushSubscriptionRepository implements PushSubscriptionRepo
             .switchIfEmpty(Mono.empty());
     }
 
+    @Override
+    public Publisher<Void> delete(Username username) {
+        return dao.deleteAll(username);
+    }
+
     @Override
     public Publisher<PushSubscription> get(Username username, Set<PushSubscriptionId> ids) {
         return dao.selectAll(username)
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStep.java
similarity index 56%
copy from server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java
copy to server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStep.java
index f8fa49f08f..21d06def90 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStep.java
@@ -19,29 +19,32 @@
 
 package org.apache.james.jmap.api.pushsubscription;
 
-import java.time.ZonedDateTime;
-import java.util.Set;
+import javax.inject.Inject;
 
 import org.apache.james.core.Username;
-import org.apache.james.jmap.api.model.PushSubscription;
-import org.apache.james.jmap.api.model.PushSubscriptionCreationRequest;
-import org.apache.james.jmap.api.model.PushSubscriptionExpiredTime;
-import org.apache.james.jmap.api.model.PushSubscriptionId;
-import org.apache.james.jmap.api.model.TypeName;
+import org.apache.james.user.api.DeleteUserDataTaskStep;
 import org.reactivestreams.Publisher;
 
-public interface PushSubscriptionRepository {
-    Publisher<PushSubscription> save(Username username, PushSubscriptionCreationRequest pushSubscriptionCreationRequest);
+public class PushDeleteUserDataTaskStep implements DeleteUserDataTaskStep {
+    private final PushSubscriptionRepository pushSubscriptionRepository;
 
-    Publisher<PushSubscriptionExpiredTime> updateExpireTime(Username username, PushSubscriptionId id, ZonedDateTime newExpire);
+    @Inject
+    public PushDeleteUserDataTaskStep(PushSubscriptionRepository pushSubscriptionRepository) {
+        this.pushSubscriptionRepository = pushSubscriptionRepository;
+    }
 
-    Publisher<Void> updateTypes(Username username, PushSubscriptionId id, Set<TypeName> types);
+    @Override
+    public StepName name() {
+        return new StepName("PushDeleteUserDataTaskStep");
+    }
 
-    Publisher<Void> validateVerificationCode(Username username, PushSubscriptionId id);
+    @Override
+    public int priority() {
+        return 3;
+    }
 
-    Publisher<Void> revoke(Username username, PushSubscriptionId id);
-
-    Publisher<PushSubscription> get(Username username, Set<PushSubscriptionId> ids);
-
-    Publisher<PushSubscription> list(Username username);
+    @Override
+    public Publisher<Void> deleteUserData(Username username) {
+        return pushSubscriptionRepository.delete(username);
+    }
 }
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java
index f8fa49f08f..f1766cc9e9 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java
@@ -41,6 +41,8 @@ public interface PushSubscriptionRepository {
 
     Publisher<Void> revoke(Username username, PushSubscriptionId id);
 
+    Publisher<Void> delete(Username username);
+
     Publisher<PushSubscription> get(Username username, Set<PushSubscriptionId> ids);
 
     Publisher<PushSubscription> list(Username username);
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java
index aff9a18454..c7cf16ac85 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java
@@ -117,6 +117,11 @@ public class MemoryPushSubscriptionRepository implements PushSubscriptionReposit
         return Mono.fromCallable(() -> table.remove(username, id)).then();
     }
 
+    @Override
+    public Publisher<Void> delete(Username username) {
+        return Mono.fromCallable(() -> table.rowMap().remove(username)).then();
+    }
+
     @Override
     public Publisher<PushSubscription> get(Username username, Set<PushSubscriptionId> ids) {
         return Flux.fromStream(table.row(username).entrySet().stream())
diff --git a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStepTest.scala b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStepTest.scala
new file mode 100644
index 0000000000..e369becf43
--- /dev/null
+++ b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStepTest.scala
@@ -0,0 +1,63 @@
+/** ****************************************************************
+ * 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.pushsubscription
+
+import java.net.URL
+import java.time.Clock
+
+import org.apache.james.jmap.api.identity.CustomIdentityDAOContract.bob
+import org.apache.james.jmap.api.model.{DeviceClientId, PushSubscriptionCreationRequest, PushSubscriptionServerURL}
+import org.apache.james.jmap.api.pushsubscription.PushSubscriptionRepositoryContract.ALICE
+import org.apache.james.jmap.memory.pushsubscription.MemoryPushSubscriptionRepository
+import org.assertj.core.api.Assertions.{assertThat, assertThatCode}
+import org.junit.jupiter.api.{BeforeEach, Test}
+import reactor.core.publisher.Flux
+import reactor.core.scala.publisher.SMono
+
+class PushDeleteUserDataTaskStepTest {
+  var pushSubscriptionRepository: PushSubscriptionRepository = _
+  var testee: PushDeleteUserDataTaskStep = _
+
+  @BeforeEach
+  def beforeEach(): Unit = {
+    pushSubscriptionRepository = new MemoryPushSubscriptionRepository(Clock.systemUTC())
+    testee = new PushDeleteUserDataTaskStep(pushSubscriptionRepository)
+  }
+
+  @Test
+  def shouldBeIdempotent(): Unit = {
+    assertThatCode(() => SMono(testee.deleteUserData(bob)).block())
+      .doesNotThrowAnyException()
+  }
+
+  @Test
+  def shouldDeleteUserData(): Unit = {
+    val validRequest = PushSubscriptionCreationRequest(
+      deviceClientId = DeviceClientId("1"),
+      url = PushSubscriptionServerURL(new URL("https://example.com/push")),
+      types = Seq(CustomTypeName1))
+    SMono.fromPublisher(pushSubscriptionRepository.save(ALICE, validRequest)).block().id
+
+    SMono(testee.deleteUserData(ALICE)).block()
+
+    assertThat(Flux.from(pushSubscriptionRepository.list(ALICE)).collectList().block())
+      .isEmpty()
+  }
+}
diff --git a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala
index 0ff84ad8e7..209563d0e9 100644
--- a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala
+++ b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala
@@ -247,6 +247,22 @@ trait PushSubscriptionRepositoryContract {
     assertThat(remaining).isEmpty()
   }
 
+  @Test
+  def deleteStoredSubscriptionShouldSucceed(): Unit = {
+    val validRequest = PushSubscriptionCreationRequest(
+      deviceClientId = DeviceClientId("1"),
+      url = PushSubscriptionServerURL(new URL("https://example.com/push")),
+      types = Seq(CustomTypeName1))
+    val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE, validRequest)).block().id
+    val singleRecordSaved = SFlux.fromPublisher(testee.get(ALICE, Set(pushSubscriptionId).asJava)).count().block()
+    assertThat(singleRecordSaved).isEqualTo(1)
+
+    SMono.fromPublisher(testee.delete(ALICE)).block()
+    val remaining = SFlux.fromPublisher(testee.get(ALICE, Set(pushSubscriptionId).asJava)).collectSeq().block().asJava
+
+    assertThat(remaining).isEmpty()
+  }
+
   @Test
   def revokeNotFoundShouldNotFail(): Unit = {
     val pushSubscriptionId = PushSubscriptionId.generate()
@@ -254,6 +270,13 @@ trait PushSubscriptionRepositoryContract {
       .doesNotThrowAnyException()
   }
 
+  @Test
+  def deleteNotFoundShouldNotFail(): Unit = {
+    val pushSubscriptionId = PushSubscriptionId.generate()
+    assertThatCode(() => SMono.fromPublisher(testee.delete(ALICE)).block())
+      .doesNotThrowAnyException()
+  }
+
   @Test
   def getStoredSubscriptionShouldSucceed(): Unit = {
     val deviceClientId1 = DeviceClientId("1")


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


[james-project] 07/07: JAMES-3909 Integration tests for data deletion

Posted by rc...@apache.org.
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

commit 2a353de3fd8028171575f68acc84605c869bd010
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed May 24 09:34:41 2023 +0700

    JAMES-3909 Integration tests for data deletion
    
    RRT, JMAP datas, Vacations, Delegations
---
 .../james/modules/data/CassandraJmapModule.java    |   9 +
 .../modules/data/CassandraVacationModule.java      |   5 +
 .../main/java/org/apache/james/CoreDataModule.java |   7 +-
 .../java/org/apache/james/utils/DataProbeImpl.java |  16 +-
 .../james/modules/data/MemoryDataJmapModule.java   |   9 +
 .../james/modules/data/MemoryDataModule.java       |   5 +
 .../java/org/apache/james/probe/DataProbe.java     |   3 +
 .../RecipientRewriteTableUserDeletionTaskStep.java |   3 +
 .../identity/CassandraCustomIdentityDAO.scala      |   2 +-
 .../james/jmap/api/model/PushSubscription.scala    |   4 +
 .../memory/MemoryUserDeletionIntegrationTest.java  | 200 ++++++++++++++++++++-
 11 files changed, 255 insertions(+), 8 deletions(-)

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 7a3e8a65f4..e1b9584628 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
@@ -30,12 +30,15 @@ import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTO;
 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.FiltersDeleteUserDataTaskStep;
 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.identity.IdentityUserDeletionTaskStep;
 import org.apache.james.jmap.api.projections.EmailQueryView;
 import org.apache.james.jmap.api.projections.MessageFastViewProjection;
 import org.apache.james.jmap.api.projections.MessageFastViewProjectionHealthCheck;
+import org.apache.james.jmap.api.pushsubscription.PushDeleteUserDataTaskStep;
 import org.apache.james.jmap.api.pushsubscription.PushSubscriptionRepository;
 import org.apache.james.jmap.api.upload.UploadRepository;
 import org.apache.james.jmap.cassandra.access.CassandraAccessModule;
@@ -57,6 +60,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.DeleteUserDataTaskStep;
 import org.apache.james.user.api.UsernameChangeTaskStep;
 import org.apache.james.utils.PropertiesProvider;
 
@@ -116,6 +120,11 @@ public class CassandraJmapModule extends AbstractModule {
         Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class)
             .addBinding()
             .to(FilterUsernameChangeTaskStep.class);
+
+        Multibinder<DeleteUserDataTaskStep> deleteUserDataTaskSteps = Multibinder.newSetBinder(binder(), DeleteUserDataTaskStep.class);
+        deleteUserDataTaskSteps.addBinding().to(FiltersDeleteUserDataTaskStep.class);
+        deleteUserDataTaskSteps.addBinding().to(IdentityUserDeletionTaskStep.class);
+        deleteUserDataTaskSteps.addBinding().to(PushDeleteUserDataTaskStep.class);
     }
 
     @Singleton
diff --git a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraVacationModule.java b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraVacationModule.java
index 61e47df883..f916dbdea3 100644
--- a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraVacationModule.java
+++ b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/data/CassandraVacationModule.java
@@ -21,7 +21,9 @@ package org.apache.james.modules.data;
 
 import org.apache.james.DefaultVacationService;
 import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.user.api.DeleteUserDataTaskStep;
 import org.apache.james.vacation.api.NotificationRegistry;
+import org.apache.james.vacation.api.VacationDeleteUserTaskStep;
 import org.apache.james.vacation.api.VacationRepository;
 import org.apache.james.vacation.api.VacationService;
 import org.apache.james.vacation.cassandra.CassandraNotificationRegistry;
@@ -47,6 +49,9 @@ public class CassandraVacationModule extends AbstractModule {
         Multibinder<CassandraModule> cassandraVacationDefinitions = Multibinder.newSetBinder(binder(), CassandraModule.class);
         cassandraVacationDefinitions.addBinding().toInstance(org.apache.james.vacation.cassandra.CassandraVacationModule.MODULE);
         cassandraVacationDefinitions.addBinding().toInstance(org.apache.james.vacation.cassandra.CassandraNotificationRegistryModule.MODULE);
+
+        Multibinder<DeleteUserDataTaskStep> deleteUserDataTaskSteps = Multibinder.newSetBinder(binder(), DeleteUserDataTaskStep.class);
+        deleteUserDataTaskSteps.addBinding().to(VacationDeleteUserTaskStep.class);
     }
 
 }
diff --git a/server/container/guice/common/src/main/java/org/apache/james/CoreDataModule.java b/server/container/guice/common/src/main/java/org/apache/james/CoreDataModule.java
index ae54975465..f04d4a3786 100644
--- a/server/container/guice/common/src/main/java/org/apache/james/CoreDataModule.java
+++ b/server/container/guice/common/src/main/java/org/apache/james/CoreDataModule.java
@@ -24,7 +24,9 @@ import java.util.Set;
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.rrt.ForwardUsernameChangeTaskStep;
+import org.apache.james.rrt.RecipientRewriteTableUserDeletionTaskStep;
 import org.apache.james.server.core.configuration.ConfigurationProvider;
+import org.apache.james.user.api.DelegationUserDeletionTaskStep;
 import org.apache.james.user.api.DeleteUserDataTaskStep;
 import org.apache.james.user.api.UsernameChangeTaskStep;
 
@@ -40,7 +42,10 @@ public class CoreDataModule extends AbstractModule {
         Multibinder.newSetBinder(binder(), UserEntityValidator.class).addBinding().to(RecipientRewriteTableUserEntityValidator.class);
 
         Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class).addBinding().to(ForwardUsernameChangeTaskStep.class);
-        Multibinder.newSetBinder(binder(), DeleteUserDataTaskStep.class);
+
+        Multibinder<DeleteUserDataTaskStep> deleteUserDataTaskSteps = Multibinder.newSetBinder(binder(), DeleteUserDataTaskStep.class);
+        deleteUserDataTaskSteps.addBinding().to(RecipientRewriteTableUserDeletionTaskStep.class);
+        deleteUserDataTaskSteps.addBinding().to(DelegationUserDeletionTaskStep.class);
     }
 
     @Provides
diff --git a/server/container/guice/common/src/main/java/org/apache/james/utils/DataProbeImpl.java b/server/container/guice/common/src/main/java/org/apache/james/utils/DataProbeImpl.java
index 7dc2815105..1bb0826fcc 100644
--- a/server/container/guice/common/src/main/java/org/apache/james/utils/DataProbeImpl.java
+++ b/server/container/guice/common/src/main/java/org/apache/james/utils/DataProbeImpl.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.utils;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -39,6 +40,7 @@ import org.apache.james.util.streams.Iterators;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
+import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 public class DataProbeImpl implements GuiceProbe, DataProbe {
@@ -48,11 +50,8 @@ public class DataProbeImpl implements GuiceProbe, DataProbe {
     private final DelegationStore delegationStore;
 
     @Inject
-    private DataProbeImpl(
-        DomainList domainList,
-        UsersRepository usersRepository,
-        RecipientRewriteTable recipientRewriteTable,
-        DelegationStore delegationStore) {
+    private DataProbeImpl(DomainList domainList, UsersRepository usersRepository,
+                          RecipientRewriteTable recipientRewriteTable, DelegationStore delegationStore) {
         this.domainList = domainList;
         this.usersRepository = usersRepository;
         this.recipientRewriteTable = recipientRewriteTable;
@@ -145,4 +144,11 @@ public class DataProbeImpl implements GuiceProbe, DataProbe {
         Mono.from(delegationStore.addAuthorizedUser(baseUser, userWithAccess))
             .block();
     }
+
+    @Override
+    public Collection<Username> listAuthorizedUsers(Username baseUser) {
+        return Flux.from(delegationStore.authorizedUsers(baseUser))
+            .collectList()
+            .block();
+    }
 }
\ No newline at end of file
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 c7974d853a..09a866669e 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
@@ -22,12 +22,15 @@ package org.apache.james.modules.data;
 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.FiltersDeleteUserDataTaskStep;
 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.identity.IdentityUserDeletionTaskStep;
 import org.apache.james.jmap.api.projections.EmailQueryView;
 import org.apache.james.jmap.api.projections.MessageFastViewProjection;
 import org.apache.james.jmap.api.projections.MessageFastViewProjectionHealthCheck;
+import org.apache.james.jmap.api.pushsubscription.PushDeleteUserDataTaskStep;
 import org.apache.james.jmap.api.upload.UploadRepository;
 import org.apache.james.jmap.memory.access.MemoryAccessTokenRepository;
 import org.apache.james.jmap.memory.identity.MemoryCustomIdentityDAO;
@@ -37,6 +40,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.DeleteUserDataTaskStep;
 import org.apache.james.user.api.UsernameChangeTaskStep;
 
 import com.google.inject.AbstractModule;
@@ -76,5 +80,10 @@ public class MemoryDataJmapModule extends AbstractModule {
         Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class)
             .addBinding()
             .to(FilterUsernameChangeTaskStep.class);
+
+        Multibinder<DeleteUserDataTaskStep> deleteUserDataTaskSteps = Multibinder.newSetBinder(binder(), DeleteUserDataTaskStep.class);
+        deleteUserDataTaskSteps.addBinding().to(FiltersDeleteUserDataTaskStep.class);
+        deleteUserDataTaskSteps.addBinding().to(IdentityUserDeletionTaskStep.class);
+        deleteUserDataTaskSteps.addBinding().to(PushDeleteUserDataTaskStep.class);
     }
 }
diff --git a/server/container/guice/memory/src/main/java/org/apache/james/modules/data/MemoryDataModule.java b/server/container/guice/memory/src/main/java/org/apache/james/modules/data/MemoryDataModule.java
index 42e8cb262b..de0beda071 100644
--- a/server/container/guice/memory/src/main/java/org/apache/james/modules/data/MemoryDataModule.java
+++ b/server/container/guice/memory/src/main/java/org/apache/james/modules/data/MemoryDataModule.java
@@ -41,11 +41,13 @@ import org.apache.james.rrt.lib.AliasReverseResolverImpl;
 import org.apache.james.rrt.lib.CanSendFromImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.server.core.configuration.ConfigurationProvider;
+import org.apache.james.user.api.DeleteUserDataTaskStep;
 import org.apache.james.util.date.DefaultZonedDateTimeProvider;
 import org.apache.james.util.date.ZonedDateTimeProvider;
 import org.apache.james.utils.InitializationOperation;
 import org.apache.james.utils.InitilizationOperationBuilder;
 import org.apache.james.vacation.api.NotificationRegistry;
+import org.apache.james.vacation.api.VacationDeleteUserTaskStep;
 import org.apache.james.vacation.api.VacationRepository;
 import org.apache.james.vacation.api.VacationService;
 import org.apache.james.vacation.memory.MemoryNotificationRegistry;
@@ -105,6 +107,9 @@ public class MemoryDataModule extends AbstractModule {
 
         Multibinder.newSetBinder(binder(), MailRepositoryFactory.class)
                 .addBinding().to(MemoryMailRepositoryFactory.class);
+
+        Multibinder<DeleteUserDataTaskStep> deleteUserDataTaskSteps = Multibinder.newSetBinder(binder(), DeleteUserDataTaskStep.class);
+        deleteUserDataTaskSteps.addBinding().to(VacationDeleteUserTaskStep.class);
     }
 
     @ProvidesIntoSet
diff --git a/server/data/data-api/src/main/java/org/apache/james/probe/DataProbe.java b/server/data/data-api/src/main/java/org/apache/james/probe/DataProbe.java
index 4e637db32d..24a90ecf57 100644
--- a/server/data/data-api/src/main/java/org/apache/james/probe/DataProbe.java
+++ b/server/data/data-api/src/main/java/org/apache/james/probe/DataProbe.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.probe;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -83,4 +84,6 @@ public interface DataProbe {
     void addGroupAliasMapping(String fromGroup, String toAddress) throws Exception;
 
     void addAuthorizedUser(Username baseUser, Username userWithAccess);
+
+    Collection<Username> listAuthorizedUsers(Username baseUser);
 }
\ No newline at end of file
diff --git a/server/data/data-api/src/main/java/org/apache/james/rrt/RecipientRewriteTableUserDeletionTaskStep.java b/server/data/data-api/src/main/java/org/apache/james/rrt/RecipientRewriteTableUserDeletionTaskStep.java
index ecc8afe636..ae056c1180 100644
--- a/server/data/data-api/src/main/java/org/apache/james/rrt/RecipientRewriteTableUserDeletionTaskStep.java
+++ b/server/data/data-api/src/main/java/org/apache/james/rrt/RecipientRewriteTableUserDeletionTaskStep.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.rrt;
 
+import javax.inject.Inject;
+
 import org.apache.james.core.Username;
 import org.apache.james.rrt.api.RecipientRewriteTable;
 import org.apache.james.rrt.lib.Mapping;
@@ -35,6 +37,7 @@ import reactor.core.publisher.Mono;
 public class RecipientRewriteTableUserDeletionTaskStep implements DeleteUserDataTaskStep {
     private final RecipientRewriteTable rrt;
 
+    @Inject
     public RecipientRewriteTableUserDeletionTaskStep(RecipientRewriteTable rrt) {
         this.rrt = rrt;
     }
diff --git a/server/data/data-jmap-cassandra/src/main/scala/org/apache/james/jmap/cassandra/identity/CassandraCustomIdentityDAO.scala b/server/data/data-jmap-cassandra/src/main/scala/org/apache/james/jmap/cassandra/identity/CassandraCustomIdentityDAO.scala
index 9078b49b31..3b2b3393d8 100644
--- a/server/data/data-jmap-cassandra/src/main/scala/org/apache/james/jmap/cassandra/identity/CassandraCustomIdentityDAO.scala
+++ b/server/data/data-jmap-cassandra/src/main/scala/org/apache/james/jmap/cassandra/identity/CassandraCustomIdentityDAO.scala
@@ -111,7 +111,7 @@ case class CassandraCustomIdentityDAO @Inject()(session: CqlSession,
       .`then`()
 
   override def delete(username: Username): SMono[Unit] =
-    SMono(executor.executeVoid(deleteOneStatement.bind()
+    SMono(executor.executeVoid(deleteAllStatement.bind()
         .setString(USER, username.asString())))
       .`then`()
 
diff --git a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/PushSubscription.scala b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/PushSubscription.scala
index 69d4f77567..3dfeac1035 100644
--- a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/PushSubscription.scala
+++ b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/PushSubscription.scala
@@ -88,6 +88,10 @@ case class PushSubscriptionKeys(p256dh: String, auth: String) {
   private def asECPublicKey(): ECPublicKey = EllipticCurves.getEcPublicKey(Base64.getUrlDecoder.decode(p256dh))
 }
 
+object PushSubscriptionCreationRequest {
+  val noTypes: Seq[TypeName] = Seq()
+}
+
 case class PushSubscriptionCreationRequest(deviceClientId: DeviceClientId,
                                            url: PushSubscriptionServerURL,
                                            keys: Option[PushSubscriptionKeys] = None,
diff --git a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUserDeletionIntegrationTest.java b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUserDeletionIntegrationTest.java
index 40659bbac7..6fc450b308 100644
--- a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUserDeletionIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUserDeletionIntegrationTest.java
@@ -28,11 +28,30 @@ import static org.apache.james.jmap.JMAPTestingConstants.DOMAIN;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.hamcrest.Matchers.hasSize;
 
+import java.net.URL;
+import java.util.List;
+import java.util.Optional;
+
 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.MailAddress;
+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.identity.CustomIdentityDAO;
+import org.apache.james.jmap.api.identity.IdentityCreationRequest;
+import org.apache.james.jmap.api.model.AccountId;
+import org.apache.james.jmap.api.model.EmailAddress;
+import org.apache.james.jmap.api.model.Identity;
+import org.apache.james.jmap.api.model.PushSubscription;
+import org.apache.james.jmap.api.model.PushSubscriptionCreationRequest;
+import org.apache.james.jmap.api.model.PushSubscriptionServerURL;
+import org.apache.james.jmap.api.pushsubscription.PushSubscriptionRepository;
+import org.apache.james.jmap.draft.JmapGuiceProbe;
 import org.apache.james.mailbox.model.MailboxACL;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.modules.ACLProbeImpl;
@@ -40,15 +59,69 @@ import org.apache.james.modules.MailboxProbeImpl;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.apache.james.probe.DataProbe;
 import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.GuiceProbe;
 import org.apache.james.utils.WebAdminGuiceProbe;
+import org.apache.james.vacation.api.Vacation;
+import org.apache.james.vacation.api.VacationPatch;
 import org.apache.james.webadmin.WebAdminUtils;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
+
+import com.google.inject.Inject;
+import com.google.inject.multibindings.Multibinder;
 
 import io.restassured.specification.RequestSpecification;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import scala.None;
+import scala.Option;
+import scala.collection.immutable.Seq;
 
 class MemoryUserDeletionIntegrationTest {
+    public static class MemoryUserDeletionIntegrationTestProbe implements GuiceProbe {
+        private final FilteringManagement filteringManagement;
+        private final CustomIdentityDAO identityRepository;
+        private final PushSubscriptionRepository pushSubscriptionRepository;
+
+        @Inject
+        public MemoryUserDeletionIntegrationTestProbe(FilteringManagement filteringManagement, CustomIdentityDAO identityRepository, PushSubscriptionRepository pushSubscriptionRepository) {
+            this.filteringManagement = filteringManagement;
+            this.identityRepository = identityRepository;
+            this.pushSubscriptionRepository = pushSubscriptionRepository;
+        }
+
+        Rules listFilters(Username username) {
+            return Mono.from(filteringManagement.listRulesForUser(username))
+                .block();
+        }
+
+        void defineFilters(Username username, List<Rule> ruleList) {
+            Mono.from(filteringManagement.defineRulesForUser(username, ruleList, Optional.empty())).block();
+        }
+
+        List<Identity> listIdentities(Username username) {
+            return Flux.from(identityRepository.list(username))
+                .collectList()
+                .block();
+        }
+
+        void addIdentity(Username username, IdentityCreationRequest identity) {
+            Mono.from(identityRepository.save(username, identity)).block();
+        }
+
+        List<PushSubscription> listPushSubscriptions(Username username) {
+            return Flux.from(pushSubscriptionRepository.list(username))
+                .collectList()
+                .block();
+        }
+
+        void addPushSubscriptions(Username username, PushSubscriptionCreationRequest pushSubscription) {
+            Mono.from(pushSubscriptionRepository.save(username, pushSubscription)).block();
+        }
+    }
+
     @RegisterExtension
     static JamesServerExtension jamesServerExtension = new JamesServerBuilder<MemoryJamesConfiguration>(tmpDir ->
         MemoryJamesConfiguration.builder()
@@ -57,7 +130,10 @@ class MemoryUserDeletionIntegrationTest {
             .usersRepository(DEFAULT)
             .build())
         .server(configuration -> MemoryJamesServerMain.createServer(configuration)
-            .overrideWith(new TestJMAPServerModule()))
+            .overrideWith(new TestJMAPServerModule(),
+                binder -> Multibinder.newSetBinder(binder, GuiceProbe.class)
+                    .addBinding()
+                    .to(MemoryUserDeletionIntegrationTestProbe.class)))
         .build();
 
     private RequestSpecification webAdminApi;
@@ -108,4 +184,126 @@ class MemoryUserDeletionIntegrationTest {
             .hasSize(1)
             .containsEntry(MailboxACL.EntryKey.deserialize("owner"), MailboxACL.FULL_RIGHTS);
     }
+
+    @Test
+    void shouldDeleteRRTs(GuiceJamesServer server) throws Exception {
+        server.getProbe(DataProbeImpl.class)
+            .addAddressMapping(ALICE.getLocalPart(), DOMAIN, BOB.asString());
+
+        String taskId = webAdminApi
+            .queryParam("action", "deleteData")
+            .post("/users/" + ALICE.asString())
+            .jsonPath()
+            .get("taskId");
+
+        webAdminApi.get("/tasks/" + taskId + "/await");
+
+        assertThat(server.getProbe(DataProbeImpl.class).listMappings())
+            .isEmpty();
+    }
+
+    @Test
+    void shouldDeleteDelegation(GuiceJamesServer server) {
+        server.getProbe(DataProbeImpl.class)
+            .addAuthorizedUser(ALICE, BOB);
+
+        String taskId = webAdminApi
+            .queryParam("action", "deleteData")
+            .post("/users/" + ALICE.asString())
+            .jsonPath()
+            .get("taskId");
+
+        webAdminApi.get("/tasks/" + taskId + "/await");
+
+        assertThat(server.getProbe(DataProbeImpl.class).listAuthorizedUsers(ALICE))
+            .isEmpty();
+    }
+
+    @Test
+    void shouldDeleteVacation(GuiceJamesServer server) {
+        server.getProbe(JmapGuiceProbe.class)
+            .modifyVacation(AccountId.fromUsername(ALICE), VacationPatch.builder()
+                .textBody("text body")
+                .build());
+
+        String taskId = webAdminApi
+            .queryParam("action", "deleteData")
+            .post("/users/" + ALICE.asString())
+            .jsonPath()
+            .get("taskId");
+
+        webAdminApi.get("/tasks/" + taskId + "/await");
+
+        assertThat(server.getProbe(JmapGuiceProbe.class).retrieveVacation(AccountId.fromUsername(ALICE)))
+            .isEqualTo(Vacation.builder()
+                .enabled(false)
+                .build());
+    }
+
+    @Test
+    void shouldDeleteFilters(GuiceJamesServer server) {
+        Rule.Condition CONDITION = Rule.Condition.of(Rule.Condition.Field.CC, Rule.Condition.Comparator.CONTAINS, "something");
+        Rule.Action ACTION = Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds("id-01"));
+        Rule.Builder RULE_BUILDER = Rule.builder().name("A name").condition(CONDITION).action(ACTION);
+        Rule RULE_1 = RULE_BUILDER.id(Rule.Id.of("1")).build();
+        server.getProbe(MemoryUserDeletionIntegrationTestProbe.class)
+            .defineFilters(ALICE, ImmutableList.of(RULE_1));
+
+        String taskId = webAdminApi
+            .queryParam("action", "deleteData")
+            .post("/users/" + ALICE.asString())
+            .jsonPath()
+            .get("taskId");
+
+        webAdminApi.get("/tasks/" + taskId + "/await");
+
+        assertThat(server.getProbe(MemoryUserDeletionIntegrationTestProbe.class).listFilters(ALICE).getRules())
+            .isEmpty();
+    }
+
+    @Test
+    void shouldDeleteIdentities(GuiceJamesServer server) throws Exception {
+        server.getProbe(MemoryUserDeletionIntegrationTestProbe.class)
+            .addIdentity(ALICE, IdentityCreationRequest.fromJava(
+                ALICE.asMailAddress(),
+                Optional.of("identity name 1"),
+                Optional.of(List.of(EmailAddress.from(Optional.of("reply name 1"), new MailAddress("reply1@domain.org")))),
+                Optional.of(List.of(EmailAddress.from(Optional.of("bcc name 1"), new MailAddress("bcc1@domain.org")))),
+                Optional.of(1),
+                Optional.of("textSignature 1"),
+                Optional.of("htmlSignature 1")));
+
+        String taskId = webAdminApi
+            .queryParam("action", "deleteData")
+            .post("/users/" + ALICE.asString())
+            .jsonPath()
+            .get("taskId");
+
+        webAdminApi.get("/tasks/" + taskId + "/await");
+
+        assertThat(server.getProbe(MemoryUserDeletionIntegrationTestProbe.class).listIdentities(ALICE))
+            .isEmpty();
+    }
+
+    @Test
+    void shouldDeletePushSubscriptions(GuiceJamesServer server) throws Exception {
+        server.getProbe(MemoryUserDeletionIntegrationTestProbe.class)
+            .addPushSubscriptions(ALICE, new PushSubscriptionCreationRequest(
+                "device",
+                new PushSubscriptionServerURL(new URL("http://whatever/toto")),
+                Option.empty(),
+                Option.empty(),
+                PushSubscriptionCreationRequest.noTypes()));
+
+        String taskId = webAdminApi
+            .queryParam("action", "deleteData")
+            .post("/users/" + ALICE.asString())
+            .jsonPath()
+            .get("taskId");
+
+        webAdminApi.get("/tasks/" + taskId + "/await");
+
+        assertThat(server.getProbe(MemoryUserDeletionIntegrationTestProbe.class).listPushSubscriptions(ALICE))
+            .isEmpty();
+    }
 }


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


[james-project] 02/07: JAMES-3909 DelegationUserDeletionTaskStep

Posted by rc...@apache.org.
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

commit 80572f62d84314c46fc6cfeb7d7e649dfe046cdf
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri May 19 12:05:53 2023 +0700

    JAMES-3909 DelegationUserDeletionTaskStep
---
 .../user/api/DelegationUserDeletionTaskStep.java   | 65 +++++++++++++++++++
 .../memory/DelegationUserDeletionTaskStepTest.java | 75 ++++++++++++++++++++++
 2 files changed, 140 insertions(+)

diff --git a/server/data/data-api/src/main/java/org/apache/james/user/api/DelegationUserDeletionTaskStep.java b/server/data/data-api/src/main/java/org/apache/james/user/api/DelegationUserDeletionTaskStep.java
new file mode 100644
index 0000000000..4f3f3b9bdc
--- /dev/null
+++ b/server/data/data-api/src/main/java/org/apache/james/user/api/DelegationUserDeletionTaskStep.java
@@ -0,0 +1,65 @@
+/****************************************************************
+ * 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.user.api;
+
+import javax.inject.Inject;
+
+import org.apache.james.core.Username;
+import org.reactivestreams.Publisher;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class DelegationUserDeletionTaskStep implements DeleteUserDataTaskStep {
+    private final DelegationStore delegationStore;
+
+    @Inject
+    public DelegationUserDeletionTaskStep(DelegationStore delegationStore) {
+        this.delegationStore = delegationStore;
+    }
+
+    @Override
+    public StepName name() {
+        return new StepName("DelegationUserDeletionTaskStep");
+    }
+
+    @Override
+    public int priority() {
+        return 1;
+    }
+
+    @Override
+    public Publisher<Void> deleteUserData(Username username) {
+        return removeDelegatedUsers(username)
+            .then(removeDelegatingUsers(username));
+    }
+
+    private Mono<Void> removeDelegatedUsers(Username username) {
+        return Flux.from(delegationStore.delegatedUsers(username))
+            .concatMap(delegator -> (delegationStore.removeAuthorizedUser(delegator, username)))
+            .then();
+    }
+
+    private Mono<Void> removeDelegatingUsers(Username username) {
+        return Flux.from(delegationStore.authorizedUsers(username))
+            .concatMap(delegatee -> (delegationStore.removeAuthorizedUser(username, delegatee)))
+            .then();
+    }
+}
diff --git a/server/data/data-memory/src/test/java/org/apache/james/user/memory/DelegationUserDeletionTaskStepTest.java b/server/data/data-memory/src/test/java/org/apache/james/user/memory/DelegationUserDeletionTaskStepTest.java
new file mode 100644
index 0000000000..667a719008
--- /dev/null
+++ b/server/data/data-memory/src/test/java/org/apache/james/user/memory/DelegationUserDeletionTaskStepTest.java
@@ -0,0 +1,75 @@
+/****************************************************************
+ * 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.user.memory;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import org.apache.james.core.Username;
+import org.apache.james.user.api.DelegationUserDeletionTaskStep;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+class DelegationUserDeletionTaskStepTest {
+    private static final Username ALICE = Username.of("alice@domain.tld");
+    private static final Username BOB = Username.of("bob@domain.tld");
+
+    private MemoryDelegationStore delegationStore;
+    private DelegationUserDeletionTaskStep testee;
+
+    @BeforeEach
+    void setUp() {
+        delegationStore = new MemoryDelegationStore();
+        testee = new DelegationUserDeletionTaskStep(delegationStore);
+    }
+
+    @Test
+    void shouldDeleteDelegatingUser() {
+        Mono.from(delegationStore.addAuthorizedUser(BOB)
+            .forUser(ALICE))
+            .block();
+
+        Mono.from(testee.deleteUserData(ALICE)).block();
+
+        assertThat(Flux.from(delegationStore.authorizedUsers(ALICE)).collectList().block())
+            .isEmpty();
+    }
+
+    @Test
+    void shouldDeleteDelegateeUser() {
+        Mono.from(delegationStore.addAuthorizedUser(BOB)
+            .forUser(ALICE))
+            .block();
+
+        Mono.from(testee.deleteUserData(BOB)).block();
+
+        assertThat(Flux.from(delegationStore.authorizedUsers(ALICE)).collectList().block())
+            .isEmpty();
+    }
+
+    @Test
+    void shouldBeIdempotent() {
+        assertThatCode(() -> Mono.from(testee.deleteUserData(BOB)).block())
+            .doesNotThrowAnyException();
+    }
+}


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


[james-project] 05/07: JAMES-3909 VacationDeleteUserTaskStep

Posted by rc...@apache.org.
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

commit 9e20aeef2691031a883e91dd8fd7a5f399704c8f
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri May 19 16:42:50 2023 +0700

    JAMES-3909 VacationDeleteUserTaskStep
---
 .../vacation/api/VacationDeleteUserTaskStep.java   | 59 ++++++++++++++++++
 .../memory/VacationDeleteUserTaskStepTest.java     | 70 ++++++++++++++++++++++
 2 files changed, 129 insertions(+)

diff --git a/server/data/data-api/src/main/java/org/apache/james/vacation/api/VacationDeleteUserTaskStep.java b/server/data/data-api/src/main/java/org/apache/james/vacation/api/VacationDeleteUserTaskStep.java
new file mode 100644
index 0000000000..70725fbd8c
--- /dev/null
+++ b/server/data/data-api/src/main/java/org/apache/james/vacation/api/VacationDeleteUserTaskStep.java
@@ -0,0 +1,59 @@
+/******************************************************************
+ * 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.vacation.api;
+
+import javax.inject.Inject;
+
+import org.apache.james.core.Username;
+import org.apache.james.user.api.DeleteUserDataTaskStep;
+import org.apache.james.util.ValuePatch;
+import org.reactivestreams.Publisher;
+
+public class VacationDeleteUserTaskStep implements DeleteUserDataTaskStep {
+    private final VacationRepository vacationRepository;
+
+    @Inject
+    public VacationDeleteUserTaskStep(VacationRepository vacationRepository) {
+        this.vacationRepository = vacationRepository;
+    }
+
+    @Override
+    public StepName name() {
+        return new StepName("VacationDeleteUserTaskStep");
+    }
+
+    @Override
+    public int priority() {
+        return 4;
+    }
+
+    @Override
+    public Publisher<Void> deleteUserData(Username username) {
+        return vacationRepository.modifyVacation(AccountId.fromUsername(username),
+            VacationPatch.builder()
+                .isEnabled(false)
+                .subject(ValuePatch.remove())
+                .fromDate(ValuePatch.remove())
+                .htmlBody(ValuePatch.remove())
+                .textBody(ValuePatch.remove())
+                .toDate(ValuePatch.remove())
+                .build());
+    }
+}
diff --git a/server/data/data-memory/src/test/java/org/apache/james/vacation/memory/VacationDeleteUserTaskStepTest.java b/server/data/data-memory/src/test/java/org/apache/james/vacation/memory/VacationDeleteUserTaskStepTest.java
new file mode 100644
index 0000000000..57bf370717
--- /dev/null
+++ b/server/data/data-memory/src/test/java/org/apache/james/vacation/memory/VacationDeleteUserTaskStepTest.java
@@ -0,0 +1,70 @@
+/******************************************************************
+ * 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.vacation.memory;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import org.apache.james.core.Username;
+import org.apache.james.vacation.api.AccountId;
+import org.apache.james.vacation.api.Vacation;
+import org.apache.james.vacation.api.VacationDeleteUserTaskStep;
+import org.apache.james.vacation.api.VacationPatch;
+import org.apache.james.vacation.api.VacationRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import reactor.core.publisher.Mono;
+
+class VacationDeleteUserTaskStepTest {
+    public static final Username BOB = Username.of("bob");
+    private VacationDeleteUserTaskStep testee;
+    private VacationRepository vacationRepository;
+
+    @BeforeEach
+    void beforeEach() {
+        vacationRepository = new MemoryVacationRepository();
+        testee = new VacationDeleteUserTaskStep(vacationRepository);
+    }
+
+    @Test
+    void shouldOverridePersonalData() {
+        VacationPatch vacationPatch = VacationPatch.builder()
+            .isEnabled(true)
+            .build();
+        vacationRepository.modifyVacation(AccountId.fromUsername(BOB), vacationPatch).block();
+
+        Mono.from(testee.deleteUserData(BOB)).block();
+
+        assertThat(Mono.from(vacationRepository.retrieveVacation(AccountId.fromUsername(BOB))).block())
+            .isEqualTo(Vacation.builder()
+                .enabled(false)
+                .build());
+    }
+
+    @Test
+    void shouldNotCreateVacationWhenNone() {
+        Mono.from(testee.deleteUserData(BOB)).block();
+
+
+        assertThat(Mono.from(vacationRepository.retrieveVacation(AccountId.fromUsername(BOB))).block())
+            .isEqualTo(Vacation.builder()
+                .enabled(false)
+                .build());
+    }
+}


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


[james-project] 06/07: JAMES-3909 FiltersDeleteUserDataTaskStep

Posted by rc...@apache.org.
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

commit 728b0ec6f0a94b0fdcdd71cd980306e5f04854e8
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri May 19 16:55:06 2023 +0700

    JAMES-3909 FiltersDeleteUserDataTaskStep
---
 .../filtering/FiltersDeleteUserDataTaskStep.java   | 52 +++++++++++++++++++
 .../impl/FiltersDeleteUserDataTaskStepTest.java    | 58 ++++++++++++++++++++++
 2 files changed, 110 insertions(+)

diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/FiltersDeleteUserDataTaskStep.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/FiltersDeleteUserDataTaskStep.java
new file mode 100644
index 0000000000..28f0280e38
--- /dev/null
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/FiltersDeleteUserDataTaskStep.java
@@ -0,0 +1,52 @@
+/******************************************************************
+ * 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;
+
+import javax.inject.Inject;
+
+import org.apache.james.core.Username;
+import org.apache.james.user.api.DeleteUserDataTaskStep;
+import org.reactivestreams.Publisher;
+
+import reactor.core.publisher.Mono;
+
+public class FiltersDeleteUserDataTaskStep implements DeleteUserDataTaskStep {
+    private final FilteringManagement filteringManagement;
+
+    @Inject
+    public FiltersDeleteUserDataTaskStep(FilteringManagement filteringManagement) {
+        this.filteringManagement = filteringManagement;
+    }
+
+    @Override
+    public StepName name() {
+        return new StepName("FiltersDeleteUserDataTaskStep ");
+    }
+
+    @Override
+    public int priority() {
+        return 5;
+    }
+
+    @Override
+    public Publisher<Void> deleteUserData(Username username) {
+        return Mono.from(filteringManagement.clearRulesForUser(username)).then();
+    }
+}
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/FiltersDeleteUserDataTaskStepTest.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/FiltersDeleteUserDataTaskStepTest.java
new file mode 100644
index 0000000000..06a9fc080b
--- /dev/null
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/impl/FiltersDeleteUserDataTaskStepTest.java
@@ -0,0 +1,58 @@
+/******************************************************************
+ * 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.apache.james.jmap.api.filtering.RuleFixture.RULE_1;
+import static org.apache.james.jmap.api.filtering.RuleFixture.RULE_2;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Optional;
+
+import org.apache.james.core.Username;
+import org.apache.james.eventsourcing.eventstore.memory.InMemoryEventStore;
+import org.apache.james.jmap.api.filtering.FilteringManagement;
+import org.apache.james.jmap.api.filtering.FiltersDeleteUserDataTaskStep;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import reactor.core.publisher.Mono;
+
+class FiltersDeleteUserDataTaskStepTest {
+    private static final Username BOB = Username.of("bob");
+
+    private FilteringManagement filteringManagement;
+    private FiltersDeleteUserDataTaskStep testee;
+
+    @BeforeEach
+    void beforeEach() {
+        filteringManagement = new EventSourcingFilteringManagement(new InMemoryEventStore());
+        testee = new FiltersDeleteUserDataTaskStep(filteringManagement);
+    }
+
+    @Test
+    void shouldClearRules() {
+        Mono.from(filteringManagement.defineRulesForUser(BOB, Optional.empty(), RULE_1, RULE_2)).block();
+
+        Mono.from(testee.deleteUserData(BOB)).block();
+
+        assertThat(Mono.from(filteringManagement.listRulesForUser(BOB)).block().getRules())
+            .isEmpty();
+    }
+}


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


[james-project] 03/07: JAMES-3909 IdentityUserDeletionTaskStep

Posted by rc...@apache.org.
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

commit 8d732379d4d768154dc574be3a65a08702e25781
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri May 19 15:28:24 2023 +0700

    JAMES-3909 IdentityUserDeletionTaskStep
---
 .../identity/CassandraCustomIdentityDAO.scala      |  9 ++++
 .../jmap/api/identity/CustomIdentityDAO.scala      |  2 +
 .../identity/IdentityUserDeletionTaskStep.scala    | 35 ++++++++++++++
 .../memory/identity/MemoryCustomIdentityDAO.scala  |  2 +
 .../api/identity/CustomIdentityDAOContract.scala   | 27 ++++++++++-
 .../IdentityUserDeletionTaskStepTest.scala         | 55 ++++++++++++++++++++++
 6 files changed, 128 insertions(+), 2 deletions(-)

diff --git a/server/data/data-jmap-cassandra/src/main/scala/org/apache/james/jmap/cassandra/identity/CassandraCustomIdentityDAO.scala b/server/data/data-jmap-cassandra/src/main/scala/org/apache/james/jmap/cassandra/identity/CassandraCustomIdentityDAO.scala
index 1aa1628fc5..9078b49b31 100644
--- a/server/data/data-jmap-cassandra/src/main/scala/org/apache/james/jmap/cassandra/identity/CassandraCustomIdentityDAO.scala
+++ b/server/data/data-jmap-cassandra/src/main/scala/org/apache/james/jmap/cassandra/identity/CassandraCustomIdentityDAO.scala
@@ -71,6 +71,10 @@ case class CassandraCustomIdentityDAO @Inject()(session: CqlSession,
     .whereColumn(ID).isEqualTo(bindMarker(ID))
     .build())
 
+  val deleteAllStatement: PreparedStatement = session.prepare(deleteFrom(TABLE_NAME)
+    .whereColumn(USER).isEqualTo(bindMarker(USER))
+    .build())
+
   override def save(user: Username, creationRequest: IdentityCreationRequest): SMono[Identity] =
     save(user, IdentityId.generate, creationRequest)
 
@@ -106,6 +110,11 @@ case class CassandraCustomIdentityDAO @Inject()(session: CqlSession,
         .setUuid(ID, id.id)))
       .`then`()
 
+  override def delete(username: Username): SMono[Unit] =
+    SMono(executor.executeVoid(deleteOneStatement.bind()
+        .setString(USER, username.asString())))
+      .`then`()
+
   private def insert(username: Username, identity: Identity): SMono[Identity] = {
     val replyTo: java.util.Set[UdtValue] = toJavaSet(identity.replyTo.getOrElse(List()))
     val bcc: java.util.Set[UdtValue] = toJavaSet(identity.bcc.getOrElse(List()))
diff --git a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
index c3f80b8836..2a2fc672a7 100644
--- a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
+++ b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
@@ -147,6 +147,8 @@ trait CustomIdentityDAO {
   def upsert(user: Username, patch: Identity): SMono[Unit]
 
   def delete(username: Username, ids: Seq[IdentityId]): Publisher[Unit]
+
+  def delete(username: Username): Publisher[Unit]
 }
 
 class DefaultIdentitySupplier @Inject()(canSendFrom: CanSendFrom, usersRepository: UsersRepository) {
diff --git a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/IdentityUserDeletionTaskStep.scala b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/IdentityUserDeletionTaskStep.scala
new file mode 100644
index 0000000000..7eb3f2eb96
--- /dev/null
+++ b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/IdentityUserDeletionTaskStep.scala
@@ -0,0 +1,35 @@
+/****************************************************************
+ * 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.identity
+
+import com.google.inject.Inject
+import org.apache.james.core.Username
+import org.apache.james.user.api.DeleteUserDataTaskStep
+import org.apache.james.user.api.DeleteUserDataTaskStep.StepName
+import org.reactivestreams.Publisher
+import reactor.core.publisher.Mono
+
+class IdentityUserDeletionTaskStep @Inject()(customIdentityDAO: CustomIdentityDAO) extends DeleteUserDataTaskStep {
+  override val name: StepName = new StepName("IdentityUserDeletionTaskStep")
+
+  override val priority: Int = 1
+
+  override def deleteUserData(username: Username): Publisher[Void] = Mono.from(customIdentityDAO.delete(username)).`then`()
+}
diff --git a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/memory/identity/MemoryCustomIdentityDAO.scala b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/memory/identity/MemoryCustomIdentityDAO.scala
index 9935e1570e..f9810ff176 100644
--- a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/memory/identity/MemoryCustomIdentityDAO.scala
+++ b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/memory/identity/MemoryCustomIdentityDAO.scala
@@ -56,4 +56,6 @@ class MemoryCustomIdentityDAO extends CustomIdentityDAO {
   override def delete(username: Username, ids: Seq[IdentityId]): Publisher[Unit] = SFlux.fromIterable(ids)
     .doOnNext(id => table.remove(username, id))
     .`then`()
+
+  override def delete(username: Username): Publisher[Unit] = SMono.fromCallable(() => table.rowMap().remove(username))
 }
\ No newline at end of file
diff --git a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/identity/CustomIdentityDAOContract.scala b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/identity/CustomIdentityDAOContract.scala
index 7b12a95d6c..f692860d84 100644
--- a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/identity/CustomIdentityDAOContract.scala
+++ b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/identity/CustomIdentityDAOContract.scala
@@ -30,8 +30,8 @@ import reactor.core.scala.publisher.{SFlux, SMono}
 import scala.jdk.OptionConverters._
 
 object CustomIdentityDAOContract {
-  private val bob: Username = Username.of("bob@localhost")
-  private val CREATION_REQUEST: IdentityCreationRequest = IdentityCreationRequest(name = Some(IdentityName("Bob (custom address)")),
+  val bob: Username = Username.of("bob@localhost")
+  val CREATION_REQUEST: IdentityCreationRequest = IdentityCreationRequest(name = Some(IdentityName("Bob (custom address)")),
     email = bob.asMailAddress(),
     replyTo = Some(List(EmailAddress(Some(EmailerName("My Boss")), new MailAddress("boss@domain.tld")))),
     bcc = Some(List(EmailAddress(Some(EmailerName("My Boss 2")), new MailAddress("boss2@domain.tld")))),
@@ -124,6 +124,17 @@ trait CustomIdentityDAOContract {
       .isEmpty()
   }
 
+  @Test
+  def saveShouldNotReturnDeletedAllValues(): Unit = {
+    val identity: Identity = SMono(testee().save(bob, CREATION_REQUEST))
+      .block()
+
+    SMono(testee().delete(bob)).block()
+
+    assertThat(SFlux(testee().list(bob)).asJava().collectList().block())
+      .isEmpty()
+  }
+
   @Test
   def saveShouldDefineDefaultValuesInCaseSomePropertiesEmpty(): Unit = {
     val identity: Identity = SMono(testee().save(bob,
@@ -158,6 +169,18 @@ trait CustomIdentityDAOContract {
       .isEmpty()
   }
 
+  @Test
+  def deleteAllShouldBeIdempotent(): Unit = {
+    val identity: Identity = SMono(testee().save(bob, CREATION_REQUEST))
+      .block()
+
+    SMono(testee().delete(bob)).block()
+    SMono(testee().delete(bob)).block()
+
+    assertThat(SFlux(testee().list(bob)).asJava().collectList().block())
+      .isEmpty()
+  }
+
   @Test
   def updateShouldModifyUnderlyingRecord(): Unit = {
     val identity: Identity = SMono(testee().save(bob, CREATION_REQUEST))
diff --git a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/memory/identity/IdentityUserDeletionTaskStepTest.scala b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/memory/identity/IdentityUserDeletionTaskStepTest.scala
new file mode 100644
index 0000000000..23e9db7df9
--- /dev/null
+++ b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/memory/identity/IdentityUserDeletionTaskStepTest.scala
@@ -0,0 +1,55 @@
+/****************************************************************
+ * 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.memory.identity
+
+import org.apache.james.jmap.api.identity.CustomIdentityDAOContract.{CREATION_REQUEST, bob}
+import org.apache.james.jmap.api.identity.IdentityUserDeletionTaskStep
+import org.assertj.core.api.Assertions.{assertThat, assertThatCode}
+import org.junit.jupiter.api.{BeforeEach, Test}
+import reactor.core.publisher.Flux
+import reactor.core.scala.publisher.SMono
+
+class IdentityUserDeletionTaskStepTest {
+  var identityDAO: MemoryCustomIdentityDAO = _
+  var testee: IdentityUserDeletionTaskStep = _
+
+  @BeforeEach
+  def setUp(): Unit = {
+    identityDAO = new MemoryCustomIdentityDAO()
+    testee = new IdentityUserDeletionTaskStep(identityDAO)
+  }
+
+  @Test
+  def shouldDeleteUserIdentity(): Unit = {
+    SMono(identityDAO
+      .save(bob, CREATION_REQUEST))
+      .block()
+
+    SMono(testee.deleteUserData(bob)).block()
+
+    assertThat(Flux.from(identityDAO.list(bob)).collectList().block()).isEmpty()
+  }
+
+  @Test
+  def shouldBeIdempotent(): Unit = {
+    assertThatCode(() => SMono(testee.deleteUserData(bob)).block())
+      .doesNotThrowAnyException()
+  }
+}


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


[james-project] 01/07: JAMES-3909 RecipientRewriteTableUserDeletionTaskStep

Posted by rc...@apache.org.
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

commit 21cbd399259959ed9b2cf8586b5aaec126c67b8c
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri May 19 11:42:47 2023 +0700

    JAMES-3909 RecipientRewriteTableUserDeletionTaskStep
---
 .../RecipientRewriteTableUserDeletionTaskStep.java |  89 +++++++++++++
 ...ipientRewriteTableUserDeletionTaskStepTest.java | 137 +++++++++++++++++++++
 2 files changed, 226 insertions(+)

diff --git a/server/data/data-api/src/main/java/org/apache/james/rrt/RecipientRewriteTableUserDeletionTaskStep.java b/server/data/data-api/src/main/java/org/apache/james/rrt/RecipientRewriteTableUserDeletionTaskStep.java
new file mode 100644
index 0000000000..ecc8afe636
--- /dev/null
+++ b/server/data/data-api/src/main/java/org/apache/james/rrt/RecipientRewriteTableUserDeletionTaskStep.java
@@ -0,0 +1,89 @@
+/****************************************************************
+ * 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.rrt;
+
+import org.apache.james.core.Username;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.lib.Mapping;
+import org.apache.james.rrt.lib.MappingSource;
+import org.apache.james.user.api.DeleteUserDataTaskStep;
+import org.apache.james.util.ReactorUtils;
+import org.reactivestreams.Publisher;
+
+import com.github.fge.lambdas.Throwing;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class RecipientRewriteTableUserDeletionTaskStep implements DeleteUserDataTaskStep {
+    private final RecipientRewriteTable rrt;
+
+    public RecipientRewriteTableUserDeletionTaskStep(RecipientRewriteTable rrt) {
+        this.rrt = rrt;
+    }
+
+    @Override
+    public StepName name() {
+        return new StepName("RecipientRewriteTableUserDeletionTaskStep");
+    }
+
+    @Override
+    public int priority() {
+        return 0;
+    }
+
+    @Override
+    public Publisher<Void> deleteUserData(Username username) {
+        return deleteRRT(username)
+            .then(deleteForwards(username))
+            .then(deleteGroup(username));
+    }
+
+    private Flux<Void> deleteRRT(Username username) {
+        MappingSource mappingSource = MappingSource.fromUser(username);
+
+        return Mono.fromCallable(() -> rrt.getStoredMappings(mappingSource))
+            .subscribeOn(ReactorUtils.BLOCKING_CALL_WRAPPER)
+            .flatMapMany(mappings -> Flux.fromStream(mappings.asStream()))
+            .flatMap(mapping -> deleteMapping(mappingSource, mapping));
+    }
+
+    private Mono<Void> deleteMapping(MappingSource mappingSource, Mapping mapping) {
+        return Mono.fromRunnable(Throwing.runnable(() -> rrt.removeMapping(mappingSource, mapping)))
+            .subscribeOn(ReactorUtils.BLOCKING_CALL_WRAPPER)
+            .then();
+    }
+
+    private Mono<Void> deleteForwards(Username username) {
+        return deleteSource(Mapping.forward(username.asString()));
+    }
+
+    private Mono<Void> deleteGroup(Username username) {
+        return deleteSource(Mapping.group(username.asString()));
+    }
+
+    private Mono<Void> deleteSource(Mapping mapping) {
+        return Mono.fromCallable(() -> rrt.listSources(mapping))
+            .subscribeOn(ReactorUtils.BLOCKING_CALL_WRAPPER)
+            .flatMapMany(Flux::fromStream)
+            .flatMap(source -> deleteMapping(source, mapping))
+            .then();
+    }
+}
diff --git a/server/data/data-memory/src/test/java/org/apache/james/rrt/memory/RecipientRewriteTableUserDeletionTaskStepTest.java b/server/data/data-memory/src/test/java/org/apache/james/rrt/memory/RecipientRewriteTableUserDeletionTaskStepTest.java
new file mode 100644
index 0000000000..24c48dbb9c
--- /dev/null
+++ b/server/data/data-memory/src/test/java/org/apache/james/rrt/memory/RecipientRewriteTableUserDeletionTaskStepTest.java
@@ -0,0 +1,137 @@
+/****************************************************************
+ * 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.rrt.memory;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.mockito.Mockito.mock;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.rrt.RecipientRewriteTableUserDeletionTaskStep;
+import org.apache.james.rrt.lib.MappingSource;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import reactor.core.publisher.Mono;
+
+class RecipientRewriteTableUserDeletionTaskStepTest {
+    private static final Username BOB = Username.of("bob@domain.tld");
+    private MemoryRecipientRewriteTable rrt;
+    private RecipientRewriteTableUserDeletionTaskStep testee;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        DNSService dnsService = mock(DNSService.class);
+        MemoryDomainList domainList = new MemoryDomainList(dnsService);
+        domainList.configure(DomainListConfiguration.DEFAULT);
+        domainList.addDomain(Domain.of("domain.tld"));
+
+        rrt = new MemoryRecipientRewriteTable();
+        rrt.setUsersRepository(MemoryUsersRepository.withVirtualHosting(domainList));
+        rrt.setUserEntityValidator(UserEntityValidator.NOOP);
+        rrt.setDomainList(domainList);
+        rrt.configure(new BaseHierarchicalConfiguration());
+
+        testee = new RecipientRewriteTableUserDeletionTaskStep(rrt);
+    }
+
+    @Test
+    void shouldNotFailWhenNoMapping() {
+        assertThatCode(() -> Mono.from(testee.deleteUserData(BOB)).block())
+            .doesNotThrowAnyException();
+    }
+
+    @Test
+    void shouldDeleteForwardMappings() throws Exception {
+        rrt.addForwardMapping(MappingSource.fromUser(BOB), "alice@domain.tld");
+
+        Mono.from(testee.deleteUserData(BOB)).block();
+
+        assertThat(rrt.getAllMappings())
+            .isEmpty();
+    }
+
+    @Test
+    void shouldDeleteForwardMappingsWhenDestination() throws Exception {
+        rrt.addForwardMapping(MappingSource.fromUser("alice", "domain.tld"), BOB.asString());
+
+        Mono.from(testee.deleteUserData(BOB)).block();
+
+        assertThat(rrt.getAllMappings())
+            .isEmpty();
+    }
+
+    @Test
+    void shouldDeleteAliasMapping() throws Exception {
+        rrt.addAliasMapping(MappingSource.fromUser(BOB), "alice@domain.tld");
+
+        Mono.from(testee.deleteUserData(BOB)).block();
+
+        assertThat(rrt.getAllMappings())
+            .isEmpty();
+    }
+
+    @Test
+    void shouldDeleteAddressMapping() throws Exception {
+        rrt.addAddressMapping(MappingSource.fromUser(BOB), "alice@domain.tld");
+
+        Mono.from(testee.deleteUserData(BOB)).block();
+
+        assertThat(rrt.getAllMappings())
+            .isEmpty();
+    }
+
+    @Test
+    void shouldDeleteRegexMapping() throws Exception {
+        rrt.addRegexMapping(MappingSource.fromUser(BOB), "alice@domain.tld");
+
+        Mono.from(testee.deleteUserData(BOB)).block();
+
+        assertThat(rrt.getAllMappings())
+            .isEmpty();
+    }
+
+    @Test
+    void shouldDeleteErrorMapping() throws Exception {
+        rrt.addErrorMapping(MappingSource.fromUser(BOB), "alice@domain.tld");
+
+        Mono.from(testee.deleteUserData(BOB)).block();
+
+        assertThat(rrt.getAllMappings())
+            .isEmpty();
+    }
+
+    @Test
+    void shouldDeleteGroupMapping() throws Exception {
+        rrt.addGroupMapping(MappingSource.fromUser("alice", "domain.tld"), BOB.asString());
+
+        Mono.from(testee.deleteUserData(BOB)).block();
+
+        assertThat(rrt.getAllMappings())
+            .isEmpty();
+    }
+}


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