You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2022/10/18 03:41:53 UTC

[james-project] branch master updated (7c28d83dd0 -> 80180aa166)

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

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


    from 7c28d83dd0 [JAMES-3805] further improves docker pulsar extension
     new fbd7807e75 JAMES-3830 UserQuotaRootResolver support method list all QuotaRoot of delegated mailboxes
     new 80180aa166 JAMES-3830 Quota extension and shared mailboxes

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../james/mailbox/quota/UserQuotaRootResolver.java |   3 +
 .../store/quota/DefaultUserQuotaRootResolver.java  |  14 +
 .../quota/DefaultUserQuotaRootResolverTest.java    |  24 +-
 .../rfc8621/contract/QuotaGetMethodContract.scala  | 316 ++++++++++++++++++++-
 .../scala/org/apache/james/jmap/mail/Quotas.scala  |  14 +-
 .../apache/james/jmap/method/QuotaGetMethod.scala  |  37 ++-
 6 files changed, 372 insertions(+), 36 deletions(-)


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 01/02: JAMES-3830 UserQuotaRootResolver support method list all QuotaRoot of delegated mailboxes

Posted by rc...@apache.org.
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 fbd7807e75272865ab47bac5b46cd4edf67361ab
Author: Tung Van TRAN <vt...@linagora.com>
AuthorDate: Tue Oct 11 17:35:23 2022 +0700

    JAMES-3830 UserQuotaRootResolver support method list all QuotaRoot of delegated mailboxes
---
 .../james/mailbox/quota/UserQuotaRootResolver.java |  3 +++
 .../store/quota/DefaultUserQuotaRootResolver.java  | 13 ++++++++++++
 .../quota/DefaultUserQuotaRootResolverTest.java    | 24 +++++++++++++++++++---
 3 files changed, 37 insertions(+), 3 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/quota/UserQuotaRootResolver.java b/mailbox/api/src/main/java/org/apache/james/mailbox/quota/UserQuotaRootResolver.java
index e2c1fd8f7c..d3dd310bb0 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/quota/UserQuotaRootResolver.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/quota/UserQuotaRootResolver.java
@@ -20,11 +20,14 @@ package org.apache.james.mailbox.quota;
 
 import org.apache.james.core.Username;
 import org.apache.james.mailbox.model.QuotaRoot;
