You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by GitBox <gi...@apache.org> on 2021/11/11 04:15:53 UTC

[GitHub] [james-project] vttranlina commented on a change in pull request #739: JAMES-3534 API, Memory & contract test for identity storage

vttranlina commented on a change in pull request #739:
URL: https://github.com/apache/james-project/pull/739#discussion_r747184096



##########
File path: server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Identity.scala
##########
@@ -20,26 +20,22 @@
 package org.apache.james.jmap.mail
 
 import java.nio.charset.StandardCharsets
+import java.util.UUID
 
 import com.google.common.collect.ImmutableList
-import com.google.common.hash.Hashing
-import eu.timepit.refined.refineV
 import javax.inject.Inject
 import org.apache.james.core.MailAddress
-import org.apache.james.jmap.core.Id.Id
 import org.apache.james.mailbox.MailboxSession
 import org.apache.james.rrt.api.CanSendFrom
 
 import scala.jdk.CollectionConverters._
+import scala.util.Try
 
 case class IdentityName(name: String) extends AnyVal
 case class TextSignature(name: String) extends AnyVal
 case class HtmlSignature(name: String) extends AnyVal
 case class MayDeleteIdentity(value: Boolean) extends AnyVal
-case class IdentityId(id: Id)
-case class IdentityIds(ids: List[IdentityId]) {
-  def contains(identityId: IdentityId): Boolean = ids.contains(identityId)
-}
+case class IdentityId(id: UUID)

Review comment:
       Why do we change `Id` to `UUID`? 

##########
File path: server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/IdentityDAO.scala
##########
@@ -0,0 +1,113 @@
+package org.apache.james.jmap.api.identity
+
+import java.nio.charset.StandardCharsets
+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.rrt.api.CanSendFrom
+import org.reactivestreams.Publisher
+import reactor.core.scala.publisher.{SFlux, SMono}
+import reactor.core.scheduler.Schedulers
+
+import scala.jdk.CollectionConverters._
+import scala.util.Try
+
+case class IdentityCreationRequest(name: IdentityName,
+                                    email: MailAddress,
+                                    replyTo: Option[List[EmailAddress]],
+                                    bcc: Option[List[EmailAddress]],
+                                    textSignature: Option[TextSignature],
+                                    htmlSignature: Option[HtmlSignature]) {
+  def asIdentity(id: IdentityId): Identity = Identity(id, name, email, replyTo, bcc, textSignature, htmlSignature, mayDelete = MayDeleteIdentity(true))
+}
+
+trait IdentityUpdate {
+  def update(identity: Identity): Identity
+}
+case class IdentityNameUpdate(name: IdentityName) extends IdentityUpdate {
+  override def update(identity: Identity): Identity = identity.copy(name = name)
+}
+case class IdentityReplyToUpdate(replyTo: Option[List[EmailAddress]]) extends IdentityUpdate {
+  override def update(identity: Identity): Identity = identity.copy(replyTo = replyTo)
+}
+case class IdentityBccUpdate(bcc: Option[List[EmailAddress]]) extends IdentityUpdate {
+  override def update(identity: Identity): Identity = identity.copy(bcc = bcc)
+}
+case class IdentityTextSignatureUpdate(textSignature: Option[TextSignature]) extends IdentityUpdate {
+  override def update(identity: Identity): Identity = identity.copy(textSignature = textSignature)
+}
+case class IdentityHtmlSignatureUpdate(htmlSignature: Option[HtmlSignature]) extends IdentityUpdate {
+  override def update(identity: Identity): Identity = identity.copy(htmlSignature = htmlSignature)
+}
+
+case class IdentityUpdateRequest(name: IdentityNameUpdate,
+                                 replyTo: IdentityReplyToUpdate,
+                                 bcc: IdentityBccUpdate,
+                                 textSignature: IdentityTextSignatureUpdate,
+                                 htmlSignature: IdentityHtmlSignatureUpdate) {
+  def update(identity: Identity): Identity =
+    List(name, replyTo, bcc, textSignature, htmlSignature)
+      .foldLeft(identity)((acc, update) => update.update(acc))
+}
+
+trait CustomIdentityDAO {
+  def save(user: Username, creationRequest: IdentityCreationRequest): Publisher[Identity]
+
+  def list(user: Username): Publisher[Identity]
+
+  def update(user: Username, identityId: IdentityId, identityUpdate: IdentityUpdate): Publisher[Unit]
+
+  def delete(username: Username, ids: Seq[IdentityId]): Publisher[Unit]
+}
+
+class DefaultIdentitySupplier @Inject()(canSendFrom: CanSendFrom) {
+  def listIdentities(username: Username): List[Identity] =
+    canSendFrom.allValidFromAddressesForUser(username)
+      .collect(ImmutableList.toImmutableList()).asScala.toList
+      .flatMap(address =>
+        from(address).map(id =>
+          Identity(
+            id = id,
+            name = IdentityName(address.asString()),
+            email = address,
+            replyTo = None,
+            bcc = None,
+            textSignature = None,
+            htmlSignature = None,
+            mayDelete = MayDeleteIdentity(false))))
+
+  private def from(address: MailAddress): Option[IdentityId] =
+    Try(UUID.nameUUIDFromBytes(address.asString().getBytes(StandardCharsets.UTF_8)))
+      .toEither
+      .toOption
+      .map(IdentityId)
+}
+
+// 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 list(user: Username): Publisher[Identity] = SFlux.merge(Seq(
+    customIdentityDao.list(user),
+    SMono.fromCallable(() => identityFactory.listIdentities(user))
+      .subscribeOn(Schedulers.elastic())
+      .flatMapMany(SFlux.fromIterable)))
+
+  def update(user: Username, identityId: IdentityId, identityUpdate: IdentityUpdate): Publisher[Unit] = customIdentityDao.update(user, identityId, identityUpdate)
+
+  def delete(username: Username, ids: Seq[IdentityId]): Publisher[Unit] = customIdentityDao.delete(username, ids)

Review comment:
       Should we check Id is exists in `identityFactory`, if yes => throw an exception?
   
   From https://jmap.io/spec-mail.html#identityset
   ```
   Servers may wish to set this to false for the user’s username or other default address. Attempts to destroy an Identity with mayDelete: false will be rejected with a standard forbidden SetError.
   ```
   

##########
File path: server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MDNSendMethod.scala
##########
@@ -76,7 +76,9 @@ class MDNSendMethod @Inject()(serializer: MDNSerializer,
                          invocation: InvocationWithContext,
                          mailboxSession: MailboxSession,
                          request: MDNSendRequest): SFlux[InvocationWithContext] =
-    identityResolver.resolveIdentityId(request.identityId, mailboxSession)
+    request.identityId.validate
+      .fold(e => SMono.error(new IllegalArgumentException("The IdentityId cannot be found", e)),

Review comment:
       "The IdentityId is invalid"?

##########
File path: server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentityGetMethod.scala
##########
@@ -31,12 +32,12 @@ 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, JsObject, JsSuccess}
-import reactor.core.scala.publisher.SMono
+import reactor.core.scala.publisher.{SFlux, SMono}
 import reactor.core.scheduler.Schedulers
 
-class IdentityGetMethod @Inject() (identityFactory: IdentityFactory,
-                                  val metricFactory: MetricFactory,
-                                  val sessionSupplier: SessionSupplier) extends MethodRequiringAccountId[IdentityGetRequest] {
+class IdentityGetMethod @Inject() (identityFactory: IdentityRepository,

Review comment:
       `identityFactory` -> `identityRepository`, will more clear




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



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