diff --git a/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedServer.java b/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedServer.java index 5eaca9a202..875663f2f1 100644 --- a/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedServer.java +++ b/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedServer.java @@ -130,7 +130,6 @@ import com.linagora.tmail.blob.guice.BlobStoreCacheModulesChooser; import com.linagora.tmail.blob.guice.BlobStoreConfiguration; import com.linagora.tmail.blob.guice.BlobStoreModulesChooser; -import com.linagora.tmail.contact.RabbitMQEmailAddressContactModule; import com.linagora.tmail.encrypted.ClearEmailContentFactory; import com.linagora.tmail.encrypted.EncryptedMailboxManager; import com.linagora.tmail.encrypted.KeystoreManager; @@ -145,6 +144,7 @@ import com.linagora.tmail.imap.TMailIMAPModule; import com.linagora.tmail.james.jmap.ContactSupportCapabilitiesModule; import com.linagora.tmail.james.jmap.TMailJMAPModule; +import com.linagora.tmail.james.jmap.contact.RabbitMQEmailAddressContactModule; import com.linagora.tmail.james.jmap.firebase.CassandraFirebaseSubscriptionRepositoryModule; import com.linagora.tmail.james.jmap.firebase.FirebaseCommonModule; import com.linagora.tmail.james.jmap.firebase.FirebaseModuleChooserConfiguration; diff --git a/tmail-backend/guice/distributed/src/main/java/com/linagora/tmail/contact/RabbitMQEmailAddressContactModule.java b/tmail-backend/guice/distributed/src/main/java/com/linagora/tmail/james/jmap/contact/RabbitMQEmailAddressContactModule.java similarity index 99% rename from tmail-backend/guice/distributed/src/main/java/com/linagora/tmail/contact/RabbitMQEmailAddressContactModule.java rename to tmail-backend/guice/distributed/src/main/java/com/linagora/tmail/james/jmap/contact/RabbitMQEmailAddressContactModule.java index 0c8471004b..538ba9fe42 100644 --- a/tmail-backend/guice/distributed/src/main/java/com/linagora/tmail/contact/RabbitMQEmailAddressContactModule.java +++ b/tmail-backend/guice/distributed/src/main/java/com/linagora/tmail/james/jmap/contact/RabbitMQEmailAddressContactModule.java @@ -1,4 +1,4 @@ -package com.linagora.tmail.contact; +package com.linagora.tmail.james.jmap.contact; import jakarta.inject.Named; import jakarta.inject.Provider; diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LabelChangesMethodContract.scala b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LabelChangesMethodContract.scala index 0341149e36..b3bec41da4 100644 --- a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LabelChangesMethodContract.scala +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LabelChangesMethodContract.scala @@ -6,8 +6,8 @@ import com.google.common.collect.ImmutableList import com.linagora.tmail.james.common.LabelChangesMethodContract.firebasePushClient import com.linagora.tmail.james.common.probe.JmapGuiceLabelProbe import com.linagora.tmail.james.jmap.firebase.{FirebasePushClient, FirebasePushRequest} -import com.linagora.tmail.james.jmap.label.{LabelChange, LabelTypeName} -import com.linagora.tmail.james.jmap.model.LabelId +import com.linagora.tmail.james.jmap.label.{LabelTypeName} +import com.linagora.tmail.james.jmap.model.{LabelChange, LabelId} import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT import io.restassured.RestAssured.{`given`, requestSpecification} import io.restassured.http.ContentType.JSON diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/probe/JmapGuiceLabelProbe.java b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/probe/JmapGuiceLabelProbe.java index 826109f282..5adb8e4420 100644 --- a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/probe/JmapGuiceLabelProbe.java +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/probe/JmapGuiceLabelProbe.java @@ -7,10 +7,10 @@ import org.apache.james.core.Username; import org.apache.james.utils.GuiceProbe; -import com.linagora.tmail.james.jmap.label.LabelChange; import com.linagora.tmail.james.jmap.label.LabelChangeRepository; import com.linagora.tmail.james.jmap.label.LabelRepository; import com.linagora.tmail.james.jmap.model.Label; +import com.linagora.tmail.james.jmap.model.LabelChange; import com.linagora.tmail.james.jmap.model.LabelCreationRequest; import reactor.core.publisher.Flux; diff --git a/tmail-backend/jmap/extensions-api/pom.xml b/tmail-backend/jmap/extensions-api/pom.xml new file mode 100644 index 0000000000..2db88b9577 --- /dev/null +++ b/tmail-backend/jmap/extensions-api/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + com.linagora.tmail + tmail-backend + 1.0.0-SNAPSHOT + ../../pom.xml + + + jmap-extensions-api + Twake Mail :: JMAP :: Extensions :: API + + + 5.3.7 + + + + + ${james.groupId} + james-server-data-jmap + + + ${james.groupId} + james-server-jmap-rfc-8621 + + + ${james.groupId} + james-server-testing + test + + + ${james.groupId} + testing-base + test + + + com.github.f4b6a3 + uuid-creator + ${uuid-creator.version} + + + com.google.guava + guava + + + org.mockito + mockito-core + test + + + + + + net.alchim31.maven + scala-maven-plugin + + + io.github.evis + scalafix-maven-plugin_2.13 + + ${project.parent.parent.basedir}/.scalafix.conf + + + + + \ No newline at end of file diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/EmailAddressContactInjectKeys.java b/tmail-backend/jmap/extensions-api/src/main/java/com/linagora/tmail/james/jmap/EmailAddressContactInjectKeys.java similarity index 100% rename from tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/EmailAddressContactInjectKeys.java rename to tmail-backend/jmap/extensions-api/src/main/java/com/linagora/tmail/james/jmap/EmailAddressContactInjectKeys.java diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionHelper.java b/tmail-backend/jmap/extensions-api/src/main/java/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionHelper.java similarity index 100% rename from tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionHelper.java rename to tmail-backend/jmap/extensions-api/src/main/java/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionHelper.java diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionRepository.java b/tmail-backend/jmap/extensions-api/src/main/java/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionRepository.java similarity index 100% rename from tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionRepository.java rename to tmail-backend/jmap/extensions-api/src/main/java/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionRepository.java diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionUserDeletionTaskStep.java b/tmail-backend/jmap/extensions-api/src/main/java/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionUserDeletionTaskStep.java similarity index 100% rename from tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionUserDeletionTaskStep.java rename to tmail-backend/jmap/extensions-api/src/main/java/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionUserDeletionTaskStep.java diff --git a/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/PublicAssetTotalSizeLimit.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/PublicAssetTotalSizeLimit.scala new file mode 100644 index 0000000000..5a2c9f6b5b --- /dev/null +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/PublicAssetTotalSizeLimit.scala @@ -0,0 +1,20 @@ +package com.linagora.tmail.james.jmap + +import eu.timepit.refined +import org.apache.james.jmap.core.UnsignedInt.{UnsignedInt, UnsignedIntConstraint} +import org.apache.james.util.Size + +import scala.util.{Failure, Success, Try} + +object PublicAssetTotalSizeLimit { + val DEFAULT: PublicAssetTotalSizeLimit = PublicAssetTotalSizeLimit.of(Size.of(20L, Size.Unit.M)).get + + def of(size: Size): Try[PublicAssetTotalSizeLimit] = refined.refineV[UnsignedIntConstraint](size.asBytes()) match { + case Right(value) => Success(PublicAssetTotalSizeLimit(value)) + case Left(error) => Failure(new NumberFormatException(error)) + } +} + +case class PublicAssetTotalSizeLimit(value: UnsignedInt) { + def asLong(): Long = value.value +} \ No newline at end of file diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/ContactUserDeletionTaskStep.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/ContactUserDeletionTaskStep.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/ContactUserDeletionTaskStep.scala rename to tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/ContactUserDeletionTaskStep.scala diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/ContactUsernameChangeTaskStep.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/ContactUsernameChangeTaskStep.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/ContactUsernameChangeTaskStep.scala rename to tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/ContactUsernameChangeTaskStep.scala diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContact.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContact.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContact.scala rename to tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContact.scala diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactEventModule.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactEventModule.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactEventModule.scala rename to tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactEventModule.scala diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactListener.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactListener.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactListener.scala rename to tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactListener.scala diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactMessage.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactMessage.scala similarity index 93% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactMessage.scala rename to tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactMessage.scala index c11b14a1b9..acd0def1c9 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactMessage.scala +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactMessage.scala @@ -2,8 +2,8 @@ package com.linagora.tmail.james.jmap.contact import java.util.Locale -import com.linagora.tmail.james.jmap.contact.TmailContactMessageScope.{DOMAIN, USER} -import com.linagora.tmail.james.jmap.contact.TmailContactMessageType.{ADDITION, REMOVAL, UPDATE} +import TmailContactMessageScope.{DOMAIN, USER} +import TmailContactMessageType.{ADDITION, REMOVAL, UPDATE} import org.apache.james.core.{Domain, MailAddress, Username} import scala.util.Try diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactMessageHandler.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactMessageHandler.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactMessageHandler.scala rename to tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactMessageHandler.scala diff --git a/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/label/LabelChangeRepository.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/label/LabelChangeRepository.scala new file mode 100644 index 0000000000..02050e6624 --- /dev/null +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/label/LabelChangeRepository.scala @@ -0,0 +1,19 @@ +package com.linagora.tmail.james.jmap.label + +import com.linagora.tmail.james.jmap.label.LabelChangeRepository.DEFAULT_MAX_IDS_TO_RETURN +import com.linagora.tmail.james.jmap.model.{LabelChange, LabelChanges} +import org.apache.james.jmap.api.change.{Limit, State} +import org.apache.james.jmap.api.model.AccountId +import org.reactivestreams.Publisher + +object LabelChangeRepository { + val DEFAULT_MAX_IDS_TO_RETURN : Limit = Limit.of(256) +} + +trait LabelChangeRepository { + def save(labelChange: LabelChange): Publisher[Void] + + def getSinceState(accountId: AccountId, state: State, maxIdsToReturn: Option[Limit] = Some(DEFAULT_MAX_IDS_TO_RETURN)): Publisher[LabelChanges] + + def getLatestState(accountId: AccountId): Publisher[State] +} \ No newline at end of file diff --git a/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/label/LabelRepository.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/label/LabelRepository.scala new file mode 100644 index 0000000000..b96f5dd83b --- /dev/null +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/label/LabelRepository.scala @@ -0,0 +1,25 @@ +package com.linagora.tmail.james.jmap.label + +import java.util + +import com.linagora.tmail.james.jmap.model.{Color, DisplayName, Label, LabelCreationRequest, LabelId} +import org.apache.james.core.Username +import org.reactivestreams.Publisher + +trait LabelRepository { + def addLabel(username: Username, labelCreationRequest: LabelCreationRequest): Publisher[Label] + + def addLabel(username: Username, label: Label): Publisher[Void] + + def addLabels(username: Username, labelCreationRequests: util.Collection[LabelCreationRequest]): Publisher[Label] + + def updateLabel(username: Username, labelId: LabelId, newDisplayName: Option[DisplayName] = None, newColor: Option[Color] = None): Publisher[Void] + + def getLabels(username: Username, ids: util.Collection[LabelId]): Publisher[Label] + + def listLabels(username: Username): Publisher[Label] + + def deleteLabel(username: Username, labelId: LabelId): Publisher[Void] + + def deleteAllLabels(username: Username): Publisher[Void] +} \ No newline at end of file diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/LabelUserDeletionTaskStep.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/label/LabelUserDeletionTaskStep.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/LabelUserDeletionTaskStep.scala rename to tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/label/LabelUserDeletionTaskStep.scala diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/LabelUsernameChangeTaskStep.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/label/LabelUsernameChangeTaskStep.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/LabelUsernameChangeTaskStep.scala rename to tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/label/LabelUsernameChangeTaskStep.scala diff --git a/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/model/FirebaseSubscription.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/model/FirebaseSubscription.scala new file mode 100644 index 0000000000..b0769e00a7 --- /dev/null +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/model/FirebaseSubscription.scala @@ -0,0 +1,117 @@ +package com.linagora.tmail.james.jmap.model + +import java.time.ZonedDateTime +import java.util.UUID + +import eu.timepit.refined.auto._ +import org.apache.james.jmap.api.model.ExpireTimeInvalidException.TIME_FORMATTER +import org.apache.james.jmap.api.model.TypeName +import org.apache.james.jmap.core.Id.Id +import org.apache.james.jmap.core.{Id, Properties} +import org.apache.james.jmap.method.WithoutAccountId + +import scala.util.Try + +object FirebaseSubscriptionId { + def generate(): FirebaseSubscriptionId = FirebaseSubscriptionId(UUID.randomUUID) + + def liftOrThrow(unparsedId: UnparsedFirebaseSubscriptionId): Either[IllegalArgumentException, FirebaseSubscriptionId] = + liftOrThrow(unparsedId.id.value) + + def liftOrThrow(value: String): Either[IllegalArgumentException, FirebaseSubscriptionId] = + Try(UUID.fromString(value)) + .map(value1 => FirebaseSubscriptionId(value1)) + .toEither + .left.map(e => new IllegalArgumentException("FirebaseSubscriptionId is invalid", e)) +} + +case class FirebaseSubscriptionId(value: UUID) { + def serialize: String = value.toString + + def asUnparsedFirebaseSubscriptionId: UnparsedFirebaseSubscriptionId = + UnparsedFirebaseSubscriptionId(Id.validate(serialize).toOption.get) +} + +case class DeviceClientId(value: String) extends AnyVal + +case class FirebaseToken(value: String) extends AnyVal + +case class FirebaseSubscriptionExpiredTime(value: ZonedDateTime) { + def isAfter(date: ZonedDateTime): Boolean = value.isAfter(date) + + def isBefore(date: ZonedDateTime): Boolean = value.isBefore(date) +} + +object FirebaseSubscriptionCreation { + val serverSetProperty: Set[String] = Set("id") + val assignableProperties: Set[String] = Set("deviceClientId", "token", "expires", "types") + val knownProperties: Set[String] = assignableProperties ++ serverSetProperty +} + +case class FirebaseSubscriptionCreationRequest(deviceClientId: DeviceClientId, + token: FirebaseToken, + expires: Option[FirebaseSubscriptionExpiredTime] = None, + types: Seq[TypeName]) { + + def validate: Either[IllegalArgumentException, FirebaseSubscriptionCreationRequest] = + validateTypes + + private def validateTypes: Either[IllegalArgumentException, FirebaseSubscriptionCreationRequest] = + if (types.isEmpty) { + scala.Left(new IllegalArgumentException("types must not be empty")) + } else { + Right(this) + } +} + +object FirebaseSubscription { + val EXPIRES_TIME_MAX_DAY: Int = 7 + val allProperties: Properties = Properties("id", "deviceClientId", "expires", "types") + val idProperty: Properties = Properties("id") + + def from(creationRequest: FirebaseSubscriptionCreationRequest, + expireTime: FirebaseSubscriptionExpiredTime): FirebaseSubscription = + FirebaseSubscription(id = FirebaseSubscriptionId.generate(), + deviceClientId = creationRequest.deviceClientId, + token = creationRequest.token, + expires = expireTime, + types = creationRequest.types) +} + +case class FirebaseSubscription(id: FirebaseSubscriptionId, + deviceClientId: DeviceClientId, + token: FirebaseToken, + expires: FirebaseSubscriptionExpiredTime, + types: Seq[TypeName]) { + def withTypes(types: Seq[TypeName]): FirebaseSubscription = copy(types = types) + + def withExpires(expires: FirebaseSubscriptionExpiredTime): FirebaseSubscription = copy(expires = expires) +} + +case class FirebaseSubscriptionNotFoundException(id: FirebaseSubscriptionId) extends RuntimeException + +case class ExpireTimeInvalidException(expires: ZonedDateTime, message: String) extends IllegalStateException(s"`${expires.format(TIME_FORMATTER)}` $message") + +case class DeviceClientIdInvalidException(deviceClientId: DeviceClientId, message: String) extends IllegalArgumentException(s"`${deviceClientId.value}` $message") + +case class TokenInvalidException(message: String) extends IllegalArgumentException(message) + +case class MissingOrInvalidFirebaseCredentialException(message: String) extends IllegalArgumentException(message) + +case class UnparsedFirebaseSubscriptionId(id: Id) + +case class FirebaseSubscriptionIds(list: List[UnparsedFirebaseSubscriptionId]) + +case class FirebaseSubscriptionGetRequest(ids: Option[FirebaseSubscriptionIds], + properties: Option[Properties]) extends WithoutAccountId { + + def validateProperties: Either[IllegalArgumentException, Properties] = + properties match { + case None => Right(FirebaseSubscription.allProperties) + case Some(value) => + value -- FirebaseSubscription.allProperties match { + case invalidProperties if invalidProperties.isEmpty() => Right(value ++ FirebaseSubscription.idProperty) + case invalidProperties: Properties => Left(new IllegalArgumentException(s"The following properties [${invalidProperties.format()}] do not exist.")) + } + } +} \ No newline at end of file diff --git a/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/model/Label.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/model/Label.scala new file mode 100644 index 0000000000..2a38004603 --- /dev/null +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/model/Label.scala @@ -0,0 +1,86 @@ +package com.linagora.tmail.james.jmap.model + +import java.util.UUID + +import eu.timepit.refined +import eu.timepit.refined.auto._ +import eu.timepit.refined.string.MatchesRegex +import org.apache.james.jmap.core.Id.Id +import org.apache.james.jmap.core.{AccountId, Id, Properties, SetError, UuidState} +import org.apache.james.jmap.mail.Keyword +import org.apache.james.jmap.method.WithAccountId + +case class LabelCreationParseException(setError: SetError) extends Exception + +object LabelId { + def fromKeyword(keyword: Keyword): LabelId = + LabelId(Id.validate(keyword.flagName).toOption.get) + + def generate(): LabelId = + LabelId(Id.validate(UUID.randomUUID().toString).toOption.get) +} + +case class LabelId(id: Id) { + def toKeyword: Keyword = + Keyword.of(id.value).get + + def asUnparsedLabelId: UnparsedLabelId = + UnparsedLabelId(id) + + def serialize: String = id.value +} + +object KeywordUtil { + def generate(): Keyword = + Keyword.of(UUID.randomUUID().toString).get +} + +case class DisplayName(value: String) + +object Color { + private type ColorRegex = MatchesRegex["^#[a-fA-F0-9]{6}$"] + + def validate(string: String): Either[IllegalArgumentException, Color] = + refined.refineV[ColorRegex](string) match { + case Left(_) => scala.Left(new IllegalArgumentException(s"The string should be a valid hexadecimal color value following this pattern #[a-fA-F0-9]{6}")) + case Right(value) => scala.Right(Color(value)) + } +} + +case class Color(value: String) + +object LabelCreationRequest { + val serverSetProperty = Set("id", "keyword") + val assignableProperties = Set("displayName", "color") + val knownProperties = assignableProperties ++ serverSetProperty +} + +case class LabelCreationRequest(displayName: DisplayName, color: Option[Color]) { + def toLabel: Label = { + val keyword: Keyword = KeywordUtil.generate() + + Label(id = LabelId.fromKeyword(keyword), + displayName = displayName, + keyword = keyword, + color = color) + } +} + +object Label { + val allProperties: Properties = Properties("id", "displayName", "keyword", "color") + val idProperty: Properties = Properties("id") +} + +case class Label(id: LabelId, displayName: DisplayName, keyword: Keyword, color: Option[Color]) { + def update(newDisplayName: Option[DisplayName], newColor: Option[Color]): Label = + copy(displayName = newDisplayName.getOrElse(displayName), + color = newColor.orElse(color)) +} + +case class LabelNotFoundException(id: LabelId) extends RuntimeException + +case class UnparsedLabelId(id: Id) { + def asLabelId: LabelId = LabelId(id) +} + +case class LabelIds(list: List[UnparsedLabelId]) \ No newline at end of file diff --git a/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/model/LabelChange.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/model/LabelChange.scala new file mode 100644 index 0000000000..9108cebc20 --- /dev/null +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/model/LabelChange.scala @@ -0,0 +1,64 @@ +package com.linagora.tmail.james.jmap.model + +import java.util.function.Supplier + +import org.apache.james.jmap.api.change.{JmapChange, State} +import org.apache.james.jmap.api.model.AccountId + +case class LabelChange(accountId: AccountId, + created: Set[LabelId] = Set(), + updated: Set[LabelId] = Set(), + destroyed: Set[LabelId] = Set(), + state: State) extends JmapChange { + override def getAccountId: AccountId = accountId + + override def isNoop: Boolean = created.isEmpty && updated.isEmpty && destroyed.isEmpty + + override def forSharee(accountId: AccountId, state: Supplier[State]): JmapChange = + LabelChange(accountId = accountId, + created = created, + updated = updated, + destroyed = destroyed, + state = state.get()) +} + +object LabelChanges { + def from(labelChange: LabelChange): LabelChanges = LabelChanges( + created = labelChange.created, + updated = labelChange.updated, + destroyed = labelChange.destroyed, + newState = labelChange.state) + + def initial(): LabelChanges = LabelChanges( + created = Set(), + updated = Set(), + destroyed = Set(), + newState = State.INITIAL) + + def merge(limit: Int, change1: LabelChanges, change2: LabelChanges): LabelChanges = + change1.canAppendMoreItem match { + case false => change1 + case true if change1.newState.equals(State.INITIAL) => change2 + case true => + val createdTemp: Set[LabelId] = (change1.created ++ change2.created).diff(change2.destroyed) + val updatedTemp: Set[LabelId] = (change1.updated ++ change2.updated.diff(createdTemp)).diff(change2.destroyed) + val destroyedTemp: Set[LabelId] = change1.destroyed ++ change2.destroyed.diff(change1.created) + if (createdTemp.size + updatedTemp.size + destroyedTemp.size > limit) { + change1.copy(hasMoreChanges = true, canAppendMoreItem = false) + } else { + change1.copy(created = createdTemp, + updated = updatedTemp, + destroyed = destroyedTemp, + newState = change2.newState) + } + } +} + +case class LabelChanges(created: Set[LabelId] = Set(), + updated: Set[LabelId] = Set(), + destroyed: Set[LabelId] = Set(), + hasMoreChanges: Boolean = false, + newState: State, + private val canAppendMoreItem: Boolean = true) { + def getAllChanges: Set[LabelId] = created ++ updated ++ destroyed +} \ No newline at end of file diff --git a/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetRepository.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetRepository.scala new file mode 100644 index 0000000000..0db1323e75 --- /dev/null +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetRepository.scala @@ -0,0 +1,34 @@ +package com.linagora.tmail.james.jmap.publicAsset + +import org.apache.james.blob.api.BlobId +import org.apache.james.core.Username +import org.apache.james.jmap.api.model.IdentityId +import org.reactivestreams.Publisher +import reactor.core.scala.publisher.SMono + +trait PublicAssetRepository { + def create(username: Username, creationRequest: PublicAssetCreationRequest): Publisher[PublicAssetStorage] + + def update(username: Username, id: PublicAssetId, identityIds: Set[IdentityId]): Publisher[Void] + + def remove(username: Username, id: PublicAssetId): Publisher[Void] + + def revoke(username: Username): Publisher[Void] + + def get(username: Username, ids: Set[PublicAssetId]): Publisher[PublicAssetStorage] + + def get(username: Username, id: PublicAssetId): Publisher[PublicAssetStorage] = get(username, Set(id)) + + def list(username: Username): Publisher[PublicAssetStorage] + + def listPublicAssetMetaDataOrderByIdAsc(username: Username): Publisher[PublicAssetMetadata] + + def listAllBlobIds(): Publisher[BlobId] + + def updateIdentityIds(username: Username, id: PublicAssetId, identityIdsToAdd: Seq[IdentityId], identityIdsToRemove: Seq[IdentityId]): Publisher[Void] = + SMono(get(username, id)) + .map(publicAsset => (publicAsset.identityIds.toSet ++ identityIdsToAdd.toSet) -- identityIdsToRemove.toSet) + .flatMap(identityIds => SMono(update(username, id, identityIds))) + + def getTotalSize(username: Username): Publisher[Long] +} \ No newline at end of file diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetStorage.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetRequest.scala similarity index 91% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetStorage.scala rename to tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetRequest.scala index daf863edbf..9152612436 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetStorage.scala +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetRequest.scala @@ -2,7 +2,6 @@ package com.linagora.tmail.james.jmap.publicAsset import java.io.InputStream import java.net.URI -import java.time.Instant import java.util.UUID import com.github.f4b6a3.uuid.UuidCreator @@ -16,32 +15,21 @@ import org.apache.james.core.Username import org.apache.james.jmap.api.model.IdentityId import org.apache.james.jmap.api.model.Size.Size import org.apache.james.jmap.core.JmapRfc8621Configuration +import org.apache.james.jmap.mail.{BlobId => JmapBlobId} import org.apache.james.mailbox.model.ContentType import scala.util.Try -object PublicAssetIdFactory { - def generate(): PublicAssetId = PublicAssetId(UuidCreator.getTimeBased) - - def from(value: String): Either[(String, IllegalArgumentException), PublicAssetId] = - Try(PublicAssetId(UUID.fromString(value))) - .toEither - .left.map(e => value -> new IllegalArgumentException(e)) +object PublicAssetSetCreationRequest { + val knownProperties: Set[String] = Set("blobId", "identityIds") } -object PublicAssetId { - def fromString(value: String): Try[PublicAssetId] = - Try(PublicAssetId(UUID.fromString(value))) -} +case class PublicAssetSetCreationRequest(blobId: JmapBlobId, identityIds: Option[Map[IdentityId, Boolean]] = None) -case class PublicAssetId(value: UUID) { - def asString(): String = value.toString -} - -object PublicAssetURIPrefix { - def fromConfiguration(configuration: JmapRfc8621Configuration): Either[Throwable, URI] = - Try(new URI(configuration.urlPrefixString)).toEither -} +case class PublicAssetCreationRequest(size: Size, + contentType: ImageContentType, + identityIds: Seq[IdentityId] = Seq.empty, + content: () => InputStream) object PublicURI { def fromString(value: String): Either[Throwable, PublicURI] = Try(new URI(value)) @@ -62,6 +50,29 @@ object PublicURI { } } +object PublicAssetIdFactory { + def generate(): PublicAssetId = PublicAssetId(UuidCreator.getTimeBased) + + def from(value: String): Either[(String, IllegalArgumentException), PublicAssetId] = + Try(PublicAssetId(UUID.fromString(value))) + .toEither + .left.map(e => value -> new IllegalArgumentException(e)) +} + +object PublicAssetId { + def fromString(value: String): Try[PublicAssetId] = + Try(PublicAssetId(UUID.fromString(value))) +} + +case class PublicAssetId(value: UUID) { + def asString(): String = value.toString +} + +object PublicAssetURIPrefix { + def fromConfiguration(configuration: JmapRfc8621Configuration): Either[Throwable, URI] = + Try(new URI(configuration.urlPrefixString)).toEither +} + case class PublicURI(value: URI) extends AnyVal object ImageContentType { @@ -87,24 +98,6 @@ object ImageContentType { .map(e => PublicAssetInvalidContentTypeException(e)) } -trait PublicAssetException extends RuntimeException { - def message: String - - override def getMessage: String = message -} - -case class PublicAssetInvalidContentTypeException(contentType: String) extends PublicAssetException { - override val message: String = s"Invalid content type: $contentType" -} - -case class PublicAssetNotFoundException(id: PublicAssetId) extends PublicAssetException { - override val message: String = s"Public asset not found: ${id.asString()}" -} - -case class PublicAssetQuotaLimitExceededException(limitAsByte: Long) extends PublicAssetException { - override val message: String = s"Exceeding public asset quota limit of $limitAsByte bytes" -} - case class PublicAssetStorage(id: PublicAssetId, publicURI: PublicURI, size: Size, @@ -117,11 +110,6 @@ case class PublicAssetStorage(id: PublicAssetId, def contentTypeAsString(): String = contentType.value } -case class PublicAssetCreationRequest(size: Size, - contentType: ImageContentType, - identityIds: Seq[IdentityId] = Seq.empty, - content: () => InputStream) - object PublicAssetMetadata { def from(publicAsset: PublicAssetStorage): PublicAssetMetadata = PublicAssetMetadata( @@ -150,4 +138,26 @@ case class PublicAssetMetadata(id: PublicAssetId, content = () => content) def sizeAsLong(): java.lang.Long = size.value +} + +trait PublicAssetException extends RuntimeException { + def message: String + + override def getMessage: String = message +} + +case class PublicAssetNotFoundException(id: PublicAssetId) extends PublicAssetException { + override val message: String = s"Public asset not found: ${id.asString()}" +} + +case class PublicAssetQuotaLimitExceededException(limitAsByte: Long) extends PublicAssetException { + override val message: String = s"Exceeding public asset quota limit of $limitAsByte bytes" +} + +case class PublicAssetInvalidContentTypeException(contentType: String) extends PublicAssetException { + override val message: String = s"Invalid content type: $contentType" +} + +case class PublicAssetIdentityIdNotFoundException(identityIds: Seq[IdentityId]) extends PublicAssetException { + override val message: String = s"IdentityId not found: ${identityIds.map(_.id.toString).mkString(", ")}" } \ No newline at end of file diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetSetService.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetSetService.scala similarity index 92% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetSetService.scala rename to tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetSetService.scala index ee1390b409..bff522b6ef 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetSetService.scala +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetSetService.scala @@ -1,6 +1,6 @@ package com.linagora.tmail.james.jmap.publicAsset -import com.linagora.tmail.james.jmap.JMAPExtensionConfiguration +import com.linagora.tmail.james.jmap.PublicAssetTotalSizeLimit import jakarta.inject.Inject import org.apache.james.core.Username import org.apache.james.jmap.api.identity.IdentityRepository @@ -10,7 +10,7 @@ import reactor.core.scala.publisher.{SFlux, SMono} class PublicAssetSetService @Inject()(val identityRepository: IdentityRepository, val publicAssetRepository: PublicAssetRepository, - val configuration: JMAPExtensionConfiguration) { + val publicAssetTotalSizeLimit: PublicAssetTotalSizeLimit) { def checkIdentityIdsExist(identityIds: Seq[IdentityId], session: MailboxSession): SMono[Seq[IdentityId]] = SFlux(identityRepository.list(session.getUser)) @@ -27,7 +27,7 @@ class PublicAssetSetService @Inject()(val identityRepository: IdentityRepository .onErrorResume { case _: PublicAssetQuotaLimitExceededException => cleanUpPublicAsset(username, creationRequest.size.value) .filter(cleanedUpSize => cleanedUpSize >= creationRequest.size.value) - .switchIfEmpty(SMono.error(new PublicAssetQuotaLimitExceededException(configuration.publicAssetTotalSizeLimit.asLong()))) + .switchIfEmpty(SMono.error(new PublicAssetQuotaLimitExceededException(publicAssetTotalSizeLimit.asLong()))) .flatMap(_ => SMono(publicAssetRepository.create(username, creationRequest))) } diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettings.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettings.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettings.scala rename to tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettings.scala diff --git a/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsRepository.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsRepository.scala new file mode 100644 index 0000000000..581da104cb --- /dev/null +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsRepository.scala @@ -0,0 +1,17 @@ +package com.linagora.tmail.james.jmap.settings + +import org.apache.james.core.Username +import org.apache.james.jmap.core.UuidState +import org.reactivestreams.Publisher + +trait JmapSettingsRepository { + def get(username: Username): Publisher[JmapSettings] + + def getLatestState(username: Username): Publisher[UuidState] + + def reset(username: Username, settings: JmapSettingsUpsertRequest): Publisher[SettingsStateUpdate] + + def updatePartial(username: Username, settingsPatch: JmapSettingsPatch): Publisher[SettingsStateUpdate] + + def delete(username: Username): Publisher[Void] +} diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsUserDeletionTaskStep.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsUserDeletionTaskStep.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsUserDeletionTaskStep.scala rename to tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsUserDeletionTaskStep.scala diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsUsernameChangeTaskStep.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsUsernameChangeTaskStep.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsUsernameChangeTaskStep.scala rename to tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsUsernameChangeTaskStep.scala diff --git a/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/ticket/ForbiddenException.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/ticket/ForbiddenException.scala new file mode 100644 index 0000000000..79a83e3bb5 --- /dev/null +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/ticket/ForbiddenException.scala @@ -0,0 +1,3 @@ +package com.linagora.tmail.james.jmap.ticket + +case class ForbiddenException() extends RuntimeException \ No newline at end of file diff --git a/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/ticket/Ticket.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/ticket/Ticket.scala new file mode 100644 index 0000000000..9f4ea8d490 --- /dev/null +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/ticket/Ticket.scala @@ -0,0 +1,12 @@ +package com.linagora.tmail.james.jmap.ticket + +import java.net.InetAddress + +import org.apache.james.core.Username +import org.apache.james.jmap.core.UTCDate + +case class Ticket(clientAddress: InetAddress, + value: TicketValue, + generatedOn: UTCDate, + validUntil: UTCDate, + username: Username) \ No newline at end of file diff --git a/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/ticket/TicketStore.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/ticket/TicketStore.scala new file mode 100644 index 0000000000..ee36efa622 --- /dev/null +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/ticket/TicketStore.scala @@ -0,0 +1,11 @@ +package com.linagora.tmail.james.jmap.ticket + +import reactor.core.scala.publisher.SMono + +trait TicketStore { + def persist(ticket: Ticket): SMono[Unit] + + def retrieve(value: TicketValue): SMono[Ticket] + + def delete(ticketValue: TicketValue): SMono[Unit] +} diff --git a/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/ticket/TicketValue.scala b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/ticket/TicketValue.scala new file mode 100644 index 0000000000..8fdeba45e2 --- /dev/null +++ b/tmail-backend/jmap/extensions-api/src/main/scala/com/linagora/tmail/james/jmap/ticket/TicketValue.scala @@ -0,0 +1,15 @@ +package com.linagora.tmail.james.jmap.ticket + +import java.util.UUID + +import scala.util.Try + +object TicketValue { + def parse(string: String): Either[IllegalArgumentException, TicketValue] = Try(UUID.fromString(string)) + .map(TicketValue(_)) + .fold(e => Left(new IllegalArgumentException("TicketValue must be backed by a UUID", e)), Right(_)) + + def generate: TicketValue = TicketValue(UUID.randomUUID()) +} + +case class TicketValue(value: UUID) \ No newline at end of file diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactSearchEngineContract.scala b/tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactSearchEngineContract.scala similarity index 98% rename from tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactSearchEngineContract.scala rename to tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactSearchEngineContract.scala index b68bdfa53f..c9b109f87c 100644 --- a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactSearchEngineContract.scala +++ b/tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactSearchEngineContract.scala @@ -2,7 +2,7 @@ package com.linagora.tmail.james.jmap.contact import java.util.stream.IntStream -import com.linagora.tmail.james.jmap.contact.EmailAddressContactSearchEngineContract.{accountId, accountIdB, bigContactsNumber, contactEmptyNameFieldsA, contactEmptyNameFieldsB, contactFieldsA, contactFieldsB, contactFieldsFrench, domain, firstnameB, mailAddressA, otherContactEmptyNameFields, otherContactFields, otherContactFieldsWithUppercaseEmail, otherMailAddress, surnameB} +import EmailAddressContactSearchEngineContract.{accountId, accountIdB, bigContactsNumber, contactEmptyNameFieldsA, contactEmptyNameFieldsB, contactFieldsA, contactFieldsB, contactFieldsFrench, domain, firstnameB, mailAddressA, otherContactEmptyNameFields, otherContactFields, otherContactFieldsWithUppercaseEmail, otherMailAddress, surnameB} import org.apache.james.core.{Domain, MailAddress, Username} import org.apache.james.jmap.api.model.AccountId import org.assertj.core.api.Assertions.{assertThat, assertThatCode, assertThatThrownBy} diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/QueryType.scala b/tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/contact/QueryType.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/QueryType.scala rename to tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/contact/QueryType.scala diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionRepositoryContract.scala b/tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionRepositoryContract.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionRepositoryContract.scala rename to tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/firebase/FirebaseSubscriptionRepositoryContract.scala diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/label/LabelChangeRepositoryContract.scala b/tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/label/LabelChangeRepositoryContract.scala similarity index 95% rename from tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/label/LabelChangeRepositoryContract.scala rename to tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/label/LabelChangeRepositoryContract.scala index 622f2f90e5..952a7662a9 100644 --- a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/label/LabelChangeRepositoryContract.scala +++ b/tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/label/LabelChangeRepositoryContract.scala @@ -4,7 +4,7 @@ import java.time.ZonedDateTime import java.util.stream.IntStream import com.linagora.tmail.james.jmap.label.LabelChangeRepositoryContract.DATE -import com.linagora.tmail.james.jmap.model.LabelId +import com.linagora.tmail.james.jmap.model.{LabelChange, LabelChanges, LabelId} import org.apache.james.core.Username import org.apache.james.jmap.api.change.{Limit, State} import org.apache.james.jmap.api.exception.ChangeNotFoundException @@ -344,20 +344,3 @@ trait LabelChangeRepositoryContract { } } - -class MemoryLabelChangeRepositoryTest extends LabelChangeRepositoryContract { - var repository: MemoryLabelChangeRepository = _ - var updatableTickingClock: UpdatableTickingClock = _ - - override def testee: LabelChangeRepository = repository - - override def stateFactory: State.Factory = State.Factory.DEFAULT - - override def setClock(newTime: ZonedDateTime): Unit = updatableTickingClock.setInstant(newTime.toInstant) - - @BeforeEach - def setup(): Unit = { - updatableTickingClock = new UpdatableTickingClock(DATE.toInstant) - repository = MemoryLabelChangeRepository(updatableTickingClock) - } -} diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/label/LabelRepositoryContract.scala b/tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/label/LabelRepositoryContract.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/label/LabelRepositoryContract.scala rename to tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/label/LabelRepositoryContract.scala diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetRepositoryContract.scala b/tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetRepositoryContract.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetRepositoryContract.scala rename to tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetRepositoryContract.scala diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetServiceContract.scala b/tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetServiceContract.scala similarity index 98% rename from tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetServiceContract.scala rename to tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetServiceContract.scala index a9f30d3f2c..a4d92cedc1 100644 --- a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetServiceContract.scala +++ b/tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetServiceContract.scala @@ -3,7 +3,7 @@ package com.linagora.tmail.james.jmap.publicAsset import java.time.Instant import java.time.temporal.ChronoUnit -import com.linagora.tmail.james.jmap.JMAPExtensionConfiguration.PUBLIC_ASSET_TOTAL_SIZE_LIMIT_DEFAULT +import com.linagora.tmail.james.jmap.PublicAssetTotalSizeLimit import com.linagora.tmail.james.jmap.publicAsset.PublicAssetServiceContract.{CLOCK, IDENTITY1, IDENTITY_ID1, PUBLIC_ASSET_TOTAL_SIZE_LIMIT_IN_CONFIGURED, identityRepository} import org.apache.james.core.Username import org.apache.james.jmap.api.identity.IdentityRepository @@ -30,7 +30,7 @@ object PublicAssetServiceContract { val identityRepository: IdentityRepository = mock(classOf[IdentityRepository]) - val PUBLIC_ASSET_TOTAL_SIZE_LIMIT_IN_CONFIGURED: Long = PUBLIC_ASSET_TOTAL_SIZE_LIMIT_DEFAULT.asLong() + val PUBLIC_ASSET_TOTAL_SIZE_LIMIT_IN_CONFIGURED: Long = PublicAssetTotalSizeLimit.DEFAULT.asLong() val CLOCK = new UpdatableTickingClock(Instant.now()) } diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsRepositoryContract.scala b/tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsRepositoryContract.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsRepositoryContract.scala rename to tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsRepositoryContract.scala diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/ticket/TicketStoreContract.scala b/tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/ticket/TicketStoreContract.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/ticket/TicketStoreContract.scala rename to tmail-backend/jmap/extensions-api/src/test/scala/com/linagora/tmail/james/jmap/ticket/TicketStoreContract.scala diff --git a/tmail-backend/jmap/extensions-cassandra/pom.xml b/tmail-backend/jmap/extensions-cassandra/pom.xml index 0fe78b4f63..86f7af7002 100644 --- a/tmail-backend/jmap/extensions-cassandra/pom.xml +++ b/tmail-backend/jmap/extensions-cassandra/pom.xml @@ -12,17 +12,6 @@ Twake Mail :: JMAP :: Extensions :: Cassandra - - ${project.groupId} - jmap-extensions - - - ${project.groupId} - jmap-extensions - test-jar - test - - ${james.groupId} apache-james-backends-cassandra @@ -53,6 +42,10 @@ james-server-guice-common test + + ${james.groupId} + james-server-guice-configuration + ${james.groupId} james-server-testing @@ -64,8 +57,13 @@ test - org.testcontainers - testcontainers + ${project.groupId} + jmap-extensions-api + + + ${project.groupId} + jmap-extensions-api + test-jar test @@ -73,6 +71,11 @@ mockito-core test + + org.testcontainers + testcontainers + test + diff --git a/tmail-backend/jmap/extensions-cassandra/src/main/scala/com/linagora/tmail/james/jmap/label/CassandraLabelChangeDAO.scala b/tmail-backend/jmap/extensions-cassandra/src/main/scala/com/linagora/tmail/james/jmap/label/CassandraLabelChangeDAO.scala index c997b6d1b1..54892df980 100644 --- a/tmail-backend/jmap/extensions-cassandra/src/main/scala/com/linagora/tmail/james/jmap/label/CassandraLabelChangeDAO.scala +++ b/tmail-backend/jmap/extensions-cassandra/src/main/scala/com/linagora/tmail/james/jmap/label/CassandraLabelChangeDAO.scala @@ -8,7 +8,7 @@ import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder.{ASC, DE import com.datastax.oss.driver.api.core.{CqlIdentifier, CqlSession} import com.datastax.oss.driver.api.querybuilder.QueryBuilder.{bindMarker, insertInto, selectFrom} import com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition.rows -import com.linagora.tmail.james.jmap.model.LabelId +import com.linagora.tmail.james.jmap.model.{LabelChange, LabelId} import jakarta.inject.Inject import org.apache.james.backends.cassandra.components.CassandraModule import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor diff --git a/tmail-backend/jmap/extensions-cassandra/src/main/scala/com/linagora/tmail/james/jmap/label/CassandraLabelChangeRepository.scala b/tmail-backend/jmap/extensions-cassandra/src/main/scala/com/linagora/tmail/james/jmap/label/CassandraLabelChangeRepository.scala index 6ef7f36e96..24779c5f5d 100644 --- a/tmail-backend/jmap/extensions-cassandra/src/main/scala/com/linagora/tmail/james/jmap/label/CassandraLabelChangeRepository.scala +++ b/tmail-backend/jmap/extensions-cassandra/src/main/scala/com/linagora/tmail/james/jmap/label/CassandraLabelChangeRepository.scala @@ -1,6 +1,7 @@ package com.linagora.tmail.james.jmap.label import com.linagora.tmail.james.jmap.label.LabelChangeRepository.DEFAULT_MAX_IDS_TO_RETURN +import com.linagora.tmail.james.jmap.model.{LabelChange, LabelChanges} import jakarta.inject.Inject import org.apache.james.jmap.api.change.{Limit, State} import org.apache.james.jmap.api.exception.ChangeNotFoundException diff --git a/tmail-backend/jmap/extensions-cassandra/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/CassandraPublicAssetRepository.scala b/tmail-backend/jmap/extensions-cassandra/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/CassandraPublicAssetRepository.scala index 64d3dd7e96..6b52009ab4 100644 --- a/tmail-backend/jmap/extensions-cassandra/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/CassandraPublicAssetRepository.scala +++ b/tmail-backend/jmap/extensions-cassandra/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/CassandraPublicAssetRepository.scala @@ -2,11 +2,10 @@ package com.linagora.tmail.james.jmap.publicAsset import java.io.ByteArrayInputStream import java.net.URI -import java.time.Clock import com.google.inject.multibindings.Multibinder import com.google.inject.{AbstractModule, Scopes} -import com.linagora.tmail.james.jmap.JMAPExtensionConfiguration +import com.linagora.tmail.james.jmap.PublicAssetTotalSizeLimit import jakarta.inject.{Inject, Named} import org.apache.james.backends.cassandra.components.CassandraModule import org.apache.james.blob.api.{BlobId, BlobStore, BucketName} @@ -18,15 +17,15 @@ import reactor.core.scala.publisher.SMono class CassandraPublicAssetRepository @Inject()(val dao: CassandraPublicAssetDAO, val blobStore: BlobStore, - val configuration: JMAPExtensionConfiguration, + val publicAssetTotalSizeLimit: PublicAssetTotalSizeLimit, @Named("publicAssetUriPrefix") publicAssetUriPrefix: URI) extends PublicAssetRepository { private val bucketName: BucketName = blobStore.getDefaultBucketName override def create(username: Username, creationRequest: PublicAssetCreationRequest): Publisher[PublicAssetStorage] = SMono(getTotalSize(username)) - .filter(totalSize => (totalSize + creationRequest.size.value) <= configuration.publicAssetTotalSizeLimit.asLong()) + .filter(totalSize => (totalSize + creationRequest.size.value) <= publicAssetTotalSizeLimit.asLong()) .flatMap(_ => SMono(createAsset(username, creationRequest))) - .switchIfEmpty(SMono.error(PublicAssetQuotaLimitExceededException(configuration.publicAssetTotalSizeLimit.asLong()))) + .switchIfEmpty(SMono.error(PublicAssetQuotaLimitExceededException(publicAssetTotalSizeLimit.asLong()))) private def createAsset(username: Username, creationRequest: PublicAssetCreationRequest): Publisher[PublicAssetStorage] = SMono.fromCallable(() => creationRequest.content.apply().readAllBytes()) diff --git a/tmail-backend/jmap/extensions-cassandra/src/test/java/com/linagora/tmail/james/jmap/label/CassandraLabelChangeRepositoryTest.java b/tmail-backend/jmap/extensions-cassandra/src/test/java/com/linagora/tmail/james/jmap/label/CassandraLabelChangeRepositoryTest.java index b96e3198b3..73cc5fa882 100644 --- a/tmail-backend/jmap/extensions-cassandra/src/test/java/com/linagora/tmail/james/jmap/label/CassandraLabelChangeRepositoryTest.java +++ b/tmail-backend/jmap/extensions-cassandra/src/test/java/com/linagora/tmail/james/jmap/label/CassandraLabelChangeRepositoryTest.java @@ -19,6 +19,8 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import com.linagora.tmail.james.jmap.model.LabelChange; + public class CassandraLabelChangeRepositoryTest implements LabelChangeRepositoryContract { static final CassandraModule MODULE = CassandraLabelChangeTable.MODULE(); diff --git a/tmail-backend/jmap/extensions-cassandra/src/test/java/com/linagora/tmail/james/jmap/publicAsset/CassandraPublicAssetRepositoryTest.java b/tmail-backend/jmap/extensions-cassandra/src/test/java/com/linagora/tmail/james/jmap/publicAsset/CassandraPublicAssetRepositoryTest.java index 3a0541ea0d..07c8e09f19 100644 --- a/tmail-backend/jmap/extensions-cassandra/src/test/java/com/linagora/tmail/james/jmap/publicAsset/CassandraPublicAssetRepositoryTest.java +++ b/tmail-backend/jmap/extensions-cassandra/src/test/java/com/linagora/tmail/james/jmap/publicAsset/CassandraPublicAssetRepositoryTest.java @@ -5,10 +5,11 @@ import org.apache.james.blob.api.BucketName; import org.apache.james.blob.memory.MemoryBlobStoreDAO; import org.apache.james.server.blob.deduplication.DeDuplicationBlobStore; +import org.apache.james.util.Size; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.RegisterExtension; -import com.linagora.tmail.james.jmap.JMAPExtensionConfiguration; +import com.linagora.tmail.james.jmap.PublicAssetTotalSizeLimit; class CassandraPublicAssetRepositoryTest implements PublicAssetRepositoryContract { @RegisterExtension @@ -18,10 +19,11 @@ class CassandraPublicAssetRepositoryTest implements PublicAssetRepositoryContrac @BeforeEach void setup(CassandraCluster cassandra) { + PublicAssetTotalSizeLimit publicAssetTotalSizeLimit = PublicAssetTotalSizeLimit.DEFAULT(); publicAssetRepository = new CassandraPublicAssetRepository( new CassandraPublicAssetDAO(cassandra.getConf(), blobIdFactory()), new DeDuplicationBlobStore(new MemoryBlobStoreDAO(), BucketName.DEFAULT, blobIdFactory()), - new JMAPExtensionConfiguration(JMAPExtensionConfiguration.PUBLIC_ASSET_TOTAL_SIZE_LIMIT_DEFAULT()), + publicAssetTotalSizeLimit, PublicAssetRepositoryContract.PUBLIC_ASSET_URI_PREFIX()); } diff --git a/tmail-backend/jmap/extensions-cassandra/src/test/java/com/linagora/tmail/james/jmap/publicAsset/CassandraPublicAssetServiceTest.java b/tmail-backend/jmap/extensions-cassandra/src/test/java/com/linagora/tmail/james/jmap/publicAsset/CassandraPublicAssetServiceTest.java index 327ad23b05..66d7c3aa72 100644 --- a/tmail-backend/jmap/extensions-cassandra/src/test/java/com/linagora/tmail/james/jmap/publicAsset/CassandraPublicAssetServiceTest.java +++ b/tmail-backend/jmap/extensions-cassandra/src/test/java/com/linagora/tmail/james/jmap/publicAsset/CassandraPublicAssetServiceTest.java @@ -5,10 +5,11 @@ import org.apache.james.blob.api.BucketName; import org.apache.james.blob.memory.MemoryBlobStoreDAO; import org.apache.james.server.blob.deduplication.DeDuplicationBlobStore; +import org.apache.james.util.Size; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.RegisterExtension; -import com.linagora.tmail.james.jmap.JMAPExtensionConfiguration; +import com.linagora.tmail.james.jmap.PublicAssetTotalSizeLimit; class CassandraPublicAssetServiceTest implements PublicAssetServiceContract { @@ -21,14 +22,14 @@ class CassandraPublicAssetServiceTest implements PublicAssetServiceContract { @BeforeEach void setup(CassandraCluster cassandra) { - JMAPExtensionConfiguration jmapExtensionConfiguration = new JMAPExtensionConfiguration(JMAPExtensionConfiguration.PUBLIC_ASSET_TOTAL_SIZE_LIMIT_DEFAULT()); + PublicAssetTotalSizeLimit publicAssetTotalSizeLimit = PublicAssetTotalSizeLimit.DEFAULT(); publicAssetRepository = new CassandraPublicAssetRepository( new CassandraPublicAssetDAO(cassandra.getConf(), blobIdFactory()), new DeDuplicationBlobStore(new MemoryBlobStoreDAO(), BucketName.DEFAULT, blobIdFactory()), - jmapExtensionConfiguration, + publicAssetTotalSizeLimit, PublicAssetRepositoryContract.PUBLIC_ASSET_URI_PREFIX()); - publicAssetSetService = new PublicAssetSetService(PublicAssetServiceContract.identityRepository(), publicAssetRepository, jmapExtensionConfiguration); + publicAssetSetService = new PublicAssetSetService(PublicAssetServiceContract.identityRepository(), publicAssetRepository, publicAssetTotalSizeLimit); } @Override diff --git a/tmail-backend/jmap/extensions-opensearch/pom.xml b/tmail-backend/jmap/extensions-opensearch/pom.xml index 33fcc19859..7caa403f32 100644 --- a/tmail-backend/jmap/extensions-opensearch/pom.xml +++ b/tmail-backend/jmap/extensions-opensearch/pom.xml @@ -12,16 +12,6 @@ Twake Mail :: JMAP :: Extensions :: Opensearch - - ${project.groupId} - jmap-extensions - - - ${project.groupId} - jmap-extensions - test - test-jar - ${james.groupId} apache-james-backends-opensearch @@ -42,6 +32,16 @@ testing-base test + + ${project.groupId} + jmap-extensions-api + + + ${project.groupId} + jmap-extensions-api + test + test-jar + com.google.guava guava diff --git a/tmail-backend/jmap/extensions-rabbitmq/pom.xml b/tmail-backend/jmap/extensions-rabbitmq/pom.xml index 4afc3a66dc..b455e4e683 100644 --- a/tmail-backend/jmap/extensions-rabbitmq/pom.xml +++ b/tmail-backend/jmap/extensions-rabbitmq/pom.xml @@ -12,16 +12,6 @@ Twake Mail :: JMAP :: Extensions :: RabbitMQ - - ${project.groupId} - jmap-extensions - - - ${project.groupId} - jmap-extensions - test - test-jar - ${james.groupId} apache-james-backends-rabbitmq @@ -47,11 +37,41 @@ testing-base test + + ${project.groupId} + jmap-extensions-api + + + ${project.groupId} + jmap-extensions-api + test + test-jar + + + com.typesafe.play + play-json_${scala.base} + 2.10.5 + org.mockito mockito-core test + + + + net.alchim31.maven + scala-maven-plugin + + + io.github.evis + scalafix-maven-plugin_2.13 + + ${project.parent.parent.basedir}/.scalafix.conf + + + + \ No newline at end of file diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/EmailAddressContactMessageSerializer.scala b/tmail-backend/jmap/extensions-rabbitmq/src/main/scala/com/linagora/tmail/james/jmap/json/EmailAddressContactMessageSerializer.scala similarity index 82% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/EmailAddressContactMessageSerializer.scala rename to tmail-backend/jmap/extensions-rabbitmq/src/main/scala/com/linagora/tmail/james/jmap/json/EmailAddressContactMessageSerializer.scala index faaa2f09c9..32322b5042 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/EmailAddressContactMessageSerializer.scala +++ b/tmail-backend/jmap/extensions-rabbitmq/src/main/scala/com/linagora/tmail/james/jmap/json/EmailAddressContactMessageSerializer.scala @@ -1,12 +1,20 @@ package com.linagora.tmail.james.jmap.json import com.linagora.tmail.james.jmap.contact.{ContactOwner, EmailAddressContactMessage, MessageEntry, TmailContactMessageScope, TmailContactMessageType} +import org.apache.james.core.MailAddress import play.api.libs.functional.syntax.toFunctionalBuilderOps -import play.api.libs.json.{JsError, JsPath, JsResult, JsString, JsSuccess, JsValue, Json, Reads} +import play.api.libs.json.{JsError, JsPath, JsResult, JsString, JsSuccess, JsValue, Json, Reads, Writes} import scala.util.{Failure, Success, Try} object EmailAddressContactMessageSerializer { + implicit val mailAddressReads: Reads[MailAddress] = { + case JsString(value) => Try(JsSuccess(new MailAddress(value))) + .fold(e => JsError(s"Invalid mailAddress: ${e.getMessage}"), mailAddress => mailAddress) + case _ => JsError("Expecting mailAddress to be represented by a JsString") + } + + implicit val mailAddressWrites: Writes[MailAddress] = mail => JsString(mail.toString) private implicit val contactMessageTypeReads: Reads[TmailContactMessageType] = { case JsString(value) => TmailContactMessageType.from(value) diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/json/EmailAddressContactMessageSerializerTest.scala b/tmail-backend/jmap/extensions-rabbitmq/src/test/scala/com/linagora/tmail/james/jmap/json/EmailAddressContactMessageSerializerTest.scala similarity index 100% rename from tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/json/EmailAddressContactMessageSerializerTest.scala rename to tmail-backend/jmap/extensions-rabbitmq/src/test/scala/com/linagora/tmail/james/jmap/json/EmailAddressContactMessageSerializerTest.scala diff --git a/tmail-backend/jmap/extensions/pom.xml b/tmail-backend/jmap/extensions/pom.xml index cedc4de34f..3babafc141 100644 --- a/tmail-backend/jmap/extensions/pom.xml +++ b/tmail-backend/jmap/extensions/pom.xml @@ -16,14 +16,6 @@ - - ${project.groupId} - mailbox-encrypted-api - - - ${project.groupId} - team-mailboxes - ${james.groupId} apache-james-mailbox-deleted-messages-vault @@ -38,10 +30,6 @@ blob-storage-strategy test - - ${james.groupId} - james-server-webadmin-mailbox-deleted-message-vault - ${james.groupId} event-bus-api @@ -72,6 +60,10 @@ james-server-testing test + + ${james.groupId} + james-server-webadmin-mailbox-deleted-message-vault + ${james.groupId} metrics-tests @@ -83,9 +75,34 @@ test - com.github.f4b6a3 - uuid-creator - ${uuid-creator.version} + ${project.groupId} + jmap-extensions-api + + + ${project.groupId} + jmap-extensions-api + test-jar + test + + + ${project.groupId} + mailbox-encrypted-api + + + ${project.groupId} + team-mailboxes + + + org.bouncycastle + bcpkix-jdk18on + + + org.bouncycastle + bcprov-jdk18on + + + com.github.spullara.mustache.java + compiler com.google.firebase @@ -100,8 +117,13 @@ guice - com.github.spullara.mustache.java - compiler + org.mnode.ical4j + ical4j + 3.2.9 + + + com.ibm.icu + icu4j io.jsonwebtoken @@ -117,28 +139,16 @@ jjwt-jackson runtime - - org.bouncycastle - bcpkix-jdk18on - - - org.bouncycastle - bcprov-jdk18on - - - org.mnode.ical4j - ical4j - 3.2.9 - - - com.ibm.icu - icu4j - org.mockito mockito-core test + + com.github.f4b6a3 + uuid-creator + ${uuid-creator.version} + diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/JMAPExtensionConfiguration.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/JMAPExtensionConfiguration.scala index f301c0d27b..76fc9e2c5c 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/JMAPExtensionConfiguration.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/JMAPExtensionConfiguration.scala @@ -9,10 +9,8 @@ import com.google.common.base.Preconditions import com.linagora.tmail.james.jmap.JMAPExtensionConfiguration.{CALENDAR_EVENT_REPLY_SUPPORTED_LANGUAGES_DEFAULT, PUBLIC_ASSET_TOTAL_SIZE_LIMIT_DEFAULT, TICKET_IP_VALIDATION_ENABLED} import com.linagora.tmail.james.jmap.method.CalendarEventReplySupportedLanguage.LANGUAGE_DEFAULT import com.linagora.tmail.james.jmap.model.LanguageLocation -import eu.timepit.refined import org.apache.commons.configuration2.Configuration import org.apache.james.core.MailAddress -import org.apache.james.jmap.core.UnsignedInt.{UnsignedInt, UnsignedIntConstraint} import org.apache.james.server.core.MissingArgumentException import org.apache.james.util.{DurationParser, Size} @@ -20,7 +18,7 @@ import scala.util.{Failure, Success, Try} object JMAPExtensionConfiguration { val PUBLIC_ASSET_TOTAL_SIZE_LIMIT_PROPERTY: String = "public.asset.total.size" - val PUBLIC_ASSET_TOTAL_SIZE_LIMIT_DEFAULT: PublicAssetTotalSizeLimit = PublicAssetTotalSizeLimit.of(Size.of(20L, Size.Unit.M)).get + val PUBLIC_ASSET_TOTAL_SIZE_LIMIT_DEFAULT: PublicAssetTotalSizeLimit = PublicAssetTotalSizeLimit.DEFAULT val TICKET_IP_VALIDATION_PROPERTY: String = "authentication.strategy.rfc8621.tickets.ip.validation.enabled" val TICKET_IP_VALIDATION_ENABLED: TicketIpValidationEnable = TicketIpValidationEnable(true) val CALENDAR_EVENT_REPLY_SUPPORTED_LANGUAGES_PROPERTY: String = "calendarEvent.reply.supportedLanguages" @@ -72,13 +70,6 @@ object JMAPExtensionConfiguration { } } -object PublicAssetTotalSizeLimit { - def of(size: Size): Try[PublicAssetTotalSizeLimit] = refined.refineV[UnsignedIntConstraint](size.asBytes()) match { - case Right(value) => Success(PublicAssetTotalSizeLimit(value)) - case Left(error) => Failure(new NumberFormatException(error)) - } -} - case class JMAPExtensionConfiguration(publicAssetTotalSizeLimit: PublicAssetTotalSizeLimit = PUBLIC_ASSET_TOTAL_SIZE_LIMIT_DEFAULT, supportMailAddress: Option[MailAddress] = None, supportHttpLink: Option[URL] = None, @@ -91,10 +82,6 @@ case class JMAPExtensionConfiguration(publicAssetTotalSizeLimit: PublicAssetTota } } -case class PublicAssetTotalSizeLimit(value: UnsignedInt) { - def asLong(): Long = value.value -} - case class TicketIpValidationEnable(value: Boolean) case class CalendarEventReplySupportedLanguagesConfig(supportedLanguages: Set[Locale]) diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/TMailJMAPModule.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/TMailJMAPModule.scala index 6d69eb7c62..a393b1fe65 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/TMailJMAPModule.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/TMailJMAPModule.scala @@ -24,4 +24,7 @@ class TMailJMAPModule extends AbstractModule { @Provides def provideJMAPExtensionConfiguration(@Named("jmap") configuration: Configuration): JMAPExtensionConfiguration = JMAPExtensionConfiguration.from(configuration) + + @Provides + def providePublicAssetTotalSizeLimit(jmapExtensionConfiguration: JMAPExtensionConfiguration): PublicAssetTotalSizeLimit = jmapExtensionConfiguration.publicAssetTotalSizeLimit } diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/FirebaseSubscriptionSerializer.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/FirebaseSubscriptionSerializer.scala index 385dd23765..6e0b58b479 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/FirebaseSubscriptionSerializer.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/FirebaseSubscriptionSerializer.scala @@ -1,10 +1,14 @@ package com.linagora.tmail.james.jmap.json import com.linagora.tmail.james.jmap.method.{ApiKey, AppId, AuthDomain, DatabaseUrl, FirebaseCapabilityProperties, MessagingSenderId, ProjectId, StorageBucket, VapidPublicKey} -import com.linagora.tmail.james.jmap.model.{DeviceClientId, FirebaseSubscription, FirebaseSubscriptionCreationId, FirebaseSubscriptionCreationRequest, FirebaseSubscriptionCreationResponse, FirebaseSubscriptionExpiredTime, FirebaseSubscriptionGetRequest, FirebaseSubscriptionGetResponse, FirebaseSubscriptionId, FirebaseSubscriptionIds, FirebaseSubscriptionPatchObject, FirebaseSubscriptionSetRequest, FirebaseSubscriptionSetResponse, FirebaseSubscriptionUpdateResponse, FirebaseToken, UnparsedFirebaseSubscriptionId} +import com.linagora.tmail.james.jmap.model.{DeviceClientId, FirebaseSubscription, FirebaseSubscriptionCreation, FirebaseSubscriptionCreationId, FirebaseSubscriptionCreationParseException, FirebaseSubscriptionCreationRequest, FirebaseSubscriptionCreationResponse, FirebaseSubscriptionExpiredTime, FirebaseSubscriptionGetRequest, FirebaseSubscriptionGetResponse, FirebaseSubscriptionId, FirebaseSubscriptionIds, FirebaseSubscriptionPatchObject, FirebaseSubscriptionSetRequest, FirebaseSubscriptionSetResponse, FirebaseSubscriptionUpdateResponse, FirebaseToken, UnparsedFirebaseSubscriptionId} +import eu.timepit.refined.collection.NonEmpty +import eu.timepit.refined.refineV +import eu.timepit.refined.types.string.NonEmptyString import jakarta.inject.Inject import org.apache.james.jmap.api.change.TypeStateFactory import org.apache.james.jmap.api.model.TypeName +import org.apache.james.jmap.core.SetError.SetErrorDescription import org.apache.james.jmap.core.{Properties, SetError, UTCDate} import org.apache.james.jmap.json.mapWrites import play.api.libs.json._ @@ -85,7 +89,37 @@ class FirebaseSubscriptionSerializer @Inject()(typeStateFactory: TypeStateFactor def deserializeFirebaseSubscriptionSetRequest(input: JsValue): JsResult[FirebaseSubscriptionSetRequest] = Json.fromJson[FirebaseSubscriptionSetRequest](input) - def deserializeFirebaseSubscriptionCreationRequest(input: JsValue): JsResult[FirebaseSubscriptionCreationRequest] = Json.fromJson[FirebaseSubscriptionCreationRequest](input) + private val subscriptionCreationRequestStandardReads: Reads[FirebaseSubscriptionCreationRequest] = Json.reads[FirebaseSubscriptionCreationRequest] + + implicit val subscriptionCreationRequestReads: Reads[FirebaseSubscriptionCreationRequest] = new Reads[FirebaseSubscriptionCreationRequest] { + override def reads(json: JsValue): JsResult[FirebaseSubscriptionCreationRequest] = + subscriptionCreationRequestStandardReads.reads(json) + .flatMap(request => { + validateProperties(json.as[JsObject]) + .fold(_ => JsError("Failed to validate properties"), _ => JsSuccess(request)) + }) + + def validateProperties(jsObject: JsObject): Either[FirebaseSubscriptionCreationParseException, JsObject] = + (jsObject.keys.intersect(FirebaseSubscriptionCreation.serverSetProperty), jsObject.keys.diff(FirebaseSubscriptionCreation.knownProperties)) match { + case (_, unknownProperties) if unknownProperties.nonEmpty => + Left(FirebaseSubscriptionCreationParseException(SetError.invalidArguments( + SetErrorDescription("Some unknown properties were specified"), + Some(toProperties(unknownProperties.toSet))))) + case (specifiedServerSetProperties, _) if specifiedServerSetProperties.nonEmpty => + Left(FirebaseSubscriptionCreationParseException(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(_)) + })) + } + + def deserializeFirebaseSubscriptionCreationRequest(input: JsValue): JsResult[FirebaseSubscriptionCreationRequest] = Json.fromJson[FirebaseSubscriptionCreationRequest](input)(subscriptionCreationRequestReads) def serialize(response: FirebaseSubscriptionGetResponse, properties: Properties): JsValue = Json.toJson(response) diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/LabelSerializer.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/LabelSerializer.scala index 9b4876a6c1..8c6406dc8a 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/LabelSerializer.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/LabelSerializer.scala @@ -1,10 +1,15 @@ package com.linagora.tmail.james.jmap.json -import com.linagora.tmail.james.jmap.model.{Color, DisplayName, Label, LabelCreationId, LabelCreationRequest, LabelCreationResponse, LabelGetRequest, LabelGetResponse, LabelId, LabelIds, LabelPatchObject, LabelSetRequest, LabelSetResponse, LabelUpdateResponse, UnparsedLabelId} +import com.linagora.tmail.james.jmap.method.JSON_CUSTOM_VALIDATION_ERROR +import com.linagora.tmail.james.jmap.model.{Color, DisplayName, Label, LabelCreationId, LabelCreationParseException, LabelCreationRequest, LabelCreationResponse, LabelGetRequest, LabelGetResponse, LabelId, LabelIds, LabelPatchObject, LabelSetRequest, LabelSetResponse, LabelUpdateResponse, UnparsedLabelId} +import eu.timepit.refined.collection.NonEmpty +import eu.timepit.refined.refineV +import eu.timepit.refined.types.string.NonEmptyString +import org.apache.james.jmap.core.SetError.SetErrorDescription import org.apache.james.jmap.core.{Properties, SetError, UuidState} import org.apache.james.jmap.json.mapWrites import org.apache.james.jmap.mail.Keyword -import play.api.libs.json.{Format, JsArray, JsError, JsObject, JsResult, JsString, JsSuccess, JsValue, Json, Reads, Writes, __} +import play.api.libs.json.{Format, JsArray, JsError, JsObject, JsResult, JsString, JsSuccess, JsValue, Json, JsonValidationError, Reads, Writes, __} object LabelSerializer { private implicit val labelIdFormat: Format[LabelId] = Json.valueFormat[LabelId] @@ -48,7 +53,39 @@ object LabelSerializer { private implicit val labelMapUpdateRequestReads: Reads[Map[UnparsedLabelId, LabelPatchObject]] = Reads.mapReads[UnparsedLabelId, LabelPatchObject] { string => unparsedLabelIdReads.reads(JsString(string)) } - implicit val labelCreationRequest: Reads[LabelCreationRequest] = Json.reads[LabelCreationRequest] + implicit val labelCreationRequestStandardWrites: Writes[LabelCreationRequest] = Json.writes[LabelCreationRequest] + + private val labelCreationRequestStandardReads: Reads[LabelCreationRequest] = Json.reads[LabelCreationRequest] + + implicit val labelCreationRequestReads: Reads[LabelCreationRequest] = new Reads[LabelCreationRequest] { + override def reads(json: JsValue): JsResult[LabelCreationRequest] = { + validateProperties(json.as[JsObject]) + .fold(exception => JsError(JsonValidationError(JSON_CUSTOM_VALIDATION_ERROR, exception.setError)), + _ => labelCreationRequestStandardReads.reads(json)) + } + + def validateProperties(jsObject: JsObject): Either[LabelCreationParseException, JsObject] = + (jsObject.keys.intersect(LabelCreationRequest.serverSetProperty), + jsObject.keys.diff(LabelCreationRequest.knownProperties)) match { + case (_, unknownProperties) if unknownProperties.nonEmpty => + Left(LabelCreationParseException(SetError.invalidArguments( + SetErrorDescription("Some unknown properties were specified"), + Some(toProperties(unknownProperties.toSet))))) + case (specifiedServerSetProperties, _) if specifiedServerSetProperties.nonEmpty => + Left(LabelCreationParseException(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(_)) + })) + } + + def deserializeLabelCreationRequest(input: JsValue): JsResult[LabelCreationRequest] = Json.fromJson[LabelCreationRequest](input) private implicit val labelMapUpdateResponseWrites: Writes[Map[LabelId, LabelUpdateResponse]] = mapWrites[LabelId, LabelUpdateResponse](_.id.value, labelUpdateResponseWrites) diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/PublicAssetSerializer.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/PublicAssetSerializer.scala index c621359cae..e24c9d94b3 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/PublicAssetSerializer.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/json/PublicAssetSerializer.scala @@ -1,11 +1,12 @@ package com.linagora.tmail.james.jmap.json -import com.linagora.tmail.james.jmap.method.standardErrorMessage +import com.linagora.tmail.james.jmap.method.{JSON_CUSTOM_VALIDATION_ERROR, standardErrorMessage} import com.linagora.tmail.james.jmap.model.{PublicAssetDTO, PublicAssetGetRequest, PublicAssetGetResponse} import com.linagora.tmail.james.jmap.publicAsset.ImageContentType.ImageContentType -import com.linagora.tmail.james.jmap.publicAsset.{PublicAssetCreationId, PublicAssetCreationResponse, PublicAssetId, PublicAssetPatchObject, PublicAssetSetCreationRequest, PublicAssetSetRequest, PublicAssetSetResponse, PublicAssetUpdateResponse, PublicURI, UnparsedPublicAssetId, ValidatedPublicAssetPatchObject} +import com.linagora.tmail.james.jmap.publicAsset.{PublicAssetCreationId, PublicAssetCreationParseException, PublicAssetCreationResponse, PublicAssetId, PublicAssetPatchObject, PublicAssetSetCreationRequest, PublicAssetSetRequest, PublicAssetSetResponse, PublicAssetUpdateResponse, PublicURI, UnparsedPublicAssetId, ValidatedPublicAssetPatchObject} import org.apache.james.jmap.api.model.IdentityId -import org.apache.james.jmap.core.{SetError, UuidState} +import org.apache.james.jmap.core.SetError.SetErrorDescription +import org.apache.james.jmap.core.{Properties, SetError, UuidState} import org.apache.james.jmap.json.{mapMarkerReads, mapWrites} import org.apache.james.jmap.mail.{IdentityIds, UnparsedIdentityId, BlobId => JmapBlobId} import play.api.libs.json.{JsBoolean, JsError, JsNull, JsObject, JsPath, JsResult, JsString, JsSuccess, JsValue, Json, JsonValidationError, Reads, Writes} @@ -91,7 +92,23 @@ object PublicAssetSerializer { private implicit val blobIdReads: Reads[JmapBlobId] = Json.valueReads[JmapBlobId] private implicit val unparsedIdentityIdReads: Reads[UnparsedIdentityId] = Json.valueReads[UnparsedIdentityId] private implicit val identityIdsReads: Reads[IdentityIds] = Json.valueReads[IdentityIds] - private implicit val publicAssetCreationRequestReads: Reads[PublicAssetSetCreationRequest] = Json.reads[PublicAssetSetCreationRequest] + + private implicit val publicAssetCreationRequestStandardReads: Reads[PublicAssetSetCreationRequest] = Json.reads[PublicAssetSetCreationRequest] + private implicit val publicAssetCreationRequestReads: Reads[PublicAssetSetCreationRequest] = new Reads[PublicAssetSetCreationRequest] { + override def reads(json: JsValue): JsResult[PublicAssetSetCreationRequest] = + validateProperties(json.as[JsObject]) + .fold(exception => JsError(JsonValidationError(JSON_CUSTOM_VALIDATION_ERROR, exception.setError)), + _ => publicAssetCreationRequestStandardReads.reads(json)) + + def validateProperties(jsObject: JsObject): Either[PublicAssetCreationParseException, JsObject] = { + jsObject.fields.find(mapEntry => !PublicAssetSetCreationRequest.knownProperties.contains(mapEntry._1)) + .map(e => Left(PublicAssetCreationParseException(SetError.invalidArguments( + SetErrorDescription("Some unknown properties were specified"), + Some(Properties.toProperties(Set(e._1))))))) + .getOrElse(Right(jsObject)) + } + } + private implicit val publicAssetCreationIdReads: Reads[PublicAssetCreationId] = Json.reads[PublicAssetCreationId] private implicit val unparsedPublicAssetIdReads: Reads[UnparsedPublicAssetId] = Json.valueReads[UnparsedPublicAssetId] diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/LabelChangesPopulate.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/LabelChangesPopulate.scala index 6e897838c8..b2ed5ce709 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/LabelChangesPopulate.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/LabelChangesPopulate.scala @@ -4,7 +4,7 @@ import com.google.inject.Inject import com.linagora.tmail.james.jmap.method.LabelSetCreatePerformer.LabelCreationResults import com.linagora.tmail.james.jmap.method.LabelSetDeletePerformer.LabelDeletionResults import com.linagora.tmail.james.jmap.method.LabelUpdateResults -import com.linagora.tmail.james.jmap.model.LabelId +import com.linagora.tmail.james.jmap.model.{LabelChange, LabelId} import jakarta.inject.Named import org.apache.james.core.Username import org.apache.james.events.Event.EventId diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/LabelChangeRepository.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/MemoryLabelChangeRepository.scala similarity index 50% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/LabelChangeRepository.scala rename to tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/MemoryLabelChangeRepository.scala index 5efec10892..2fad813a4d 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/LabelChangeRepository.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/MemoryLabelChangeRepository.scala @@ -1,13 +1,12 @@ package com.linagora.tmail.james.jmap.label import java.time.{Clock, Instant} -import java.util.function.Supplier import com.google.common.collect.{HashBasedTable, Table, Tables} import com.linagora.tmail.james.jmap.label.LabelChangeRepository.DEFAULT_MAX_IDS_TO_RETURN -import com.linagora.tmail.james.jmap.model.LabelId +import com.linagora.tmail.james.jmap.model.{LabelChange, LabelChanges} import jakarta.inject.Inject -import org.apache.james.jmap.api.change.{JmapChange, Limit, State} +import org.apache.james.jmap.api.change.{Limit, State} import org.apache.james.jmap.api.exception.ChangeNotFoundException import org.apache.james.jmap.api.model import org.apache.james.jmap.api.model.{AccountId, TypeName} @@ -15,35 +14,6 @@ import org.apache.james.jmap.core.UuidState import org.reactivestreams.Publisher import reactor.core.scala.publisher.{SFlux, SMono} -object LabelChangeRepository { - val DEFAULT_MAX_IDS_TO_RETURN : Limit = Limit.of(256) -} - -trait LabelChangeRepository { - def save(labelChange: LabelChange): Publisher[Void] - - def getSinceState(accountId: AccountId, state: State, maxIdsToReturn: Option[Limit] = Some(DEFAULT_MAX_IDS_TO_RETURN)): Publisher[LabelChanges] - - def getLatestState(accountId: AccountId): Publisher[State] -} - -case class LabelChange(accountId: AccountId, - created: Set[LabelId] = Set(), - updated: Set[LabelId] = Set(), - destroyed: Set[LabelId] = Set(), - state: State) extends JmapChange { - override def getAccountId: AccountId = accountId - - override def isNoop: Boolean = created.isEmpty && updated.isEmpty && destroyed.isEmpty - - override def forSharee(accountId: AccountId, state: Supplier[State]): JmapChange = - LabelChange(accountId = accountId, - created = created, - updated = updated, - destroyed = destroyed, - state = state.get()) -} - case object LabelTypeName extends TypeName { override val asString: String = "Label" @@ -55,47 +25,6 @@ case object LabelTypeName extends TypeName { override def parseState(string: String): Either[IllegalArgumentException, model.State] = UuidState.parse(string) } -object LabelChanges { - def from(labelChange: LabelChange): LabelChanges = LabelChanges( - created = labelChange.created, - updated = labelChange.updated, - destroyed = labelChange.destroyed, - newState = labelChange.state) - - def initial(): LabelChanges = LabelChanges( - created = Set(), - updated = Set(), - destroyed = Set(), - newState = State.INITIAL) - - def merge(limit: Int, change1: LabelChanges, change2: LabelChanges): LabelChanges = - change1.canAppendMoreItem match { - case false => change1 - case true if change1.newState.equals(State.INITIAL) => change2 - case true => - val createdTemp: Set[LabelId] = (change1.created ++ change2.created).diff(change2.destroyed) - val updatedTemp: Set[LabelId] = (change1.updated ++ change2.updated.diff(createdTemp)).diff(change2.destroyed) - val destroyedTemp: Set[LabelId] = change1.destroyed ++ change2.destroyed.diff(change1.created) - if (createdTemp.size + updatedTemp.size + destroyedTemp.size > limit) { - change1.copy(hasMoreChanges = true, canAppendMoreItem = false) - } else { - change1.copy(created = createdTemp, - updated = updatedTemp, - destroyed = destroyedTemp, - newState = change2.newState) - } - } -} - -case class LabelChanges(created: Set[LabelId] = Set(), - updated: Set[LabelId] = Set(), - destroyed: Set[LabelId] = Set(), - hasMoreChanges: Boolean = false, - newState: State, - private val canAppendMoreItem: Boolean = true) { - def getAllChanges: Set[LabelId] = created ++ updated ++ destroyed -} - case class MemoryLabelChangeRepository @Inject()(clock: Clock) extends LabelChangeRepository { import scala.jdk.CollectionConverters._ diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/LabelRepository.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/MemoryLabelRepository.scala similarity index 82% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/LabelRepository.scala rename to tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/MemoryLabelRepository.scala index 911d1490bc..edc2566e27 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/LabelRepository.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/label/MemoryLabelRepository.scala @@ -14,24 +14,6 @@ import reactor.core.scala.publisher.{SFlux, SMono} import scala.jdk.CollectionConverters._ -trait LabelRepository { - def addLabel(username: Username, labelCreationRequest: LabelCreationRequest): Publisher[Label] - - def addLabel(username: Username, label: Label): Publisher[Void] - - def addLabels(username: Username, labelCreationRequests: util.Collection[LabelCreationRequest]): Publisher[Label] - - def updateLabel(username: Username, labelId: LabelId, newDisplayName: Option[DisplayName] = None, newColor: Option[Color] = None): Publisher[Void] - - def getLabels(username: Username, ids: util.Collection[LabelId]): Publisher[Label] - - def listLabels(username: Username): Publisher[Label] - - def deleteLabel(username: Username, labelId: LabelId): Publisher[Void] - - def deleteAllLabels(username: Username): Publisher[Void] -} - class MemoryLabelRepository extends LabelRepository { private val labelsTable: Table[Username, Keyword, Label] = Tables.synchronizedTable(HashBasedTable.create()) diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/FirebaseSubscriptionSetCreatePerformer.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/FirebaseSubscriptionSetCreatePerformer.scala index 9b67e402a9..5f1886a199 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/FirebaseSubscriptionSetCreatePerformer.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/FirebaseSubscriptionSetCreatePerformer.scala @@ -68,8 +68,7 @@ class FirebaseSubscriptionSetCreatePerformer @Inject()(val repository: FirebaseS private def parseCreate(json: JsObject): Either[Exception, FirebaseSubscriptionCreationRequest] = for { - validJsObject <- FirebaseSubscriptionCreation.validateProperties(json) - parsedRequest <- serializer.deserializeFirebaseSubscriptionCreationRequest(validJsObject).asEither + parsedRequest <- serializer.deserializeFirebaseSubscriptionCreationRequest(json).asEither .left.map(errors => FirebaseSubscriptionCreationParseException.from(errors)) validatedRequest <- parsedRequest.validate .left.map(e => FirebaseSubscriptionCreationParseException(SetError.invalidArguments(SetErrorDescription(e.getMessage)))) diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/LabelChangesMethod.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/LabelChangesMethod.scala index ecaf45d1d6..5ec194bd98 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/LabelChangesMethod.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/LabelChangesMethod.scala @@ -2,9 +2,9 @@ package com.linagora.tmail.james.jmap.method import com.google.inject.Inject import com.linagora.tmail.james.jmap.json.{LabelChangesSerializer => Serializer} -import com.linagora.tmail.james.jmap.label.{LabelChangeRepository, LabelChanges} +import com.linagora.tmail.james.jmap.label.LabelChangeRepository import com.linagora.tmail.james.jmap.method.CapabilityIdentifier.LINAGORA_LABEL -import com.linagora.tmail.james.jmap.model.{LabelChangesRequest => Request, LabelChangesResponse => Response} +import com.linagora.tmail.james.jmap.model.{LabelChanges, LabelChangesRequest => Request, LabelChangesResponse => Response} import eu.timepit.refined.auto._ import org.apache.james.jmap.api.change.{State => JavaState} import org.apache.james.jmap.api.exception.ChangeNotFoundException diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/LabelSetCreatePerformer.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/LabelSetCreatePerformer.scala index 23e46709b1..9c92ee18c8 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/LabelSetCreatePerformer.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/LabelSetCreatePerformer.scala @@ -3,17 +3,15 @@ package com.linagora.tmail.james.jmap.method import com.linagora.tmail.james.jmap.json.LabelSerializer import com.linagora.tmail.james.jmap.label.LabelRepository import com.linagora.tmail.james.jmap.method.LabelSetCreatePerformer.{LabelCreationFailure, LabelCreationResult, LabelCreationResults, LabelCreationSuccess} -import com.linagora.tmail.james.jmap.model.{LabelCreationId, LabelCreationRequest, LabelCreationResponse, LabelSetRequest} +import com.linagora.tmail.james.jmap.model.{LabelCreationId, LabelCreationParseException, LabelCreationRequest, LabelCreationResponse, LabelSetRequest} import jakarta.inject.Inject import org.apache.james.jmap.core.SetError import org.apache.james.jmap.core.SetError.SetErrorDescription import org.apache.james.mailbox.MailboxSession import org.apache.james.metrics.api.MetricFactory -import play.api.libs.json.{JsError, JsObject, JsSuccess, Json} +import play.api.libs.json.{JsError, JsObject, JsPath, JsSuccess, Json, JsonValidationError} import reactor.core.scala.publisher.{SFlux, SMono} -case class LabelCreationParseException(setError: SetError) extends Exception - object LabelSetCreatePerformer { sealed trait LabelCreationResult { def labelCreationId: LabelCreationId @@ -58,11 +56,10 @@ class LabelSetCreatePerformer @Inject()(val labelRepository: LabelRepository, .map(LabelCreationResults) private def parseCreate(jsObject: JsObject): Either[LabelCreationParseException, LabelCreationRequest] = - LabelCreationRequest.validateProperties(jsObject) - .flatMap(validJsObject => Json.fromJson(validJsObject)(LabelSerializer.labelCreationRequest) match { + LabelSerializer.deserializeLabelCreationRequest(jsObject) match { case JsSuccess(creationRequest, _) => Right(creationRequest) case JsError(errors) => Left(LabelCreationParseException(standardError(errors))) - }) + } private def createLabel(mailboxSession: MailboxSession, clientId: LabelCreationId, diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/PublicAssetSetMethod.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/PublicAssetSetMethod.scala index aa62578fd7..96ce2884a2 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/PublicAssetSetMethod.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/PublicAssetSetMethod.scala @@ -1,5 +1,6 @@ package com.linagora.tmail.james.jmap.method +import cats.implicits.toTraverseOps import com.google.common.base.Preconditions import com.linagora.tmail.james.jmap.json.PublicAssetSerializer import com.linagora.tmail.james.jmap.method.CapabilityIdentifier.LINAGORA_PUBLIC_ASSETS @@ -9,7 +10,8 @@ import jakarta.inject.Inject import org.apache.james.jmap.api.model.IdentityId import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} -import org.apache.james.jmap.core.{ClientId, Id, Invocation, ServerId, SessionTranslator, UuidState} +import org.apache.james.jmap.core.SetError.SetErrorDescription +import org.apache.james.jmap.core.{ClientId, Id, Invocation, Properties, ServerId, SessionTranslator, SetError, UuidState} import org.apache.james.jmap.method.{InvocationWithContext, MethodRequiringAccountId} import org.apache.james.jmap.routes.{Blob, BlobNotFoundException, BlobResolvers, ProcessingContext, SessionSupplier} import org.apache.james.mailbox.MailboxSession @@ -96,14 +98,26 @@ class PublicAssetSetCreatePerformer @Inject()(val publicAssetRepository: PublicA .flatMap(publicAsset => SMono(publicAssetSetService.create(mailboxSession.getUser, publicAsset))) private def parseCreate(jsObject: JsObject): Either[PublicAssetCreationParseException, PublicAssetSetCreationRequest] = - PublicAssetSetCreationRequest.validateProperties(jsObject) - .flatMap(validJsObject => PublicAssetSerializer.deserializePublicAssetSetCreationRequest(validJsObject) match { - case JsSuccess(creationRequest, _) => Right(creationRequest) - case JsError(errors) => Left(PublicAssetCreationParseException(standardError(errors))) - }) + PublicAssetSerializer.deserializePublicAssetSetCreationRequest(jsObject) match { + case JsSuccess(creationRequest, _) => Right(creationRequest) + case JsError(errors) => Left(PublicAssetCreationParseException(standardError(errors))) + } + + + private def doParseIdentityIds(creationRequest: PublicAssetSetCreationRequest): Either[PublicAssetCreationParseException, List[IdentityId]] = + creationRequest.identityIds match { + case None => Right(List.empty) + case Some(identityIdMap) => identityIdMap.map { + case (identityId: IdentityId, bolVal) => if (bolVal) { + Right(identityId) + } else { + scala.Left(PublicAssetCreationParseException(SetError.invalidArguments(SetErrorDescription(s"identityId '$identityId' must be a true"), Some(Properties.toProperties(Set("identityIds")))))) + } + }.toList.sequence + } private def parseIdentityIds(creationRequest: PublicAssetSetCreationRequest): SMono[Seq[IdentityId]] = - SMono.fromCallable(() => creationRequest.parseIdentityIds) + SMono.fromCallable(() => doParseIdentityIds(creationRequest)) .flatMap(result => result.fold(e => SMono.error(e), ids => SMono.just(ids))) private def generatePublicAssetCreationRequest(creationRequest: PublicAssetSetCreationRequest, diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/package.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/package.scala index 50fb655671..a4db2bb65b 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/package.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/package.scala @@ -8,6 +8,7 @@ import play.api.libs.json.{JsError, JsPath, JsResult, JsonValidationError} import scala.language.implicitConversions package object method { + val JSON_CUSTOM_VALIDATION_ERROR: String = "error.custom.validation" def standardErrorMessage(errors: collection.Seq[(JsPath, collection.Seq[JsonValidationError])]): String = errors.head match { @@ -19,8 +20,15 @@ package object method { case _ => ResponseSerializer.serialize(JsError(errors)).toString() } + private def tryExtractSetErrorMessage(errors: collection.Seq[(JsPath, collection.Seq[JsonValidationError])]): Option[SetError] = + errors.head match { + case (_, Seq(JsonValidationError(Seq(JSON_CUSTOM_VALIDATION_ERROR), setError: SetError))) => Option(setError) + case _ => None + } + def standardError(errors: collection.Seq[(JsPath, collection.Seq[JsonValidationError])]): SetError = - SetError.invalidArguments(SetErrorDescription(standardErrorMessage(errors))) + tryExtractSetErrorMessage(errors) + .getOrElse(SetError.invalidArguments(SetErrorDescription(standardErrorMessage(errors)))) implicit class AsEitherRequest[T](val jsResult: JsResult[T]) { def asEitherRequest: Either[IllegalArgumentException, T] = diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/model/FirebaseSubscription.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/model/FirebaseSubscription.scala index 5258342508..ca42885d21 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/model/FirebaseSubscription.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/model/FirebaseSubscription.scala @@ -1,7 +1,6 @@ package com.linagora.tmail.james.jmap.model import java.time.ZonedDateTime -import java.util.UUID import cats.data.Validated import cats.instances.list._ @@ -11,7 +10,6 @@ import com.linagora.tmail.james.jmap.model.FirebaseSubscriptionPatchObject.updat import com.linagora.tmail.james.jmap.model.FirebaseSubscriptionUpdateFailure.LOGGER import eu.timepit.refined.auto._ import org.apache.james.jmap.api.change.TypeStateFactory -import org.apache.james.jmap.api.model.ExpireTimeInvalidException.TIME_FORMATTER import org.apache.james.jmap.api.model.TypeName import org.apache.james.jmap.core.Id.Id import org.apache.james.jmap.core.Properties.toProperties @@ -23,104 +21,6 @@ import play.api.libs.json.{JsArray, JsObject, JsPath, JsString, JsValue, JsonVal import scala.util.{Failure, Success, Try} -object FirebaseSubscriptionId { - def generate(): FirebaseSubscriptionId = FirebaseSubscriptionId(UUID.randomUUID) - - def liftOrThrow(unparsedId: UnparsedFirebaseSubscriptionId): Either[IllegalArgumentException, FirebaseSubscriptionId] = - liftOrThrow(unparsedId.id.value) - - def liftOrThrow(value: String): Either[IllegalArgumentException, FirebaseSubscriptionId] = - Try(UUID.fromString(value)) - .map(value1 => FirebaseSubscriptionId(value1)) - .toEither - .left.map(e => new IllegalArgumentException("FirebaseSubscriptionId is invalid", e)) -} - -case class FirebaseSubscriptionId(value: UUID) { - def serialize: String = value.toString - - def asUnparsedFirebaseSubscriptionId: UnparsedFirebaseSubscriptionId = - UnparsedFirebaseSubscriptionId(Id.validate(serialize).toOption.get) -} - -case class DeviceClientId(value: String) extends AnyVal - -case class FirebaseToken(value: String) extends AnyVal - -case class FirebaseSubscriptionExpiredTime(value: ZonedDateTime) { - def isAfter(date: ZonedDateTime): Boolean = value.isAfter(date) - - def isBefore(date: ZonedDateTime): Boolean = value.isBefore(date) -} - -case class FirebaseSubscriptionCreationRequest(deviceClientId: DeviceClientId, - token: FirebaseToken, - expires: Option[FirebaseSubscriptionExpiredTime] = None, - types: Seq[TypeName]) { - - def validate: Either[IllegalArgumentException, FirebaseSubscriptionCreationRequest] = - validateTypes - - private def validateTypes: Either[IllegalArgumentException, FirebaseSubscriptionCreationRequest] = - if (types.isEmpty) { - scala.Left(new IllegalArgumentException("types must not be empty")) - } else { - Right(this) - } -} - -object FirebaseSubscription { - val EXPIRES_TIME_MAX_DAY: Int = 7 - val allProperties: Properties = Properties("id", "deviceClientId", "expires", "types") - val idProperty: Properties = Properties("id") - - def from(creationRequest: FirebaseSubscriptionCreationRequest, - expireTime: FirebaseSubscriptionExpiredTime): FirebaseSubscription = - FirebaseSubscription(id = FirebaseSubscriptionId.generate(), - deviceClientId = creationRequest.deviceClientId, - token = creationRequest.token, - expires = expireTime, - types = creationRequest.types) -} - -case class FirebaseSubscription(id: FirebaseSubscriptionId, - deviceClientId: DeviceClientId, - token: FirebaseToken, - expires: FirebaseSubscriptionExpiredTime, - types: Seq[TypeName]) { - def withTypes(types: Seq[TypeName]): FirebaseSubscription = copy(types = types) - - def withExpires(expires: FirebaseSubscriptionExpiredTime): FirebaseSubscription = copy(expires = expires) -} - -case class FirebaseSubscriptionNotFoundException(id: FirebaseSubscriptionId) extends RuntimeException - -case class ExpireTimeInvalidException(expires: ZonedDateTime, message: String) extends IllegalStateException(s"`${expires.format(TIME_FORMATTER)}` $message") - -case class DeviceClientIdInvalidException(deviceClientId: DeviceClientId, message: String) extends IllegalArgumentException(s"`${deviceClientId.value}` $message") - -case class TokenInvalidException(message: String) extends IllegalArgumentException(message) - -case class MissingOrInvalidFirebaseCredentialException(message: String) extends IllegalArgumentException(message) - -case class UnparsedFirebaseSubscriptionId(id: Id) - -case class FirebaseSubscriptionIds(list: List[UnparsedFirebaseSubscriptionId]) - -case class FirebaseSubscriptionGetRequest(ids: Option[FirebaseSubscriptionIds], - properties: Option[Properties]) extends WithoutAccountId { - - def validateProperties: Either[IllegalArgumentException, Properties] = - properties match { - case None => Right(FirebaseSubscription.allProperties) - case Some(value) => - value -- FirebaseSubscription.allProperties match { - case invalidProperties if invalidProperties.isEmpty() => Right(value ++ FirebaseSubscription.idProperty) - case invalidProperties: Properties => Left(new IllegalArgumentException(s"The following properties [${invalidProperties.format()}] do not exist.")) - } - } -} - object FirebaseSubscriptionGetResponse { def from(list: Seq[FirebaseSubscription], requestIds: Option[FirebaseSubscriptionIds]): FirebaseSubscriptionGetResponse = requestIds match { @@ -159,9 +59,9 @@ object FirebaseSubscriptionCreationParseException { case class FirebaseSubscriptionCreationParseException(setError: SetError) extends Exception object FirebaseSubscriptionCreation { - private val serverSetProperty: Set[String] = Set("id") - private val assignableProperties: Set[String] = Set("deviceClientId", "token", "expires", "types") - private val knownProperties: Set[String] = assignableProperties ++ serverSetProperty + val serverSetProperty: Set[String] = Set("id") + val assignableProperties: Set[String] = Set("deviceClientId", "token", "expires", "types") + val knownProperties: Set[String] = assignableProperties ++ serverSetProperty def validateProperties(jsObject: JsObject): Either[FirebaseSubscriptionCreationParseException, JsObject] = (jsObject.keys.intersect(serverSetProperty), jsObject.keys.diff(knownProperties)) match { diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/model/Label.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/model/Label.scala index 1f04d11ccf..81b4f1d354 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/model/Label.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/model/Label.scala @@ -1,112 +1,7 @@ package com.linagora.tmail.james.jmap.model -import java.util.UUID - -import com.linagora.tmail.james.jmap.method.LabelCreationParseException -import eu.timepit.refined -import eu.timepit.refined.auto._ -import eu.timepit.refined.collection.NonEmpty -import eu.timepit.refined.refineV -import eu.timepit.refined.string.MatchesRegex -import eu.timepit.refined.types.string.NonEmptyString -import org.apache.james.jmap.core.Id.Id -import org.apache.james.jmap.core.SetError.SetErrorDescription -import org.apache.james.jmap.core.{AccountId, Id, Properties, SetError, UuidState} -import org.apache.james.jmap.mail.Keyword +import org.apache.james.jmap.core.{AccountId, Properties, UuidState} import org.apache.james.jmap.method.WithAccountId -import play.api.libs.json.JsObject - -object LabelId { - def fromKeyword(keyword: Keyword): LabelId = - LabelId(Id.validate(keyword.flagName).toOption.get) - - def generate(): LabelId = - LabelId(Id.validate(UUID.randomUUID().toString).toOption.get) -} - -case class LabelId(id: Id) { - def toKeyword: Keyword = - Keyword.of(id.value).get - - def asUnparsedLabelId: UnparsedLabelId = - UnparsedLabelId(id) - - def serialize: String = id.value -} - -object KeywordUtil { - def generate(): Keyword = - Keyword.of(UUID.randomUUID().toString).get -} - -case class DisplayName(value: String) - -object Color { - private type ColorRegex = MatchesRegex["^#[a-fA-F0-9]{6}$"] - - def validate(string: String): Either[IllegalArgumentException, Color] = - refined.refineV[ColorRegex](string) match { - case Left(_) => scala.Left(new IllegalArgumentException(s"The string should be a valid hexadecimal color value following this pattern #[a-fA-F0-9]{6}")) - case Right(value) => scala.Right(Color(value)) - } -} - -case class Color(value: String) - -object LabelCreationRequest { - private val serverSetProperty = Set("id", "keyword") - private val assignableProperties = Set("displayName", "color") - private val knownProperties = assignableProperties ++ serverSetProperty - - def validateProperties(jsObject: JsObject): Either[LabelCreationParseException, JsObject] = - (jsObject.keys.intersect(serverSetProperty), jsObject.keys.diff(knownProperties)) match { - case (_, unknownProperties) if unknownProperties.nonEmpty => - Left(LabelCreationParseException(SetError.invalidArguments( - SetErrorDescription("Some unknown properties were specified"), - Some(toProperties(unknownProperties.toSet))))) - case (specifiedServerSetProperties, _) if specifiedServerSetProperties.nonEmpty => - Left(LabelCreationParseException(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 LabelCreationRequest(displayName: DisplayName, color: Option[Color]) { - def toLabel: Label = { - val keyword: Keyword = KeywordUtil.generate() - - Label(id = LabelId.fromKeyword(keyword), - displayName = displayName, - keyword = keyword, - color = color) - } -} - -object Label { - val allProperties: Properties = Properties("id", "displayName", "keyword", "color") - val idProperty: Properties = Properties("id") -} - -case class Label(id: LabelId, displayName: DisplayName, keyword: Keyword, color: Option[Color]) { - def update(newDisplayName: Option[DisplayName], newColor: Option[Color]): Label = - copy(displayName = newDisplayName.getOrElse(displayName), - color = newColor.orElse(color)) -} - -case class LabelNotFoundException(id: LabelId) extends RuntimeException - -case class UnparsedLabelId(id: Id) { - def asLabelId: LabelId = LabelId(id) -} - -case class LabelIds(list: List[UnparsedLabelId]) case class LabelGetRequest(accountId: AccountId, ids: Option[LabelIds], @@ -138,4 +33,4 @@ object LabelGetResponse { case class LabelGetResponse(accountId: AccountId, state: UuidState, list: Seq[Label], - notFound: Seq[UnparsedLabelId] = Seq()) + notFound: Seq[UnparsedLabelId] = Seq()) \ No newline at end of file diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/model/LabelChange.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/model/LabelChange.scala index 065bc9e03d..74e6803c99 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/model/LabelChange.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/model/LabelChange.scala @@ -1,6 +1,5 @@ package com.linagora.tmail.james.jmap.model -import com.linagora.tmail.james.jmap.label.LabelChanges import org.apache.james.jmap.api.change.Limit import org.apache.james.jmap.core.{AccountId, UuidState} import org.apache.james.jmap.method.WithAccountId diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetRepository.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/MemoryPublicAssetRepository.scala similarity index 75% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetRepository.scala rename to tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/MemoryPublicAssetRepository.scala index fa427ad4f8..16af9c68ee 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetRepository.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/MemoryPublicAssetRepository.scala @@ -5,7 +5,7 @@ import java.net.URI import java.time.Clock import com.google.common.collect.{HashBasedTable, ImmutableList, Table, Tables} -import com.linagora.tmail.james.jmap.JMAPExtensionConfiguration +import com.linagora.tmail.james.jmap.PublicAssetTotalSizeLimit import jakarta.inject.{Inject, Named} import org.apache.james.blob.api.{BlobId, BlobStore, BucketName} import org.apache.james.core.Username @@ -16,35 +16,8 @@ import reactor.core.scala.publisher.{SFlux, SMono} import scala.jdk.CollectionConverters._ -trait PublicAssetRepository { - def create(username: Username, creationRequest: PublicAssetCreationRequest): Publisher[PublicAssetStorage] - - def update(username: Username, id: PublicAssetId, identityIds: Set[IdentityId]): Publisher[Void] - - def remove(username: Username, id: PublicAssetId): Publisher[Void] - - def revoke(username: Username): Publisher[Void] - - def get(username: Username, ids: Set[PublicAssetId]): Publisher[PublicAssetStorage] - - def get(username: Username, id: PublicAssetId): Publisher[PublicAssetStorage] = get(username, Set(id)) - - def list(username: Username): Publisher[PublicAssetStorage] - - def listPublicAssetMetaDataOrderByIdAsc(username: Username): Publisher[PublicAssetMetadata] - - def listAllBlobIds(): Publisher[BlobId] - - def updateIdentityIds(username: Username, id: PublicAssetId, identityIdsToAdd: Seq[IdentityId], identityIdsToRemove: Seq[IdentityId]): Publisher[Void] = - SMono(get(username, id)) - .map(publicAsset => (publicAsset.identityIds.toSet ++ identityIdsToAdd.toSet) -- identityIdsToRemove.toSet) - .flatMap(identityIds => SMono(update(username, id, identityIds))) - - def getTotalSize(username: Username): Publisher[Long] -} - class MemoryPublicAssetRepository @Inject()(val blobStore: BlobStore, - val configuration: JMAPExtensionConfiguration, + val publicAssetTotalSizeLimit: PublicAssetTotalSizeLimit, @Named("publicAssetUriPrefix") publicAssetUriPrefix: URI) extends PublicAssetRepository { private val tableStore: Table[Username, PublicAssetId, PublicAssetMetadata] = Tables.synchronizedTable(HashBasedTable.create()) @@ -52,9 +25,9 @@ class MemoryPublicAssetRepository @Inject()(val blobStore: BlobStore, override def create(username: Username, creationRequest: PublicAssetCreationRequest): Publisher[PublicAssetStorage] = SMono(getTotalSize(username)) - .filter(totalSize => (totalSize + creationRequest.size.value) <= configuration.publicAssetTotalSizeLimit.asLong()) + .filter(totalSize => (totalSize + creationRequest.size.value) <= publicAssetTotalSizeLimit.asLong()) .flatMap(_ => SMono(createAsset(username, creationRequest))) - .switchIfEmpty(SMono.error(PublicAssetQuotaLimitExceededException(configuration.publicAssetTotalSizeLimit.asLong()))) + .switchIfEmpty(SMono.error(PublicAssetQuotaLimitExceededException(publicAssetTotalSizeLimit.asLong()))) private def createAsset(username: Username, creationRequest: PublicAssetCreationRequest): SMono[PublicAssetStorage] = SMono.fromCallable(() => creationRequest.content.apply().readAllBytes()) diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetSetRequest.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetSetRequest.scala index 515336cf2f..7f7e9e612a 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetSetRequest.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetSetRequest.scala @@ -2,7 +2,6 @@ package com.linagora.tmail.james.jmap.publicAsset import java.util.UUID -import cats.implicits._ import com.linagora.tmail.james.jmap.json.PublicAssetSerializer.PublicAssetSetUpdateReads import com.linagora.tmail.james.jmap.method.PublicAssetSetMethod.LOGGER import com.linagora.tmail.james.jmap.method.standardErrorMessage @@ -10,11 +9,10 @@ import com.linagora.tmail.james.jmap.publicAsset.ImageContentType.ImageContentTy import org.apache.james.jmap.api.model.IdentityId 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.mail.{IdentityIds, BlobId => JmapBlobId} -import org.apache.james.jmap.method.{WithAccountId, standardError} +import org.apache.james.jmap.core.{AccountId, SetError, UuidState} +import org.apache.james.jmap.method.WithAccountId import org.apache.james.jmap.routes.BlobNotFoundException -import play.api.libs.json.{JsArray, JsError, JsObject, JsString, JsSuccess, JsValue} +import play.api.libs.json.{JsError, JsObject, JsSuccess} import scala.util.Try @@ -25,33 +23,6 @@ case class PublicAssetSetRequest(accountId: AccountId, case class PublicAssetCreationId(id: Id) -object PublicAssetSetCreationRequest { - val knownProperties: Set[String] = Set("blobId", "identityIds") - - def validateProperties(jsObject: JsObject): Either[PublicAssetCreationParseException, JsObject] = { - jsObject.fields.find(mapEntry => !knownProperties.contains(mapEntry._1)) - .map(e => Left(PublicAssetCreationParseException(SetError.invalidArguments( - SetErrorDescription("Some unknown properties were specified"), - Some(Properties.toProperties(Set(e._1))))))) - .getOrElse(Right(jsObject)) - } -} - -case class PublicAssetSetCreationRequest(blobId: JmapBlobId, identityIds: Option[Map[IdentityId, Boolean]] = None) { - - def parseIdentityIds: Either[PublicAssetCreationParseException, List[IdentityId]] = - identityIds match { - case None => Right(List.empty) - case Some(identityIdMap) => identityIdMap.map { - case (identityId: IdentityId, bolVal) => if (bolVal) { - Right(identityId) - } else { - scala.Left(PublicAssetCreationParseException(SetError.invalidArguments(SetErrorDescription(s"identityId '$identityId' must be a true"), Some(Properties.toProperties(Set("identityIds")))))) - } - }.toList.sequence - } -} - case class UnparsedPublicAssetId(id: String) { def tryAsPublicAssetId: Either[IllegalArgumentException, PublicAssetId] = @@ -102,10 +73,6 @@ case class PublicAssetInvalidIdentityIdException(identityId: String) extends Pub override val message: String = s"Invalid identityId: $identityId" } -case class PublicAssetIdentityIdNotFoundException(identityIds: Seq[IdentityId]) extends PublicAssetException { - override val message: String = s"IdentityId not found: ${identityIds.map(_.id.toString).mkString(", ")}" -} - sealed trait PublicAssetCreationResult { def publicAssetCreationId: PublicAssetCreationId } diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsRepository.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/settings/MemoryJmapSettingsRepository.scala similarity index 89% rename from tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsRepository.scala rename to tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/settings/MemoryJmapSettingsRepository.scala index 14d6170c9e..313bee5ae1 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/settings/JmapSettingsRepository.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/settings/MemoryJmapSettingsRepository.scala @@ -14,18 +14,6 @@ import reactor.core.scala.publisher.SMono import scala.collection.mutable -trait JmapSettingsRepository { - def get(username: Username): Publisher[JmapSettings] - - def getLatestState(username: Username): Publisher[UuidState] - - def reset(username: Username, settings: JmapSettingsUpsertRequest): Publisher[SettingsStateUpdate] - - def updatePartial(username: Username, settingsPatch: JmapSettingsPatch): Publisher[SettingsStateUpdate] - - def delete(username: Username): Publisher[Void] -} - case class MemoryJmapSettingsRepository @Inject()() extends JmapSettingsRepository { import scala.jdk.CollectionConverters._ diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/ticket/TicketManager.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/ticket/TicketManager.scala index 6af3ef9452..aac027b056 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/ticket/TicketManager.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/ticket/TicketManager.scala @@ -2,7 +2,6 @@ package com.linagora.tmail.james.jmap.ticket import java.net.InetAddress import java.time.{Clock, ZonedDateTime} -import java.util.UUID import com.linagora.tmail.james.jmap.JMAPExtensionConfiguration import jakarta.inject.Inject @@ -11,23 +10,6 @@ import org.apache.james.jmap.core.UTCDate import reactor.core.scala.publisher.SMono import scala.collection.mutable -import scala.util.Try - -object TicketValue { - def parse(string: String): Either[IllegalArgumentException, TicketValue] = Try(UUID.fromString(string)) - .map(TicketValue(_)) - .fold(e => Left(new IllegalArgumentException("TicketValue must be backed by a UUID", e)), Right(_)) - - def generate: TicketValue = TicketValue(UUID.randomUUID()) -} - -case class ForbiddenException() extends RuntimeException -case class TicketValue(value: UUID) -case class Ticket(clientAddress: InetAddress, - value: TicketValue, - generatedOn: UTCDate, - validUntil: UTCDate, - username: Username) object TicketManager { private val validity: java.time.Duration = java.time.Duration.ofMinutes(1) @@ -73,14 +55,6 @@ class TicketManager @Inject() (clock: Clock, ticketStore: TicketStore, jmapExten .flatMap(ticketStore.delete) } -trait TicketStore { - def persist(ticket: Ticket): SMono[Unit] - - def retrieve(value: TicketValue): SMono[Ticket] - - def delete(ticketValue: TicketValue): SMono[Unit] -} - class MemoryTicketStore extends TicketStore { private val map: mutable.Map[TicketValue, Ticket] = mutable.Map() diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/ContactUserDeletionTaskStepTest.scala b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/ContactUserDeletionTaskStepTest.scala index 9e30d29534..5f1bb78974 100644 --- a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/ContactUserDeletionTaskStepTest.scala +++ b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/ContactUserDeletionTaskStepTest.scala @@ -1,6 +1,6 @@ package com.linagora.tmail.james.jmap.contact -import com.linagora.tmail.james.jmap.contact.ContactUsernameChangeTaskStepTest.{ALICE, ALICE_ACCOUNT_ID, ALICE_CONTACT, ANDRE_CONTACT, BOB_ACCOUNT_ID, MARIE_CONTACT} +import ContactUsernameChangeTaskStepTest.{ALICE, ALICE_ACCOUNT_ID, ALICE_CONTACT, ANDRE_CONTACT, BOB_ACCOUNT_ID, MARIE_CONTACT} import org.apache.james.core.Domain import org.assertj.core.api.Assertions.{assertThat, assertThatCode} import org.junit.jupiter.api.{BeforeEach, Test} diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/ContactUsernameChangeTaskStepTest.scala b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/ContactUsernameChangeTaskStepTest.scala index 32508d54e2..f247bf433d 100644 --- a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/ContactUsernameChangeTaskStepTest.scala +++ b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/ContactUsernameChangeTaskStepTest.scala @@ -1,6 +1,6 @@ package com.linagora.tmail.james.jmap.contact -import com.linagora.tmail.james.jmap.contact.ContactUsernameChangeTaskStepTest.{ALICE, ALICE_ACCOUNT_ID, ANDRE_CONTACT, BOB, BOB_ACCOUNT_ID, MARIE_CONTACT} +import ContactUsernameChangeTaskStepTest.{ALICE, ALICE_ACCOUNT_ID, ANDRE_CONTACT, BOB, BOB_ACCOUNT_ID, MARIE_CONTACT} import org.apache.james.core.{MailAddress, Username} import org.apache.james.jmap.api.model.AccountId import org.assertj.core.api.Assertions.assertThat diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactListenerIntegrationTest.scala b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactListenerIntegrationTest.scala index 90cdbba8e4..9ed93606bc 100644 --- a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactListenerIntegrationTest.scala +++ b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/contact/EmailAddressContactListenerIntegrationTest.scala @@ -1,6 +1,6 @@ package com.linagora.tmail.james.jmap.contact -import com.linagora.tmail.james.jmap.contact.EmailAddressContactListenerIntegrationTest.{ACCOUNT_ID, CONTACT, CONTACT_ADDED_EVENT} +import EmailAddressContactListenerIntegrationTest.{ACCOUNT_ID, CONTACT, CONTACT_ADDED_EVENT} import org.apache.james.core.MailAddress import org.apache.james.events.EventBusTestFixture.{EVENT_ID, NO_KEYS, USERNAME} import org.apache.james.events.delivery.InVmEventDelivery diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/json/LabelSerializerTest.scala b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/json/LabelSerializerTest.scala new file mode 100644 index 0000000000..240b0d1530 --- /dev/null +++ b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/json/LabelSerializerTest.scala @@ -0,0 +1,66 @@ +package com.linagora.tmail.james.jmap.json + +import com.linagora.tmail.james.jmap.label.LabelRepositoryContract.RED +import com.linagora.tmail.james.jmap.model.{DisplayName, LabelCreationRequest} +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import play.api.libs.json.{JsResult, Json} + +class LabelSerializerTest { + + @Test + def deserializeCreationRequestShouldSuccess(): Unit = { + val jsInput = Json.parse( + """ + { + "displayName":"Label 1", + "color":"#FF0000" + } + """) + + val deserializeResult: JsResult[LabelCreationRequest] = LabelSerializer.deserializeLabelCreationRequest(jsInput) + + assertThat(deserializeResult.isSuccess) + .isTrue + assertThat(deserializeResult.get) + .usingRecursiveComparison() + .isEqualTo(LabelCreationRequest( + displayName = DisplayName("Label 1"), + color = Some(RED) + )) + } + + @Test + def givenObjectContainsUnknownPropertyDeserializeCreationRequestShouldFail(): Unit = { + val jsInput = Json.parse( + """ + { + "displayName":"Label 1", + "color":"#FF0000", + "unknown": "BAD" + } + """) + + val deserializeResult: JsResult[LabelCreationRequest] = LabelSerializer.deserializeLabelCreationRequest(jsInput) + + assertThat(deserializeResult.isError) + .isTrue + } + + @Test + def givenObjectContainsServerPropertyDeserializeCreationRequestShouldFail(): Unit = { + val jsInput = Json.parse( + """ + { + "displayName":"Label 1", + "color":"#FF0000", + "id": "BAD" + } + """) + + val deserializeResult: JsResult[LabelCreationRequest] = LabelSerializer.deserializeLabelCreationRequest(jsInput) + + assertThat(deserializeResult.isError) + .isTrue + } +} diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/json/PublicAssetSerializerTest.scala b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/json/PublicAssetSerializerTest.scala index c2fec7f5f4..4079b35260 100644 --- a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/json/PublicAssetSerializerTest.scala +++ b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/json/PublicAssetSerializerTest.scala @@ -135,6 +135,20 @@ class PublicAssetSerializerTest { .isTrue } + @Test + def deserializeCreationRequestShouldFailWhenUnknownPropertySpecified(): Unit = { + val jsInput: JsValue = Json.parse( + """{ + | "blobId": "1234", + | "unknown": "something" + |}""".stripMargin) + + val deserializeResult: JsResult[PublicAssetSetCreationRequest] = PublicAssetSerializer.deserializePublicAssetSetCreationRequest(jsInput) + + assertThat(deserializeResult.isError) + .isTrue + } + @Test def serializePublicAssetSetResponseShouldSucceed(): Unit = { val response = PublicAssetSetResponse( diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/label/MemoryLabelChangeRepositoryTest.scala b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/label/MemoryLabelChangeRepositoryTest.scala new file mode 100644 index 0000000000..22663df8bc --- /dev/null +++ b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/label/MemoryLabelChangeRepositoryTest.scala @@ -0,0 +1,25 @@ +package com.linagora.tmail.james.jmap.label + +import java.time.ZonedDateTime + +import com.linagora.tmail.james.jmap.label.LabelChangeRepositoryContract.DATE +import org.apache.james.jmap.api.change.State +import org.apache.james.utils.UpdatableTickingClock +import org.junit.jupiter.api.BeforeEach + +class MemoryLabelChangeRepositoryTest extends LabelChangeRepositoryContract { + var repository: MemoryLabelChangeRepository = _ + var updatableTickingClock: UpdatableTickingClock = _ + + override def testee: LabelChangeRepository = repository + + override def stateFactory: State.Factory = State.Factory.DEFAULT + + override def setClock(newTime: ZonedDateTime): Unit = updatableTickingClock.setInstant(newTime.toInstant) + + @BeforeEach + def setup(): Unit = { + updatableTickingClock = new UpdatableTickingClock(DATE.toInstant) + repository = MemoryLabelChangeRepository(updatableTickingClock) + } +} \ No newline at end of file diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/MemoryPublicAssetRepositoryTest.scala b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/MemoryPublicAssetRepositoryTest.scala index 42a0bd69ec..31e6b306c9 100644 --- a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/MemoryPublicAssetRepositoryTest.scala +++ b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/MemoryPublicAssetRepositoryTest.scala @@ -15,8 +15,9 @@ class MemoryPublicAssetRepositoryTest extends PublicAssetRepositoryContract { @BeforeEach def setup(): Unit = { + val blobStore = new DeDuplicationBlobStore(new MemoryBlobStoreDAO, BucketName.DEFAULT, blobIdFactory) - memoryPublicAssetRepository = new MemoryPublicAssetRepository(blobStore, JMAPExtensionConfiguration(), PUBLIC_ASSET_URI_PREFIX) + memoryPublicAssetRepository = new MemoryPublicAssetRepository(blobStore, JMAPExtensionConfiguration.PUBLIC_ASSET_TOTAL_SIZE_LIMIT_DEFAULT, PUBLIC_ASSET_URI_PREFIX) } override def teste: PublicAssetRepository = memoryPublicAssetRepository diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/MemoryPublicAssetServiceTest.scala b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/MemoryPublicAssetServiceTest.scala index f4124767d7..f6e18926df 100644 --- a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/MemoryPublicAssetServiceTest.scala +++ b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/MemoryPublicAssetServiceTest.scala @@ -15,10 +15,9 @@ class MemoryPublicAssetServiceTest extends PublicAssetServiceContract { @BeforeEach def setup(): Unit = { - val jmapConfiguration = JMAPExtensionConfiguration() val blobStore = new DeDuplicationBlobStore(new MemoryBlobStoreDAO, BucketName.DEFAULT, blobIdFactory) - memoryPublicAssetRepository = new MemoryPublicAssetRepository(blobStore, jmapConfiguration, PUBLIC_ASSET_URI_PREFIX) - publicAssetSetService = new PublicAssetSetService(identityRepository, memoryPublicAssetRepository, jmapConfiguration) + memoryPublicAssetRepository = new MemoryPublicAssetRepository(blobStore, JMAPExtensionConfiguration.PUBLIC_ASSET_TOTAL_SIZE_LIMIT_DEFAULT, PUBLIC_ASSET_URI_PREFIX) + publicAssetSetService = new PublicAssetSetService(identityRepository, memoryPublicAssetRepository, JMAPExtensionConfiguration.PUBLIC_ASSET_TOTAL_SIZE_LIMIT_DEFAULT) } override def testee: PublicAssetSetService = publicAssetSetService diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetDeletionTaskStepTest.scala b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetDeletionTaskStepTest.scala index 1237cac3f0..fc371562e9 100644 --- a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetDeletionTaskStepTest.scala +++ b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/publicAsset/PublicAssetDeletionTaskStepTest.scala @@ -26,7 +26,7 @@ class PublicAssetDeletionTaskStepTest { publicAssetRepository = new MemoryPublicAssetRepository(new DeDuplicationBlobStore(new MemoryBlobStoreDAO, BucketName.DEFAULT, new PlainBlobId.Factory()), - JMAPExtensionConfiguration(), + JMAPExtensionConfiguration.PUBLIC_ASSET_TOTAL_SIZE_LIMIT_DEFAULT, PUBLIC_ASSET_URI_PREFIX) publicAssetDeletionTaskStep = new PublicAssetDeletionTaskStep(publicAssetRepository); } diff --git a/tmail-backend/mailets/pom.xml b/tmail-backend/mailets/pom.xml index a4f338e4d8..2462294688 100644 --- a/tmail-backend/mailets/pom.xml +++ b/tmail-backend/mailets/pom.xml @@ -14,24 +14,6 @@ Mailets for Twake Mail - - ${project.groupId} - jmap-extensions - - - ${project.groupId} - team-mailboxes - - - ${project.groupId} - team-mailboxes - test-jar - test - - - ${project.groupId} - tmail-rate-limiter-api - ${james.groupId} apache-james-backends-redis @@ -109,23 +91,23 @@ ${james.groupId} - james-server-rate-limiter-redis - test + james-server-mailets ${james.groupId} - james-server-rate-limiter-redis + james-server-queue-api test - test-jar ${james.groupId} - james-server-queue-api + james-server-rate-limiter-redis test ${james.groupId} - james-server-mailets + james-server-rate-limiter-redis + test + test-jar ${james.groupId} @@ -143,20 +125,42 @@ test - org.mockito - mockito-core - test + ${project.groupId} + jmap-extensions-api - org.testcontainers - testcontainers + ${project.groupId} + team-mailboxes + + + ${project.groupId} + team-mailboxes + test-jar test + + ${project.groupId} + tmail-rate-limiter-api + + + com.sparkjava + spark-core + net.javacrumbs.json-unit json-unit-assertj test + + org.mockito + mockito-core + test + + + org.testcontainers + testcontainers + test + diff --git a/tmail-backend/pom.xml b/tmail-backend/pom.xml index 7d0437c6db..a19d268d92 100644 --- a/tmail-backend/pom.xml +++ b/tmail-backend/pom.xml @@ -48,6 +48,7 @@ imap-extensions jmap/extensions + jmap/extensions-api jmap/extensions-cassandra jmap/extensions-opensearch jmap/extensions-rabbitmq @@ -142,6 +143,17 @@ ${project.version} test-jar + + ${project.groupId} + jmap-extensions-api + ${project.version} + + + ${project.groupId} + jmap-extensions-api + ${project.version} + test-jar + ${project.groupId} jmap-extensions-opensearch diff --git a/tmail-backend/tmail-third-party/openpaas/pom.xml b/tmail-backend/tmail-third-party/openpaas/pom.xml index 9d63363100..a3f1cf7f07 100644 --- a/tmail-backend/tmail-third-party/openpaas/pom.xml +++ b/tmail-backend/tmail-third-party/openpaas/pom.xml @@ -15,10 +15,6 @@ OpenPaaS integration for Twake Mail - - ${project.groupId} - jmap-extensions - ${james.groupId} apache-james-backends-rabbitmq @@ -47,6 +43,10 @@ testing-base test + + ${project.groupId} + jmap-extensions-api + com.fasterxml.jackson.core jackson-annotations @@ -59,6 +59,10 @@ com.fasterxml.jackson.core jackson-databind + + com.sparkjava + spark-core + io.projectreactor reactor-core @@ -74,7 +78,6 @@ - diff --git a/tmail-backend/webadmin/webadmin-email-address-contact/pom.xml b/tmail-backend/webadmin/webadmin-email-address-contact/pom.xml index 38694b7d7d..ee9124bff9 100644 --- a/tmail-backend/webadmin/webadmin-email-address-contact/pom.xml +++ b/tmail-backend/webadmin/webadmin-email-address-contact/pom.xml @@ -14,10 +14,6 @@ Twake Mail :: Webadmin :: Email Address Contacts - - ${project.groupId} - jmap-extensions - ${james.groupId} james-server-data-api @@ -47,6 +43,10 @@ metrics-tests test + + ${project.groupId} + jmap-extensions-api + net.javacrumbs.json-unit json-unit-assertj