From 479aa7ce41d6c17dd951f7c4b8519829a0a578d8 Mon Sep 17 00:00:00 2001 From: Jan-Eric Hellenberg Date: Wed, 4 Dec 2024 15:32:09 +0100 Subject: [PATCH] Make using full domain path converter configurable --- .../sample-configuration/imap.properties | 2 + .../james/app/PostgresTmailConfiguration.java | 19 +++++ .../tmail/james/app/PostgresTmailServer.java | 11 ++- .../imap/TMailCrossDomainIMAPModule.java | 15 ++++ .../imap/TMailCrossDomainPathConverter.java | 84 +++++++++++++++++++ .../tmail/imap/TMailPathConverter.java | 43 ++-------- 6 files changed, 138 insertions(+), 36 deletions(-) create mode 100644 tmail-backend/apps/postgres/sample-configuration/imap.properties create mode 100644 tmail-backend/imap-extensions/src/main/java/com/linagora/tmail/imap/TMailCrossDomainIMAPModule.java create mode 100644 tmail-backend/imap-extensions/src/main/java/com/linagora/tmail/imap/TMailCrossDomainPathConverter.java diff --git a/tmail-backend/apps/postgres/sample-configuration/imap.properties b/tmail-backend/apps/postgres/sample-configuration/imap.properties new file mode 100644 index 00000000000..5c0ae994765 --- /dev/null +++ b/tmail-backend/apps/postgres/sample-configuration/imap.properties @@ -0,0 +1,2 @@ +# Boolean. Optional, default to false. Whether full domain display is enabled for team mailboxes. +imap.teamMailbox.fullDomain.enabled=false diff --git a/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailConfiguration.java b/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailConfiguration.java index 25dc850e0cb..56870d85fa0 100644 --- a/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailConfiguration.java +++ b/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailConfiguration.java @@ -36,6 +36,7 @@ public record PostgresTmailConfiguration(ConfigurationPath configurationPath, Ja LinagoraServicesDiscoveryModuleChooserConfiguration linagoraServicesDiscoveryModuleChooserConfiguration, boolean jmapEnabled, boolean rlsEnabled, + boolean teamMailboxFullDomainEnabled, PropertiesProvider propertiesProvider, PostgresJamesConfiguration.EventBusImpl eventBusImpl) implements Configuration { public static class Builder { @@ -50,6 +51,7 @@ public static class Builder { private Optional linagoraServicesDiscoveryModuleChooserConfiguration; private Optional jmapEnabled; private Optional rlsEnabled; + private Optional teamMailboxFullDomainEnabled; private Optional eventBusImpl; private Builder() { @@ -64,6 +66,7 @@ private Builder() { linagoraServicesDiscoveryModuleChooserConfiguration = Optional.empty(); jmapEnabled = Optional.empty(); rlsEnabled = Optional.empty(); + teamMailboxFullDomainEnabled = Optional.empty(); eventBusImpl = Optional.empty(); } @@ -140,6 +143,11 @@ public Builder rlsEnabled(Optional rlsEnabled) { return this; } + public Builder teamMailboxFullDomainEnabled(Optional teamMailboxFullDomainEnabled) { + this.teamMailboxFullDomainEnabled = teamMailboxFullDomainEnabled; + return this; + } + public Builder eventBusImpl(PostgresJamesConfiguration.EventBusImpl eventBusImpl) { this.eventBusImpl = Optional.of(eventBusImpl); return this; @@ -190,6 +198,16 @@ public PostgresTmailConfiguration build() { boolean rlsEnabled = this.rlsEnabled.orElse(readRLSEnabledFromFile(propertiesProvider)); + boolean teamMailboxFullDomainEnabled = this.teamMailboxFullDomainEnabled.orElseGet(() -> { + try { + return propertiesProvider.getConfiguration("imap").getBoolean("imap.teamMailbox.fullDomain.enabled"); + } catch (FileNotFoundException e) { + return false; + } catch (ConfigurationException e) { + throw new RuntimeException(e); + } + }); + PostgresJamesConfiguration.EventBusImpl eventBusImpl = this.eventBusImpl.orElseGet(() -> PostgresJamesConfiguration.EventBusImpl.from(propertiesProvider)); return new PostgresTmailConfiguration( @@ -204,6 +222,7 @@ public PostgresTmailConfiguration build() { servicesDiscoveryModuleChooserConfiguration, jmapEnabled, rlsEnabled, + teamMailboxFullDomainEnabled, propertiesProvider, eventBusImpl); } diff --git a/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailServer.java b/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailServer.java index 2ed9ba52825..97aaba71124 100644 --- a/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailServer.java +++ b/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailServer.java @@ -7,6 +7,7 @@ import java.util.Set; import java.util.function.Function; +import com.linagora.tmail.imap.TMailCrossDomainIMAPModule; import org.apache.commons.lang3.NotImplementedException; import org.apache.james.ExtraProperties; import org.apache.james.GuiceJamesServer; @@ -307,7 +308,7 @@ public static GuiceJamesServer createServer(PostgresTmailConfiguration configura new PostgresTicketStoreModule(), new TasksHeathCheckModule(), chooseEventBusModules(configuration), - new TMailIMAPModule()); + chooseIMAPModule(configuration)); private static final Module SCANNING_QUOTA_SEARCH_MODULE = new AbstractModule() { @Override @@ -413,6 +414,14 @@ public static Module chooseJmapEventBusModule(PostgresTmailConfiguration configu return Modules.EMPTY_MODULE; } + public static Module chooseIMAPModule(PostgresTmailConfiguration configuration) { + if (configuration.teamMailboxFullDomainEnabled()) { + return new TMailCrossDomainIMAPModule(); + } else { + return new TMailIMAPModule(); + } + } + private static List chooseFirebase(FirebaseModuleChooserConfiguration moduleChooserConfiguration) { if (moduleChooserConfiguration.enable()) { return List.of(new PostgresFirebaseRepositoryModule(), new FirebaseCommonModule()); diff --git a/tmail-backend/imap-extensions/src/main/java/com/linagora/tmail/imap/TMailCrossDomainIMAPModule.java b/tmail-backend/imap-extensions/src/main/java/com/linagora/tmail/imap/TMailCrossDomainIMAPModule.java new file mode 100644 index 00000000000..9241298e9bc --- /dev/null +++ b/tmail-backend/imap-extensions/src/main/java/com/linagora/tmail/imap/TMailCrossDomainIMAPModule.java @@ -0,0 +1,15 @@ +package com.linagora.tmail.imap; + +import com.google.inject.AbstractModule; +import com.google.inject.Scopes; +import org.apache.james.imap.main.PathConverter; +import org.apache.james.imap.processor.NamespaceSupplier; + +public class TMailCrossDomainIMAPModule extends AbstractModule { + + @Override + protected void configure() { + bind(NamespaceSupplier.class).to(TMailNamespaceSupplier.class).in(Scopes.SINGLETON); + bind(PathConverter.Factory.class).to(TMailCrossDomainPathConverter.Factory.class).in(Scopes.SINGLETON); + } +} diff --git a/tmail-backend/imap-extensions/src/main/java/com/linagora/tmail/imap/TMailCrossDomainPathConverter.java b/tmail-backend/imap-extensions/src/main/java/com/linagora/tmail/imap/TMailCrossDomainPathConverter.java new file mode 100644 index 00000000000..6727ebee95f --- /dev/null +++ b/tmail-backend/imap-extensions/src/main/java/com/linagora/tmail/imap/TMailCrossDomainPathConverter.java @@ -0,0 +1,84 @@ +package com.linagora.tmail.imap; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import com.linagora.tmail.team.TeamMailbox; +import com.linagora.tmail.team.TeamMailboxNameSpace; +import org.apache.commons.lang3.StringUtils; +import org.apache.james.core.Domain; +import org.apache.james.core.Username; +import org.apache.james.imap.api.display.ModifiedUtf7; +import org.apache.james.imap.api.process.ImapSession; +import org.apache.james.imap.main.PathConverter; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.search.MailboxQuery; +import org.apache.james.mailbox.model.search.PrefixedRegex; +import org.apache.james.mailbox.model.search.Wildcard; + +import java.util.List; +import java.util.Optional; + +public class TMailCrossDomainPathConverter extends TMailPathConverter implements PathConverter { + + public static class Factory implements PathConverter.Factory { + public TMailCrossDomainPathConverter forSession(ImapSession session) { + return new TMailCrossDomainPathConverter(session.getMailboxSession()); + } + + public TMailCrossDomainPathConverter forSession(MailboxSession session) { + return new TMailCrossDomainPathConverter(session); + } + } + + private TMailCrossDomainPathConverter(MailboxSession mailboxSession) { + super(mailboxSession); + } + + public Optional mailboxName(boolean relative, MailboxPath path, MailboxSession session) { + // TODO: thunderbird zombie folders :( + // https://bugzilla.mozilla.org/show_bug.cgi?id=544883 + if (path.getNamespace().equalsIgnoreCase(TeamMailboxNameSpace.TEAM_MAILBOX_NAMESPACE())) { + // FIXME: hacky implementation + // Convert local path like MailboxPath(#Teammailbox, team-mailbox@, /) + // to external representation like #TeamMailbox/@/ + List mailboxNameParts = Splitter.on(session.getPathDelimiter()).splitToList(path.getName()); + String rest = Joiner.on(mailboxSession.getPathDelimiter()).join(Iterables.skip(mailboxNameParts, 1)); + if (!rest.isEmpty()) { + rest = mailboxSession.getPathDelimiter() + rest; + } + Optional res = Optional.of(path.getNamespace() + session.getPathDelimiter() + mailboxNameParts.getFirst() + + "@" + path.getUser().getDomainPart().map(Domain::asString).orElse("local").replace(String.valueOf(session.getPathDelimiter()), "__") + + rest); + return res; + } else { + return defaultpathConverter.mailboxName(relative, path, session); + } + } + + protected MailboxPath getTeamMailboxPath(String absolutePath) { + // FIXME: hacky implementation + // Convert absolute path like #TeamMailbox/@/ to local representation + // MailboxPath(#Teammailbox, team-mailbox@, /) + + List mailboxPathParts = Splitter.on(mailboxSession.getPathDelimiter()).splitToList(absolutePath); + String mailboxName = Joiner.on(mailboxSession.getPathDelimiter()).join(Iterables.skip(mailboxPathParts, 1)); + List mailboxNameParts = Splitter.on("@").splitToList(mailboxName); + if (mailboxNameParts.size() < 2) { + return new MailboxPath(TeamMailboxNameSpace.TEAM_MAILBOX_NAMESPACE(), null, mailboxNameParts.getFirst()); + } + + List mailboxNameParts2 = Splitter.on(mailboxSession.getPathDelimiter()).splitToList(mailboxNameParts.get(1)); + String rest = Joiner.on(mailboxSession.getPathDelimiter()).join(Iterables.skip(mailboxNameParts2, 1)); + if (!rest.isEmpty()) { + rest = mailboxSession.getPathDelimiter() + rest; + } + MailboxPath res = new MailboxPath(TeamMailboxNameSpace.TEAM_MAILBOX_NAMESPACE(), teamMailboxUsername(mailboxNameParts2.getFirst().replace("__", String.valueOf(mailboxSession.getPathDelimiter()))), mailboxNameParts.getFirst() + rest); + return res; + } + + protected Username teamMailboxUsername(String domain) { + return Username.from(TeamMailbox.TEAM_MAILBOX_LOCAL_PART(), Optional.of(domain)); + } +} diff --git a/tmail-backend/imap-extensions/src/main/java/com/linagora/tmail/imap/TMailPathConverter.java b/tmail-backend/imap-extensions/src/main/java/com/linagora/tmail/imap/TMailPathConverter.java index 900691f2733..9321c5e3231 100644 --- a/tmail-backend/imap-extensions/src/main/java/com/linagora/tmail/imap/TMailPathConverter.java +++ b/tmail-backend/imap-extensions/src/main/java/com/linagora/tmail/imap/TMailPathConverter.java @@ -33,10 +33,10 @@ public TMailPathConverter forSession(MailboxSession session) { } } - private final MailboxSession mailboxSession; - private final PathConverter defaultpathConverter; + protected final MailboxSession mailboxSession; + protected final PathConverter defaultpathConverter; - private TMailPathConverter(MailboxSession mailboxSession) { + protected TMailPathConverter(MailboxSession mailboxSession) { this.mailboxSession = mailboxSession; this.defaultpathConverter = PathConverter.Factory.DEFAULT.forSession(mailboxSession); } @@ -50,20 +50,8 @@ public MailboxPath buildFullPath(String mailboxName) { } public Optional mailboxName(boolean relative, MailboxPath path, MailboxSession session) { - if (path.getNamespace().equalsIgnoreCase(TeamMailboxNameSpace.TEAM_MAILBOX_NAMESPACE())) { - // FIXME: hacky implementation - // Convert local path like MailboxPath(#Teammailbox, team-mailbox@, /) - // to external representation like #TeamMailbox/@/ - List mailboxNameParts = Splitter.on(session.getPathDelimiter()).splitToList(path.getName()); - String rest = Joiner.on(mailboxSession.getPathDelimiter()).join(Iterables.skip(mailboxNameParts, 1)); - if (!rest.isEmpty()) { - rest = mailboxSession.getPathDelimiter() + rest; - } - Optional res = Optional.of(path.getNamespace() + session.getPathDelimiter() + mailboxNameParts.getFirst() - + "@" + path.getUser().getDomainPart().map(Domain::asString).orElse("local").replace(String.valueOf(session.getPathDelimiter()), "__") - + rest); - return res; + return Optional.of(path.getNamespace() + session.getPathDelimiter() + path.getName()); } else { return defaultpathConverter.mailboxName(relative, path, session); } @@ -104,28 +92,13 @@ public MailboxQuery mailboxQuery(String finalReferencename, String mailboxName, return defaultpathConverter.mailboxQuery(finalReferencename, mailboxName, session); } - private MailboxPath getTeamMailboxPath(String absolutePath) { - // FIXME: hacky implementation - // Convert absolute path like #TeamMailbox/@/ to local representation - // MailboxPath(#Teammailbox, team-mailbox@, /) - + protected MailboxPath getTeamMailboxPath(String absolutePath) { List mailboxPathParts = Splitter.on(mailboxSession.getPathDelimiter()).splitToList(absolutePath); String mailboxName = Joiner.on(mailboxSession.getPathDelimiter()).join(Iterables.skip(mailboxPathParts, 1)); - List mailboxNameParts = Splitter.on("@").splitToList(mailboxName); - if (mailboxNameParts.size() < 2) { - return new MailboxPath(TeamMailboxNameSpace.TEAM_MAILBOX_NAMESPACE(), null, mailboxNameParts.getFirst()); - } - - List mailboxNameParts2 = Splitter.on(mailboxSession.getPathDelimiter()).splitToList(mailboxNameParts.get(1)); - String rest = Joiner.on(mailboxSession.getPathDelimiter()).join(Iterables.skip(mailboxNameParts2, 1)); - if (!rest.isEmpty()) { - rest = mailboxSession.getPathDelimiter() + rest; - } - MailboxPath res = new MailboxPath(TeamMailboxNameSpace.TEAM_MAILBOX_NAMESPACE(), teamMailboxUsername(mailboxNameParts2.getFirst().replace("__", String.valueOf(mailboxSession.getPathDelimiter()))), mailboxNameParts.getFirst() + rest); - return res; + return new MailboxPath(TeamMailboxNameSpace.TEAM_MAILBOX_NAMESPACE(), teamMailboxUsername(mailboxSession), mailboxName); } - private Username teamMailboxUsername(String domain) { - return Username.from(TeamMailbox.TEAM_MAILBOX_LOCAL_PART(), Optional.of(domain)); + protected Username teamMailboxUsername(MailboxSession mailboxSession) { + return Username.from(TeamMailbox.TEAM_MAILBOX_LOCAL_PART(), mailboxSession.getUser().getDomainPart().map(Domain::asString)); } }