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

commit 871d5f233225ce56be6e71bc99f973a9c01598f0
Author: Benoit Tellier <[email protected]>
AuthorDate: Wed Sep 23 13:50:30 2020 +0700

    JAMES-3375 MailboxManager::search should enable finner grained control over 
the search mailboxes
    
    Enable to either:
     - Search personal mailboxes
     - Search all accessible mailboxes
    
     In addition to the existing inMailboxes/notInMailboxes criteria
---
 .../mailbox/model/MultimailboxesSearchQuery.java   |  74 +++-
 .../apache/james/mailbox/MailboxManagerTest.java   |  68 ++++
 .../model/MultimailboxesSearchQueryTest.java       |  13 +-
 .../james/mailbox/store/StoreMailboxManager.java   |  24 +-
 .../contract/EmailQueryMethodContract.scala        | 398 ++++++++++++++++++++-
 .../james/jmap/method/EmailQueryMethod.scala       |  14 +-
 .../james/jmap/utils/search/MailboxFilter.scala    |  13 +-
 7 files changed, 571 insertions(+), 33 deletions(-)

diff --git 
a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MultimailboxesSearchQuery.java
 
b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MultimailboxesSearchQuery.java
index b0b867d..540f05f 100644
--- 
a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MultimailboxesSearchQuery.java
+++ 
b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MultimailboxesSearchQuery.java
@@ -21,6 +21,11 @@ package org.apache.james.mailbox.model;
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.model.search.MailboxQuery;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
@@ -31,18 +36,67 @@ public class MultimailboxesSearchQuery {
     public static Builder from(SearchQuery searchQuery) {
         return new Builder(searchQuery);
     }
+
+    public interface Namespace {
+        boolean keepAccessible(Mailbox mailbox);
+
+        MailboxQuery associatedMailboxSearchQuery();
+    }
+
+    public static class PersonalNamespace implements Namespace {
+        private final MailboxSession session;
+
+        public PersonalNamespace(MailboxSession session) {
+            this.session = session;
+        }
+
+        @Override
+        public boolean keepAccessible(Mailbox mailbox) {
+            return mailbox.generateAssociatedPath().belongsTo(session);
+        }
+
+        @Override
+        public MailboxQuery associatedMailboxSearchQuery() {
+            return MailboxQuery.privateMailboxesBuilder(session)
+                .matchesAllMailboxNames()
+                .build();
+        }
+    }
+
+    public static class AccessibleNamespace implements Namespace {
+        @Override
+        public boolean keepAccessible(Mailbox mailbox) {
+            return true;
+        }
+
+        @Override
+        public MailboxQuery associatedMailboxSearchQuery() {
+            return MailboxQuery.builder().matchesAllMailboxNames().build();
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(AccessibleNamespace.class);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return obj instanceof AccessibleNamespace;
+        }
+    }
     
     public static class Builder {
-        
         private final SearchQuery searchQuery;
         private ImmutableSet.Builder<MailboxId> mailboxIds;
         private ImmutableSet.Builder<MailboxId> notInMailboxIds;
+        private Optional<Namespace> namespace;
 
         private Builder(SearchQuery searchQuery) {
             Preconditions.checkNotNull(searchQuery);
             this.searchQuery = searchQuery;
             this.mailboxIds = ImmutableSet.builder();
             this.notInMailboxIds = ImmutableSet.builder();
+            this.namespace = Optional.empty();
         }
 
         public Builder inMailboxes(Collection<MailboxId> mailboxIds) {
@@ -58,13 +112,21 @@ public class MultimailboxesSearchQuery {
             this.notInMailboxIds.addAll(mailboxIds);
             return this;
         }
+
+        public Builder inNamespace(Namespace namespace) {
+            this.namespace = Optional.of(namespace);
+            return this;
+        }
         
         public Builder notInMailboxes(MailboxId... mailboxIds) {
             return notInMailboxes(Arrays.asList(mailboxIds));
         }
 
         public MultimailboxesSearchQuery build() {
-            return new MultimailboxesSearchQuery(searchQuery, 
mailboxIds.build(), notInMailboxIds.build());
+            return new MultimailboxesSearchQuery(searchQuery,
+                mailboxIds.build(),
+                notInMailboxIds.build(),
+                namespace.orElse(new AccessibleNamespace()));
         }
 
     }
@@ -72,12 +134,14 @@ public class MultimailboxesSearchQuery {
     private final SearchQuery searchQuery;
     private final ImmutableSet<MailboxId> inMailboxes;
     private final ImmutableSet<MailboxId> notInMailboxes;
+    private final Namespace namespace;
 
     @VisibleForTesting
-    MultimailboxesSearchQuery(SearchQuery searchQuery, ImmutableSet<MailboxId> 
inMailboxes, ImmutableSet<MailboxId> notInMailboxes) {
+    MultimailboxesSearchQuery(SearchQuery searchQuery, ImmutableSet<MailboxId> 
inMailboxes, ImmutableSet<MailboxId> notInMailboxes, Namespace namespace) {
         this.searchQuery = searchQuery;
         this.inMailboxes = inMailboxes;
         this.notInMailboxes = notInMailboxes;
+        this.namespace = namespace;
     }
 
     public ImmutableSet<MailboxId> getInMailboxes() {
@@ -91,4 +155,8 @@ public class MultimailboxesSearchQuery {
     public SearchQuery getSearchQuery() {
         return searchQuery;
     }
+
+    public Namespace getNamespace() {
+        return namespace;
+    }
 }
diff --git 
a/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java 
b/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java
index 95a220a..a274329 100644
--- a/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java
+++ b/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java
@@ -76,6 +76,8 @@ import org.apache.james.mailbox.model.MessageId;
 import org.apache.james.mailbox.model.MessageRange;
 import org.apache.james.mailbox.model.MessageResult;
 import org.apache.james.mailbox.model.MultimailboxesSearchQuery;
+import 
org.apache.james.mailbox.model.MultimailboxesSearchQuery.AccessibleNamespace;
+import 
org.apache.james.mailbox.model.MultimailboxesSearchQuery.PersonalNamespace;
 import org.apache.james.mailbox.model.Quota;
 import org.apache.james.mailbox.model.QuotaRoot;
 import org.apache.james.mailbox.model.SearchQuery;
@@ -1256,6 +1258,39 @@ public abstract class MailboxManagerTest<T extends 
MailboxManager> {
         }
 
         @Test
+        void 
searchForMessageShouldReturnMessagesFromMyDelegatedMailboxesWhenAccessibleNamespace()
 throws Exception {
+            assumeTrue(mailboxManager.hasCapability(MailboxCapabilities.ACL));
+
+            session = mailboxManager.createSystemSession(USER_1);
+            MailboxSession sessionFromDelegater = 
mailboxManager.createSystemSession(USER_2);
+            MailboxPath delegatedMailboxPath = MailboxPath.forUser(USER_2, 
"SHARED");
+            MailboxId delegatedMailboxId = 
mailboxManager.createMailbox(delegatedMailboxPath, sessionFromDelegater).get();
+            MessageManager delegatedMessageManager = 
mailboxManager.getMailbox(delegatedMailboxId, sessionFromDelegater);
+
+            MessageId messageId = delegatedMessageManager
+                .appendMessage(AppendCommand.from(message), 
sessionFromDelegater)
+                .getId().getMessageId();
+
+            mailboxManager.setRights(delegatedMailboxPath,
+                MailboxACL.EMPTY.apply(MailboxACL.command()
+                    .forUser(USER_1)
+                    .rights(MailboxACL.Right.Read, MailboxACL.Right.Lookup)
+                    .asAddition()),
+                sessionFromDelegater);
+
+            MultimailboxesSearchQuery multiMailboxesQuery = 
MultimailboxesSearchQuery
+                .from(SearchQuery.matchAll())
+                .inNamespace(new AccessibleNamespace())
+                .build();
+
+            assertThat(
+                Flux.from(mailboxManager.search(multiMailboxesQuery, session, 
DEFAULT_MAXIMUM_LIMIT))
+                    .collectList()
+                    .block())
+                .containsOnly(messageId);
+        }
+
+        @Test
         void 
searchForMessageShouldNotReturnMessagesFromMyDelegatedMailboxesICanNotRead() 
throws Exception {
             assumeTrue(mailboxManager.hasCapability(MailboxCapabilities.ACL));
 
@@ -1370,6 +1405,39 @@ public abstract class MailboxManagerTest<T extends 
MailboxManager> {
         }
 
         @Test
+        void searchShouldRestrictResultsToTheSuppliedUserNamespace() throws 
Exception {
+            assumeTrue(mailboxManager.hasCapability(MailboxCapabilities.ACL));
+
+            session = mailboxManager.createSystemSession(USER_1);
+            MailboxSession sessionFromDelegater = 
mailboxManager.createSystemSession(USER_2);
+            MailboxPath delegatedMailboxPath = MailboxPath.forUser(USER_2, 
"SHARED");
+            MailboxId delegatedMailboxId = 
mailboxManager.createMailbox(delegatedMailboxPath, sessionFromDelegater).get();
+            MessageManager delegatedMessageManager = 
mailboxManager.getMailbox(delegatedMailboxId, sessionFromDelegater);
+
+            MessageId messageId = delegatedMessageManager
+                .appendMessage(AppendCommand.from(message), 
sessionFromDelegater)
+                .getId().getMessageId();
+
+            mailboxManager.setRights(delegatedMailboxPath,
+                MailboxACL.EMPTY.apply(MailboxACL.command()
+                    .forUser(USER_1)
+                    .rights(MailboxACL.Right.Read, MailboxACL.Right.Lookup)
+                    .asAddition()),
+                sessionFromDelegater);
+
+            MultimailboxesSearchQuery multiMailboxesQuery = 
MultimailboxesSearchQuery
+                .from(SearchQuery.matchAll())
+                .inNamespace(new PersonalNamespace(session))
+                .build();
+
+            assertThat(
+                Flux.from(mailboxManager.search(multiMailboxesQuery, session, 
DEFAULT_MAXIMUM_LIMIT))
+                    .collectList()
+                    .block())
+                .isEmpty();
+        }
+
+        @Test
         void searchForMessageShouldOnlySearchInGivenMailbox() throws Exception 
{
             assumeTrue(mailboxManager.hasCapability(MailboxCapabilities.ACL));
 
diff --git 
a/mailbox/api/src/test/java/org/apache/james/mailbox/model/MultimailboxesSearchQueryTest.java
 
b/mailbox/api/src/test/java/org/apache/james/mailbox/model/MultimailboxesSearchQueryTest.java
index 6d50cf0..d932a14 100644
--- 
a/mailbox/api/src/test/java/org/apache/james/mailbox/model/MultimailboxesSearchQueryTest.java
+++ 
b/mailbox/api/src/test/java/org/apache/james/mailbox/model/MultimailboxesSearchQueryTest.java
@@ -21,6 +21,7 @@ package org.apache.james.mailbox.model;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
+import 
org.apache.james.mailbox.model.MultimailboxesSearchQuery.AccessibleNamespace;
 import org.junit.jupiter.api.Test;
 
 import com.google.common.collect.ImmutableSet;
@@ -42,7 +43,7 @@ class MultimailboxesSearchQueryTest {
     void buildShouldBuildWheninMailboxes() {
         ImmutableSet<MailboxId> inMailboxes = ImmutableSet.of();
         ImmutableSet<MailboxId> notInMailboxes = ImmutableSet.of();
-        MultimailboxesSearchQuery expected = new 
MultimailboxesSearchQuery(EMPTY_QUERY, inMailboxes, notInMailboxes);
+        MultimailboxesSearchQuery expected = new 
MultimailboxesSearchQuery(EMPTY_QUERY, inMailboxes, notInMailboxes, new 
AccessibleNamespace());
         MultimailboxesSearchQuery actual = 
MultimailboxesSearchQuery.from(EMPTY_QUERY).build();
 
         assertThat(actual).isEqualToComparingFieldByField(expected);
@@ -52,7 +53,7 @@ class MultimailboxesSearchQueryTest {
     void buildShouldBuildWhenEmptyMailboxes() {
         ImmutableSet<MailboxId> inMailboxes = ImmutableSet.of();
         ImmutableSet<MailboxId> notInMailboxes = ImmutableSet.of();
-        MultimailboxesSearchQuery expected = new 
MultimailboxesSearchQuery(EMPTY_QUERY, inMailboxes, notInMailboxes);
+        MultimailboxesSearchQuery expected = new 
MultimailboxesSearchQuery(EMPTY_QUERY, inMailboxes, notInMailboxes, new 
AccessibleNamespace());
         MultimailboxesSearchQuery actual = 
MultimailboxesSearchQuery.from(EMPTY_QUERY).inMailboxes().build();
 
         assertThat(actual).isEqualToComparingFieldByField(expected);
@@ -62,7 +63,7 @@ class MultimailboxesSearchQueryTest {
     void buildShouldBuildWhenEmptyNotInMailboxes() {
         ImmutableSet<MailboxId> inMailboxes = ImmutableSet.of();
         ImmutableSet<MailboxId> notInMailboxes = ImmutableSet.of();
-        MultimailboxesSearchQuery expected = new 
MultimailboxesSearchQuery(EMPTY_QUERY, inMailboxes, notInMailboxes);
+        MultimailboxesSearchQuery expected = new 
MultimailboxesSearchQuery(EMPTY_QUERY, inMailboxes, notInMailboxes, new 
AccessibleNamespace());
         MultimailboxesSearchQuery actual = 
MultimailboxesSearchQuery.from(EMPTY_QUERY).notInMailboxes().build();
 
         assertThat(actual).isEqualToComparingFieldByField(expected);
@@ -73,7 +74,7 @@ class MultimailboxesSearchQueryTest {
     void buildShouldBuildWhenOneMailbox() {
         ImmutableSet<MailboxId> inMailboxes = ImmutableSet.of(ID_1);
         ImmutableSet<MailboxId> notInMailboxes = ImmutableSet.of();
-        MultimailboxesSearchQuery expected = new 
MultimailboxesSearchQuery(EMPTY_QUERY, inMailboxes, notInMailboxes);
+        MultimailboxesSearchQuery expected = new 
MultimailboxesSearchQuery(EMPTY_QUERY, inMailboxes, notInMailboxes, new 
AccessibleNamespace());
         MultimailboxesSearchQuery actual = 
MultimailboxesSearchQuery.from(EMPTY_QUERY).inMailboxes(ID_1).build();
 
         assertThat(actual).isEqualToComparingFieldByField(expected);
@@ -83,7 +84,7 @@ class MultimailboxesSearchQueryTest {
     void buildShouldBuildWhenOneNotInMailbox() {
         ImmutableSet<MailboxId> inMailboxes = ImmutableSet.of();
         ImmutableSet<MailboxId> notInMailboxes = ImmutableSet.of(ID_1);
-        MultimailboxesSearchQuery expected = new 
MultimailboxesSearchQuery(EMPTY_QUERY, inMailboxes, notInMailboxes);
+        MultimailboxesSearchQuery expected = new 
MultimailboxesSearchQuery(EMPTY_QUERY, inMailboxes, notInMailboxes, new 
AccessibleNamespace());
         MultimailboxesSearchQuery actual = 
MultimailboxesSearchQuery.from(EMPTY_QUERY).notInMailboxes(ID_1).build();
 
         assertThat(actual).isEqualToComparingFieldByField(expected);
@@ -94,7 +95,7 @@ class MultimailboxesSearchQueryTest {
     void buildShouldBuildWhenAllDefined() {
         ImmutableSet<MailboxId> inMailboxes = ImmutableSet.of(ID_1);
         ImmutableSet<MailboxId> notInMailboxes = ImmutableSet.of(ID_2);
-        MultimailboxesSearchQuery expected = new 
MultimailboxesSearchQuery(EMPTY_QUERY, inMailboxes, notInMailboxes);
+        MultimailboxesSearchQuery expected = new 
MultimailboxesSearchQuery(EMPTY_QUERY, inMailboxes, notInMailboxes, new 
AccessibleNamespace());
         MultimailboxesSearchQuery actual = 
MultimailboxesSearchQuery.from(EMPTY_QUERY).inMailboxes(ID_1).notInMailboxes(ID_2).build();
         
         assertThat(actual).isEqualToComparingFieldByField(expected);
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 faf4d50..52e66c9 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
@@ -696,32 +696,32 @@ public class StoreMailboxManager implements 
MailboxManager {
 
     @Override
     public Flux<MessageId> search(MultimailboxesSearchQuery expression, 
MailboxSession session, long limit) throws MailboxException {
-        return getInMailboxes(expression.getInMailboxes(), session)
-            .filter(id -> !expression.getNotInMailboxes().contains(id))
+        return getInMailboxes(expression, session)
+            .filter(id -> 
!expression.getNotInMailboxes().contains(id.getMailboxId()))
+            .filter(mailbox -> 
expression.getNamespace().keepAccessible(mailbox))
+            .map(Mailbox::getMailboxId)
             .collect(Guavate.toImmutableSet())
             .flatMapMany(Throwing.function(ids -> index.search(session, ids, 
expression.getSearchQuery(), limit)));
     }
 
 
-    private Flux<MailboxId> getInMailboxes(ImmutableSet<MailboxId> 
inMailboxes, MailboxSession session) throws MailboxException {
-        if (inMailboxes.isEmpty()) {
-            return getAllReadableMailbox(session);
+    private Flux<Mailbox> getInMailboxes(MultimailboxesSearchQuery expression, 
MailboxSession session) throws MailboxException {
+        if (expression.getInMailboxes().isEmpty()) {
+            return 
searchMailboxes(expression.getNamespace().associatedMailboxSearchQuery(), 
session, Right.Read);
         } else {
-            return filterReadable(inMailboxes, session);
+            return filterReadable(expression.getInMailboxes(), session);
         }
     }
 
-    private Flux<MailboxId> getAllReadableMailbox(MailboxSession session) 
throws MailboxException {
-        return 
searchMailboxes(MailboxQuery.builder().matchesAllMailboxNames().build(), 
session, Right.Read)
-            .map(Mailbox::getMailboxId);
+    private Flux<Mailbox> getAllReadableMailbox(MailboxQuery mailboxQuery, 
MailboxSession session) throws MailboxException {
+        return searchMailboxes(mailboxQuery, session, Right.Read);
     }
 
-    private Flux<MailboxId> filterReadable(ImmutableSet<MailboxId> 
inMailboxes, MailboxSession session) throws MailboxException {
+    private Flux<Mailbox> filterReadable(ImmutableSet<MailboxId> inMailboxes, 
MailboxSession session) throws MailboxException {
         MailboxMapper mailboxMapper = 
mailboxSessionMapperFactory.getMailboxMapper(session);
         return Flux.fromIterable(inMailboxes)
             .concatMap(mailboxMapper::findMailboxById)
-            .filter(Throwing.<Mailbox>predicate(mailbox -> 
storeRightManager.hasRight(mailbox, Right.Read, session)).sneakyThrow())
-            .map(Mailbox::getMailboxId);
+            .filter(Throwing.<Mailbox>predicate(mailbox -> 
storeRightManager.hasRight(mailbox, Right.Read, session)).sneakyThrow());
     }
 
     @Override
diff --git 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
index e521a7f..3b5a1f9 100644
--- 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
@@ -41,11 +41,12 @@ import org.apache.james.jmap.http.UserCredential
 import org.apache.james.jmap.model.UTCDate
 import 
org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, 
ANDRE, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, 
baseRequestSpecBuilder}
 import org.apache.james.mailbox.MessageManager.AppendCommand
+import org.apache.james.mailbox.model.MailboxACL.Right
 import org.apache.james.mailbox.model.MailboxPath.inbox
-import org.apache.james.mailbox.model.{MailboxPath, MessageId}
+import org.apache.james.mailbox.model.{MailboxACL, MailboxPath, MessageId}
 import org.apache.james.mime4j.dom.Message
 import org.apache.james.mime4j.message.DefaultMessageWriter
-import org.apache.james.modules.MailboxProbeImpl
+import org.apache.james.modules.{ACLProbeImpl, MailboxProbeImpl}
 import org.apache.james.utils.DataProbeImpl
 import org.awaitility.Awaitility
 import org.awaitility.Duration.ONE_HUNDRED_MILLISECONDS
@@ -156,6 +157,395 @@ trait EmailQueryMethodContract {
   }
 
   @Test
+  def emailInSharedMailboxesShouldNotBeDisplayedWhenNoExtension(server: 
GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(ANDRE.asString, inbox(ANDRE),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(inbox(ANDRE), BOB.asString, new 
MailboxACL.Rfc4314Rights(Right.Read))
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6"
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState()}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": []
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def emailInSharedMailboxesShouldBeDisplayedWhenExtension(server: 
GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(ANDRE.asString, inbox(ANDRE),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(inbox(ANDRE), BOB.asString, new 
MailboxACL.Rfc4314Rights(Right.Read))
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail",
+         |    "urn:apache:james:params:jmap:mail:shares"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6"
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId1)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId1.serialize()}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def 
inMailboxFilterShouldReturnEmptyForSharedMailboxesWhenNoExtension(server: 
GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(ANDRE.asString, inbox(ANDRE),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(inbox(ANDRE), BOB.asString, new 
MailboxACL.Rfc4314Rights(Right.Read))
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"inMailbox": "${andreInboxId.serialize()}"}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [
+           |        [
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState()}",
+           |                "canCalculateChanges": false,
+           |                "ids": [
+           |
+           |                ],
+           |                "position": 0,
+           |                "limit": 256
+           |            },
+           |            "c1"
+           |        ]
+           |    ]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def inMailboxFilterShouldAcceptSharedMailboxesWhenExtension(server: 
GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(ANDRE.asString, inbox(ANDRE),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(inbox(ANDRE), BOB.asString, new 
MailboxACL.Rfc4314Rights(Right.Read))
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail",
+         |    "urn:apache:james:params:jmap:mail:shares"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"inMailbox": "${andreInboxId.serialize()}"}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId1)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId1.serialize()}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def 
inMailboxOtherThanFilterShouldReturnEmptyForSharedMailboxesWhenNoExtension(server:
 GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(ANDRE.asString, inbox(ANDRE),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(inbox(ANDRE), BOB.asString, new 
MailboxACL.Rfc4314Rights(Right.Read))
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"inMailboxOtherThan": 
["${andreInboxId.serialize()}"]}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState()}",
+           |                "canCalculateChanges": false,
+           |                "ids": [],
+           |                "position": 0,
+           |                "limit": 256
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def inMailboxOtherThanFilterShouldAcceptSharedMailboxesWhenExtension(server: 
GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
+    val bobInboxId = mailboxProbe.createMailbox(inbox(BOB))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(ANDRE.asString, inbox(ANDRE),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+    val messageId2: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(inbox(ANDRE), BOB.asString, new 
MailboxACL.Rfc4314Rights(Right.Read))
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail",
+         |    "urn:apache:james:params:jmap:mail:shares"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"inMailboxOtherThan": 
["${andreInboxId.serialize()}"]}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId2)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId2.serialize()}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
   def hasAttachmentShouldKeepMessageWithoutAttachmentWhenFalse(server: 
GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
     mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
@@ -1342,7 +1732,7 @@ trait EmailQueryMethodContract {
         .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
         .body(request)
       .when
-        .post.prettyPeek()
+        .post
       .`then`
         .statusCode(SC_OK)
         .contentType(JSON)
@@ -1391,7 +1781,7 @@ trait EmailQueryMethodContract {
         .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
         .body(request)
       .when
-        .post.prettyPeek()
+        .post
       .`then`
         .statusCode(SC_OK)
         .contentType(JSON)
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
index 6bee1dc..4feb97e 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
@@ -45,13 +45,13 @@ import scala.jdk.CollectionConverters._
 class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer,
                                   mailboxManager: MailboxManager,
                                   metricFactory: MetricFactory) extends Method 
{
-  override val methodName = MethodName("Email/query")
+  override val methodName: MethodName = MethodName("Email/query")
   override val requiredCapabilities: Capabilities = 
Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY)
 
   override def process(capabilities: Set[CapabilityIdentifier], invocation: 
Invocation, mailboxSession: MailboxSession, processingContext: 
ProcessingContext): Publisher[(Invocation, ProcessingContext)] =
     metricFactory.decoratePublisherWithTimerMetricLogP99(JMAP_RFC8621_PREFIX + 
methodName.value,
       asEmailQueryRequest(invocation.arguments)
-        .flatMap(processRequest(mailboxSession, invocation, _))
+        .flatMap(processRequest(mailboxSession, invocation, _, capabilities))
         .onErrorResume {
           case e: UnsupportedRequestParameterException => 
SMono.just(Invocation.error(
             ErrorCode.InvalidArguments,
@@ -71,10 +71,10 @@ class EmailQueryMethod @Inject() (serializer: 
EmailQuerySerializer,
         }
         .map(invocationResult => (invocationResult, processingContext)))
 
-  private def processRequest(mailboxSession: MailboxSession, invocation: 
Invocation, request: EmailQueryRequest): SMono[Invocation] = {
-    searchQueryFromRequest(request) match {
+  private def processRequest(mailboxSession: MailboxSession, invocation: 
Invocation, request: EmailQueryRequest, capabilities: 
Set[CapabilityIdentifier]): SMono[Invocation] = {
+    searchQueryFromRequest(request, capabilities, mailboxSession) match {
       case Left(error) => SMono.raiseError(error)
-      case Right(searchQuery) => for {
+      case Right(searchQuery) =>  for {
         positionToUse <- Position.validateRequestPosition(request.position)
         limitToUse <- Limit.validateRequestLimit(request.limit)
         response <- executeQuery(mailboxSession, request, searchQuery, 
positionToUse, limitToUse)
@@ -94,7 +94,7 @@ class EmailQueryMethod @Inject() (serializer: 
EmailQuerySerializer,
         limit = Some(limitToUse).filterNot(used => 
request.limit.map(_.value).contains(used.value))))
   }
 
-  private def searchQueryFromRequest(request: EmailQueryRequest): 
Either[UnsupportedOperationException, MultimailboxesSearchQuery] = {
+  private def searchQueryFromRequest(request: EmailQueryRequest, capabilities: 
Set[CapabilityIdentifier], session: MailboxSession): 
Either[UnsupportedOperationException, MultimailboxesSearchQuery] = {
     val comparators: List[Comparator] = 
request.comparator.getOrElse(Set(Comparator.default)).toList
 
     comparators.map(_.toSort)
@@ -104,7 +104,7 @@ class EmailQueryMethod @Inject() (serializer: 
EmailQuerySerializer,
       } yield queryFilter
         .sorts(sorts.asJava)
         .build())
-      .map(MailboxFilter.buildQuery(request, _))
+      .map(MailboxFilter.buildQuery(request, _, capabilities, session))
   }
 
   private def asEmailQueryRequest(arguments: Arguments): 
SMono[EmailQueryRequest] =
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
index 3ef1239..c547cf2 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
@@ -22,6 +22,10 @@ import java.util.Date
 
 import cats.implicits._
 import org.apache.james.jmap.mail.{EmailQueryRequest, 
UnsupportedFilterException}
+import org.apache.james.jmap.model.CapabilityIdentifier
+import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
+import org.apache.james.mailbox.MailboxSession
+import 
org.apache.james.mailbox.model.MultimailboxesSearchQuery.{AccessibleNamespace, 
Namespace, PersonalNamespace}
 import org.apache.james.mailbox.model.SearchQuery.DateResolution.Second
 import org.apache.james.mailbox.model.SearchQuery.{DateComparator, 
DateOperator, DateResolution, InternalDateCriterion}
 import org.apache.james.mailbox.model.{MultimailboxesSearchQuery, SearchQuery}
@@ -47,13 +51,20 @@ case object NotInMailboxFilter extends MailboxFilter {
 }
 
 object MailboxFilter {
-  def buildQuery(request: EmailQueryRequest, searchQuery: SearchQuery) = {
+  def buildQuery(request: EmailQueryRequest, searchQuery: SearchQuery, 
capabilities: Set[CapabilityIdentifier], session: MailboxSession) = {
     val multiMailboxQueryBuilder = MultimailboxesSearchQuery.from(searchQuery)
+        .inNamespace(queryNamespace(capabilities, session))
 
     List(InMailboxFilter, 
NotInMailboxFilter).foldLeft(multiMailboxQueryBuilder)((builder, filter) => 
filter.toQuery(builder, request))
       .build()
   }
 
+  private def queryNamespace(capabilities: Set[CapabilityIdentifier], session: 
MailboxSession): Namespace = if 
(capabilities.contains(CapabilityIdentifier.JAMES_SHARES)) {
+    new AccessibleNamespace()
+  } else {
+    new PersonalNamespace(session)
+  }
+
   sealed trait QueryFilter {
     def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): 
Either[UnsupportedFilterException, SearchQuery.Builder]
   }


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

Reply via email to