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