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 <[email protected]> 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" : "[email protected]", + | "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" : "[email protected]", + | "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: [email protected] For additional commands, e-mail: [email protected]
