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 e32c1d9d6d JAMES-3885 Migrates mailboxes, acls, subscriptions upon 
username changes (#1450)
e32c1d9d6d is described below

commit e32c1d9d6d71eb1319b966bc6d688c88d230d439
Author: Benoit TELLIER <[email protected]>
AuthorDate: Sat Feb 25 21:12:38 2023 +0700

    JAMES-3885 Migrates mailboxes, acls, subscriptions upon username changes 
(#1450)
---
 .../apache/james/mailbox/model/MailboxPath.java    |   4 +
 .../james/mailbox/store/StoreMailboxManager.java   |  81 ++++++-----
 .../docs/modules/ROOT/pages/operate/webadmin.adoc  |   3 +
 .../modules/mailbox/CassandraMailboxModule.java    |   7 +
 .../james/modules/mailbox/JPAMailboxModule.java    |   7 +
 .../james/modules/mailbox/MemoryMailboxModule.java |   7 +
 server/container/mailbox-adapter/pom.xml           |  31 +++++
 .../adapter/mailbox/ACLUsernameChangeTaskStep.java |  95 +++++++++++++
 .../mailbox/MailboxUsernameChangeTaskStep.java     | 102 ++++++++++++++
 .../mailbox/ACLUsernameChangeTaskStepTest.java     | 132 ++++++++++++++++++
 .../mailbox/MailboxUsernameChangeTaskStepTest.java | 150 +++++++++++++++++++++
 .../MemoryUsernameChangeIntegrationTest.java       |  48 ++++++-
 src/site/markdown/server/manage-webadmin.md        |   3 +
 13 files changed, 634 insertions(+), 36 deletions(-)

diff --git 
a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java 
b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java
index b78d477128..a600c89894 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java
@@ -133,6 +133,10 @@ public class MailboxPath {
         return user;
     }
 
+    public MailboxPath withUser(Username username) {
+        return new MailboxPath(namespace, username, name);
+    }
+
     /**
      * Get the name of the mailbox. This is the pure name without user or
      * namespace, so this is what a user would see in his client.
diff --git 
a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
 
b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
index 72fad772e5..1a5304e0e1 100644
--- 
a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
+++ 
b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
@@ -97,6 +97,7 @@ import com.google.common.collect.ImmutableSet;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import reactor.core.publisher.SynchronousSink;
 import reactor.core.scheduler.Schedulers;
 import reactor.util.retry.Retry;
 import reactor.util.retry.RetryBackoffSpec;
@@ -533,52 +534,60 @@ public class StoreMailboxManager implements 
MailboxManager {
 
     @Override
     public Mono<List<MailboxRenamedResult>> renameMailboxReactive(MailboxPath 
from, MailboxPath to, RenameOption option, MailboxSession session) {
+        return renameMailboxReactive(from, to, option, session, session);
+    }
+
+    public Mono<List<MailboxRenamedResult>> renameMailboxReactive(MailboxPath 
from, MailboxPath to, RenameOption option,
+                                                                  
MailboxSession fromSession, MailboxSession toSession) {
         LOGGER.debug("renameMailbox {} to {}", from, to);
-        MailboxMapper mapper = 
mailboxSessionMapperFactory.getMailboxMapper(session);
+        MailboxMapper mapper = 
mailboxSessionMapperFactory.getMailboxMapper(fromSession);
 
-        return sanitizedPath(from, to, session)
+        return sanitizedPath(from, to, fromSession, toSession)
         .flatMap(sanitizedPath -> mapper.executeReactive(
             mapper.findMailboxByPath(from)
                 .switchIfEmpty(Mono.error(() -> new 
MailboxNotFoundException(from)))
-                .flatMap(mailbox -> doRenameMailbox(mailbox, sanitizedPath, 
session, mapper)
-                    .flatMap(renamedResults -> 
renameSubscriptionsIfNeeded(renamedResults, option, session)))));
+                .flatMap(mailbox -> doRenameMailbox(mailbox, sanitizedPath, 
fromSession, toSession, mapper)
+                    .flatMap(renamedResults -> 
renameSubscriptionsIfNeeded(renamedResults, option, fromSession, toSession)))));
     }
 
-    private Mono<MailboxPath> sanitizedPath(MailboxPath from, MailboxPath to, 
MailboxSession session) {
-        return Mono.fromCallable(() -> {
-            MailboxPath sanitizedMailboxPath = 
to.sanitize(session.getPathDelimiter());
-            validateDestinationPath(sanitizedMailboxPath, session);
-            assertIsOwner(session, from);
-            return sanitizedMailboxPath;
-        });
+    private Mono<MailboxPath> sanitizedPath(MailboxPath from, MailboxPath to, 
MailboxSession fromSession, MailboxSession toSession) {
+        MailboxPath sanitizedMailboxPath = 
to.sanitize(toSession.getPathDelimiter());
+
+        return validateDestinationPath(sanitizedMailboxPath, toSession)
+            .then(Mono.fromRunnable(Throwing.runnable(() -> 
assertIsOwner(fromSession, from))))
+            .thenReturn(sanitizedMailboxPath);
     }
 
     private Mono<MailboxPath> sanitizedPath(MailboxPath to, MailboxSession 
session) {
-        return Mono.fromCallable(() -> {
-            MailboxPath sanitizedMailboxPath = 
to.sanitize(session.getPathDelimiter());
-            validateDestinationPath(sanitizedMailboxPath, session);
-            return sanitizedMailboxPath;
-        });
+        MailboxPath sanitizedMailboxPath = 
to.sanitize(session.getPathDelimiter());
+
+        return validateDestinationPath(sanitizedMailboxPath, session)
+            .thenReturn(sanitizedMailboxPath);
     }
 
     private Mono<List<MailboxRenamedResult>> 
renameSubscriptionsIfNeeded(List<MailboxRenamedResult> renamedResults,
                                                                    
RenameOption option, MailboxSession session) {
+        return renameSubscriptionsIfNeeded(renamedResults, option, session, 
session);
+    }
+
+    private Mono<List<MailboxRenamedResult>> 
renameSubscriptionsIfNeeded(List<MailboxRenamedResult> renamedResults,
+                                                                   
RenameOption option, MailboxSession fromSession, MailboxSession toSession) {
         if (option == RenameOption.RENAME_SUBSCRIPTIONS) {
-            SubscriptionMapper subscriptionMapper = 
mailboxSessionMapperFactory.getSubscriptionMapper(session);
+            SubscriptionMapper subscriptionMapper = 
mailboxSessionMapperFactory.getSubscriptionMapper(fromSession);
 
-            return 
subscriptionMapper.findSubscriptionsForUserReactive(session.getUser())
+            return 
subscriptionMapper.findSubscriptionsForUserReactive(fromSession.getUser())
                 .collectList()
                 .flatMap(subscriptions -> Flux.fromIterable(renamedResults)
                     .flatMap(renamedResult -> {
-                        Subscription legacySubscription = new 
Subscription(session.getUser(), renamedResult.getOriginPath().getName());
+                        Function<Subscription, Mono<Void>> renameFunction = 
subscription -> subscriptionMapper.deleteReactive(subscription)
+                            .then(subscriptionMapper.saveReactive(new 
Subscription(toSession.getUser(), 
renamedResult.getDestinationPath().asEscapedString())));
+                        Subscription legacySubscription = new 
Subscription(fromSession.getUser(), renamedResult.getOriginPath().getName());
                         if (subscriptions.contains(legacySubscription)) {
-                            return 
subscriptionMapper.deleteReactive(legacySubscription)
-                                .then(subscriptionMapper.saveReactive(new 
Subscription(session.getUser(), 
renamedResult.getDestinationPath().asEscapedString())));
+                            return renameFunction.apply(legacySubscription);
                         }
-                        Subscription subscription = new 
Subscription(session.getUser(), 
renamedResult.getOriginPath().asEscapedString());
+                        Subscription subscription = new 
Subscription(fromSession.getUser(), 
renamedResult.getOriginPath().asEscapedString());
                         if (subscriptions.contains(subscription)) {
-                            return 
subscriptionMapper.deleteReactive(subscription)
-                                .then(subscriptionMapper.saveReactive(new 
Subscription(session.getUser(), 
renamedResult.getDestinationPath().asEscapedString())));
+                            return renameFunction.apply(subscription);
                         }
                         return Mono.empty();
                     })
@@ -605,16 +614,19 @@ public class StoreMailboxManager implements 
MailboxManager {
                 mapper.findMailboxById(mailboxId)
                     .doOnNext(Throwing.<Mailbox>consumer(mailbox -> 
assertIsOwner(session, mailbox.generateAssociatedPath())).sneakyThrow())
                     .switchIfEmpty(Mono.error(() -> new 
MailboxNotFoundException(mailboxId)))
-                    .flatMap(mailbox -> doRenameMailbox(mailbox, 
sanitizedPath, session, mapper)
+                    .flatMap(mailbox -> doRenameMailbox(mailbox, 
sanitizedPath, session, session, mapper)
                         .flatMap(renamedResults -> 
renameSubscriptionsIfNeeded(renamedResults, option, session)))));
     }
 
-    private void validateDestinationPath(MailboxPath newMailboxPath, 
MailboxSession session) throws MailboxException {
-        if (block(mailboxExists(newMailboxPath, session))) {
-            throw new MailboxExistsException(newMailboxPath.toString());
-        }
-        assertIsOwner(session, newMailboxPath);
-        newMailboxPath.assertAcceptable(session.getPathDelimiter());
+    private Mono<Void> validateDestinationPath(MailboxPath newMailboxPath, 
MailboxSession session) {
+        return mailboxExists(newMailboxPath, session)
+            .handle(Throwing.<Boolean, 
SynchronousSink<Void>>biConsumer((exists, sink) -> {
+                if (exists) {
+                    sink.error(new 
MailboxExistsException(newMailboxPath.toString()));
+                }
+                assertIsOwner(session, newMailboxPath);
+                newMailboxPath.assertAcceptable(session.getPathDelimiter());
+            }).sneakyThrow());
     }
 
     private void assertIsOwner(MailboxSession mailboxSession, MailboxPath 
mailboxPath) throws MailboxNotFoundException {
@@ -624,7 +636,7 @@ public class StoreMailboxManager implements MailboxManager {
         }
     }
 
-    private Mono<List<MailboxRenamedResult>> doRenameMailbox(Mailbox mailbox, 
MailboxPath newMailboxPath, MailboxSession session, MailboxMapper mapper) {
+    private Mono<List<MailboxRenamedResult>> doRenameMailbox(Mailbox mailbox, 
MailboxPath newMailboxPath, MailboxSession fromSession, MailboxSession 
toSession, MailboxMapper mapper) {
         // TODO put this into a serilizable transaction
 
         ImmutableList.Builder<MailboxRenamedResult> resultBuilder = 
ImmutableList.builder();
@@ -636,7 +648,7 @@ public class StoreMailboxManager implements MailboxManager {
         // Find submailboxes
         MailboxQuery.UserBound query = MailboxQuery.builder()
             .userAndNamespaceFrom(from)
-            .expression(new PrefixedWildcard(from.getName() + 
session.getPathDelimiter()))
+            .expression(new PrefixedWildcard(from.getName() + 
fromSession.getPathDelimiter()))
             .build()
             .asUserBound();
 
@@ -651,6 +663,7 @@ public class StoreMailboxManager implements MailboxManager {
                         String subNewName = newMailboxPath.getName() + 
subOriginalName.substring(from.getName().length());
                         MailboxPath fromPath = new MailboxPath(from, 
subOriginalName);
                         sub.setName(subNewName);
+                        sub.setUser(toSession.getUser());
                         return mapper.rename(sub)
                             .map(mailboxId -> {
                                 resultBuilder.add(new 
MailboxRenamedResult(sub.getMailboxId(), fromPath, 
sub.generateAssociatedPath()));
@@ -663,7 +676,7 @@ public class StoreMailboxManager implements MailboxManager {
             .then(Mono.defer(() -> Flux.fromIterable(resultBuilder.build())
                 .concatMap(result -> 
eventBus.dispatch(EventFactory.mailboxRenamed()
                         .randomEventId()
-                        .mailboxSession(session)
+                        .mailboxSession(fromSession)
                         .mailboxId(result.getMailboxId())
                         .oldPath(result.getOriginPath())
                         .newPath(result.getDestinationPath())
diff --git 
a/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc 
b/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
index 229b6f2ad3..9621808429 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
@@ -635,6 +635,9 @@ Implemented migration steps are:
  - `ForwardUsernameChangeTaskStep`: creates forward from old user to new user 
and migrates existing forwards
  - `FilterUsernameChangeTaskStep`: migrates users filtering rules
  - `DelegationUsernameChangeTaskStep`: migrates delegations where the impacted 
user is either delegatee or delegator
+ - `MailboxUsernameChangeTaskStep`: migrates mailboxes belonging to the old 
user to the account of the new user. It also
+ migrates user's mailbox subscriptions.
+ - `ACLUsernameChangeTaskStep`: migrates ACLs on mailboxes the migrated user 
has access to and updates subscriptions accordingly.
 
 Response codes:
 
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 00d6ddb446..cf38c919b4 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
@@ -22,6 +22,8 @@ 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.MailboxUsernameChangeTaskStep;
 import org.apache.james.adapter.mailbox.UserRepositoryAuthenticator;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import 
org.apache.james.backends.cassandra.versions.CassandraSchemaVersionManager;
@@ -113,6 +115,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.UsernameChangeTaskStep;
 import org.apache.james.utils.MailboxManagerDefinition;
 import org.apache.mailbox.tools.indexer.MessageIdReIndexerImpl;
 import org.apache.mailbox.tools.indexer.ReIndexerImpl;
@@ -240,6 +243,10 @@ public class CassandraMailboxModule extends AbstractModule 
{
             .addBinding().to(AttachmentBlobReferenceSource.class);
         Multibinder.newSetBinder(binder(), BlobReferenceSource.class)
             .addBinding().to(MessageBlobReferenceSource.class);
+
+        Multibinder<UsernameChangeTaskStep> usernameChangeTaskStepMultibinder 
= Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class);
+        
usernameChangeTaskStepMultibinder.addBinding().to(MailboxUsernameChangeTaskStep.class);
+        
usernameChangeTaskStepMultibinder.addBinding().to(ACLUsernameChangeTaskStep.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 13a89ce360..62138140bf 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
@@ -22,6 +22,8 @@ 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.MailboxUsernameChangeTaskStep;
 import org.apache.james.adapter.mailbox.UserRepositoryAuthenticator;
 import org.apache.james.adapter.mailbox.UserRepositoryAuthorizator;
 import org.apache.james.events.EventListener;
@@ -59,6 +61,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.UsernameChangeTaskStep;
 import org.apache.james.utils.MailboxManagerDefinition;
 import org.apache.mailbox.tools.indexer.ReIndexerImpl;
 
@@ -121,6 +124,10 @@ public class JPAMailboxModule extends AbstractModule {
 
         
bind(MailboxManager.class).annotatedWith(Names.named(MAILBOXMANAGER_NAME)).to(MailboxManager.class);
         
bind(MailboxManagerConfiguration.class).toInstance(MailboxManagerConfiguration.DEFAULT);
+
+        Multibinder<UsernameChangeTaskStep> usernameChangeTaskStepMultibinder 
= Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class);
+        
usernameChangeTaskStepMultibinder.addBinding().to(MailboxUsernameChangeTaskStep.class);
+        
usernameChangeTaskStepMultibinder.addBinding().to(ACLUsernameChangeTaskStep.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 dd9993ca6e..033d4daccb 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
@@ -23,7 +23,9 @@ 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.DelegationStoreAuthorizator;
+import org.apache.james.adapter.mailbox.MailboxUsernameChangeTaskStep;
 import org.apache.james.adapter.mailbox.UserRepositoryAuthenticator;
 import org.apache.james.events.EventListener;
 import org.apache.james.jmap.api.change.EmailChangeRepository;
@@ -72,6 +74,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.UsernameChangeTaskStep;
 import org.apache.james.utils.MailboxManagerDefinition;
 import 
org.apache.james.vault.memory.metadata.MemoryDeletedMessageMetadataVault;
 import org.apache.james.vault.metadata.DeletedMessageMetadataVault;
@@ -153,6 +156,10 @@ public class MemoryMailboxModule extends AbstractModule {
 
         
bind(MailboxManager.class).annotatedWith(Names.named(MAILBOXMANAGER_NAME)).to(MailboxManager.class);
         
bind(MailboxManagerConfiguration.class).toInstance(MailboxManagerConfiguration.DEFAULT);
+
+        Multibinder<UsernameChangeTaskStep> usernameChangeTaskStepMultibinder 
= Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class);
+        
usernameChangeTaskStepMultibinder.addBinding().to(MailboxUsernameChangeTaskStep.class);
+        
usernameChangeTaskStepMultibinder.addBinding().to(ACLUsernameChangeTaskStep.class);
     }
 
     @Singleton
diff --git a/server/container/mailbox-adapter/pom.xml 
b/server/container/mailbox-adapter/pom.xml
index e96a98cf33..5e0a3a52bd 100644
--- a/server/container/mailbox-adapter/pom.xml
+++ b/server/container/mailbox-adapter/pom.xml
@@ -43,6 +43,27 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-mailbox-memory</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-mailbox-memory</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-mailbox-store</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>event-bus-api</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>james-server-data-api</artifactId>
@@ -52,6 +73,16 @@
             <artifactId>james-server-data-memory</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-testing</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>metrics-tests</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>testing-base</artifactId>
diff --git 
a/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/ACLUsernameChangeTaskStep.java
 
b/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/ACLUsernameChangeTaskStep.java
new file mode 100644
index 0000000000..813dc8a043
--- /dev/null
+++ 
b/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/ACLUsernameChangeTaskStep.java
@@ -0,0 +1,95 @@
+/****************************************************************
+ * 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.exception.SubscriptionException;
+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.user.api.UsernameChangeTaskStep;
+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 ACLUsernameChangeTaskStep implements UsernameChangeTaskStep {
+    private final MailboxManager mailboxManager;
+    private final SubscriptionManager subscriptionManager;
+
+    @Inject
+    public ACLUsernameChangeTaskStep(MailboxManager mailboxManager, 
SubscriptionManager subscriptionManager) {
+        this.mailboxManager = mailboxManager;
+        this.subscriptionManager = subscriptionManager;
+    }
+
+    @Override
+    public StepName name() {
+        return new StepName("ACLUsernameChangeTaskStep");
+    }
+
+    @Override
+    public int priority() {
+        return 3;
+    }
+
+    @Override
+    public Publisher<Void> changeUsername(Username oldUsername, Username 
newUsername) {
+        MailboxSession oldSession = 
mailboxManager.createSystemSession(oldUsername);
+        MailboxSession newSession = 
mailboxManager.createSystemSession(newUsername);
+        return 
mailboxManager.search(MailboxQuery.builder().matchesAllMailboxNames().build(), 
oldSession)
+            .filter(mailbox -> 
!mailbox.getPath().getUser().equals(oldUsername))
+            .concatMap(mailbox -> migrateACLs(oldUsername, newUsername, 
mailbox))
+            .then(updateSubscriptionsOnDeletedMailboxes(oldUsername, 
oldSession, newSession));
+    }
+
+    private Mono<Void> updateSubscriptionsOnDeletedMailboxes(Username 
oldUsername, MailboxSession oldSession, MailboxSession newSession) {
+        try {
+            return 
Flux.from(subscriptionManager.subscriptionsReactive(oldSession))
+                .filter(subscription -> 
!subscription.getUser().equals(oldUsername))
+                .concatMap(subscription -> 
Mono.from(subscriptionManager.subscribeReactive(subscription, newSession))
+                    
.then(Mono.from(subscriptionManager.unsubscribeReactive(subscription, 
oldSession))))
+                .then();
+        } catch (SubscriptionException e) {
+            return Mono.error(e);
+        }
+    }
+
+    private Publisher<? extends Void> migrateACLs(Username oldUsername, 
Username newUsername, MailboxMetaData mailbox) {
+        MailboxSession ownerSession = 
mailboxManager.createSystemSession(mailbox.getPath().getUser());
+        MailboxACL.Rfc4314Rights rights = 
Optional.ofNullable(mailbox.getMailbox().getACL().getEntries().get(MailboxACL.EntryKey.createUserEntryKey(oldUsername)))
+            .orElse(MailboxACL.NO_RIGHTS);
+
+        return Mono.fromRunnable(Throwing.runnable(() -> 
mailboxManager.applyRightsCommand(mailbox.getId(), 
MailboxACL.command().rights(rights).forUser(newUsername).asAddition(), 
ownerSession)))
+            .then(Mono.fromRunnable(Throwing.runnable(() -> 
mailboxManager.applyRightsCommand(mailbox.getId(), 
MailboxACL.command().rights(rights).forUser(oldUsername).asRemoval(), 
ownerSession))))
+            .subscribeOn(ReactorUtils.BLOCKING_CALL_WRAPPER)
+            .then();
+    }
+}
diff --git 
a/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/MailboxUsernameChangeTaskStep.java
 
b/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/MailboxUsernameChangeTaskStep.java
new file mode 100644
index 0000000000..cf3ee76bc5
--- /dev/null
+++ 
b/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/MailboxUsernameChangeTaskStep.java
@@ -0,0 +1,102 @@
+/****************************************************************
+ * 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 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.MailboxACL;
+import org.apache.james.mailbox.model.MailboxMetaData;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.search.MailboxQuery;
+import org.apache.james.mailbox.store.StoreMailboxManager;
+import org.apache.james.user.api.UsernameChangeTaskStep;
+import org.reactivestreams.Publisher;
+
+import com.github.fge.lambdas.Throwing;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class MailboxUsernameChangeTaskStep implements UsernameChangeTaskStep {
+    private final StoreMailboxManager mailboxManager;
+    private final SubscriptionManager subscriptionManager;
+
+    @Inject
+    public MailboxUsernameChangeTaskStep(StoreMailboxManager mailboxManager, 
SubscriptionManager subscriptionManager) {
+        this.mailboxManager = mailboxManager;
+        this.subscriptionManager = subscriptionManager;
+    }
+
+    @Override
+    public StepName name() {
+        return new StepName("MailboxUsernameChangeTaskStep");
+    }
+
+    @Override
+    public int priority() {
+        return 2;
+    }
+
+    @Override
+    public Publisher<Void> changeUsername(Username oldUsername, Username 
newUsername) {
+        MailboxSession fromSession = 
mailboxManager.createSystemSession(oldUsername);
+        MailboxSession toSession = 
mailboxManager.createSystemSession(newUsername);
+
+        MailboxQuery queryUser = MailboxQuery.builder()
+            .privateNamespace()
+            .user(fromSession.getUser())
+            .build();
+
+        return mailboxManager.search(queryUser, 
MailboxManager.MailboxSearchFetchType.Minimal, fromSession)
+            // Only keep top level, rename takes care of sub mailboxes
+            .filter(mailbox -> 
mailbox.getPath().getHierarchyLevels(fromSession.getPathDelimiter()).size() == 
1)
+            .concatMap(mailbox -> migrateMailbox(fromSession, toSession, 
mailbox));
+    }
+
+    private Mono<Void> migrateMailbox(MailboxSession fromSession, 
MailboxSession toSession, org.apache.james.mailbox.model.MailboxMetaData 
mailbox) {
+        MailboxPath renamedPath = 
mailbox.getPath().withUser(toSession.getUser());
+        return mailboxManager.renameMailboxReactive(mailbox.getPath(), 
renamedPath,
+            MailboxManager.RenameOption.RENAME_SUBSCRIPTIONS,
+            fromSession, toSession)
+            .then(renameSubscriptionsForDelegatee(mailbox, renamedPath))
+            .then();
+    }
+
+    private Mono<Void> renameSubscriptionsForDelegatee(MailboxMetaData 
mailbox, MailboxPath renamedPath) {
+        return 
Flux.fromIterable(mailbox.getResolvedAcls().getEntries().entrySet())
+            .filter(entry -> entry.getKey().getNameType() == 
MailboxACL.NameType.user && !entry.getKey().isNegative())
+            .map(entry -> Username.of(entry.getKey().getName()))
+            .concatMap(Throwing.function(userWithAccess ->
+                
Flux.from(subscriptionManager.subscriptionsReactive(mailboxManager.createSystemSession(userWithAccess)))
+                    .filter(subscribedMailbox -> 
subscribedMailbox.equals(mailbox.getPath()))
+                    .concatMap(any -> renameSubscription(mailbox, renamedPath, 
userWithAccess))))
+            .then();
+    }
+
+    private Mono<Void> renameSubscription(MailboxMetaData mailbox, MailboxPath 
renamedPath, Username user) {
+        MailboxSession session = mailboxManager.createSystemSession(user);
+        return Mono.from(subscriptionManager.subscribeReactive(renamedPath, 
session))
+            
.then(Mono.from(subscriptionManager.unsubscribeReactive(mailbox.getPath(), 
session)));
+    }
+}
diff --git 
a/server/container/mailbox-adapter/src/test/java/org/apache/james/adapter/mailbox/ACLUsernameChangeTaskStepTest.java
 
b/server/container/mailbox-adapter/src/test/java/org/apache/james/adapter/mailbox/ACLUsernameChangeTaskStepTest.java
new file mode 100644
index 0000000000..a905abc050
--- /dev/null
+++ 
b/server/container/mailbox-adapter/src/test/java/org/apache/james/adapter/mailbox/ACLUsernameChangeTaskStepTest.java
@@ -0,0 +1,132 @@
+/****************************************************************
+ * 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.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.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 ACLUsernameChangeTaskStepTest {
+    private static final Username ALICE = Username.of("alice");
+    private static final Username BOB = Username.of("bob");
+    private static final Username CEDRIC = Username.of("cedric");
+
+    private InMemoryMailboxManager mailboxManager;
+    private ACLUsernameChangeTaskStep testee;
+    private StoreSubscriptionManager subscriptionManager;
+
+    @BeforeEach
+    void setUp() {
+        InMemoryIntegrationResources resources = 
InMemoryIntegrationResources.defaultResources();
+        mailboxManager = resources.getMailboxManager();
+        subscriptionManager = new 
StoreSubscriptionManager(resources.getMailboxManager().getMapperFactory(), 
resources.getMailboxManager().getMapperFactory(),
+            resources.getEventBus());
+        testee = new ACLUsernameChangeTaskStep(mailboxManager, 
subscriptionManager);
+    }
+
+    @Test
+    void shouldMigrateACLs() throws Exception {
+        MailboxSession cedricSession = 
mailboxManager.createSystemSession(CEDRIC);
+        mailboxManager.createMailbox(MailboxPath.inbox(CEDRIC), 
MailboxManager.CreateOption.NONE, cedricSession);
+        mailboxManager.applyRightsCommand(MailboxPath.inbox(CEDRIC),
+            
MailboxACL.command().forUser(ALICE).rights(MailboxACL.FULL_RIGHTS).asAddition(),
+            cedricSession);
+
+        Mono.from(testee.changeUsername(ALICE, BOB)).block();
+
+        MailboxSession bobSession = mailboxManager.createSystemSession(BOB);
+        MailboxQuery allMailboxes = 
MailboxQuery.builder().matchesAllMailboxNames().build();
+        // Bob sees the migrated mailbox
+        assertThat(mailboxManager.search(allMailboxes, Minimal, bobSession)
+            .map(MailboxMetaData::getPath)
+            .collectList()
+            .block())
+            .containsOnly(MailboxPath.inbox(CEDRIC));
+        // Bob can access the migrated mailbox
+        assertThatCode(() -> 
mailboxManager.getMailbox(MailboxPath.inbox(CEDRIC), bobSession))
+            .doesNotThrowAnyException();
+    }
+
+    @Test
+    void shouldMigrateSubscriptionsOnDelegatedUsers() throws Exception {
+        MailboxSession cedricSession = 
mailboxManager.createSystemSession(CEDRIC);
+        mailboxManager.createMailbox(MailboxPath.inbox(CEDRIC), 
MailboxManager.CreateOption.NONE, cedricSession);
+        mailboxManager.applyRightsCommand(MailboxPath.inbox(CEDRIC),
+            
MailboxACL.command().forUser(ALICE).rights(MailboxACL.FULL_RIGHTS).asAddition(),
+            cedricSession);
+        
subscriptionManager.subscribe(mailboxManager.createSystemSession(ALICE), 
MailboxPath.inbox(CEDRIC));
+
+        Mono.from(testee.changeUsername(ALICE, BOB)).block();
+
+        MailboxSession bobSession = mailboxManager.createSystemSession(BOB);
+        
assertThat(subscriptionManager.subscriptions(bobSession)).containsOnly(MailboxPath.inbox(CEDRIC));
+    }
+
+    @Test
+    void shouldDeleteDelegatedSubscriptionsForBaseUsers() throws Exception {
+        MailboxSession cedricSession = 
mailboxManager.createSystemSession(CEDRIC);
+        mailboxManager.createMailbox(MailboxPath.inbox(CEDRIC), 
MailboxManager.CreateOption.NONE, cedricSession);
+        mailboxManager.applyRightsCommand(MailboxPath.inbox(CEDRIC),
+            
MailboxACL.command().forUser(ALICE).rights(MailboxACL.FULL_RIGHTS).asAddition(),
+            cedricSession);
+        
subscriptionManager.subscribe(mailboxManager.createSystemSession(ALICE), 
MailboxPath.inbox(CEDRIC));
+
+        Mono.from(testee.changeUsername(ALICE, BOB)).block();
+
+        
assertThat(subscriptionManager.subscriptions(mailboxManager.createSystemSession(ALICE))).isEmpty();
+    }
+
+    @Test
+    void shouldDeleteRightsOnOriginalAccount() throws Exception {
+        MailboxSession cedricSession = 
mailboxManager.createSystemSession(CEDRIC);
+        mailboxManager.createMailbox(MailboxPath.inbox(CEDRIC), 
MailboxManager.CreateOption.NONE, cedricSession);
+        mailboxManager.applyRightsCommand(MailboxPath.inbox(CEDRIC),
+            
MailboxACL.command().forUser(ALICE).rights(MailboxACL.FULL_RIGHTS).asAddition(),
+            cedricSession);
+
+        Mono.from(testee.changeUsername(ALICE, BOB)).block();
+
+        MailboxSession aliceSession = 
mailboxManager.createSystemSession(ALICE);
+        MailboxQuery allMailboxes = 
MailboxQuery.builder().matchesAllMailboxNames().build();
+        // Alice no longer sees the migrated mailbox
+        assertThat(mailboxManager.search(allMailboxes, Minimal, aliceSession)
+            .map(MailboxMetaData::getPath)
+            .collectList()
+            .block())
+            .isEmpty();
+    }
+
+    // todo Alice subscribes to smb else mailbox
+}
\ No newline at end of file
diff --git 
a/server/container/mailbox-adapter/src/test/java/org/apache/james/adapter/mailbox/MailboxUsernameChangeTaskStepTest.java
 
b/server/container/mailbox-adapter/src/test/java/org/apache/james/adapter/mailbox/MailboxUsernameChangeTaskStepTest.java
new file mode 100644
index 0000000000..3ed5fdf096
--- /dev/null
+++ 
b/server/container/mailbox-adapter/src/test/java/org/apache/james/adapter/mailbox/MailboxUsernameChangeTaskStepTest.java
@@ -0,0 +1,150 @@
+/****************************************************************
+ * 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.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.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 MailboxUsernameChangeTaskStepTest {
+    private static final Username ALICE = Username.of("alice");
+    private static final Username BOB = Username.of("bob");
+    private static final Username CEDRIC = Username.of("cedric");
+
+    private InMemoryMailboxManager mailboxManager;
+    private StoreSubscriptionManager subscriptionManager;
+    private MailboxUsernameChangeTaskStep testee;
+
+    @BeforeEach
+    void setUp() {
+        InMemoryIntegrationResources resources = 
InMemoryIntegrationResources.defaultResources();
+        mailboxManager = resources.getMailboxManager();
+        subscriptionManager = new 
StoreSubscriptionManager(resources.getMailboxManager().getMapperFactory(), 
resources.getMailboxManager().getMapperFactory(),
+            resources.getEventBus());
+        testee = new MailboxUsernameChangeTaskStep(mailboxManager, 
subscriptionManager);
+    }
+
+    @Test
+    void shouldMigrateMailboxes() throws Exception {
+        MailboxSession fromSession = mailboxManager.createSystemSession(ALICE);
+        mailboxManager.createMailbox(MailboxPath.inbox(ALICE), 
MailboxManager.CreateOption.NONE, fromSession);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "test"), 
MailboxManager.CreateOption.NONE, fromSession);
+
+        Mono.from(testee.changeUsername(ALICE, BOB)).block();
+
+        
assertThat(mailboxManager.list(mailboxManager.createSystemSession(BOB)))
+            .containsOnly(MailboxPath.inbox(BOB),
+                MailboxPath.forUser(BOB, "test"));
+    }
+
+    @Test
+    void shouldMigrateACLsForOtherUsers() throws Exception {
+        MailboxSession fromSession = mailboxManager.createSystemSession(ALICE);
+        mailboxManager.createMailbox(MailboxPath.inbox(ALICE), 
MailboxManager.CreateOption.NONE, fromSession);
+        mailboxManager.applyRightsCommand(MailboxPath.inbox(ALICE),
+            
MailboxACL.command().forUser(CEDRIC).rights(MailboxACL.FULL_RIGHTS).asAddition(),
+            fromSession);
+
+        Mono.from(testee.changeUsername(ALICE, BOB)).block();
+
+        MailboxSession cedricSession = 
mailboxManager.createSystemSession(CEDRIC);
+        MailboxQuery allMailboxes = 
MailboxQuery.builder().matchesAllMailboxNames().build();
+        // Cedric sees the migrated mailbox
+        assertThat(mailboxManager.search(allMailboxes, Minimal, cedricSession)
+            .map(MailboxMetaData::getPath)
+            .collectList()
+            .block())
+            .containsOnly(MailboxPath.inbox(BOB));
+        // Cedric can access the migrated mailbox
+        assertThatCode(() -> mailboxManager.getMailbox(MailboxPath.inbox(BOB), 
cedricSession))
+            .doesNotThrowAnyException();
+    }
+
+    @Test
+    void shouldSubscriptionsForDelegatedUsers() throws Exception {
+        MailboxSession fromSession = mailboxManager.createSystemSession(ALICE);
+        mailboxManager.createMailbox(MailboxPath.inbox(ALICE), 
MailboxManager.CreateOption.NONE, fromSession);
+        mailboxManager.applyRightsCommand(MailboxPath.inbox(ALICE),
+            
MailboxACL.command().forUser(CEDRIC).rights(MailboxACL.FULL_RIGHTS).asAddition(),
+            fromSession);
+
+        MailboxSession cedricSession = 
mailboxManager.createSystemSession(CEDRIC);
+        subscriptionManager.subscribe(cedricSession, MailboxPath.inbox(ALICE));
+
+        Mono.from(testee.changeUsername(ALICE, BOB)).block();
+
+
+        assertThat(subscriptionManager.subscriptions(cedricSession))
+            .containsOnly(MailboxPath.inbox(BOB));
+    }
+
+    @Test
+    void shouldMigrateSubMailboxes() throws Exception {
+        MailboxSession fromSession = mailboxManager.createSystemSession(ALICE);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "test"), 
MailboxManager.CreateOption.NONE, fromSession);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "test.child"), 
MailboxManager.CreateOption.NONE, fromSession);
+
+        Mono.from(testee.changeUsername(ALICE, BOB)).block();
+
+        
assertThat(mailboxManager.list(mailboxManager.createSystemSession(BOB)))
+            .containsOnly(MailboxPath.forUser(BOB, "test"),
+                MailboxPath.forUser(BOB, "test.child"));
+    }
+
+    @Test
+    void shouldRemoveSubscriptionForOldUser() throws Exception {
+        MailboxSession fromSession = mailboxManager.createSystemSession(ALICE);
+        mailboxManager.createMailbox(MailboxPath.inbox(ALICE), 
MailboxManager.CreateOption.NONE, fromSession);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "subscribed"), 
MailboxManager.CreateOption.CREATE_SUBSCRIPTION, fromSession);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, 
"unsubscribed"), MailboxManager.CreateOption.NONE, fromSession);
+
+        Mono.from(testee.changeUsername(ALICE, BOB)).block();
+
+        assertThat(subscriptionManager.subscriptions(fromSession)).isEmpty();
+    }
+
+    @Test
+    void shouldTransferSubscriptionToNewUser() throws Exception {
+        MailboxSession fromSession = mailboxManager.createSystemSession(ALICE);
+        mailboxManager.createMailbox(MailboxPath.inbox(ALICE), 
MailboxManager.CreateOption.NONE, fromSession);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, "subscribed"), 
MailboxManager.CreateOption.CREATE_SUBSCRIPTION, fromSession);
+        mailboxManager.createMailbox(MailboxPath.forUser(ALICE, 
"unsubscribed"), MailboxManager.CreateOption.NONE, fromSession);
+
+        Mono.from(testee.changeUsername(ALICE, BOB)).block();
+
+        
assertThat(subscriptionManager.subscriptions(mailboxManager.createSystemSession(BOB)))
+            .containsOnly(MailboxPath.forUser(BOB, "subscribed"));
+    }
+}
\ No newline at end of file
diff --git 
a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java
 
b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java
index 3da89cc460..8fb644729a 100644
--- 
a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java
+++ 
b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java
@@ -48,6 +48,10 @@ import org.apache.james.jmap.api.filtering.Rule;
 import org.apache.james.jmap.api.filtering.Rules;
 import org.apache.james.jmap.api.filtering.Version;
 import org.apache.james.jmap.draft.JmapGuiceProbe;
+import org.apache.james.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.util.Port;
@@ -107,7 +111,6 @@ class MemoryUsernameChangeIntegrationTest {
         .build();
 
     private RequestSpecification webAdminApi;
-    private Port jmapPort;
 
     @BeforeEach
     void setUp(GuiceJamesServer jmapServer) throws Exception {
@@ -117,7 +120,7 @@ class MemoryUsernameChangeIntegrationTest {
         dataProbe.addUser(ALICE.asString(), ALICE_PASSWORD);
         dataProbe.addUser(CEDRIC.asString(), CEDRIC_PASSWORD);
 
-        jmapPort = jmapServer.getProbe(JmapGuiceProbe.class).getJmapPort();
+        Port jmapPort = 
jmapServer.getProbe(JmapGuiceProbe.class).getJmapPort();
         RestAssured.requestSpecification = jmapRequestSpecBuilder
             .setPort(jmapPort.getValue())
             .build();
@@ -125,6 +128,47 @@ class MemoryUsernameChangeIntegrationTest {
         webAdminApi = 
WebAdminUtils.spec(jmapServer.getProbe(WebAdminGuiceProbe.class).getWebAdminPort());
     }
 
+    @Test
+    void shouldMigrateACLs(GuiceJamesServer server) throws Exception {
+        
server.getProbe(MailboxProbeImpl.class).createMailbox(MailboxPath.inbox(CEDRIC));
+        
server.getProbe(ACLProbeImpl.class).addRights(MailboxPath.inbox(CEDRIC), 
ALICE.asString(), MailboxACL.FULL_RIGHTS);
+
+        String taskId = webAdminApi
+            .queryParam("action", "rename")
+            .post("/users/" + ALICE.asString() + "/rename/" + BOB.asString())
+            .jsonPath()
+            .get("taskId");
+
+        webAdminApi.get("/tasks/" + taskId + "/await");
+
+        MailboxACL acls = 
server.getProbe(ACLProbeImpl.class).retrieveRights(MailboxPath.inbox(CEDRIC));
+
+        assertThat(acls.getEntries()).hasSize(2)
+            .containsEntry(MailboxACL.EntryKey.createUserEntryKey(BOB), 
MailboxACL.FULL_RIGHTS);
+    }
+
+    @Test
+    void shouldMigrateMailboxes() {
+        webAdminApi.put("/users/" + ALICE.asString() + "/mailboxes/test");
+
+        String taskId = webAdminApi
+            .queryParam("action", "rename")
+            .post("/users/" + ALICE.asString() + "/rename/" + BOB.asString())
+            .jsonPath()
+            .get("taskId");
+
+        webAdminApi.get("/tasks/" + taskId + "/await");
+
+        webAdminApi.get("/users/" + ALICE.asString() + "/mailboxes")
+            .then()
+            .body(".", hasSize(0));
+
+        webAdminApi.get("/users/" + BOB.asString() + "/mailboxes")
+            .then()
+            .body(".", hasSize(1))
+            .body("[0].mailboxName", is("test"));
+    }
+
     @Test
     void shouldAdaptForwards() {
         String taskId = webAdminApi
diff --git a/src/site/markdown/server/manage-webadmin.md 
b/src/site/markdown/server/manage-webadmin.md
index 2338b0e334..9212a810e9 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -487,6 +487,9 @@ Implemented migration steps are:
  - `ForwardUsernameChangeTaskStep`: creates forward from old user to new user 
and migrates existing forwards
  - `FilterUsernameChangeTaskStep`: migrates users filtering rules
  - `DelegationUsernameChangeTaskStep`: migrates delegations where the impacted 
user is either delegatee or delegator
+ - `MailboxUsernameChangeTaskStep`: migrates mailboxes belonging to the old 
user to the account of the new user. It also
+ migrates user's mailbox subscriptions.
+ - `ACLUsernameChangeTaskStep`: migrates ACLs on mailboxes the migrated user 
has access to and updates subscriptions accordingly.
 
 Response codes:
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to