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:12 UTC
[james-project] 06/12: JAMES-3491 Custom extensions should be
advertised in the JMAP 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 ff7f6d1953cc7fe9a19b3bb6aa8f59a675e22e13
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Jan 28 12:51:29 2021 +0700
JAMES-3491 Custom extensions should be advertised in the JMAP session
---
.../org/apache/james/jmap/draft/JMAPModule.java | 5 +
.../rfc8621/contract/CustomMethodContract.scala | 118 +++++++++++++++++++--
.../org/apache/james/jmap/core/Capabilities.scala | 10 +-
.../james/jmap/json/ResponseSerializer.scala | 5 +-
.../apache/james/jmap/routes/SessionSupplier.scala | 17 ++-
.../james/jmap/json/SessionSerializationTest.scala | 2 +-
.../james/jmap/routes/SessionRoutesTest.scala | 4 +-
.../james/jmap/routes/SessionSupplierTest.scala | 8 +-
8 files changed, 144 insertions(+), 25 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 7185537..f585569 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
@@ -162,6 +162,11 @@ public class JMAPModule extends AbstractModule {
return DefaultCapabilities.coreCapability(configuration.maxUploadSize());
}
+ @ProvidesIntoSet
+ Capability webSocketCapability(JmapRfc8621Configuration configuration) {
+ return DefaultCapabilities.webSocketCapability(configuration.webSocketUrl());
+ }
+
@Provides
@Singleton
JMAPConfiguration provideConfiguration(PropertiesProvider propertiesProvider) throws ConfigurationException {
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 87ca8d1..48b8b8b 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
@@ -20,7 +20,7 @@
package org.apache.james.jmap.rfc8621.contract
import com.google.inject.AbstractModule
-import com.google.inject.multibindings.{Multibinder, ProvidesIntoSet}
+import com.google.inject.multibindings.Multibinder
import eu.timepit.refined.auto._
import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
import io.restassured.RestAssured._
@@ -44,20 +44,107 @@ import reactor.core.scala.publisher.SMono
object CustomMethodContract {
val CUSTOM: CapabilityIdentifier = "urn:apache:james:params:jmap:custom"
+
+ private val expected_session_object: String =
+ s"""{
+ | "capabilities" : {
+ | "urn:ietf:params:jmap:submission": {
+ | "maxDelayedSend": 0,
+ | "submissionExtensions": []
+ | },
+ | "urn:ietf:params:jmap:core" : {
+ | "maxSizeUpload" : 20971520,
+ | "maxConcurrentUpload" : 4,
+ | "maxSizeRequest" : 10000000,
+ | "maxConcurrentRequests" : 4,
+ | "maxCallsInRequest" : 16,
+ | "maxObjectsInGet" : 500,
+ | "maxObjectsInSet" : 500,
+ | "collationAlgorithms" : [ "i;unicode-casemap" ]
+ | },
+ | "urn:ietf:params:jmap:mail" : {
+ | "maxMailboxesPerEmail" : 10000000,
+ | "maxMailboxDepth" : null,
+ | "maxSizeMailboxName" : 200,
+ | "maxSizeAttachmentsPerEmail" : 20000000,
+ | "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": {},
+ | "$CUSTOM": {},
+ | "urn:apache:james:params:jmap:mail:shares": {},
+ | "urn:ietf:params:jmap:vacationresponse":{}
+ | },
+ | "accounts" : {
+ | "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6" : {
+ | "name" : "bob@domain.tld",
+ | "isPersonal" : true,
+ | "isReadOnly" : false,
+ | "accountCapabilities" : {
+ | "urn:ietf:params:jmap:submission": {
+ | "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,
+ | "maxSizeRequest" : 10000000,
+ | "maxConcurrentRequests" : 4,
+ | "maxCallsInRequest" : 16,
+ | "maxObjectsInGet" : 500,
+ | "maxObjectsInSet" : 500,
+ | "collationAlgorithms" : [ "i;unicode-casemap" ]
+ | },
+ | "urn:ietf:params:jmap:mail" : {
+ | "maxMailboxesPerEmail" : 10000000,
+ | "maxMailboxDepth" : null,
+ | "maxSizeMailboxName" : 200,
+ | "maxSizeAttachmentsPerEmail" : 20000000,
+ | "emailQuerySortOptions" : ["receivedAt", "sentAt"],
+ | "mayCreateTopLevelMailbox" : true
+ | },
+ | "urn:apache:james:params:jmap:mail:quota": {},
+ | "urn:apache:james:params:jmap:mail:shares": {},
+ | "$CUSTOM": {},
+ | "urn:ietf:params:jmap:vacationresponse":{}
+ | }
+ | }
+ | },
+ | "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",
+ | "urn:apache:james:params:jmap:mail:shares": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "$CUSTOM": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "urn:ietf:params:jmap:vacationresponse": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6"
+ | },
+ | "username" : "bob@domain.tld",
+ | "apiUrl" : "http://domain.com/jmap",
+ | "downloadUrl" : "http://domain.com/download/{accountId}/{blobId}/?type={type}&name={name}",
+ | "uploadUrl" : "http://domain.com/upload/{accountId}",
+ | "eventSourceUrl" : "http://domain.com/eventSource",
+ | "state" : "2c9f1b12-b35a-43e6-9af2-0106fb53a943"
+ |}""".stripMargin
}
case class CustomCapabilityProperties() extends CapabilityProperties
case class CustomCapability(properties: CustomCapabilityProperties = CustomCapabilityProperties(), identifier: CapabilityIdentifier = CUSTOM) extends Capability
-class CustomCapabilitiesModule extends AbstractModule {
- @ProvidesIntoSet
- private def capability(): Capability = CustomCapability()
-}
-
class CustomMethodModule extends AbstractModule {
override protected def configure(): Unit = {
- install(new CustomCapabilitiesModule)
+ val supportedCapabilities: Multibinder[Capability] = Multibinder.newSetBinder(binder, classOf[Capability])
+ supportedCapabilities.addBinding.toInstance(CustomCapability())
Multibinder.newSetBinder(binder(), classOf[Method])
.addBinding()
.to(classOf[CustomMethod])
@@ -74,7 +161,6 @@ class CustomMethod extends Method {
}
trait CustomMethodContract {
-
@BeforeEach
def setUp(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[DataProbeImpl])
@@ -88,6 +174,22 @@ trait CustomMethodContract {
}
@Test
+ def getShouldReturnCorrectSession(): Unit = {
+ val sessionJson: String = `given`()
+ .when()
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .get("/session")
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract()
+ .body()
+ .asString()
+
+ assertThatJson(sessionJson).isEqualTo(CustomMethodContract.expected_session_object)
+ }
+
+ @Test
def customMethodShouldRespondOKWithRFC8621VersionAndSupportedMethod(): Unit = {
val response = `given`()
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
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 689c9ca..d3f7be4 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
@@ -51,7 +51,7 @@ object DefaultCapabilities {
val VACATION_RESPONSE_CAPABILITY = VacationResponseCapability()
val SUBMISSION_CAPABILITY = SubmissionCapability()
- def supported(configuration: JmapRfc8621Configuration): Capabilities = Capabilities(
+ def supported(configuration: JmapRfc8621Configuration): Capabilities = Capabilities.of(
coreCapability(configuration.maxUploadSize),
MAIL_CAPABILITY,
QUOTA_CAPABILITY,
@@ -61,8 +61,10 @@ object DefaultCapabilities {
webSocketCapability(configuration.webSocketUrl))
}
-case class Capabilities(capabilities: Capability*) {
- def toSet: Set[Capability] = capabilities.toSet
+object Capabilities {
+ def of(capabilities: Capability*): Capabilities = Capabilities(capabilities.toSet)
+}
- def ids: Set[CapabilityIdentifier] = toSet.map(_.identifier())
+case class Capabilities(capabilities: Set[Capability]) {
+ def ids: Set[CapabilityIdentifier] = capabilities.map(_.identifier())
}
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 f9fcee5..ebf7eb4 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
@@ -118,12 +118,13 @@ object ResponseSerializer {
jsObject.+(capability.identifier.value, submissionPropertiesWrites.writes(capability.properties))
case capability: WebSocketCapability =>
jsObject.+(capability.identifier.value, webSocketPropertiesWrites.writes(capability.properties))
- case _ => jsObject
+ case _ =>
+ jsObject.+(capability.identifier.value, JsObject(Map[String, JsValue]()))
}
})
}
- private implicit val capabilitiesWrites: Writes[Capabilities] = capabilities => setCapabilityWrites.writes(capabilities.toSet)
+ private implicit val capabilitiesWrites: Writes[Capabilities] = capabilities => setCapabilityWrites.writes(capabilities.capabilities)
private implicit val identifierMapWrite: Writes[Map[CapabilityIdentifier, AccountId]] =
mapWrites[CapabilityIdentifier, AccountId](_.value, accountIdWrites)
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 bb9270f..b9beac4 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
@@ -22,13 +22,20 @@ package org.apache.james.jmap.routes
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, DefaultCapabilities, IsPersonal, IsReadOnly, JmapRfc8621Configuration, Session}
+import org.apache.james.jmap.core.{Account, AccountId, Capabilities, Capability, IsPersonal, IsReadOnly, JmapRfc8621Configuration, Session}
+
+import scala.jdk.CollectionConverters._
+
+class SessionSupplier(val configuration: JmapRfc8621Configuration, defaultCapabilities: Set[Capability]) {
+ @Inject
+ def this(configuration: JmapRfc8621Configuration, defaultCapabilities: java.util.Set[Capability]) {
+ this(configuration, defaultCapabilities.asScala.toSet)
+ }
-class SessionSupplier @Inject() (val configuration: JmapRfc8621Configuration) {
def generate(username: Username): Either[IllegalArgumentException, Session] =
accounts(username)
.map(account => Session(
- DefaultCapabilities.supported(configuration),
+ Capabilities(defaultCapabilities),
List(account),
primaryAccounts(account.accountId),
username,
@@ -38,10 +45,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(configuration).toSet)
+ Account.from(username, IsPersonal(true), IsReadOnly(false), defaultCapabilities)
private def primaryAccounts(accountId: AccountId): Map[CapabilityIdentifier, AccountId] =
- DefaultCapabilities.supported(configuration).toSet
+ defaultCapabilities
.map(capability => (capability.identifier(), accountId))
.toMap
}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
index 861ce63..9a28d4e 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
@@ -76,7 +76,7 @@ object SessionSerializationTest {
emailQuerySortOptions = EMAIL_QUERY_SORT_OPTIONS,
mayCreateTopLevelMailbox = MAY_CREATE_TOP_LEVEL_MAILBOX))
- private val CAPABILITIES = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY, QuotaCapability(), SharesCapability(), VacationResponseCapability())
+ private val CAPABILITIES = Capabilities.of(CORE_CAPABILITY, MAIL_CAPABILITY, QuotaCapability(), SharesCapability(), VacationResponseCapability())
private val IS_PERSONAL : IsPersonal = IsPersonal(true)
private val IS_NOT_PERSONAL : IsPersonal = IsPersonal(false)
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 9e903e2..e857584 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
@@ -30,9 +30,9 @@ import io.restassured.http.ContentType
import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
import org.apache.http.HttpStatus
import org.apache.james.core.Username
-import org.apache.james.jmap.core.JmapRfc8621Configuration
import org.apache.james.jmap.core.JmapRfc8621Configuration.LOCALHOST_URL_PREFIX
import org.apache.james.jmap.core.State.INSTANCE
+import org.apache.james.jmap.core.{DefaultCapabilities, JmapRfc8621Configuration}
import org.apache.james.jmap.http.Authenticator
import org.apache.james.jmap.routes.SessionRoutesTest.{BOB, TEST_CONFIGURATION}
import org.apache.james.jmap.{JMAPConfiguration, JMAPRoutesHandler, JMAPServer, Version, VersionParser}
@@ -66,7 +66,7 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
.thenReturn(Mono.just(mockedSession))
val sessionRoutes = new SessionRoutes(
- sessionSupplier = new SessionSupplier(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION),
+ sessionSupplier = new SessionSupplier(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION, DefaultCapabilities.supported(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION).capabilities),
authenticator = mockedAuthFilter)
jmapServer = new JMAPServer(
TEST_CONFIGURATION,
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 83b7790..0b0eed7 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
@@ -20,7 +20,7 @@
package org.apache.james.jmap.routes
import org.apache.james.core.Username
-import org.apache.james.jmap.core.JmapRfc8621Configuration
+import org.apache.james.jmap.core.{DefaultCapabilities, JmapRfc8621Configuration}
import org.apache.james.jmap.routes.SessionSupplierTest.USERNAME
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
@@ -33,11 +33,13 @@ class SessionSupplierTest extends AnyWordSpec with Matchers {
"generate" should {
"return correct username" in {
- new SessionSupplier(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION).generate(USERNAME).toOption.get.username should equal(USERNAME)
+ new SessionSupplier(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION, DefaultCapabilities.supported(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION).capabilities)
+ .generate(USERNAME).toOption.get.username should equal(USERNAME)
}
"return correct account" which {
- val accounts = new SessionSupplier(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION).generate(USERNAME).toOption.get.accounts
+ val accounts = new SessionSupplier(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION, DefaultCapabilities.supported(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION).capabilities)
+ .generate(USERNAME).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