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 d014a4bc3259505e3c4cd6661b80d96e59278247 Author: Florent Azavant <fazav...@linagora.com> AuthorDate: Thu Nov 7 14:28:26 2024 +0100 [ISSUE-5314] only the `p` right can be granted to `anyone` --- .../contract/MailboxSetMethodContract.scala | 118 +++++++++++++++++++++ .../org/apache/james/jmap/mail/MailboxSet.scala | 39 +++++-- 2 files changed, 147 insertions(+), 10 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 d93c732d31..52b6fb8b3d 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 @@ -6009,6 +6009,64 @@ trait MailboxSetMethodContract { .containsKey(MailboxACL.ANYONE_KEY) } + @Test + def partialRightsUpdateShouldNotGrantAnyoneRightOtherThanPost(server: GuiceJamesServer): Unit = { + val path = MailboxPath.forUser(BOB, "mailbox") + val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path) + + 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/anyone": ["r", "l"] + | } + | } + | }, + | "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) + .whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[0][1].oldState", + "methodResponses[1][1].state") + .isEqualTo( + s"""{ + | "sessionState": "${SESSION_STATE.value}", + | "methodResponses": [ + | ["Mailbox/set", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "notUpdated": { + | "${mailboxId.serialize}": { + | "type": "invalidPatch", + | "description": "only the `Post` right can be granted to the identifier `anyone`" + | } + | } + | }, "c1"] + | ] + |}""".stripMargin) + } + @Test def resetRightsUpdateShouldCorrectlyHandleAnyoneKeyword(server: GuiceJamesServer): Unit = { val path = MailboxPath.forUser(BOB, "mailbox") @@ -6088,6 +6146,66 @@ trait MailboxSetMethodContract { .containsKey(MailboxACL.ANYONE_KEY) } + @Test + def resetRightsUpdateShouldNotGrantAnyoneRightOtherThanPost(server: GuiceJamesServer): Unit = { + val path = MailboxPath.forUser(BOB, "mailbox") + val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path) + + 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": { + | "anyone":["r", "l"] + | } + | } + | } + | }, + | "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) + .whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[0][1].oldState", + "methodResponses[1][1].state") + .isEqualTo( + s"""{ + | "sessionState": "${SESSION_STATE.value}", + | "methodResponses": [ + | ["Mailbox/set", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "notUpdated": { + | "${mailboxId.serialize}": { + | "type": "invalidPatch", + | "description": "only the `Post` right can be granted to the identifier `anyone`" + | } + | } + | }, "c1"] + | ] + |}""".stripMargin) + } + @Test def partialRightsUpdateShouldFailWhenInvalidRights(server: GuiceJamesServer): Unit = { val path = 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 1071271491..ac05d4d91d 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 @@ -33,11 +33,13 @@ import org.apache.james.jmap.json.MailboxSerializer import org.apache.james.jmap.mail.MailboxName.MailboxName import org.apache.james.jmap.mail.MailboxPatchObject.MailboxPatchObjectKey import org.apache.james.jmap.method.{MailboxCreationParseException, SetRequest, WithAccountId} -import org.apache.james.mailbox.model.MailboxACL.EntryKey +import org.apache.james.mailbox.model.MailboxACL.{ANYONE_KEY, ANYONE_NEGATIVE_KEY, EntryKey} import org.apache.james.mailbox.model.{MailboxId, MailboxACL => JavaMailboxACL} import org.apache.james.mailbox.{MailboxSession, Role} import play.api.libs.json.{JsBoolean, JsError, JsNull, JsObject, JsString, JsSuccess, JsValue} +import scala.collection.convert.ImplicitConversions.`collection asJava` + case class MailboxSetRequest(accountId: AccountId, ifInState: Option[UuidState], create: Option[Map[MailboxCreationId, JsObject]], @@ -265,11 +267,29 @@ object NameUpdate { } } +object IsSubscribedUpdate { + def parse(newValue: JsValue): Either[PatchUpdateValidationException, Update] = newValue match { + 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")) + } +} + +object SharedWithAnyoneValidator { + def isValidPatch(entryKey: EntryKey, rights: Seq[Right]): Boolean = { + !((entryKey == ANYONE_KEY || entryKey == ANYONE_NEGATIVE_KEY) && rights.exists(_ != Right.Post))} + + def areValidPatches(rightsMap: Map[EntryKey, Seq[Right]]): Boolean = + rightsMap.forall { case (entryKey, rightsSeq) => isValidPatch(entryKey, rightsSeq)} +} + object SharedWithResetUpdate { def parse(serializer: MailboxSerializer, capabilities: Set[CapabilityIdentifier]) (newValue: JsValue): Either[PatchUpdateValidationException, Update] = if (capabilities.contains(CapabilityIdentifier.JAMES_SHARES)) { serializer.deserializeRights(input = newValue) match { + case JsSuccess(value, _) if !SharedWithAnyoneValidator.areValidPatches(value.rights) => + Left(InvalidPatchException("only the `Post` right can be granted to the identifier `anyone`")) case JsSuccess(value, _) => scala.Right(SharedWithResetUpdate(value)) case JsError(errors) => Left(InvalidUpdateException("sharedWith", s"Specified value do not match the expected JSON format: $errors")) } @@ -278,25 +298,24 @@ object SharedWithResetUpdate { } } -object IsSubscribedUpdate { - def parse(newValue: JsValue): Either[PatchUpdateValidationException, Update] = newValue match { - 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")) - } -} - object SharedWithPartialUpdate { def parse(serializer: MailboxSerializer, capabilities: Set[CapabilityIdentifier]) ( property: String, newValue: JsValue): Either[PatchUpdateValidationException, Update] = if (capabilities.contains(CapabilityIdentifier.JAMES_SHARES)) { parseEntryKey(property) .flatMap(entryKey => parseRights(newValue, property, serializer) - .map(rights => SharedWithPartialUpdate(entryKey, rights))) + .flatMap(rights => createUpdateIfValidPatch(entryKey, rights))) } else { MailboxPatchObject.notFound(property) } + private def createUpdateIfValidPatch(entryKey: EntryKey, rights: Rfc4314Rights): Either[PatchUpdateValidationException, Update] = + if (SharedWithAnyoneValidator.isValidPatch(entryKey, rights.toRights)) { + scala.Right(SharedWithPartialUpdate(entryKey, rights)) + } else { + scala.Left(InvalidPatchException("only the `Post` right can be granted to the identifier `anyone`")) + } + def parseEntryKey(property: String): Either[PatchUpdateValidationException, EntryKey] = try { scala.Right(EntryKey.deserialize(property.substring(MailboxPatchObject.sharedWithPrefix.length))) } catch { --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org