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