You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2021/02/03 02:51:11 UTC
[james-project] 05/12: JAMES-3491 Advertise the JMAP websocket
capability in the session
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 647e2e8843b83dc21ec1908a599ee71f45458e84
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Jan 28 12:29:50 2021 +0700
JAMES-3491 Advertise the JMAP websocket capability in the session
---
.../jmap/rfc8621/contract/SessionRoutesContract.scala | 9 +++++++++
.../scala/org/apache/james/jmap/core/Capabilities.scala | 17 +++++++++++------
.../scala/org/apache/james/jmap/core/Capability.scala | 11 ++++++++++-
.../james/jmap/core/JmapRfc8621Configuration.scala | 1 +
.../org/apache/james/jmap/json/ResponseSerializer.scala | 5 +++++
.../org/apache/james/jmap/routes/SessionSupplier.scala | 8 +++-----
.../james/jmap/json/MailboxGetSerializationTest.scala | 7 +++++--
.../apache/james/jmap/routes/JMAPApiRoutesTest.scala | 4 ++--
.../apache/james/jmap/routes/SessionRoutesTest.scala | 9 +++++++++
9 files changed, 55 insertions(+), 16 deletions(-)
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/SessionRoutesContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
index 9ba8df0..7e1abc8 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
@@ -63,6 +63,10 @@ object SessionRoutesContract {
| "emailQuerySortOptions" : ["receivedAt", "sentAt"],
| "mayCreateTopLevelMailbox" : true
| },
+ | "urn:ietf:params:jmap:websocket": {
+ | "supportsPush": false,
+ | "url": "http://domain.com/jmap/ws"
+ | },
| "urn:apache:james:params:jmap:mail:quota": {},
| "urn:apache:james:params:jmap:mail:shares": {},
| "urn:ietf:params:jmap:vacationresponse":{}
@@ -77,6 +81,10 @@ object SessionRoutesContract {
| "maxDelayedSend": 0,
| "submissionExtensions": []
| },
+ | "urn:ietf:params:jmap:websocket": {
+ | "supportsPush": false,
+ | "url": "http://domain.com/jmap/ws"
+ | },
| "urn:ietf:params:jmap:core" : {
| "maxSizeUpload" : 20971520,
| "maxConcurrentUpload" : 4,
@@ -103,6 +111,7 @@ object SessionRoutesContract {
| },
| "primaryAccounts" : {
| "urn:ietf:params:jmap:submission": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "urn:ietf:params:jmap:websocket": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "urn:ietf:params:jmap:core" : "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "urn:ietf:params:jmap:mail" : "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "urn:apache:james:params:jmap:mail:quota": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
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 af4ad66..689c9ca 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
@@ -18,8 +18,10 @@
****************************************************************/
package org.apache.james.jmap.core
+import java.net.URL
+
import eu.timepit.refined.auto._
-import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, EMAIL_SUBMISSION, JAMES_QUOTA, JAMES_SHARES, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE}
+import org.apache.james.jmap.core.CapabilityIdentifier.CapabilityIdentifier
object DefaultCapabilities {
def coreCapability(maxUploadSize: MaxSizeUpload) = CoreCapability(
@@ -32,6 +34,10 @@ object DefaultCapabilities {
MaxObjectsInGet(500L),
MaxObjectsInSet(500L),
collationAlgorithms = List("i;unicode-casemap")))
+
+ def webSocketCapability(url: URL) = WebSocketCapability(
+ properties = WebSocketCapabilityProperties(SupportsPush(false), url))
+
val MAIL_CAPABILITY = MailCapability(
properties = MailCapabilityProperties(
MaxMailboxesPerEmail(Some(10_000_000L)),
@@ -45,15 +51,14 @@ object DefaultCapabilities {
val VACATION_RESPONSE_CAPABILITY = VacationResponseCapability()
val SUBMISSION_CAPABILITY = SubmissionCapability()
- val SUPPORTED_CAPABILITY_IDENTIFIERS: Set[CapabilityIdentifier] =
- Set(JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE, JAMES_SHARES, JAMES_QUOTA, EMAIL_SUBMISSION)
-
- def supported(maxUploadSize: MaxSizeUpload): Capabilities = Capabilities(coreCapability(maxUploadSize),
+ def supported(configuration: JmapRfc8621Configuration): Capabilities = Capabilities(
+ coreCapability(configuration.maxUploadSize),
MAIL_CAPABILITY,
QUOTA_CAPABILITY,
SHARES_CAPABILITY,
VACATION_RESPONSE_CAPABILITY,
- SUBMISSION_CAPABILITY)
+ SUBMISSION_CAPABILITY,
+ webSocketCapability(configuration.webSocketUrl))
}
case class Capabilities(capabilities: Capability*) {
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 94b0c50..d3582bd 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,12 +19,14 @@
package org.apache.james.jmap.core
+import java.net.URL
+
import eu.timepit.refined
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.string.Uri
-import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, EMAIL_SUBMISSION, JAMES_QUOTA, JAMES_SHARES, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE}
+import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, EMAIL_SUBMISSION, JAMES_QUOTA, JAMES_SHARES, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE, JMAP_WEBSOCKET}
import org.apache.james.jmap.core.CoreCapabilityProperties.CollationAlgorithm
import org.apache.james.jmap.core.MailCapability.EmailQuerySortOption
import org.apache.james.jmap.core.UnsignedInt.{UnsignedInt, UnsignedIntConstraint}
@@ -41,6 +43,7 @@ object CapabilityIdentifier {
val JMAP_MAIL: CapabilityIdentifier = "urn:ietf:params:jmap:mail"
val JMAP_VACATION_RESPONSE: CapabilityIdentifier = "urn:ietf:params:jmap:vacationresponse"
val EMAIL_SUBMISSION: CapabilityIdentifier = "urn:ietf:params:jmap:submission"
+ val JMAP_WEBSOCKET: CapabilityIdentifier = "urn:ietf:params:jmap:websocket"
val JAMES_QUOTA: CapabilityIdentifier = "urn:apache:james:params:jmap:mail:quota"
val JAMES_SHARES: CapabilityIdentifier = "urn:apache:james:params:jmap:mail:shares"
}
@@ -55,6 +58,8 @@ trait Capability {
final case class CoreCapability(properties: CoreCapabilityProperties,
identifier: CapabilityIdentifier = JMAP_CORE) extends Capability
+case class WebSocketCapability(properties: WebSocketCapabilityProperties, identifier: CapabilityIdentifier = JMAP_WEBSOCKET) extends Capability
+
object MaxSizeUpload {
def of(size: Size): Try[MaxSizeUpload] = refined.refineV[UnsignedIntConstraint](size.asBytes()) match {
case Right(value) => Success(MaxSizeUpload(value))
@@ -83,6 +88,10 @@ final case class CoreCapabilityProperties(maxSizeUpload: MaxSizeUpload,
maxObjectsInSet: MaxObjectsInSet,
collationAlgorithms: List[CollationAlgorithm]) extends CapabilityProperties
+final case class WebSocketCapabilityProperties(supportsPush: SupportsPush,
+ url: URL) extends CapabilityProperties
+
+final case class SupportsPush(value: Boolean) extends AnyVal
final case class MaxDelayedSend(value: Int) extends AnyVal
final case class EhloName(value: String) extends AnyVal
final case class EhloArgs(value: String) extends AnyVal
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 9ce796a..43f8420 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
@@ -48,4 +48,5 @@ case class JmapRfc8621Configuration(urlPrefixString: String, maxUploadSize: MaxS
val downloadUrl: URL = new URL(urlPrefixString + "/download/{accountId}/{blobId}/?type={type}&name={name}")
val uploadUrl: URL = new URL(s"$urlPrefixString/upload/{accountId}")
val eventSourceUrl: URL = new URL(s"$urlPrefixString/eventSource")
+ val webSocketUrl: URL = new URL(s"$urlPrefixString/jmap/ws")
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
index 02ca667..f9fcee5 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
@@ -91,11 +91,14 @@ object ResponseSerializer {
private implicit val maxDelayedSendWrites: Writes[MaxDelayedSend] = Json.valueWrites[MaxDelayedSend]
private implicit val ehloNameWrites: Writes[EhloName] = Json.valueWrites[EhloName]
private implicit val ehloArgsWrites: Writes[EhloArgs] = Json.valueWrites[EhloArgs]
+ private implicit val supportsPushWrites: Writes[SupportsPush] = Json.valueWrites[SupportsPush]
private implicit val submissionPropertiesWrites: Writes[SubmissionProperties] = Json.writes[SubmissionProperties]
+ private implicit val webSocketPropertiesWrites: Writes[WebSocketCapabilityProperties] = Json.writes[WebSocketCapabilityProperties]
private implicit val quotaCapabilityWrites: Writes[QuotaCapabilityProperties] = OWrites[QuotaCapabilityProperties](_ => Json.obj())
private implicit val sharesCapabilityWrites: Writes[SharesCapabilityProperties] = OWrites[SharesCapabilityProperties](_ => Json.obj())
private implicit val vacationResponseCapabilityWrites: Writes[VacationResponseCapabilityProperties] = OWrites[VacationResponseCapabilityProperties](_ => Json.obj())
private implicit val submissionCapabilityWrites: Writes[SubmissionCapability] = OWrites[SubmissionCapability](_ => Json.obj())
+ private implicit val webSocketCapabilityWrites: Writes[WebSocketCapability] = OWrites[WebSocketCapability](_ => Json.obj())
private implicit val setCapabilityWrites: Writes[Set[_ <: Capability]] =
(set: Set[_ <: Capability]) => {
@@ -113,6 +116,8 @@ object ResponseSerializer {
jsObject.+(capability.identifier.value, vacationResponseCapabilityWrites.writes(capability.properties))
case capability: SubmissionCapability =>
jsObject.+(capability.identifier.value, submissionPropertiesWrites.writes(capability.properties))
+ case capability: WebSocketCapability =>
+ jsObject.+(capability.identifier.value, webSocketPropertiesWrites.writes(capability.properties))
case _ => jsObject
}
})
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 c9d631c..bb9270f 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
@@ -25,12 +25,10 @@ import org.apache.james.jmap.core.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.core.{Account, AccountId, DefaultCapabilities, IsPersonal, IsReadOnly, JmapRfc8621Configuration, Session}
class SessionSupplier @Inject() (val configuration: JmapRfc8621Configuration) {
- private val maxSizeUpload = configuration.maxUploadSize
-
def generate(username: Username): Either[IllegalArgumentException, Session] =
accounts(username)
.map(account => Session(
- DefaultCapabilities.supported(maxSizeUpload),
+ DefaultCapabilities.supported(configuration),
List(account),
primaryAccounts(account.accountId),
username,
@@ -40,10 +38,10 @@ class SessionSupplier @Inject() (val configuration: JmapRfc8621Configuration) {
eventSourceUrl = configuration.eventSourceUrl))
private def accounts(username: Username): Either[IllegalArgumentException, Account] =
- Account.from(username, IsPersonal(true), IsReadOnly(false), DefaultCapabilities.supported(maxSizeUpload).toSet)
+ Account.from(username, IsPersonal(true), IsReadOnly(false), DefaultCapabilities.supported(configuration).toSet)
private def primaryAccounts(accountId: AccountId): Map[CapabilityIdentifier, AccountId] =
- DefaultCapabilities.supported(maxSizeUpload).toSet
+ DefaultCapabilities.supported(configuration).toSet
.map(capability => (capability.identifier(), accountId))
.toMap
}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala
index 5eb339a..62c4e92 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala
@@ -21,8 +21,9 @@ package org.apache.james.jmap.json
import eu.timepit.refined.auto._
import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
+import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, EMAIL_SUBMISSION, JAMES_QUOTA, JAMES_SHARES, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE}
import org.apache.james.jmap.core.State.INSTANCE
-import org.apache.james.jmap.core.{AccountId, DefaultCapabilities, Properties}
+import org.apache.james.jmap.core.{AccountId, Properties}
import org.apache.james.jmap.json.Fixture._
import org.apache.james.jmap.json.MailboxGetSerializationTest._
import org.apache.james.jmap.json.MailboxSerializationTest.MAILBOX
@@ -144,6 +145,8 @@ class MailboxGetSerializationTest extends AnyWordSpec with Matchers {
"Serialize MailboxGetResponse" should {
"succeed" in {
+ val supportedCapabilityIdentifiers: Set[CapabilityIdentifier] =
+ Set(JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE, JAMES_SHARES, JAMES_QUOTA, EMAIL_SUBMISSION)
val actualValue: MailboxGetResponse = MailboxGetResponse(
accountId = ACCOUNT_ID,
state = INSTANCE,
@@ -197,7 +200,7 @@ class MailboxGetSerializationTest extends AnyWordSpec with Matchers {
|}
|""".stripMargin
- assertThatJson(Json.stringify(SERIALIZER.serialize(actualValue, Mailbox.allProperties, DefaultCapabilities.SUPPORTED_CAPABILITY_IDENTIFIERS))).isEqualTo(expectedJson)
+ assertThatJson(Json.stringify(SERIALIZER.serialize(actualValue, Mailbox.allProperties, supportedCapabilityIdentifiers))).isEqualTo(expectedJson)
}
}
}
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 61faf1b..ab43abe 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.UPLOAD_LIMIT_30_MB).capabilities.toSet))
+ private val JMAP_API_ROUTE: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, new JMAPApi(JMAP_METHODS, DefaultCapabilities.supported(JmapRfc8621Configuration("http://127.0.0.1")).capabilities.toSet))
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.UPLOAD_LIMIT_30_MB).capabilities.toSet))
+ val apiRoute: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, new JMAPApi(methods, DefaultCapabilities.supported(JmapRfc8621Configuration("http://127.0.0.1")).capabilities.toSet))
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 2f21d81..9e903e2 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
@@ -142,6 +142,10 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
| "emailQuerySortOptions" : ["receivedAt", "sentAt"],
| "mayCreateTopLevelMailbox" : true
| },
+ | "urn:ietf:params:jmap:websocket": {
+ | "supportsPush": false,
+ | "url": "http://localhost/jmap/ws"
+ | },
| "urn:apache:james:params:jmap:mail:quota": {},
| "urn:apache:james:params:jmap:mail:shares": {},
| "urn:ietf:params:jmap:vacationresponse":{}
@@ -156,6 +160,10 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
| "maxDelayedSend": 0,
| "submissionExtensions": []
| },
+ | "urn:ietf:params:jmap:websocket": {
+ | "supportsPush": false,
+ | "url": "http://localhost/jmap/ws"
+ | },
| "urn:ietf:params:jmap:core" : {
| "maxSizeUpload" : 31457280,
| "maxConcurrentUpload" : 4,
@@ -182,6 +190,7 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
| },
| "primaryAccounts" : {
| "urn:ietf:params:jmap:submission": "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
+ | "urn:ietf:params:jmap:websocket": "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
| "urn:ietf:params:jmap:core" : "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
| "urn:ietf:params:jmap:mail" : "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
| "urn:apache:james:params:jmap:mail:quota": "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org