You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2023/05/25 05:45:00 UTC

[james-project] branch master updated: JAMES-3909 Mailboxes deletion step (#1571)

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

btellier 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 d7e717632e JAMES-3909 Mailboxes deletion step (#1571)
d7e717632e is described below

commit d7e717632ea2de8efa4e7ac05df54b7be7a58cec
Author: Trần Hồng Quân <55...@users.noreply.github.com>
AuthorDate: Thu May 25 12:44:54 2023 +0700

    JAMES-3909 Mailboxes deletion step (#1571)
    
    Delete mailboxes + messages + ACLs + subscriptions upon user data deletion.
---
 .../modules/mailbox/CassandraMailboxModule.java    |   5 +
 .../james/modules/mailbox/JPAMailboxModule.java    |   5 +
 .../james/modules/mailbox/MemoryMailboxModule.java |   5 +
 .../mailbox/MailboxUserDeletionTaskStep.java       | 109 +++++++++++
 .../mailbox/MailboxUserDeletionTaskStepTest.java   | 212 +++++++++++++++++++++
 .../memory/MemoryUserDeletionIntegrationTest.java  | 111 +++++++++++
 6 files changed, 447 insertions(+)

diff --git a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
index 174ed29b0b..81e6f3d411 100644
--- a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
+++ b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
@@ -23,6 +23,7 @@ import static org.apache.james.modules.Names.MAILBOXMANAGER_NAME;
 import javax.inject.Singleton;
 
 import org.apache.james.adapter.mailbox.ACLUsernameChangeTaskStep;
+import org.apache.james.adapter.mailbox.MailboxUserDeletionTaskStep;
 import org.apache.james.adapter.mailbox.MailboxUsernameChangeTaskStep;
 import org.apache.james.adapter.mailbox.QuotaUsernameChangeTaskStep;
 import org.apache.james.adapter.mailbox.UserRepositoryAuthenticator;
@@ -117,6 +118,7 @@ import org.apache.james.mailbox.store.mail.ModSeqProvider;
 import org.apache.james.mailbox.store.mail.ThreadIdGuessingAlgorithm;
 import org.apache.james.mailbox.store.mail.UidProvider;
 import org.apache.james.mailbox.store.user.SubscriptionMapperFactory;
+import org.apache.james.user.api.DeleteUserDataTaskStep;
 import org.apache.james.user.api.UsernameChangeTaskStep;
 import org.apache.james.utils.MailboxManagerDefinition;
 import org.apache.mailbox.tools.indexer.MessageIdReIndexerImpl;
@@ -250,6 +252,9 @@ public class CassandraMailboxModule extends AbstractModule {
         usernameChangeTaskStepMultibinder.addBinding().to(MailboxUsernameChangeTaskStep.class);
         usernameChangeTaskStepMultibinder.addBinding().to(ACLUsernameChangeTaskStep.class);
         usernameChangeTaskStepMultibinder.addBinding().to(QuotaUsernameChangeTaskStep.class);
+
+        Multibinder<DeleteUserDataTaskStep> deleteUserDataTaskStepMultibinder = Multibinder.newSetBinder(binder(), DeleteUserDataTaskStep.class);
+        deleteUserDataTaskStepMultibinder.addBinding().to(MailboxUserDeletionTaskStep.class);
     }
 
     @Provides
diff --git a/server/container/guice/mailbox-jpa/src/main/java/org/apache/james/modules/mailbox/JPAMailboxModule.java b/server/container/guice/mailbox-jpa/src/main/java/org/apache/james/modules/mailbox/JPAMailboxModule.java
index 5bf648d6d8..f56415cb62 100644
--- a/server/container/guice/mailbox-jpa/src/main/java/org/apache/james/modules/mailbox/JPAMailboxModule.java
+++ b/server/container/guice/mailbox-jpa/src/main/java/org/apache/james/modules/mailbox/JPAMailboxModule.java
@@ -23,6 +23,7 @@ import static org.apache.james.modules.Names.MAILBOXMANAGER_NAME;
 import javax.inject.Singleton;
 
 import org.apache.james.adapter.mailbox.ACLUsernameChangeTaskStep;
+import org.apache.james.adapter.mailbox.MailboxUserDeletionTaskStep;
 import org.apache.james.adapter.mailbox.MailboxUsernameChangeTaskStep;
 import org.apache.james.adapter.mailbox.QuotaUsernameChangeTaskStep;
 import org.apache.james.adapter.mailbox.UserRepositoryAuthenticator;
@@ -62,6 +63,7 @@ import org.apache.james.mailbox.store.mail.UidProvider;
 import org.apache.james.mailbox.store.mail.model.DefaultMessageId;
 import org.apache.james.mailbox.store.user.SubscriptionMapperFactory;
 import org.apache.james.modules.data.JPAEntityManagerModule;
+import org.apache.james.user.api.DeleteUserDataTaskStep;
 import org.apache.james.user.api.UsernameChangeTaskStep;
 import org.apache.james.utils.MailboxManagerDefinition;
 import org.apache.mailbox.tools.indexer.ReIndexerImpl;
@@ -130,6 +132,9 @@ public class JPAMailboxModule extends AbstractModule {
         usernameChangeTaskStepMultibinder.addBinding().to(MailboxUsernameChangeTaskStep.class);
         usernameChangeTaskStepMultibinder.addBinding().to(ACLUsernameChangeTaskStep.class);
         usernameChangeTaskStepMultibinder.addBinding().to(QuotaUsernameChangeTaskStep.class);
+
+        Multibinder<DeleteUserDataTaskStep> deleteUserDataTaskStepMultibinder = Multibinder.newSetBinder(binder(), DeleteUserDataTaskStep.class);
+        deleteUserDataTaskStepMultibinder.addBinding().to(MailboxUserDeletionTaskStep.class);
     }
     
     @Singleton
diff --git a/server/container/guice/memory/src/main/java/org/apache/james/modules/mailbox/MemoryMailboxModule.java b/server/container/guice/memory/src/main/java/org/apache/james/modules/mailbox/MemoryMailboxModule.java
index 61083eb515..0eddc3cc35 100644
--- a/server/container/guice/memory/src/main/java/org/apache/james/modules/mailbox/MemoryMailboxModule.java
+++ b/server/container/guice/memory/src/main/java/org/apache/james/modules/mailbox/MemoryMailboxModule.java
@@ -25,6 +25,7 @@ import javax.inject.Singleton;
 
 import org.apache.james.adapter.mailbox.ACLUsernameChangeTaskStep;
 import org.apache.james.adapter.mailbox.DelegationStoreAuthorizator;
+import org.apache.james.adapter.mailbox.MailboxUserDeletionTaskStep;
 import org.apache.james.adapter.mailbox.MailboxUsernameChangeTaskStep;
 import org.apache.james.adapter.mailbox.QuotaUsernameChangeTaskStep;
 import org.apache.james.adapter.mailbox.UserRepositoryAuthenticator;
@@ -75,6 +76,7 @@ import org.apache.james.mailbox.store.mail.UidProvider;
 import org.apache.james.mailbox.store.search.MessageSearchIndex;
 import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex;
 import org.apache.james.mailbox.store.user.SubscriptionMapperFactory;
+import org.apache.james.user.api.DeleteUserDataTaskStep;
 import org.apache.james.user.api.UsernameChangeTaskStep;
 import org.apache.james.utils.MailboxManagerDefinition;
 import org.apache.james.vault.memory.metadata.MemoryDeletedMessageMetadataVault;
@@ -162,6 +164,9 @@ public class MemoryMailboxModule extends AbstractModule {
         usernameChangeTaskStepMultibinder.addBinding().to(MailboxUsernameChangeTaskStep.class);
         usernameChangeTaskStepMultibinder.addBinding().to(ACLUsernameChangeTaskStep.class);
         usernameChangeTaskStepMultibinder.addBinding().to(QuotaUsernameChangeTaskStep.class);
+
+        Multibinder<DeleteUserDataTaskStep> deleteUserDataTaskStepMultibinder = Multibinder.newSetBinder(binder(), DeleteUserDataTaskStep.class);
+        deleteUserDataTaskStepMultibinder.addBinding().to(MailboxUserDeletionTaskStep.class);
     }
 
     @Singleton
diff --git a/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/MailboxUserDeletionTaskStep.java b/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/MailboxUserDeletionTaskStep.java
new file mode 100644
index 0000000000..035686decf
--- /dev/null
+++ b/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/MailboxUserDeletionTaskStep.java
@@ -0,0 +1,109 @@
+/****************************************************************
+ * 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.adapter.mailbox;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.SubscriptionManager;
+import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.MailboxACL;
+import org.apache.james.mailbox.model.MailboxMetaData;
+import org.apache.james.mailbox.model.search.MailboxQuery;
+import org.apache.james.mailbox.store.StoreMailboxManager;
+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 MailboxUserDeletionTaskStep implements DeleteUserDataTaskStep {
+    private final StoreMailboxManager mailboxManager;
+    private final SubscriptionManager subscriptionManager;
+
+    @Inject
+    public MailboxUserDeletionTaskStep(StoreMailboxManager mailboxManager, SubscriptionManager subscriptionManager) {
+        this.mailboxManager = mailboxManager;
+        this.subscriptionManager = subscriptionManager;
+    }
+
+    @Override
+    public StepName name() {
+        return new StepName("MailboxUserDeletionTaskStep");
+    }
+
+    @Override
+    public int priority() {
+        return 5;
+    }
+
+    @Override
+    public Publisher<Void> deleteUserData(Username username) {
+        MailboxSession mailboxSession = mailboxManager.createSystemSession(username);
+
+        return getAllMailboxesOfUser(mailboxSession)
+            .concatMap(mailbox -> deleteMailbox(mailboxSession, mailbox)
+                .then(deleteSubscription(mailboxSession, mailbox)))
+            .then(getSharedMailboxesOfUser(mailboxSession)
+                .flatMap(sharedMailbox -> revokeACLs(username, sharedMailbox)
+                    .then(deleteSubscription(mailboxSession, sharedMailbox)))
+                .then());
+    }
+
+    private Flux<MailboxMetaData> getAllMailboxesOfUser(MailboxSession mailboxSession) {
+        MailboxQuery queryMailboxesOfUser = MailboxQuery.builder()
+            .privateNamespace()
+            .user(mailboxSession.getUser())
+            .build();
+
+        return mailboxManager.search(queryMailboxesOfUser, MailboxManager.MailboxSearchFetchType.Minimal, mailboxSession);
+    }
+
+    private Flux<MailboxMetaData> getSharedMailboxesOfUser(MailboxSession mailboxSession) {
+        return mailboxManager.search(MailboxQuery.builder().matchesAllMailboxNames().build(), mailboxSession)
+            .filter(mailbox -> !mailbox.getPath().getUser().equals(mailboxSession.getUser()));
+    }
+
+    private Mono<Mailbox> deleteMailbox(MailboxSession mailboxSession, MailboxMetaData mailbox) {
+        return mailboxManager.deleteMailboxReactive(mailbox.getId(), mailboxSession);
+    }
+
+    private Mono<Void> deleteSubscription(MailboxSession mailboxSession, MailboxMetaData mailbox) {
+        return Mono.from(subscriptionManager.unsubscribeReactive(mailbox.getPath(), mailboxSession));
+    }
+
+    private Mono<Void> revokeACLs(Username username, MailboxMetaData mailbox) {
+        MailboxSession ownerSession = mailboxManager.createSystemSession(mailbox.getPath().getUser());
+        MailboxACL.Rfc4314Rights rights = Optional.ofNullable(mailbox.getMailbox().getACL().getEntries().get(MailboxACL.EntryKey.createUserEntryKey(username)))
+            .orElse(MailboxACL.NO_RIGHTS);
+
+        return Mono.fromRunnable(Throwing.runnable(() -> mailboxManager.applyRightsCommand(mailbox.getId(), MailboxACL.command().rights(rights).forUser(username).asRemoval(), ownerSession)))
+            .subscribeOn(ReactorUtils.BLOCKING_CALL_WRAPPER)
+            .then();
+    }
+
+}
diff --git a/server/container/mailbox-adapter/src/test/java/org/apache/james/adapter/mailbox/MailboxUserDeletionTaskStepTest.java b/server/container/mailbox-adapter/src/test/java/org/apache/james/adapter/mailbox/MailboxUserDeletionTaskStepTest.java
new file mode 100644
index 0000000000..4040a4050b
--- /dev/null
+++ b/server/container/mailbox-adapter/src/test/java/org/apache/james/adapter/mailbox/MailboxUserDeletionTaskStepTest.java
@@ -0,0 +1,212 @@
+/****************************************************************
+ * 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.adapter.mailbox;
+
+import static org.apache.james.mailbox.MailboxManager.MailboxSearchFetchType.Minimal;
+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.mailbox.MailboxManager;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageManager;
+import org.apache.james.mailbox.inmemory.InMemoryMailboxManager;
+import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
+import org.apache.james.mailbox.model.MailboxACL;
+import org.apache.james.mailbox.model.MailboxMetaData;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.MultimailboxesSearchQuery;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.model.search.MailboxQuery;
+import org.apache.james.mailbox.store.StoreSubscriptionManager;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import reactor.core.publisher.Mono;
+
+class MailboxUserDeletionTaskStepTest {
+    private static final Username ALICE = Username.of("alice");
+    private static final Username BOB = Username.of("bob");
+
+    private InMemoryMailboxManager mailboxManager;
+    private StoreSubscriptionManager subscriptionManager;
+    private MailboxUserDeletionTaskStep testee;
+
+    @BeforeEach
+    void setUp() {
+        InMemoryIntegrationResources resources = InMemoryIntegrationResources.defaultResources();
+        mailboxManager = resources.getMailboxManager();
+        subscriptionManager = new StoreSubscriptionManager(resources.getMailboxManager().getMapperFactory(), resources.getMailboxManager().getMapperFactory(),
+            resources.getEventBus());
+        testee = new MailboxUserDeletionTaskStep(mailboxManager, subscriptionManager);
+    }
+
+    @Test
+    void shouldRemoveSubscriptionsOnMailboxes() throws Exception {
+        MailboxSession session = mailboxManager.createSystemSession(ALICE);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "subscribed1"), MailboxManager.CreateOption.CREATE_SUBSCRIPTION, session);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "subscribed2"), MailboxManager.CreateOption.CREATE_SUBSCRIPTION, session);
+
+        Mono.from(testee.deleteUserData(ALICE)).block();
+
+        assertThat(subscriptionManager.subscriptions(session)).isEmpty();
+    }
+
+    @Test
+    void shouldRemoveSubscriptionsOnSubMailboxes() throws Exception {
+        MailboxSession session = mailboxManager.createSystemSession(ALICE);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "parent"), MailboxManager.CreateOption.CREATE_SUBSCRIPTION, session);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "parent.child1"), MailboxManager.CreateOption.CREATE_SUBSCRIPTION, session);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "parent.child2"), MailboxManager.CreateOption.CREATE_SUBSCRIPTION, session);
+
+        Mono.from(testee.deleteUserData(ALICE)).block();
+
+        assertThat(subscriptionManager.subscriptions(session)).isEmpty();
+    }
+
+    @Test
+    void shouldRemoveSubscriptionsOnSharedMailboxes() throws Exception {
+        // BOB shares his Inbox access to ALICE
+        MailboxSession bobSession = mailboxManager.createSystemSession(BOB);
+        mailboxManager.createMailbox(MailboxPath.inbox(BOB), MailboxManager.CreateOption.NONE, bobSession);
+        mailboxManager.applyRightsCommand(MailboxPath.inbox(BOB),
+            MailboxACL.command().forUser(ALICE).rights(MailboxACL.FULL_RIGHTS).asAddition(),
+            bobSession);
+
+        // ALICE subscribes to BOB Inbox
+        subscriptionManager.subscribe(mailboxManager.createSystemSession(ALICE), MailboxPath.inbox(BOB));
+
+        Mono.from(testee.deleteUserData(ALICE)).block();
+
+        // ALICE subscription on the shared mailbox should be deleted
+        assertThat(subscriptionManager.subscriptions(mailboxManager.createSystemSession(ALICE)))
+            .isEmpty();
+    }
+
+    @Test
+    void shouldRemoveMailboxes() throws Exception {
+        MailboxSession session = mailboxManager.createSystemSession(ALICE);
+        mailboxManager.createMailbox(MailboxPath.inbox(ALICE), MailboxManager.CreateOption.NONE, session);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "test"), MailboxManager.CreateOption.NONE, session);
+
+        Mono.from(testee.deleteUserData(ALICE)).block();
+
+        assertThat(mailboxManager.list(session))
+            .isEmpty();
+    }
+
+    @Test
+    void shouldRemoveMessagesInMailboxes() throws Exception {
+        MailboxSession session = mailboxManager.createSystemSession(ALICE);
+        mailboxManager.createMailbox(MailboxPath.inbox(ALICE), MailboxManager.CreateOption.NONE, session);
+        mailboxManager.getMailbox(MailboxPath.inbox(ALICE), session)
+            .appendMessage(MessageManager.AppendCommand.builder()
+                .build("message content"), session);
+
+        Mono.from(testee.deleteUserData(ALICE)).block();
+
+        assertThat(mailboxManager.search(MultimailboxesSearchQuery.from(SearchQuery.matchAll()).build(), session, 100L)
+            .collectList().block())
+            .isEmpty();
+    }
+
+    @Test
+    void shouldRemoveSubMailboxes() throws Exception {
+        MailboxSession session = mailboxManager.createSystemSession(ALICE);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "parent"), MailboxManager.CreateOption.NONE, session);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "parent.child1"), MailboxManager.CreateOption.NONE, session);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "parent.child2"), MailboxManager.CreateOption.NONE, session);
+
+        Mono.from(testee.deleteUserData(ALICE)).block();
+
+        assertThat(mailboxManager.list(session))
+            .isEmpty();
+    }
+
+    @Test
+    void shouldRemoveMessagesInSubMailboxes() throws Exception {
+        MailboxSession session = mailboxManager.createSystemSession(ALICE);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "parent.child1"), MailboxManager.CreateOption.NONE, session);
+        mailboxManager.getMailbox(MailboxPath.forUser(ALICE, "parent.child1"), session)
+            .appendMessage(MessageManager.AppendCommand.builder()
+                .build("message content"), session);
+
+        Mono.from(testee.deleteUserData(ALICE)).block();
+
+        assertThat(mailboxManager.search(MultimailboxesSearchQuery.from(SearchQuery.matchAll()).build(), session, 100L)
+            .collectList().block())
+            .isEmpty();
+    }
+
+    @Test
+    void shouldNotRemoveMailboxesOfOtherUsers() throws Exception {
+        MailboxSession bobSession = mailboxManager.createSystemSession(BOB);
+        mailboxManager.createMailbox(MailboxPath.inbox(BOB), MailboxManager.CreateOption.NONE, bobSession);
+
+        Mono.from(testee.deleteUserData(ALICE)).block();
+
+        assertThat(mailboxManager.list(bobSession))
+            .containsOnly(MailboxPath.inbox(BOB));
+    }
+
+    @Test
+    void shouldNotRemoveSubscriptionsOnMailboxesOfOtherUsers() throws Exception {
+        MailboxSession bobSession = mailboxManager.createSystemSession(BOB);
+        mailboxManager.createMailbox(MailboxPath.forUser(BOB, "subscribed"), MailboxManager.CreateOption.CREATE_SUBSCRIPTION, bobSession);
+
+        Mono.from(testee.deleteUserData(ALICE)).block();
+
+        assertThat(subscriptionManager.subscriptions(bobSession))
+            .containsOnly(MailboxPath.forUser(BOB, "subscribed"));
+    }
+
+    @Test
+    void shouldRevokeRightsOnSharedMailboxes() throws Exception {
+        // BOB creates Inbox
+        MailboxSession bobSession = mailboxManager.createSystemSession(BOB);
+        mailboxManager.createMailbox(MailboxPath.inbox(BOB), MailboxManager.CreateOption.NONE, bobSession);
+
+        // BOB shares his Inbox access to ALICE
+        mailboxManager.applyRightsCommand(MailboxPath.inbox(BOB),
+            MailboxACL.command().forUser(ALICE).rights(MailboxACL.FULL_RIGHTS).asAddition(),
+            bobSession);
+
+        Mono.from(testee.deleteUserData(ALICE)).block();
+
+        // Alice ACL on the shared mailbox should be no longer existed
+        MailboxSession aliceSession = mailboxManager.createSystemSession(ALICE);
+
+        assertThat(mailboxManager.search(MailboxQuery.builder().matchesAllMailboxNames().build(), Minimal, aliceSession)
+            .map(MailboxMetaData::getPath)
+            .collectList()
+            .block())
+            .isEmpty();
+        assertThat(mailboxManager.hasRight(MailboxPath.inbox(BOB), MailboxACL.Right.Read, aliceSession))
+            .isFalse();
+    }
+
+    @Test
+    void shouldBeIdempotent() {
+        Mono.from(testee.deleteUserData(ALICE)).block();
+
+        assertThatCode(() -> Mono.from(testee.deleteUserData(ALICE)).block())
+            .doesNotThrowAnyException();
+    }
+}
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
new file mode 100644
index 0000000000..40659bbac7
--- /dev/null
+++ b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUserDeletionIntegrationTest.java
@@ -0,0 +1,111 @@
+/****************************************************************
+ * 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.webadmin.integration.memory;
+
+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;
+import static org.apache.james.jmap.JMAPTestingConstants.BOB;
+import static org.apache.james.jmap.JMAPTestingConstants.BOB_PASSWORD;
+import static org.apache.james.jmap.JMAPTestingConstants.DOMAIN;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+
+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.mailbox.model.MailboxACL;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.modules.ACLProbeImpl;
+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.WebAdminGuiceProbe;
+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 io.restassured.specification.RequestSpecification;
+
+class MemoryUserDeletionIntegrationTest {
+    @RegisterExtension
+    static JamesServerExtension jamesServerExtension = new JamesServerBuilder<MemoryJamesConfiguration>(tmpDir ->
+        MemoryJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .usersRepository(DEFAULT)
+            .build())
+        .server(configuration -> MemoryJamesServerMain.createServer(configuration)
+            .overrideWith(new TestJMAPServerModule()))
+        .build();
+
+    private RequestSpecification webAdminApi;
+
+    @BeforeEach
+    void setUp(GuiceJamesServer jmapServer) throws Exception {
+        DataProbe dataProbe = jmapServer.getProbe(DataProbeImpl.class);
+        dataProbe.addDomain(DOMAIN);
+        dataProbe.addUser(ALICE.asString(), ALICE_PASSWORD);
+        dataProbe.addUser(BOB.asString(), BOB_PASSWORD);
+
+        webAdminApi = WebAdminUtils.spec(jmapServer.getProbe(WebAdminGuiceProbe.class).getWebAdminPort());
+    }
+
+    @Test
+    void shouldDeleteMailboxes() {
+        webAdminApi.put("/users/" + ALICE.asString() + "/mailboxes/test");
+
+        String taskId = webAdminApi
+            .queryParam("action", "deleteData")
+            .post("/users/" + ALICE.asString())
+            .jsonPath()
+            .get("taskId");
+
+        webAdminApi.get("/tasks/" + taskId + "/await");
+
+        webAdminApi.get("/users/" + ALICE.asString() + "/mailboxes")
+            .then()
+            .body(".", hasSize(0));
+    }
+
+    @Test
+    void shouldDeleteACLs(GuiceJamesServer server) throws Exception {
+        server.getProbe(MailboxProbeImpl.class).createMailbox(MailboxPath.inbox(BOB));
+        server.getProbe(ACLProbeImpl.class).addRights(MailboxPath.inbox(BOB), ALICE.asString(), MailboxACL.FULL_RIGHTS);
+
+        String taskId = webAdminApi
+            .queryParam("action", "deleteData")
+            .post("/users/" + ALICE.asString())
+            .jsonPath()
+            .get("taskId");
+
+        webAdminApi.get("/tasks/" + taskId + "/await");
+
+        // Bob Inbox should no longer accept Alice access
+        MailboxACL acls = server.getProbe(ACLProbeImpl.class).retrieveRights(MailboxPath.inbox(BOB));
+        assertThat(acls.getEntries())
+            .hasSize(1)
+            .containsEntry(MailboxACL.EntryKey.deserialize("owner"), MailboxACL.FULL_RIGHTS);
+    }
+}


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