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

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

commit d31b8bfc0ec68b19b58acd14eb0e9690b8ef80a7
Author: Gautier DI FOLCO <gdifo...@linagora.com>
AuthorDate: Tue Feb 18 11:58:02 2020 +0100

    JAMES-3067 Recursively find allowed From headers
---
 .../data/CassandraRecipientRewriteTableModule.java |   4 +
 .../data/JPARecipientRewriteTableModule.java       |   4 +
 .../james/modules/data/MemoryDataModule.java       |   5 +
 .../META-INF/org/apache/james/spring-server.xml    |   1 +
 .../java/org/apache/james/util/StreamUtils.java    |  68 +++++++++
 .../org/apache/james/util/StreamUtilsTest.java     |  70 +++++++++
 .../james/rrt/api/RecipientRewriteTable.java       |   2 +
 .../rrt/api/ReverseRecipientRewriteTable.java}     |  42 +-----
 .../apache/james/rrt/lib/CanSendFromContract.java  | 147 +++++++++++++++---
 .../lib/ReverseRecipientRewriteTableContract.java  | 167 +++++++++++++++++++++
 .../rrt/lib/AbstractRecipientRewriteTable.java     |   9 ++
 .../org/apache/james/rrt/lib/CanSendFromImpl.java  |  48 ++----
 .../rrt/lib/ReverseRecipientRewriteTableImpl.java  | 105 +++++++++++++
 .../apache/james/rrt/lib/CanSendFromImplTest.java  |   4 +-
 ...a => ReverseRecipientRewriteTableImplTest.java} |  17 ++-
 .../methods/SetMessagesCreationProcessorTest.java  |   5 +-
 .../methods/SetMessagesUpdateProcessorTest.java    |   5 +-
 .../apache/james/smtpserver/SMTPServerTest.java    |   6 +-
 .../james/webadmin/routes/UserRoutesTest.java      |   6 +-
 19 files changed, 609 insertions(+), 106 deletions(-)

diff --git 
a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraRecipientRewriteTableModule.java
 
b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraRecipientRewriteTableModule.java
index 5a1836f..4eaccfc 100644
--- 
a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraRecipientRewriteTableModule.java
+++ 
b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraRecipientRewriteTableModule.java
@@ -21,11 +21,13 @@ package org.apache.james.modules.data;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.cassandra.CassandraMappingsSourcesDAO;
 import org.apache.james.rrt.cassandra.CassandraRRTModule;
 import org.apache.james.rrt.cassandra.CassandraRecipientRewriteTable;
 import org.apache.james.rrt.cassandra.CassandraRecipientRewriteTableDAO;
 import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.server.core.configuration.ConfigurationProvider;
 import org.apache.james.utils.InitializationOperation;
 import org.apache.james.utils.InitilizationOperationBuilder;
