You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2020/08/27 02:24:32 UTC
[james-project] branch master updated (7098d70 -> 5a899ed)
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git.
from 7098d70 JAMES-3359 Reject Mailbox/set updates when capability is omitted
new ee6c8bb JAMES-3359 Mailbox/set parentId updates
new e099ddb JAMES-3359 Mailbox/set update should support name/parentId NOOP
new 1cdd21d JAMES-3359 Mailbox/set update should handle parentId formatting errors
new 76068ab JAMES-3359 Mailbox/set update should handle existing/not found mailboxes
new 6c75d52 JAMES-3359 Mailbox/set updates parentId: test delegation behaviour
new 54aa761 JAMES-3359 Lookup right should be propagated to new parent if needed
new ff10686 JAMES-3359 Mailbox/set name updates should not contain delimiter
new 746efff JAMES-3359 Mailbox/set parentId updates: prevent loops
new 9728174 JAMES-3359 Fix typos in MailboxSetMethodContract
new c1d704b JAMES-3359 Review MailboxSetMethodContract indent
new 54d4e4a JAMES-3359 Simplify tests by applying Mailbox/get property filtering
new b3e78a2 JAMES-3359 Fix some issues in MailboxSetMethodContract for the Distributed version
new 6e58b87 JAMES-3357 Mailbox/set create should reject unknown or server-set properties
new 5a899ed JAMES-3095 Avoid listing all subscriptions for each mailbox
The 14 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../apache/james/mailbox/model/MailboxPath.java | 10 +
.../james/mailbox/model/MailboxPathTest.java | 18 +
.../contract/MailboxSetMethodContract.scala | 1812 ++++++++++++++++++--
.../org/apache/james/jmap/mail/MailboxGet.scala | 2 +
.../org/apache/james/jmap/mail/MailboxSet.scala | 101 +-
.../james/jmap/method/MailboxSetMethod.scala | 128 +-
.../apache/james/jmap/model/MailboxFactory.scala | 9 +-
7 files changed, 1848 insertions(+), 232 deletions(-)
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 11/14: JAMES-3359 Simplify tests by applying
Mailbox/get property filtering
Posted by bt...@apache.org.
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 54d4e4a7c67c32242713ce20ded32f9429f164e7
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Aug 25 10:12:47 2020 +0700
JAMES-3359 Simplify tests by applying Mailbox/get property filtering
---
.../contract/MailboxSetMethodContract.scala | 262 +++------------------
1 file changed, 33 insertions(+), 229 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 28f447e..79f9d7f 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
@@ -5098,6 +5098,7 @@ trait MailboxSetMethodContract {
| ["Mailbox/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "properties": ["id", "rights"],
| "ids": ["${mailboxId.serialize()}"]
| },
| "c2"]
@@ -5134,25 +5135,6 @@ trait MailboxSetMethodContract {
| "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"]
@@ -5502,7 +5484,8 @@ trait MailboxSetMethodContract {
| ["Mailbox/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${mailboxId.serialize()}"]
+ | "ids": ["${mailboxId.serialize()}"],
+ | "properties": ["id", "name", "parentId"]
| },
| "c2"]
| ]
@@ -5539,24 +5522,7 @@ trait MailboxSetMethodContract {
| "list": [{
| "id": "${mailboxId.serialize()}",
| "name": "mailbox",
- | "parentId": "${parentId.serialize()}",
- | "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
+ | "parentId": "${parentId.serialize()}"
| }],
| "notFound": []
| }, "c2"]
@@ -5590,7 +5556,8 @@ trait MailboxSetMethodContract {
| ["Mailbox/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${mailboxId.serialize()}"]
+ | "ids": ["${mailboxId.serialize()}"],
+ | "properties": ["id", "name", "parentId"]
| },
| "c2"]
| ]
@@ -5627,24 +5594,7 @@ trait MailboxSetMethodContract {
| "list": [{
| "id": "${mailboxId.serialize()}",
| "name": "mailbox",
- | "parentId": "${newParentId.serialize()}",
- | "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
+ | "parentId": "${newParentId.serialize()}"
| }],
| "notFound": []
| }, "c2"]
@@ -5678,7 +5628,8 @@ trait MailboxSetMethodContract {
| ["Mailbox/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${mailboxId.serialize()}"]
+ | "ids": ["${mailboxId.serialize()}"],
+ | "properties": ["id", "name", "parentId"]
| },
| "c2"]
| ]
@@ -5715,24 +5666,7 @@ trait MailboxSetMethodContract {
| "list": [{
| "id": "${mailboxId.serialize()}",
| "name": "newName",
- | "parentId": "${parentId.serialize()}",
- | "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
+ | "parentId": "${parentId.serialize()}"
| }],
| "notFound": []
| }, "c2"]
@@ -5767,7 +5701,8 @@ trait MailboxSetMethodContract {
| ["Mailbox/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${mailboxId.serialize()}"]
+ | "ids": ["${mailboxId.serialize()}"],
+ | "properties": ["id", "name", "parentId"]
| },
| "c2"]
| ]
@@ -5804,24 +5739,7 @@ trait MailboxSetMethodContract {
| "list": [{
| "id": "${mailboxId.serialize()}",
| "name": "newName",
- | "parentId": "${newParentId.serialize()}",
- | "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
+ | "parentId": "${newParentId.serialize()}"
| }],
| "notFound": []
| }, "c2"]
@@ -5855,7 +5773,8 @@ trait MailboxSetMethodContract {
| ["Mailbox/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${mailboxId.serialize()}"]
+ | "ids": ["${mailboxId.serialize()}"],
+ | "properties": ["id", "name", "parentId"]
| },
| "c2"]
| ]
@@ -5891,24 +5810,7 @@ trait MailboxSetMethodContract {
| "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
+ | "name": "mailbox"
| }],
| "notFound": []
| }, "c2"]
@@ -5942,7 +5844,8 @@ trait MailboxSetMethodContract {
| ["Mailbox/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${mailboxId.serialize()}"]
+ | "ids": ["${mailboxId.serialize()}"],
+ | "properties": ["id", "name", "parentId"]
| },
| "c2"]
| ]
@@ -5978,24 +5881,7 @@ trait MailboxSetMethodContract {
| "state": "000001",
| "list": [{
| "id": "${mailboxId.serialize()}",
- | "name": "newName",
- | "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
+ | "name": "newName"
| }],
| "notFound": []
| }, "c2"]
@@ -6030,7 +5916,8 @@ trait MailboxSetMethodContract {
| ["Mailbox/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${mailboxId.serialize()}"]
+ | "ids": ["${mailboxId.serialize()}"],
+ | "properties": ["id", "name", "parentId"]
| },
| "c2"]
| ]
@@ -6066,24 +5953,7 @@ trait MailboxSetMethodContract {
| "state": "000001",
| "list": [{
| "id": "${mailboxId.serialize()}",
- | "name": "newName",
- | "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
+ | "name": "newName"
| }],
| "notFound": []
| }, "c2"]
@@ -6115,7 +5985,8 @@ trait MailboxSetMethodContract {
| ["Mailbox/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${mailboxId.serialize()}"]
+ | "ids": ["${mailboxId.serialize()}"],
+ | "properties": ["id", "name", "parentId"]
| },
| "c2"]
| ]
@@ -6151,24 +6022,7 @@ trait MailboxSetMethodContract {
| "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
+ | "name": "mailbox"
| }],
| "notFound": []
| }, "c2"]
@@ -6202,7 +6056,8 @@ trait MailboxSetMethodContract {
| ["Mailbox/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${mailboxId.serialize()}"]
+ | "ids": ["${mailboxId.serialize()}"],
+ | "properties": ["id", "name", "parentId"]
| },
| "c2"]
| ]
@@ -6239,24 +6094,7 @@ trait MailboxSetMethodContract {
| "list": [{
| "id": "${mailboxId.serialize()}",
| "parentId": "${parentId.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
+ | "name": "mailbox"
| }],
| "notFound": []
| }, "c2"]
@@ -6299,7 +6137,8 @@ trait MailboxSetMethodContract {
| ["Mailbox/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${mailboxId.serialize()}"]
+ | "ids": ["${mailboxId.serialize()}"],
+ | "properties": ["id", "name", "parentId"]
| },
| "c3"
| ]
@@ -6366,26 +6205,8 @@ trait MailboxSetMethodContract {
| "state": "000001",
| "list": [{
| "id": "${mailboxId.serialize()}",
- | "sortOrder":1000,
| "name": "mailbox",
- | "parentId": "$parentId",
- | "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
+ | "parentId": "$parentId"
| }],
| "notFound": []
| }, "c3"]
@@ -6895,7 +6716,8 @@ trait MailboxSetMethodContract {
| ["Mailbox/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${parentId.serialize()}"]
+ | "ids": ["${parentId.serialize()}"],
+ | "properties": ["id", "name", "parentId", "rights"]
| },
| "c2"]
| ]
@@ -6932,24 +6754,6 @@ trait MailboxSetMethodContract {
| "list": [{
| "id": "${parentId.serialize()}",
| "name": "parent",
- | "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"]
| }
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 01/14: JAMES-3359 Mailbox/set parentId updates
Posted by bt...@apache.org.
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 ee6c8bb7436ad335f654b723064352296d71fd11
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Aug 20 11:51:11 2020 +0700
JAMES-3359 Mailbox/set parentId updates
---
.../apache/james/mailbox/model/MailboxPath.java | 10 +
.../james/mailbox/model/MailboxPathTest.java | 18 +
.../contract/MailboxSetMethodContract.scala | 625 +++++++++++++++++++++
.../org/apache/james/jmap/mail/MailboxSet.scala | 38 +-
.../james/jmap/method/MailboxSetMethod.scala | 59 +-
5 files changed, 728 insertions(+), 22 deletions(-)
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java
index 4e6b194..29507a4 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java
@@ -34,8 +34,10 @@ import org.apache.james.mailbox.exception.TooLongMailboxNameException;
import com.google.common.base.CharMatcher;
import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
/**
* The path to a mailbox.
@@ -145,6 +147,14 @@ public class MailboxPath {
return levels;
}
+ /**
+ * @return the name of the mailbox, accounting for the delimiter
+ */
+ public String getName(char delimiter) {
+ return Iterables.getLast(Splitter.on(delimiter)
+ .splitToList(name));
+ }
+
public MailboxPath sanitize(char delimiter) {
if (name == null) {
return this;
diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/model/MailboxPathTest.java b/mailbox/api/src/test/java/org/apache/james/mailbox/model/MailboxPathTest.java
index f421ed7..5e4a31f 100644
--- a/mailbox/api/src/test/java/org/apache/james/mailbox/model/MailboxPathTest.java
+++ b/mailbox/api/src/test/java/org/apache/james/mailbox/model/MailboxPathTest.java
@@ -51,6 +51,24 @@ class MailboxPathTest {
}
@Test
+ void getNameShouldReturnSubfolder() {
+ assertThat(MailboxPath.forUser(USER, "inbox.folder.subfolder").getName('.'))
+ .isEqualTo("subfolder");
+ }
+
+ @Test
+ void getNameShouldNoopWhenNoDelimiter() {
+ assertThat(MailboxPath.forUser(USER, "name").getName('.'))
+ .isEqualTo("name");
+ }
+
+ @Test
+ void getNameShouldNoopWhenEmpty() {
+ assertThat(MailboxPath.forUser(USER, "").getName('.'))
+ .isEqualTo("");
+ }
+
+ @Test
void getHierarchyLevelsShouldBeOrdered() {
assertThat(MailboxPath.forUser(USER, "inbox.folder.subfolder")
.getHierarchyLevels('.'))
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 2a0fe99..0c67ce4 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
@@ -5422,4 +5422,629 @@ trait MailboxSetMethodContract {
.getEntries)
.doesNotContainKeys(EntryKey.createUserEntryKey(DAVID))
}
+
+ @Test
+ def updateShouldAllowParentIdChangeWhenTopLevelMailbox(server: GuiceJamesServer): Unit = {
+ val parentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "parent"))
+ val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+ val request =
+ s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": "${parentId.serialize()}"
+ | }
+ | }
+ | },
+ | "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",
+ | "parentId": "${parentId.serialize()}",
+ | "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
+ | }],
+ | "notFound": []
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def updateShouldAllowParentIdChangeWhenChildMailbox(server: GuiceJamesServer): Unit = {
+ val parentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "parent"))
+ val newParentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "newParent"))
+ val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "parent.mailbox"))
+ val request =
+ s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": "${newParentId.serialize()}"
+ | }
+ | }
+ | },
+ | "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",
+ | "parentId": "${newParentId.serialize()}",
+ | "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
+ | }],
+ | "notFound": []
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def updateShouldAllowParentIdChangeWhenTopLevelMailboxAndNewMane(server: GuiceJamesServer): Unit = {
+ val parentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "parent"))
+ val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+ val request =
+ s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": "${parentId.serialize()}",
+ | "/name": "newName"
+ | }
+ | }
+ | },
+ | "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": "newName",
+ | "parentId": "${parentId.serialize()}",
+ | "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
+ | }],
+ | "notFound": []
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def updateShouldAllowParentIdChangeWhenChildMailboxAndNewName(server: GuiceJamesServer): Unit = {
+ val parentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "parent"))
+ val newParentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "newParent"))
+ val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "parent.mailbox"))
+ val request =
+ s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": "${newParentId.serialize()}",
+ | "/name": "newName"
+ | }
+ | }
+ | },
+ | "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": "newName",
+ | "parentId": "${newParentId.serialize()}",
+ | "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
+ | }],
+ | "notFound": []
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def updateShouldAllowParentIdDropWhenChildMailbox(server: GuiceJamesServer): Unit = {
+ val parentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "parent"))
+ val newParentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "newParent"))
+ val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "parent.mailbox"))
+ val request =
+ s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": null
+ | }
+ | }
+ | },
+ | "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
+ | }],
+ | "notFound": []
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def updateShouldAllowParentIdDropWhenTopLevelMailboxAndNewName(server: GuiceJamesServer): Unit = {
+ val parentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "parent"))
+ val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+ val request =
+ s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": null,
+ | "/name": "newName"
+ | }
+ | }
+ | },
+ | "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": "newName",
+ | "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
+ | }],
+ | "notFound": []
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def updateShouldAllowParentIdDropWhenChildMailboxAndNewName(server: GuiceJamesServer): Unit = {
+ val parentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "parent"))
+ val newParentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "newParent"))
+ val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "parent.mailbox"))
+ val request =
+ s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": null,
+ | "/name": "newName"
+ | }
+ | }
+ | },
+ | "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": "newName",
+ | "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
+ | }],
+ | "notFound": []
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
}
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 f3f8db5..6c9ff08 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
@@ -34,6 +34,7 @@ import org.apache.james.jmap.mail.MailboxSetRequest.{MailboxCreationId, Unparsed
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model.State.State
import org.apache.james.jmap.model.{AccountId, CapabilityIdentifier}
+import org.apache.james.jmap.routes.ProcessingContext
import org.apache.james.mailbox.Role
import org.apache.james.mailbox.model.{MailboxId, MailboxACL => JavaMailboxACL}
import play.api.libs.json.{JsBoolean, JsError, JsNull, JsObject, JsString, JsSuccess, JsValue}
@@ -86,8 +87,11 @@ object MailboxPatchObject {
}
case class MailboxPatchObject(value: Map[String, JsValue]) {
- def validate(serializer: Serializer, capabilities: Set[CapabilityIdentifier]): Either[PatchUpdateValidationException, ValidatedMailboxPathObject] = {
- val asUpdatedIterable = updates(serializer, capabilities)
+ def validate(processingContext: ProcessingContext,
+ mailboxIdFactory: MailboxId.Factory,
+ serializer: Serializer,
+ capabilities: Set[CapabilityIdentifier]): Either[PatchUpdateValidationException, ValidatedMailboxPathObject] = {
+ val asUpdatedIterable = updates(serializer, capabilities, processingContext, mailboxIdFactory)
val maybeParseException: Option[PatchUpdateValidationException] = asUpdatedIterable
.flatMap(x => x match {
@@ -113,6 +117,12 @@ case class MailboxPatchObject(value: Map[String, JsValue]) {
case _ => None
}).headOption
+ val parentIdUpdate: Option[ParentIdUpdate] = asUpdatedIterable
+ .flatMap(x => x match {
+ case scala.Right(ParentIdUpdate(newId)) => Some(ParentIdUpdate(newId))
+ case _ => None
+ }).headOption
+
val partialRightsUpdates: Seq[SharedWithPartialUpdate] = asUpdatedIterable.flatMap(x => x match {
case scala.Right(SharedWithPartialUpdate(username, rights)) => Some(SharedWithPartialUpdate(username, rights))
case _ => None
@@ -128,14 +138,16 @@ case class MailboxPatchObject(value: Map[String, JsValue]) {
.map(e => Left(e))
.getOrElse(scala.Right(ValidatedMailboxPathObject(
nameUpdate = nameUpdate,
+ parentIdUpdate = parentIdUpdate,
isSubscribedUpdate = isSubscribedUpdate,
rightsReset = rightsReset,
rightsPartialUpdates = partialRightsUpdates)))
}
- private def updates(serializer: Serializer, capabilities: Set[CapabilityIdentifier]): Iterable[Either[PatchUpdateValidationException, Update]] = value.map({
+ def updates(serializer: Serializer, capabilities: Set[CapabilityIdentifier], processingContext: ProcessingContext, mailboxIdFactory: MailboxId.Factory): Iterable[Either[PatchUpdateValidationException, Update]] = value.map({
case (property, newValue) => property match {
case "/name" => NameUpdate.parse(newValue)
+ case "/parentId" => ParentIdUpdate.parse(newValue, processingContext, mailboxIdFactory)
case "/sharedWith" => SharedWithResetUpdate.parse(serializer, capabilities)(newValue)
case "/role" => Left(ServerSetPropertyException(MailboxPatchObject.roleProperty))
case "/sortOrder" => Left(ServerSetPropertyException(MailboxPatchObject.sortOrderProperty))
@@ -161,9 +173,12 @@ case class MailboxPatchObject(value: Map[String, JsValue]) {
}
case class ValidatedMailboxPathObject(nameUpdate: Option[NameUpdate],
+ parentIdUpdate: Option[ParentIdUpdate],
isSubscribedUpdate: Option[IsSubscribedUpdate],
rightsReset: Option[SharedWithResetUpdate],
- rightsPartialUpdates: Seq[SharedWithPartialUpdate])
+ rightsPartialUpdates: Seq[SharedWithPartialUpdate]) {
+ val shouldUpdateMailboxPath: Boolean = nameUpdate.isDefined || parentIdUpdate.isDefined
+}
case class MailboxSetResponse(accountId: AccountId,
oldState: Option[State],
@@ -284,6 +299,20 @@ object SharedWithPartialUpdate {
}
}
+object ParentIdUpdate {
+ def parse(newValue: JsValue, processingContext: ProcessingContext, mailboxIdFactory: MailboxId.Factory): Either[PatchUpdateValidationException, Update] =
+ newValue match {
+ case JsString(id) =>
+ val value: Either[String, UnparsedMailboxId] = refineV(id)
+ value.fold(error => Left(InvalidUpdateException("/parentId", error)),
+ id => processingContext.resolveMailboxId(id, mailboxIdFactory)
+ .fold(e => Left(InvalidUpdateException("/parentId", e.getMessage)),
+ mailboxId => scala.Right(ParentIdUpdate(Some(mailboxId)))))
+ case JsNull => scala.Right(ParentIdUpdate(None))
+ case _ => Left(InvalidUpdateException("/parentId", "Expecting a JSON string or null as an argument"))
+ }
+}
+
sealed trait Update
case class NameUpdate(newName: String) extends Update
case class SharedWithResetUpdate(rights: Rights) extends Update
@@ -291,6 +320,7 @@ case class IsSubscribedUpdate(isSubscribed: Option[IsSubscribed]) extends Update
case class SharedWithPartialUpdate(username: Username, rights: Rfc4314Rights) extends Update {
def asACLCommand(): JavaMailboxACL.ACLCommand = JavaMailboxACL.command().forUser(username).rights(rights.asJava).asReplacement()
}
+case class ParentIdUpdate(newId: Option[MailboxId]) 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 3613c27..b223b03 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,7 +23,7 @@ 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.{InvalidPatchException, InvalidPropertyException, InvalidUpdateException, IsSubscribed, MailboxCreationRequest, MailboxCreationResponse, MailboxPatchObject, MailboxRights, MailboxSetError, MailboxSetRequest, MailboxSetResponse, MailboxUpdateResponse, NameUpdate, Properties, RemoveEmailsOnDestroy, ServerSetPropertyException, SetErrorDescription, SortOrder, TotalEmails, TotalThreads, UnreadEmails, UnreadThreads, UnsupportedPropertyUpdatedException, Validat [...]
+import org.apache.james.jmap.mail.{InvalidPatchException, InvalidPropertyException, InvalidUpdateException, IsSubscribed, MailboxCreationRequest, MailboxCreationResponse, MailboxPatchObject, MailboxRights, MailboxSetError, MailboxSetRequest, MailboxSetResponse, MailboxUpdateResponse, NameUpdate, ParentIdUpdate, Properties, RemoveEmailsOnDestroy, ServerSetPropertyException, SetErrorDescription, SortOrder, TotalEmails, TotalThreads, UnreadEmails, UnreadThreads, UnsupportedPropertyUpdatedEx [...]
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}
@@ -162,7 +162,7 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
case (unparsedMailboxId: UnparsedMailboxId, patch: MailboxPatchObject) =>
processingContext.resolveMailboxId(unparsedMailboxId, mailboxIdFactory).fold(
e => SMono.just(UpdateFailure(unparsedMailboxId, e)),
- mailboxId => updateMailbox(mailboxSession, mailboxId, patch, capabilities))
+ mailboxId => updateMailbox(mailboxSession, processingContext, mailboxId, patch, capabilities))
.onErrorResume(e => SMono.just(UpdateFailure(unparsedMailboxId, e)))
})
.collectSeq()
@@ -170,10 +170,11 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
}
private def updateMailbox(mailboxSession: MailboxSession,
+ processingContext: ProcessingContext,
mailboxId: MailboxId,
patch: MailboxPatchObject,
capabilities: Set[CapabilityIdentifier]): SMono[UpdateResult] = {
- patch.validate(serializer, capabilities)
+ patch.validate(processingContext, mailboxIdFactory, serializer, capabilities)
.fold(e => SMono.raiseError(e), validatedPatch =>
updateMailboxPath(mailboxId, validatedPatch, mailboxSession)
.`then`(updateMailboxRights(mailboxId, validatedPatch, mailboxSession))
@@ -199,21 +200,53 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
}
private def updateMailboxPath(mailboxId: MailboxId, validatedPatch: ValidatedMailboxPathObject, mailboxSession: MailboxSession): SMono[UpdateResult] = {
- validatedPatch.nameUpdate.map(nameUpdate => {
+ if (validatedPatch.shouldUpdateMailboxPath) {
SMono.fromCallable(() => {
val mailbox = mailboxManager.getMailbox(mailboxId, mailboxSession)
if (isASystemMailbox(mailbox)) {
throw SystemMailboxChangeException(mailboxId)
}
+ val newPath = applyNameUpdate(validatedPatch.nameUpdate, mailboxSession)
+ .andThen(applyParentIdUpdate(validatedPatch.parentIdUpdate, mailboxSession))
+ .apply(mailbox.getMailboxPath)
mailboxManager.renameMailbox(mailboxId,
- computeMailboxPath(mailbox, nameUpdate, mailboxSession),
+ newPath,
RenameOption.RENAME_SUBSCRIPTIONS,
mailboxSession)
}).`then`(SMono.just[UpdateResult](UpdateSuccess(mailboxId)))
.subscribeOn(Schedulers.elastic())
- })
- // No updated properties passed. Noop.
- .getOrElse(SMono.just[UpdateResult](UpdateSuccess(mailboxId)))
+ } else {
+ SMono.just[UpdateResult](UpdateSuccess(mailboxId))
+ }
+ }
+
+ private def applyParentIdUpdate(maybeParentIdUpdate: Option[ParentIdUpdate], mailboxSession: MailboxSession): MailboxPath => MailboxPath = {
+ maybeParentIdUpdate.map(parentIdUpdate => applyParentIdUpdate(parentIdUpdate, mailboxSession))
+ .getOrElse(x => x)
+ }
+
+ private def applyNameUpdate(maybeNameUpdate: Option[NameUpdate], mailboxSession: MailboxSession): MailboxPath => MailboxPath = {
+ originalPath => maybeNameUpdate.map(nameUpdate => {
+ val originalParentPath: Option[MailboxPath] = originalPath.getHierarchyLevels(mailboxSession.getPathDelimiter)
+ .asScala
+ .reverse
+ .drop(1)
+ .headOption
+ originalParentPath.map(_.child(nameUpdate.newName, mailboxSession.getPathDelimiter))
+ .getOrElse(MailboxPath.forUser(mailboxSession.getUser, nameUpdate.newName))
+ }).getOrElse(originalPath)
+ }
+
+ private def applyParentIdUpdate(parentIdUpdate: ParentIdUpdate, mailboxSession: MailboxSession): MailboxPath => MailboxPath = {
+ originalPath => {
+ val currentName = originalPath.getName(mailboxSession.getPathDelimiter)
+ parentIdUpdate.newId
+ .map(id => {
+ val parentPath = mailboxManager.getMailbox(id, mailboxSession).getMailboxPath
+ parentPath.child(currentName, mailboxSession.getPathDelimiter)
+ })
+ .getOrElse(MailboxPath.forUser(originalPath.getUser, currentName))
+ }
}
private def updateMailboxRights(mailboxId: MailboxId,
@@ -239,16 +272,6 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
}
- private def computeMailboxPath(mailbox: MessageManager, nameUpdate: NameUpdate, mailboxSession: MailboxSession): MailboxPath = {
- val originalPath: MailboxPath = mailbox.getMailboxPath
- val maybeParentPath: Option[MailboxPath] = originalPath.getHierarchyLevels(mailboxSession.getPathDelimiter)
- .asScala
- .reverse
- .drop(1)
- .headOption
- maybeParentPath.map(_.child(nameUpdate.newName, mailboxSession.getPathDelimiter))
- .getOrElse(MailboxPath.forUser(mailboxSession.getUser, nameUpdate.newName))
- }
private def deleteMailboxes(mailboxSession: MailboxSession, mailboxSetRequest: MailboxSetRequest, processingContext: ProcessingContext): SMono[DeletionResults] = {
SFlux.fromIterable(mailboxSetRequest.destroy.getOrElse(Seq()))
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 06/14: JAMES-3359 Lookup right should be propagated
to new parent if needed
Posted by bt...@apache.org.
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 54aa76130e8d71111382b7bc8ed24cab77ae92f8
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Aug 20 14:32:16 2020 +0700
JAMES-3359 Lookup right should be propagated to new parent if needed
---
.../contract/MailboxSetMethodContract.scala | 95 ++++++++++++++++++++++
1 file changed, 95 insertions(+)
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 e388765..3d2a337 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
@@ -6821,4 +6821,99 @@ trait MailboxSetMethodContract {
| ]
|}""".stripMargin)
}
+
+ @Test
+ def lookupRightShouldBePropagated(server: GuiceJamesServer): Unit = {
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ val path = MailboxPath.forUser(BOB, "mailbox")
+ val mailboxId: MailboxId = mailboxProbe.createMailbox(path)
+ val parentPath = MailboxPath.forUser(BOB, "parent")
+ val parentId = mailboxProbe.createMailbox(parentPath)
+
+ val aCLProbeImpl = server.getProbe(classOf[ACLProbeImpl])
+ aCLProbeImpl.replaceRights(path, ANDRE.asString, MailboxACL.FULL_RIGHTS)
+
+ 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()}": {
+ | "/parentId": "${parentId.serialize()}"
+ | }
+ | }
+ | },
+ | "c1"
+ | ],
+ | ["Mailbox/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${parentId.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": "${parentId.serialize()}",
+ | "name": "parent",
+ | "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"]
+ | }
+ | }],
+ | "notFound": []
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 02/14: JAMES-3359 Mailbox/set update should support
name/parentId NOOP
Posted by bt...@apache.org.
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 e099ddb8e3fa43ed2f01ad5450dcd5043fd7491c
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Aug 20 11:55:34 2020 +0700
JAMES-3359 Mailbox/set update should support name/parentId NOOP
---
.../contract/MailboxSetMethodContract.scala | 173 +++++++++++++++++++++
.../james/jmap/method/MailboxSetMethod.scala | 13 +-
2 files changed, 181 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 0c67ce4..871345f 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
@@ -6047,4 +6047,177 @@ trait MailboxSetMethodContract {
| ]
|}""".stripMargin)
}
+
+ @Test
+ def updateShouldAllowNameNoop(server: GuiceJamesServer): Unit = {
+ val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+ val request =
+ s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/name": "mailbox"
+ | }
+ | }
+ | },
+ | "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
+ | }],
+ | "notFound": []
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def updateShouldAllowParentIdNoop(server: GuiceJamesServer): Unit = {
+ val parentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "parent"))
+ val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "parent.mailbox"))
+ val request =
+ s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": "${parentId.serialize()}"
+ | }
+ | }
+ | },
+ | "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()}",
+ | "parentId": "${parentId.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
+ | }],
+ | "notFound": []
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
}
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 b223b03..3e85d105 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
@@ -206,13 +206,16 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
if (isASystemMailbox(mailbox)) {
throw SystemMailboxChangeException(mailboxId)
}
+ val oldPath = mailbox.getMailboxPath
val newPath = applyNameUpdate(validatedPatch.nameUpdate, mailboxSession)
.andThen(applyParentIdUpdate(validatedPatch.parentIdUpdate, mailboxSession))
- .apply(mailbox.getMailboxPath)
- mailboxManager.renameMailbox(mailboxId,
- newPath,
- RenameOption.RENAME_SUBSCRIPTIONS,
- mailboxSession)
+ .apply(oldPath)
+ if (!oldPath.equals(newPath)) {
+ mailboxManager.renameMailbox(mailboxId,
+ newPath,
+ RenameOption.RENAME_SUBSCRIPTIONS,
+ mailboxSession)
+ }
}).`then`(SMono.just[UpdateResult](UpdateSuccess(mailboxId)))
.subscribeOn(Schedulers.elastic())
} else {
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 04/14: JAMES-3359 Mailbox/set update should handle
existing/not found mailboxes
Posted by bt...@apache.org.
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 76068ab22c014b1ca5dd6a8ea6d65da324c4d499
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Aug 20 13:55:17 2020 +0700
JAMES-3359 Mailbox/set update should handle existing/not found mailboxes
---
.../contract/MailboxSetMethodContract.scala | 175 +++++++++++++++++++++
.../org/apache/james/jmap/mail/MailboxGet.scala | 2 +
.../org/apache/james/jmap/mail/MailboxSet.scala | 25 ++-
.../james/jmap/method/MailboxSetMethod.scala | 71 +++++----
4 files changed, 237 insertions(+), 36 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 07a7ed9..25760d5 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
@@ -6461,4 +6461,179 @@ trait MailboxSetMethodContract {
| ]
|}""".stripMargin)
}
+
+ @Test
+ def updateShouldFailWhenParentIdNotFound(server: GuiceJamesServer): Unit = {
+ val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+ val parentId = randomMailboxId
+ val request = s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": "${parentId.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",
+ | "notUpdated": {
+ | "${mailboxId.serialize()}": {
+ | "type": "notFound",
+ | "description": "${parentId.serialize()} can not be found"
+ | }
+ | }
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def updateShouldFailWhenTargetMailboxAlreadyExists(server: GuiceJamesServer): Unit = {
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ val mailboxId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+ val parentId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent"))
+ mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent.mailbox"))
+ val request = s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": "${parentId.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",
+ | "notUpdated": {
+ | "${mailboxId.serialize()}": {
+ | "type": "invalidArguments",
+ | "description": "Mailbox with name=#private:bob@domain.tld:parent.mailbox already exists.",
+ | "properties":["/parentId"]
+ | }
+ | }
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def updateShouldFailWhenTargetMailboxAlreadyExistsAndBothPropertiesSpecified(server: GuiceJamesServer): Unit = {
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ val mailboxId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+ val parentId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent"))
+ mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent.newMailbox"))
+ val request = s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": "${parentId.serialize()}",
+ | "/name": "newMailbox"
+ | }
+ | }
+ | },
+ | "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
+
+ println(response)
+
+ assertThatJson(response).isEqualTo(
+ s"""{
+ | "sessionState": "75128aab4b1b",
+ | "methodResponses": [
+ | ["Mailbox/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "newState": "000001",
+ | "notUpdated": {
+ | "${mailboxId.serialize()}": {
+ | "type": "invalidArguments",
+ | "description": "Mailbox with name=#private:bob@domain.tld:parent.newMailbox already exists.",
+ | "properties":["/name", "/parentId"]
+ | }
+ | }
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala
index 1169652..1fde4e0 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala
@@ -28,6 +28,8 @@ case class Ids(value: List[UnparsedMailboxId])
case class Properties(value: List[NonEmptyString]) {
def asSetOfString: Set[String] = value.map(_.toString()).toSet
+
+ def intersect(properties: Properties): Properties = Properties(value.toSet.intersect(properties.value.toSet).toList)
}
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 6c9ff08..3bc5fac 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 eu.timepit.refined.types.string.NonEmptyString
import org.apache.james.core.Username
import org.apache.james.jmap.json.Serializer
import org.apache.james.jmap.mail.MailboxName.MailboxName
@@ -90,7 +91,7 @@ case class MailboxPatchObject(value: Map[String, JsValue]) {
def validate(processingContext: ProcessingContext,
mailboxIdFactory: MailboxId.Factory,
serializer: Serializer,
- capabilities: Set[CapabilityIdentifier]): Either[PatchUpdateValidationException, ValidatedMailboxPathObject] = {
+ capabilities: Set[CapabilityIdentifier]): Either[PatchUpdateValidationException, ValidatedMailboxPatchObject] = {
val asUpdatedIterable = updates(serializer, capabilities, processingContext, mailboxIdFactory)
val maybeParseException: Option[PatchUpdateValidationException] = asUpdatedIterable
@@ -136,7 +137,7 @@ case class MailboxPatchObject(value: Map[String, JsValue]) {
maybeParseException
.orElse(bothPartialAndResetRights)
.map(e => Left(e))
- .getOrElse(scala.Right(ValidatedMailboxPathObject(
+ .getOrElse(scala.Right(ValidatedMailboxPatchObject(
nameUpdate = nameUpdate,
parentIdUpdate = parentIdUpdate,
isSubscribedUpdate = isSubscribedUpdate,
@@ -172,12 +173,22 @@ case class MailboxPatchObject(value: Map[String, JsValue]) {
}
}
-case class ValidatedMailboxPathObject(nameUpdate: Option[NameUpdate],
- parentIdUpdate: Option[ParentIdUpdate],
- isSubscribedUpdate: Option[IsSubscribedUpdate],
- rightsReset: Option[SharedWithResetUpdate],
- rightsPartialUpdates: Seq[SharedWithPartialUpdate]) {
+object ValidatedMailboxPatchObject {
+ val nameProperty: NonEmptyString = "/name"
+ val parentIdProperty: NonEmptyString = "/parentId"
+}
+
+case class ValidatedMailboxPatchObject(nameUpdate: Option[NameUpdate],
+ parentIdUpdate: Option[ParentIdUpdate],
+ isSubscribedUpdate: Option[IsSubscribedUpdate],
+ rightsReset: Option[SharedWithResetUpdate],
+ rightsPartialUpdates: Seq[SharedWithPartialUpdate]) {
val shouldUpdateMailboxPath: Boolean = nameUpdate.isDefined || parentIdUpdate.isDefined
+
+ val updatedProperties: Properties = Properties(List(
+ nameUpdate.map(_ => ValidatedMailboxPatchObject.nameProperty),
+ parentIdUpdate.map(_ => ValidatedMailboxPatchObject.parentIdProperty))
+ .flatMap(_.toList))
}
case class MailboxSetResponse(accountId: AccountId,
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 3e85d105..2a22946 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,7 +23,7 @@ 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.{InvalidPatchException, InvalidPropertyException, InvalidUpdateException, IsSubscribed, MailboxCreationRequest, MailboxCreationResponse, MailboxPatchObject, MailboxRights, MailboxSetError, MailboxSetRequest, MailboxSetResponse, MailboxUpdateResponse, NameUpdate, ParentIdUpdate, Properties, RemoveEmailsOnDestroy, ServerSetPropertyException, SetErrorDescription, SortOrder, TotalEmails, TotalThreads, UnreadEmails, UnreadThreads, UnsupportedPropertyUpdatedEx [...]
+import org.apache.james.jmap.mail.{InvalidPatchException, InvalidPropertyException, InvalidUpdateException, IsSubscribed, MailboxCreationRequest, MailboxCreationResponse, MailboxPatchObject, MailboxRights, MailboxSetError, MailboxSetRequest, MailboxSetResponse, MailboxUpdateResponse, NameUpdate, ParentIdUpdate, Properties, RemoveEmailsOnDestroy, ServerSetPropertyException, SetErrorDescription, SortOrder, TotalEmails, TotalThreads, UnreadEmails, UnreadThreads, UnsupportedPropertyUpdatedEx [...]
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}
@@ -106,17 +106,21 @@ case class DeletionResults(results: Seq[DeletionResult]) {
sealed trait UpdateResult
case class UpdateSuccess(mailboxId: MailboxId) extends UpdateResult
-case class UpdateFailure(mailboxId: UnparsedMailboxId, exception: Throwable) extends UpdateResult {
+case class UpdateFailure(mailboxId: UnparsedMailboxId, exception: Throwable, patch: Option[ValidatedMailboxPatchObject]) extends UpdateResult {
+ def filter(acceptableProperties: Properties): Option[Properties] = Some(patch
+ .map(_.updatedProperties.intersect(acceptableProperties))
+ .getOrElse(acceptableProperties))
+
def asMailboxSetError: MailboxSetError = exception match {
case e: MailboxNotFoundException => MailboxSetError.notFound(Some(SetErrorDescription(e.getMessage)))
- case e: MailboxNameException => MailboxSetError.invalidArgument(Some(SetErrorDescription(e.getMessage)), Some(Properties(List("/name"))))
- case e: MailboxExistsException => MailboxSetError.invalidArgument(Some(SetErrorDescription(e.getMessage)), Some(Properties(List("/name"))))
+ case e: MailboxNameException => MailboxSetError.invalidArgument(Some(SetErrorDescription(e.getMessage)), filter(Properties(List("/name", "/parentId"))))
+ case e: MailboxExistsException => MailboxSetError.invalidArgument(Some(SetErrorDescription(e.getMessage)), filter(Properties(List("/name", "/parentId"))))
case e: UnsupportedPropertyUpdatedException => MailboxSetError.invalidArgument(Some(SetErrorDescription(s"${e.property} property do not exist thus cannot be updated")), Some(Properties(List(e.property))))
case e: InvalidUpdateException => MailboxSetError.invalidArgument(Some(SetErrorDescription(s"${e.cause}")), Some(Properties(List(e.property))))
case e: ServerSetPropertyException => MailboxSetError.invalidArgument(Some(SetErrorDescription("Can not modify server-set properties")), Some(Properties(List(e.property))))
case e: InvalidPropertyException => MailboxSetError.invalidPatch(Some(SetErrorDescription(s"${e.cause}")))
case e: InvalidPatchException => MailboxSetError.invalidPatch(Some(SetErrorDescription(s"${e.cause}")))
- case e: SystemMailboxChangeException => MailboxSetError.invalidArgument(Some(SetErrorDescription("Invalid change to a system mailbox")), Some(Properties(List("/name"))))
+ case e: SystemMailboxChangeException => MailboxSetError.invalidArgument(Some(SetErrorDescription("Invalid change to a system mailbox")), filter(Properties(List("/name", "parentId"))))
case e: InsufficientRightsException => MailboxSetError.invalidArgument(Some(SetErrorDescription("Invalid change to a delegated mailbox")), None)
case _ => MailboxSetError.serverFail(Some(SetErrorDescription(exception.getMessage)), None)
}
@@ -161,9 +165,9 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
.flatMap({
case (unparsedMailboxId: UnparsedMailboxId, patch: MailboxPatchObject) =>
processingContext.resolveMailboxId(unparsedMailboxId, mailboxIdFactory).fold(
- e => SMono.just(UpdateFailure(unparsedMailboxId, e)),
- mailboxId => updateMailbox(mailboxSession, processingContext, mailboxId, patch, capabilities))
- .onErrorResume(e => SMono.just(UpdateFailure(unparsedMailboxId, e)))
+ e => SMono.just(UpdateFailure(unparsedMailboxId, e, None)),
+ mailboxId => updateMailbox(mailboxSession, processingContext, mailboxId, unparsedMailboxId, patch, capabilities))
+ .onErrorResume(e => SMono.just(UpdateFailure(unparsedMailboxId, e, None)))
})
.collectSeq()
.map(UpdateResults)
@@ -172,16 +176,17 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
private def updateMailbox(mailboxSession: MailboxSession,
processingContext: ProcessingContext,
mailboxId: MailboxId,
+ unparsedMailboxId: UnparsedMailboxId,
patch: MailboxPatchObject,
capabilities: Set[CapabilityIdentifier]): SMono[UpdateResult] = {
patch.validate(processingContext, mailboxIdFactory, serializer, capabilities)
.fold(e => SMono.raiseError(e), validatedPatch =>
- updateMailboxPath(mailboxId, validatedPatch, mailboxSession)
- .`then`(updateMailboxRights(mailboxId, validatedPatch, mailboxSession))
- .`then`(updateSubscription(mailboxId, validatedPatch, mailboxSession)))
+ updateMailboxRights(mailboxId, validatedPatch, mailboxSession)
+ .`then`(updateSubscription(mailboxId, validatedPatch, mailboxSession))
+ .`then`(updateMailboxPath(mailboxId, unparsedMailboxId, validatedPatch, mailboxSession)))
}
- private def updateSubscription(mailboxId: MailboxId, validatedPatch: ValidatedMailboxPathObject, mailboxSession: MailboxSession): SMono[UpdateResult] = {
+ private def updateSubscription(mailboxId: MailboxId, validatedPatch: ValidatedMailboxPatchObject, mailboxSession: MailboxSession): SMono[UpdateResult] = {
validatedPatch.isSubscribedUpdate.map(isSubscribedUpdate => {
SMono.fromCallable(() => {
val mailbox = mailboxManager.getMailbox(mailboxId, mailboxSession)
@@ -199,24 +204,32 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
.getOrElse(SMono.just[UpdateResult](UpdateSuccess(mailboxId)))
}
- private def updateMailboxPath(mailboxId: MailboxId, validatedPatch: ValidatedMailboxPathObject, mailboxSession: MailboxSession): SMono[UpdateResult] = {
+ private def updateMailboxPath(mailboxId: MailboxId,
+ unparsedMailboxId: UnparsedMailboxId,
+ validatedPatch: ValidatedMailboxPatchObject,
+ mailboxSession: MailboxSession): SMono[UpdateResult] = {
if (validatedPatch.shouldUpdateMailboxPath) {
- SMono.fromCallable(() => {
- val mailbox = mailboxManager.getMailbox(mailboxId, mailboxSession)
- if (isASystemMailbox(mailbox)) {
- throw SystemMailboxChangeException(mailboxId)
+ SMono.fromCallable[UpdateResult](() => {
+ try {
+ val mailbox = mailboxManager.getMailbox(mailboxId, mailboxSession)
+ if (isASystemMailbox(mailbox)) {
+ throw SystemMailboxChangeException(mailboxId)
+ }
+ val oldPath = mailbox.getMailboxPath
+ val newPath = applyNameUpdate(validatedPatch.nameUpdate, mailboxSession)
+ .andThen(applyParentIdUpdate(validatedPatch.parentIdUpdate, mailboxSession))
+ .apply(oldPath)
+ if (!oldPath.equals(newPath)) {
+ mailboxManager.renameMailbox(mailboxId,
+ newPath,
+ RenameOption.RENAME_SUBSCRIPTIONS,
+ mailboxSession)
+ }
+ UpdateSuccess(mailboxId)
+ } catch {
+ case e: Exception => UpdateFailure(unparsedMailboxId, e, Some(validatedPatch))
}
- val oldPath = mailbox.getMailboxPath
- val newPath = applyNameUpdate(validatedPatch.nameUpdate, mailboxSession)
- .andThen(applyParentIdUpdate(validatedPatch.parentIdUpdate, mailboxSession))
- .apply(oldPath)
- if (!oldPath.equals(newPath)) {
- mailboxManager.renameMailbox(mailboxId,
- newPath,
- RenameOption.RENAME_SUBSCRIPTIONS,
- mailboxSession)
- }
- }).`then`(SMono.just[UpdateResult](UpdateSuccess(mailboxId)))
+ })
.subscribeOn(Schedulers.elastic())
} else {
SMono.just[UpdateResult](UpdateSuccess(mailboxId))
@@ -253,7 +266,7 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
}
private def updateMailboxRights(mailboxId: MailboxId,
- validatedPatch: ValidatedMailboxPathObject,
+ validatedPatch: ValidatedMailboxPatchObject,
mailboxSession: MailboxSession): SMono[UpdateResult] = {
val resetOperation: SMono[Unit] = validatedPatch.rightsReset.map(sharedWithResetUpdate => {
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 12/14: JAMES-3359 Fix some issues in
MailboxSetMethodContract for the Distributed version
Posted by bt...@apache.org.
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 b3e78a28fa23a4780f6a9745e046850c1f5d10aa
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Wed Aug 26 11:25:04 2020 +0700
JAMES-3359 Fix some issues in MailboxSetMethodContract for the Distributed version
---
.../jmap/rfc8621/contract/MailboxSetMethodContract.scala | 14 +++++++-------
1 file changed, 7 insertions(+), 7 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 79f9d7f..50434e1 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
@@ -4577,7 +4577,7 @@ trait MailboxSetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "newState": "000001",
| "notUpdated": {
- | "1": {
+ | "${mailboxId.serialize}": {
| "type": "invalidArguments",
| "description": "/sharedWith property do not exist thus cannot be updated",
| "properties": ["/sharedWith"]
@@ -4637,7 +4637,7 @@ trait MailboxSetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "newState": "000001",
| "notUpdated": {
- | "1": {
+ | "${mailboxId.serialize}": {
| "type": "invalidArguments",
| "description": "/sharedWith/${ANDRE.asString()} property do not exist thus cannot be updated",
| "properties": ["/sharedWith/${ANDRE.asString()}"]
@@ -4697,7 +4697,7 @@ trait MailboxSetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "newState": "000001",
| "notUpdated": {
- | "1": {
+ | "${mailboxId.serialize}": {
| "type": "invalidArguments",
| "description": "/quotas property do not exist thus cannot be updated",
| "properties": ["/quotas"]
@@ -6259,7 +6259,7 @@ trait MailboxSetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "newState": "000001",
| "notUpdated": {
- | "1": {
+ | "${mailboxId.serialize}": {
| "type": "invalidArguments",
| "description": "Expecting a JSON string or null as an argument",
| "properties": ["/parentId"]
@@ -6315,7 +6315,7 @@ trait MailboxSetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "newState": "000001",
| "notUpdated": {
- | "1": {
+ | "${mailboxId.serialize}": {
| "type": "invalidArguments",
| "description": "ClientId(#C42) was not used in previously defined creationIds",
| "properties": ["/parentId"]
@@ -6812,9 +6812,9 @@ trait MailboxSetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "newState": "000001",
| "notUpdated": {
- | "1": {
+ | "${mailboxId.serialize}": {
| "type": "invalidArguments",
- | "description": "1 parentId property cannot be updated as this mailbox has child mailboxes",
+ | "description": "${mailboxId.serialize} parentId property cannot be updated as this mailbox has child mailboxes",
| "properties": ["parentId"]
| }
| }
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 13/14: JAMES-3357 Mailbox/set create should reject
unknown or server-set properties
Posted by bt...@apache.org.
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 6e58b8789ce700e6db70988202e6f851c298160a
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Aug 25 14:27:44 2020 +0700
JAMES-3357 Mailbox/set create should reject unknown or server-set properties
---
.../contract/MailboxSetMethodContract.scala | 62 +++++++++++++++++++++-
.../org/apache/james/jmap/mail/MailboxSet.scala | 27 ++++++++++
.../james/jmap/method/MailboxSetMethod.scala | 9 ++--
3 files changed, 92 insertions(+), 6 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 50434e1..8805072 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
@@ -657,7 +657,6 @@ trait MailboxSetMethodContract {
}
@Test
- @Disabled("should we support that? Anyway seems hard with Play-JSON")
def mailboxSetShouldReturnNotCreatedWhenUnknownParameter(): Unit = {
val request =
"""
@@ -706,7 +705,66 @@ trait MailboxSetMethodContract {
| "notCreated": {
| "C42": {
| "type": "invalidArguments",
- | "description": "Unknown 'unknown' property in mailbox object"
+ | "description": "Some unknown properties were specified",
+ | "properties": ["unknown"]
+ | }
+ | }
+ | },
+ | "c1"]]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def mailboxSetShouldReturnNotCreatedWhenServerSetParameter(): Unit = {
+ val request =
+ """
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "create": {
+ | "C42": {
+ | "name": "plop",
+ | "id": "what?"
+ | }
+ | }
+ | },
+ | "c1"
+ | ]
+ | ]
+ |}
+ |""".stripMargin
+
+ val response: String =
+ `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": "Some server-set properties were specified",
+ | "properties": ["id"]
| }
| }
| },
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 73fc849..d1620aa 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
@@ -32,6 +32,7 @@ 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}
+import org.apache.james.jmap.method.MailboxCreationParseException
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model.State.State
import org.apache.james.jmap.model.{AccountId, CapabilityIdentifier}
@@ -59,6 +60,32 @@ object MailboxSetRequest {
}
case class RemoveEmailsOnDestroy(value: Boolean) extends AnyVal
+
+object MailboxCreationRequest {
+ private val serverSetProperty = Set("id", "sortOrder", "role", "totalEmails", "totalThreads", "unreadEmails", "unreadThreads", "myRights")
+ private val assignableProperties = Set("name", "parentId", "isSubscribed", "rights")
+ private val knownProperties = assignableProperties ++ serverSetProperty
+
+ def validateProperties(jsObject: JsObject): Either[MailboxCreationParseException, JsObject] =
+ (jsObject.keys.intersect(serverSetProperty), jsObject.keys.diff(knownProperties)) match {
+ case (_, unknownProperties) if unknownProperties.nonEmpty =>
+ Left(MailboxCreationParseException(MailboxSetError.invalidArgument(
+ Some(SetErrorDescription("Some unknown properties were specified")),
+ Some(toProperties(unknownProperties.toList)))))
+ case (specifiedServerSetProperties, _) if specifiedServerSetProperties.nonEmpty =>
+ Left(MailboxCreationParseException(MailboxSetError.invalidArgument(
+ Some(SetErrorDescription("Some server-set properties were specified")),
+ Some(toProperties(specifiedServerSetProperties.toList)))))
+ case _ => scala.Right(jsObject)
+ }
+
+ private def toProperties(strings: List[String]): Properties = Properties(strings
+ .flatMap(string => {
+ val refinedValue: Either[String, NonEmptyString] = refineV[NonEmpty](string)
+ refinedValue.fold(_ => None, Some(_))
+ }))
+}
+
case class MailboxCreationRequest(name: MailboxName,
parentId: Option[UnparsedMailboxId],
isSubscribed: Option[IsSubscribed],
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 a412f8a..3f532d9 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
@@ -369,10 +369,11 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
}
private def parseCreate(jsObject: JsObject): Either[MailboxCreationParseException, MailboxCreationRequest] =
- Json.fromJson(jsObject)(serializer.mailboxCreationRequest) match {
- case JsSuccess(creationRequest, _) => Right(creationRequest)
- case JsError(errors) => Left(MailboxCreationParseException(mailboxSetError(errors)))
- }
+ MailboxCreationRequest.validateProperties(jsObject)
+ .flatMap(validJsObject => Json.fromJson(validJsObject)(serializer.mailboxCreationRequest) match {
+ case JsSuccess(creationRequest, _) => Right(creationRequest)
+ case JsError(errors) => Left(MailboxCreationParseException(mailboxSetError(errors)))
+ })
private def mailboxSetError(errors: collection.Seq[(JsPath, collection.Seq[JsonValidationError])]): MailboxSetError =
errors.head match {
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 07/14: JAMES-3359 Mailbox/set name updates should
not contain delimiter
Posted by bt...@apache.org.
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 ff10686887debdd0936e9001a52215cd8883fced
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Aug 20 14:45:23 2020 +0700
JAMES-3359 Mailbox/set name updates should not contain delimiter
---
.../contract/MailboxSetMethodContract.scala | 53 ++++++++++++++++++++++
.../org/apache/james/jmap/mail/MailboxSet.scala | 23 +++++++---
.../james/jmap/method/MailboxSetMethod.scala | 2 +-
3 files changed, 70 insertions(+), 8 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 3d2a337..765675d 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
@@ -4167,6 +4167,59 @@ trait MailboxSetMethodContract {
|}""".stripMargin)
}
+ @Test
+ def updateShouldFailWhenMailboxNameContainsDelimiter(server: GuiceJamesServer): Unit = {
+ val mailboxId1: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.forUser(BOB, "previousName"))
+ val request =
+ s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | ["Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId1.serialize()}": {
+ | "/name": "a.b"
+ | }
+ | }
+ | },
+ | "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",
+ | "notUpdated": {
+ | "${mailboxId1.serialize()}": {
+ | "type": "invalidArguments",
+ | "description": "The mailbox 'a.b' contains an illegal character: '.'",
+ | "properties": ["/name"]
+ | }
+ | }
+ | }, "c1"]
+ | ]
+ |}""".stripMargin)
+ }
+
@Disabled("JAMES-3359 The storage layer should rely on the mailbox path and not the name to allow handling of this case")
@Test
def updateShouldAllowDifferentIsSubscribedValuesWhenMailboxHaveTheSameName(server: GuiceJamesServer): Unit = {
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 3bc5fac..73fc849 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
@@ -36,8 +36,8 @@ import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model.State.State
import org.apache.james.jmap.model.{AccountId, CapabilityIdentifier}
import org.apache.james.jmap.routes.ProcessingContext
-import org.apache.james.mailbox.Role
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}
case class MailboxSetRequest(accountId: AccountId,
@@ -91,8 +91,9 @@ case class MailboxPatchObject(value: Map[String, JsValue]) {
def validate(processingContext: ProcessingContext,
mailboxIdFactory: MailboxId.Factory,
serializer: Serializer,
- capabilities: Set[CapabilityIdentifier]): Either[PatchUpdateValidationException, ValidatedMailboxPatchObject] = {
- val asUpdatedIterable = updates(serializer, capabilities, processingContext, mailboxIdFactory)
+ capabilities: Set[CapabilityIdentifier],
+ mailboxSession: MailboxSession): Either[PatchUpdateValidationException, ValidatedMailboxPatchObject] = {
+ val asUpdatedIterable = updates(serializer, capabilities, processingContext, mailboxIdFactory, mailboxSession)
val maybeParseException: Option[PatchUpdateValidationException] = asUpdatedIterable
.flatMap(x => x match {
@@ -145,9 +146,13 @@ case class MailboxPatchObject(value: Map[String, JsValue]) {
rightsPartialUpdates = partialRightsUpdates)))
}
- def updates(serializer: Serializer, capabilities: Set[CapabilityIdentifier], processingContext: ProcessingContext, mailboxIdFactory: MailboxId.Factory): Iterable[Either[PatchUpdateValidationException, Update]] = value.map({
+ def updates(serializer: Serializer,
+ capabilities: Set[CapabilityIdentifier],
+ processingContext: ProcessingContext,
+ mailboxIdFactory: MailboxId.Factory,
+ mailboxSession: MailboxSession): Iterable[Either[PatchUpdateValidationException, Update]] = value.map({
case (property, newValue) => property match {
- case "/name" => NameUpdate.parse(newValue)
+ case "/name" => NameUpdate.parse(newValue, mailboxSession)
case "/parentId" => ParentIdUpdate.parse(newValue, processingContext, mailboxIdFactory)
case "/sharedWith" => SharedWithResetUpdate.parse(serializer, capabilities)(newValue)
case "/role" => Left(ServerSetPropertyException(MailboxPatchObject.roleProperty))
@@ -256,8 +261,12 @@ object MailboxSetResponse {
case class MailboxUpdateResponse(value: JsObject)
object NameUpdate {
- def parse(newValue: JsValue): Either[PatchUpdateValidationException, Update] = newValue match {
- case JsString(newName) => scala.Right(NameUpdate(newName))
+ def parse(newValue: JsValue, mailboxSession: MailboxSession): Either[PatchUpdateValidationException, Update] = newValue match {
+ case JsString(newName) => if (newName.contains(mailboxSession.getPathDelimiter)) {
+ Left(InvalidUpdateException("/name", s"The mailbox '$newName' contains an illegal character: '${mailboxSession.getPathDelimiter}'"))
+ } else {
+ scala.Right(NameUpdate(newName))
+ }
case _ => Left(InvalidUpdateException("/name", "Expecting a JSON string as an argument"))
}
}
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 2a22946..ba022f4 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
@@ -179,7 +179,7 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
unparsedMailboxId: UnparsedMailboxId,
patch: MailboxPatchObject,
capabilities: Set[CapabilityIdentifier]): SMono[UpdateResult] = {
- patch.validate(processingContext, mailboxIdFactory, serializer, capabilities)
+ patch.validate(processingContext, mailboxIdFactory, serializer, capabilities, mailboxSession)
.fold(e => SMono.raiseError(e), validatedPatch =>
updateMailboxRights(mailboxId, validatedPatch, mailboxSession)
.`then`(updateSubscription(mailboxId, validatedPatch, mailboxSession))
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 14/14: JAMES-3095 Avoid listing all subscriptions
for each mailbox
Posted by bt...@apache.org.
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 5a899ed533056c98c4179acdf6a211ab5f2dc8f9
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Aug 26 14:05:11 2020 +0700
JAMES-3095 Avoid listing all subscriptions for each mailbox
---
.../main/scala/org/apache/james/jmap/model/MailboxFactory.scala | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/MailboxFactory.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/MailboxFactory.scala
index 91e63b9..7b9f57f 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/MailboxFactory.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/MailboxFactory.scala
@@ -67,6 +67,12 @@ case class MailboxValidation(mailboxName: MailboxName,
totalEmails: TotalEmails,
totalThreads: TotalThreads)
+case class Subscriptions(subscribedNames: Set[String]) {
+ def isSubscribed(name: String): IsSubscribed = IsSubscribed(subscribedNames.contains(name))
+
+ def isSubscribed(metaData: MailboxMetaData): IsSubscribed = isSubscribed(metaData.getPath.getName)
+}
+
class MailboxFactory @Inject() (subscriptionManager: SubscriptionManager, mailboxManager: MailboxManager) {
private def getRole(mailboxPath: MailboxPath, mailboxSession: MailboxSession): Option[Role] = Role.from(mailboxPath.getName)
@@ -117,6 +123,7 @@ class MailboxFactory @Inject() (subscriptionManager: SubscriptionManager, mailbo
mailboxSession: MailboxSession,
allMailboxesMetadata: Seq[MailboxMetaData],
quotaLoader: QuotaLoader): SMono[Mailbox] = {
+ val subscriptions: Subscriptions = Subscriptions(subscriptionManager.subscriptions(mailboxSession).asScala.toSet)
val sanitizedCounters: MailboxCounters = mailboxMetaData.getCounters.sanitize()
MailboxValidation.validate(mailboxMetaData.getPath, mailboxSession.getPathDelimiter, sanitizedCounters.getUnseen, sanitizedCounters.getUnseen, sanitizedCounters.getCount, sanitizedCounters.getCount) match {
@@ -134,7 +141,7 @@ class MailboxFactory @Inject() (subscriptionManager: SubscriptionManager, mailbo
.map(_.getId)
.headOption
val myRights: MailboxRights = getMyRights(mailboxMetaData.getPath, mailboxMetaData.getResolvedAcls, mailboxSession)
- val isSubscribed: IsSubscribed = retrieveIsSubscribed(mailboxMetaData.getPath, mailboxSession)
+ val isSubscribed: IsSubscribed = subscriptions.isSubscribed(mailboxMetaData)
Mailbox(
id = id,
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 05/14: JAMES-3359 Mailbox/set updates parentId:
test delegation behaviour
Posted by bt...@apache.org.
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 6c75d5249e320f56c240b7392853d80ec4d5c16b
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Aug 20 14:21:55 2020 +0700
JAMES-3359 Mailbox/set updates parentId: test delegation behaviour
---
.../contract/MailboxSetMethodContract.scala | 189 ++++++++++++++++++++-
1 file changed, 187 insertions(+), 2 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 25760d5..e388765 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
@@ -6616,8 +6616,6 @@ trait MailboxSetMethodContract {
.body
.asString
- println(response)
-
assertThatJson(response).isEqualTo(
s"""{
| "sessionState": "75128aab4b1b",
@@ -6636,4 +6634,191 @@ trait MailboxSetMethodContract {
| ]
|}""".stripMargin)
}
+
+ @Test
+ def updateShouldFailWhenRenamingParentIdWithinADelegatedAccount(server: GuiceJamesServer): Unit = {
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ val path = MailboxPath.forUser(ANDRE, "mailbox")
+ val mailboxId: MailboxId = mailboxProbe.createMailbox(path)
+ val parentPath = MailboxPath.forUser(ANDRE, "parent")
+ val parentId = mailboxProbe.createMailbox(parentPath)
+
+ val aCLProbeImpl = server.getProbe(classOf[ACLProbeImpl])
+ aCLProbeImpl.replaceRights(path, BOB.asString, MailboxACL.FULL_RIGHTS)
+ aCLProbeImpl.replaceRights(parentPath, BOB.asString, MailboxACL.FULL_RIGHTS)
+
+ val request = s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": "${parentId.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",
+ | "notUpdated": {
+ | "${mailboxId.serialize()}": {
+ | "type": "notFound",
+ | "description": "#private:andre@domain.tld:parent.mailbox"
+ | }
+ | }
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def updateShouldFailWhenRenamingParentIdFromADelegatedAccount(server: GuiceJamesServer): Unit = {
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ val path = MailboxPath.forUser(ANDRE, "mailbox")
+ val mailboxId: MailboxId = mailboxProbe.createMailbox(path)
+ val parentPath = MailboxPath.forUser(BOB, "parent")
+ val parentId = mailboxProbe.createMailbox(parentPath)
+
+ val aCLProbeImpl = server.getProbe(classOf[ACLProbeImpl])
+ aCLProbeImpl.replaceRights(path, BOB.asString, MailboxACL.FULL_RIGHTS)
+
+ val request = s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": "${parentId.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",
+ | "notUpdated": {
+ | "${mailboxId.serialize()}": {
+ | "type": "notFound",
+ | "description": "#private:andre@domain.tld:mailbox"
+ | }
+ | }
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def updateShouldFailWhenRenamingParentIdToADelegatedAccount(server: GuiceJamesServer): Unit = {
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ val path = MailboxPath.forUser(BOB, "mailbox")
+ val mailboxId: MailboxId = mailboxProbe.createMailbox(path)
+ val parentPath = MailboxPath.forUser(ANDRE, "parent")
+ val parentId = mailboxProbe.createMailbox(parentPath)
+
+ val aCLProbeImpl = server.getProbe(classOf[ACLProbeImpl])
+ aCLProbeImpl.replaceRights(parentPath, BOB.asString, MailboxACL.FULL_RIGHTS)
+
+ val request = s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": "${parentId.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",
+ | "notUpdated": {
+ | "${mailboxId.serialize()}": {
+ | "type": "notFound",
+ | "description": "#private:andre@domain.tld:parent.mailbox"
+ | }
+ | }
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 10/14: JAMES-3359 Review MailboxSetMethodContract
indent
Posted by bt...@apache.org.
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 c1d704bb5fc4850f85754834d85152b1a49baeeb
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Aug 24 10:00:43 2020 +0700
JAMES-3359 Review MailboxSetMethodContract indent
---
.../contract/MailboxSetMethodContract.scala | 306 ++++++++++-----------
1 file changed, 153 insertions(+), 153 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 9a06964..28f447e 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
@@ -1632,18 +1632,18 @@ trait MailboxSetMethodContract {
|}
|""".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 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"""{
@@ -1689,18 +1689,18 @@ trait MailboxSetMethodContract {
|}
|""".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 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"""{
@@ -1745,18 +1745,18 @@ trait MailboxSetMethodContract {
|}
|""".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 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"""{
@@ -1807,18 +1807,18 @@ trait MailboxSetMethodContract {
|}
|""".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 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"""{
@@ -1861,18 +1861,18 @@ trait MailboxSetMethodContract {
|}
|""".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 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"""{
@@ -1909,15 +1909,15 @@ trait MailboxSetMethodContract {
|}
|""".stripMargin
- `given`
- .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
- .body(request)
- .when
- .post
- .`then`
- .log().ifValidationFails()
- .statusCode(SC_OK)
- .contentType(JSON)
+ `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
+ .post
+ .`then`
+ .log().ifValidationFails()
+ .statusCode(SC_OK)
+ .contentType(JSON)
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
@@ -1985,15 +1985,15 @@ trait MailboxSetMethodContract {
|}
|""".stripMargin
- `given`
- .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
- .body(request)
- .when
- .post
- .`then`
- .log().ifValidationFails()
- .statusCode(SC_OK)
- .contentType(JSON)
+ `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
+ .post
+ .`then`
+ .log().ifValidationFails()
+ .statusCode(SC_OK)
+ .contentType(JSON)
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
@@ -2061,18 +2061,18 @@ trait MailboxSetMethodContract {
|}
|""".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 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"""{
@@ -2122,18 +2122,18 @@ trait MailboxSetMethodContract {
|}
|""".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 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"""{
@@ -2176,18 +2176,18 @@ trait MailboxSetMethodContract {
|}
|""".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 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"""{
@@ -2232,18 +2232,18 @@ trait MailboxSetMethodContract {
|}
|""".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 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"""{
@@ -2283,18 +2283,18 @@ trait MailboxSetMethodContract {
|}
|""".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 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"""{
@@ -2343,17 +2343,17 @@ trait MailboxSetMethodContract {
|""".stripMargin
`given`
- .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
- .body(request)
- .when
- .post
- .`then`
- .log().ifValidationFails()
- .statusCode(SC_OK)
- .contentType(JSON)
- // We need to limit ourself to simple body assertions in order not to infer id allocation
- .body("methodResponses[0][1].created.C42.totalThreads", equalTo(0))
- .body("methodResponses[1][1].destroyed", hasSize(1))
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
+ .post
+ .`then`
+ .log().ifValidationFails()
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ // We need to limit ourself to simple body assertions in order not to infer id allocation
+ .body("methodResponses[0][1].created.C42.totalThreads", equalTo(0))
+ .body("methodResponses[1][1].destroyed", hasSize(1))
}
@Test
@@ -4746,9 +4746,9 @@ trait MailboxSetMethodContract {
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
- .when
+ .when
.post
- .`then`
+ .`then`
.log().ifValidationFails()
.statusCode(SC_OK)
.contentType(JSON)
@@ -4826,9 +4826,9 @@ trait MailboxSetMethodContract {
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
- .when
+ .when
.post
- .`then`
+ .`then`
.log().ifValidationFails()
.statusCode(SC_OK)
.contentType(JSON)
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 08/14: JAMES-3359 Mailbox/set parentId updates:
prevent loops
Posted by bt...@apache.org.
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 746efffb4e8b905b5155ec49e6e2bfe6d17ed288
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Aug 20 14:59:10 2020 +0700
JAMES-3359 Mailbox/set parentId updates: prevent loops
We apply the same strategy than in Draft: preventing changing parentId of a mailbox having childs
respects the mailbox tree structure.
---
.../contract/MailboxSetMethodContract.scala | 59 ++++++++++++++++++++++
.../james/jmap/method/MailboxSetMethod.scala | 14 +++--
2 files changed, 68 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 765675d..355e3d5 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
@@ -6969,4 +6969,63 @@ trait MailboxSetMethodContract {
| ]
|}""".stripMargin)
}
+
+ @Test
+ def updatingParentIdShouldFailWhenMailboxHasAChild(server: GuiceJamesServer): Unit = {
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ val mailboxId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+ mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox.child"))
+ val parentId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent"))
+
+ val request = s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": "${parentId.serialize()}"
+ | }
+ | }
+ | },
+ | "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",
+ | "notUpdated": {
+ | "1": {
+ | "type": "invalidArguments",
+ | "description": "1 parentId property cannot be updated as this mailbox has child mailboxes",
+ | "properties": ["parentId"]
+ | }
+ | }
+ | }, "c1"]
+ | ]
+ |}""".stripMargin)
+ }
}
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 ba022f4..a412f8a 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
@@ -122,6 +122,7 @@ case class UpdateFailure(mailboxId: UnparsedMailboxId, exception: Throwable, pat
case e: InvalidPatchException => MailboxSetError.invalidPatch(Some(SetErrorDescription(s"${e.cause}")))
case e: SystemMailboxChangeException => MailboxSetError.invalidArgument(Some(SetErrorDescription("Invalid change to a system mailbox")), filter(Properties(List("/name", "parentId"))))
case e: InsufficientRightsException => MailboxSetError.invalidArgument(Some(SetErrorDescription("Invalid change to a delegated mailbox")), None)
+ case e: MailboxHasChildException => MailboxSetError.invalidArgument(Some(SetErrorDescription(s"${e.mailboxId.serialize()} parentId property cannot be updated as this mailbox has child mailboxes")), Some(Properties(List("parentId"))))
case _ => MailboxSetError.serverFail(Some(SetErrorDescription(exception.getMessage)), None)
}
}
@@ -216,8 +217,8 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
throw SystemMailboxChangeException(mailboxId)
}
val oldPath = mailbox.getMailboxPath
- val newPath = applyNameUpdate(validatedPatch.nameUpdate, mailboxSession)
- .andThen(applyParentIdUpdate(validatedPatch.parentIdUpdate, mailboxSession))
+ val newPath = applyParentIdUpdate(mailboxId, validatedPatch.parentIdUpdate, mailboxSession)
+ .andThen(applyNameUpdate(validatedPatch.nameUpdate, mailboxSession))
.apply(oldPath)
if (!oldPath.equals(newPath)) {
mailboxManager.renameMailbox(mailboxId,
@@ -236,8 +237,8 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
}
}
- private def applyParentIdUpdate(maybeParentIdUpdate: Option[ParentIdUpdate], mailboxSession: MailboxSession): MailboxPath => MailboxPath = {
- maybeParentIdUpdate.map(parentIdUpdate => applyParentIdUpdate(parentIdUpdate, mailboxSession))
+ private def applyParentIdUpdate(mailboxId: MailboxId, maybeParentIdUpdate: Option[ParentIdUpdate], mailboxSession: MailboxSession): MailboxPath => MailboxPath = {
+ maybeParentIdUpdate.map(parentIdUpdate => applyParentIdUpdate(mailboxId, parentIdUpdate, mailboxSession))
.getOrElse(x => x)
}
@@ -253,11 +254,14 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
}).getOrElse(originalPath)
}
- private def applyParentIdUpdate(parentIdUpdate: ParentIdUpdate, mailboxSession: MailboxSession): MailboxPath => MailboxPath = {
+ private def applyParentIdUpdate(mailboxId: MailboxId, parentIdUpdate: ParentIdUpdate, mailboxSession: MailboxSession): MailboxPath => MailboxPath = {
originalPath => {
val currentName = originalPath.getName(mailboxSession.getPathDelimiter)
parentIdUpdate.newId
.map(id => {
+ if (mailboxManager.hasChildren(originalPath, mailboxSession)) {
+ throw MailboxHasChildException(mailboxId)
+ }
val parentPath = mailboxManager.getMailbox(id, mailboxSession).getMailboxPath
parentPath.child(currentName, mailboxSession.getPathDelimiter)
})
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 09/14: JAMES-3359 Fix typos in
MailboxSetMethodContract
Posted by bt...@apache.org.
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 97281746651fa54a4549c170d72a5f3ae0979e42
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Aug 21 14:27:04 2020 +0700
JAMES-3359 Fix typos in MailboxSetMethodContract
---
.../contract/MailboxSetMethodContract.scala | 56 +++++++++-------------
1 file changed, 23 insertions(+), 33 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 355e3d5..9a06964 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
@@ -5566,12 +5566,10 @@ trait MailboxSetMethodContract {
@Test
def updateShouldAllowParentIdChangeWhenChildMailbox(server: GuiceJamesServer): Unit = {
- val parentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "parent"))
- val newParentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "newParent"))
- val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "parent.mailbox"))
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent"))
+ val newParentId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "newParent"))
+ val mailboxId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent.mailbox"))
val request =
s"""
|{
@@ -5655,11 +5653,10 @@ trait MailboxSetMethodContract {
}
@Test
- def updateShouldAllowParentIdChangeWhenTopLevelMailboxAndNewMane(server: GuiceJamesServer): Unit = {
- val parentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "parent"))
- val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+ def updateShouldAllowParentIdChangeWhenTopLevelMailboxAndNewName(server: GuiceJamesServer): Unit = {
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ val parentId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent"))
+ val mailboxId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox"))
val request =
s"""
|{
@@ -5745,12 +5742,10 @@ trait MailboxSetMethodContract {
@Test
def updateShouldAllowParentIdChangeWhenChildMailboxAndNewName(server: GuiceJamesServer): Unit = {
- val parentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "parent"))
- val newParentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "newParent"))
- val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "parent.mailbox"))
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent"))
+ val newParentId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "newParent"))
+ val mailboxId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent.mailbox"))
val request =
s"""
|{
@@ -5836,12 +5831,10 @@ trait MailboxSetMethodContract {
@Test
def updateShouldAllowParentIdDropWhenChildMailbox(server: GuiceJamesServer): Unit = {
- val parentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "parent"))
- val newParentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "newParent"))
- val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "parent.mailbox"))
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent"))
+ mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "newParent"))
+ val mailboxId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent.mailbox"))
val request =
s"""
|{
@@ -5925,10 +5918,9 @@ trait MailboxSetMethodContract {
@Test
def updateShouldAllowParentIdDropWhenTopLevelMailboxAndNewName(server: GuiceJamesServer): Unit = {
- val parentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "parent"))
- val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent"))
+ val mailboxId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox"))
val request =
s"""
|{
@@ -6013,12 +6005,10 @@ trait MailboxSetMethodContract {
@Test
def updateShouldAllowParentIdDropWhenChildMailboxAndNewName(server: GuiceJamesServer): Unit = {
- val parentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "parent"))
- val newParentId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "newParent"))
- val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
- .createMailbox(MailboxPath.forUser(BOB, "parent.mailbox"))
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent"))
+ mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "newParent"))
+ val mailboxId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "parent.mailbox"))
val request =
s"""
|{
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 03/14: JAMES-3359 Mailbox/set update should handle
parentId formatting errors
Posted by bt...@apache.org.
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 1cdd21d0b358f7c23b95da99dae026cda33bda88
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Aug 20 12:36:08 2020 +0700
JAMES-3359 Mailbox/set update should handle parentId formatting errors
---
.../contract/MailboxSetMethodContract.scala | 241 +++++++++++++++++++++
1 file changed, 241 insertions(+)
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 871345f..07a7ed9 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
@@ -6220,4 +6220,245 @@ trait MailboxSetMethodContract {
| ]
|}""".stripMargin)
}
+
+ @Test
+ def updateShouldAcceptCreationIdsForParentId(server: GuiceJamesServer): Unit = {
+ val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+ val request = s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "create": {
+ | "C42": {
+ | "name": "parent"
+ | }
+ | }
+ | },
+ | "c1"
+ | ],
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": "#C42"
+ | }
+ | }
+ | },
+ | "c2"
+ | ],
+ | ["Mailbox/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${mailboxId.serialize()}"]
+ | },
+ | "c3"
+ | ]
+ | ]
+ |}
+ |""".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 parentId: String = server.getProbe(classOf[MailboxProbeImpl])
+ .getMailboxId("#private", BOB.asString(), "parent")
+ .serialize()
+
+ assertThatJson(response).isEqualTo(
+ s"""{
+ | "sessionState": "75128aab4b1b",
+ | "methodResponses": [
+ | ["Mailbox/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "newState": "000001",
+ | "created": {
+ | "C42": {
+ | "id": "$parentId",
+ | "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
+ | }
+ | }
+ | }, "c1"],
+ | ["Mailbox/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "newState": "000001",
+ | "updated": {
+ | "${mailboxId.serialize()}": {}
+ | }
+ | }, "c2"],
+ | ["Mailbox/get", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "state": "000001",
+ | "list": [{
+ | "id": "${mailboxId.serialize()}",
+ | "sortOrder":1000,
+ | "name": "mailbox",
+ | "parentId": "$parentId",
+ | "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
+ | }],
+ | "notFound": []
+ | }, "c3"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def updateShouldRejectInvalidParentIdJsonPayload(server: GuiceJamesServer): Unit = {
+ val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+ val request = s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": ["invalid"]
+ | }
+ | }
+ | },
+ | "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",
+ | "notUpdated": {
+ | "1": {
+ | "type": "invalidArguments",
+ | "description": "Expecting a JSON string or null as an argument",
+ | "properties": ["/parentId"]
+ | }
+ | }
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def updateShouldRejectUnresolvableCreationIds(server: GuiceJamesServer): Unit = {
+ val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+ val request = s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [
+ | [
+ | "Mailbox/set",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${mailboxId.serialize()}": {
+ | "/parentId": "#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
+
+ assertThatJson(response).isEqualTo(
+ s"""{
+ | "sessionState": "75128aab4b1b",
+ | "methodResponses": [
+ | ["Mailbox/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "newState": "000001",
+ | "notUpdated": {
+ | "1": {
+ | "type": "invalidArguments",
+ | "description": "ClientId(#C42) was not used in previously defined creationIds",
+ | "properties": ["/parentId"]
+ | }
+ | }
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org