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 019d93b44cbfd9052fa16085b7d8763c9d369763
Author: LanKhuat <[email protected]>
AuthorDate: Tue Aug 18 10:41:33 2020 +0700

    JAMES-3359 Mailbox/set update Rights reset
---
 .../james/jmap/rfc8621/contract/Fixture.scala      |   2 +
 .../contract/MailboxSetMethodContract.scala        | 191 ++++++++++++++++++++-
 .../org/apache/james/jmap/json/Serializer.scala    |   2 +
 .../org/apache/james/jmap/mail/MailboxSet.scala    |  14 +-
 .../james/jmap/method/MailboxSetMethod.scala       |  31 +++-
 5 files changed, 231 insertions(+), 9 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/Fixture.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/Fixture.scala
index 7022a76..71d2358 100644
--- 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/Fixture.scala
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/Fixture.scala
@@ -70,6 +70,8 @@ object Fixture {
   val _2_DOT_DOMAIN: Domain = Domain.of("do.main.tld")
   val BOB: Username = Username.fromLocalPartWithDomain("bob", DOMAIN)
   val ANDRE: Username = Username.fromLocalPartWithDomain("andre", DOMAIN)
+  val CEDRIC: Username = Username.fromLocalPartWithDomain("cedric", DOMAIN)
+  val DAVID: Username = Username.fromLocalPartWithDomain("david", DOMAIN)
   val ALICE: Username = Username.fromLocalPartWithDomain("alice", 
_2_DOT_DOMAIN)
   val BOB_PASSWORD: String = "bobpassword"
   val ANDRE_PASSWORD: String = "andrepassword"
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 a3bc8ca..bf52e3a 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
@@ -31,7 +31,7 @@ import org.apache.http.HttpStatus.SC_OK
 import org.apache.james.GuiceJamesServer
 import org.apache.james.jmap.draft.MessageIdProbe
 import org.apache.james.jmap.http.UserCredential
-import 
org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, 
ANDRE, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
+import org.apache.james.jmap.rfc8621.contract.Fixture._
 import org.apache.james.mailbox.MessageManager.AppendCommand
 import org.apache.james.mailbox.model.MailboxACL.Right
 import org.apache.james.mailbox.model.{MailboxACL, MailboxId, MailboxPath}
@@ -3220,4 +3220,193 @@ trait MailboxSetMethodContract {
     //Should be replaced with JMAP message query when it is available
     
assertThat(server.getProbe(classOf[MessageIdProbe]).getMessages(messageId.getMessageId,
 BOB)).isNotEmpty
   }
+
+  @Test
+  def updateShouldAllowSettingRights(server: GuiceJamesServer): Unit = {
+    val mailboxId = 
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.forUser(BOB,
 "mailbox"))
+    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()}": {
+         |                      "/sharedWith": {
+         |                        "${ANDRE.asString()}":["r", "l"]
+         |                      }
+         |                    }
+         |                }
+         |           },
+         |    "c1"
+         |       ],
+         |       ["Mailbox/get",
+         |         {
+         |           "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |           "ids": ["${mailboxId.serialize()}"]
+         |          },
+         |       "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
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |  "sessionState": "75128aab4b1b",
+         |  "methodResponses": [
+         |    ["Mailbox/set", {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "newState": "000001",
+         |      "updated": {
+         |        "${mailboxId.serialize()}": {}
+         |      }
+         |    }, "c1"],
+         |    ["Mailbox/get", {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "state": "000001",
+         |      "list": [{
+         |        "id": "${mailboxId.serialize()}",
+         |        "name": "mailbox",
+         |        "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": false,
+         |        "namespace": "Personal",
+         |        "rights": {
+         |          "${ANDRE.asString()}": ["l", "r"]
+         |        }
+         |      }],
+         |      "notFound": []
+         |    }, "c2"]
+         |  ]
+         |}""".stripMargin)
+  }
+
+  @Test
+  def updateShouldAllowResettingRights(server: GuiceJamesServer): Unit = {
+    val path = MailboxPath.forUser(BOB, "mailbox")
+    val mailboxId = 
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
+
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(path, ANDRE.asString(), new 
MailboxACL.Rfc4314Rights(Right.Lookup, Right.Administer))
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(path, CEDRIC.asString(), new 
MailboxACL.Rfc4314Rights(Right.Read, 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()}": {
+         |                      "/sharedWith": {
+         |                        "${ANDRE.asString()}":["r", "l"],
+         |                        "${DAVID.asString()}":["r", "l", "w"]
+         |                      }
+         |                    }
+         |                }
+         |           },
+         |    "c1"
+         |       ],
+         |       ["Mailbox/get",
+         |         {
+         |           "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |           "ids": ["${mailboxId.serialize()}"]
+         |          },
+         |       "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
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |  "sessionState": "75128aab4b1b",
+         |  "methodResponses": [
+         |    ["Mailbox/set", {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "newState": "000001",
+         |      "updated": {
+         |        "${mailboxId.serialize()}": {}
+         |      }
+         |    }, "c1"],
+         |    ["Mailbox/get", {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "state": "000001",
+         |      "list": [{
+         |        "id": "${mailboxId.serialize()}",
+         |        "name": "mailbox",
+         |        "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": false,
+         |        "namespace": "Personal",
+         |        "rights": {
+         |          "${ANDRE.asString()}": ["l", "r"],
+         |          "${DAVID.asString()}": ["l", "r", "w"]
+         |        }
+         |      }],
+         |      "notFound": []
+         |    }, "c2"]
+         |  ]
+         |}""".stripMargin)
+  }
 }
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 212e840..2140562 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
@@ -391,4 +391,6 @@ class Serializer @Inject() (mailboxIdFactory: 
MailboxId.Factory) {
   def deserializeMailboxGetRequest(input: JsValue): 
JsResult[MailboxGetRequest] = Json.fromJson[MailboxGetRequest](input)
 
   def deserializeMailboxSetRequest(input: JsValue): 
JsResult[MailboxSetRequest] = Json.fromJson[MailboxSetRequest](input)
+
+  def deserializeRights(input: JsValue): JsResult[Rights] = 
Json.fromJson[Rights](input)
 }
