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 2020/10/26 05:20:19 UTC

[james-project] 06/08: JAMES-3432 Add test case downloadShouldRejectWhenDownloadFromOther

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 8335aa3962e04210147f4fe043fe06853c982e32
Author: duc91 <vd...@linagora.com>
AuthorDate: Fri Oct 23 15:03:32 2020 +0700

    JAMES-3432 Add test case downloadShouldRejectWhenDownloadFromOther
---
 .../james/jmap/rfc8621/RFC8621MethodsModule.java   |  2 +-
 .../jmap/rfc8621/contract/DownloadContract.scala   | 20 ++++-
 .../jmap/rfc8621/contract/UploadContract.scala     | 87 +++++++++++++++-------
 .../jmap/rfc8621/memory/MemoryUploadContract.java  | 38 ++++++++++
 .../apache/james/jmap/json/UploadSerializer.scala  | 18 +++++
 .../apache/james/jmap/routes/DownloadRoutes.scala  | 57 ++++++++++----
 .../apache/james/jmap/routes/UploadRoutes.scala    | 55 ++++++++------
 7 files changed, 212 insertions(+), 65 deletions(-)

diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
index 13eeaed..d8c2771 100644
--- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
+++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
@@ -47,8 +47,8 @@ import org.apache.james.jmap.method.VacationResponseSetMethod;
 import org.apache.james.jmap.method.ZoneIdProvider;
 import org.apache.james.jmap.model.JmapRfc8621Configuration;
 import org.apache.james.jmap.routes.DownloadRoutes;
-import org.apache.james.jmap.routes.UploadRoutes;
 import org.apache.james.jmap.routes.JMAPApiRoutes;
+import org.apache.james.jmap.routes.UploadRoutes;
 import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.utils.PropertiesProvider;
 import org.slf4j.Logger;
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/DownloadContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/DownloadContract.scala
index 5e0e753..20579b9 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/DownloadContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/DownloadContract.scala
@@ -29,7 +29,7 @@ import org.apache.http.HttpStatus.{SC_NOT_FOUND, SC_OK, SC_UNAUTHORIZED}
 import org.apache.james.GuiceJamesServer
 import org.apache.james.jmap.http.UserCredential
 import org.apache.james.jmap.rfc8621.contract.DownloadContract.accountId
-import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ANDRE, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
+import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ALICE_ACCOUNT_ID, ANDRE, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
 import org.apache.james.mailbox.MessageManager.AppendCommand
 import org.apache.james.mailbox.model.MailboxACL.Right
 import org.apache.james.mailbox.model.{MailboxACL, MailboxPath, MessageId}
@@ -153,6 +153,24 @@ trait DownloadContract {
   }
 
   @Test
+  def downloadingInOtherAccountsShouldFail(server: GuiceJamesServer): Unit = {
+    val path = MailboxPath.inbox(BOB)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
+    val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, path, AppendCommand.from(
+        ClassLoader.getSystemResourceAsStream("eml/multipart_simple.eml")))
+      .getMessageId
+
+    `given`
+      .basePath("")
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+    .when
+      .get(s"/download/$ALICE_ACCOUNT_ID/${messageId.serialize}")
+    .`then`
+      .statusCode(SC_UNAUTHORIZED)
+  }
+
+  @Test
   def downloadPartShouldSucceedWhenDelegated(server: GuiceJamesServer): Unit = {
     val path = MailboxPath.inbox(ANDRE)
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
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/UploadContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/UploadContract.scala
index 9605f44..770efcd 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/UploadContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/UploadContract.scala
@@ -1,7 +1,26 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *  http://www.apache.org/licenses/LICENSE-2.0                  *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
 package org.apache.james.jmap.rfc8621.contract
 
 import java.io.{ByteArrayInputStream, InputStream}
 import java.nio.charset.StandardCharsets
+
 import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
 import io.restassured.RestAssured.{`given`, requestSpecification}
 import io.restassured.http.ContentType
@@ -10,11 +29,11 @@ import org.apache.commons.io.IOUtils
 import org.apache.http.HttpStatus.{SC_CREATED, SC_NOT_FOUND, SC_OK, SC_UNAUTHORIZED}
 import org.apache.james.GuiceJamesServer
 import org.apache.james.jmap.http.UserCredential
-import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, BOB, BOB_PASSWORD, DOMAIN, RFC8621_VERSION_HEADER, authScheme, baseRequestSpecBuilder}
+import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ALICE, ALICE_ACCOUNT_ID, ALICE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, RFC8621_VERSION_HEADER, _2_DOT_DOMAIN, authScheme, baseRequestSpecBuilder}
 import org.apache.james.jmap.rfc8621.contract.UploadContract.{BIG_INPUT_STREAM, VALID_INPUT_STREAM}
 import org.apache.james.utils.DataProbeImpl
 import org.assertj.core.api.Assertions.assertThat
