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 089e97c254c249e2c11ae7028ab155e3c10a4811 Author: Benoit TELLIER <btell...@linagora.com> AuthorDate: Thu Oct 17 10:28:19 2024 +0200 JAMES-2182 Fix rights for DELETE --- .../apache/james/mailbox/MailboxManagerTest.java | 34 ++++++--- .../james/mailbox/store/StoreMailboxManager.java | 20 ++++-- .../apache/james/imap/scripts/SharingAccessL.test | 2 +- .../apache/james/imap/scripts/SharingAccessLR.test | 2 +- .../james/imap/scripts/SharingAccessLRA.test | 2 +- .../james/imap/scripts/SharingAccessLRI.test | 2 +- .../james/imap/scripts/SharingAccessLRK.test | 2 +- .../james/imap/scripts/SharingAccessLRS.test | 2 +- .../james/imap/scripts/SharingAccessLRT.test | 2 +- .../james/imap/scripts/SharingAccessLRTE.test | 2 +- .../james/imap/scripts/SharingAccessLRW.test | 2 +- .../james/imap/scripts/SharingAccessLRX.test | 8 +-- .../contract/MailboxSetMethodContract.scala | 80 +++++++++++++++++++++- 13 files changed, 131 insertions(+), 29 deletions(-) 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 8384acd1eb..3f851fa6e7 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 @@ -273,7 +273,7 @@ public abstract class MailboxManagerTest<T extends MailboxManager> { @Test void shareeShouldBeAbleToCreateMailbox() throws Exception { - // GIVEN USER1 shared his INBOX + assumeTrue(mailboxManager.hasCapability(MailboxCapabilities.ACL)); session = mailboxManager.createSystemSession(USER_1); MailboxPath mailboxPath = MailboxPath.inbox(session); mailboxManager.createMailbox(mailboxPath, session); @@ -284,12 +284,10 @@ public abstract class MailboxManagerTest<T extends MailboxManager> { MailboxACL.Right.Read, MailboxACL.Right.CreateMailbox))) .asAddition(), session); - // When USER_2 create a mailbox child MailboxSession session2 = mailboxManager.createSystemSession(USER_2); MailboxPath childPath = MailboxPath.inbox(session).child("child", session2.getPathDelimiter()); mailboxManager.createMailbox(childPath, session2); - // Then child path inherit rights assertThat(mailboxManager.getMailbox(childPath, session) .getMailboxEntity().getACL().getEntries().get(MailboxACL.EntryKey.createUserEntryKey(USER_2))) .isEqualTo(MailboxACL.Rfc4314Rights.fromSerializedRfc4314Rights("lrk")); @@ -297,7 +295,7 @@ public abstract class MailboxManagerTest<T extends MailboxManager> { @Test void shareeShouldBeAbleToCreateMailboxChildren() throws Exception { - // GIVEN USER1 shared his INBOX + assumeTrue(mailboxManager.hasCapability(MailboxCapabilities.ACL)); session = mailboxManager.createSystemSession(USER_1); MailboxPath mailboxPath = MailboxPath.inbox(session); mailboxManager.createMailbox(mailboxPath, session); @@ -308,14 +306,12 @@ public abstract class MailboxManagerTest<T extends MailboxManager> { MailboxACL.Right.Read, MailboxACL.Right.CreateMailbox))) .asAddition(), session); - // When USER_2 create a mailbox child MailboxSession session2 = mailboxManager.createSystemSession(USER_2); MailboxPath childPath = MailboxPath.inbox(session) .child("child", session2.getPathDelimiter()) .child("anotherkid", session2.getPathDelimiter()); mailboxManager.createMailbox(childPath, session2); - // Then child path inherit rights assertThat(mailboxManager.getMailbox(childPath, session) .getMailboxEntity().getACL().getEntries().get(MailboxACL.EntryKey.createUserEntryKey(USER_2))) .isEqualTo(MailboxACL.Rfc4314Rights.fromSerializedRfc4314Rights("lrk")); @@ -323,7 +319,7 @@ public abstract class MailboxManagerTest<T extends MailboxManager> { @Test void shareeShouldBeAbleToCreateMailboxChildrenIntermediatePaths() throws Exception { - // GIVEN USER1 shared his INBOX + assumeTrue(mailboxManager.hasCapability(MailboxCapabilities.ACL)); session = mailboxManager.createSystemSession(USER_1); MailboxPath mailboxPath = MailboxPath.inbox(session); mailboxManager.createMailbox(mailboxPath, session); @@ -334,19 +330,37 @@ public abstract class MailboxManagerTest<T extends MailboxManager> { MailboxACL.Right.Read, MailboxACL.Right.CreateMailbox))) .asAddition(), session); - // When USER_2 create a mailbox child MailboxSession session2 = mailboxManager.createSystemSession(USER_2); MailboxPath intermediatePath = MailboxPath.inbox(session) .child("child", session2.getPathDelimiter()); MailboxPath childPath = intermediatePath.child("anotherkid", session2.getPathDelimiter()); mailboxManager.createMailbox(childPath, session2); - // Then child path inherit rights assertThat(mailboxManager.getMailbox(intermediatePath, session) .getMailboxEntity().getACL().getEntries().get(MailboxACL.EntryKey.createUserEntryKey(USER_2))) .isEqualTo(MailboxACL.Rfc4314Rights.fromSerializedRfc4314Rights("lrk")); } + @Test + void shareeShouldBeAbleToDeleteMailbox() throws Exception { + assumeTrue(mailboxManager.hasCapability(MailboxCapabilities.ACL)); + session = mailboxManager.createSystemSession(USER_1); + MailboxPath mailboxPath = MailboxPath.forUser(USER_1, "child"); + mailboxManager.createMailbox(mailboxPath, session); + mailboxManager.applyRightsCommand(mailboxPath, + MailboxACL.command() + .key(MailboxACL.EntryKey.createUserEntryKey(USER_2)) + .rights(MailboxACL.Rfc4314Rights.of(ImmutableList.of(MailboxACL.Right.Lookup, + MailboxACL.Right.Read, MailboxACL.Right.DeleteMailbox))) + .asAddition(), session); + + MailboxSession session2 = mailboxManager.createSystemSession(USER_2); + mailboxManager.deleteMailbox(mailboxPath, session2); + + assertThatThrownBy(() -> mailboxManager.getMailbox(mailboxPath, session)) + .isInstanceOf(MailboxNotFoundException.class); + } + @Test void creatingMixedCaseINBOXShouldCreateItAsINBOX() throws Exception { session = mailboxManager.createSystemSession(USER_1); @@ -2195,7 +2209,7 @@ public abstract class MailboxManagerTest<T extends MailboxManager> { mailboxManager.createMailbox(inbox, sessionUser1); assertThatThrownBy(() -> mailboxManager.deleteMailbox(inbox, sessionUser2)) - .isInstanceOf(MailboxNotFoundException.class); + .isInstanceOf(InsufficientRightsException.class); } 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 5e7d20f951..6f9548fe25 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 @@ -460,10 +460,10 @@ public class StoreMailboxManager implements MailboxManager { @Override public void deleteMailbox(final MailboxPath mailboxPath, final MailboxSession session) throws MailboxException { LOGGER.info("deleteMailbox {}", mailboxPath); - assertIsOwner(session, mailboxPath); MailboxMapper mailboxMapper = mailboxSessionMapperFactory.getMailboxMapper(session); mailboxMapper.execute(() -> block(mailboxMapper.findMailboxByPath(mailboxPath) + .filterWhen(mailbox -> assertCanDeleteReactive(session, mailbox.generateAssociatedPath()).thenReturn(true)) .flatMap(mailbox -> doDeleteMailbox(mailboxMapper, mailbox, session)) .switchIfEmpty(Mono.error(() -> new MailboxNotFoundException(mailboxPath))))); } @@ -497,18 +497,28 @@ public class StoreMailboxManager implements MailboxManager { @Override public Mono<Void> deleteMailboxReactive(MailboxPath mailboxPath, MailboxSession session) { LOGGER.info("deleteMailbox {}", mailboxPath); - if (!mailboxPath.belongsTo(session)) { - LOGGER.info("Mailbox {} does not belong to {}", mailboxPath.asString(), session.getUser().asString()); - return Mono.error(new MailboxNotFoundException(mailboxPath.asString())); - } MailboxMapper mailboxMapper = mailboxSessionMapperFactory.getMailboxMapper(session); return mailboxMapper.executeReactive(mailboxMapper.findMailboxByPath(mailboxPath) + .filterWhen(mailbox -> assertCanDeleteReactive(session, mailbox.generateAssociatedPath()).thenReturn(true)) .flatMap(mailbox -> doDeleteMailbox(mailboxMapper, mailbox, session)) .switchIfEmpty(Mono.error(() -> new MailboxNotFoundException(mailboxPath)))) .then(); } + private Mono<Void> assertCanDeleteReactive(MailboxSession session, MailboxPath path) { + if (path.belongsTo(session)) { + return Mono.empty(); + } + return Mono.from(hasRightReactive(path, Right.DeleteMailbox, session)) + .flatMap(hasRight -> { + if (hasRight) { + return Mono.empty(); + } + return Mono.error(new InsufficientRightsException("user '" + session.getUser().asString() + "' is not allowed to delete the mailbox '" + path.asString() + "'")); + }); + } + private Mono<Mailbox> doDeleteMailbox(MailboxMapper mailboxMapper, Mailbox mailbox, MailboxSession session) { MessageMapper messageMapper = mailboxSessionMapperFactory.getMessageMapper(session); diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessL.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessL.test index 60d9f64d04..004cb21dbd 100644 --- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessL.test +++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessL.test @@ -59,7 +59,7 @@ C: a4 COPY 1:* #user.boby.mailbox-l S: a4 NO COPY processing failed. C: a5 DELETE #user.boby.mailbox-l -S: a5 NO DELETE failed. No such mailbox. +S: a5 NO DELETE processing failed. C: a5 SETACL #user.boby.mailbox-l imapuser lra S: a5 NO SETACL You need the Administer right to perform command SETACL on mailbox #user.boby.mailbox-l. diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLR.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLR.test index 1e6e2a4595..8d7212a5ea 100644 --- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLR.test +++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLR.test @@ -60,7 +60,7 @@ C: a6 UNSELECT S: a6 OK UNSELECT completed. C: a5 DELETE #user.boby.mailbox-lr -S: a5 NO DELETE failed. No such mailbox. +S: a5 NO DELETE processing failed. C: a5 SETACL #user.boby.mailbox-lr imapuser lra S: a5 NO SETACL You need the Administer right to perform command SETACL on mailbox #user.boby.mailbox-lr. diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRA.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRA.test index 49975b13c0..c08191f132 100644 --- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRA.test +++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRA.test @@ -60,7 +60,7 @@ C: a6 UNSELECT S: a6 OK UNSELECT completed. C: a5 DELETE #user.boby.mailbox-lra -S: a5 NO DELETE failed. No such mailbox. +S: a5 NO DELETE processing failed. # TODO if I have 'a' right I shall be able to administer! # org.apache.james.mailbox.exception.InsufficientRightsException: Setting ACL is only permitted to the owner of the mailbox diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRI.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRI.test index 4a6e71579e..fc1bf6cdd6 100644 --- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRI.test +++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRI.test @@ -60,7 +60,7 @@ C: a6 UNSELECT S: a6 OK UNSELECT completed. C: a5 DELETE #user.boby.mailbox-lri -S: a5 NO DELETE failed. No such mailbox. +S: a5 NO DELETE processing failed. C: a5 SETACL #user.boby.mailbox-lri imapuser lra S: a5 NO SETACL You need the Administer right to perform command SETACL on mailbox #user.boby.mailbox-lri. diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRK.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRK.test index 57da76bf11..ea62d95e0d 100644 --- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRK.test +++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRK.test @@ -60,7 +60,7 @@ C: a6 UNSELECT S: a6 OK UNSELECT completed. C: a5 DELETE #user.boby.mailbox-lrk -S: a5 NO DELETE failed. No such mailbox. +S: a5 NO DELETE processing failed. C: a5 SETACL #user.boby.mailbox-lrk imapuser lra S: a5 NO SETACL You need the Administer right to perform command SETACL on mailbox #user.boby.mailbox-lrk. diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRS.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRS.test index add7607bf1..867f624b35 100644 --- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRS.test +++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRS.test @@ -61,7 +61,7 @@ C: a6 UNSELECT S: a6 OK UNSELECT completed. C: a5 DELETE #user.boby.mailbox-lrs -S: a5 NO DELETE failed. No such mailbox. +S: a5 NO DELETE processing failed. C: a5 SETACL #user.boby.mailbox-lrs imapuser lra S: a5 NO SETACL You need the Administer right to perform command SETACL on mailbox #user.boby.mailbox-lrs. diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRT.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRT.test index 4dab79df20..d972b0da75 100644 --- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRT.test +++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRT.test @@ -61,7 +61,7 @@ C: a6 UNSELECT S: a6 OK UNSELECT completed. C: a5 DELETE #user.boby.mailbox-lrt -S: a5 NO DELETE failed. No such mailbox. +S: a5 NO DELETE processing failed. C: a5 SETACL #user.boby.mailbox-lrt imapuser lra S: a5 NO SETACL You need the Administer right to perform command SETACL on mailbox #user.boby.mailbox-lrt. diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRTE.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRTE.test index 30c9d77e9a..9bf858fc3c 100644 --- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRTE.test +++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRTE.test @@ -61,7 +61,7 @@ C: a6 UNSELECT S: a6 OK UNSELECT completed. C: a5 DELETE #user.boby.mailbox-lrte -S: a5 NO DELETE failed. No such mailbox. +S: a5 NO DELETE processing failed. C: a5 SETACL #user.boby.mailbox-lrte imapuser lra S: a5 NO SETACL You need the Administer right to perform command SETACL on mailbox #user.boby.mailbox-lrte. diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRW.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRW.test index c7fb43fa2b..4056b27f71 100644 --- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRW.test +++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRW.test @@ -61,7 +61,7 @@ C: a6 UNSELECT S: a6 OK UNSELECT completed. C: a5 DELETE #user.boby.mailbox-lrw -S: a5 NO DELETE failed. No such mailbox. +S: a5 NO DELETE processing failed. C: a5 SETACL #user.boby.mailbox-lrw imapuser lra S: a5 NO SETACL You need the Administer right to perform command SETACL on mailbox #user.boby.mailbox-lrw. diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRX.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRX.test index 78548086e7..e3f31d9291 100644 --- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRX.test +++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/SharingAccessLRX.test @@ -59,9 +59,6 @@ S: a4 NO COPY processing failed. C: a6 UNSELECT S: a6 OK UNSELECT completed. -# TODO x right shall be enough to delete! -C: a5 DELETE #user.boby.mailbox-lrx -S: a5 NO DELETE failed. No such mailbox. C: a5 SETACL #user.boby.mailbox-lrx imapuser lra S: a5 NO SETACL You need the Administer right to perform command SETACL on mailbox #user.boby.mailbox-lrx. @@ -102,4 +99,7 @@ S: \* 1 FETCH \(FLAGS \(\\Recent\)\) S: F11 OK FETCH completed. C: F15 EXPUNGE -S: F15 NO EXPUNGE failed. Mailbox is read only. \ No newline at end of file +S: F15 NO EXPUNGE failed. Mailbox is read only. + +C: a5 DELETE #user.boby.mailbox-lrx +S: a5 OK DELETE completed. \ No newline at end of file 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/MailboxSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala index 5e9a9c3455..8c36e10f0e 100644 --- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala +++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala @@ -2116,7 +2116,7 @@ trait MailboxSetMethodContract { val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path) server.getProbe(classOf[ACLProbeImpl]) - .replaceRights(path, BOB.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read, Right.CreateMailbox)) + .replaceRights(path, BOB.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read)) val request = s""" |{ @@ -2173,6 +2173,84 @@ trait MailboxSetMethodContract { |}""".stripMargin) } + @Test + def mailboxSetShouldCreateChildMailboxWhenSharedParentMailboxWithCreateRight(server: GuiceJamesServer): Unit = { + val path = MailboxPath.forUser(ANDRE, "mailbox") + val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path) + + server.getProbe(classOf[ACLProbeImpl]) + .replaceRights(path, BOB.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read, Right.CreateMailbox)) + val request = + s""" + |{ + | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ], + | "methodCalls": [ + | [ + | "Mailbox/set", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "create": { + | "C42": { + | "name": "childMailbox", + | "parentId":"${mailboxId.serialize}" + | } + | } + | }, + | "c1" + | ] + | ] + |} + |""".stripMargin + + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .log().ifValidationFails() + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[0][1].oldState") + .isEqualTo( + s"""{ + | "sessionState": "${SESSION_STATE.value}", + | "methodResponses": [[ + | "Mailbox/set", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "created":{ + | "C42":{ + | "id":"3", + | "isSubscribed":true, + | "myRights":{ + | "mayAddItems":true, + | "mayCreateChild":true, + | "mayDelete":true, + | "mayReadItems":true, + | "mayRemoveItems":true, + | "mayRename":true, + | "maySetKeywords":true, + | "maySetSeen":true, + | "maySubmit":true + | }, + | "sortOrder":1000, + | "totalEmails":0, + | "totalThreads":0, + | "unreadEmails":0, + | "unreadThreads":0 + | } + | } + | }, + | "c1"]] + |}""".stripMargin) + } + @Test @Tag(CategoryTags.BASIC_FEATURE) def deleteShouldSucceedWhenMailboxExists(server: GuiceJamesServer): Unit = { --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org