You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2022/01/06 01:51:52 UTC
[james-project] branch master updated: JAMES-3680 Capability should be aware of request URL prefixes (#807)
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/master by this push:
new 8607439 JAMES-3680 Capability should be aware of request URL prefixes (#807)
8607439 is described below
commit 8607439fb9bb4c309b5d3df5aba73a3b7a1902e5
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Thu Jan 6 08:51:44 2022 +0700
JAMES-3680 Capability should be aware of request URL prefixes (#807)
---
.../org/apache/james/jmap/draft/JMAPModule.java | 34 ++++----
.../rfc8621/contract/CustomMethodContract.scala | 12 ++-
.../org/apache/james/jmap/core/Capabilities.scala | 20 +++--
.../org/apache/james/jmap/core/Capability.scala | 95 +++++++++++++++++++++-
.../james/jmap/core/JmapRfc8621Configuration.scala | 3 +
.../org/apache/james/jmap/routes/JmapApi.scala | 10 +--
.../apache/james/jmap/routes/SessionRoutes.scala | 24 ++----
.../apache/james/jmap/routes/SessionSupplier.scala | 63 +++++---------
.../change/StateChangeEventSerializerTest.scala | 1 -
.../jmap/core/JmapUrlEndpointResolverTest.scala | 76 +----------------
.../james/jmap/routes/JMAPApiRoutesTest.scala | 4 +-
.../james/jmap/routes/SessionRoutesTest.scala | 5 +-
.../james/jmap/routes/SessionSupplierTest.scala | 8 +-
13 files changed, 179 insertions(+), 176 deletions(-)
diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JMAPModule.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JMAPModule.java
index 438a7d0..2acbbb8 100644
--- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JMAPModule.java
+++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JMAPModule.java
@@ -37,16 +37,22 @@ import org.apache.james.jmap.JMAPConfiguration;
import org.apache.james.jmap.JMAPServer;
import org.apache.james.jmap.Version;
import org.apache.james.jmap.change.MailboxChangeListener;
-import org.apache.james.jmap.core.Capability;
-import org.apache.james.jmap.core.DefaultCapabilities;
+import org.apache.james.jmap.core.CapabilityFactory;
+import org.apache.james.jmap.core.CoreCapabilityFactory;
import org.apache.james.jmap.core.JmapRfc8621Configuration;
+import org.apache.james.jmap.core.MDNCapabilityFactory$;
+import org.apache.james.jmap.core.MailCapabilityFactory$;
+import org.apache.james.jmap.core.QuotaCapabilityFactory$;
+import org.apache.james.jmap.core.SharesCapabilityFactory$;
+import org.apache.james.jmap.core.SubmissionCapabilityFactory$;
+import org.apache.james.jmap.core.VacationResponseCapabilityFactory$;
+import org.apache.james.jmap.core.WebSocketCapabilityFactory$;
import org.apache.james.jmap.draft.methods.RequestHandler;
import org.apache.james.jmap.draft.send.PostDequeueDecoratorFactory;
import org.apache.james.jmap.draft.utils.JsoupHtmlTextExtractor;
import org.apache.james.jmap.event.PropagateLookupRightListener;
import org.apache.james.jmap.mailet.filter.JMAPFiltering;
import org.apache.james.jmap.rfc8621.RFC8621MethodsModule;
-import org.apache.james.jmap.routes.JmapUrlEndpointResolver;
import org.apache.james.jwt.JwtConfiguration;
import org.apache.james.jwt.JwtTokenVerifier;
import org.apache.james.lifecycle.api.StartUpCheck;
@@ -135,13 +141,13 @@ public class JMAPModule extends AbstractModule {
supportedVersions.addBinding().toInstance(Version.DRAFT);
supportedVersions.addBinding().toInstance(Version.RFC8621);
- Multibinder<Capability> supportedCapabilities = Multibinder.newSetBinder(binder(), Capability.class);
- supportedCapabilities.addBinding().toInstance(DefaultCapabilities.MAIL_CAPABILITY());
- supportedCapabilities.addBinding().toInstance(DefaultCapabilities.QUOTA_CAPABILITY());
- supportedCapabilities.addBinding().toInstance(DefaultCapabilities.SHARES_CAPABILITY());
- supportedCapabilities.addBinding().toInstance(DefaultCapabilities.VACATION_RESPONSE_CAPABILITY());
- supportedCapabilities.addBinding().toInstance(DefaultCapabilities.SUBMISSION_CAPABILITY());
- supportedCapabilities.addBinding().toInstance(DefaultCapabilities.MDN_CAPABILITY());
+ Multibinder<CapabilityFactory> supportedCapabilities = Multibinder.newSetBinder(binder(), CapabilityFactory.class);
+ supportedCapabilities.addBinding().toInstance(MailCapabilityFactory$.MODULE$);
+ supportedCapabilities.addBinding().toInstance(QuotaCapabilityFactory$.MODULE$);
+ supportedCapabilities.addBinding().toInstance(SharesCapabilityFactory$.MODULE$);
+ supportedCapabilities.addBinding().toInstance(VacationResponseCapabilityFactory$.MODULE$);
+ supportedCapabilities.addBinding().toInstance(SubmissionCapabilityFactory$.MODULE$);
+ supportedCapabilities.addBinding().toInstance(MDNCapabilityFactory$.MODULE$);
}
@ProvidesIntoSet
@@ -161,13 +167,13 @@ public class JMAPModule extends AbstractModule {
}
@ProvidesIntoSet
- Capability coreCapability(JmapRfc8621Configuration configuration) {
- return DefaultCapabilities.coreCapability(configuration.maxUploadSize());
+ CapabilityFactory coreCapability(JmapRfc8621Configuration configuration) {
+ return new CoreCapabilityFactory(configuration.maxUploadSize());
}
@ProvidesIntoSet
- Capability webSocketCapability(JmapRfc8621Configuration configuration) {
- return DefaultCapabilities.webSocketCapability(JmapUrlEndpointResolver.from(configuration).webSocketUrl());
+ CapabilityFactory webSocketCapability(JmapRfc8621Configuration configuration) {
+ return WebSocketCapabilityFactory$.MODULE$;
}
@Provides
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/CustomMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/CustomMethodContract.scala
index 84a813d..d2e5487 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/CustomMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/CustomMethodContract.scala
@@ -43,7 +43,7 @@ import org.apache.james.jmap.change.{AccountIdRegistrationKey, StateChangeEvent}
import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE}
import org.apache.james.jmap.core.Invocation.MethodName
import org.apache.james.jmap.core.ResponseObject.SESSION_STATE
-import org.apache.james.jmap.core.{Capability, CapabilityProperties}
+import org.apache.james.jmap.core.{Capability, CapabilityFactory, CapabilityProperties, UrlPrefixes}
import org.apache.james.jmap.draft.JmapGuiceProbe
import org.apache.james.jmap.http.UserCredential
import org.apache.james.jmap.mail
@@ -177,10 +177,16 @@ case class CustomCapabilityProperties() extends CapabilityProperties {
case class CustomCapability(properties: CustomCapabilityProperties = CustomCapabilityProperties(), identifier: CapabilityIdentifier = CUSTOM) extends Capability
+case object CustomCapabilityFactory extends CapabilityFactory {
+ override def create(urlPrefixes: UrlPrefixes): Capability = CustomCapability()
+
+ override def id(): CapabilityIdentifier = CUSTOM
+}
+
class CustomMethodModule extends AbstractModule {
override protected def configure(): Unit = {
- val supportedCapabilities: Multibinder[Capability] = Multibinder.newSetBinder(binder, classOf[Capability])
- supportedCapabilities.addBinding.toInstance(CustomCapability())
+ val supportedCapabilities: Multibinder[CapabilityFactory] = Multibinder.newSetBinder(binder, classOf[CapabilityFactory])
+ supportedCapabilities.addBinding.toInstance(CustomCapabilityFactory)
Multibinder.newSetBinder(binder(), classOf[Method])
.addBinding()
.to(classOf[CustomMethod])
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capabilities.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capabilities.scala
index 229e917..ae113f3 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capabilities.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capabilities.scala
@@ -20,9 +20,9 @@ package org.apache.james.jmap.core
import java.net.URI
+import com.google.common.annotations.VisibleForTesting
import eu.timepit.refined.auto._
import org.apache.james.jmap.core.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.routes.JmapUrlEndpointResolver
object DefaultCapabilities {
def coreCapability(maxUploadSize: MaxSizeUpload) = CoreCapability(
@@ -53,14 +53,16 @@ object DefaultCapabilities {
val VACATION_RESPONSE_CAPABILITY = VacationResponseCapability()
val SUBMISSION_CAPABILITY = SubmissionCapability()
- def supported(configuration: JmapRfc8621Configuration): Capabilities = Capabilities.of(
- coreCapability(configuration.maxUploadSize),
- MAIL_CAPABILITY,
- QUOTA_CAPABILITY,
- SHARES_CAPABILITY,
- VACATION_RESPONSE_CAPABILITY,
- SUBMISSION_CAPABILITY,
- webSocketCapability(JmapUrlEndpointResolver.from(configuration).webSocketUrl))
+ @VisibleForTesting
+ def supported(configuration: JmapRfc8621Configuration): Set[CapabilityFactory] = Set(
+ CoreCapabilityFactory(configuration.maxUploadSize),
+ MailCapabilityFactory,
+ QuotaCapabilityFactory,
+ SharesCapabilityFactory,
+ VacationResponseCapabilityFactory,
+ SharesCapabilityFactory,
+ SubmissionCapabilityFactory,
+ WebSocketCapabilityFactory)
}
object Capabilities {
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capability.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capability.scala
index 920d087..d8ceafd 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capability.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capability.scala
@@ -19,7 +19,7 @@
package org.apache.james.jmap.core
-import java.net.URI
+import java.net.{URI, URL}
import eu.timepit.refined
import eu.timepit.refined.api.Refined
@@ -33,6 +33,7 @@ import org.apache.james.jmap.core.UnsignedInt.{UnsignedInt, UnsignedIntConstrain
import org.apache.james.jmap.json.ResponseSerializer
import org.apache.james.util.Size
import play.api.libs.json.{JsObject, Json}
+import reactor.netty.http.server.HttpServerRequest
import scala.util.{Failure, Success, Try}
@@ -60,11 +61,61 @@ trait Capability {
def properties(): CapabilityProperties
}
+object UrlPrefixes {
+ private val JMAP_PREFIX_HEADER: String = "X-JMAP-PREFIX"
+ private val JMAP_WEBSOCKET_PREFIX_HEADER: String = "X-JMAP-WEBSOCKET-PREFIX"
+
+ def from(jmapRfc8621Configuration: JmapRfc8621Configuration, request: HttpServerRequest): UrlPrefixes =
+ if (jmapRfc8621Configuration.dynamicJmapPrefixResolutionEnabled) {
+ UrlPrefixes(
+ safeURL(request.requestHeaders().get(JMAP_PREFIX_HEADER))
+ .map(_.toURI)
+ .getOrElse(new URI(jmapRfc8621Configuration.urlPrefixString)),
+ safeURI(request.requestHeaders().get(JMAP_WEBSOCKET_PREFIX_HEADER))
+ .getOrElse(new URI(jmapRfc8621Configuration.websocketPrefixString)))
+ } else {
+ jmapRfc8621Configuration.urlPrefixes()
+ }
+
+ private def safeURI(string: String): Option[URI] = Option(string).flatMap(s => Try(new URI(s)).toOption)
+
+ private def safeURL(string: String): Option[URL] = Option(string).flatMap(s => Try(new URL(s)).toOption)
+}
+
+final case class UrlPrefixes(httpUrlPrefix: URI, webSocketURLPrefix: URI)
+
+trait CapabilityFactory {
+ def create(urlPrefixes: UrlPrefixes): Capability
+
+ def id(): CapabilityIdentifier
+}
+
final case class CoreCapability(properties: CoreCapabilityProperties,
identifier: CapabilityIdentifier = JMAP_CORE) extends Capability
+final case class CoreCapabilityFactory(maxUploadSize: MaxSizeUpload) extends CapabilityFactory {
+ override def id(): CapabilityIdentifier = JMAP_CORE
+
+ override def create(urlPrefixes: UrlPrefixes): Capability = CoreCapability(CoreCapabilityProperties(
+ maxUploadSize,
+ MaxConcurrentUpload(4L),
+ MaxSizeRequest(10_000_000L),
+ MaxConcurrentRequests(4L),
+ MaxCallsInRequest(16L),
+ MaxObjectsInGet(500L),
+ MaxObjectsInSet(500L),
+ collationAlgorithms = List("i;unicode-casemap")))
+}
+
case class WebSocketCapability(properties: WebSocketCapabilityProperties, identifier: CapabilityIdentifier = JMAP_WEBSOCKET) extends Capability
+case object WebSocketCapabilityFactory extends CapabilityFactory {
+ override def id(): CapabilityIdentifier = JMAP_WEBSOCKET
+
+ override def create(urlPrefixes: UrlPrefixes): Capability = WebSocketCapability(
+ WebSocketCapabilityProperties(SupportsPush(true), new URI(urlPrefixes.webSocketURLPrefix + "/jmap/ws")))
+}
+
object MaxSizeUpload {
def of(size: Size): Try[MaxSizeUpload] = refined.refineV[UnsignedIntConstraint](size.asBytes()) match {
case Right(value) => Success(MaxSizeUpload(value))
@@ -108,6 +159,12 @@ final case class EhloArgs(value: String) extends AnyVal
final case class SubmissionCapability(identifier: CapabilityIdentifier = EMAIL_SUBMISSION,
properties: SubmissionProperties = SubmissionProperties()) extends Capability
+case object SubmissionCapabilityFactory extends CapabilityFactory {
+ override def id(): CapabilityIdentifier = EMAIL_SUBMISSION
+
+ override def create(urlPrefixes: UrlPrefixes): Capability = SubmissionCapability()
+}
+
final case class SubmissionProperties(maxDelayedSend: MaxDelayedSend = MaxDelayedSend(0),
submissionExtensions: Map[EhloName, List[EhloArgs]] = Map()) extends CapabilityProperties {
override def jsonify(): JsObject = ResponseSerializer.submissionPropertiesWrites.writes(this)
@@ -120,6 +177,18 @@ object MailCapability {
final case class MailCapability(properties: MailCapabilityProperties,
identifier: CapabilityIdentifier = JMAP_MAIL) extends Capability
+case object MailCapabilityFactory extends CapabilityFactory {
+ override def id(): CapabilityIdentifier = JMAP_MAIL
+
+ override def create(urlPrefixes: UrlPrefixes): Capability = MailCapability(MailCapabilityProperties(
+ MaxMailboxesPerEmail(Some(10_000_000L)),
+ MaxMailboxDepth(None),
+ MaxSizeMailboxName(200L),
+ MaxSizeAttachmentsPerEmail(20_000_000L),
+ emailQuerySortOptions = List("receivedAt", "sentAt", "size", "from", "to", "subject"),
+ MayCreateTopLevelMailbox(true)))
+}
+
case class MaxMailboxesPerEmail(value: Option[UnsignedInt])
case class MaxMailboxDepth(value: Option[UnsignedInt])
case class MaxSizeMailboxName(value: UnsignedInt)
@@ -142,10 +211,22 @@ final case class QuotaCapabilityProperties() extends CapabilityProperties {
final case class QuotaCapability(properties: QuotaCapabilityProperties = QuotaCapabilityProperties(),
identifier: CapabilityIdentifier = JAMES_QUOTA) extends Capability
+case object QuotaCapabilityFactory extends CapabilityFactory {
+ override def id(): CapabilityIdentifier = JAMES_QUOTA
+
+ override def create(urlPrefixes: UrlPrefixes): Capability = QuotaCapability()
+}
+
final case class SharesCapabilityProperties() extends CapabilityProperties {
override def jsonify(): JsObject = Json.obj()
}
+case object SharesCapabilityFactory extends CapabilityFactory {
+ override def id(): CapabilityIdentifier = JAMES_SHARES
+
+ override def create(urlPrefixes: UrlPrefixes): Capability = SharesCapability()
+}
+
final case class SharesCapability(properties: SharesCapabilityProperties = SharesCapabilityProperties(),
identifier: CapabilityIdentifier = JAMES_SHARES) extends Capability
@@ -153,6 +234,12 @@ final case class MDNCapabilityProperties() extends CapabilityProperties {
override def jsonify(): JsObject = Json.obj()
}
+case object MDNCapabilityFactory extends CapabilityFactory {
+ override def id(): CapabilityIdentifier = JMAP_MDN
+
+ override def create(urlPrefixes: UrlPrefixes): Capability = MDNCapability()
+}
+
final case class MDNCapability(properties: MDNCapabilityProperties = MDNCapabilityProperties(),
identifier: CapabilityIdentifier = JMAP_MDN) extends Capability
@@ -160,5 +247,11 @@ final case class VacationResponseCapabilityProperties() extends CapabilityProper
override def jsonify(): JsObject = Json.obj()
}
+case object VacationResponseCapabilityFactory extends CapabilityFactory {
+ override def id(): CapabilityIdentifier = JMAP_VACATION_RESPONSE
+
+ override def create(urlPrefixes: UrlPrefixes): Capability = VacationResponseCapability()
+}
+
final case class VacationResponseCapability(properties: VacationResponseCapabilityProperties = VacationResponseCapabilityProperties(),
identifier: CapabilityIdentifier = JMAP_VACATION_RESPONSE) extends Capability
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala
index 70b29ae..c8f0651 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala
@@ -19,6 +19,7 @@
package org.apache.james.jmap.core
+import java.net.{URI, URL}
import java.util.Optional
import org.apache.commons.configuration2.Configuration
@@ -75,6 +76,8 @@ case class JmapRfc8621Configuration(urlPrefixString: String,
maxTimeoutSeconds = maxTimeoutSeconds,
maxConnections = maxConnections)
+ def urlPrefixes(): UrlPrefixes = UrlPrefixes(new URI(urlPrefixString), new URI(websocketPrefixString))
+
def getAuthenticationStrategiesAsJava(): Optional[java.util.List[String]] = authenticationStrategies.toJava
def withAuthenticationStrategies(list: Optional[java.util.List[String]]): JmapRfc8621Configuration =
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JmapApi.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JmapApi.scala
index 3d19067..32f0f33 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JmapApi.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JmapApi.scala
@@ -21,7 +21,7 @@ package org.apache.james.jmap.routes
import javax.inject.Inject
import org.apache.james.jmap.core.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.core.Invocation.MethodName
-import org.apache.james.jmap.core.{Capability, ErrorCode, Invocation, MissingCapabilityException, RequestObject, ResponseObject}
+import org.apache.james.jmap.core.{CapabilityFactory, ErrorCode, Invocation, MissingCapabilityException, RequestObject, ResponseObject}
import org.apache.james.jmap.method.{InvocationWithContext, Method}
import org.apache.james.mailbox.MailboxSession
import org.slf4j.{Logger, LoggerFactory}
@@ -34,19 +34,19 @@ object JMAPApi {
val LOGGER: Logger = LoggerFactory.getLogger(classOf[JMAPApi])
}
-class JMAPApi (methods: Set[Method], defaultCapabilities: Set[Capability]) {
+class JMAPApi (methods: Set[Method], defaultCapabilities: Set[CapabilityIdentifier]) {
private val methodsByName: Map[MethodName, Method] = methods.map(method => method.methodName -> method).toMap
@Inject
- def this(javaMethods: java.util.Set[Method], supportedCapabilities: java.util.Set[Capability]) {
- this(javaMethods.asScala.toSet, supportedCapabilities.asScala.toSet)
+ def this(javaMethods: java.util.Set[Method], supportedCapabilities: java.util.Set[CapabilityFactory]) {
+ this(javaMethods.asScala.toSet, supportedCapabilities.asScala.map(x => x.id()).toSet)
}
def process(requestObject: RequestObject,
mailboxSession: MailboxSession): SMono[ResponseObject] = {
val processingContext: ProcessingContext = ProcessingContext(Map.empty, Map.empty)
- val unsupportedCapabilities = requestObject.using.toSet -- defaultCapabilities.map(_.identifier())
+ val unsupportedCapabilities = requestObject.using.toSet -- defaultCapabilities
val capabilities: Set[CapabilityIdentifier] = requestObject.using.toSet
if (unsupportedCapabilities.nonEmpty) {
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionRoutes.scala
index 01d1424..f34b04d 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionRoutes.scala
@@ -19,7 +19,6 @@
package org.apache.james.jmap.routes
-import java.net.{URI, URL}
import java.nio.charset.StandardCharsets
import java.util.stream.Stream
@@ -29,38 +28,35 @@ import io.netty.handler.codec.http.{HttpMethod, HttpResponseStatus}
import javax.inject.{Inject, Named}
import org.apache.james.jmap.HttpConstants.{JSON_CONTENT_TYPE, JSON_CONTENT_TYPE_UTF8}
import org.apache.james.jmap.JMAPRoutes.CORS_CONTROL
-import org.apache.james.jmap.core.{ProblemDetails, Session}
+import org.apache.james.jmap.core.{JmapRfc8621Configuration, ProblemDetails, Session, UrlPrefixes}
import org.apache.james.jmap.exceptions.UnauthorizedException
import org.apache.james.jmap.http.Authenticator
import org.apache.james.jmap.http.rfc8621.InjectionKeys
import org.apache.james.jmap.json.ResponseSerializer
-import org.apache.james.jmap.routes.SessionRoutes.{JMAP_PREFIX_HEADER, JMAP_SESSION, JMAP_WEBSOCKET_PREFIX_HEADER, LOGGER, WELL_KNOWN_JMAP}
+import org.apache.james.jmap.routes.SessionRoutes.{JMAP_SESSION, LOGGER, WELL_KNOWN_JMAP}
import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
import org.slf4j.{Logger, LoggerFactory}
import play.api.libs.json.Json
import reactor.core.publisher.Mono
import reactor.core.scala.publisher.SMono
import reactor.core.scheduler.Schedulers
-import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
-
-import scala.util.Try
+import reactor.netty.http.server.HttpServerResponse
object SessionRoutes {
private val JMAP_SESSION: String = "/jmap/session"
private val WELL_KNOWN_JMAP: String = "/.well-known/jmap"
private val LOGGER: Logger = LoggerFactory.getLogger(classOf[SessionRoutes])
- private val JMAP_PREFIX_HEADER: String = "X-JMAP-PREFIX"
- private val JMAP_WEBSOCKET_PREFIX_HEADER: String = "X-JMAP-WEBSOCKET-PREFIX"
}
class SessionRoutes @Inject() (@Named(InjectionKeys.RFC_8621) val authenticator: Authenticator,
- val sessionSupplier: SessionSupplier) extends JMAPRoutes {
+ val sessionSupplier: SessionSupplier,
+ val jmapRfc8621Configuration: JmapRfc8621Configuration) extends JMAPRoutes {
private val generateSession: JMAPRoute.Action =
(request, response) => SMono.fromPublisher(authenticator.authenticate(request))
.map(_.getUser)
.handle[Session] {
- case (username, sink) => sessionSupplier.generate(username, extractJMAPPrefix(request), extractJMAPWebSocketPrefix(request))
+ case (username, sink) => sessionSupplier.generate(username, UrlPrefixes.from(jmapRfc8621Configuration, request))
.fold(sink.error, session => sink.next(session))
}
.flatMap(session => sendRespond(session, response))
@@ -68,14 +64,6 @@ class SessionRoutes @Inject() (@Named(InjectionKeys.RFC_8621) val authenticator:
.subscribeOn(Schedulers.elastic())
.asJava()
- private def extractJMAPPrefix(request: HttpServerRequest): Option[URL] =
- Option(request.requestHeaders().get(JMAP_PREFIX_HEADER))
- .flatMap(value => Try(new URL(value)).toOption)
-
- private def extractJMAPWebSocketPrefix(request: HttpServerRequest): Option[URI] =
- Option(request.requestHeaders().get(JMAP_WEBSOCKET_PREFIX_HEADER))
- .flatMap(value => Try(new URI(value)).toOption)
-
private val redirectToSession: JMAPRoute.Action = JMAPRoutes.redirectTo(JMAP_SESSION)
override def routes: Stream[JMAPRoute] =
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionSupplier.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionSupplier.scala
index d9d4b8e..e5ceb1f 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionSupplier.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionSupplier.scala
@@ -19,19 +19,19 @@
package org.apache.james.jmap.routes
-import java.net.{URI, URL}
+import java.net.URL
import javax.inject.Inject
import org.apache.james.core.Username
import org.apache.james.jmap.core.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.core.{Account, AccountId, Capabilities, Capability, IsPersonal, IsReadOnly, JmapRfc8621Configuration, Session, WebSocketCapability}
+import org.apache.james.jmap.core.{Account, AccountId, Capabilities, Capability, CapabilityFactory, IsPersonal, IsReadOnly, Session, UrlPrefixes}
import scala.jdk.CollectionConverters._
-class SessionSupplier(val configuration: JmapRfc8621Configuration, defaultCapabilities: Set[Capability]) {
+class SessionSupplier(capabilityFactories: Set[CapabilityFactory]) {
@Inject
- def this(configuration: JmapRfc8621Configuration, defaultCapabilities: java.util.Set[Capability]) {
- this(configuration, defaultCapabilities.asScala.toSet)
+ def this(defaultCapabilities: java.util.Set[CapabilityFactory]) {
+ this(defaultCapabilities.asScala.toSet)
}
def validate(username: Username, accountId: AccountId): Boolean = AccountId.from(username)
@@ -39,19 +39,15 @@ class SessionSupplier(val configuration: JmapRfc8621Configuration, defaultCapabi
.toOption
.getOrElse(false)
- def generate(username: Username, urlPrefix: Option[URL] = None, webSocketUriPrefix: Option[URI] = None): Either[IllegalArgumentException, Session] = {
- val urlEndpointResolver: JmapUrlEndpointResolver = new JmapUrlEndpointResolver(configuration, urlPrefix, webSocketUriPrefix)
- val capabilities: Set[Capability] = defaultCapabilities
- .map {
- case websocketCapability: WebSocketCapability =>
- websocketCapability.copy(properties = websocketCapability.properties.copy(url = urlEndpointResolver.webSocketUrl))
- case another => another
- }
- accounts(username)
+ def generate(username: Username, urlPrefixes: UrlPrefixes): Either[IllegalArgumentException, Session] = {
+ val urlEndpointResolver: JmapUrlEndpointResolver = new JmapUrlEndpointResolver(urlPrefixes)
+ val capabilities: Set[Capability] = capabilityFactories
+ .map(cf => cf.create(urlPrefixes))
+ accounts(username, capabilities)
.map(account => Session(
Capabilities(capabilities),
List(account),
- primaryAccounts(account.accountId),
+ primaryAccounts(account.accountId, capabilities),
username,
apiUrl = urlEndpointResolver.apiUrl,
downloadUrl = urlEndpointResolver.downloadUrl,
@@ -59,40 +55,21 @@ class SessionSupplier(val configuration: JmapRfc8621Configuration, defaultCapabi
eventSourceUrl = urlEndpointResolver.eventSourceUrl))
}
- private def accounts(username: Username): Either[IllegalArgumentException, Account] =
- Account.from(username, IsPersonal(true), IsReadOnly(false), defaultCapabilities)
+ private def accounts(username: Username, capabilities: Set[Capability]): Either[IllegalArgumentException, Account] =
+ Account.from(username, IsPersonal(true), IsReadOnly(false), capabilities)
- private def primaryAccounts(accountId: AccountId): Map[CapabilityIdentifier, AccountId] =
- defaultCapabilities
+ private def primaryAccounts(accountId: AccountId, capabilities: Set[Capability]): Map[CapabilityIdentifier, AccountId] =
+ capabilities
.map(capability => (capability.identifier(), accountId))
.toMap
}
-object JmapUrlEndpointResolver {
- def from(configuration: JmapRfc8621Configuration): JmapUrlEndpointResolver = new JmapUrlEndpointResolver(configuration)
-}
-
-class JmapUrlEndpointResolver(val configuration: JmapRfc8621Configuration,
- urlPrefixRequest: Option[URL] = None,
- uriWebSocketPrefixRequest: Option[URI] = None) {
-
- val urlPrefix: String = Some(configuration.dynamicJmapPrefixResolutionEnabled)
- .filter(enabled => enabled)
- .flatMap(_ => urlPrefixRequest.map(_.toString))
- .getOrElse(configuration.urlPrefixString)
-
- val uriWebSocketPrefix: String = Some(configuration.dynamicJmapPrefixResolutionEnabled)
- .filter(enabled => enabled)
- .flatMap(_ => uriWebSocketPrefixRequest.map(_.toString))
- .getOrElse(configuration.websocketPrefixString)
-
- val apiUrl: URL = new URL(s"$urlPrefix/jmap")
-
- val downloadUrl: URL = new URL(s"$urlPrefix/download/{accountId}/{blobId}?type={type}&name={name}")
+class JmapUrlEndpointResolver(val urlPrefixes: UrlPrefixes) {
+ val apiUrl: URL = new URL(urlPrefixes.httpUrlPrefix.toString + "/jmap")
- val uploadUrl: URL = new URL(s"$urlPrefix/upload/{accountId}")
+ val downloadUrl: URL = new URL(urlPrefixes.httpUrlPrefix.toString + "/download/{accountId}/{blobId}?type={type}&name={name}")
- val webSocketUrl: URI = new URI(s"$uriWebSocketPrefix/jmap/ws")
+ val uploadUrl: URL = new URL(urlPrefixes.httpUrlPrefix.toString + "/upload/{accountId}")
- val eventSourceUrl: URL = new URL(s"$urlPrefix/eventSource?types={types}&closeAfter={closeafter}&ping={ping}")
+ val eventSourceUrl: URL = new URL(urlPrefixes.httpUrlPrefix.toString + "/eventSource?types={types}&closeAfter={closeafter}&ping={ping}")
}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeEventSerializerTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeEventSerializerTest.scala
index 08f0010..cc30918 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeEventSerializerTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeEventSerializerTest.scala
@@ -21,7 +21,6 @@ package org.apache.james.jmap.change
import org.apache.james.JsonSerializationVerifier
import org.apache.james.core.Username
import org.apache.james.events.Event.EventId
-import org.apache.james.jmap.api.change
import org.apache.james.jmap.api.change.TypeStateFactory
import org.apache.james.jmap.api.model.TypeName
import org.apache.james.jmap.change.StateChangeEventSerializerTest.{EVENT, EVENT_EMPTY_TYPE_STATE_MAP, EVENT_ID, EVENT_JSON, EVENT_JSON_EMPTY_TYPE_STATE_MAP, EVENT_JSON_NO_DELIVERY, EVENT_NO_DELIVERY, USERNAME}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/core/JmapUrlEndpointResolverTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/core/JmapUrlEndpointResolverTest.scala
index 40aa9e3..325c32d 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/core/JmapUrlEndpointResolverTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/core/JmapUrlEndpointResolverTest.scala
@@ -22,8 +22,7 @@ package org.apache.james.jmap.core
import java.net.{URI, URL}
import org.apache.commons.configuration2.{Configuration, PropertiesConfiguration}
-import org.apache.james.jmap.core.JmapConfigProperties.{DYNAMIC_JMAP_PREFIX_RESOLUTION_ENABLED_PROPERTY, URL_PREFIX_PROPERTY, WEBSOCKET_URL_PREFIX_PROPERTY}
-import org.apache.james.jmap.core.JmapUrlEndpointResolverTest.{emptyConfiguration, providedConfiguration}
+import org.apache.james.jmap.core.JmapConfigProperties.{URL_PREFIX_PROPERTY, WEBSOCKET_URL_PREFIX_PROPERTY}
import org.apache.james.jmap.routes.JmapUrlEndpointResolver
import org.scalatest.matchers.must.Matchers
import org.scalatest.wordspec.AnyWordSpec
@@ -41,83 +40,12 @@ object JmapUrlEndpointResolverTest {
class JmapUrlEndpointResolverTest extends AnyWordSpec with Matchers {
"JmapUrlEndpointResolverTest" should {
"succeed to configuration urlPrefix when provided" in {
- val jmapRfc8621Configuration: JmapRfc8621Configuration = JmapRfc8621Configuration.from(providedConfiguration())
- val testee : JmapUrlEndpointResolver= new JmapUrlEndpointResolver(jmapRfc8621Configuration)
+ val testee : JmapUrlEndpointResolver= new JmapUrlEndpointResolver(UrlPrefixes(new URI("http://random-domain.com"), new URI("ws://random-domain.com")))
testee.apiUrl must be(new URL("http://random-domain.com/jmap"))
testee.downloadUrl must be(new URL("http://random-domain.com/download/{accountId}/{blobId}?type={type}&name={name}"))
testee.uploadUrl must be(new URL("http://random-domain.com/upload/{accountId}"))
testee.eventSourceUrl must be(new URL("http://random-domain.com/eventSource?types={types}&closeAfter={closeafter}&ping={ping}"))
- testee.webSocketUrl must be(new URI("ws://random-domain.com/jmap/ws"))
- }
-
- "load default config for urlPrefix when no configuration provided" in {
- val jmapRfc8621Configuration: JmapRfc8621Configuration = JmapRfc8621Configuration.from(emptyConfiguration)
- val testee : JmapUrlEndpointResolver= new JmapUrlEndpointResolver(jmapRfc8621Configuration)
-
- testee.apiUrl must be(new URL("http://localhost/jmap"))
- testee.downloadUrl must be(new URL("http://localhost/download/{accountId}/{blobId}?type={type}&name={name}"))
- testee.uploadUrl must be(new URL("http://localhost/upload/{accountId}"))
- testee.eventSourceUrl must be(new URL("http://localhost/eventSource?types={types}&closeAfter={closeafter}&ping={ping}"))
- testee.webSocketUrl must be(new URI("ws://localhost/jmap/ws"))
- }
-
- "dynamic jmap prefix should be set when has prefix request and configuration provided is true" in {
- val configuration: Configuration = providedConfiguration()
- configuration.addProperty(DYNAMIC_JMAP_PREFIX_RESOLUTION_ENABLED_PROPERTY, true)
- val jmapRfc8621Configuration: JmapRfc8621Configuration = JmapRfc8621Configuration.from(configuration)
- val testee : JmapUrlEndpointResolver= new JmapUrlEndpointResolver(jmapRfc8621Configuration,
- Some(new URL("http://dynamic.tld/prefix")),
- Some(new URI("ws://dynamic.tld/prefix")))
-
- testee.apiUrl must be(new URL("http://dynamic.tld/prefix/jmap"))
- testee.downloadUrl must be(new URL("http://dynamic.tld/prefix/download/{accountId}/{blobId}?type={type}&name={name}"))
- testee.uploadUrl must be(new URL("http://dynamic.tld/prefix/upload/{accountId}"))
- testee.eventSourceUrl must be(new URL("http://dynamic.tld/prefix/eventSource?types={types}&closeAfter={closeafter}&ping={ping}"))
- testee.webSocketUrl must be(new URI("ws://dynamic.tld/prefix/jmap/ws"))
- }
-
- "dynamic jmap prefix should NOT be set when has prefix request and configuration provided is false" in {
- val configuration: Configuration = providedConfiguration()
- configuration.addProperty(DYNAMIC_JMAP_PREFIX_RESOLUTION_ENABLED_PROPERTY, false)
- val jmapRfc8621Configuration: JmapRfc8621Configuration = JmapRfc8621Configuration.from(configuration)
- val testee : JmapUrlEndpointResolver= new JmapUrlEndpointResolver(jmapRfc8621Configuration,
- Some(new URL("http://dynamic.tld/prefix")),
- Some(new URI("ws://dynamic.tld/prefix")))
-
- testee.apiUrl must be(new URL("http://random-domain.com/jmap"))
- testee.downloadUrl must be(new URL("http://random-domain.com/download/{accountId}/{blobId}?type={type}&name={name}"))
- testee.uploadUrl must be(new URL("http://random-domain.com/upload/{accountId}"))
- testee.eventSourceUrl must be(new URL("http://random-domain.com/eventSource?types={types}&closeAfter={closeafter}&ping={ping}"))
- testee.webSocketUrl must be(new URI("ws://random-domain.com/jmap/ws"))
- }
-
- "dynamic jmap prefix should NOT be set when has prefix request and no configuration provided" in {
- val configuration: Configuration = providedConfiguration()
- val jmapRfc8621Configuration: JmapRfc8621Configuration = JmapRfc8621Configuration.from(configuration)
- val testee : JmapUrlEndpointResolver= new JmapUrlEndpointResolver(jmapRfc8621Configuration,
- Some(new URL("http://dynamic.tld/prefix")),
- Some(new URI("ws://dynamic.tld/prefix")))
-
- testee.apiUrl must be(new URL("http://random-domain.com/jmap"))
- testee.downloadUrl must be(new URL("http://random-domain.com/download/{accountId}/{blobId}?type={type}&name={name}"))
- testee.uploadUrl must be(new URL("http://random-domain.com/upload/{accountId}"))
- testee.eventSourceUrl must be(new URL("http://random-domain.com/eventSource?types={types}&closeAfter={closeafter}&ping={ping}"))
- testee.webSocketUrl must be(new URI("ws://random-domain.com/jmap/ws"))
- }
-
- "dynamic jmap prefix should NOT be set when has not prefix request" in {
- val configuration: Configuration = providedConfiguration()
- configuration.addProperty(DYNAMIC_JMAP_PREFIX_RESOLUTION_ENABLED_PROPERTY, true)
-
- val jmapRfc8621Configuration: JmapRfc8621Configuration = JmapRfc8621Configuration.from(configuration)
- val testee : JmapUrlEndpointResolver= new JmapUrlEndpointResolver(jmapRfc8621Configuration, None, None)
-
- testee.apiUrl must be(new URL("http://random-domain.com/jmap"))
- testee.downloadUrl must be(new URL("http://random-domain.com/download/{accountId}/{blobId}?type={type}&name={name}"))
- testee.uploadUrl must be(new URL("http://random-domain.com/upload/{accountId}"))
- testee.eventSourceUrl must be(new URL("http://random-domain.com/eventSource?types={types}&closeAfter={closeafter}&ping={ping}"))
- testee.webSocketUrl must be(new URI("ws://random-domain.com/jmap/ws"))
}
}
}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
index 84c7d47..4586488 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
@@ -78,7 +78,7 @@ object JMAPApiRoutesTest {
private val userProvisionner: UserProvisioning = new UserProvisioning(usersRepository, new RecordingMetricFactory)
private val JMAP_METHODS: Set[Method] = Set(new CoreEchoMethod)
- private val JMAP_API_ROUTE: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, new JMAPApi(JMAP_METHODS, DefaultCapabilities.supported(JmapRfc8621Configuration("http://127.0.0.1", "ws://127.0.0.1")).capabilities.toSet))
+ private val JMAP_API_ROUTE: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, new JMAPApi(JMAP_METHODS, DefaultCapabilities.supported(JmapRfc8621Configuration("http://127.0.0.1", "ws://127.0.0.1")).map(_.id())))
private val ROUTES_HANDLER: ImmutableSet[JMAPRoutesHandler] = ImmutableSet.of(new JMAPRoutesHandler(Version.RFC8621, JMAP_API_ROUTE))
private val userBase64String: String = Base64.getEncoder.encodeToString("user1:password".getBytes(StandardCharsets.UTF_8))
@@ -442,7 +442,7 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
when(mockCoreEchoMethod.requiredCapabilities).thenReturn(Set(JMAP_CORE))
val methods: Set[Method] = Set(mockCoreEchoMethod)
- val apiRoute: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, new JMAPApi(methods, DefaultCapabilities.supported(JmapRfc8621Configuration("http://127.0.0.1", "ws://127.0.0.1")).capabilities.toSet))
+ val apiRoute: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, new JMAPApi(methods, DefaultCapabilities.supported(JmapRfc8621Configuration("http://127.0.0.1", "ws://127.0.0.1")).map(_.id())))
val routesHandler: ImmutableSet[JMAPRoutesHandler] = ImmutableSet.of(new JMAPRoutesHandler(Version.RFC8621, apiRoute))
val versionParser: VersionParser = new VersionParser(SUPPORTED_VERSIONS, JMAPConfiguration.DEFAULT)
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionRoutesTest.scala
index 1162fdc..0061236 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionRoutesTest.scala
@@ -66,8 +66,9 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
.thenReturn(Mono.just(mockedSession))
val sessionRoutes = new SessionRoutes(
- sessionSupplier = new SessionSupplier(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION, DefaultCapabilities.supported(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION).capabilities),
- authenticator = mockedAuthFilter)
+ sessionSupplier = new SessionSupplier(DefaultCapabilities.supported(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION)),
+ authenticator = mockedAuthFilter,
+ jmapRfc8621Configuration = JmapRfc8621Configuration.LOCALHOST_CONFIGURATION)
jmapServer = new JMAPServer(
TEST_CONFIGURATION,
Set(new JMAPRoutesHandler(Version.RFC8621, sessionRoutes)).asJava,
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionSupplierTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionSupplierTest.scala
index 0b0eed7..4c463b3 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionSupplierTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionSupplierTest.scala
@@ -33,13 +33,13 @@ class SessionSupplierTest extends AnyWordSpec with Matchers {
"generate" should {
"return correct username" in {
- new SessionSupplier(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION, DefaultCapabilities.supported(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION).capabilities)
- .generate(USERNAME).toOption.get.username should equal(USERNAME)
+ new SessionSupplier(DefaultCapabilities.supported(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION))
+ .generate(USERNAME, JmapRfc8621Configuration.LOCALHOST_CONFIGURATION.urlPrefixes()).toOption.get.username should equal(USERNAME)
}
"return correct account" which {
- val accounts = new SessionSupplier(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION, DefaultCapabilities.supported(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION).capabilities)
- .generate(USERNAME).toOption.get.accounts
+ val accounts = new SessionSupplier(DefaultCapabilities.supported(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION))
+ .generate(USERNAME, JmapRfc8621Configuration.LOCALHOST_CONFIGURATION.urlPrefixes()).toOption.get.accounts
"has size" in {
accounts should have size 1
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org