-import org.junit.jupiter.api.{BeforeEach, Test}
+import org.junit.jupiter.api.{BeforeEach, Disabled, Test}
 import play.api.libs.json.{JsString, Json}
 
 object UploadContract {
@@ -29,6 +48,8 @@ trait UploadContract {
       .fluent
       .addDomain(DOMAIN.asString)
       .addUser(BOB.asString, BOB_PASSWORD)
+      .addDomain(_2_DOT_DOMAIN.asString())
+      .addUser(ALICE.asString(), ALICE_PASSWORD)
 
     requestSpecification = baseRequestSpecBuilder(server)
       .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
@@ -36,7 +57,7 @@ trait UploadContract {
   }
 
   @Test
-  def shouldUploadFileAndOnlyOwnerCanAccess(): Unit = {
+  def shouldUploadFileAndAllowToDownloadIt(): Unit = {
     val uploadResponse: String = `given`
       .basePath("")
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
@@ -69,63 +90,75 @@ trait UploadContract {
   }
 
   @Test
-  def shouldRejectWhenUploadFileTooBig(): Unit = {
-    val response: String = `given`
+  def bobShouldNotBeAllowedToUploadInAliceAccount(): Unit = {
+    `given`
       .basePath("")
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .contentType(ContentType.BINARY)
-      .body(BIG_INPUT_STREAM)
+      .body(VALID_INPUT_STREAM)
+    .when
+      .post(s"/upload/$ALICE_ACCOUNT_ID/")
+    .`then`
+      .statusCode(SC_UNAUTHORIZED)
+  }
+
+  @Test
+  def aliceShouldNotAccessOrDownloadFileUploadedByBob(): Unit = {
+    val uploadResponse: String = `given`
+      .basePath("")
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(VALID_INPUT_STREAM)
     .when
       .post(s"/upload/$ACCOUNT_ID/")
     .`then`
-      .statusCode(SC_OK)
+      .statusCode(SC_CREATED)
       .extract
       .body
       .asString
 
-    // fixme: dont know we limit size or not?
-    assertThatJson(response)
-      .isEqualTo("Should be error")
-  }
+    val blobId: String = Json.parse(uploadResponse).\("blobId").get.asInstanceOf[JsString].value
 
-  @Test
-  def uploadShouldRejectWhenUnauthenticated(): Unit = {
     `given`
-      .auth()
-      .none()
+      .auth().basic(ALICE.asString(), ALICE_PASSWORD)
       .basePath("")
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .contentType(ContentType.BINARY)
-      .body(VALID_INPUT_STREAM)
     .when
-      .post(s"/upload/$ACCOUNT_ID/")
+      .get(s"/download/$ALICE_ACCOUNT_ID/$blobId")
     .`then`
       .statusCode(SC_UNAUTHORIZED)
   }
 
   @Test
-  def uploadShouldSucceedButExpiredWhenDownload(): Unit = {
-    val uploadResponse: String = `given`
+  @Disabled("JAMES-1788 Upload size limitation needs to be contributed")
+  def shouldRejectWhenUploadFileTooBig(): Unit = {
+    val response: String = `given`
       .basePath("")
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(VALID_INPUT_STREAM)
+      .contentType(ContentType.BINARY)
+      .body(BIG_INPUT_STREAM)
     .when
       .post(s"/upload/$ACCOUNT_ID/")
     .`then`
-      .statusCode(SC_CREATED)
+      .statusCode(SC_OK)
       .extract
       .body
       .asString
 
-    val blobId: String = Json.parse(uploadResponse).\("blobId").get.asInstanceOf[JsString].value
+    assertThatJson(response)
+      .isEqualTo("Should be error")
+  }
 
-    // fixme: dont know how to delete file with existing attachment api
+  @Test
+  def uploadShouldRejectWhenUnauthenticated(): Unit = {
     `given`
+      .auth()
+      .none()
       .basePath("")
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .contentType(ContentType.BINARY)
+      .body(VALID_INPUT_STREAM)
     .when
-      .get(s"/download/$ACCOUNT_ID/$blobId")
+      .post(s"/upload/$ACCOUNT_ID/")
     .`then`
-      .statusCode(SC_NOT_FOUND)
+      .statusCode(SC_UNAUTHORIZED)
   }
 }
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryUploadContract.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryUploadContract.java
new file mode 100644
index 0000000..9f55acd
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryUploadContract.java
@@ -0,0 +1,38 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.rfc8621.memory;
+
+import static org.apache.james.MemoryJamesServerMain.IN_MEMORY_SERVER_AGGREGATE_MODULE;
+
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.UploadContract;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class MemoryUploadContract implements UploadContract {
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
+        .server(configuration -> GuiceJamesServer.forConfiguration(configuration)
+            .combineWith(IN_MEMORY_SERVER_AGGREGATE_MODULE)
+            .overrideWith(new TestJMAPServerModule()))
+        .build();
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/UploadSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/UploadSerializer.scala
index a7f8b25..3014869 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/UploadSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/UploadSerializer.scala
@@ -1,3 +1,21 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *  http://www.apache.org/licenses/LICENSE-2.0                  *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
 package org.apache.james.jmap.json
 
 import org.apache.james.jmap.mail.BlobId
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
index f7c580e..702fce3 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
@@ -36,8 +36,11 @@ import org.apache.james.jmap.http.Authenticator
 import org.apache.james.jmap.http.rfc8621.InjectionKeys
 import org.apache.james.jmap.mail.Email.Size
 import org.apache.james.jmap.mail.{BlobId, EmailBodyPart, PartId}
+import org.apache.james.jmap.model.Id.Id
+import org.apache.james.jmap.model.{AccountId, Id}
 import org.apache.james.jmap.routes.DownloadRoutes.{BUFFER_SIZE, LOGGER}
 import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
+import org.apache.james.mailbox.exception.AttachmentNotFoundException
 import org.apache.james.mailbox.model.{AttachmentId, AttachmentMetadata, ContentType, FetchGroup, MessageId, MessageResult}
 import org.apache.james.mailbox.{AttachmentManager, MailboxSession, MessageIdManager}
 import org.apache.james.mime4j.codec.EncoderUtil
@@ -133,10 +136,13 @@ class MessageBlobResolver @Inject()(val messageIdFactory: MessageId.Factory,
 class AttachmentBlobResolver @Inject()(val attachmentManager: AttachmentManager) extends BlobResolver {
   override def resolve(blobId: BlobId, mailboxSession: MailboxSession): BlobResolutionResult =
     AttachmentId.from(org.apache.james.mailbox.model.BlobId.fromString(blobId.value.value)) match {
-      case attachmentId: AttachmentId => Applicable(
-        SMono.fromCallable(() => attachmentManager.getAttachment(attachmentId, mailboxSession))
-          .map((attachmentMetadata: AttachmentMetadata) => AttachmentBlob(attachmentMetadata, attachmentManager.load(attachmentMetadata, mailboxSession)))
-      )
+      case attachmentId: AttachmentId =>
+        Try(attachmentManager.getAttachment(attachmentId, mailboxSession)) match {
+          case Success(attachmentMetadata) => Applicable(
+            SMono.fromCallable(() => AttachmentBlob(attachmentMetadata, attachmentManager.load(attachmentMetadata, mailboxSession))))
+          case Failure(_) => Applicable(SMono.raiseError(BlobNotFoundException(blobId)))
+        }
+
       case _ => NonApplicable()
     }
 }
@@ -205,17 +211,7 @@ class DownloadRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val authenticator:
 
   private def get(request: HttpServerRequest, response: HttpServerResponse): Mono[Void] =
     SMono(authenticator.authenticate(request))
-      .flatMap((mailboxSession: MailboxSession) =>
-        SMono.fromTry(BlobId.of(request.param(blobIdParam)))
-          .flatMap(blobResolvers.resolve(_, mailboxSession))
-          .flatMap(blob => downloadBlob(
-            optionalName = queryParam(request, nameParam),
-            response = response,
-            blobContentType = queryParam(request, contentTypeParam)
-              .map(ContentType.of)
-              .getOrElse(blob.contentType),
-            blob = blob))
-          .`then`)
+      .flatMap(mailboxSession => getIfOwner(request, response, mailboxSession))
       .onErrorResume {
         case e: UnauthorizedException => SMono.fromPublisher(handleAuthenticationFailure(response, LOGGER, e)).`then`
         case _: BlobNotFoundException => SMono.fromPublisher(response.status(SC_NOT_FOUND).send).`then`
@@ -227,6 +223,37 @@ class DownloadRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val authenticator:
       .asJava()
       .`then`
 
+  private def get(request: HttpServerRequest, response: HttpServerResponse, mailboxSession: MailboxSession): SMono[Unit] = {
+    SMono.fromTry(BlobId.of(request.param(blobIdParam)))
+      .flatMap(blobResolvers.resolve(_, mailboxSession))
+      .flatMap(blob => downloadBlob(
+        optionalName = queryParam(request, nameParam),
+        response = response,
+        blobContentType = queryParam(request, contentTypeParam)
+          .map(ContentType.of)
+          .getOrElse(blob.contentType),
+        blob = blob)
+        .`then`())
+  }
+
+  private def getIfOwner(request: HttpServerRequest, response: HttpServerResponse, mailboxSession: MailboxSession): SMono[Unit] = {
+    Id.validate(request.param(accountIdParam)) match {
+      case Right(id: Id) => {
+        val targetAccountId: AccountId = AccountId(id)
+        AccountId.from(mailboxSession.getUser).map(accountId => accountId.equals(targetAccountId))
+          .fold[SMono[Unit]](
+            e => SMono.raiseError(e),
+            value => if (value) {
+              get(request, response, mailboxSession)
+            } else {
+              SMono.raiseError(new UnauthorizedException("You cannot upload to others"))
+            })
+      }
+
+      case Left(throwable: Throwable) => SMono.raiseError(throwable)
+    }
+  }
+
   private def downloadBlob(optionalName: Option[String],
                            response: HttpServerResponse,
                            blobContentType: ContentType,
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/UploadRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/UploadRoutes.scala
index f06fa51..6e37695 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/UploadRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/UploadRoutes.scala
@@ -33,7 +33,7 @@ import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
 import org.apache.james.jmap.http.Authenticator
 import org.apache.james.jmap.http.rfc8621.InjectionKeys
 import org.apache.james.jmap.mail.Email.Size
-import org.apache.james.jmap.routes.UploadRoutes.{LOGGER, fromAttachment}
+import org.apache.james.jmap.routes.UploadRoutes.{LOGGER, sanitizeSize}
 import org.apache.james.mailbox.{AttachmentManager, MailboxSession}
 import org.apache.james.mailbox.model.{AttachmentMetadata, ContentType}
 import org.apache.james.util.ReactorUtils
@@ -48,6 +48,8 @@ import eu.timepit.refined.refineV
 import org.apache.james.jmap.exceptions.UnauthorizedException
 import org.apache.james.jmap.json.UploadSerializer
 import org.apache.james.jmap.mail.BlobId
+import org.apache.james.jmap.model.{AccountId, Id}
+import org.apache.james.jmap.model.Id.Id
 
 object UploadRoutes {
   val LOGGER: Logger = LoggerFactory.getLogger(classOf[DownloadRoutes])
@@ -63,27 +65,18 @@ object UploadRoutes {
     },
       refinedValue => refinedValue)
   }
-
-  def fromAttachment(attachmentMetadata: AttachmentMetadata): UploadResponse =
-    UploadResponse(
-        blobId = BlobId.of(attachmentMetadata.getAttachmentId.getId).get,
-        `type` = ContentType.of(attachmentMetadata.getType.asString),
-        size = sanitizeSize(attachmentMetadata.getSize),
-        expires = None)
 }
 
-case class UploadResponse(blobId: BlobId,
+case class UploadResponse(accountId: AccountId,
+                          blobId: BlobId,
                           `type`: ContentType,
-                          size: Size,
-                          expires: Option[ZonedDateTime])
+                          size: Size)
 
 class UploadRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val authenticator: Authenticator,
                              val attachmentManager: AttachmentManager,
                              val serializer: UploadSerializer) extends JMAPRoutes {
 
-  class CancelledUploadException extends RuntimeException {
-
-  }
+  class CancelledUploadException extends RuntimeException
 
   private val accountIdParam: String = "accountId"
   private val uploadURI = s"/upload/{$accountIdParam}/"
@@ -113,21 +106,41 @@ class UploadRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val authenticator: A
   }
 
   def post(request: HttpServerRequest, response: HttpServerResponse, contentType: ContentType, session: MailboxSession): SMono[Void] = {
-    SMono.fromCallable(() => ReactorUtils.toInputStream(request.receive.asByteBuffer))
-      .flatMap(content => handle(contentType, content, session, response))
-      .subscribeOn(Schedulers.elastic())
+    Id.validate(request.param(accountIdParam)) match {
+      case Right(id: Id) => {
+        val targetAccountId: AccountId = AccountId(id)
+        AccountId.from(session.getUser).map(accountId => accountId.equals(targetAccountId))
+          .fold[SMono[Void]](
+            e => SMono.raiseError(e),
+            value => if (value) {
+              SMono.fromCallable(() => ReactorUtils.toInputStream(request.receive.asByteBuffer))
+              .flatMap(content => handle(targetAccountId, contentType, content, session, response))
+              .subscribeOn(Schedulers.elastic())
+            } else {
+              SMono.raiseError(new UnauthorizedException("Attempt to upload in another account"))
+            })
+      }
+
+      case Left(throwable: Throwable) => SMono.raiseError(throwable)
+    }
   }
 
-  def handle(contentType: ContentType, content: InputStream, mailboxSession: MailboxSession, response: HttpServerResponse): SMono[Void] =
-    uploadContent(contentType, content, mailboxSession)
+  def handle(accountId: AccountId, contentType: ContentType, content: InputStream, mailboxSession: MailboxSession, response: HttpServerResponse): SMono[Void] =
+    uploadContent(accountId, contentType, content, mailboxSession)
       .flatMap(uploadResponse => SMono.fromPublisher(response
             .header(CONTENT_TYPE, uploadResponse.`type`.asString())
             .status(CREATED)
             .sendString(SMono.just(serializer.serialize(uploadResponse).toString()))))
 
-  def uploadContent(contentType: ContentType, inputStream: InputStream, session: MailboxSession): SMono[UploadResponse] =
+  def uploadContent(accountId: AccountId, contentType: ContentType, inputStream: InputStream, session: MailboxSession): SMono[UploadResponse] =
     SMono
       .fromPublisher(attachmentManager.storeAttachment(contentType, inputStream, session))
-      .map(fromAttachment)
+      .map(fromAttachment(_, accountId))
 
+  private def fromAttachment(attachmentMetadata: AttachmentMetadata, accountId: AccountId): UploadResponse =
+    UploadResponse(
+        blobId = BlobId.of(attachmentMetadata.getAttachmentId.getId).get,
+        `type` = ContentType.of(attachmentMetadata.getType.asString),
+        size = sanitizeSize(attachmentMetadata.getSize),
+        accountId = accountId)
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org