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 766360163ad41098a5952d3fd9c5bd53a6b08307 Author: Rene Cordier <[email protected]> AuthorDate: Fri Aug 21 11:07:15 2020 +0700 JAMES-3359 Mailbox/set update isSubscribed delegation handling --- .../contract/MailboxSetMethodContract.scala | 348 +++++++++++++++++++++ .../org/apache/james/jmap/mail/MailboxSet.scala | 6 +- .../james/jmap/method/MailboxSetMethod.scala | 5 +- 3 files changed, 355 insertions(+), 4 deletions(-) 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 e431c0c..437a152 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 @@ -4136,6 +4136,354 @@ trait MailboxSetMethodContract { @Test + def updateShouldSubscribeDelegatedMailboxes(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)) + + val request = + s""" + |{ + | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:apache:james:params:jmap:mail:shares" ], + | "methodCalls": [ + | ["Mailbox/set", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "update": { + | "${mailboxId.serialize()}" : { + | "/isSubscribed": true + | } + | } + | }, + | "c3"], + | ["Mailbox/get", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "ids": ["${mailboxId.serialize()}"] + | }, + | "c4"] + | ] + |} + |""".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).isEqualTo( + s"""{ + | "sessionState": "75128aab4b1b", + | "methodResponses": [ + | ["Mailbox/set", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "newState": "000001", + | "updated": { + | "${mailboxId.serialize()}": {} + | } + | }, "c3"], + | ["Mailbox/get", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "state": "000001", + | "list": [{ + | "id": "${mailboxId.serialize()}", + | "name": "mailbox", + | "sortOrder": 1000, + | "totalEmails": 0, + | "unreadEmails": 0, + | "totalThreads": 0, + | "unreadThreads": 0, + | "myRights": { + | "mayReadItems": false, + | "mayAddItems": false, + | "mayRemoveItems": false, + | "maySetSeen": false, + | "maySetKeywords": false, + | "mayCreateChild": false, + | "mayRename": false, + | "mayDelete": false, + | "maySubmit": false + | }, + | "isSubscribed": true, + | "namespace": "Delegated[[email protected]]", + | "rights": { + | "[email protected]": ["l"] + | } + | }], + | "notFound": [] + | }, "c4"] + | ] + |}""".stripMargin) + } + + @Test + def updateShouldUnsubscribeDelegatedMailboxes(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)) + + val request = + s""" + |{ + | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:apache:james:params:jmap:mail:shares" ], + | "methodCalls": [ + | ["Mailbox/set", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "update": { + | "${mailboxId.serialize()}" : { + | "/isSubscribed": true + | } + | } + | }, + | "c2"], + | ["Mailbox/set", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "update": { + | "${mailboxId.serialize()}" : { + | "/isSubscribed": false + | } + | } + | }, + | "c3"], + | ["Mailbox/get", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "ids": ["${mailboxId.serialize()}"] + | }, + | "c4"] + | ] + |} + |""".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).isEqualTo( + s"""{ + | "sessionState": "75128aab4b1b", + | "methodResponses": [ + | ["Mailbox/set", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "newState": "000001", + | "updated": { + | "${mailboxId.serialize()}": {} + | } + | }, "c2"], + | ["Mailbox/set", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "newState": "000001", + | "updated": { + | "${mailboxId.serialize()}": {} + | } + | }, "c3"], + | ["Mailbox/get", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "state": "000001", + | "list": [{ + | "id": "${mailboxId.serialize()}", + | "name": "mailbox", + | "sortOrder": 1000, + | "totalEmails": 0, + | "unreadEmails": 0, + | "totalThreads": 0, + | "unreadThreads": 0, + | "myRights": { + | "mayReadItems": false, + | "mayAddItems": false, + | "mayRemoveItems": false, + | "maySetSeen": false, + | "maySetKeywords": false, + | "mayCreateChild": false, + | "mayRename": false, + | "mayDelete": false, + | "maySubmit": false + | }, + | "isSubscribed": false, + | "namespace": "Delegated[[email protected]]", + | "rights": { + | "[email protected]": ["l"] + | } + | }], + | "notFound": [] + | }, "c4"] + | ] + |}""".stripMargin) + } + + @Test + def updateShouldUnsubscribeDelegatedMailboxesWhenNull(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)) + + val request = + s""" + |{ + | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:apache:james:params:jmap:mail:shares" ], + | "methodCalls": [ + | ["Mailbox/set", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "update": { + | "${mailboxId.serialize()}" : { + | "/isSubscribed": true + | } + | } + | }, + | "c2"], + | ["Mailbox/set", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "update": { + | "${mailboxId.serialize()}" : { + | "/isSubscribed": null + | } + | } + | }, + | "c3"], + | ["Mailbox/get", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "ids": ["${mailboxId.serialize()}"] + | }, + | "c4"] + | ] + |} + |""".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).isEqualTo( + s"""{ + | "sessionState": "75128aab4b1b", + | "methodResponses": [ + | ["Mailbox/set", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "newState": "000001", + | "updated": { + | "${mailboxId.serialize()}": {} + | } + | }, "c2"], + | ["Mailbox/set", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "newState": "000001", + | "updated": { + | "${mailboxId.serialize()}": {} + | } + | }, "c3"], + | ["Mailbox/get", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "state": "000001", + | "list": [{ + | "id": "${mailboxId.serialize()}", + | "name": "mailbox", + | "sortOrder": 1000, + | "totalEmails": 0, + | "unreadEmails": 0, + | "totalThreads": 0, + | "unreadThreads": 0, + | "myRights": { + | "mayReadItems": false, + | "mayAddItems": false, + | "mayRemoveItems": false, + | "maySetSeen": false, + | "maySetKeywords": false, + | "mayCreateChild": false, + | "mayRename": false, + | "mayDelete": false, + | "maySubmit": false + | }, + | "isSubscribed": false, + | "namespace": "Delegated[[email protected]]", + | "rights": { + | "[email protected]": ["l"] + | } + | }], + | "notFound": [] + | }, "c4"] + | ] + |}""".stripMargin) + } + + @Test + def updateShouldNotAffectSubscriptionOfOthers(server: GuiceJamesServer): Unit = { + val path = MailboxPath.forUser(BOB, "mailbox") + val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl]) + val mailboxId: MailboxId = mailboxProbe + .createMailbox(path) + server.getProbe(classOf[ACLProbeImpl]) + .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup)) + + val request = + s""" + |{ + | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:apache:james:params:jmap:mail:shares" ], + | "methodCalls": [ + | ["Mailbox/set", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "update": { + | "${mailboxId.serialize()}" : { + | "/isSubscribed": true + | } + | } + | }, + | "c2"] + | ] + |} + |""".stripMargin + + `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .log().ifValidationFails() + .statusCode(SC_OK) + .contentType(JSON) + + assertThat(mailboxProbe.listSubscriptions(ANDRE.asString())).isEmpty() + } + + @Test def updateShouldFailWhenInvalidIsSubscribedJSON(server: GuiceJamesServer): Unit = { val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.forUser(BOB, "mailbox")) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala index 44bb866..4fc8b92 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala @@ -180,8 +180,8 @@ object SharedWithResetUpdate { object IsSubscribedUpdate { def parse(newValue: JsValue): Either[PatchUpdateValidationException, Update] = newValue match { - case JsBoolean(value) => scala.Right(IsSubscribedUpdate(IsSubscribed(value))) - case JsNull => scala.Right(IsSubscribedUpdate(IsSubscribed(true))) + case JsBoolean(value) => scala.Right(IsSubscribedUpdate(Some(IsSubscribed(value)))) + case JsNull => scala.Right(IsSubscribedUpdate(None)) case _ => Left(InvalidUpdateException("/isSubscribed", "Expecting a JSON boolean as an argument")) } } @@ -189,7 +189,7 @@ object IsSubscribedUpdate { sealed trait Update case class NameUpdate(newName: String) extends Update case class SharedWithResetUpdate(rights: Rights) extends Update -case class IsSubscribedUpdate(isSubscribed: IsSubscribed) extends Update +case class IsSubscribedUpdate(isSubscribed: Option[IsSubscribed]) extends Update class PatchUpdateValidationException() extends IllegalArgumentException case class UnsupportedPropertyUpdatedException(property: MailboxPatchObjectKey) extends PatchUpdateValidationException diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala index d1915f2..0ecc29a 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala @@ -205,7 +205,10 @@ class MailboxSetMethod @Inject()(serializer: Serializer, maybeIsSubscribedUpdate.map(isSubscribedUpdate => { SMono.fromCallable(() => { val mailbox = mailboxManager.getMailbox(mailboxId, mailboxSession) - if (isSubscribedUpdate.isSubscribed.value) { + val isOwner = mailbox.getMailboxPath.belongsTo(mailboxSession) + val shouldSubscribe = isSubscribedUpdate.isSubscribed.map(_.value).getOrElse(isOwner) + + if (shouldSubscribe) { subscriptionManager.subscribe(mailboxSession, mailbox.getMailboxPath.getName) } else { subscriptionManager.unsubscribe(mailboxSession, mailbox.getMailboxPath.getName) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