\ No newline at end of file
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 b34af2c..739fa5c 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
@@ -26,6 +26,7 @@ import eu.timepit.refined.boolean.And
 import eu.timepit.refined.collection.NonEmpty
 import eu.timepit.refined.refineV
 import eu.timepit.refined.string.StartsWith
+import org.apache.james.jmap.json.Serializer
 import org.apache.james.jmap.mail.MailboxName.MailboxName
 import org.apache.james.jmap.mail.MailboxPatchObject.MailboxPatchObjectKey
 import org.apache.james.jmap.mail.MailboxSetRequest.{MailboxCreationId, 
UnparsedMailboxId}
@@ -33,7 +34,7 @@ import org.apache.james.jmap.model.AccountId
 import org.apache.james.jmap.model.State.State
 import org.apache.james.mailbox.Role
 import org.apache.james.mailbox.model.MailboxId
-import play.api.libs.json.{JsObject, JsString, JsValue}
+import play.api.libs.json._
 
 case class MailboxSetRequest(accountId: AccountId,
                              ifInState: Option[State],
@@ -65,9 +66,10 @@ object MailboxPatchObject {
 }
 
 case class MailboxPatchObject(value: Map[String, JsValue]) {
-  def updates: Iterable[Either[PatchUpdateValidationException, Update]] = 
value.map({
+  def updates(serializer: Serializer): 
Iterable[Either[PatchUpdateValidationException, Update]] = value.map({
     case (property, newValue) => property match {
       case "/name" => NameUpdate.parse(newValue)
+      case "/sharedWith" => SharedWithResetUpdate.parse(newValue, serializer)
       case property =>
         val refinedKey: Either[String, MailboxPatchObjectKey] = 
refineV(property)
         refinedKey.fold[Either[PatchUpdateValidationException, Update]](
@@ -134,8 +136,16 @@ object NameUpdate {
   }
 }
 
+object SharedWithResetUpdate {
+  def parse(newValue: JsValue, serializer: Serializer): 
Either[PatchUpdateValidationException, Update] = 
serializer.deserializeRights(input = newValue) match {
+    case JsSuccess(value, _) => scala.Right(SharedWithResetUpdate(value))
+    case JsError(errors) => Left(InvalidUpdateException("/sharedWith", 
s"Specified value do not match the expected JSON format: $errors"))
+  }
+}
+
 sealed trait Update
 case class NameUpdate(newName: String) extends Update
+case class SharedWithResetUpdate(rights: Rights) 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 d7bd881..24bebd5 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
@@ -23,10 +23,10 @@ import eu.timepit.refined.auto._
 import javax.inject.Inject
 import org.apache.james.jmap.json.Serializer
 import org.apache.james.jmap.mail.MailboxSetRequest.{MailboxCreationId, 
UnparsedMailboxId}
-import org.apache.james.jmap.mail.{InvalidPropertyException, 
InvalidUpdateException, IsSubscribed, MailboxCreationRequest, 
MailboxCreationResponse, MailboxPatchObject, MailboxRights, MailboxSetError, 
MailboxSetRequest, MailboxSetResponse, MailboxUpdateResponse, NameUpdate, 
PatchUpdateValidationException, Properties, RemoveEmailsOnDestroy, 
SetErrorDescription, SortOrder, TotalEmails, TotalThreads, UnreadEmails, 
UnreadThreads, UnsupportedPropertyUpdatedException}
+import org.apache.james.jmap.mail.{InvalidPropertyException, 
InvalidUpdateException, IsSubscribed, MailboxCreationRequest, 
MailboxCreationResponse, MailboxPatchObject, MailboxRights, MailboxSetError, 
MailboxSetRequest, MailboxSetResponse, MailboxUpdateResponse, NameUpdate, 
PatchUpdateValidationException, Properties, RemoveEmailsOnDestroy, 
SetErrorDescription, SharedWithResetUpdate, SortOrder, TotalEmails, 
TotalThreads, UnreadEmails, UnreadThreads, UnsupportedPropertyUpdatedException}
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
-import org.apache.james.jmap.model.{ClientId, Id, Invocation, ServerId, State}
+import org.apache.james.jmap.model._
 import org.apache.james.jmap.routes.ProcessingContext
 import org.apache.james.mailbox.MailboxManager.RenameOption
 import org.apache.james.mailbox.exception.{InsufficientRightsException, 
MailboxExistsException, MailboxNameException, MailboxNotFoundException}
@@ -180,22 +180,28 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
   private def updateMailbox(mailboxSession: MailboxSession,
                             maiboxId: MailboxId,
                             patch: MailboxPatchObject): SMono[UpdateResult] = {
-    val maybeParseException: Option[PatchUpdateValidationException] = 
patch.updates
+    val updates = patch.updates(serializer)
+    val maybeParseException: Option[PatchUpdateValidationException] = updates
       .flatMap(x => x match {
         case Left(e) => Some(e)
         case _ => None
       }).headOption
 
-    val maybeNameUpdate: Option[NameUpdate] = patch.updates
+    val maybeNameUpdate: Option[NameUpdate] = updates
       .flatMap(x => x match {
         case Right(NameUpdate(newName)) => Some(NameUpdate(newName))
         case _ => None
       }).headOption
 
-    def renameMailbox: SMono[UpdateResult] = updateMailboxPath(maiboxId, 
maybeNameUpdate, mailboxSession)
+    val maybeSharedWithResetUpdate: Option[SharedWithResetUpdate] = updates
+      .flatMap(x => x match {
+        case Right(SharedWithResetUpdate(rights)) => 
Some(SharedWithResetUpdate(rights))
+        case _ => None
+      }).headOption
 
     maybeParseException.map(e => SMono.raiseError[UpdateResult](e))
-      .getOrElse(renameMailbox)
+      .getOrElse(updateMailboxPath(maiboxId, maybeNameUpdate, mailboxSession)
+        .`then`(updateMailboxRights(maiboxId, maybeSharedWithResetUpdate, 
mailboxSession)))
   }
 
   private def updateMailboxPath(mailboxId: MailboxId, maybeNameUpdate: 
Option[NameUpdate], mailboxSession: MailboxSession): SMono[UpdateResult] = {
@@ -216,6 +222,19 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
       .getOrElse(SMono.just[UpdateResult](UpdateSuccess(mailboxId)))
   }
 
+  private def updateMailboxRights(mailboxId: MailboxId,
+                                  maybeSharedWithResetUpdate: 
Option[SharedWithResetUpdate],
+                                  mailboxSession: MailboxSession): 
SMono[UpdateResult] = {
+   maybeSharedWithResetUpdate.map(sharedWithResetUpdate => {
+      SMono.fromCallable(() => {
+        mailboxManager.setRights(mailboxId, 
sharedWithResetUpdate.rights.toMailboxAcl.asJava, mailboxSession)
+      }).`then`(SMono.just[UpdateResult](UpdateSuccess(mailboxId)))
+        .subscribeOn(Schedulers.elastic())
+    })
+      // No updated properties passed. Noop.
+      .getOrElse(SMono.just[UpdateResult](UpdateSuccess(mailboxId)))
+  }
+
   private def computeMailboxPath(mailbox: MessageManager, nameUpdate: 
NameUpdate, mailboxSession: MailboxSession): MailboxPath = {
     val originalPath: MailboxPath = mailbox.getMailboxPath
     val maybeParentPath: Option[MailboxPath] = 
originalPath.getHierarchyLevels(mailboxSession.getPathDelimiter)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to