+import org.reactivestreams.Publisher;
 
 public interface UserQuotaRootResolver extends QuotaRootResolver {
 
     QuotaRoot forUser(Username username);
 
+    Publisher<QuotaRoot> listAllAccessibleQuotaRoots(Username username);
+
     /**
      * When the username might not point to a user entity, and an extra translation step is required
      * for the quota root.
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/DefaultUserQuotaRootResolver.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/DefaultUserQuotaRootResolver.java
index dc5ebf0676..df23ba4bfb 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/DefaultUserQuotaRootResolver.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/DefaultUserQuotaRootResolver.java
@@ -29,6 +29,7 @@ import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.SessionProvider;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.MailboxACL;
 import org.apache.james.mailbox.model.MailboxConstants;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxPath;
@@ -127,6 +128,18 @@ public class DefaultUserQuotaRootResolver implements UserQuotaRootResolver {
         return Mono.just(getQuotaRoot(mailboxPath));
     }
 
+    @Override
+    public Publisher<QuotaRoot> listAllAccessibleQuotaRoots(Username username) {
+        MailboxSession session = sessionProvider.createSystemSession(username);
+
+        Flux<QuotaRoot> quotaRootListFromDelegatedMailboxes = factory.getMailboxMapper(session)
+            .findNonPersonalMailboxes(username, MailboxACL.Right.Read)
+            .flatMap(this::getQuotaRootReactive)
+            .distinct();
+
+        return Flux.concat(quotaRootListFromDelegatedMailboxes, Flux.just(forUser(username)));
+    }
+
     @Override
     public Publisher<QuotaRoot> getQuotaRootReactive(Mailbox mailbox) {
         return Mono.just(getQuotaRoot(mailbox));
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/DefaultUserQuotaRootResolverTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/DefaultUserQuotaRootResolverTest.java
index 596a86ff01..718c83a782 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/DefaultUserQuotaRootResolverTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/DefaultUserQuotaRootResolverTest.java
@@ -59,10 +59,13 @@ class DefaultUserQuotaRootResolverTest {
     DefaultUserQuotaRootResolver testee;
     MailboxSessionMapperFactory mockedFactory;
 
+    SessionProvider mockSessionProvider;
+
     @BeforeEach
     void setUp() {
         mockedFactory = mock(MailboxSessionMapperFactory.class);
-        testee = new DefaultUserQuotaRootResolver(mock(SessionProvider.class), mockedFactory);
+        mockSessionProvider = mock(SessionProvider.class);
+        testee = new DefaultUserQuotaRootResolver(mockSessionProvider, mockedFactory);
     }
 
     @Test
@@ -101,8 +104,8 @@ class DefaultUserQuotaRootResolverTest {
     @Test
     void retrieveAssociatedMailboxesShouldThrowWhenQuotaRootContainsSeparator2Times() {
         assertThatThrownBy(() -> testee.retrieveAssociatedMailboxes(
-                    QuotaRoot.quotaRoot("#private&be&nwa", Optional.empty()), MAILBOX_SESSION)
-                .collectList().block())
+                QuotaRoot.quotaRoot("#private&be&nwa", Optional.empty()), MAILBOX_SESSION)
+            .collectList().block())
             .hasCauseInstanceOf(MailboxException.class);
     }
 
@@ -115,4 +118,19 @@ class DefaultUserQuotaRootResolverTest {
         assertThat(testee.getQuotaRoot(MAILBOX_ID)).isEqualTo(QUOTA_ROOT);
     }
 
+    @Test
+    void listAllAccessibleQuotaRootsShouldReturnQuotaRootOfDelegatedMailboxes() {
+        MailboxMapper mockedMapper = mock(MailboxMapper.class);
+        MailboxSession mockSession = mock(MailboxSession.class);
+        when(mockSessionProvider.createSystemSession(BENWA)).thenReturn(mockSession);
+        when(mockedFactory.getMailboxMapper(mockSession)).thenReturn(mockedMapper);
+
+        Mailbox delegatedMailbox = new Mailbox(MailboxPath.forUser(Username.of("delegated"), "test"),
+            UidValidity.of(11), TestId.of(1));
+        when(mockedMapper.findNonPersonalMailboxes(any(), any())).thenReturn(Flux.just(delegatedMailbox));
+
+        assertThat(Flux.from(testee.listAllAccessibleQuotaRoots(BENWA)).collectList().block())
+            .containsExactlyInAnyOrder(QUOTA_ROOT, testee.getQuotaRoot(delegatedMailbox));
+    }
+
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 02/02: JAMES-3830 Quota extension and shared mailboxes

Posted by rc...@apache.org.
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 80180aa166b93d7df3ec2ec868a617c79b5f8c66
Author: Tung Van TRAN <vt...@linagora.com>
AuthorDate: Thu Oct 13 08:23:36 2022 +0700

    JAMES-3830 Quota extension and shared mailboxes
---
 .../store/quota/DefaultUserQuotaRootResolver.java  |   3 +-
 .../rfc8621/contract/QuotaGetMethodContract.scala  | 316 ++++++++++++++++++++-
 .../scala/org/apache/james/jmap/mail/Quotas.scala  |  14 +-
 .../apache/james/jmap/method/QuotaGetMethod.scala  |  37 ++-
 4 files changed, 336 insertions(+), 34 deletions(-)

diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/DefaultUserQuotaRootResolver.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/DefaultUserQuotaRootResolver.java
index df23ba4bfb..00506ca094 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/DefaultUserQuotaRootResolver.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/DefaultUserQuotaRootResolver.java
@@ -38,6 +38,7 @@ import org.apache.james.mailbox.model.search.MailboxQuery;
 import org.apache.james.mailbox.quota.QuotaRootDeserializer;
 import org.apache.james.mailbox.quota.UserQuotaRootResolver;
 import org.apache.james.mailbox.store.MailboxSessionMapperFactory;
+import org.apache.james.util.ReactorUtils;
 import org.reactivestreams.Publisher;
 
 import com.google.common.base.Preconditions;
@@ -134,7 +135,7 @@ public class DefaultUserQuotaRootResolver implements UserQuotaRootResolver {
 
         Flux<QuotaRoot> quotaRootListFromDelegatedMailboxes = factory.getMailboxMapper(session)
             .findNonPersonalMailboxes(username, MailboxACL.Right.Read)
-            .flatMap(this::getQuotaRootReactive)
+            .flatMap(this::getQuotaRootReactive, ReactorUtils.DEFAULT_CONCURRENCY)
             .distinct();
 
         return Flux.concat(quotaRootListFromDelegatedMailboxes, Flux.just(forUser(username)));
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/QuotaGetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/QuotaGetMethodContract.scala
index ed3f742667..f99b6d6b17 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/QuotaGetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/QuotaGetMethodContract.scala
@@ -32,14 +32,15 @@ import org.apache.james.jmap.http.UserCredential
 import org.apache.james.jmap.mail.{CountResourceType, QuotaIdFactory}
 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.MailboxPath
+import org.apache.james.mailbox.model.{MailboxACL, MailboxPath}
 import org.apache.james.mime4j.dom.Message
-import org.apache.james.modules.{MailboxProbeImpl, QuotaProbesImpl}
+import org.apache.james.modules.{ACLProbeImpl, MailboxProbeImpl, QuotaProbesImpl}
 import org.apache.james.utils.DataProbeImpl
 import org.junit.jupiter.api.{BeforeEach, Test}
 
 import java.nio.charset.StandardCharsets
-
+import org.apache.james.mailbox.model.MailboxACL.Right.Read
+import org.apache.james.mailbox.model.MailboxACL.Right.Lookup
 
 trait QuotaGetMethodContract {
 
@@ -140,7 +141,7 @@ trait QuotaGetMethodContract {
          |                "list": [
          |                    {
          |                        "used": 0,
-         |                        "name": "account:count:Mail",
+         |                        "name": "#private&bob@domain.tld@domain.tld:account:count:Mail",
          |                        "id": "08417be420b6dd6fa77d48fb2438e0d19108cd29424844bb109b52d356fab528",
          |                        "dataTypes": [
          |                            "Mail"
@@ -152,7 +153,7 @@ trait QuotaGetMethodContract {
          |                    },
          |                    {
          |                        "used": 0,
-         |                        "name": "account:octets:Mail",
+         |                        "name": "#private&bob@domain.tld@domain.tld:account:octets:Mail",
          |                        "id": "eab6ce8ac5d9730a959e614854410cf39df98ff3760a623b8e540f36f5184947",
          |                        "dataTypes": [
          |                            "Mail"
@@ -312,7 +313,7 @@ trait QuotaGetMethodContract {
          |                "list": [
          |                    {
          |                        "used": 0,
-         |                        "name": "account:count:Mail",
+         |                        "name": "#private&bob@domain.tld@domain.tld:account:count:Mail",
          |                        "id": "08417be420b6dd6fa77d48fb2438e0d19108cd29424844bb109b52d356fab528",
          |                        "dataTypes": [
          |                            "Mail"
@@ -383,7 +384,7 @@ trait QuotaGetMethodContract {
          |                "list": [
          |                    {
          |                        "used": 1,
-         |                        "name": "account:count:Mail",
+         |                        "name": "#private&bob@domain.tld@domain.tld:account:count:Mail",
          |                        "id": "08417be420b6dd6fa77d48fb2438e0d19108cd29424844bb109b52d356fab528",
          |                        "dataTypes": [
          |                            "Mail"
@@ -395,7 +396,7 @@ trait QuotaGetMethodContract {
          |                    },
          |                    {
          |                        "used": 85,
-         |                        "name": "account:octets:Mail",
+         |                        "name": "#private&bob@domain.tld@domain.tld:account:octets:Mail",
          |                        "id": "eab6ce8ac5d9730a959e614854410cf39df98ff3760a623b8e540f36f5184947",
          |                        "dataTypes": [
          |                            "Mail"
@@ -714,7 +715,7 @@ trait QuotaGetMethodContract {
          |        {
          |          "id": "08417be420b6dd6fa77d48fb2438e0d19108cd29424844bb109b52d356fab528",
          |          "used": 0,
-         |          "name": "account:count:Mail",
+         |          "name": "#private&bob@domain.tld@domain.tld:account:count:Mail",
          |          "limit": 100
          |        }
          |      ],
@@ -873,7 +874,7 @@ trait QuotaGetMethodContract {
          |                "list": [
          |                    {
          |                        "used": 1,
-         |                        "name": "account:count:Mail",
+         |                        "name": "#private&bob@domain.tld@domain.tld:account:count:Mail",
          |                        "id": "08417be420b6dd6fa77d48fb2438e0d19108cd29424844bb109b52d356fab528",
          |                        "dataTypes": [
          |                            "Mail"
@@ -885,7 +886,7 @@ trait QuotaGetMethodContract {
          |                    },
          |                    {
          |                        "used": 85,
-         |                        "name": "account:octets:Mail",
+         |                        "name": "#private&bob@domain.tld@domain.tld:account:octets:Mail",
          |                        "id": "eab6ce8ac5d9730a959e614854410cf39df98ff3760a623b8e540f36f5184947",
          |                        "dataTypes": [
          |                            "Mail"
@@ -903,4 +904,297 @@ trait QuotaGetMethodContract {
          |}
          |""".stripMargin)
   }
+
+  @Test
+  def quotaGetShouldNotReturnQuotaRootOfDelegatedMailboxWhenNotExtension(server: GuiceJamesServer): Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val bobQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB))
+    quotaProbe.setMaxMessageCount(bobQuotaRoot, QuotaCountLimit.count(100L))
+
+    // setup delegated Mailbox
+    val andreMailbox = MailboxPath.forUser(ANDRE, "mailbox")
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andreMailbox)
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(andreMailbox, BOB.asString, new MailboxACL.Rfc4314Rights(Read))
+
+    quotaProbe.setMaxMessageCount(quotaProbe.getQuotaRoot(andreMailbox), QuotaCountLimit.count(88L))
+
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota"],
+           |  "methodCalls": [[
+           |    "Quota/get",
+           |    {
+           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |      "ids": null
+           |    },
+           |    "c1"]]
+           |}""".stripMargin)
+  .when
+      .post
+  .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |    "sessionState": "${SESSION_STATE.value}",
+         |    "methodResponses": [
+         |        [
+         |            "Quota/get",
+         |            {
+         |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |                "notFound": [],
+         |                "state": "${INSTANCE.value}",
+         |                "list": [
+         |                    {
+         |                        "used": 0,
+         |                        "name": "#private&bob@domain.tld@domain.tld:account:count:Mail",
+         |                        "id": "08417be420b6dd6fa77d48fb2438e0d19108cd29424844bb109b52d356fab528",
+         |                        "dataTypes": [
+         |                            "Mail"
+         |                        ],
+         |                        "limit": 100,
+         |                        "warnLimit": 90,
+         |                        "resourceType": "count",
+         |                        "scope": "account"
+         |                    }
+         |                ]
+         |            },
+         |            "c1"
+         |        ]
+         |    ]
+         |}
+         |""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldReturnQuotaRootOfDelegatedMailboxWhenExtension(server: GuiceJamesServer): Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val bobQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB))
+    quotaProbe.setMaxMessageCount(bobQuotaRoot, QuotaCountLimit.count(100L))
+
+    // setup delegated Mailbox
+    val andreMailbox = MailboxPath.forUser(ANDRE, "mailbox")
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andreMailbox)
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(andreMailbox, BOB.asString, new MailboxACL.Rfc4314Rights(Read))
+
+    quotaProbe.setMaxMessageCount(quotaProbe.getQuotaRoot(andreMailbox), QuotaCountLimit.count(88L))
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota",
+           |    "urn:apache:james:params:jmap:mail:shares" ],
+           |  "methodCalls": [[
+           |    "Quota/get",
+           |    {
+           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |      "ids": null
+           |    },
+           |    "c1"]]
+           |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |    "sessionState": "${SESSION_STATE.value}",
+         |    "methodResponses": [
+         |        [
+         |            "Quota/get",
+         |            {
+         |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |                "notFound": [],
+         |                "state": "${INSTANCE.value}",
+         |                "list": [
+         |                    {
+         |                        "used": 0,
+         |                        "name": "#private&bob@domain.tld@domain.tld:account:count:Mail",
+         |                        "id": "08417be420b6dd6fa77d48fb2438e0d19108cd29424844bb109b52d356fab528",
+         |                        "dataTypes": [
+         |                            "Mail"
+         |                        ],
+         |                        "limit": 100,
+         |                        "warnLimit": 90,
+         |                        "resourceType": "count",
+         |                        "scope": "account"
+         |                    },
+         |                    {
+         |                        "used": 0,
+         |                        "name": "#private&andre@domain.tld@domain.tld:account:count:Mail",
+         |                        "warnLimit": 79,
+         |                        "id": "04cbe4578878e02a74e47ae6be66c88cc8aafd3a5fc698457d712ee5f9a5b4ca",
+         |                        "dataTypes": [
+         |                            "Mail"
+         |                        ],
+         |                        "limit": 88,
+         |                        "resourceType": "count",
+         |                        "scope": "account"
+         |                    }
+         |                ]
+         |            },
+         |            "c1"
+         |        ]
+         |    ]
+         |}
+         |""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldReturnQuotaRootOfDelegatedMailboxWhenNotHasReadRight(server: GuiceJamesServer): Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val bobQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB))
+    quotaProbe.setMaxMessageCount(bobQuotaRoot, QuotaCountLimit.count(100L))
+
+    // setup delegated Mailbox
+    val andreMailbox = MailboxPath.forUser(ANDRE, "mailbox")
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andreMailbox)
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(andreMailbox, BOB.asString, new MailboxACL.Rfc4314Rights(Lookup))
+
+    quotaProbe.setMaxMessageCount(quotaProbe.getQuotaRoot(andreMailbox), QuotaCountLimit.count(88L))
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota",
+           |    "urn:apache:james:params:jmap:mail:shares" ],
+           |  "methodCalls": [[
+           |    "Quota/get",
+           |    {
+           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |      "ids": null
+           |    },
+           |    "c1"]]
+           |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |    "sessionState": "${SESSION_STATE.value}",
+         |    "methodResponses": [
+         |        [
+         |            "Quota/get",
+         |            {
+         |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |                "notFound": [],
+         |                "state": "${INSTANCE.value}",
+         |                "list": [
+         |                    {
+         |                        "used": 0,
+         |                        "name": "#private&bob@domain.tld@domain.tld:account:count:Mail",
+         |                        "id": "08417be420b6dd6fa77d48fb2438e0d19108cd29424844bb109b52d356fab528",
+         |                        "dataTypes": [
+         |                            "Mail"
+         |                        ],
+         |                        "limit": 100,
+         |                        "warnLimit": 90,
+         |                        "resourceType": "count",
+         |                        "scope": "account"
+         |                    }
+         |                ]
+         |            },
+         |            "c1"
+         |        ]
+         |    ]
+         |}
+         |""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldReturnQuotaRootOfDelegatedMailboxWhenProvideCorrectId(server: GuiceJamesServer): Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val bobQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB))
+    quotaProbe.setMaxMessageCount(bobQuotaRoot, QuotaCountLimit.count(100L))
+
+    // setup delegated Mailbox
+    val andreMailbox = MailboxPath.forUser(ANDRE, "mailbox")
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andreMailbox)
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(andreMailbox, BOB.asString, new MailboxACL.Rfc4314Rights(Read))
+
+    quotaProbe.setMaxMessageCount(quotaProbe.getQuotaRoot(andreMailbox), QuotaCountLimit.count(88L))
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota",
+           |    "urn:apache:james:params:jmap:mail:shares" ],
+           |  "methodCalls": [[
+           |    "Quota/get",
+           |    {
+           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |      "ids": ["04cbe4578878e02a74e47ae6be66c88cc8aafd3a5fc698457d712ee5f9a5b4ca"]
+           |    },
+           |    "c1"]]
+           |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |    "sessionState": "${SESSION_STATE.value}",
+         |    "methodResponses": [
+         |        [
+         |            "Quota/get",
+         |            {
+         |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |                "notFound": [],
+         |                "state": "${INSTANCE.value}",
+         |                "list": [
+         |                    {
+         |                        "used": 0,
+         |                        "name": "#private&andre@domain.tld@domain.tld:account:count:Mail",
+         |                        "warnLimit": 79,
+         |                        "id": "04cbe4578878e02a74e47ae6be66c88cc8aafd3a5fc698457d712ee5f9a5b4ca",
+         |                        "dataTypes": [
+         |                            "Mail"
+         |                        ],
+         |                        "limit": 88,
+         |                        "resourceType": "count",
+         |                        "scope": "account"
+         |                    }
+         |                ]
+         |            },
+         |            "c1"
+         |        ]
+         |    ]
+         |}
+         |""".stripMargin)
+  }
+
 }
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala
index 938a6612e1..1c4545a5d9 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala
@@ -84,9 +84,9 @@ object JmapQuota {
   val allProperties: Properties = Properties("id", "resourceType", "used", "limit", "scope", "name", "dataTypes", "warnLimit", "softLimit", "description")
   val idProperty: Properties = Properties("id")
 
-  def propertiesFiltered(requestedProperties: Properties) : Properties = idProperty ++ requestedProperties
+  def propertiesFiltered(requestedProperties: Properties): Properties = idProperty ++ requestedProperties
 
-  def extractUserMessageCountQuota(quota: ModelQuota[QuotaCountLimit, QuotaCountUsage], countQuotaIdPlaceHolder: Id): Option[JmapQuota] =
+  def extractUserMessageCountQuota(quota: ModelQuota[QuotaCountLimit, QuotaCountUsage], countQuotaIdPlaceHolder: Id, quotaRoot: ModelQuotaRoot): Option[JmapQuota] =
     Option(quota.getLimitByScope.get(ModelQuota.Scope.User))
       .map(limit => JmapQuota(
         id = countQuotaIdPlaceHolder,
@@ -94,11 +94,11 @@ object JmapQuota {
         used = UnsignedInt.liftOrThrow(quota.getUsed.asLong()),
         limit = UnsignedInt.liftOrThrow(limit.asLong()),
         scope = AccountScope,
-        name = QuotaName.from(AccountScope, CountResourceType, List(MailDataType)),
+        name = QuotaName.from(quotaRoot, AccountScope, CountResourceType, List(MailDataType)),
         dataTypes = List(MailDataType),
         warnLimit = Some(UnsignedInt.liftOrThrow((limit.asLong() * WARN_LIMIT_PERCENTAGE).toLong))))
 
-  def extractUserMessageSizeQuota(quota: ModelQuota[QuotaSizeLimit, QuotaSizeUsage], sizeQuotaIdPlaceHolder: Id): Option[JmapQuota] =
+  def extractUserMessageSizeQuota(quota: ModelQuota[QuotaSizeLimit, QuotaSizeUsage], sizeQuotaIdPlaceHolder: Id, quotaRoot: ModelQuotaRoot): Option[JmapQuota] =
     Option(quota.getLimitByScope.get(ModelQuota.Scope.User))
       .map(limit => JmapQuota(
         id = sizeQuotaIdPlaceHolder,
@@ -106,7 +106,7 @@ object JmapQuota {
         used = UnsignedInt.liftOrThrow(quota.getUsed.asLong()),
         limit = UnsignedInt.liftOrThrow(limit.asLong()),
         scope = AccountScope,
-        name = QuotaName.from(AccountScope, OctetsResourceType, List(MailDataType)),
+        name = QuotaName.from(quotaRoot, AccountScope, OctetsResourceType, List(MailDataType)),
         dataTypes = List(MailDataType),
         warnLimit = Some(UnsignedInt.liftOrThrow((limit.asLong() * WARN_LIMIT_PERCENTAGE).toLong))))
 }
@@ -123,8 +123,8 @@ case class JmapQuota(id: Id,
                      description: Option[QuotaDescription] = None)
 
 object QuotaName {
-  def from(scope: Scope, resourceType: ResourceType, dataTypes: List[DataType]): QuotaName =
-    QuotaName(s"${scope.asString()}:${resourceType.asString()}:${dataTypes.map(_.asString()).mkString("_")}")
+  def from(quotaRoot: ModelQuotaRoot, scope: Scope, resourceType: ResourceType, dataTypes: List[DataType]): QuotaName =
+    QuotaName(s"${quotaRoot.asString()}:${scope.asString()}:${resourceType.asString()}:${dataTypes.map(_.asString()).mkString("_")}")
 }
 
 case class QuotaName(string: String)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/QuotaGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/QuotaGetMethod.scala
index 73dc94ade1..3cd6c519f4 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/QuotaGetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/QuotaGetMethod.scala
@@ -21,7 +21,7 @@ package org.apache.james.jmap.method
 
 import eu.timepit.refined.auto._
 import org.apache.james.core.Username
-import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_QUOTA}
+import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JAMES_SHARES, JMAP_CORE, JMAP_QUOTA}
 import org.apache.james.jmap.core.Invocation.{Arguments, MethodCallId, MethodName}
 import org.apache.james.jmap.core.{ErrorCode, Invocation, MissingCapabilityException, Properties}
 import org.apache.james.jmap.json.{QuotaSerializer, ResponseSerializer}
@@ -53,7 +53,7 @@ class QuotaGetMethod @Inject()(val metricFactory: MetricFactory,
     val requestedProperties: Properties = request.properties.getOrElse(JmapQuota.allProperties)
 
     (requestedProperties -- JmapQuota.allProperties match {
-      case invalidProperties if invalidProperties.isEmpty() => getQuotaGetResponse(request, mailboxSession.getUser)
+      case invalidProperties if invalidProperties.isEmpty() => getQuotaGetResponse(request, mailboxSession.getUser, capabilities)
         .reduce(QuotaResponseGetResult.empty)(QuotaResponseGetResult.merge)
         .map(result => result.asResponse(accountId = request.accountId))
         .map(response => Invocation(
@@ -77,15 +77,15 @@ class QuotaGetMethod @Inject()(val metricFactory: MetricFactory,
       case errors: JsError => Left(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
     }
 
-  private def getQuotaGetResponse(quotaGetRequest: QuotaGetRequest, username: Username): SFlux[QuotaResponseGetResult] =
+  private def getQuotaGetResponse(quotaGetRequest: QuotaGetRequest, username: Username, capabilities: Set[CapabilityIdentifier]): SFlux[QuotaResponseGetResult] =
     quotaGetRequest.ids match {
       case None =>
-        jmapQuotaManagerWrapper.list(username)
+        jmapQuotaManagerWrapper.list(username, capabilities)
           .collectSeq()
           .map(listJmapQuota => QuotaResponseGetResult(jmapQuotaSet = listJmapQuota.toSet))
           .flatMapMany(result => SFlux.just(result))
       case Some(ids) => SFlux.fromIterable(ids.value)
-        .flatMap(id => jmapQuotaManagerWrapper.get(username, id.id)
+        .flatMap(id => jmapQuotaManagerWrapper.get(username, id.id, capabilities)
           .map(jmapQuota => QuotaResponseGetResult(jmapQuotaSet = Set(jmapQuota)))
           .switchIfEmpty(SMono.just(QuotaResponseGetResult(notFound = QuotaNotFound(Set(id))))))
     }
@@ -98,32 +98,39 @@ class QuotaGetMethod @Inject()(val metricFactory: MetricFactory,
 
 case class JmapQuotaManagerWrapper(private var quotaManager: QuotaManager,
                                    private var quotaRootResolver: UserQuotaRootResolver) {
-  def get(username: Username, quotaId: Id): SFlux[JmapQuota] =
-    SMono.just(quotaRootResolver.forUser(username))
-      .flatMapMany(quotaRoot => getJmapQuota(quotaRoot, Some(quotaId)))
+  def get(username: Username, quotaId: Id, capabilities: Set[CapabilityIdentifier]): SFlux[JmapQuota] =
+    retrieveQuotaRoot(username, capabilities)
+      .flatMap(quotaRoot => getJmapQuota(quotaRoot, Some(quotaId)))
 
-  def list(username: Username): SFlux[JmapQuota] =
-    SMono.just(quotaRootResolver.forUser(username))
-      .flatMapMany(quotaRoot => getJmapQuota(quotaRoot))
+  def list(username: Username, capabilities: Set[CapabilityIdentifier]): SFlux[JmapQuota] =
+    retrieveQuotaRoot(username, capabilities)
+      .flatMap(quotaRoot => getJmapQuota(quotaRoot))
+
+  def retrieveQuotaRoot(username: Username, capabilities: Set[CapabilityIdentifier]): SFlux[QuotaRoot] =
+    SMono.just(capabilities)
+      .map(value => value.contains(JAMES_SHARES))
+      .filter(containSharedCapability => containSharedCapability)
+      .flatMapMany(_ => SFlux(quotaRootResolver.listAllAccessibleQuotaRoots(username)))
+      .switchIfEmpty(SFlux.just(quotaRootResolver.forUser(username)))
 
   private def getJmapQuota(quotaRoot: QuotaRoot, quotaId: Option[Id] = None): SFlux[JmapQuota] =
     (quotaId match {
       case None => SMono(quotaManager.getQuotasReactive(quotaRoot))
-        .flatMapMany(quotas => SMono.fromCallable(() => JmapQuota.extractUserMessageCountQuota(quotas.getMessageQuota, QuotaIdFactory.from(quotaRoot, CountResourceType)))
-          .mergeWith(SMono.fromCallable(() => JmapQuota.extractUserMessageSizeQuota(quotas.getStorageQuota, QuotaIdFactory.from(quotaRoot, OctetsResourceType)))))
+        .flatMapMany(quotas => SMono.fromCallable(() => JmapQuota.extractUserMessageCountQuota(quotas.getMessageQuota, QuotaIdFactory.from(quotaRoot, CountResourceType), quotaRoot))
+          .mergeWith(SMono.fromCallable(() => JmapQuota.extractUserMessageSizeQuota(quotas.getStorageQuota, QuotaIdFactory.from(quotaRoot, OctetsResourceType), quotaRoot))))
 
       case Some(quotaIdValue) =>
         val quotaCountPublisher = SMono.fromCallable(() => QuotaIdFactory.from(quotaRoot, CountResourceType))
           .filter(countQuotaId => countQuotaId.value.equals(quotaIdValue.value))
           .flatMap(_ => SMono.fromCallable(() => quotaManager.getMessageQuota(quotaRoot))
             .subscribeOn(ReactorUtils.BLOCKING_CALL_WRAPPER))
-          .map(quota => JmapQuota.extractUserMessageCountQuota(quota, quotaIdValue))
+          .map(quota => JmapQuota.extractUserMessageCountQuota(quota, quotaIdValue, quotaRoot))
 
         val quotaSizePublisher = SMono.fromCallable(() => QuotaIdFactory.from(quotaRoot, OctetsResourceType))
           .filter(sizeQuotaId => sizeQuotaId.value.equals(quotaIdValue.value))
           .flatMap(_ => SMono.fromCallable(() => quotaManager.getStorageQuota(quotaRoot))
             .subscribeOn(ReactorUtils.BLOCKING_CALL_WRAPPER))
-          .map(quota => JmapQuota.extractUserMessageSizeQuota(quota, quotaIdValue))
+          .map(quota => JmapQuota.extractUserMessageSizeQuota(quota, quotaIdValue, quotaRoot))
 
         quotaCountPublisher.mergeWith(quotaSizePublisher)
     }).flatMap(SMono.justOrEmpty)


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org