You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2020/08/19 07:44:59 UTC

[james-project] branch master updated: JAMES-3357 Handle rights upon Mailbox/set create

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 <bt...@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": {
+         |					"andre@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/andre@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/andre@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-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org