You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2023/01/11 02:35:29 UTC

[james-project] branch master updated (5c9bcee122 -> 5b439520f3)

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

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


    from 5c9bcee122 JAMES-3756 Show the list of accounts being delegated accounts in JMAP session (#1376)
     new 5d371177b2 JAMES-3756 Introduce DelegationProbe
     new fe6539d90a JAMES-3756 Fix a space typo in UserDoesNotExistException message
     new 5b439520f3 JAMES-3756 Delegate/set create

The 3 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:
 .../exception/UserDoesNotExistException.java       |   2 +-
 .../james/jmap/rfc8621/RFC8621MethodsModule.java   |   2 +
 ....java => DistributedDelegateSetMethodTest.java} |   8 +-
 .../rfc8621/contract/DelegateSetContract.scala     | 495 +++++++++++++++++++++
 .../rfc8621/contract/probe/DelegationProbe.scala   |  42 +-
 ...dTest.java => MemoryDelegateSetMethodTest.java} |   8 +-
 .../apache/james/jmap/delegation/DelegateSet.scala |  59 +++
 .../apache/james/jmap/delegation/Delegation.scala  |  56 +++
 .../james/jmap/json/DelegationSerializer.scala     |  55 +++
 .../jmap/method/DelegateSetCreatePerformer.scala   |  95 ++++
 ...titySetMethod.scala => DelegateSetMethod.scala} |  43 +-
 11 files changed, 811 insertions(+), 54 deletions(-)
 copy server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/{DistributedIdentitySetTest.java => DistributedDelegateSetMethodTest.java} (90%)
 create mode 100644 server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/DelegateSetContract.scala
 copy mailet/standard/src/test/java/org/apache/james/transport/matchers/HasMailAttributeTest.java => server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/probe/DelegationProbe.scala (61%)
 copy server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/{MemoryIdentityGetMethodTest.java => MemoryDelegateSetMethodTest.java} (87%)
 create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateSet.scala
 create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/Delegation.scala
 create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/DelegationSerializer.scala
 create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetCreatePerformer.scala
 copy server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/{IdentitySetMethod.scala => DelegateSetMethod.scala} (66%)


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


[james-project] 02/03: JAMES-3756 Fix a space typo in UserDoesNotExistException message

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

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

commit fe6539d90aa057007619e9f5a7bd7bbe3e4a4615
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Mon Jan 9 15:09:56 2023 +0700

    JAMES-3756 Fix a space typo in UserDoesNotExistException message
---
 .../org/apache/james/mailbox/exception/UserDoesNotExistException.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/exception/UserDoesNotExistException.java b/mailbox/api/src/main/java/org/apache/james/mailbox/exception/UserDoesNotExistException.java
