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
The following commit(s) were added to refs/heads/master by this push: new 32a6ea9 JAMES-3357 Handle rights upon Mailbox/set create 32a6ea9 is described below commit 32a6ea9e6d1ea30e872e679e57c9b1f8628e459b Author: Benoit Tellier <btell...@linagora.com> AuthorDate: Fri Aug 14 15:31:13 2020 +0700 JAMES-3357 Handle rights upon Mailbox/set create --- .../contract/MailboxSetMethodContract.scala | 226 +++++++++++++++++++++ .../org/apache/james/jmap/json/Serializer.scala | 16 ++ .../org/apache/james/jmap/mail/MailboxSet.scala | 5 +- .../scala/org/apache/james/jmap/mail/Rights.scala | 2 +- .../james/jmap/method/MailboxSetMethod.scala | 11 +- 5 files changed, 255 insertions(+), 5 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 1e7dec4..f8d8a96 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 @@ -390,6 +390,232 @@ trait MailboxSetMethodContract { } @Test + def mailboxSetCreationShouldHandleRights(server: GuiceJamesServer): Unit = { + 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", + | "create": { + | "C42": { + | "name": "myMailbox", + | "rights": { + | "${ANDRE.asString()}": ["l", "r"] + | } + | } + | } + | }, + | "c1" + | ], + | ["Mailbox/get", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "ids": ["#C42"] + | }, + | "c2"] + | ] + |} + |""".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 + + val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl]) + .getMailboxId("#private", BOB.asString(), "myMailbox") + .serialize() + + assertThatJson(response).isEqualTo( + s"""{ + | "sessionState": "75128aab4b1b", + | "methodResponses": [ + | ["Mailbox/set", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "newState": "000001", + | "created": { + | "C42": { + | "id": "$mailboxId", + | "totalEmails": 0, + | "unreadEmails": 0, + | "totalThreads": 0, + | "unreadThreads": 0, + | "myRights": { + | "mayReadItems": true, + | "mayAddItems": true, + | "mayRemoveItems": true, + | "maySetSeen": true, + | "maySetKeywords": true, + | "mayCreateChild": true, + | "mayRename": true, + | "mayDelete": true, + | "maySubmit": true + | }, + | "isSubscribed": true + | } + | } + | }, "c1"], + | ["Mailbox/get", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "state": "000001", + | "list": [{ + | "id": "$mailboxId", + | "name": "myMailbox", + | "sortOrder": 1000, + | "totalEmails": 0, + | "unreadEmails": 0, + | "totalThreads": 0, + | "unreadThreads": 0, + | "myRights": { + | "mayReadItems": true, + | "mayAddItems": true, + | "mayRemoveItems": true, + | "maySetSeen": true, + | "maySetKeywords": true, + | "mayCreateChild": true, + | "mayRename": true, + | "mayDelete": true, + | "maySubmit": true + | }, + | "isSubscribed": true, + | "namespace":"Personal", + | "rights": { + | "an...@domain.tld": ["l", "r"] + | } + | }], + | "notFound":[] + | }, "c2"] + | ] + |}""".stripMargin) + } + + @Test + def mailboxSetCreationShouldValidateRights(): Unit = { + 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", + | "create": { + | "C42": { + | "name": "myMailbox", + | "rights": { + | "${ANDRE.asString()}": ["invalid"] + | } + | } + | } + | }, + | "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).isEqualTo( + s"""{ + | "sessionState": "75128aab4b1b", + | "methodResponses": [ + | ["Mailbox/set", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "newState": "000001", + | "notCreated": { + | "C42": { + | "type": "invalidArguments", + | "description": "'/rights/an...@domain.tld(0)' property in mailbox object is not valid: Rights must have size 1" + | } + | } + | }, "c1"] + | ] + |}""".stripMargin) + } + + @Test + def mailboxSetCreationShouldValidateRightsWhenWrongValue(): Unit = { + 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", + | "create": { + | "C42": { + | "name": "myMailbox", + | "rights": { + | "${ANDRE.asString()}": ["z"] + | } + | } + | } + | }, + | "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).isEqualTo( + s"""{ + | "sessionState": "75128aab4b1b", + | "methodResponses": [ + | ["Mailbox/set", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "newState": "000001", + | "notCreated": { + | "C42": { + | "type": "invalidArguments", + | "description": "'/rights/an...@domain.tld(0)' property in mailbox object is not valid: Unknown right 'z'" + | } + | } + | }, "c1"] + | ] + |}""".stripMargin) + } + + @Test def mailboxGetShouldAllowTheUseOfCreationIds(server: GuiceJamesServer): Unit = { val request = """ diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala index ad8a19b..131bb41 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala @@ -178,8 +178,24 @@ class Serializer @Inject() (mailboxIdFactory: MailboxId.Factory) { private implicit val mailboxACLWrites: Writes[MailboxACL.Right] = right => JsString(right.asCharacter.toString) private implicit val rightWrites: Writes[Right] = Json.valueWrites[Right] + private implicit val rightRead: Reads[Right] = { + case jsString: JsString => + if (jsString.value.length != 1) { + JsError("Rights must have size 1") + } else { + Right.forChar(jsString.value.charAt(0)) + .map(right => JsSuccess(right)) + .getOrElse(JsError(s"Unknown right '${jsString.value}'")) + } + case _ => JsError("Right must be represented as a String") + } private implicit val rightsWrites: Writes[Rights] = Json.valueWrites[Rights] + private implicit val mapRightsReads: Reads[Map[Username, Seq[Right]]] = _.validate[Map[String, Seq[Right]]] + .map(rawMap => + rawMap.map(entry => (Username.of(entry._1), entry._2))) + private implicit val rightsReads: Reads[Rights] = json => mapRightsReads.reads(json).map(rawMap => Rights(rawMap)) + private implicit def rightsMapWrites(implicit rightWriter: Writes[Seq[Right]]): Writes[Map[Username, Seq[Right]]] = (m: Map[Username, Seq[Right]]) => { m.foldLeft(JsObject.empty)((jsObject, kv) => { 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 2d2c0b7..f50e5ff 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 @@ -50,7 +50,10 @@ object MailboxSetRequest { } case class RemoveEmailsOnDestroy(value: Boolean) extends AnyVal -case class MailboxCreationRequest(name: MailboxName, parentId: Option[UnparsedMailboxId], isSubscribed: Option[IsSubscribed]) +case class MailboxCreationRequest(name: MailboxName, + parentId: Option[UnparsedMailboxId], + isSubscribed: Option[IsSubscribed], + rights: Option[Rights]) case class MailboxPatchObject(value: Map[String, JsObject]) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Rights.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Rights.scala index 3f350fb..dac0ea1 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Rights.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Rights.scala @@ -122,7 +122,7 @@ case object Applicable extends RightsApplicability case object NotApplicable extends RightsApplicability case object Unsupported extends RightsApplicability -case class Rights private(rights: Map[Username, Seq[Right]]) { +case class Rights(rights: Map[Username, Seq[Right]]) { def removeEntriesFor(username: Username) = copy(rights = rights - username) def toMailboxAcl: MailboxACL = { 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 3c7470c..6ac4d5a 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 @@ -182,7 +182,7 @@ class MailboxSetMethod @Inject()(serializer: Serializer, .flatMap(mailboxCreationRequest => resolvePath(mailboxSession, mailboxCreationRequest, processingContext) .flatMap(path => createMailbox(mailboxSession = mailboxSession, path = path, - isSubscribed = mailboxCreationRequest.isSubscribed.getOrElse(IsSubscribed(true))))) + mailboxCreationRequest = mailboxCreationRequest))) .fold(e => CreationFailure(mailboxCreationId, e), mailboxId => { recordCreationIdInProcessingContext(mailboxCreationId, processingContext, mailboxId) @@ -206,13 +206,18 @@ class MailboxSetMethod @Inject()(serializer: Serializer, private def createMailbox(mailboxSession: MailboxSession, path: MailboxPath, - isSubscribed: IsSubscribed): Either[Exception, MailboxId] = { + mailboxCreationRequest: MailboxCreationRequest): Either[Exception, MailboxId] = { try { //can safely do a get as the Optional is empty only if the mailbox name is empty which is forbidden by the type constraint on MailboxName val mailboxId = mailboxManager.createMailbox(path, mailboxSession).get() - if (isSubscribed.value) { + + if (mailboxCreationRequest.isSubscribed.getOrElse(IsSubscribed(true)).value) { subscriptionManager.subscribe(mailboxSession, path.getName) } + + mailboxCreationRequest.rights + .foreach(rights => mailboxManager.setRights(mailboxId, rights.toMailboxAcl.asJava, mailboxSession)) + Right(mailboxId) } catch { case error: Exception => Left(error) --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org