You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2020/11/17 03:37:19 UTC

[james-project] branch master updated (fb48808 -> 1495d5e)

This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git.


    from fb48808  JAMES-3440 Configuration examples
     new 5a644d2  JAMES-3337 Fixup document the use of JWT
     new bedde56  JAMES-3447 Email/set create should return blobId, threadId, size
     new a167674  JAMES-3400 Add user list command
     new 13383db  JAMES-3400 Add user create command
     new a8509f8  JAMES-3400 Add user delete command
     new 1495d5e  JAMES-3400 Add user exist command

The 6 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:
 .../pages/distributed/configure/webadmin.adoc      |   2 +-
 .../org/apache/james/mailbox/MessageManager.java   |   8 +-
 .../james/mailbox/store/StoreMessageManager.java   |   2 +-
 .../methods/SetMessagesCreationProcessorTest.java  |   3 +-
 .../methods/SetMessagesUpdateProcessorTest.java    |   3 +-
 .../rfc8621/contract/EmailSetMethodContract.scala  | 823 +++++++++++++--------
 .../james/jmap/json/EmailSetSerializer.scala       |   2 +-
 .../scala/org/apache/james/jmap/mail/Email.scala   |   2 +-
 .../org/apache/james/jmap/mail/EmailSet.scala      |   3 +-
 .../jmap/method/EmailSetCreatePerformer.scala      |   6 +-
 .../java/org/apache/james/cli/WebAdminCli.java     |   2 +
 .../DomainCommand.java => user/UserCommand.java}   |  22 +-
 .../UserCreateCommand.java}                        |  37 +-
 .../UserDeleteCommand.java}                        |  23 +-
 .../UserExistCommand.java}                         |  40 +-
 .../UserListCommand.java}                          |  24 +-
 .../{DomainClient.java => UserClient.java}         |  23 +-
 .../apache/james/httpclient/model/UserName.java}   |  53 +-
 .../james/httpclient/model/UserPassword.java       |  53 +-
 .../java/org/apache/james/cli/UserManageTest.java  | 180 +++++
 20 files changed, 856 insertions(+), 455 deletions(-)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{domain/DomainCommand.java => user/UserCommand.java} (79%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{domain/DomainCreateCommand.java => user/UserCreateCommand.java} (59%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{domain/DomainDeleteCommand.java => user/UserDeleteCommand.java} (76%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{domain/DomainExistCommand.java => user/UserExistCommand.java} (52%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{domain/DomainListCommand.java => user/UserListCommand.java} (72%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/{DomainClient.java => UserClient.java} (70%)
 copy server/{blob/blob-api/src/test/java/org/apache/james/blob/api/BlobTypeTest.java => protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/model/UserName.java} (78%)
 copy protocols/api/src/test/java/org/apache/james/protocols/api/ProtocolSessionTest.java => server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/model/UserPassword.java (77%)
 create mode 100644 server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 04/06: JAMES-3400 Add user create command

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 13383db2409b8e8b4049eb815688e8f0c32b2f35
Author: quanth <hq...@linagora.com>
AuthorDate: Wed Nov 11 17:26:12 2020 +0700

    JAMES-3400 Add user create command
---
 .../org/apache/james/cli/user/UserCommand.java     |  3 +-
 .../apache/james/cli/user/UserCreateCommand.java   | 69 ++++++++++++++++++++++
 .../org/apache/james/httpclient/UserClient.java    |  8 +++
 .../{UserClient.java => model/UserPassword.java}   | 18 +++---
 .../java/org/apache/james/cli/UserManageTest.java  | 33 +++++++++++
 5 files changed, 121 insertions(+), 10 deletions(-)

diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java
index 92c27ae..fdd7da1 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java
@@ -30,7 +30,8 @@ import picocli.CommandLine;
     name = "user",
     description = "Manage Users",
     subcommands = {
-        UserListCommand.class
+        UserListCommand.class,
+        UserCreateCommand.class
     })
 public class UserCommand implements Callable<Integer> {
 
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCreateCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCreateCommand.java
new file mode 100644
index 0000000..7c0cd52
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCreateCommand.java
@@ -0,0 +1,69 @@
+/******************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one     *
+ * or more contributor license agreements.  See the NOTICE file   *
+ * distributed with this work for additional information          *
+ * regarding copyright ownership.  The ASF licenses this file     *
+ * to you under the Apache License, Version 2.0 (the              *
+ * "License"); you may not use this file except in compliance     *
+ * with the License.  You may obtain a copy of the License at     *
+ *                                                                *
+ * http://www.apache.org/licenses/LICENSE-2.0                     *
+ *                                                                *
+ * Unless required by applicable law or agreed to in writing,     *
+ * software distributed under the License is distributed on an    *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY         *
+ * KIND, either express or implied.  See the License for the      *
+ * specific language governing permissions and limitations        *
+ * under the License.                                             *
+ ******************************************************************/
+
+package org.apache.james.cli.user;
+
+import java.util.concurrent.Callable;
+
+import org.apache.james.cli.WebAdminCli;
+import org.apache.james.httpclient.UserClient;
+import org.apache.james.httpclient.model.UserPassword;
+
+import feign.Feign;
+import feign.Response;
+import feign.jackson.JacksonEncoder;
+import picocli.CommandLine;
+
+@CommandLine.Command(
+    name = "create",
+    description = "Create a new User")
+public class UserCreateCommand implements Callable<Integer> {
+
+    public static final int CREATED_CODE = 204;
+    public static final int BAD_REQUEST_CODE = 400;
+
+    @CommandLine.ParentCommand UserCommand userCommand;
+
+    @CommandLine.Parameters(description = "Username")
+    String userName;
+
+    @CommandLine.Option(names = {"-p", "--password"}, description = "Password", arity = "0..1", interactive = true, required = true)
+    char[] password;
+
+    @Override
+    public Integer call() {
+        try {
+            UserClient userClient = Feign.builder()
+                .encoder(new JacksonEncoder())
+                .target(UserClient.class, userCommand.webAdminCli.jamesUrl + "/users");
+            Response rs = userClient.createAUser(userName, new UserPassword(new String(password)));
+            if (rs.status() == CREATED_CODE) {
+                userCommand.out.println("The user was created successfully");
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
+            } else if (rs.status() == BAD_REQUEST_CODE) {
+                userCommand.out.println("The user name or the payload is invalid");
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            }
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        } catch (Exception e) {
+            e.printStackTrace(userCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
+    }
+}
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java
index deb2b2b..90b134b 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java
@@ -22,12 +22,20 @@ package org.apache.james.httpclient;
 import java.util.List;
 
 import org.apache.james.httpclient.model.UserName;
+import org.apache.james.httpclient.model.UserPassword;
 
+import feign.Headers;
+import feign.Param;
 import feign.RequestLine;
+import feign.Response;
 
 public interface UserClient {
 
     @RequestLine("GET")
     List<UserName> getUserNameList();
 
+    @RequestLine("PUT /{userName}")
+    @Headers("Content-Type: application/json")
+    Response createAUser(@Param("userName") String userName, UserPassword password);
+
 }
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/model/UserPassword.java
similarity index 82%
copy from server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java
copy to server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/model/UserPassword.java
index deb2b2b..a0e0a9e 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/model/UserPassword.java
@@ -17,17 +17,17 @@
  * under the License.                                             *
  ******************************************************************/
 
-package org.apache.james.httpclient;
+package org.apache.james.httpclient.model;
 
-import java.util.List;
+import com.fasterxml.jackson.annotation.JsonProperty;
 
-import org.apache.james.httpclient.model.UserName;
+public class UserPassword {
 
-import feign.RequestLine;
+    @JsonProperty("password")
+    private String password;
 
-public interface UserClient {
+    public UserPassword(String password) {
+        this.password = password;
+    }
 
-    @RequestLine("GET")
-    List<UserName> getUserNameList();
-
-}
\ No newline at end of file
+}
diff --git a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java
index ebfe623..6804ec3 100644
--- a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java
+++ b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java
@@ -75,4 +75,37 @@ public class UserManageTest {
         assertThat(exitCode).isEqualTo(0);
         assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("hqtran@linagora.com".concat("\n").concat("testing@linagora.com").toCharArray());
     }
+
+    @Test
+    void userCreateShouldAddValidUserSucceed(GuiceJamesServer server) throws Exception {
+        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+        dataProbe = server.getProbe(DataProbeImpl.class);
+        dataProbe.fluent().addDomain("linagora.com");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "user", "create", "hqtran@linagora.com", "--password", "123456");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The user was created successfully");
+        assertThat(dataProbe.listUsers()).containsOnly("hqtran@linagora.com");
+    }
+
+    @Test
+    void userCreateShouldFailWithInvalidUsername(GuiceJamesServer server) throws Exception {
+        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+        dataProbe = server.getProbe(DataProbeImpl.class);
+        dataProbe.fluent().addDomain("linagora.com");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "user", "create", "hq/tran@linagora.com", "--password", "123456");
+
+        int exitCode1 = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "user", "create", "hqtran@google.com", "--password", "123456");
+
+        assertThat(exitCode).isEqualTo(1);
+        assertThat(exitCode1).isEqualTo(1);
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The user name or the payload is invalid");
+        assertThat(dataProbe.listUsers()).isEmpty();
+    }
+
 }
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 02/06: JAMES-3447 Email/set create should return blobId, threadId, size

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit bedde56801279f29aaf08db2fab1a0103e2fa427
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Nov 16 16:01:50 2020 +0700

    JAMES-3447 Email/set create should return blobId, threadId, size
---
 .../org/apache/james/mailbox/MessageManager.java   |   8 +-
 .../james/mailbox/store/StoreMessageManager.java   |   2 +-
 .../methods/SetMessagesCreationProcessorTest.java  |   3 +-
 .../methods/SetMessagesUpdateProcessorTest.java    |   3 +-
 .../rfc8621/contract/EmailSetMethodContract.scala  | 823 +++++++++++++--------
 .../james/jmap/json/EmailSetSerializer.scala       |   2 +-
 .../scala/org/apache/james/jmap/mail/Email.scala   |   2 +-
 .../org/apache/james/jmap/mail/EmailSet.scala      |   3 +-
 .../jmap/method/EmailSetCreatePerformer.scala      |   6 +-
 9 files changed, 522 insertions(+), 330 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java b/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java
index 779d84e..c5e8512 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java
@@ -147,10 +147,12 @@ public interface MessageManager {
 
     class AppendResult {
         private final ComposedMessageId id;
+        private final Long size;
         private final Optional<List<MessageAttachmentMetadata>> messageAttachments;
 
-        public AppendResult(ComposedMessageId id, Optional<List<MessageAttachmentMetadata>> messageAttachments) {
+        public AppendResult(ComposedMessageId id, Long size, Optional<List<MessageAttachmentMetadata>> messageAttachments) {
             this.id = id;
+            this.size = size;
             this.messageAttachments = messageAttachments;
         }
 
@@ -158,6 +160,10 @@ public interface MessageManager {
             return id;
         }
 
+        public Long getSize() {
+            return size;
+        }
+
         public List<MessageAttachmentMetadata> getMessageAttachments() {
             Preconditions.checkState(messageAttachments.isPresent(), "'attachment storage' not supported by the implementation");
             return messageAttachments.get();
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
index 0103598..a1edc78 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
@@ -450,7 +450,7 @@ public class StoreMessageManager implements MessageManager {
                     .block();
                 MessageMetaData messageMetaData = data.getLeft();
                 ComposedMessageId ids = new ComposedMessageId(mailbox.getMailboxId(), messageMetaData.getMessageId(), messageMetaData.getUid());
-                return new AppendResult(ids, data.getRight());
+                return new AppendResult(ids, messageMetaData.getSize(), data.getRight());
             }, MailboxPathLocker.LockType.Write);
         }
     }
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
index 453f932..6e4c162 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
@@ -102,6 +102,7 @@ public class SetMessagesCreationProcessorTest {
     private static final InMemoryId OUTBOX_ID = InMemoryId.of(12345);
     private static final String DRAFTS = "drafts";
     private static final InMemoryId DRAFTS_ID = InMemoryId.of(12);
+    private static final Long TEST_MESSAGE_SIZE = 1L;
 
     private final CreationMessage.Builder creationMessageBuilder = CreationMessage.builder()
             .from(DraftEmailer.builder().name("alice").email("alice@example.com").build())
@@ -194,7 +195,7 @@ public class SetMessagesCreationProcessorTest {
         when(outbox.getMailboxPath()).thenReturn(MailboxPath.forUser(USER, OUTBOX));
         
         when(outbox.appendMessage(any(MessageManager.AppendCommand.class), any(MailboxSession.class)))
-            .thenReturn(new MessageManager.AppendResult(new ComposedMessageId(OUTBOX_ID, TestMessageId.of(23), MessageUid.of(1)),
+            .thenReturn(new MessageManager.AppendResult(new ComposedMessageId(OUTBOX_ID, TestMessageId.of(23), MessageUid.of(1)), TEST_MESSAGE_SIZE,
                 Optional.of(ImmutableList.of())));
 
         drafts = mock(MessageManager.class);
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
index b33f25c..5ce3a4b 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
@@ -92,6 +92,7 @@ public class SetMessagesUpdateProcessorTest {
     private static final InMemoryId OUTBOX_ID = InMemoryId.of(12345);
     private static final String DRAFTS = "drafts";
     private static final InMemoryId DRAFTS_ID = InMemoryId.of(12);
+    private static final Long TEST_MESSAGE_SIZE = 1L;
 
     public static class TestSystemMailboxesProvider implements SystemMailboxesProvider {
 
@@ -200,7 +201,7 @@ public class SetMessagesUpdateProcessorTest {
 
         when(outbox.appendMessage(any(MessageManager.AppendCommand.class), any(MailboxSession.class)))
             .thenReturn(new MessageManager.AppendResult(
-                new ComposedMessageId(OUTBOX_ID, TestMessageId.of(23), MessageUid.of(1)),
+                new ComposedMessageId(OUTBOX_ID, TestMessageId.of(23), MessageUid.of(1)), TEST_MESSAGE_SIZE,
                 Optional.empty()));
 
         drafts = mock(MessageManager.class);
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/EmailSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
index 38bfb79..b843fed 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
@@ -49,7 +49,7 @@ import org.assertj.core.api.Assertions.assertThat
 import org.junit.jupiter.api.{BeforeEach, Test}
 import org.junit.jupiter.params.ParameterizedTest
 import org.junit.jupiter.params.provider.ValueSource
-import play.api.libs.json.{JsString, Json}
+import play.api.libs.json.{JsNumber, JsString, Json}
 
 import scala.jdk.CollectionConverters._
 
@@ -186,10 +186,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val createResponse = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = createResponse
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = createResponse
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -475,10 +493,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val responseAsJson = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -535,10 +571,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val responseAsJson = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -592,10 +646,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val responseAsJson = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -648,10 +720,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val createResponse = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = createResponse
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = createResponse
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -709,10 +799,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val createResponse = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = createResponse
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = createResponse
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -767,10 +875,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val createResponse = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = createResponse
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = createResponse
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -826,10 +952,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val responseAsJson = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -1162,10 +1306,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val responseAsJson = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -1241,10 +1403,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val responseAsJson = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -1794,20 +1974,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val blobIdToDownload = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
-      .\("attachments")
-      .\(0)
-      .\("blobId")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].created.aaaaaa")
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -1821,7 +2009,7 @@ trait EmailSetMethodContract {
            |  "attachments": [
            |    {
            |      "partId": "5",
-           |      "blobId": "$blobIdToDownload",
+           |      "blobId": "${messageId}_5",
            |      "size": 11,
            |      "type": "text/plain",
            |      "charset": "UTF-8",
@@ -1836,7 +2024,7 @@ trait EmailSetMethodContract {
       .basePath("")
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
     .when
-      .get(s"/download/$accountId/$blobIdToDownload")
+      .get(s"/download/$accountId/${messageId}_5")
     .`then`
       .statusCode(SC_OK)
       .contentType("text/plain")
@@ -1929,18 +2117,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].created.aaaaaa")
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -2075,18 +2273,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].created.aaaaaa")
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -2239,79 +2447,59 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
 
     assertThatJson(response)
+      .inPath(s"methodResponses[1][1].list[0]")
       .isEqualTo(
         s"""{
-           |  "sessionState": "75128aab4b1b",
-           |  "methodResponses": [
-           |    ["Email/set", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "newState": "000001",
-           |      "created": {
-           |        "aaaaaa": {
-           |          "id": "$messageId"
-           |        }
-           |      }
-           |    }, "c1"],
-           |    ["Email/get", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "state": "000001",
-           |      "list": [
-           |        {
-           |          "id": "$messageId",
-           |          "bodyStructure": {
-           |            "type": "multipart/mixed",
+           |  "id": "$messageId",
+           |  "bodyStructure": {
+           |    "type": "multipart/mixed",
+           |    "subParts": [
+           |      {
+           |        "type": "multipart/related",
+           |        "subParts": [
+           |          {
+           |            "type": "multipart/alternative",
            |            "subParts": [
            |              {
-           |                "type": "multipart/related",
-           |                "subParts": [
-           |                  {
-           |                    "type": "multipart/alternative",
-           |                    "subParts": [
-           |                      {
-           |                        "type": "text/html"
-           |                      },
-           |                      {
-           |                        "type": "text/plain"
-           |                      }
-           |                    ]
-           |                  },
-           |                  {
-           |                    "type": "text/plain",
-           |                    "disposition": "inline",
-           |                    "cid": "abc"
-           |                  },
-           |                  {
-           |                    "type": "text/plain",
-           |                    "disposition": "inline",
-           |                    "cid": "def"
-           |                  }
-           |                ]
+           |                "type": "text/html"
            |              },
            |              {
-           |                "type": "text/plain",
-           |                "disposition": "attachment"
+           |                "type": "text/plain"
            |              }
            |            ]
+           |          },
+           |          {
+           |            "type": "text/plain",
+           |            "disposition": "inline",
+           |            "cid": "abc"
+           |          },
+           |          {
+           |            "type": "text/plain",
+           |            "disposition": "inline",
+           |            "cid": "def"
            |          }
-           |        }
-           |      ],
-           |      "notFound": []
-           |    }, "c2"]
-           |  ]
+           |        ]
+           |      },
+           |      {
+           |        "type": "text/plain",
+           |        "disposition": "attachment"
+           |      }
+           |    ]
+           |  }
            |}""".stripMargin)
   }
 
@@ -2396,64 +2584,54 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
 
     assertThatJson(response)
+      .inPath("methodResponses[0][1].created.aaaaaa")
       .isEqualTo(
         s"""{
-           |  "sessionState": "75128aab4b1b",
-           |  "methodResponses": [
-           |    ["Email/set", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "newState": "000001",
-           |      "created": {
-           |        "aaaaaa": {
-           |          "id": "$messageId"
-           |        }
-           |      }
-           |    }, "c1"],
-           |    ["Email/get", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "state": "000001",
-           |      "list": [
-           |        {
-           |          "id": "$messageId",
-           |          "bodyStructure": {
-           |            "type": "multipart/mixed",
-           |            "subParts": [
-           |              {
-           |                "type":"multipart/alternative",
-           |                "subParts": [
-           |                  {
-           |                    "type":"text/html"
-           |                  },
-           |                  {
-           |                    "type":"text/plain"
-           |                  }
-           |                ]
-           |              },
-           |              {
-           |                "type": "text/plain",
-           |                "disposition": "attachment"
-           |              }
-           |            ]
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
+
+    assertThatJson(response)
+      .inPath(s"methodResponses[1][1].list[0]")
+      .isEqualTo(
+        s"""{
+           |  "id": "$messageId",
+           |  "bodyStructure": {
+           |    "type": "multipart/mixed",
+           |    "subParts": [
+           |      {
+           |        "type":"multipart/alternative",
+           |        "subParts": [
+           |          {
+           |            "type":"text/html"
+           |          },
+           |          {
+           |            "type":"text/plain"
            |          }
-           |        }
-           |      ],
-           |      "notFound": []
-           |    }, "c2"]
-           |  ]
+           |        ]
+           |      },
+           |      {
+           |        "type": "text/plain",
+           |        "disposition": "attachment"
+           |      }
+           |    ]
+           |  }
            |}""".stripMargin)
   }
 
@@ -2546,70 +2724,60 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
 
     assertThatJson(response)
+      .inPath("methodResponses[0][1].created.aaaaaa")
       .isEqualTo(
         s"""{
-           |  "sessionState": "75128aab4b1b",
-           |  "methodResponses": [
-           |    ["Email/set", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "newState": "000001",
-           |      "created": {
-           |        "aaaaaa": {
-           |          "id": "$messageId"
-           |        }
-           |      }
-           |    }, "c1"],
-           |    ["Email/get", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "state": "000001",
-           |      "list": [
-           |        {
-           |          "id": "$messageId",
-           |          "bodyStructure": {
-           |            "type": "multipart/related",
-           |            "subParts": [
-           |              {
-           |                "type":"multipart/alternative",
-           |                "subParts": [
-           |                  {
-           |                    "type":"text/html"
-           |                  },
-           |                  {
-           |                    "type":"text/plain"
-           |                  }
-           |                ]
-           |              },
-           |              {
-           |                "type": "text/plain",
-           |                "disposition": "inline",
-           |                "cid": "abc"
-           |              },
-           |              {
-           |                "type": "text/plain",
-           |                "disposition": "inline",
-           |                "cid": "def"
-           |              }
-           |            ]
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
+
+    assertThatJson(response)
+      .inPath(s"methodResponses[1][1].list[0]")
+      .isEqualTo(
+        s"""{
+           |  "id": "$messageId",
+           |  "bodyStructure": {
+           |    "type": "multipart/related",
+           |    "subParts": [
+           |      {
+           |        "type":"multipart/alternative",
+           |        "subParts": [
+           |          {
+           |            "type":"text/html"
+           |          },
+           |          {
+           |            "type":"text/plain"
            |          }
-           |        }
-           |      ],
-           |      "notFound": []
-           |    }, "c2"]
-           |  ]
+           |        ]
+           |      },
+           |      {
+           |        "type": "text/plain",
+           |        "disposition": "inline",
+           |        "cid": "abc"
+           |      },
+           |      {
+           |        "type": "text/plain",
+           |        "disposition": "inline",
+           |        "cid": "def"
+           |      }
+           |    ]
+           |  }
            |}""".stripMargin)
   }
 
@@ -2685,18 +2853,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].created.aaaaaa")
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .isEqualTo(
@@ -2708,7 +2886,10 @@ trait EmailSetMethodContract {
            |      "newState": "000001",
            |      "created": {
            |        "aaaaaa": {
-           |          "id": "$messageId"
+           |          "id": "$messageId",
+           |          "blobId": "$messageId",
+           |          "threadId": "$messageId",
+           |          "size": $size
            |        }
            |      }
            |    }, "c1"],
@@ -2794,18 +2975,18 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
 
     assertThatJson(response)
       .isEqualTo(
@@ -2817,7 +2998,10 @@ trait EmailSetMethodContract {
            |      "newState": "000001",
            |      "created": {
            |        "aaaaaa": {
-           |          "id": "$messageId"
+           |          "id": "$messageId",
+           |          "blobId": "$messageId",
+           |          "threadId": "$messageId",
+           |          "size": $size
            |        }
            |      }
            |    }, "c1"],
@@ -2956,80 +3140,69 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val createResponse = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = createResponse
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = createResponse
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
 
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
+      .inPath("methodResponses[0][1].created.aaaaaa")
       .isEqualTo(
         s"""{
-           |  "sessionState": "75128aab4b1b",
-           |  "methodResponses": [
-           |    ["Email/set", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "newState": "000001",
-           |      "created": {
-           |        "aaaaaa": {
-           |          "id": "$messageId"
-           |        }
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
+
+    assertThatJson(response)
+      .inPath(s"methodResponses[1][1].list[0]")
+      .isEqualTo(
+        s"""{
+           |  "id": "$messageId",
+           |  "bodyStructure": {
+           |    "type": "multipart/mixed",
+           |    "subParts": [
+           |      {
+           |        "type": "multipart/related",
+           |        "subParts": [
+           |          {
+           |            "type": "multipart/alternative",
+           |            "subParts": [
+           |              {
+           |                "type": "text/html"
+           |              },
+           |              {
+           |                "type": "text/plain"
+           |              }
+           |            ]
+           |          },
+           |          {
+           |            "type": "text/plain",
+           |            "disposition": "inline",
+           |            "cid": "abc"
+           |          },
+           |          {
+           |            "type": "text/plain",
+           |            "disposition": "inline",
+           |            "cid": "def"
+           |          }
+           |        ]
+           |      },
+           |      {
+           |        "type": "text/plain",
+           |        "disposition": "attachment"
            |      }
-           |    }, "c1"],
-           |    ["Email/get", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "state": "000001",
-           |      "list": [
-           |        {
-           |          "id": "$messageId",
-           |          "bodyStructure": {
-           |          "type": "multipart/mixed",
-           |          "subParts": [
-           |            {
-           |              "type": "multipart/related",
-           |              "subParts": [
-           |                {
-           |                  "type": "multipart/alternative",
-           |                  "subParts": [
-           |                    {
-           |                      "type": "text/html"
-           |                    },
-           |                    {
-           |                      "type": "text/plain"
-           |                    }
-           |                  ]
-           |                },
-           |                {
-           |                  "type": "text/plain",
-           |                  "disposition": "inline",
-           |                  "cid": "abc"
-           |                },
-           |                {
-           |                  "type": "text/plain",
-           |                  "disposition": "inline",
-           |                  "cid": "def"
-           |                }
-           |              ]
-           |            },
-           |            {
-           |              "type": "text/plain",
-           |              "disposition": "attachment"
-           |             }
-           |           ]
-           |         }
-           |       }
-           |     ],
-           |     "notFound": []
-           |    }, "c2"]
-           |  ]
+           |    ]
+           |  }
            |}""".stripMargin)
   }
 
@@ -3103,26 +3276,34 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val blobIdToDownload = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
-      .\("attachments")
-      .\(0)
-      .\("blobId")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
 
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[1][1].list[0].id")
-      .inPath(s"methodResponses[1][1].list")
+      .inPath("methodResponses[0][1].created.aaaaaa")
       .isEqualTo(
-        s"""[{
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
+
+    assertThatJson(response)
+      .inPath(s"methodResponses[1][1].list[0]")
+      .isEqualTo(
+        s"""{
+           |  "id": "$messageId",
            |  "mailboxIds": {
            |    "${mailboxId.serialize}": true
            |  },
@@ -3131,7 +3312,7 @@ trait EmailSetMethodContract {
            |    {
            |      "name": "myAttachment",
            |      "partId": "5",
-           |      "blobId": "$blobIdToDownload",
+           |      "blobId": "${messageId}_5",
            |      "size": 11,
            |      "type": "text/plain",
            |      "charset": "UTF-8",
@@ -3140,7 +3321,7 @@ trait EmailSetMethodContract {
            |      "location": "http://125.26.23.36/content"
            |    }
            |  ]
-           |}]""".stripMargin)
+           |}""".stripMargin)
   }
 
   @Test
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
index 6ebc2c6..accb3fb 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
@@ -202,6 +202,7 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
     keywordsMap => STRICT_KEYWORDS_FACTORY.fromSet(keywordsMap.keys.toSet)
       .fold(e => JsError(e.getMessage), keywords => JsSuccess(keywords)))
 
+  private implicit val blobIdFormat: Format[BlobId] = Json.valueFormat[BlobId]
   private implicit val unitWrites: Writes[Unit] = _ => JsNull
   private implicit val updatedWrites: Writes[Map[MessageId, Unit]] = mapWrites[MessageId, Unit](_.serialize, unitWrites)
   private implicit val notDestroyedWrites: Writes[Map[UnparsedMessageId, SetError]] = mapWrites[UnparsedMessageId, SetError](_.value, setErrorWrites)
@@ -339,7 +340,6 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
       case AsGroupedAddresses => GroupedAddressReads
     }
 
-  private implicit val blobIdReads: Reads[BlobId] = Json.valueReads[BlobId]
   private implicit val nameReads: Reads[Name] = Json.valueReads[Name]
   private implicit val charsetReads: Reads[Charset] = Json.valueReads[Charset]
   private implicit val dispositionReads: Reads[Disposition] = Json.valueReads[Disposition]
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
index 534b1ec..23fd436 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
@@ -82,7 +82,7 @@ object Email {
   type Size = Long Refined NonNegative
   val Zero: Size = 0L
 
-  private[mail] def sanitizeSize(value: Long): Size = {
+  def sanitizeSize(value: Long): Size = {
     val size: Either[String, Size] = refineV[NonNegative](value)
     size.fold(e => {
       logger.error(s"Encountered an invalid Email size: $e")
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
index ca1af42..dab3d2c 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
@@ -32,6 +32,7 @@ import org.apache.james.jmap.core.Id.Id
 import org.apache.james.jmap.core.State.State
 import org.apache.james.jmap.core.{AccountId, SetError, UTCDate}
 import org.apache.james.jmap.mail.Disposition.INLINE
+import org.apache.james.jmap.mail.Email.Size
 import org.apache.james.jmap.mail.EmailSet.{EmailCreationId, UnparsedMessageId}
 import org.apache.james.jmap.method.WithAccountId
 import org.apache.james.mailbox.exception.AttachmentNotFoundException
@@ -394,5 +395,5 @@ case class InvalidEmailPropertyException(property: String, cause: String) extend
 
 case class InvalidEmailUpdateException(property: String, cause: String) extends EmailUpdateValidationException
 
-case class EmailCreationResponse(id: MessageId)
+case class EmailCreationResponse(id: MessageId, blobId: Option[BlobId], threadId: Option[BlobId], size: Size)
 
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala
index 662c750..0bc684f 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala
@@ -29,7 +29,7 @@ import org.apache.james.jmap.core.SetError.SetErrorDescription
 import org.apache.james.jmap.core.{Properties, SetError, UTCDate}
 import org.apache.james.jmap.json.EmailSetSerializer
 import org.apache.james.jmap.mail.EmailSet.EmailCreationId
-import org.apache.james.jmap.mail.{EmailCreationRequest, EmailCreationResponse, EmailSetRequest}
+import org.apache.james.jmap.mail.{BlobId, Email, EmailCreationRequest, EmailCreationResponse, EmailSetRequest}
 import org.apache.james.jmap.method.EmailSetCreatePerformer.{CreationFailure, CreationResult, CreationResults, CreationSuccess}
 import org.apache.james.mailbox.MessageManager.AppendCommand
 import org.apache.james.mailbox.exception.{AttachmentNotFoundException, MailboxNotFoundException}
@@ -99,7 +99,9 @@ class EmailSetCreatePerformer @Inject()(serializer: EmailSetSerializer,
                 .withInternalDate(Date.from(request.receivedAt.getOrElse(UTCDate(ZonedDateTime.now())).asUTC.toInstant))
                 .build(message),
                 mailboxSession)
-            CreationSuccess(clientId, EmailCreationResponse(appendResult.getId.getMessageId))
+
+            val blobId: Option[BlobId] = BlobId.of(appendResult.getId.getMessageId).toOption
+            CreationSuccess(clientId, EmailCreationResponse(appendResult.getId.getMessageId, blobId, blobId, Email.sanitizeSize(appendResult.getSize)))
           })
             .subscribeOn(Schedulers.elastic())
             .onErrorResume(e => SMono.just[CreationResult](CreationFailure(clientId, e))))


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 05/06: JAMES-3400 Add user delete command

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit a8509f8ce3c04da7e68b712626e4b819c25cdea7
Author: quanth <hq...@linagora.com>
AuthorDate: Mon Nov 16 10:27:44 2020 +0700

    JAMES-3400 Add user delete command
---
 .../org/apache/james/cli/user/UserCommand.java     |  3 +-
 .../{UserCommand.java => UserDeleteCommand.java}   | 46 ++++++++++++----------
 .../org/apache/james/httpclient/UserClient.java    |  3 ++
 .../java/org/apache/james/cli/UserManageTest.java  | 25 ++++++++++++
 4 files changed, 56 insertions(+), 21 deletions(-)

diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java
index fdd7da1..6d42722 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java
@@ -31,7 +31,8 @@ import picocli.CommandLine;
     description = "Manage Users",
     subcommands = {
         UserListCommand.class,
-        UserCreateCommand.class
+        UserCreateCommand.class,
+        UserDeleteCommand.class
     })
 public class UserCommand implements Callable<Integer> {
 
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserDeleteCommand.java
similarity index 61%
copy from server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java
copy to server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserDeleteCommand.java
index fdd7da1..b5adab1 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserDeleteCommand.java
@@ -19,35 +19,41 @@
 
 package org.apache.james.cli.user;
 
-import java.io.PrintStream;
 import java.util.concurrent.Callable;
 
 import org.apache.james.cli.WebAdminCli;
+import org.apache.james.httpclient.UserClient;
 
+import feign.Feign;
+import feign.Response;
 import picocli.CommandLine;
 
 @CommandLine.Command(
-    name = "user",
-    description = "Manage Users",
-    subcommands = {
-        UserListCommand.class,
-        UserCreateCommand.class
-    })
-public class UserCommand implements Callable<Integer> {
-
-    protected final WebAdminCli webAdminCli;
-    protected final PrintStream out;
-    protected final PrintStream err;
-
-    public UserCommand(PrintStream out, WebAdminCli webAdminCli, PrintStream err) {
-        this.out = out;
-        this.webAdminCli = webAdminCli;
-        this.err = err;
-    }
+    name = "delete",
+    description = "Delete a user")
+public class UserDeleteCommand implements Callable<Integer> {
+
+    public static final int DELETED_CODE = 204;
+
+    @CommandLine.ParentCommand UserCommand userCommand;
+
+    @CommandLine.Parameters String userName;
 
     @Override
     public Integer call() {
-        return WebAdminCli.CLI_FINISHED_SUCCEED;
+        try {
+            UserClient userClient = Feign.builder()
+                .target(UserClient.class, userCommand.webAdminCli.jamesUrl + "/users");
+            Response rs = userClient.deleteAUser(userName);
+            if (rs.status() == DELETED_CODE) {
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
+            } else {
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            }
+        } catch (Exception e) {
+            e.printStackTrace(userCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
     }
 
-}
\ No newline at end of file
+}
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java
index 90b134b..a85da6d 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java
@@ -38,4 +38,7 @@ public interface UserClient {
     @Headers("Content-Type: application/json")
     Response createAUser(@Param("userName") String userName, UserPassword password);
 
+    @RequestLine("DELETE /{userToBeDeleted}")
+    Response deleteAUser(@Param("userToBeDeleted") String userName);
+
 }
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java
index 6804ec3..fb3821f 100644
--- a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java
+++ b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java
@@ -108,4 +108,29 @@ public class UserManageTest {
         assertThat(dataProbe.listUsers()).isEmpty();
     }
 
+    @Test
+    void userDeleteWithAddedUserShouldSucceed(GuiceJamesServer server) throws Exception {
+        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+        dataProbe = server.getProbe(DataProbeImpl.class);
+        dataProbe.fluent().addDomain("linagora.com")
+            .addUser("hqtran@linagora.com", "123456");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "user", "delete", "hqtran@linagora.com");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(dataProbe.listUsers()).isEmpty();
+    }
+
+    @Test
+    void userDeleteWithNonExistingUserShouldSucceed(GuiceJamesServer server) throws Exception {
+        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+        dataProbe = server.getProbe(DataProbeImpl.class);
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "user", "delete", "hqtran@linagora.com");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(dataProbe.listUsers()).doesNotContain("hqtran@linagora.com");
+    }
 }
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 06/06: JAMES-3400 Add user exist command

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 1495d5eabf6e2532d8a2240344311adb5d612cb3
Author: quanth <hq...@linagora.com>
AuthorDate: Mon Nov 16 11:04:07 2020 +0700

    JAMES-3400 Add user exist command
---
 .../org/apache/james/cli/user/UserCommand.java     |  3 +-
 .../apache/james/cli/user/UserExistCommand.java    | 76 ++++++++++++++++++++++
 .../org/apache/james/httpclient/UserClient.java    |  3 +
 .../java/org/apache/james/cli/UserManageTest.java  | 44 +++++++++++++
 4 files changed, 125 insertions(+), 1 deletion(-)

diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java
index 6d42722..6e27b3d 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java
@@ -32,7 +32,8 @@ import picocli.CommandLine;
     subcommands = {
         UserListCommand.class,
         UserCreateCommand.class,
-        UserDeleteCommand.class
+        UserDeleteCommand.class,
+        UserExistCommand.class
     })
 public class UserCommand implements Callable<Integer> {
 
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserExistCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserExistCommand.java
new file mode 100644
index 0000000..5d4b391
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserExistCommand.java
@@ -0,0 +1,76 @@
+/******************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one     *
+ * or more contributor license agreements.  See the NOTICE file   *
+ * distributed with this work for additional information          *
+ * regarding copyright ownership.  The ASF licenses this file     *
+ * to you under the Apache License, Version 2.0 (the              *
+ * "License"); you may not use this file except in compliance     *
+ * with the License.  You may obtain a copy of the License at     *
+ *                                                                *
+ * http://www.apache.org/licenses/LICENSE-2.0                     *
+ *                                                                *
+ * Unless required by applicable law or agreed to in writing,     *
+ * software distributed under the License is distributed on an    *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY         *
+ * KIND, either express or implied.  See the License for the      *
+ * specific language governing permissions and limitations        *
+ * under the License.                                             *
+ ******************************************************************/
+
+package org.apache.james.cli.user;
+
+import java.util.concurrent.Callable;
+
+import org.apache.james.cli.WebAdminCli;
+import org.apache.james.httpclient.UserClient;
+
+import feign.Feign;
+import feign.Response;
+import picocli.CommandLine;
+
+@CommandLine.Command(
+    name = "exist",
+    description = "Check if a user exists")
+public class UserExistCommand implements Callable<Integer> {
+
+    public static final int EXISTED_CODE = 200;
+    public static final int USER_NAME_INVALID_CODE = 400;
+    public static final int NOT_EXISTED_CODE = 404;
+
+    @CommandLine.ParentCommand UserCommand userCommand;
+
+    @CommandLine.Parameters String userName;
+
+    @Override
+    public Integer call() {
+        try {
+            UserClient userClient = Feign.builder()
+                .target(UserClient.class, userCommand.webAdminCli.jamesUrl + "/users");
+            Response rs = userClient.doesExist(userName);
+            if (rs.status() == EXISTED_CODE) {
+                userCommand.out.println(userName + " exists");
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
+            } else if (rs.status() == USER_NAME_INVALID_CODE) {
+                userCommand.out.println("The user name is invalid.\n" +
+                    "A user has two attributes: username and password. A valid user should satisfy these criteria:\n" +
+                    "-  username and password cannot be null or empty\n" +
+                    "-  username should not be longer than 255 characters\n" +
+                    "-  username can not contain '/'\n" +
+                    "-  username can not contain multiple domain delimiter('@')\n" +
+                    "-  A username can have only a local part when virtualHosting is disabled. E.g.'myUser'\n" +
+                    "-  When virtualHosting is enabled, a username should have a domain part, and the domain part " +
+                    "should be concatenated after a domain delimiter('@'). E.g. 'myuser@james.org'");
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            } else if (rs.status() == NOT_EXISTED_CODE) {
+                userCommand.out.println(userName + " does not exist");
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
+            }
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        } catch (Exception e) {
+            e.printStackTrace(userCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
+    }
+
+}
+
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java
index a85da6d..9e3a290 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java
@@ -41,4 +41,7 @@ public interface UserClient {
     @RequestLine("DELETE /{userToBeDeleted}")
     Response deleteAUser(@Param("userToBeDeleted") String userName);
 
+    @RequestLine("HEAD /{userName}")
+    Response doesExist(@Param("userName") String userName);
+
 }
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java
index fb3821f..6308bab 100644
--- a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java
+++ b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java
@@ -133,4 +133,48 @@ public class UserManageTest {
         assertThat(exitCode).isEqualTo(0);
         assertThat(dataProbe.listUsers()).doesNotContain("hqtran@linagora.com");
     }
+
+    @Test
+    void userExistCommandWithNonExistingUserShouldFail(GuiceJamesServer server) {
+        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "user", "exist", "hqtran@linagora.com");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("hqtran@linagora.com does not exist");
+    }
+
+    @Test
+    void userExistCommandWithInvalidUserNameShouldFail(GuiceJamesServer server) {
+        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "user", "exist", "hqtran@@linagora.com");
+
+        assertThat(exitCode).isEqualTo(1);
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The user name is invalid.\n" +
+            "A user has two attributes: username and password. A valid user should satisfy these criteria:\n" +
+            "-  username and password cannot be null or empty\n" +
+            "-  username should not be longer than 255 characters\n" +
+            "-  username can not contain '/'\n" +
+            "-  username can not contain multiple domain delimiter('@')\n" +
+            "-  A username can have only a local part when virtualHosting is disabled. E.g.'myUser'\n" +
+            "-  When virtualHosting is enabled, a username should have a domain part, and the domain part " +
+            "should be concatenated after a domain delimiter('@'). E.g. 'myuser@james.org'");
+    }
+
+    @Test
+    void userExistCommandWithAddedUserShouldSucceed(GuiceJamesServer server) throws Exception {
+        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+        dataProbe = server.getProbe(DataProbeImpl.class);
+        dataProbe.fluent().addDomain("linagora.com")
+            .addUser("hqtran@linagora.com", "123456");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "user", "exist", "hqtran@linagora.com");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("hqtran@linagora.com exists");
+    }
 }
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 01/06: JAMES-3337 Fixup document the use of JWT

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 5a644d270956b8da66dcadea277cd0bc01fa3dbc
Author: quanth <hq...@linagora.com>
AuthorDate: Mon Nov 16 18:01:17 2020 +0700

    JAMES-3337 Fixup document the use of JWT
---
 docs/modules/servers/pages/distributed/configure/webadmin.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/modules/servers/pages/distributed/configure/webadmin.adoc b/docs/modules/servers/pages/distributed/configure/webadmin.adoc
index 3e36c4f..a215cb3 100644
--- a/docs/modules/servers/pages/distributed/configure/webadmin.adoc
+++ b/docs/modules/servers/pages/distributed/configure/webadmin.adoc
@@ -79,7 +79,7 @@ One can use OpenSSL to generate a JWT key pair :
 The private key can be used to generate JWT tokens, for instance
 using link:https://github.com/vandium-io/jwtgen[jwtgen]:
 
-    jwtgen -a RS256 -p rs256-4096-private.rsa 4096 -c "sub=bob@domain.tld" -e 3600 -V
+    jwtgen -a RS256 -p rs256-4096-private.rsa 4096 -c "sub=bob@domain.tld" -c "admin=true" -e 3600 -V
 
 This token can then be passed as `Bearer` of the `Authorization` header :
 


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 03/06: JAMES-3400 Add user list command

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit a167674f02c0a82d6fe59ad7a5af19ba48b9430e
Author: quanth <hq...@linagora.com>
AuthorDate: Fri Nov 6 14:11:49 2020 +0700

    JAMES-3400 Add user list command
---
 .../java/org/apache/james/cli/WebAdminCli.java     |  2 +
 .../{WebAdminCli.java => user/UserCommand.java}    | 53 ++++++---------
 .../UserListCommand.java}                          | 53 ++++++---------
 .../org/apache/james/httpclient/UserClient.java    | 33 +++++++++
 .../apache/james/httpclient/model/UserName.java    | 33 +++++++++
 .../java/org/apache/james/cli/UserManageTest.java  | 78 ++++++++++++++++++++++
 6 files changed, 184 insertions(+), 68 deletions(-)

diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
index 0160461..7efdebd 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
@@ -23,6 +23,7 @@ import java.io.PrintStream;
 import java.util.concurrent.Callable;
 
 import org.apache.james.cli.domain.DomainCommand;
+import org.apache.james.cli.user.UserCommand;
 
 import picocli.CommandLine;
 
@@ -57,6 +58,7 @@ public class WebAdminCli implements Callable<Integer> {
         return new CommandLine(parent)
             .addSubcommand(new CommandLine.HelpCommand())
             .addSubcommand(new DomainCommand(out, parent, err))
+            .addSubcommand(new UserCommand(out, parent, err))
             .execute(args);
     }
 
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java
similarity index 54%
copy from server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
copy to server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java
index 0160461..92c27ae 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserCommand.java
@@ -17,51 +17,36 @@
  * under the License.                                             *
  ******************************************************************/
 
-package org.apache.james.cli;
+package org.apache.james.cli.user;
 
 import java.io.PrintStream;
 import java.util.concurrent.Callable;
 
-import org.apache.james.cli.domain.DomainCommand;
+import org.apache.james.cli.WebAdminCli;
 
 import picocli.CommandLine;
 
 @CommandLine.Command(
-    name = "james-cli",
-    description = "James Webadmin CLI")
-public class WebAdminCli implements Callable<Integer> {
-
-    public static final int CLI_FINISHED_SUCCEED = 0;
-    public static final int CLI_FINISHED_FAILED = 1;
-
-    public @CommandLine.Option(
-        names = "--url",
-        description = "James server URL",
-        defaultValue = "http://127.0.0.1:8000")
-    String jamesUrl;
+    name = "user",
+    description = "Manage Users",
+    subcommands = {
+        UserListCommand.class
+    })
+public class UserCommand implements Callable<Integer> {
+
+    protected final WebAdminCli webAdminCli;
+    protected final PrintStream out;
+    protected final PrintStream err;
+
+    public UserCommand(PrintStream out, WebAdminCli webAdminCli, PrintStream err) {
+        this.out = out;
+        this.webAdminCli = webAdminCli;
+        this.err = err;
+    }
 
     @Override
     public Integer call() {
-        return CLI_FINISHED_SUCCEED;
-    }
-
-    public static void main(String[] args) {
-        PrintStream out = System.out;
-        PrintStream err = System.err;
-        int exitCode = execute(out, err, args);
-        System.exit(exitCode);
-    }
-
-    public static int execute(PrintStream out, PrintStream err, String[] args) {
-        WebAdminCli parent = new WebAdminCli();
-        return new CommandLine(parent)
-            .addSubcommand(new CommandLine.HelpCommand())
-            .addSubcommand(new DomainCommand(out, parent, err))
-            .execute(args);
-    }
-
-    public static int executeFluent(PrintStream out, PrintStream err, String... args) {
-        return execute(out, err, args);
+        return WebAdminCli.CLI_FINISHED_SUCCEED;
     }
 
 }
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserListCommand.java
similarity index 53%
copy from server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
copy to server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserListCommand.java
index 0160461..9e2b0d7 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/user/UserListCommand.java
@@ -17,51 +17,36 @@
  * under the License.                                             *
  ******************************************************************/
 
-package org.apache.james.cli;
+package org.apache.james.cli.user;
 
-import java.io.PrintStream;
 import java.util.concurrent.Callable;
 
-import org.apache.james.cli.domain.DomainCommand;
+import org.apache.james.cli.WebAdminCli;
+import org.apache.james.httpclient.UserClient;
 
+import feign.Feign;
+import feign.jackson.JacksonDecoder;
 import picocli.CommandLine;
 
 @CommandLine.Command(
-    name = "james-cli",
-    description = "James Webadmin CLI")
-public class WebAdminCli implements Callable<Integer> {
+    name = "list",
+    description = "Show all users on the users list")
+public class UserListCommand implements Callable<Integer> {
 
-    public static final int CLI_FINISHED_SUCCEED = 0;
-    public static final int CLI_FINISHED_FAILED = 1;
-
-    public @CommandLine.Option(
-        names = "--url",
-        description = "James server URL",
-        defaultValue = "http://127.0.0.1:8000")
-    String jamesUrl;
+    @CommandLine.ParentCommand UserCommand userCommand;
 
     @Override
     public Integer call() {
-        return CLI_FINISHED_SUCCEED;
-    }
-
-    public static void main(String[] args) {
-        PrintStream out = System.out;
-        PrintStream err = System.err;
-        int exitCode = execute(out, err, args);
-        System.exit(exitCode);
-    }
-
-    public static int execute(PrintStream out, PrintStream err, String[] args) {
-        WebAdminCli parent = new WebAdminCli();
-        return new CommandLine(parent)
-            .addSubcommand(new CommandLine.HelpCommand())
-            .addSubcommand(new DomainCommand(out, parent, err))
-            .execute(args);
-    }
-
-    public static int executeFluent(PrintStream out, PrintStream err, String... args) {
-        return execute(out, err, args);
+        try {
+            UserClient userClient = Feign.builder()
+                .decoder(new JacksonDecoder())
+                .target(UserClient.class, userCommand.webAdminCli.jamesUrl + "/users");
+            userClient.getUserNameList().forEach(userName -> userCommand.out.println(userName.getUserName()));
+            return WebAdminCli.CLI_FINISHED_SUCCEED;
+        } catch (Exception e) {
+            e.printStackTrace(userCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
     }
 
 }
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java
new file mode 100644
index 0000000..deb2b2b
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/UserClient.java
@@ -0,0 +1,33 @@
+/******************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one     *
+ * or more contributor license agreements.  See the NOTICE file   *
+ * distributed with this work for additional information          *
+ * regarding copyright ownership.  The ASF licenses this file     *
+ * to you under the Apache License, Version 2.0 (the              *
+ * "License"); you may not use this file except in compliance     *
+ * with the License.  You may obtain a copy of the License at     *
+ *                                                                *
+ * http://www.apache.org/licenses/LICENSE-2.0                     *
+ *                                                                *
+ * Unless required by applicable law or agreed to in writing,     *
+ * software distributed under the License is distributed on an    *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY         *
+ * KIND, either express or implied.  See the License for the      *
+ * specific language governing permissions and limitations        *
+ * under the License.                                             *
+ ******************************************************************/
+
+package org.apache.james.httpclient;
+
+import java.util.List;
+
+import org.apache.james.httpclient.model.UserName;
+
+import feign.RequestLine;
+
+public interface UserClient {
+
+    @RequestLine("GET")
+    List<UserName> getUserNameList();
+
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/model/UserName.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/model/UserName.java
new file mode 100644
index 0000000..091e167
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/model/UserName.java
@@ -0,0 +1,33 @@
+/******************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one     *
+ * or more contributor license agreements.  See the NOTICE file   *
+ * distributed with this work for additional information          *
+ * regarding copyright ownership.  The ASF licenses this file     *
+ * to you under the Apache License, Version 2.0 (the              *
+ * "License"); you may not use this file except in compliance     *
+ * with the License.  You may obtain a copy of the License at     *
+ *                                                                *
+ * http://www.apache.org/licenses/LICENSE-2.0                     *
+ *                                                                *
+ * Unless required by applicable law or agreed to in writing,     *
+ * software distributed under the License is distributed on an    *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY         *
+ * KIND, either express or implied.  See the License for the      *
+ * specific language governing permissions and limitations        *
+ * under the License.                                             *
+ ******************************************************************/
+
+package org.apache.james.httpclient.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class UserName {
+
+    @JsonProperty("username")
+    private String userName;
+
+    public String getUserName() {
+        return userName;
+    }
+
+}
diff --git a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java
new file mode 100644
index 0000000..ebfe623
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/UserManageTest.java
@@ -0,0 +1,78 @@
+/******************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one     *
+ * or more contributor license agreements.  See the NOTICE file   *
+ * distributed with this work for additional information          *
+ * regarding copyright ownership.  The ASF licenses this file     *
+ * to you under the Apache License, Version 2.0 (the              *
+ * "License"); you may not use this file except in compliance     *
+ * with the License.  You may obtain a copy of the License at     *
+ *                                                                *
+ * http://www.apache.org/licenses/LICENSE-2.0                     *
+ *                                                                *
+ * Unless required by applicable law or agreed to in writing,     *
+ * software distributed under the License is distributed on an    *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY         *
+ * KIND, either express or implied.  See the License for the      *
+ * specific language governing permissions and limitations        *
+ * under the License.                                             *
+ ******************************************************************/
+
+package org.apache.james.cli;
+
+import static org.apache.james.MemoryJamesServerMain.IN_MEMORY_SERVER_AGGREGATE_MODULE;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.apache.james.util.Port;
+import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.WebAdminGuiceProbe;
+import org.apache.james.webadmin.integration.WebadminIntegrationTestModule;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class UserManageTest {
+
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
+        .server(configuration -> GuiceJamesServer.forConfiguration(configuration)
+            .combineWith(IN_MEMORY_SERVER_AGGREGATE_MODULE)
+            .overrideWith(new WebadminIntegrationTestModule())
+            .overrideWith(new TestJMAPServerModule()))
+        .build();
+
+    private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream();
+    private final ByteArrayOutputStream errorStreamCaptor = new ByteArrayOutputStream();
+    private DataProbeImpl dataProbe;
+
+    @Test
+    void userListShouldBeEmptyWhenNoUsers(GuiceJamesServer server) {
+        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "user", "list");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString()).isEqualTo("");
+    }
+
+    @Test
+    void userListShouldShowTwoAddedUser(GuiceJamesServer server) throws Exception {
+        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+        dataProbe = server.getProbe(DataProbeImpl.class);
+        dataProbe.fluent().addDomain("linagora.com")
+            .addUser("hqtran@linagora.com", "123456")
+            .addUser("testing@linagora.com", "123456");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "user", "list");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("hqtran@linagora.com".concat("\n").concat("testing@linagora.com").toCharArray());
+    }
+}
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org