@@ -42,6 +44,8 @@ public class CassandraRecipientRewriteTableModule extends 
AbstractModule {
         bind(CassandraRecipientRewriteTableDAO.class).in(Scopes.SINGLETON);
         bind(CassandraMappingsSourcesDAO.class).in(Scopes.SINGLETON);
         
bind(RecipientRewriteTable.class).to(CassandraRecipientRewriteTable.class);
+        bind(ReverseRecipientRewriteTableImpl.class).in(Scopes.SINGLETON);
+        
bind(ReverseRecipientRewriteTable.class).to(ReverseRecipientRewriteTableImpl.class);
         bind(CanSendFromImpl.class).in(Scopes.SINGLETON);
         bind(CanSendFrom.class).to(CanSendFromImpl.class);
         Multibinder<CassandraModule> cassandraDataDefinitions = 
Multibinder.newSetBinder(binder(), CassandraModule.class);
diff --git 
a/server/container/guice/jpa-common-guice/src/main/java/org/apache/james/modules/data/JPARecipientRewriteTableModule.java
 
b/server/container/guice/jpa-common-guice/src/main/java/org/apache/james/modules/data/JPARecipientRewriteTableModule.java
index f22eb63..a608538 100644
--- 
a/server/container/guice/jpa-common-guice/src/main/java/org/apache/james/modules/data/JPARecipientRewriteTableModule.java
+++ 
b/server/container/guice/jpa-common-guice/src/main/java/org/apache/james/modules/data/JPARecipientRewriteTableModule.java
@@ -20,8 +20,10 @@ package org.apache.james.modules.data;
 
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.jpa.JPARecipientRewriteTable;
 import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.server.core.configuration.ConfigurationProvider;
 import org.apache.james.utils.InitializationOperation;
 import org.apache.james.utils.InitilizationOperationBuilder;
@@ -35,6 +37,8 @@ public class JPARecipientRewriteTableModule extends 
AbstractModule {
     public void configure() {
         bind(JPARecipientRewriteTable.class).in(Scopes.SINGLETON);
         bind(RecipientRewriteTable.class).to(JPARecipientRewriteTable.class);
+        bind(ReverseRecipientRewriteTableImpl.class).in(Scopes.SINGLETON);
+        
bind(ReverseRecipientRewriteTable.class).to(ReverseRecipientRewriteTableImpl.class);
         bind(CanSendFromImpl.class).in(Scopes.SINGLETON);
         bind(CanSendFrom.class).to(CanSendFromImpl.class);
     }
diff --git 
a/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataModule.java
 
b/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataModule.java
index d78baca..d33c1ea 100644
--- 
a/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataModule.java
+++ 
b/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataModule.java
@@ -34,7 +34,9 @@ import 
org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
 import org.apache.james.modules.server.MailStoreRepositoryModule;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.server.core.configuration.ConfigurationProvider;
 import org.apache.james.user.api.UsersRepository;
@@ -68,6 +70,9 @@ public class MemoryDataModule extends AbstractModule {
         bind(MemoryRecipientRewriteTable.class).in(Scopes.SINGLETON);
         
bind(RecipientRewriteTable.class).to(MemoryRecipientRewriteTable.class);
 
+        bind(ReverseRecipientRewriteTableImpl.class).in(Scopes.SINGLETON);
+        
bind(ReverseRecipientRewriteTable.class).to(ReverseRecipientRewriteTableImpl.class);
+
         bind(CanSendFromImpl.class).in(Scopes.SINGLETON);
         bind(CanSendFrom.class).to(CanSendFromImpl.class);
 
diff --git 
a/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml
 
b/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml
index 46b1f29..8a37ab5 100644
--- 
a/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml
+++ 
b/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml
@@ -326,5 +326,6 @@
 
     <bean id="jspfLogger" 
class="org.apache.james.smtpserver.fastfail.SPFHandler.SPFLogger"/>
 
+    <bean id="reverserecipientrewritetable" 
class="org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl" />
     <bean id="cansendfrom" class="org.apache.james.rrt.lib.CanSendFromImpl" />
 </beans>
diff --git 
a/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java 
b/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
index ccdffd2..aea59a1 100644
--- a/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
+++ b/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
@@ -21,12 +21,21 @@ package org.apache.james.util;
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 import java.util.Optional;
+import java.util.Spliterator;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import com.github.steveash.guavate.Guavate;
+import com.google.common.base.Preconditions;
 
 public class StreamUtils {
 
+    private static final boolean PARALLEL = true;
+
     @SafeVarargs
     public static <T> Stream<T> ofNullables(T... array) {
         return ofNullable(array);
@@ -54,4 +63,63 @@ public class StreamUtils {
     public static <T> Stream<T> flatten(Stream<T>... streams) {
         return flatten(Arrays.stream(streams));
     }
+
+    public static <T> Stream<T> unfold(T seed, Function<T, Optional<T>> 
generator) {
+        return StreamSupport.stream(new UnfoldSpliterator(seed, generator), 
!PARALLEL);
+    }
+
+    public static <T> Stream<T> iterate(T seed, Long limit, Function<T, 
Stream<T>> generator) {
+        Preconditions.checkArgument(limit >= 0, "StreamUtils.iterate have a 
given limit ok '{}', while it should not be negative", limit);
+        return StreamUtils.unfold(Arrays.asList(seed), 
conservativeGenerator(generator))
+            .limit(limit + 1)
+            .flatMap(List::stream);
+    }
+
+    private static <T> Function<List<T>, Optional<List<T>>> 
conservativeGenerator(Function<T, Stream<T>> generator) {
+        return previous -> {
+            List<T> generated = previous.stream()
+                .flatMap(generator)
+                .collect(Guavate.toImmutableList());
+
+            if (generated.isEmpty()) {
+                return Optional.empty();
+            } else {
+                return Optional.of(generated);
+            }
+        };
+    }
+
+    private static class UnfoldSpliterator<T> implements Spliterator<T> {
+
+        private static final Spliterator<?> NOT_ABLE_TO_SPLIT_SPLITERATOR = 
null;
+        private Optional<T> current;
+        private final Function<T, Optional<T>> generator;
+
+        private UnfoldSpliterator(T seed, Function<T, Optional<T>> generator) {
+            this.current = Optional.of(seed);
+            this.generator = generator;
+        }
+
+        @Override
+        public boolean tryAdvance(Consumer<? super T> action) {
+            current.ifPresent(action);
+            current = current.flatMap(generator);
+            return current.isPresent();
+        }
+
+        @Override
+        public Spliterator<T> trySplit() {
+            return (Spliterator<T>) NOT_ABLE_TO_SPLIT_SPLITERATOR;
+        }
+
+        @Override
+        public long estimateSize() {
+            return Long.MAX_VALUE;
+        }
+
+        @Override
+        public int characteristics() {
+            return Spliterator.IMMUTABLE & Spliterator.NONNULL & 
Spliterator.ORDERED;
+        }
+    }
 }
diff --git 
a/server/container/util/src/test/java/org/apache/james/util/StreamUtilsTest.java
 
b/server/container/util/src/test/java/org/apache/james/util/StreamUtilsTest.java
index e08ebea..67f8527 100644
--- 
a/server/container/util/src/test/java/org/apache/james/util/StreamUtilsTest.java
+++ 
b/server/container/util/src/test/java/org/apache/james/util/StreamUtilsTest.java
@@ -20,8 +20,11 @@
 package org.apache.james.util;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Stream;
 
 import org.junit.jupiter.api.Test;
@@ -112,4 +115,71 @@ class StreamUtilsTest {
             .collect(Guavate.toImmutableList()))
             .containsExactly(1, 2);
     }
+
+    @Test
+    void unfoldShouldGenerateAnFiniteStream() {
+        Stream<Integer> unfolded = StreamUtils.unfold(1, i -> {
+            if (i < 10) {
+                return Optional.of(i + 1);
+            } else {
+                return Optional.empty();
+            }
+        });
+
+        assertThat(unfolded.collect(Guavate.toImmutableList()))
+            .contains(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+    }
+
+    @Test
+    void unfoldShouldGenerateALazyInfiniteStream() {
+        AtomicInteger counter = new AtomicInteger(0);
+        Stream<Integer> unfolded = StreamUtils.unfold(1, i -> {
+            counter.incrementAndGet();
+            return Optional.of(i + 1);
+        });
+
+        assertThat(unfolded.limit(10).collect(Guavate.toImmutableList()))
+            .contains(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+
+        assertThat(counter.get())
+            .isEqualTo(10);
+    }
+
+    @Test
+    void unfoldShouldHaveAtLeastTheSeed() {
+        Stream<Integer> unfolded = StreamUtils.unfold(1, i -> 
Optional.empty());
+
+        assertThat(unfolded.collect(Guavate.toImmutableList()))
+            .contains(1);
+    }
+
+    @Test
+    void iterateWithANegativeLimitShouldThrow() {
+        assertThatCode(() -> StreamUtils.iterate(1, (long) -1, Stream::of))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void iterateWithZeroLimitShouldHaveOnlyTheSeed() {
+        Stream<Integer> generated = StreamUtils.iterate(1, (long) 0, 
Stream::of);
+
+        assertThat(generated.collect(Guavate.toImmutableList()))
+            .containsOnly(1);
+    }
+
+    @Test
+    void iterateWithEmptyGeneratorShouldHaveOnlyTheSeed() {
+        Stream<Integer> generated = StreamUtils.iterate(1, (long) 10, i -> 
Stream.of());
+
+        assertThat(generated.collect(Guavate.toImmutableList()))
+            .containsOnly(1);
+    }
+
+    @Test
+    void iterateWithGeneratorShouldHaveOnlyTheLimitedElements() {
+        Stream<Integer> generated = StreamUtils.iterate(1, (long) 5, i -> 
Stream.of(i + 1));
+
+        assertThat(generated.collect(Guavate.toImmutableList()))
+            .containsOnly(1, 2, 3, 4, 5, 6);
+    }
 }
diff --git 
a/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTable.java
 
b/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTable.java
index e3ea519..ece8f55 100644
--- 
a/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTable.java
+++ 
b/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTable.java
@@ -122,6 +122,8 @@ public interface RecipientRewriteTable {
      */
     Map<MappingSource, Mappings> getAllMappings() throws 
RecipientRewriteTableException;
 
+    int getMappingLimit();
+
     default Stream<MappingSource> listSources(Mapping mapping) throws 
RecipientRewriteTableException {
         
Preconditions.checkArgument(listSourcesSupportedType.contains(mapping.getType()),
             "Not supported mapping of type %s", mapping.getType());
diff --git 
a/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java 
b/server/data/data-api/src/main/java/org/apache/james/rrt/api/ReverseRecipientRewriteTable.java
similarity index 53%
copy from 
server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
copy to 
server/data/data-api/src/main/java/org/apache/james/rrt/api/ReverseRecipientRewriteTable.java
index ccdffd2..3f17aff 100644
--- a/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
+++ 
b/server/data/data-api/src/main/java/org/apache/james/rrt/api/ReverseRecipientRewriteTable.java
@@ -7,7 +7,7 @@
  * "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                 *
+ * 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  *
@@ -15,43 +15,15 @@
  * KIND, either express or implied.  See the License for the    *
  * specific language governing permissions and limitations      *
  * under the License.                                           *
- ****************************************************************/
+ ***************************************************************/
 
-package org.apache.james.util;
+package org.apache.james.rrt.api;
 
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Optional;
-import java.util.function.Function;
 import java.util.stream.Stream;
 
-public class StreamUtils {
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
 
-    @SafeVarargs
-    public static <T> Stream<T> ofNullables(T... array) {
-        return ofNullable(array);
-    }
-
-    public static <T> Stream<T> ofNullable(T[] array) {
-        return ofOptional(Optional.ofNullable(array));
-    }
-
-    public static <T> Stream<T> ofOptional(Optional<T[]> array) {
-        return array
-            .map(Arrays::stream)
-            .orElse(Stream.empty());
-    }
-
-    public static <T> Stream<T> flatten(Collection<Stream<T>> streams) {
-        return flatten(streams.stream());
-    }
-
-    public static <T> Stream<T> flatten(Stream<Stream<T>> streams) {
-        return streams.flatMap(Function.identity());
-    }
-
-    @SafeVarargs
-    public static <T> Stream<T> flatten(Stream<T>... streams) {
-        return flatten(Arrays.stream(streams));
-    }
+public interface ReverseRecipientRewriteTable {
+    Stream<MailAddress> listAddresses(Username user) throws 
RecipientRewriteTable.ErrorMappingException, RecipientRewriteTableException;
 }
diff --git 
a/server/data/data-api/src/test/java/org/apache/james/rrt/lib/CanSendFromContract.java
 
b/server/data/data-api/src/test/java/org/apache/james/rrt/lib/CanSendFromContract.java
index 13df95e..0a0201d 100644
--- 
a/server/data/data-api/src/test/java/org/apache/james/rrt/lib/CanSendFromContract.java
+++ 
b/server/data/data-api/src/test/java/org/apache/james/rrt/lib/CanSendFromContract.java
@@ -20,13 +20,17 @@ package org.apache.james.rrt.lib;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.util.Optional;
+import java.util.stream.IntStream;
+
 import org.apache.james.core.Domain;
 import org.apache.james.core.Username;
 import org.apache.james.rrt.api.CanSendFrom;
 
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
+import com.github.fge.lambdas.Throwing;
+
 public interface CanSendFromContract {
 
     Domain DOMAIN = Domain.of("example.com");
@@ -43,6 +47,28 @@ public interface CanSendFromContract {
 
     void addGroupMapping(String group, Username user) throws Exception;
 
+    @FunctionalInterface
+    interface RequireUserName {
+        void to(Username user) throws Exception;
+    }
+
+    @FunctionalInterface
+    interface RequireDomain {
+        void to(Domain domain) throws Exception;
+    }
+
+    default RequireUserName redirectUser(Username alias) {
+        return user -> addAliasMapping(alias, user);
+    }
+
+    default RequireDomain redirectDomain(Domain alias) {
+        return domain -> addDomainMapping(alias, domain);
+    }
+
+    default RequireUserName redirectGroup(String group) {
+        return user -> addGroupMapping(group, user);
+    }
+
     @Test
     default void userCanSendFromShouldBeFalseWhenSenderIsNotTheUser() {
         assertThat(canSendFrom().userCanSendFrom(USER, OTHER_USER)).isFalse();
@@ -55,14 +81,14 @@ public interface CanSendFromContract {
 
     @Test
     default void 
userCanSendFromShouldBeFalseWhenSenderIsAnAliasOfAnotherUser() throws Exception 
{
-        addAliasMapping(USER_ALIAS, OTHER_USER);
+        redirectUser(USER_ALIAS).to(OTHER_USER);
 
         assertThat(canSendFrom().userCanSendFrom(USER, USER_ALIAS)).isFalse();
     }
 
     @Test
     default void userCanSendFromShouldBeTrueWhenSenderIsAnAliasOfTheUser() 
throws Exception {
-        addAliasMapping(USER_ALIAS, USER);
+        redirectUser(USER_ALIAS).to(USER);
 
         assertThat(canSendFrom().userCanSendFrom(USER, USER_ALIAS)).isTrue();
     }
@@ -70,17 +96,47 @@ public interface CanSendFromContract {
     @Test
     default void 
userCanSendFromShouldBeTrueWhenSenderIsAnAliasOfAnAliasOfTheUser() throws 
Exception {
         Username userAliasBis = Username.of("aliasbis@" + DOMAIN.asString());
-        addAliasMapping(userAliasBis, USER_ALIAS);
-        addAliasMapping(USER_ALIAS, USER);
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(canSendFrom().userCanSendFrom(USER, userAliasBis)).isTrue();
+    }
+
+    @Test
+    default void 
userCanSendFromShouldBeFalseWhenSenderIsAnAliasOfAnAliasOfAnAliasOfTheUser() 
throws Exception {
+        Username userAliasBis = Username.of("aliasbis@" + DOMAIN.asString());
+        Username userAliasTer = Username.of("aliaster@" + DOMAIN.asString());
+        redirectUser(userAliasTer).to(userAliasBis);
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(canSendFrom().userCanSendFrom(USER, userAliasTer)).isTrue();
+    }
+
+    @Test
+    default void 
userCanSendFromShouldBeTrueWhenSenderIsAnAliasOfAnAliasInAnotherDomainOfTheUser()
 throws Exception {
+        Username userAlias = Username.of("aliasbis@" + 
OTHER_DOMAIN.asString());
+        Username userAliasBis = Username.of("aliaster@" + 
OTHER_DOMAIN.asString());
+        redirectUser(userAliasBis).to(userAlias);
+        redirectUser(userAlias).to(USER);
+
+        assertThat(canSendFrom().userCanSendFrom(USER, userAliasBis)).isTrue();
+    }
+
+    @Test
+    default void 
userCanSendFromShouldBeTrueWhenSenderIsAnAliasInAnotherDomainOfAnAliasOfTheUser()
 throws Exception {
+        Username userAliasBis = Username.of("aliasbis@" + 
OTHER_DOMAIN.asString());
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
 
         assertThat(canSendFrom().userCanSendFrom(USER, userAliasBis)).isTrue();
     }
 
     @Test
     default void 
userCanSendFromShouldBeTrueWhenSenderIsAnAliasOfTheDomainUser() throws 
Exception {
-        Username fromUser = Username.of(USER.getLocalPart() + "@" + 
OTHER_DOMAIN.asString());
+        Username fromUser = USER.withOtherDomain(Optional.of(OTHER_DOMAIN));
 
-        addDomainMapping(OTHER_DOMAIN, DOMAIN);
+        redirectDomain(OTHER_DOMAIN).to(DOMAIN);
 
         assertThat(canSendFrom().userCanSendFrom(USER, fromUser)).isTrue();
     }
@@ -89,7 +145,7 @@ public interface CanSendFromContract {
     default void 
userCanSendFromShouldBeFalseWhenWhenSenderIsAnAliasOfTheUserFromAGroupAlias() 
throws Exception {
         Username fromGroup = Username.of("gr...@example.com");
 
-        addGroupMapping("gr...@example.com", USER);
+        redirectGroup("gr...@example.com").to(USER);
 
         assertThat(canSendFrom().userCanSendFrom(USER, fromGroup)).isFalse();
 
@@ -103,25 +159,24 @@ public interface CanSendFromContract {
 
     @Test
     default void 
allValidFromAddressesShouldContainOnlyUserAddressWhenUserHasNoAliasAndAnotherUserHasOne()
 throws Exception {
-        addAliasMapping(USER_ALIAS, OTHER_USER);
+        redirectUser(USER_ALIAS).to(OTHER_USER);
         assertThat(canSendFrom().allValidFromAddressesForUser(USER))
             .containsExactly(USER.asMailAddress());
     }
 
     @Test
     default void 
allValidFromAddressesShouldContainUserAddressAndAnAliasOfTheUser() throws 
Exception {
-        addAliasMapping(USER_ALIAS, USER);
+        redirectUser(USER_ALIAS).to(USER);
 
         assertThat(canSendFrom().allValidFromAddressesForUser(USER))
             .containsExactlyInAnyOrder(USER.asMailAddress(), 
USER_ALIAS.asMailAddress());
     }
 
     @Test
-    @Disabled("Recursive aliases are not supported yet")
     default void 
allValidFromAddressesFromShouldBeTrueWhenSenderIsAnAliasOfAnAliasOfTheUser() 
throws Exception {
         Username userAliasBis = Username.of("aliasbis@" + DOMAIN.asString());
-        addAliasMapping(userAliasBis, USER_ALIAS);
-        addAliasMapping(USER_ALIAS, USER);
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
 
         assertThat(canSendFrom().allValidFromAddressesForUser(USER))
             .containsExactlyInAnyOrder(USER.asMailAddress(), 
USER_ALIAS.asMailAddress(), userAliasBis.asMailAddress());
@@ -129,9 +184,9 @@ public interface CanSendFromContract {
 
     @Test
     default void 
allValidFromAddressesShouldContainUserAddressAndAnAliasOfTheDomainUser() throws 
Exception {
-        Username fromUser = Username.of(USER.getLocalPart() + "@" + 
OTHER_DOMAIN.asString());
+        Username fromUser = USER.withOtherDomain(Optional.of(OTHER_DOMAIN));
 
-        addDomainMapping(OTHER_DOMAIN, DOMAIN);
+        redirectDomain(OTHER_DOMAIN).to(DOMAIN);
 
         assertThat(canSendFrom().allValidFromAddressesForUser(USER))
             .containsExactlyInAnyOrder(USER.asMailAddress(), 
fromUser.asMailAddress());
@@ -139,14 +194,66 @@ public interface CanSendFromContract {
 
     @Test
     default void 
allValidFromAddressesShouldContainUserAddressAndAnAliasOfTheDomainUserFromAnotherDomain()
 throws Exception {
-        Username userAliasOtherDomain = Username.of(USER_ALIAS.getLocalPart() 
+ "@" + OTHER_DOMAIN.asString());
+        Username userAliasOtherDomain = 
USER_ALIAS.withOtherDomain(Optional.of(OTHER_DOMAIN));
 
-        addDomainMapping(OTHER_DOMAIN, DOMAIN);
-        addAliasMapping(userAliasOtherDomain, USER);
+        redirectDomain(OTHER_DOMAIN).to(DOMAIN);
+        redirectUser(userAliasOtherDomain).to(USER);
 
-        Username userAliasMainDomain = Username.of(USER_ALIAS.getLocalPart() + 
"@" + DOMAIN.asString());
-        Username userOtherDomain = Username.of(USER.getLocalPart() + "@" + 
OTHER_DOMAIN.asString());
+        Username userAliasMainDomain = 
USER_ALIAS.withOtherDomain(Optional.of(DOMAIN));
+        Username userOtherDomain = 
USER.withOtherDomain(Optional.of(OTHER_DOMAIN));
         assertThat(canSendFrom().allValidFromAddressesForUser(USER))
             .containsExactlyInAnyOrder(USER.asMailAddress(), 
userAliasOtherDomain.asMailAddress(), userAliasMainDomain.asMailAddress(), 
userOtherDomain.asMailAddress());
     }
+
+    @Test
+    default void 
allValidFromAddressesShouldContainASendersAliasOfAnAliasOfTheUser() throws 
Exception {
+        Username userAliasBis = Username.of("aliasbis@" + DOMAIN.asString());
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(canSendFrom().userCanSendFrom(USER, userAliasBis)).isTrue();
+    }
+
+    @Test
+    default void 
allValidFromAddressesShouldNotContainAliasesRequiringMoreThanTenRecursionSteps()
 throws Exception {
+        int recursionLevel = 10;
+        IntStream.range(0, recursionLevel)
+            .forEach(Throwing.intConsumer(aliasNumber -> {
+                Username userAliasFrom = Username.of("alias" + aliasNumber + 
"@" + DOMAIN.asString());
+                Username userAliasTo;
+                if (aliasNumber == 0) {
+                    userAliasTo = USER_ALIAS;
+                } else {
+                    userAliasTo = Username.of("alias" + (aliasNumber - 1) + 
"@" + DOMAIN.asString());
+                }
+                redirectUser(userAliasFrom).to(userAliasTo);
+            }).sneakyThrow());
+
+        Username userAliasExcluded = Username.of("alias" + (recursionLevel - 
1) + "@" + DOMAIN.asString());
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(canSendFrom().allValidFromAddressesForUser(USER))
+            .doesNotContain(userAliasExcluded.asMailAddress());
+    }
+
+    @Test
+    default void 
allValidFromAddressesShouldContainASendersAliasOfAnAliasInAnotherDomainOfTheUser()
 throws Exception {
+        Username userAlias = Username.of("aliasbis@" + 
OTHER_DOMAIN.asString());
+        Username userAliasBis = Username.of("aliaster@" + 
OTHER_DOMAIN.asString());
+        redirectUser(userAliasBis).to(userAlias);
+        redirectUser(userAlias).to(USER);
+
+        assertThat(canSendFrom().allValidFromAddressesForUser(USER))
+            .contains(userAliasBis.asMailAddress());
+    }
+
+    @Test
+    default void 
allValidFromAddressesShouldContainAnUserAliasFollowingADomainAliasResolution() 
throws Exception {
+        Username userAliasBis = Username.of("aliasbis@" + 
OTHER_DOMAIN.asString());
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(canSendFrom().allValidFromAddressesForUser(USER))
+            .contains(userAliasBis.asMailAddress());
+    }
 }
diff --git 
a/server/data/data-api/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableContract.java
 
b/server/data/data-api/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableContract.java
new file mode 100644
index 0000000..edf4372
--- /dev/null
+++ 
b/server/data/data-api/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableContract.java
@@ -0,0 +1,167 @@
+/** *************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ * http://www.apache.org/licenses/LICENSE-2.0                   *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ***************************************************************/
+
+package org.apache.james.rrt.lib;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Optional;
+import java.util.stream.IntStream;
+
+import org.apache.james.core.Domain;
+import org.apache.james.core.Username;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
+import org.junit.jupiter.api.Test;
+
+import com.github.fge.lambdas.Throwing;
+
+public interface ReverseRecipientRewriteTableContract {
+
+    Domain DOMAIN = Domain.of("example.com");
+    Domain OTHER_DOMAIN = Domain.of("other.org");
+    Username USER = Username.of("u...@example.com");
+    Username USER_ALIAS = Username.of("al...@example.com");
+    Username OTHER_USER = Username.of("ot...@example.com");
+
+    ReverseRecipientRewriteTable reverseRecipientRewriteTable();
+
+    void addAliasMapping(Username alias, Username user) throws Exception;
+
+    void addDomainMapping(Domain alias, Domain domain) throws Exception;
+
+    void addGroupMapping(String group, Username user) throws Exception;
+
+    @FunctionalInterface
+    interface RequireUserName {
+        void to(Username user) throws Exception;
+    }
+
+    @FunctionalInterface
+    interface RequireDomain {
+        void to(Domain domain) throws Exception;
+    }
+
+    default CanSendFromContract.RequireUserName redirectUser(Username alias) {
+        return user -> addAliasMapping(alias, user);
+    }
+
+    default CanSendFromContract.RequireDomain redirectDomain(Domain alias) {
+        return domain -> addDomainMapping(alias, domain);
+    }
+
+    default CanSendFromContract.RequireUserName redirectGroup(String group) {
+        return user -> addGroupMapping(group, user);
+    }
+    @Test
+    default void listAddressesShouldContainOnlyUserAddressWhenUserHasNoAlias() 
throws Exception {
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .containsExactly(USER.asMailAddress());
+    }
+
+    @Test
+    default void 
listAddressesShouldContainOnlyUserAddressWhenUserHasNoAliasAndAnotherUserHasOne()
 throws Exception {
+        redirectUser(USER_ALIAS).to(OTHER_USER);
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .containsExactly(USER.asMailAddress());
+    }
+
+    @Test
+    default void listAddressesShouldContainUserAddressAndAnAliasOfTheUser() 
throws Exception {
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .containsExactlyInAnyOrder(USER.asMailAddress(), 
USER_ALIAS.asMailAddress());
+    }
+
+    @Test
+    default void 
listAddressesFromShouldBeTrueWhenSenderIsAnAliasOfAnAliasOfTheUser() throws 
Exception {
+        Username userAliasBis = Username.of("aliasbis@" + DOMAIN.asString());
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .containsExactlyInAnyOrder(USER.asMailAddress(), 
USER_ALIAS.asMailAddress(), userAliasBis.asMailAddress());
+    }
+
+    @Test
+    default void 
listAddressesShouldContainUserAddressAndAnAliasOfTheDomainUser() throws 
Exception {
+        Username fromUser = USER.withOtherDomain(Optional.of(OTHER_DOMAIN));
+
+        redirectDomain(OTHER_DOMAIN).to(DOMAIN);
+
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .containsExactlyInAnyOrder(USER.asMailAddress(), 
fromUser.asMailAddress());
+    }
+
+    @Test
+    default void 
listAddressesShouldContainUserAddressAndAnAliasOfTheDomainUserFromAnotherDomain()
 throws Exception {
+        Username userAliasOtherDomain = 
USER_ALIAS.withOtherDomain(Optional.of(OTHER_DOMAIN));
+
+        redirectDomain(OTHER_DOMAIN).to(DOMAIN);
+        redirectUser(userAliasOtherDomain).to(USER);
+
+        Username userAliasMainDomain = 
USER_ALIAS.withOtherDomain(Optional.of(DOMAIN));
+        Username userOtherDomain = 
USER.withOtherDomain(Optional.of(OTHER_DOMAIN));
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .containsExactlyInAnyOrder(USER.asMailAddress(), 
userAliasOtherDomain.asMailAddress(), userAliasMainDomain.asMailAddress(), 
userOtherDomain.asMailAddress());
+    }
+
+    @Test
+    default void 
listAddressesShouldNotContainAliasesRequiringMoreThanTenRecursionSteps() throws 
Exception {
+        int recursionLevel = 10;
+        IntStream.range(0, recursionLevel)
+            .forEach(Throwing.intConsumer(aliasNumber -> {
+                Username userAliasFrom = Username.of("alias" + aliasNumber + 
"@" + DOMAIN.asString());
+                Username userAliasTo;
+                if (aliasNumber == 0) {
+                    userAliasTo = USER_ALIAS;
+                } else {
+                    userAliasTo = Username.of("alias" + (aliasNumber - 1) + 
"@" + DOMAIN.asString());
+                }
+                redirectUser(userAliasFrom).to(userAliasTo);
+            }).sneakyThrow());
+
+        Username userAliasExcluded = Username.of("alias" + (recursionLevel - 
1) + "@" + DOMAIN.asString());
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .doesNotContain(userAliasExcluded.asMailAddress());
+    }
+
+    @Test
+    default void 
listAddressesShouldContainASendersAliasOfAnAliasInAnotherDomainOfTheUser() 
throws Exception {
+        Username userAlias = Username.of("aliasbis@" + 
OTHER_DOMAIN.asString());
+        Username userAliasBis = Username.of("aliaster@" + 
OTHER_DOMAIN.asString());
+        redirectUser(userAliasBis).to(userAlias);
+        redirectUser(userAlias).to(USER);
+
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .contains(userAliasBis.asMailAddress());
+    }
+
+    @Test
+    default void 
listAddressesShouldContainAnUserAliasFollowingADomainAliasResolution() throws 
Exception {
+        Username userAliasBis = Username.of("aliasbis@" + 
OTHER_DOMAIN.asString());
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .contains(userAliasBis.asMailAddress());
+    }
+}
diff --git 
a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java
 
b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java
index 260d5f6..cafc427 100644
--- 
a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java
+++ 
b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java
@@ -60,6 +60,15 @@ public abstract class AbstractRecipientRewriteTable 
implements RecipientRewriteT
 
     private DomainList domainList;
 
+    @Override
+    public int getMappingLimit() {
+        if (recursive) {
+            return mappingLimit;
+        } else {
+            return 0;
+        }
+    }
+
     @Inject
     public void setDomainList(DomainList domainList) {
         this.domainList = domainList;
diff --git 
a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/CanSendFromImpl.java
 
b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/CanSendFromImpl.java
index 930cc58..b2bedc8 100644
--- 
a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/CanSendFromImpl.java
+++ 
b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/CanSendFromImpl.java
@@ -23,7 +23,6 @@ import static org.apache.james.rrt.lib.Mapping.Type.Domain;
 
 import java.util.EnumSet;
 import java.util.List;
-import java.util.Optional;
 import java.util.stream.Stream;
 
 import javax.inject.Inject;
@@ -34,19 +33,24 @@ import org.apache.james.core.Username;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.util.OptionalUtils;
 
-import com.github.fge.lambdas.Throwing;
-import com.github.steveash.guavate.Guavate;
-
 public class CanSendFromImpl implements CanSendFrom {
 
+    @FunctionalInterface
+    interface DomainFetcher {
+        List<Domain> fetch(Username user);
+    }
+
     public static final EnumSet<Mapping.Type> ALIAS_TYPES_ACCEPTED_IN_FROM = 
EnumSet.of(Alias, Domain);
     private final RecipientRewriteTable recipientRewriteTable;
+    private final ReverseRecipientRewriteTable reverseRecipientRewriteTable;
 
     @Inject
-    public CanSendFromImpl(RecipientRewriteTable recipientRewriteTable) {
+    public CanSendFromImpl(RecipientRewriteTable recipientRewriteTable, 
ReverseRecipientRewriteTable reverseRecipientRewriteTable) {
         this.recipientRewriteTable = recipientRewriteTable;
+        this.reverseRecipientRewriteTable = reverseRecipientRewriteTable;
     }
 
     @Override
@@ -60,13 +64,7 @@ public class CanSendFromImpl implements CanSendFrom {
 
     @Override
     public Stream<MailAddress> allValidFromAddressesForUser(Username user) 
throws RecipientRewriteTable.ErrorMappingException, 
RecipientRewriteTableException {
-        List<Domain> domains = 
relatedDomains(user).collect(Guavate.toImmutableList());
-
-        return relatedAliases(user)
-            .flatMap(allowedUser -> domains.stream()
-                .map(Optional::of)
-                .map(allowedUser::withOtherDomain)
-                
.map(Throwing.function(Username::asMailAddress).sneakyThrow()));
+        return reverseRecipientRewriteTable.listAddresses(user);
     }
 
     private boolean emailIsAnAliasOfTheConnectedUser(Username connectedUser, 
Username fromUser) throws RecipientRewriteTable.ErrorMappingException, 
RecipientRewriteTableException {
@@ -78,30 +76,4 @@ public class CanSendFromImpl implements CanSendFrom {
             .map(Username::fromMailAddress)
             .anyMatch(alias -> alias.equals(connectedUser));
     }
-
-    private Stream<Username> relatedAliases(Username user) throws 
RecipientRewriteTableException {
-        return Stream.concat(
-            Stream.of(user),
-            recipientRewriteTable
-                .listSources(Mapping.alias(user.asString()))
-                .map(MappingSource::asUsername)
-                .flatMap(OptionalUtils::toStream)
-        );
-    }
-
-    private Stream<Domain> relatedDomains(Username user) {
-        return user.getDomainPart()
-            .map(Throwing.function(this::fetchDomains).sneakyThrow())
-            .orElseGet(Stream::empty);
-    }
-
-    private Stream<Domain> fetchDomains(Domain domain) throws 
RecipientRewriteTableException {
-        return Stream.concat(
-          Stream.of(domain),
-          recipientRewriteTable
-              .listSources(Mapping.domain(domain))
-              .map(MappingSource::asDomain)
-              .flatMap(OptionalUtils::toStream)
-        );
-    }
 }
diff --git 
a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImpl.java
 
b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImpl.java
new file mode 100644
index 0000000..419a69b
--- /dev/null
+++ 
b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImpl.java
@@ -0,0 +1,105 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ * http://www.apache.org/licenses/LICENSE-2.0                   *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ***************************************************************/
+
+package org.apache.james.rrt.lib;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+import org.apache.james.core.Domain;
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableException;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
+import org.apache.james.util.OptionalUtils;
+import org.apache.james.util.StreamUtils;
+
+import com.github.fge.lambdas.Throwing;
+import com.github.steveash.guavate.Guavate;
+
+public class ReverseRecipientRewriteTableImpl implements 
ReverseRecipientRewriteTable {
+    private final RecipientRewriteTable recipientRewriteTable;
+
+    @Inject
+    public ReverseRecipientRewriteTableImpl(RecipientRewriteTable 
recipientRewriteTable) {
+        this.recipientRewriteTable = recipientRewriteTable;
+    }
+
+    @Override
+    public Stream<MailAddress> listAddresses(Username user) throws 
RecipientRewriteTable.ErrorMappingException, RecipientRewriteTableException {
+        CanSendFromImpl.DomainFetcher domains = domainFetcher(user);
+
+        return relatedAliases(user)
+            .flatMap(allowedUser -> domains.fetch(allowedUser)
+                .stream()
+                .map(Optional::of)
+                .map(allowedUser::withOtherDomain)
+                .map(Throwing.function(Username::asMailAddress).sneakyThrow()))
+            .distinct();
+    }
+
+    private Stream<Username> relatedAliases(Username user) {
+        return StreamUtils.iterate(
+            user,
+            (long) recipientRewriteTable.getMappingLimit(),
+            Throwing.<Username, Stream<Username>>function(targetUser ->
+                recipientRewriteTable
+                    .listSources(Mapping.alias(targetUser.asString()))
+                    .map(MappingSource::asUsername)
+                    .flatMap(OptionalUtils::toStream)).sneakyThrow()
+        );
+    }
+
+    private CanSendFromImpl.DomainFetcher domainFetcher(Username user) {
+        HashMap<Domain, List<Domain>> fetchedDomains = new HashMap<>();
+        List<Domain> userDomains = 
relatedDomains(user).collect(Guavate.toImmutableList());
+        user.getDomainPart().ifPresent(domain -> fetchedDomains.put(domain, 
userDomains));
+        Function<Domain, List<Domain>> computeDomain = givenDomain -> 
Stream.concat(userDomains.stream(), 
fetchDomains(givenDomain)).collect(Guavate.toImmutableList());
+        return givenUsername ->
+            givenUsername
+                .getDomainPart()
+                .map(domain -> fetchedDomains.computeIfAbsent(domain, 
computeDomain))
+                .orElseGet(Arrays::asList);
+    }
+
+    private Stream<Domain> relatedDomains(Username user) {
+        return user.getDomainPart()
+            .map(this::fetchDomains)
+            .orElseGet(Stream::empty);
+    }
+
+    private Stream<Domain> fetchDomains(Domain domain) {
+        return StreamUtils.iterate(
+            domain,
+            (long) recipientRewriteTable.getMappingLimit(),
+            Throwing.<Domain, Stream<Domain>>function(targetDomain ->
+                recipientRewriteTable
+                    .listSources(Mapping.domain(targetDomain))
+                    .map(MappingSource::asDomain)
+                    .flatMap(OptionalUtils::toStream)).sneakyThrow()
+        );
+    }
+}
diff --git 
a/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
 
b/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
index a38467a..47863a1 100644
--- 
a/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
+++ 
b/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
@@ -26,6 +26,7 @@ import org.apache.james.dnsservice.api.DNSService;
 import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.domainlist.memory.MemoryDomainList;
 import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.junit.jupiter.api.BeforeEach;
 
@@ -47,7 +48,8 @@ public class CanSendFromImplTest implements 
CanSendFromContract {
         domainList.addDomain(OTHER_DOMAIN);
         recipientRewriteTable.setDomainList(domainList);
 
-        canSendFrom = new CanSendFromImpl(recipientRewriteTable);
+        ReverseRecipientRewriteTable reverseRecipientRewriteTable = new 
ReverseRecipientRewriteTableImpl(recipientRewriteTable);
+        canSendFrom = new CanSendFromImpl(recipientRewriteTable, 
reverseRecipientRewriteTable);
     }
 
     @Override
diff --git 
a/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
 
b/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImplTest.java
similarity index 84%
copy from 
server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
copy to 
server/data/data-memory/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImplTest.java
index a38467a..fe0d17b 100644
--- 
a/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
+++ 
b/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImplTest.java
@@ -7,7 +7,7 @@
  * "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                 *
+ * 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  *
@@ -15,7 +15,8 @@
  * KIND, either express or implied.  See the License for the    *
  * specific language governing permissions and limitations      *
  * under the License.                                           *
- ****************************************************************/
+ ***************************************************************/
+
 package org.apache.james.rrt.lib;
 
 import static org.mockito.Mockito.mock;
@@ -25,14 +26,14 @@ import org.apache.james.core.Username;
 import org.apache.james.dnsservice.api.DNSService;
 import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.domainlist.memory.MemoryDomainList;
-import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.junit.jupiter.api.BeforeEach;
 
-public class CanSendFromImplTest implements CanSendFromContract {
+public class ReverseRecipientRewriteTableImplTest implements 
ReverseRecipientRewriteTableContract {
 
     AbstractRecipientRewriteTable recipientRewriteTable;
-    CanSendFrom canSendFrom;
+    ReverseRecipientRewriteTableImpl reverseRecipientRewriteTable;
 
     @BeforeEach
     void setup() throws Exception {
@@ -47,12 +48,12 @@ public class CanSendFromImplTest implements 
CanSendFromContract {
         domainList.addDomain(OTHER_DOMAIN);
         recipientRewriteTable.setDomainList(domainList);
 
-        canSendFrom = new CanSendFromImpl(recipientRewriteTable);
+        this.reverseRecipientRewriteTable = new 
ReverseRecipientRewriteTableImpl(recipientRewriteTable);
     }
 
     @Override
-    public CanSendFrom canSendFrom() {
-        return canSendFrom;
+    public ReverseRecipientRewriteTable reverseRecipientRewriteTable() {
+        return reverseRecipientRewriteTable;
     }
 
     @Override
diff --git 
a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
 
b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
index ade1101..6740ff1 100644
--- 
a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
+++ 
b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
@@ -74,9 +74,11 @@ import org.apache.james.mailbox.model.TestMessageId;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.lib.Mapping;
 import org.apache.james.rrt.lib.MappingSource;
 import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.util.OptionalUtils;
 import org.apache.james.util.html.HtmlTextExtractor;
@@ -154,7 +156,8 @@ public class SetMessagesCreationProcessorTest {
         domainList.addDomain(Domain.of("example.com"));
         domainList.addDomain(Domain.of("other.org"));
         recipientRewriteTable.setDomainList(domainList);
-        canSendFrom = new CanSendFromImpl(recipientRewriteTable);
+        ReverseRecipientRewriteTable reverseRecipientRewriteTable = new 
ReverseRecipientRewriteTableImpl(recipientRewriteTable);
+        canSendFrom = new CanSendFromImpl(recipientRewriteTable, 
reverseRecipientRewriteTable);
         messageFullViewFactory = new MessageFullViewFactory(blobManager, 
messageContentExtractor, htmlTextExtractor,
             messageIdManager,
             new MemoryMessageFastViewProjection(new RecordingMetricFactory()));
diff --git 
a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
 
b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
index 9b6f88a..4b8f472 100644
--- 
a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
+++ 
b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
@@ -67,8 +67,10 @@ import org.apache.james.mailbox.model.TestMessageId;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.lib.CanSendFromImpl;
 import org.apache.james.rrt.lib.MappingSource;
+import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.util.OptionalUtils;
 import org.apache.james.util.html.HtmlTextExtractor;
@@ -167,7 +169,8 @@ public class SetMessagesUpdateProcessorTest {
         domainList.addDomain(Domain.of("example.com"));
         domainList.addDomain(Domain.of("other.org"));
         recipientRewriteTable.setDomainList(domainList);
-        canSendFrom = new CanSendFromImpl(recipientRewriteTable);
+        ReverseRecipientRewriteTable reverseRecipientRewriteTable = new 
ReverseRecipientRewriteTableImpl(recipientRewriteTable);
+        canSendFrom = new CanSendFromImpl(recipientRewriteTable, 
reverseRecipientRewriteTable);
         mockedMailSpool = mock(MailSpool.class);
         mockedMailboxManager = mock(MailboxManager.class);
         mockedMailboxIdFactory = mock(MailboxId.Factory.class);
diff --git 
a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java
 
b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java
index 9e5278a..c471f72 100644
--- 
a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java
+++ 
b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java
@@ -72,8 +72,10 @@ import 
org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
 import org.apache.james.queue.memory.MemoryMailQueueFactory;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.lib.CanSendFromImpl;
 import org.apache.james.rrt.lib.MappingSource;
+import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.server.core.configuration.Configuration;
 import org.apache.james.server.core.filesystem.FileSystemImpl;
@@ -198,6 +200,7 @@ public class SMTPServerTest {
     protected MemoryMailQueueFactory queueFactory;
     protected MemoryMailQueueFactory.MemoryMailQueue queue;
     protected MemoryRecipientRewriteTable rewriteTable;
+    private ReverseRecipientRewriteTable reverseRewriteTable;
     protected CanSendFrom canSendFrom;
 
     private SMTPServer smtpServer;
@@ -276,7 +279,8 @@ public class SMTPServerTest {
         dnsServer = new AlterableDNSServer();
 
         rewriteTable = new MemoryRecipientRewriteTable();
-        canSendFrom = new CanSendFromImpl(rewriteTable);
+        reverseRewriteTable = new 
ReverseRecipientRewriteTableImpl(rewriteTable);
+        canSendFrom = new CanSendFromImpl(rewriteTable, reverseRewriteTable);
         queueFactory = new MemoryMailQueueFactory(new 
RawMailQueueItemDecoratorFactory());
         queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
 
diff --git 
a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java
 
b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java
index 7936d4f..d45f79e 100644
--- 
a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java
+++ 
b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java
@@ -43,8 +43,10 @@ import org.apache.james.domainlist.api.mock.SimpleDomainList;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.lib.CanSendFromImpl;
 import org.apache.james.rrt.lib.MappingSource;
+import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.user.api.UsersRepository;
 import org.apache.james.user.api.UsersRepositoryException;
@@ -98,6 +100,7 @@ class UserRoutesTest {
         final MemoryUsersRepository usersRepository;
         final SimpleDomainList domainList;
         final MemoryRecipientRewriteTable recipientRewriteTable;
+        final ReverseRecipientRewriteTable reverseRecipientRewriteTable;
         final CanSendFrom canSendFrom;
 
         WebAdminServer webAdminServer;
@@ -107,7 +110,8 @@ class UserRoutesTest {
             this.domainList = domainList;
             this.recipientRewriteTable = new MemoryRecipientRewriteTable();
             this.recipientRewriteTable.setDomainList(domainList);
-            this.canSendFrom = new CanSendFromImpl(recipientRewriteTable);
+            this.reverseRecipientRewriteTable = new 
ReverseRecipientRewriteTableImpl(recipientRewriteTable);
+            this.canSendFrom = new CanSendFromImpl(recipientRewriteTable, 
reverseRecipientRewriteTable);
         }
 
         @Override


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org
For additional commands, e-mail: server-dev-h...@james.apache.org

Reply via email to