index 1d7be1fdf9..bcb4e56f21 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/exception/UserDoesNotExistException.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/exception/UserDoesNotExistException.java
@@ -26,7 +26,7 @@ public class UserDoesNotExistException extends MailboxException {
     private final Username name;
 
     public UserDoesNotExistException(Username name) {
-        super("User " + name.asString() + "does not exist");
+        super("User " + name.asString() + " does not exist");
         this.name = name;
     }
 


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


[james-project] 03/03: JAMES-3756 Delegate/set create

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

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

commit 5b439520f304a4f7d1a4ce932734df36b2a390e5
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Mon Jan 9 15:10:53 2023 +0700

    JAMES-3756 Delegate/set create
---
 .../james/jmap/rfc8621/RFC8621MethodsModule.java   |   2 +
 .../DistributedDelegateSetMethodTest.java          |  57 +++
 .../rfc8621/contract/DelegateSetContract.scala     | 495 +++++++++++++++++++++
 .../memory/MemoryDelegateSetMethodTest.java        |  44 ++
 .../apache/james/jmap/delegation/DelegateSet.scala |  59 +++
 .../apache/james/jmap/delegation/Delegation.scala  |  56 +++
 .../james/jmap/json/DelegationSerializer.scala     |  55 +++
 .../jmap/method/DelegateSetCreatePerformer.scala   |  95 ++++
 .../james/jmap/method/DelegateSetMethod.scala      |  68 +++
 9 files changed, 931 insertions(+)

diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
index 7889f3ba3c..c6293fe9ac 100644
--- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
+++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
@@ -47,6 +47,7 @@ import org.apache.james.jmap.http.rfc8621.InjectionKeys;
 import org.apache.james.jmap.mail.DefaultNamespaceFactory;
 import org.apache.james.jmap.mail.NamespaceFactory;
 import org.apache.james.jmap.method.CoreEchoMethod;
+import org.apache.james.jmap.method.DelegateSetMethod;
 import org.apache.james.jmap.method.EmailChangesMethod;
 import org.apache.james.jmap.method.EmailGetMethod;
 import org.apache.james.jmap.method.EmailImportMethod;
@@ -152,6 +153,7 @@ public class RFC8621MethodsModule extends AbstractModule {
         methods.addBinding().to(ThreadGetMethod.class);
         methods.addBinding().to(VacationResponseGetMethod.class);
         methods.addBinding().to(VacationResponseSetMethod.class);
+        methods.addBinding().to(DelegateSetMethod.class);
 
         Multibinder<JMAPRoutes> routes = Multibinder.newSetBinder(binder(), JMAPRoutes.class);
         routes.addBinding().to(SessionRoutes.class);
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedDelegateSetMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedDelegateSetMethodTest.java
new file mode 100644
index 0000000000..681da1bcbe
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedDelegateSetMethodTest.java
@@ -0,0 +1,57 @@
+/****************************************************************
+ * 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.jmap.rfc8621.distributed;
+
+import org.apache.james.CassandraExtension;
+import org.apache.james.CassandraRabbitMQJamesConfiguration;
+import org.apache.james.CassandraRabbitMQJamesServerMain;
+import org.apache.james.DockerOpenSearchExtension;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.jmap.rfc8621.contract.DelegateSetContract;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.apache.james.modules.AwsS3BlobStoreExtension;
+import org.apache.james.modules.RabbitMQExtension;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.apache.james.modules.blobstore.BlobStoreConfiguration;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class DistributedDelegateSetMethodTest implements DelegateSetContract {
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<CassandraRabbitMQJamesConfiguration>(tmpDir ->
+        CassandraRabbitMQJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .blobStore(BlobStoreConfiguration.builder()
+                .s3()
+                .disableCache()
+                .deduplication()
+                .noCryptoConfig())
+            .searchConfiguration(SearchConfiguration.openSearch())
+            .build())
+        .extension(new DockerOpenSearchExtension())
+        .extension(new CassandraExtension())
+        .extension(new RabbitMQExtension())
+        .extension(new AwsS3BlobStoreExtension())
+        .server(configuration -> CassandraRabbitMQJamesServerMain.createServer(configuration)
+            .overrideWith(new TestJMAPServerModule(), new DelegationProbeModule()))
+        .build();
+}
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/DelegateSetContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/DelegateSetContract.scala
new file mode 100644
index 0000000000..0d9c8d6073
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/DelegateSetContract.scala
@@ -0,0 +1,495 @@
+/****************************************************************
+ * 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.jmap.rfc8621.contract
+
+import java.util.concurrent.TimeUnit
+
+import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
+import io.restassured.RestAssured.{`given`, requestSpecification}
+import io.restassured.http.ContentType.JSON
+import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
+import org.apache.http.HttpStatus.SC_OK
+import org.apache.james.GuiceJamesServer
+import org.apache.james.jmap.delegation.DelegationId
+import org.apache.james.jmap.http.UserCredential
+import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ANDRE, ANDRE_PASSWORD, BOB, BOB_PASSWORD, CEDRIC, DOMAIN, authScheme, baseRequestSpecBuilder}
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbe
+import org.apache.james.utils.DataProbeImpl
+import org.assertj.core.api.Assertions.assertThat
+import org.awaitility.Awaitility
+import org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS
+import org.junit.jupiter.api.{BeforeEach, Test}
+
+import scala.jdk.CollectionConverters._
+
+trait DelegateSetContract {
+  private lazy val slowPacedPollInterval = ONE_HUNDRED_MILLISECONDS
+  private lazy val calmlyAwait = Awaitility.`with`
+    .pollInterval(slowPacedPollInterval)
+    .and.`with`.pollDelay(slowPacedPollInterval)
+    .await
+  private lazy val awaitAtMostTenSeconds = calmlyAwait.atMost(10, TimeUnit.SECONDS)
+
+  @BeforeEach
+  def setUp(server: GuiceJamesServer): Unit = {
+    server.getProbe(classOf[DataProbeImpl])
+      .fluent
+      .addDomain(DOMAIN.asString)
+      .addUser(BOB.asString, BOB_PASSWORD)
+      .addUser(ANDRE.asString(), ANDRE_PASSWORD)
+      .addUser(CEDRIC.asString(), "secret")
+
+    requestSpecification = baseRequestSpecBuilder(server)
+      .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
+      .build
+  }
+
+  @Test
+  def delegateSetShouldSucceed(server: GuiceJamesServer): Unit = {
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:apache:james:params:jmap:delegation"],
+         |	"methodCalls": [
+         |		[
+         |			"Delegate/set", {
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"create": {
+         |					"4f29": {
+         |						"username": "andre@domain.tld"
+         |					}
+         |				}
+         |			}, "0"
+         |		]
+         |	]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+    val delegationId = DelegationId.from(BOB, ANDRE).serialize
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"methodResponses": [
+           |		["Delegate/set", {
+           |			"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |			"newState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |			"created": {
+           |				"4f29": {
+           |					"id": "$delegationId"
+           |				}
+           |			}
+           |		}, "0"]
+           |	]
+           |}""".stripMargin)
+
+    awaitAtMostTenSeconds.untilAsserted(() =>
+      assertThat(server.getProbe(classOf[DelegationProbe]).getAuthorizedUsers(BOB).asJavaCollection)
+        .containsExactly(ANDRE))
+  }
+
+  @Test
+  def delegateSetWithSeveralCreationRequestsShouldSucceed(server: GuiceJamesServer): Unit = {
+    val delegationProbe = server.getProbe(classOf[DelegationProbe])
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:apache:james:params:jmap:delegation"],
+         |	"methodCalls": [
+         |		[
+         |			"Delegate/set", {
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"create": {
+         |					"4f29": {
+         |						"username": "andre@domain.tld"
+         |					},
+         |					"4f30": {
+         |						"username": "cedric@domain.tld"
+         |					}
+         |				}
+         |			}, "0"
+         |		]
+         |	]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+    val andreDelegationId = DelegationId.from(BOB, ANDRE).serialize
+    val cedricDelegationId = DelegationId.from(BOB, CEDRIC).serialize
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"methodResponses": [
+           |		["Delegate/set", {
+           |			"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |			"newState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |			"created": {
+           |				"4f29": {
+           |					"id": "$andreDelegationId"
+           |				},
+           |				"4f30": {
+           |					"id": "$cedricDelegationId"
+           |				}
+           |			}
+           |		}, "0"]
+           |	]
+           |}""".stripMargin)
+
+    awaitAtMostTenSeconds.untilAsserted(() =>
+      assertThat(delegationProbe.getAuthorizedUsers(BOB).asJavaCollection)
+        .containsExactlyInAnyOrder(ANDRE, CEDRIC))
+  }
+
+  @Test
+  def delegateSetShouldFailWhenUsernamePropertyIsMissing(): Unit = {
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:apache:james:params:jmap:delegation"],
+         |	"methodCalls": [
+         |		[
+         |			"Delegate/set", {
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"create": {
+         |					"4f29": {
+         |					}
+         |				}
+         |			}, "0"
+         |		]
+         |	]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"methodResponses": [
+           |		[
+           |			"Delegate/set",
+           |			{
+           |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |				"newState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |				"notCreated": {
+           |					"4f29": {
+           |						"type": "invalidArguments",
+           |						"description": "Missing '/username' property in Delegate object"
+           |					}
+           |				}
+           |			},
+           |			"0"
+           |		]
+           |	]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def delegateSetShouldFailWhenUsernameIsNull(): Unit = {
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:apache:james:params:jmap:delegation"],
+         |	"methodCalls": [
+         |		[
+         |			"Delegate/set", {
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"create": {
+         |					"4f29": {
+         |						"username": null
+         |					}
+         |				}
+         |			}, "0"
+         |		]
+         |	]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"methodResponses": [
+           |		[
+           |			"Delegate/set",
+           |			{
+           |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |				"newState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |				"notCreated": {
+           |					"4f29": {
+           |						"type": "invalidArguments",
+           |						"description": "'/username' property in Delegate object is not valid: username needs to be represented by a JsString"
+           |					}
+           |				}
+           |			},
+           |			"0"
+           |		]
+           |	]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def delegateSetShouldFailWhenWrongAccountId(): Unit = {
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:apache:james:params:jmap:delegation"],
+         |	"methodCalls": [
+         |		[
+         |			"Delegate/set", {
+         |				"accountId": "unknownAccountId",
+         |				"create": {
+         |					"4f29": {
+         |						"username": "andre@domain.tld"
+         |					}
+         |				}
+         |			}, "0"
+         |		]
+         |	]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"methodResponses": [
+           |		[
+           |			"error",
+           |			{
+           |				"type": "accountNotFound"
+           |			},
+           |			"0"
+           |		]
+           |	]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def delegateSetShouldFailWhenMissingDelegationCapability(): Unit = {
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core"],
+         |	"methodCalls": [
+         |		[
+         |			"Delegate/set", {
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"create": {
+         |					"4f29": {
+         |						"username": "andre@domain.tld"
+         |					}
+         |				}
+         |			}, "0"
+         |		]
+         |	]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"methodResponses": [
+           |		[
+           |			"error",
+           |			{
+           |				"type": "unknownMethod",
+           |				"description": "Missing capability(ies): urn:apache:james:params:jmap:delegation"
+           |			},
+           |			"0"
+           |		]
+           |	]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def delegateSetShouldFailWhenUserDoesNotExist(): Unit = {
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:apache:james:params:jmap:delegation"],
+         |	"methodCalls": [
+         |		[
+         |			"Delegate/set", {
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"create": {
+         |					"4f29": {
+         |						"username": "nonexistuser@domain.tld"
+         |					}
+         |				}
+         |			}, "0"
+         |		]
+         |	]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"methodResponses": [
+           |		[
+           |			"Delegate/set",
+           |			{
+           |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |				"newState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |				"notCreated": {
+           |					"4f29": {
+           |						"type": "invalidArguments",
+           |						"description": "User nonexistuser@domain.tld does not exist"
+           |					}
+           |				}
+           |			},
+           |			"0"
+           |		]
+           |	]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def delegateSetShouldBeIdempotent(server: GuiceJamesServer): Unit = {
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:apache:james:params:jmap:delegation"],
+         |	"methodCalls": [
+         |		[
+         |			"Delegate/set", {
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"create": {
+         |					"4f29": {
+         |						"username": "andre@domain.tld"
+         |					},
+         |					"4f30": {
+         |						"username": "andre@domain.tld"
+         |					}
+         |				}
+         |			}, "0"
+         |		]
+         |	]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+    val delegationId = DelegationId.from(BOB, ANDRE).serialize
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"methodResponses": [
+           |		["Delegate/set", {
+           |			"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |			"newState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |			"created": {
+           |				"4f29": {
+           |					"id": "$delegationId"
+           |				},
+           |				"4f30": {
+           |					"id": "$delegationId"
+           |				}
+           |			}
+           |		}, "0"]
+           |	]
+           |}""".stripMargin)
+
+    awaitAtMostTenSeconds.untilAsserted(() =>
+      assertThat(server.getProbe(classOf[DelegationProbe]).getAuthorizedUsers(BOB).asJavaCollection)
+        .containsExactly(ANDRE))
+  }
+}
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryDelegateSetMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryDelegateSetMethodTest.java
new file mode 100644
index 0000000000..d80abf70f5
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryDelegateSetMethodTest.java
@@ -0,0 +1,44 @@
+/****************************************************************
+ * 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.jmap.rfc8621.memory;
+
+import static org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT;
+
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.MemoryJamesConfiguration;
+import org.apache.james.MemoryJamesServerMain;
+import org.apache.james.jmap.rfc8621.contract.DelegateSetContract;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class MemoryDelegateSetMethodTest implements DelegateSetContract {
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<MemoryJamesConfiguration>(tmpDir ->
+        MemoryJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .usersRepository(DEFAULT)
+            .build())
+        .server(configuration -> MemoryJamesServerMain.createServer(configuration)
+            .overrideWith(new TestJMAPServerModule(), new DelegationProbeModule()))
+        .build();
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateSet.scala
new file mode 100644
index 0000000000..203a0ca498
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateSet.scala
@@ -0,0 +1,59 @@
+/****************************************************************
+ * 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.jmap.delegation
+
+import org.apache.james.core.Username
+import org.apache.james.jmap.core.Id.Id
+import org.apache.james.jmap.core.SetError.SetErrorDescription
+import org.apache.james.jmap.core.{AccountId, SetError, UuidState}
+import org.apache.james.jmap.method.WithAccountId
+import play.api.libs.json.{JsObject, JsPath, JsonValidationError}
+
+case class DelegateCreationId(id: Id) {
+  def serialize: String = id.value
+}
+
+case class DelegateSetRequest(accountId: AccountId,
+                              create: Option[Map[DelegateCreationId, JsObject]]) extends WithAccountId
+
+case class DelegateCreationRequest(username: Username)
+
+case class DelegateCreationResponse(id: DelegationId)
+
+case class DelegateSetResponse(accountId: AccountId,
+                               oldState: Option[UuidState],
+                               newState: UuidState,
+                               created: Option[Map[DelegateCreationId, DelegateCreationResponse]],
+                               notCreated: Option[Map[DelegateCreationId, SetError]])
+
+case class DelegateSetParseException(setError: SetError) extends IllegalArgumentException
+
+object DelegateSetParseException {
+  def from(errors: collection.Seq[(JsPath, collection.Seq[JsonValidationError])]): DelegateSetParseException = {
+    val setError: SetError = errors.head match {
+      case (path, Seq()) => SetError.invalidArguments(SetErrorDescription(s"'$path' property in Delegate object is not valid"))
+      case (path, Seq(JsonValidationError(Seq("error.path.missing")))) =>
+        SetError.invalidArguments(SetErrorDescription(s"Missing '$path' property in Delegate object"))
+      case (path, Seq(JsonValidationError(Seq(message)))) => SetError.invalidArguments(SetErrorDescription(s"'$path' property in Delegate object is not valid: $message"))
+      case (path, _) => SetError.invalidArguments(SetErrorDescription(s"Unknown error on property '$path'"))
+    }
+    DelegateSetParseException(setError)
+  }
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/Delegation.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/Delegation.scala
new file mode 100644
index 0000000000..c47333bdee
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/Delegation.scala
@@ -0,0 +1,56 @@
+/****************************************************************
+ * 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.jmap.delegation
+
+import java.nio.charset.StandardCharsets
+import java.util.UUID
+
+import org.apache.james.core.Username
+import org.apache.james.jmap.core.SetError.SetErrorDescription
+import org.apache.james.jmap.core.{Properties, SetError}
+import play.api.libs.json.JsObject
+
+object DelegationCreation {
+  val serverSetProperty: Set[String] = Set("id")
+  val assignableProperties: Set[String] = Set("username")
+  val knownProperties: Set[String] = assignableProperties ++ serverSetProperty
+
+  def validateProperties(serverSetProperty: Set[String], knownProperties: Set[String], jsObject: JsObject): Either[DelegateSetParseException, JsObject] =
+    (jsObject.keys.intersect(serverSetProperty), jsObject.keys.diff(knownProperties)) match {
+      case (_, unknownProperties) if unknownProperties.nonEmpty =>
+        Left(DelegateSetParseException(SetError.invalidArguments(
+          SetErrorDescription("Some unknown properties were specified"),
+          Some(Properties.toProperties(unknownProperties.toSet)))))
+      case (specifiedServerSetProperties, _) if specifiedServerSetProperties.nonEmpty =>
+        Left(DelegateSetParseException(SetError.invalidArguments(
+          SetErrorDescription("Some server-set properties were specified"),
+          Some(Properties.toProperties(specifiedServerSetProperties.toSet)))))
+      case _ => scala.Right(jsObject)
+    }
+}
+
+object DelegationId {
+  def from(baseUser: Username, targetUser: Username): DelegationId =
+    DelegationId(UUID.nameUUIDFromBytes((baseUser.asString() + targetUser.asString()).getBytes(StandardCharsets.UTF_8)))
+}
+
+case class DelegationId(id: UUID) {
+  def serialize: String = id.toString
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/DelegationSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/DelegationSerializer.scala
new file mode 100644
index 0000000000..613381b417
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/DelegationSerializer.scala
@@ -0,0 +1,55 @@
+/****************************************************************
+ * 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.jmap.json
+
+import eu.timepit.refined.refineV
+import org.apache.james.core.Username
+import org.apache.james.jmap.core.Id.IdConstraint
+import org.apache.james.jmap.core.{SetError, UuidState}
+import org.apache.james.jmap.delegation.{DelegateCreationId, DelegateCreationRequest, DelegateCreationResponse, DelegateSetRequest, DelegateSetResponse, DelegationId}
+import play.api.libs.json.{Format, JsError, JsObject, JsResult, JsString, JsSuccess, JsValue, Json, OWrites, Reads, Writes}
+
+object DelegationSerializer {
+  private implicit val delegationIdFormat: Format[DelegationId] = Json.valueFormat[DelegationId]
+  private implicit val userReads: Reads[Username] = {
+    case JsString(userAsString) => JsSuccess(Username.of(userAsString))
+    case _ => JsError("username needs to be represented by a JsString")
+  }
+  private implicit val stateWrites: Writes[UuidState] = Json.valueWrites[UuidState]
+  private implicit val delegateCreationIdWrites: Writes[DelegateCreationId] = Json.valueWrites[DelegateCreationId]
+  private implicit val delegateCreationResponseWrites: Writes[DelegateCreationResponse] = Json.writes[DelegateCreationResponse]
+  private implicit val delegateSetMapCreationResponseWrites: Writes[Map[DelegateCreationId, DelegateCreationResponse]] =
+    mapWrites[DelegateCreationId, DelegateCreationResponse](_.serialize, delegateCreationResponseWrites)
+  private implicit val delegateSetMapSetErrorForCreationWrites: Writes[Map[DelegateCreationId, SetError]] =
+    mapWrites[DelegateCreationId, SetError](_.serialize, setErrorWrites)
+  private implicit val delegateSetResponseWrites: OWrites[DelegateSetResponse] = Json.writes[DelegateSetResponse]
+
+  private implicit val mapCreationRequestByDelegateCreationId: Reads[Map[DelegateCreationId, JsObject]] =
+    Reads.mapReads[DelegateCreationId, JsObject] {string => refineV[IdConstraint](string)
+      .fold(e => JsError(s"delegate creationId needs to match id constraints: $e"),
+        id => JsSuccess(DelegateCreationId(id)))
+    }
+  private implicit val delegateSetRequestReads: Reads[DelegateSetRequest] = Json.reads[DelegateSetRequest]
+  private implicit val delegateCreationRequest: Reads[DelegateCreationRequest] = Json.reads[DelegateCreationRequest]
+
+  def serializeDelegateSetResponse(response: DelegateSetResponse): JsObject = Json.toJsObject(response)
+  def deserializeDelegateSetRequest(input: JsValue): JsResult[DelegateSetRequest] = Json.fromJson[DelegateSetRequest](input)
+  def deserializeDelegateCreationRequest(input: JsValue): JsResult[DelegateCreationRequest] = Json.fromJson[DelegateCreationRequest](input)
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetCreatePerformer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetCreatePerformer.scala
new file mode 100644
index 0000000000..789ad15a13
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetCreatePerformer.scala
@@ -0,0 +1,95 @@
+/****************************************************************
+ * 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.jmap.method
+
+import javax.inject.Inject
+import org.apache.james.jmap.core.SetError
+import org.apache.james.jmap.core.SetError.SetErrorDescription
+import org.apache.james.jmap.delegation.DelegationCreation.{knownProperties, serverSetProperty}
+import org.apache.james.jmap.delegation.{DelegateCreationId, DelegateCreationRequest, DelegateCreationResponse, DelegateSetParseException, DelegateSetRequest, DelegationCreation, DelegationId}
+import org.apache.james.jmap.json.DelegationSerializer
+import org.apache.james.jmap.method.DelegateSetCreatePerformer.{CreationFailure, CreationResult, CreationResults, CreationSuccess}
+import org.apache.james.mailbox.MailboxSession
+import org.apache.james.mailbox.exception.UserDoesNotExistException
+import org.apache.james.user.api.{DelegationStore, UsersRepository}
+import play.api.libs.json.JsObject
+import reactor.core.scala.publisher.{SFlux, SMono}
+
+object DelegateSetCreatePerformer {
+  case class CreationResults(results: Seq[CreationResult]) {
+    def created: Option[Map[DelegateCreationId, DelegateCreationResponse]] =
+      Option(results.flatMap {
+        case result: CreationSuccess => Some((result.delegateCreationId, result.response))
+        case _ => None
+      }.toMap)
+        .filter(_.nonEmpty)
+
+    def notCreated: Option[Map[DelegateCreationId, SetError]] =
+      Option(results.flatMap {
+        case failure: CreationFailure => Some((failure.delegateCreationId, failure.asMessageSetError))
+        case _ => None
+      }.toMap)
+        .filter(_.nonEmpty)
+  }
+
+  trait CreationResult
+
+  case class CreationSuccess(delegateCreationId: DelegateCreationId, response: DelegateCreationResponse) extends CreationResult
+
+  case class CreationFailure(delegateCreationId: DelegateCreationId, e: Throwable) extends CreationResult {
+    def asMessageSetError: SetError = e match {
+      case e: DelegateSetParseException => e.setError
+      case e: UserDoesNotExistException => SetError.invalidArguments(SetErrorDescription(e.getMessage))
+      case e: IllegalArgumentException => SetError.invalidArguments(SetErrorDescription(e.getMessage))
+      case _ => SetError.serverFail(SetErrorDescription(e.getMessage))
+    }
+  }
+}
+
+class DelegateSetCreatePerformer @Inject()(delegationStore: DelegationStore,
+                                           usersRepository: UsersRepository) {
+  def create(request: DelegateSetRequest, mailboxSession: MailboxSession): SMono[CreationResults] =
+    SFlux.fromIterable(request.create.getOrElse(Map()))
+      .concatMap {
+        case (delegateCreationId, json) => parseCreate(json)
+          .fold(e => SMono.just[CreationResult](CreationFailure(delegateCreationId, e)),
+            creationRequest => create(delegateCreationId, creationRequest, mailboxSession))
+      }.collectSeq()
+      .map(CreationResults)
+
+  private def parseCreate(jsObject: JsObject): Either[Exception, DelegateCreationRequest] = for {
+    validJsObject <- DelegationCreation.validateProperties(serverSetProperty, knownProperties, jsObject)
+    parsedRequest <- DelegationSerializer.deserializeDelegateCreationRequest(validJsObject).asEither
+      .left.map(errors => DelegateSetParseException.from(errors))
+  } yield {
+    parsedRequest
+  }
+
+  private def create(delegateCreationId: DelegateCreationId, request: DelegateCreationRequest, mailboxSession: MailboxSession): SMono[CreationResult] =
+    SMono.fromPublisher(usersRepository.containsReactive(request.username))
+      .filter(bool => bool)
+      .flatMap(_ => SMono.fromPublisher(delegationStore.addAuthorizedUser(mailboxSession.getUser, request.username))
+        .`then`(SMono.just[CreationResult](CreationSuccess(delegateCreationId, evaluateCreationResponse(request, mailboxSession))))
+        .onErrorResume(e => SMono.just[CreationResult](CreationFailure(delegateCreationId, e))))
+      .switchIfEmpty(SMono.just[CreationResult](CreationFailure(delegateCreationId, new UserDoesNotExistException(request.username))))
+
+  private def evaluateCreationResponse(request: DelegateCreationRequest, mailboxSession: MailboxSession): DelegateCreationResponse =
+    DelegateCreationResponse(id = DelegationId.from(mailboxSession.getUser, request.username))
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetMethod.scala
new file mode 100644
index 0000000000..ae62250cf7
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetMethod.scala
@@ -0,0 +1,68 @@
+/****************************************************************
+ * 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.jmap.method
+
+import eu.timepit.refined.auto._
+import javax.inject.Inject
+import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JAMES_DELEGATION, JMAP_CORE}
+import org.apache.james.jmap.core.Invocation.{Arguments, MethodName}
+import org.apache.james.jmap.core.{ClientId, Id, Invocation, ServerId, UuidState}
+import org.apache.james.jmap.delegation.{DelegateSetRequest, DelegateSetResponse}
+import org.apache.james.jmap.json.{DelegationSerializer, ResponseSerializer}
+import org.apache.james.jmap.routes.SessionSupplier
+import org.apache.james.mailbox.MailboxSession
+import org.apache.james.metrics.api.MetricFactory
+import play.api.libs.json.{JsError, JsSuccess}
+import reactor.core.scala.publisher.SMono
+
+class DelegateSetMethod @Inject()(createPerformer: DelegateSetCreatePerformer,
+                                  val metricFactory: MetricFactory,
+                                  val sessionSupplier: SessionSupplier) extends MethodRequiringAccountId[DelegateSetRequest] {
+  override val methodName: Invocation.MethodName = MethodName("Delegate/set")
+  override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE, JAMES_DELEGATION)
+
+  override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, DelegateSetRequest] =
+    DelegationSerializer.deserializeDelegateSetRequest(invocation.arguments.value) match {
+      case JsSuccess(delegateSetRequest, _) => Right(delegateSetRequest)
+      case errors: JsError => Left(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
+    }
+
+  override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: DelegateSetRequest): SMono[InvocationWithContext] =
+    for {
+      creationResults <- createPerformer.create(request, mailboxSession)
+    } yield InvocationWithContext(
+      invocation = Invocation(
+        methodName = methodName,
+        arguments = Arguments(DelegationSerializer.serializeDelegateSetResponse(DelegateSetResponse(
+          accountId = request.accountId,
+          oldState = None,
+          newState = UuidState.INSTANCE,
+          created = creationResults.created.filter(_.nonEmpty),
+          notCreated = creationResults.notCreated.filter(_.nonEmpty)))),
+        methodCallId = invocation.invocation.methodCallId),
+      processingContext = creationResults.created.getOrElse(Map())
+        .foldLeft(invocation.processingContext)({
+          case (processingContext, (clientId, response)) =>
+            Id.validate(response.id.serialize)
+              .fold(_ => processingContext,
+                serverId => processingContext.recordCreatedId(ClientId(clientId.id), ServerId(serverId)))
+        }))
+}
+


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


[james-project] 01/03: JAMES-3756 Introduce DelegationProbe

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

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

commit 5d371177b2c15dccf008829a71cac50b50588a80
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Mon Jan 9 15:09:05 2023 +0700

    JAMES-3756 Introduce DelegationProbe
---
 .../rfc8621/contract/probe/DelegationProbe.scala   | 42 ++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/probe/DelegationProbe.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/probe/DelegationProbe.scala
new file mode 100644
index 0000000000..b966141db6
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/probe/DelegationProbe.scala
@@ -0,0 +1,42 @@
+/****************************************************************
+ * 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.jmap.rfc8621.contract.probe
+
+import com.google.inject.AbstractModule
+import com.google.inject.multibindings.Multibinder
+import javax.inject.Inject
+import org.apache.james.core.Username
+import org.apache.james.user.api.DelegationStore
+import org.apache.james.utils.GuiceProbe
+import reactor.core.scala.publisher.SFlux
+
+class DelegationProbeModule extends AbstractModule {
+  override def configure(): Unit =
+    Multibinder.newSetBinder(binder(), classOf[GuiceProbe])
+      .addBinding()
+      .to(classOf[DelegationProbe])
+}
+
+class DelegationProbe @Inject()(delegationStore: DelegationStore) extends GuiceProbe {
+  def getAuthorizedUsers(baseUser: Username): Seq[Username] =
+    SFlux.fromPublisher(delegationStore.authorizedUsers(baseUser))
+      .collectSeq()
+      .block()
+}


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