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