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]