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 2021/11/24 01:43:36 UTC

[james-project] branch master updated (398aa92 -> 03a3229)

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 398aa92  JAMES-3673 : Separate trust store for S3 (#751)
     new 4f357ba  JAMES-3534 Implement Identity/set create
     new 56b9589  JAMES-3534 Identity/set create contract and memory tests
     new 57764bc  JAMES-3534 Check if a user can send from an address before save it as custom identity
     new 2c35af6  JAMES-3534 Introduce DistributedIdentitySetTest
     new 231604f  JAMES-3534 Identity/set create should record succeeded creation ids within the processing context
     new 2b9a953  JAMES-3534 Identity/set contract test should ignore array order
     new f577b6c  JAMES-3534 Test for Identity/set create should record succeeded creation ids within the processing context
     new 03a3229  JAMES-3225 Remove James generated dependencies from the maven local repository in the Jenkinsfile

The 8 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:
 Jenkinsfile                                        |   2 +
 .../james/jmap/rfc8621/RFC8621MethodsModule.java   |   2 +
 .../jmap/api/identity/CustomIdentityDAO.scala      |  15 +-
 .../org/apache/james/jmap/api/model/Identity.scala |   6 +-
 ...teTest.java => DistributedIdentitySetTest.java} |   9 +-
 .../rfc8621/contract/IdentitySetContract.scala     | 764 +++++++++++++++++++++
 ...Test.java => MemoryIdentitySetMethodTests.java} |   4 +-
 .../james/jmap/json/IdentitySerializer.scala       |  37 +-
 .../org/apache/james/jmap/mail/IdentitySet.scala   |  76 ++
 .../jmap/method/IdentitySetCreatePerformer.scala   | 108 +++
 ...ChangesMethod.scala => IdentitySetMethod.scala} |  66 +-
 11 files changed, 1037 insertions(+), 52 deletions(-)
 copy server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/{DistributedSessionRouteTest.java => DistributedIdentitySetTest.java} (91%)
 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/IdentitySetContract.scala
 copy server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/{MemoryEventSourceTest.java => MemoryIdentitySetMethodTests.java} (92%)
 create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/IdentitySet.scala
 create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetCreatePerformer.scala
 copy server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/{ThreadChangesMethod.scala => IdentitySetMethod.scala} (52%)

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


[james-project] 03/08: JAMES-3534 Check if a user can send from an address before save it as custom identity

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 57764bcc82244599082fc8361f30908cab3f4fca
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Wed Nov 17 14:48:12 2021 +0700

    JAMES-3534 Check if a user can send from an address before save it as custom identity
---
 .../james/jmap/api/identity/CustomIdentityDAO.scala       | 15 ++++++++++++---
 .../scala/org/apache/james/jmap/api/model/Identity.scala  |  2 +-
 2 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
index bd30740..155e68e 100644
--- a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
+++ b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
@@ -25,8 +25,9 @@ import java.util.UUID
 import com.google.common.collect.ImmutableList
 import javax.inject.Inject
 import org.apache.james.core.{MailAddress, Username}
-import org.apache.james.jmap.api.model.{EmailAddress, HtmlSignature, Identity, IdentityId, IdentityName, MayDeleteIdentity, PushSubscriptionCreationRequest, TextSignature}
+import org.apache.james.jmap.api.model.{EmailAddress, ForbiddenSendFromException, HtmlSignature, Identity, IdentityId, IdentityName, MayDeleteIdentity, TextSignature}
 import org.apache.james.rrt.api.CanSendFrom
+import org.apache.james.user.api.UsersRepository
 import org.reactivestreams.Publisher
 import reactor.core.scala.publisher.{SFlux, SMono}
 import reactor.core.scheduler.Schedulers
@@ -83,7 +84,7 @@ trait CustomIdentityDAO {
   def delete(username: Username, ids: Seq[IdentityId]): Publisher[Unit]
 }
 
-class DefaultIdentitySupplier @Inject()(canSendFrom: CanSendFrom) {
+class DefaultIdentitySupplier @Inject()(canSendFrom: CanSendFrom, usersRepository: UsersRepository) {
   def listIdentities(username: Username): List[Identity] =
     canSendFrom.allValidFromAddressesForUser(username)
       .collect(ImmutableList.toImmutableList()).asScala.toList
@@ -99,6 +100,9 @@ class DefaultIdentitySupplier @Inject()(canSendFrom: CanSendFrom) {
             htmlSignature = HtmlSignature.DEFAULT,
             mayDelete = MayDeleteIdentity(false))))
 
+  def userCanSendFrom(username: Username, mailAddress: MailAddress): Boolean =
+    canSendFrom.userCanSendFrom(username, usersRepository.getUsername(mailAddress))
+
   private def from(address: MailAddress): Option[IdentityId] =
     Try(UUID.nameUUIDFromBytes(address.asString().getBytes(StandardCharsets.UTF_8)))
       .toEither
@@ -109,7 +113,12 @@ class DefaultIdentitySupplier @Inject()(canSendFrom: CanSendFrom) {
 // This class is intended to merge default (server-set0 identities with (user defined) custom identities
 // Using the custom identities we can stores deltas of the default (server-set) identities allowing to modify them.
 class IdentityRepository @Inject()(customIdentityDao: CustomIdentityDAO, identityFactory: DefaultIdentitySupplier) {
-  def save(user: Username, creationRequest: IdentityCreationRequest): Publisher[Identity] = customIdentityDao.save(user, creationRequest)
+  def save(user: Username, creationRequest: IdentityCreationRequest): Publisher[Identity] =
+    if (identityFactory.userCanSendFrom(user, creationRequest.email)) {
+      customIdentityDao.save(user, creationRequest)
+    } else {
+      SMono.error(ForbiddenSendFromException(creationRequest.email))
+    }
 
   def list(user: Username): Publisher[Identity] = SFlux.merge(Seq(
     customIdentityDao.list(user),
diff --git a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/Identity.scala b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/Identity.scala
index acf65b4..58a9cc1 100644
--- a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/Identity.scala
+++ b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/Identity.scala
@@ -51,4 +51,4 @@ case class Identity(id: IdentityId,
                     htmlSignature: HtmlSignature,
                     mayDelete: MayDeleteIdentity)
 
-
+case class ForbiddenSendFromException(mailAddress: MailAddress) extends IllegalStateException(s"Can not send from ${mailAddress.asString()}")

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


[james-project] 08/08: JAMES-3225 Remove James generated dependencies from the maven local repository in the Jenkinsfile

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 03a32297ff6cdd395df6fe66bcd3bb0c09bd6df2
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Tue Nov 23 14:17:13 2021 +0700

    JAMES-3225 Remove James generated dependencies from the maven local repository in the Jenkinsfile
---
 Jenkinsfile | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Jenkinsfile b/Jenkinsfile
index 065d9af..508e174 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -73,6 +73,8 @@ pipeline {
             steps {
                 echo 'Cleaning up the workspace'
                 deleteDir()
+                echo 'Cleaning up James maven repo dependencies'
+                sh 'rm -rf /home/jenkins/.m2/repository/org/apache/james'
             }
         }
 

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


[james-project] 01/08: JAMES-3534 Implement Identity/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 4f357bad39383a397e1ec583bf670fedf72db441
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Tue Nov 16 12:13:58 2021 +0700

    JAMES-3534 Implement Identity/set create
---
 .../jmap/api/identity/CustomIdentityDAO.scala      |   2 +-
 .../james/jmap/json/IdentitySerializer.scala       |  37 +++++--
 .../org/apache/james/jmap/mail/IdentitySet.scala   |  76 +++++++++++++++
 .../jmap/method/IdentitySetCreatePerformer.scala   | 108 +++++++++++++++++++++
 .../james/jmap/method/IdentitySetMethod.scala      |  61 ++++++++++++
 5 files changed, 275 insertions(+), 9 deletions(-)

diff --git a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
index 027aea7..bd30740 100644
--- a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
+++ b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
@@ -25,7 +25,7 @@ import java.util.UUID
 import com.google.common.collect.ImmutableList
 import javax.inject.Inject
 import org.apache.james.core.{MailAddress, Username}
-import org.apache.james.jmap.api.model.{EmailAddress, HtmlSignature, Identity, IdentityId, IdentityName, MayDeleteIdentity, TextSignature}
+import org.apache.james.jmap.api.model.{EmailAddress, HtmlSignature, Identity, IdentityId, IdentityName, MayDeleteIdentity, PushSubscriptionCreationRequest, TextSignature}
 import org.apache.james.rrt.api.CanSendFrom
 import org.reactivestreams.Publisher
 import reactor.core.scala.publisher.{SFlux, SMono}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/IdentitySerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/IdentitySerializer.scala
index 724be33..b1d194c 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/IdentitySerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/IdentitySerializer.scala
@@ -19,26 +19,45 @@
 
 package org.apache.james.jmap.json
 
+import eu.timepit.refined.refineV
+import org.apache.james.jmap.api.identity.IdentityCreationRequest
 import org.apache.james.jmap.api.model.{EmailAddress, EmailerName, HtmlSignature, Identity, IdentityId, IdentityName, MayDeleteIdentity, TextSignature}
-import org.apache.james.jmap.core.{Properties, UuidState}
+import org.apache.james.jmap.core.Id.IdConstraint
+import org.apache.james.jmap.core.{Properties, SetError, UuidState}
 import org.apache.james.jmap.mail._
-import play.api.libs.json.{Format, JsArray, JsObject, JsResult, JsSuccess, JsValue, Json, OWrites, Reads, Writes, __}
+import play.api.libs.json.{Format, JsArray, JsError, JsObject, JsResult, JsSuccess, JsValue, Json, OWrites, Reads, Writes, __}
 
 object IdentitySerializer {
-  private implicit val emailerNameReads: Writes[EmailerName] = Json.valueWrites[EmailerName]
+  private implicit val emailerNameReads: Format[EmailerName] = Json.valueFormat[EmailerName]
   private implicit val identityIdFormat: Format[IdentityId] = Json.valueFormat[IdentityId]
   private implicit val identityIdUnparsedFormat: Format[UnparsedIdentityId] = Json.valueFormat[UnparsedIdentityId]
   private implicit val identityIdsFormat: Format[IdentityIds] = Json.valueFormat[IdentityIds]
-  private implicit val emailAddressReads: Writes[EmailAddress] = Json.writes[EmailAddress]
-  private implicit val nameWrites: Writes[IdentityName] = Json.valueWrites[IdentityName]
-  private implicit val textSignatureWrites: Writes[TextSignature] = Json.valueWrites[TextSignature]
-  private implicit val htmlSignatureWrites: Writes[HtmlSignature] = Json.valueWrites[HtmlSignature]
+  private implicit val emailAddressReads: Format[EmailAddress] = Json.format[EmailAddress]
+  private implicit val nameWrites: Format[IdentityName] = Json.valueFormat[IdentityName]
+  private implicit val textSignatureWrites: Format[TextSignature] = Json.valueFormat[TextSignature]
+  private implicit val htmlSignatureWrites: Format[HtmlSignature] = Json.valueFormat[HtmlSignature]
   private implicit val mayDeleteWrites: Writes[MayDeleteIdentity] = Json.valueWrites[MayDeleteIdentity]
   private implicit val identityWrites: Writes[Identity] = Json.writes[Identity]
   private implicit val identityGetRequestReads: Reads[IdentityGetRequest] = Json.reads[IdentityGetRequest]
   private implicit val stateWrites: Writes[UuidState] = Json.valueWrites[UuidState]
   private implicit val identityGetResponseWrites: OWrites[IdentityGetResponse] = Json.writes[IdentityGetResponse]
 
+  private implicit val identityCreationIdWrites: Writes[IdentityCreationId] = Json.valueWrites[IdentityCreationId]
+  private implicit val identityCreationResponseWrites: Writes[IdentityCreationResponse] = Json.writes[IdentityCreationResponse]
+  private implicit val identityMapCreationResponseWrites: Writes[Map[IdentityCreationId, IdentityCreationResponse]] =
+    mapWrites[IdentityCreationId, IdentityCreationResponse](id => identityCreationIdWrites.writes(id).as[String], identityCreationResponseWrites)
+  private implicit val identityMapSetErrorForCreationWrites: Writes[Map[IdentityCreationId, SetError]] =
+    mapWrites[IdentityCreationId, SetError](_.serialise, setErrorWrites)
+  private implicit val identitySetResponseWrites: OWrites[IdentitySetResponse] = Json.writes[IdentitySetResponse]
+
+  private implicit val mapCreationRequestByIdentityCreationId: Reads[Map[IdentityCreationId, JsObject]] =
+    Reads.mapReads[IdentityCreationId, JsObject] {string => refineV[IdConstraint](string)
+      .fold(e => JsError(s"identity creationId needs to match id constraints: $e"),
+        id => JsSuccess(IdentityCreationId(id)))
+    }
+  private implicit val identitySetRequestReads: Reads[IdentitySetRequest] = Json.reads[IdentitySetRequest]
+  private implicit val identityCreationRequest: Reads[IdentityCreationRequest] = Json.reads[IdentityCreationRequest]
+
   def serialize(response: IdentityGetResponse, properties: Properties): JsObject = Json.toJsObject(response)
     .transform((__ \ "list").json.update {
       case JsArray(underlying) => JsSuccess(JsArray(underlying.map {
@@ -46,7 +65,9 @@ object IdentitySerializer {
         case jsValue => jsValue
       }))
     }).get
+  def serialize(response: IdentitySetResponse): JsObject = Json.toJsObject(response)
 
   def deserialize(input: JsValue): JsResult[IdentityGetRequest] = Json.fromJson[IdentityGetRequest](input)
-
+  def deserializeIdentitySetRequest(input: JsValue): JsResult[IdentitySetRequest] = Json.fromJson[IdentitySetRequest](input)
+  def deserializeIdentityCreationRequest(input: JsValue): JsResult[IdentityCreationRequest] = Json.fromJson[IdentityCreationRequest](input)
 }
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/IdentitySet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/IdentitySet.scala
new file mode 100644
index 0000000..8fa32ac
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/IdentitySet.scala
@@ -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.jmap.mail
+
+import eu.timepit.refined.collection.NonEmpty
+import eu.timepit.refined.refineV
+import eu.timepit.refined.types.string.NonEmptyString
+import org.apache.james.jmap.api.model.{HtmlSignature, IdentityId, IdentityName, MayDeleteIdentity, TextSignature}
+import org.apache.james.jmap.core.Id.Id
+import org.apache.james.jmap.core.SetError.SetErrorDescription
+import org.apache.james.jmap.core.{AccountId, Properties, SetError, UuidState}
+import org.apache.james.jmap.method.WithAccountId
+import play.api.libs.json.JsObject
+
+object IdentityCreation {
+  private val serverSetProperty: Set[String] = Set("id", "mayDelete")
+  private val assignableProperties: Set[String] = Set("name", "email", "replyTo", "bcc", "textSignature", "htmlSignature")
+  private val knownProperties: Set[String] = assignableProperties ++ serverSetProperty
+
+  def validateProperties(jsObject: JsObject): Either[IdentityCreationParseException, JsObject] =
+    (jsObject.keys.intersect(serverSetProperty), jsObject.keys.diff(knownProperties)) match {
+      case (_, unknownProperties) if unknownProperties.nonEmpty =>
+        Left(IdentityCreationParseException(SetError.invalidArguments(
+          SetErrorDescription("Some unknown properties were specified"),
+          Some(toProperties(unknownProperties.toSet)))))
+      case (specifiedServerSetProperties, _) if specifiedServerSetProperties.nonEmpty =>
+        Left(IdentityCreationParseException(SetError.invalidArguments(
+          SetErrorDescription("Some server-set properties were specified"),
+          Some(toProperties(specifiedServerSetProperties.toSet)))))
+      case _ => scala.Right(jsObject)
+    }
+
+  private def toProperties(strings: Set[String]): Properties = Properties(strings
+    .flatMap(string => {
+      val refinedValue: Either[String, NonEmptyString] = refineV[NonEmpty](string)
+      refinedValue.fold(_ => None, Some(_))
+    }))
+}
+
+case class IdentitySetRequest(accountId: AccountId,
+                              create: Option[Map[IdentityCreationId, JsObject]]) extends WithAccountId
+
+case class IdentityCreationId(id: Id) {
+  def serialise: String = id.value
+}
+
+case class IdentityCreationResponse(id: IdentityId,
+                                    name: Option[IdentityName],
+                                    textSignature: Option[TextSignature],
+                                    htmlSignature: Option[HtmlSignature],
+                                    mayDelete: MayDeleteIdentity)
+
+case class IdentitySetResponse(accountId: AccountId,
+                               oldState: Option[UuidState],
+                               newState: UuidState,
+                               created: Option[Map[IdentityCreationId, IdentityCreationResponse]],
+                               notCreated: Option[Map[IdentityCreationId, SetError]])
+
+case class IdentityCreationParseException(setError: SetError) extends IllegalArgumentException
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetCreatePerformer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetCreatePerformer.scala
new file mode 100644
index 0000000..13d567a
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetCreatePerformer.scala
@@ -0,0 +1,108 @@
+/****************************************************************
+ * 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.api.identity.{IdentityCreationRequest, IdentityRepository}
+import org.apache.james.jmap.api.model.{ForbiddenSendFromException, HtmlSignature, Identity, IdentityName, TextSignature}
+import org.apache.james.jmap.core.SetError.SetErrorDescription
+import org.apache.james.jmap.core.{Properties, SetError}
+import org.apache.james.jmap.json.IdentitySerializer
+import org.apache.james.jmap.mail.{IdentityCreation, IdentityCreationId, IdentityCreationParseException, IdentityCreationResponse, IdentitySetRequest}
+import org.apache.james.jmap.method.IdentitySetCreatePerformer.{CreationFailure, CreationResult, CreationResults, CreationSuccess}
+import org.apache.james.mailbox.MailboxSession
+import play.api.libs.json.{JsObject, JsPath, JsonValidationError}
+import reactor.core.scala.publisher.{SFlux, SMono}
+import reactor.core.scheduler.Schedulers
+
+object IdentitySetCreatePerformer {
+  case class CreationResults(results: Seq[CreationResult]) {
+    def created: Option[Map[IdentityCreationId, IdentityCreationResponse]] =
+      Option(results.flatMap {
+        case result: CreationSuccess => Some((result.clientId, result.response))
+        case _ => None
+      }.toMap)
+        .filter(_.nonEmpty)
+
+    def notCreated: Option[Map[IdentityCreationId, SetError]] =
+      Option(results.flatMap {
+        case failure: CreationFailure => Some((failure.clientId, failure.asMessageSetError))
+        case _ => None
+      }
+        .toMap)
+        .filter(_.nonEmpty)
+  }
+
+  trait CreationResult
+
+  case class CreationSuccess(clientId: IdentityCreationId, response: IdentityCreationResponse) extends CreationResult
+
+  case class CreationFailure(clientId: IdentityCreationId, e: Throwable) extends CreationResult {
+    def asMessageSetError: SetError = e match {
+      case e: IdentityCreationParseException => e.setError
+      case e: ForbiddenSendFromException => SetError.forbiddenFrom(SetErrorDescription(e.getMessage))
+      case e: IllegalArgumentException => SetError.invalidArguments(SetErrorDescription(e.getMessage))
+      case _ => SetError.serverFail(SetErrorDescription(e.getMessage))
+    }
+  }
+}
+
+class IdentitySetCreatePerformer @Inject()(identityRepository: IdentityRepository) {
+  def create(request: IdentitySetRequest, mailboxSession: MailboxSession): SMono[CreationResults] =
+    SFlux.fromIterable(request.create.getOrElse(Map()))
+      .concatMap {
+        case (clientId, json) => parseCreate(json)
+          .fold(e => SMono.just[CreationResult](CreationFailure(clientId, e)),
+            creationRequest => create(clientId, creationRequest, mailboxSession))
+      }.collectSeq()
+      .map(CreationResults)
+
+  private def parseCreate(jsObject: JsObject): Either[Exception, IdentityCreationRequest] = for {
+    validJsObject <- IdentityCreation.validateProperties(jsObject)
+    parsedRequest <- IdentitySerializer.deserializeIdentityCreationRequest(validJsObject).asEither
+      .left.map(errors => IdentityCreationParseException(IdentitySetError(errors)))
+  } yield {
+    parsedRequest
+  }
+
+  private def create(clientId: IdentityCreationId, request: IdentityCreationRequest, mailboxSession: MailboxSession): SMono[CreationResult] =
+    SMono.fromPublisher(identityRepository.save(mailboxSession.getUser, request))
+      .map(identity => CreationSuccess(clientId, evaluateCreationResponse(request, identity)))
+      .onErrorResume(e => SMono.just[CreationResult](CreationFailure(clientId, e)))
+      .subscribeOn(Schedulers.elastic)
+
+  private def evaluateCreationResponse(request: IdentityCreationRequest, identity: Identity): IdentityCreationResponse =
+    IdentityCreationResponse(
+      id = identity.id,
+      name = request.name.fold[Option[IdentityName]](Some(IdentityName.DEFAULT))(_ => None),
+      textSignature = request.textSignature.fold[Option[TextSignature]](Some(TextSignature.DEFAULT))(_ => None),
+      htmlSignature = request.htmlSignature.fold[Option[HtmlSignature]](Some(HtmlSignature.DEFAULT))(_ => None),
+      mayDelete = identity.mayDelete)
+
+  private def IdentitySetError(errors: collection.Seq[(JsPath, collection.Seq[JsonValidationError])]): SetError =
+    errors.head match {
+      case (path, Seq()) => SetError.invalidArguments(SetErrorDescription(s"'$path' property in Identity object is not valid"))
+      case (path, Seq(JsonValidationError(Seq("error.path.missing")))) =>
+        SetError.invalidArguments(SetErrorDescription(s"Missing '$path' property in Identity object"), Some(Properties("email")))
+      case (path, Seq(JsonValidationError(Seq(message)))) => SetError.invalidArguments(SetErrorDescription(s"'$path' property in Identity object is not valid: $message"))
+      case (path, _) => SetError.invalidArguments(SetErrorDescription(s"Unknown error on property '$path'"))
+    }
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetMethod.scala
new file mode 100644
index 0000000..6138f2a
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetMethod.scala
@@ -0,0 +1,61 @@
+/****************************************************************
+ * 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, EMAIL_SUBMISSION, JMAP_CORE}
+import org.apache.james.jmap.core.Invocation.{Arguments, MethodName}
+import org.apache.james.jmap.core.{Invocation, UuidState}
+import org.apache.james.jmap.json.{IdentitySerializer, ResponseSerializer}
+import org.apache.james.jmap.mail.{IdentitySetRequest, IdentitySetResponse}
+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 IdentitySetMethod @Inject()(createPerformer: IdentitySetCreatePerformer,
+                                  val metricFactory: MetricFactory,
+                                  val sessionSupplier: SessionSupplier) extends MethodRequiringAccountId[IdentitySetRequest] {
+  override val methodName: Invocation.MethodName = MethodName("Identity/set")
+  override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE, EMAIL_SUBMISSION)
+
+  override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, IdentitySetRequest] =
+    IdentitySerializer.deserializeIdentitySetRequest(invocation.arguments.value) match {
+      case JsSuccess(identitySetRequest, _) => Right(identitySetRequest)
+      case errors: JsError => Left(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
+    }
+
+  override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: IdentitySetRequest): SMono[InvocationWithContext] =
+    for {
+      creationResults <- createPerformer.create(request, mailboxSession)
+    } yield InvocationWithContext(
+      invocation = Invocation(
+        methodName = methodName,
+        arguments = Arguments(IdentitySerializer.serialize(IdentitySetResponse(
+          accountId = request.accountId,
+          oldState = None,
+          newState = UuidState.INSTANCE,
+          created = creationResults.created.filter(_.nonEmpty),
+          notCreated = creationResults.notCreated.filter(_.nonEmpty)))),
+        methodCallId = invocation.invocation.methodCallId),
+      processingContext = invocation.processingContext)
+}

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


[james-project] 07/08: JAMES-3534 Test for Identity/set create should record succeeded creation ids within the processing context

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 f577b6c642881ab75a062a378d4674bfad418b3b
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Mon Nov 22 15:29:21 2021 +0700

    JAMES-3534 Test for Identity/set create should record succeeded creation ids within the processing context
---
 .../rfc8621/contract/IdentitySetContract.scala     | 98 ++++++++++++++++++++++
 1 file changed, 98 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/IdentitySetContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/IdentitySetContract.scala
index 5922085..31e4c7e 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/IdentitySetContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/IdentitySetContract.scala
@@ -155,6 +155,104 @@ trait IdentitySetContract {
   }
 
   @Test
+  def setIdentityAndGetIdentityCombinedShouldSucceed(): Unit = {
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:submission"],
+         |	"methodCalls": [
+         |		[
+         |			"Identity/set",
+         |			{
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"create": {
+         |					"4f29": {
+         |						"name": "Bob",
+         |						"email": "bob@domain.tld",
+         |						"replyTo": [{
+         |							"name": "Alice",
+         |							"email": "alice@domain.tld"
+         |						}],
+         |						"bcc": [{
+         |							"name": "David",
+         |							"email": "david@domain.tld"
+         |						}],
+         |						"textSignature": "Some text signature",
+         |						"htmlSignature": "<p>Some html signature</p>"
+         |					}
+         |				}
+         |			},
+         |			"c1"
+         |		],
+         |		["Identity/get",
+         |			{
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"ids": ["#4f29"]
+         |			}, "c2"
+         |		]
+         |
+         |	]
+         |}""".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": [
+           |		[
+           |			"Identity/set",
+           |			{
+           |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |				"newState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |				"created": {
+           |					"4f29": {
+           |						"id": "$${json-unit.ignore}",
+           |						"mayDelete": true
+           |					}
+           |				}
+           |			},
+           |			"c1"
+           |		],
+           |		[
+           |			"Identity/get",
+           |			{
+           |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |				"state": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |				"list": [{
+           |					"id": "$${json-unit.ignore}",
+           |					"name": "Bob",
+           |					"email": "bob@domain.tld",
+           |					"replyTo": [{
+           |						"name": "Alice",
+           |						"email": "alice@domain.tld"
+           |					}],
+           |					"bcc": [{
+           |						"name": "David",
+           |						"email": "david@domain.tld"
+           |					}],
+           |					"textSignature": "Some text signature",
+           |					"htmlSignature": "<p>Some html signature</p>",
+           |					"mayDelete": true
+           |				}]
+           |			},
+           |			"c2"
+           |		]
+           |	]
+           |}""".stripMargin)
+  }
+
+  @Test
   def setIdentityWithSomePropertiesOmittedShouldSucceedAndReturnDefaultValues(): Unit = {
     val request =
       s"""{

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


[james-project] 06/08: JAMES-3534 Identity/set contract test should ignore array order

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 2b9a9531fce85fbbb8b9f5e3ae87e2436fe6fcd2
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Mon Nov 22 14:53:01 2021 +0700

    JAMES-3534 Identity/set contract test should ignore array order
---
 .../org/apache/james/jmap/rfc8621/contract/IdentitySetContract.scala     | 1 +
 1 file changed, 1 insertion(+)

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/IdentitySetContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/IdentitySetContract.scala
index eed6ea6..5922085 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/IdentitySetContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/IdentitySetContract.scala
@@ -98,6 +98,7 @@ trait IdentitySetContract {
       .asString
 
     assertThatJson(response)
+      .when(net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER)
       .isEqualTo(
         s"""{
            |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",

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


[james-project] 02/08: JAMES-3534 Identity/set create contract and memory tests

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 56b958992d7a9c677d032d9639868f4329e028df
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Wed Nov 17 12:54:06 2021 +0700

    JAMES-3534 Identity/set create contract and memory tests
---
 .../james/jmap/rfc8621/RFC8621MethodsModule.java   |   2 +
 .../rfc8621/contract/IdentitySetContract.scala     | 665 +++++++++++++++++++++
 .../memory/MemoryIdentitySetMethodTests.java       |  35 ++
 3 files changed, 702 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 8a1d30d..42684a4 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
@@ -53,6 +53,7 @@ import org.apache.james.jmap.method.EmailQueryMethod;
 import org.apache.james.jmap.method.EmailSetMethod;
 import org.apache.james.jmap.method.EmailSubmissionSetMethod;
 import org.apache.james.jmap.method.IdentityGetMethod;
+import org.apache.james.jmap.method.IdentitySetMethod;
 import org.apache.james.jmap.method.MDNParseMethod;
 import org.apache.james.jmap.method.MDNSendMethod;
 import org.apache.james.jmap.method.MailboxChangesMethod;
@@ -120,6 +121,7 @@ public class RFC8621MethodsModule extends AbstractModule {
         methods.addBinding().to(EmailSetMethod.class);
         methods.addBinding().to(EmailSubmissionSetMethod.class);
         methods.addBinding().to(IdentityGetMethod.class);
+        methods.addBinding().to(IdentitySetMethod.class);
         methods.addBinding().to(MailboxChangesMethod.class);
         methods.addBinding().to(MailboxGetMethod.class);
         methods.addBinding().to(MailboxQueryMethod.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/IdentitySetContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/IdentitySetContract.scala
new file mode 100644
index 0000000..eed6ea6
--- /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/IdentitySetContract.scala
@@ -0,0 +1,665 @@
+/****************************************************************
+ * 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 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.core.ResponseObject.SESSION_STATE
+import org.apache.james.jmap.core.UuidState.INSTANCE
+import org.apache.james.jmap.http.UserCredential
+import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
+import org.apache.james.utils.DataProbeImpl
+import org.junit.jupiter.api.{BeforeEach, Test}
+
+trait IdentitySetContract {
+  @BeforeEach
+  def setUp(server: GuiceJamesServer): Unit = {
+    server.getProbe(classOf[DataProbeImpl])
+      .fluent
+      .addDomain(DOMAIN.asString)
+      .addDomain("domain-alias.tld")
+      .addUser(BOB.asString, BOB_PASSWORD)
+
+    requestSpecification = baseRequestSpecBuilder(server)
+      .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
+      .build
+  }
+
+  @Test
+  def setIdentityShouldSucceed(): Unit = {
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:submission"],
+         |	"methodCalls": [
+         |		[
+         |			"Identity/set",
+         |			{
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"create": {
+         |					"4f29": {
+         |						"name": "Bob",
+         |						"email": "bob@domain.tld",
+         |						"replyTo": [{
+         |							"name": "Alice",
+         |							"email": "alice@domain.tld"
+         |						}],
+         |						"bcc": [{
+         |							"name": "David",
+         |							"email": "david@domain.tld"
+         |						}],
+         |						"textSignature": "Some text signature",
+         |						"htmlSignature": "<p>Some html signature</p>"
+         |					}
+         |				}
+         |			},
+         |			"c1"
+         |		],
+         |		["Identity/get",
+         |			{
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"ids": null
+         |			}, "c2"
+         |		]
+         |
+         |	]
+         |}""".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": [
+           |		[
+           |			"Identity/set",
+           |			{
+           |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |				"newState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |				"created": {
+           |					"4f29": {
+           |						"id": "$${json-unit.ignore}",
+           |						"mayDelete": true
+           |					}
+           |				}
+           |			},
+           |			"c1"
+           |		],
+           |		[
+           |			"Identity/get",
+           |			{
+           |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |				"state": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |				"list": [{
+           |						"id": "$${json-unit.ignore}",
+           |						"name": "Bob",
+           |						"email": "bob@domain.tld",
+           |						"replyTo": [{
+           |							"name": "Alice",
+           |							"email": "alice@domain.tld"
+           |						}],
+           |						"bcc": [{
+           |							"name": "David",
+           |							"email": "david@domain.tld"
+           |						}],
+           |						"textSignature": "Some text signature",
+           |						"htmlSignature": "<p>Some html signature</p>",
+           |						"mayDelete": true
+           |					},
+           |					{
+           |						"id": "becaf930-ea9e-3ef4-81ea-206eecb04aa7",
+           |						"name": "bob@domain.tld",
+           |						"email": "bob@domain.tld",
+           |						"textSignature": "",
+           |						"htmlSignature": "",
+           |						"mayDelete": false
+           |					}
+           |				]
+           |			},
+           |			"c2"
+           |		]
+           |	]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def setIdentityWithSomePropertiesOmittedShouldSucceedAndReturnDefaultValues(): Unit = {
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:submission"],
+         |	"methodCalls": [
+         |		[
+         |			"Identity/set",
+         |			{
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"create": {
+         |					"4f29": {
+         |						"email": "bob@domain.tld"
+         |					}
+         |				}
+         |			},
+         |			"c1"
+         |		]
+         |	]
+         |}""".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)
+      .inPath("methodResponses[0][1]")
+      .isEqualTo(
+        s"""{
+           |	"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |	"newState": "${INSTANCE.serialize}",
+           |	"created": {
+           |		"4f29": {
+           |			"id": "$${json-unit.ignore}",
+           |			"name": "",
+           |			"textSignature": "",
+           |			"htmlSignature": "",
+           |			"mayDelete": true
+           |		}
+           |	}
+           |}""".stripMargin)
+  }
+
+  @Test
+  def setIdentityShouldCreatedSeveralValidCreationRequest(): Unit = {
+    val request: String =
+      """{
+        |	"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:submission"],
+        |	"methodCalls": [
+        |		[
+        |			"Identity/set",
+        |			{
+        |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+        |				"create": {
+        |					"4f28": {
+        |						"name": "Identity1",
+        |						"email": "bob@domain.tld",
+        |						"replyTo": [{
+        |							"name": "Alice",
+        |							"email": "alice@domain.tld"
+        |						}],
+        |						"bcc": [{
+        |							"name": "David",
+        |							"email": "david@domain.tld"
+        |						}],
+        |						"textSignature": "Some text signature",
+        |						"htmlSignature": "<p>Some html signature</p>"
+        |					},
+        |					"4f29": {
+        |						"name": "Identity2",
+        |						"email": "bob@domain.tld",
+        |						"replyTo": null,
+        |						"bcc": null,
+        |						"textSignature": "Some text signature",
+        |						"htmlSignature": "<p>Some html signature</p>"
+        |					}
+        |				}
+        |			},
+        |			"c1"
+        |		]
+        |	]
+        |}""".stripMargin
+
+    val response: String = `given`
+      .body(request)
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1]")
+      .isEqualTo(
+        s"""{
+           |	"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |	"newState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"created": {
+           |		"4f28": {
+           |			"id": "$${json-unit.ignore}",
+           |			"mayDelete": true
+           |		},
+           |		"4f29": {
+           |			"id": "$${json-unit.ignore}",
+           |			"mayDelete": true
+           |		}
+           |	}
+           |}""".stripMargin)
+  }
+
+  @Test
+  def setIdentityShouldReturnForbiddenFromErrorWhenForbiddenEmailProperty(): Unit = {
+    val request: String =
+      """{
+        |	"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:submission"],
+        |	"methodCalls": [
+        |		[
+        |			"Identity/set",
+        |			{
+        |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+        |				"create": {
+        |					"4f28": {
+        |						"name": "valid send from identity",
+        |						"email": "bob@domain.tld"
+        |					},
+        |					"4f29": {
+        |						"name": "forbidden send from identity",
+        |						"email": "bob-alias@domain.tld"
+        |					}
+        |				}
+        |			},
+        |			"c1"
+        |		]
+        |	]
+        |}""".stripMargin
+
+    val response: String = `given`
+      .body(request)
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1]")
+      .isEqualTo(
+        s"""{
+           |	"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |	"newState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"created": {
+           |		"4f28": {
+           |			"id": "$${json-unit.ignore}",
+           |			"textSignature": "",
+           |			"htmlSignature": "",
+           |			"mayDelete": true
+           |		}
+           |	},
+           |	"notCreated": {
+           |		"4f29": {
+           |			"type": "forbiddenFrom",
+           |			"description": "Can not send from bob-alias@domain.tld"
+           |		}
+           |	}
+           |}""".stripMargin)
+  }
+
+  @Test
+  def setIdentityShouldSucceedWhenValidEmailProperty(server: GuiceJamesServer): Unit = {
+    server.getProbe(classOf[DataProbeImpl]).addUserAliasMapping("bob-alias", "domain.tld", "bob@domain.tld")
+
+    val request: String =
+      """{
+        |	"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:submission"],
+        |	"methodCalls": [
+        |		[
+        |			"Identity/set",
+        |			{
+        |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+        |				"create": {
+        |					"4f28": {
+        |						"name": "valid send from identity",
+        |						"email": "bob@domain.tld"
+        |					},
+        |					"4f29": {
+        |						"name": "valid send from identity",
+        |						"email": "bob-alias@domain.tld"
+        |					}
+        |				}
+        |			},
+        |			"c1"
+        |		]
+        |	]
+        |}""".stripMargin
+
+    val response: String = `given`
+      .body(request)
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1]")
+      .isEqualTo(
+        s"""{
+           |	"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |	"newState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"created": {
+           |		"4f28": {
+           |			"id": "$${json-unit.ignore}",
+           |			"textSignature": "",
+           |			"htmlSignature": "",
+           |			"mayDelete": true
+           |		},
+           |		"4f29": {
+           |			"id": "$${json-unit.ignore}",
+           |			"textSignature": "",
+           |			"htmlSignature": "",
+           |			"mayDelete": true
+           |		}
+           |	}
+           |}""".stripMargin)
+  }
+
+  @Test
+  def setIdentityShouldFailWhenEmailPropertyIsMissing(): Unit = {
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:submission"],
+         |	"methodCalls": [
+         |		[
+         |			"Identity/set",
+         |			{
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"create": {
+         |					"4f29": {
+         |						"name": "Bob",
+         |						"replyTo": [{
+         |							"name": "Alice",
+         |							"email": "alice@domain.tld"
+         |						}],
+         |						"bcc": [{
+         |							"name": "David",
+         |							"email": "david@domain.tld"
+         |						}],
+         |						"textSignature": "Some text signature",
+         |						"htmlSignature": "<p>Some html signature</p>"
+         |					}
+         |				}
+         |			},
+         |			"c1"
+         |		]
+         |	]
+         |}""".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)
+      .inPath("methodResponses[0][1]")
+      .isEqualTo(
+        s"""{
+           |	"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |	"newState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"notCreated": {
+           |		"4f29": {
+           |			"type": "invalidArguments",
+           |			"description": "Missing '/email' property in Identity object",
+           |			"properties": [
+           |				"email"
+           |			]
+           |		}
+           |	}
+           |}""".stripMargin)
+  }
+
+  @Test
+  def setIdentityShouldFailWhenEmailPropertyIsNull(): Unit = {
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:submission"],
+         |	"methodCalls": [
+         |		[
+         |			"Identity/set",
+         |			{
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"create": {
+         |					"4f29": {
+         |						"name": "Bob",
+         |						"email": null,
+         |						"replyTo": [{
+         |							"name": "Alice",
+         |							"email": "alice@domain.tld"
+         |						}],
+         |						"bcc": [{
+         |							"name": "David",
+         |							"email": "david@domain.tld"
+         |						}],
+         |						"textSignature": "Some text signature",
+         |						"htmlSignature": "<p>Some html signature</p>"
+         |					}
+         |				}
+         |			},
+         |			"c1"
+         |		]
+         |	]
+         |}""".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)
+      .inPath("methodResponses[0][1]")
+      .isEqualTo(
+        s"""{
+           |	"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |	"newState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"notCreated": {
+           |		"4f29": {
+           |			"type": "invalidArguments",
+           |			"description": "'/email' property in Identity object is not valid: mail address needs to be represented with a JsString"
+           |		}
+           |	}
+           |}""".stripMargin)
+  }
+
+  @Test
+  def setIdentityShouldNotCreatedWhenCreationRequestHasServerSetProperty(): Unit = {
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:submission"],
+         |	"methodCalls": [
+         |		[
+         |			"Identity/set",
+         |			{
+         |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |				"create": {
+         |					"4f29": {
+         |						"id": "someId",
+         |            "mayDelete": false,
+         |						"email": "bob@domain.tld"
+         |					}
+         |				}
+         |			},
+         |			"c1"
+         |		]
+         |	]
+         |}""".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)
+      .inPath("methodResponses[0][1]")
+      .isEqualTo(
+        s"""{
+           |	"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |	"newState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"notCreated": {
+           |		"4f29": {
+           |			"type": "invalidArguments",
+           |			"description": "Some server-set properties were specified",
+           |			"properties": ["id", "mayDelete"]
+           |		}
+           |	}
+           |}""".stripMargin)
+  }
+
+  @Test
+  def setIdentityShouldFailWhenWrongAccountId(): Unit = {
+    val request =
+      s"""
+         |{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:submission"],
+         |	"methodCalls": [
+         |		[
+         |			"Identity/set",
+         |			{
+         |				"accountId": "unknownAccountId",
+         |				"create": {}
+         |			},
+         |			"c1"
+         |		]
+         |	]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .log().ifValidationFails()
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |  "sessionState": "${SESSION_STATE.value}",
+         |  "methodResponses": [
+         |    ["error", {
+         |      "type": "accountNotFound"
+         |    }, "c1"]
+         |  ]
+         |}""".stripMargin)
+  }
+
+  @Test
+  def setIdentityShouldFailWhenMissingCapability(): Unit = {
+    val request: String =
+      """{
+        |	"using": [],
+        |	"methodCalls": [
+        |		[
+        |			"Identity/set",
+        |			{
+        |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+        |				"create": {
+        |					"4f29": {
+        |						"email": "bob@domain.tld"
+        |					}
+        |				}
+        |			},
+        |			"c1"
+        |		]
+        |	]
+        |}""".stripMargin
+
+    val response: String = `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": "${SESSION_STATE.value}",
+           |    "methodResponses": [
+           |        [
+           |            "error",
+           |            {
+           |                "type": "unknownMethod",
+           |                "description": "Missing capability(ies): urn:ietf:params:jmap:core, urn:ietf:params:jmap:submission"
+           |            },
+           |            "c1"
+           |        ]
+           |    ]
+           |}""".stripMargin)
+  }
+
+}
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/MemoryIdentitySetMethodTests.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryIdentitySetMethodTests.java
new file mode 100644
index 0000000..1989a83
--- /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/MemoryIdentitySetMethodTests.java
@@ -0,0 +1,35 @@
+/****************************************************************
+ * 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 org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.MemoryJamesServerMain;
+import org.apache.james.jmap.rfc8621.contract.IdentitySetContract;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class MemoryIdentitySetMethodTests implements IdentitySetContract {
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
+        .server(configuration -> MemoryJamesServerMain.createServer(configuration)
+            .overrideWith(new TestJMAPServerModule()))
+        .build();
+}

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


[james-project] 05/08: JAMES-3534 Identity/set create should record succeeded creation ids within the processing context

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 231604f3913eb8ac2eeb32b356527c00dc0ea1b9
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Mon Nov 22 15:13:42 2021 +0700

    JAMES-3534 Identity/set create should record succeeded creation ids within the processing context
---
 .../main/scala/org/apache/james/jmap/api/model/Identity.scala  |  4 +++-
 .../scala/org/apache/james/jmap/method/IdentitySetMethod.scala | 10 ++++++++--
 2 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/Identity.scala b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/Identity.scala
index 58a9cc1..d57bf24 100644
--- a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/Identity.scala
+++ b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/Identity.scala
@@ -40,7 +40,9 @@ case class MayDeleteIdentity(value: Boolean) extends AnyVal
 object IdentityId {
   def generate: IdentityId = IdentityId(UUID.randomUUID())
 }
-case class IdentityId(id: UUID)
+case class IdentityId(id: UUID) {
+  def serialize: String = id.toString
+}
 
 case class Identity(id: IdentityId,
                     name: IdentityName,
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetMethod.scala
index 6138f2a..c68fc83 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetMethod.scala
@@ -23,7 +23,7 @@ import eu.timepit.refined.auto._
 import javax.inject.Inject
 import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, EMAIL_SUBMISSION, JMAP_CORE}
 import org.apache.james.jmap.core.Invocation.{Arguments, MethodName}
-import org.apache.james.jmap.core.{Invocation, UuidState}
+import org.apache.james.jmap.core.{ClientId, Id, Invocation, ServerId, UuidState}
 import org.apache.james.jmap.json.{IdentitySerializer, ResponseSerializer}
 import org.apache.james.jmap.mail.{IdentitySetRequest, IdentitySetResponse}
 import org.apache.james.jmap.routes.SessionSupplier
@@ -57,5 +57,11 @@ class IdentitySetMethod @Inject()(createPerformer: IdentitySetCreatePerformer,
           created = creationResults.created.filter(_.nonEmpty),
           notCreated = creationResults.notCreated.filter(_.nonEmpty)))),
         methodCallId = invocation.invocation.methodCallId),
-      processingContext = invocation.processingContext)
+      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] 04/08: JAMES-3534 Introduce DistributedIdentitySetTest

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 2c35af6e90e7aa83179f20d18c2d7eaba410dd39
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Wed Nov 17 14:56:48 2021 +0700

    JAMES-3534 Introduce DistributedIdentitySetTest
---
 .../distributed/DistributedIdentitySetTest.java    | 56 ++++++++++++++++++++++
 1 file changed, 56 insertions(+)

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/DistributedIdentitySetTest.java b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedIdentitySetTest.java
new file mode 100644
index 0000000..6c28c60
--- /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/DistributedIdentitySetTest.java
@@ -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.rfc8621.distributed;
+
+import org.apache.james.CassandraExtension;
+import org.apache.james.CassandraRabbitMQJamesConfiguration;
+import org.apache.james.CassandraRabbitMQJamesServerMain;
+import org.apache.james.DockerElasticSearchExtension;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.jmap.rfc8621.contract.IdentitySetContract;
+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 DistributedIdentitySetTest implements IdentitySetContract {
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<CassandraRabbitMQJamesConfiguration>(tmpDir ->
+        CassandraRabbitMQJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .blobStore(BlobStoreConfiguration.builder()
+                .s3()
+                .disableCache()
+                .deduplication()
+                .noCryptoConfig())
+            .searchConfiguration(SearchConfiguration.elasticSearch())
+            .build())
+        .extension(new DockerElasticSearchExtension())
+        .extension(new CassandraExtension())
+        .extension(new RabbitMQExtension())
+        .extension(new AwsS3BlobStoreExtension())
+        .server(configuration -> CassandraRabbitMQJamesServerMain.createServer(configuration)
+            .overrideWith(new TestJMAPServerModule()))
+        .build();
+}

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