JAMES-1779 document jmap download limitations
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/5f30b315 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/5f30b315 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/5f30b315 Branch: refs/heads/master Commit: 5f30b315f6f86b77edbc79f554d81c7b73ac7894 Parents: c9cbb88 Author: Matthieu Baechler <matthieu.baech...@linagora.com> Authored: Thu Jun 23 15:27:58 2016 +0200 Committer: Antoine Duprat <adup...@linagora.com> Committed: Tue Jun 28 09:44:23 2016 +0200 ---------------------------------------------------------------------- .../apache/james/jmap/JmapAuthentication.java | 4 ++ .../integration/cucumber/DownloadStepdefs.java | 66 +++++++++-------- .../cucumber/SetMailboxesMethodStepdefs.java | 24 ++++--- .../integration/cucumber/UserStepdefs.java | 75 ++++++++++++++++++-- .../resources/cucumber/DownloadEndpoint.feature | 46 ++++++++---- .../cucumber/MailboxModification.feature | 2 +- .../jmap/doc/specs/spec/authentication.mdwn | 3 +- 7 files changed, 159 insertions(+), 61 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/5f30b315/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/JmapAuthentication.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/JmapAuthentication.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/JmapAuthentication.java index 2341efa..cd4ea9b 100644 --- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/JmapAuthentication.java +++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/JmapAuthentication.java @@ -32,6 +32,10 @@ public class JmapAuthentication { with() .body("{\"token\": \"" + continuationToken + "\", \"method\": \"password\", \"password\": \"" + password + "\"}") .post("/authentication") + .then() + .statusCode(201) + .log().ifError() + .extract() .body() .jsonPath() .getString("accessToken") http://git-wip-us.apache.org/repos/asf/james-project/blob/5f30b315/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/DownloadStepdefs.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/DownloadStepdefs.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/DownloadStepdefs.java index 83706b0..caea495 100644 --- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/DownloadStepdefs.java +++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/DownloadStepdefs.java @@ -24,13 +24,18 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import javax.inject.Inject; import javax.mail.Flags; +import org.apache.james.jmap.api.access.AccessToken; import org.apache.james.mailbox.model.MailboxConstants; import org.apache.james.mailbox.model.MailboxPath; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; import com.jayway.restassured.response.Response; import cucumber.api.java.en.Given; @@ -44,58 +49,63 @@ public class DownloadStepdefs { private final UserStepdefs userStepdefs; private final MainStepdefs mainStepdefs; private Response response; + private Multimap<String, String> attachmentsByMessageId; + private Map<String, String> blobIdByAttachmentId; @Inject private DownloadStepdefs(MainStepdefs mainStepdefs, UserStepdefs userStepdefs) { this.mainStepdefs = mainStepdefs; this.userStepdefs = userStepdefs; + this.attachmentsByMessageId = ArrayListMultimap.create(); + this.blobIdByAttachmentId = new HashMap<>(); } - @Given("^a message containing an attachment$") - public void appendMessageWithAttachment() throws Exception { - mainStepdefs.jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, userStepdefs.username, "INBOX"); - MailboxPath mailboxPath = new MailboxPath(MailboxConstants.USER_NAMESPACE, userStepdefs.username, "INBOX"); + @Given("^\"([^\"]*)\" mailbox \"([^\"]*)\" contains a message \"([^\"]*)\" with an attachment \"([^\"]*)\"$") + public void appendMessageWithAttachmentToMailbox(String user, String mailbox, String messageId, String attachmentId) throws Throwable { + MailboxPath mailboxPath = new MailboxPath(MailboxConstants.USER_NAMESPACE, user, mailbox); - mainStepdefs.jmapServer.serverProbe().appendMessage(userStepdefs.username, mailboxPath, + mainStepdefs.jmapServer.serverProbe().appendMessage(user, mailboxPath, ClassLoader.getSystemResourceAsStream("eml/oneAttachment.eml"), new Date(), false, new Flags()); + + attachmentsByMessageId.put(messageId, attachmentId); + blobIdByAttachmentId.put(attachmentId, "4000c5145f633410b80be368c44e1c394bff9437"); } - @When("^checking for the availability of the attachment endpoint$") - public void optionDownload() throws Throwable { - if (userStepdefs.accessToken != null) { - with().header("Authorization", userStepdefs.accessToken.serialize()); + @When("^\"([^\"]*)\" checks for the availability of the attachment endpoint$") + public void optionDownload(String username) throws Throwable { + AccessToken accessToken = userStepdefs.tokenByUser.get(username); + if (accessToken != null) { + with().header("Authorization", accessToken.serialize()); } response = with().options("/download/myBlob"); } - @When("^asking for an attachment$") - public void getDownload() throws Exception { - if (userStepdefs.accessToken != null) { - with().header("Authorization", userStepdefs.accessToken.serialize()); + @When("^\"([^\"]*)\" downloads \"([^\"]*)\"$") + public void downloads(String username, String attachmentId) throws Throwable { + String blobId = blobIdByAttachmentId.get(attachmentId); + AccessToken accessToken = userStepdefs.tokenByUser.get(username); + if (accessToken != null) { + with().header("Authorization", accessToken.serialize()); } - - response = with().get("/download/myBlob"); + response = with().get("/download/" + blobId); } + - @When("^asking for an attachment without blobId parameter$") - public void getDownloadWithoutBlobId() throws Throwable { + @When("^\"([^\"]*)\" asks for an attachment without blobId parameter$") + public void getDownloadWithoutBlobId(String username) throws Throwable { + AccessToken accessToken = userStepdefs.tokenByUser.get(username); response = with() - .header("Authorization", userStepdefs.accessToken.serialize()) + .header("Authorization", accessToken.serialize()) .get("/download/"); } + - @When("^getting the attachment with its correct blobId$") - public void getDownloadWithKnownBlobId() throws Throwable { - response = with() - .header("Authorization", userStepdefs.accessToken.serialize()) - .get("/download/4000c5145f633410b80be368c44e1c394bff9437"); - } - - @When("^getting the attachment with an unknown blobId$") - public void getDownloadWithUnknownBlobId() throws Throwable { + @When("^\"([^\"]*)\" asks for an attachment with wrong blobId$") + public void getDownloadWithWrongBlobId(String username) throws Throwable { + AccessToken accessToken = userStepdefs.tokenByUser.get(username); response = with() - .header("Authorization", userStepdefs.accessToken.serialize()) + .header("Authorization", accessToken.serialize()) .get("/download/badbadbadbadbadbadbadbadbadbadbadbadbadb"); } http://git-wip-us.apache.org/repos/asf/james-project/blob/5f30b315/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/SetMailboxesMethodStepdefs.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/SetMailboxesMethodStepdefs.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/SetMailboxesMethodStepdefs.java index e3e9d16..85b643e 100644 --- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/SetMailboxesMethodStepdefs.java +++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/SetMailboxesMethodStepdefs.java @@ -37,7 +37,6 @@ import org.apache.james.mailbox.model.MailboxPath; import org.apache.james.mailbox.store.mail.model.Mailbox; import com.github.fge.lambdas.Throwing; -import com.jayway.restassured.http.ContentType; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; @@ -61,8 +60,8 @@ public class SetMailboxesMethodStepdefs { @Given("^mailbox \"([^\"]*)\" with (\\d+) messages$") public void mailboxWithMessages(String mailboxName, int messageCount) throws Throwable { - mainStepdefs.jmapServer.serverProbe().createMailbox("#private", userStepdefs.username, mailboxName); - MailboxPath mailboxPath = new MailboxPath(MailboxConstants.USER_NAMESPACE, userStepdefs.username, mailboxName); + mainStepdefs.jmapServer.serverProbe().createMailbox("#private", userStepdefs.lastConnectedUser, mailboxName); + MailboxPath mailboxPath = new MailboxPath(MailboxConstants.USER_NAMESPACE, userStepdefs.lastConnectedUser, mailboxName); IntStream .range(0, messageCount) .forEach(Throwing.intConsumer(i -> appendMessage(mailboxPath, i))); @@ -72,13 +71,14 @@ public class SetMailboxesMethodStepdefs { private void appendMessage(MailboxPath mailboxPath, int i) throws MailboxException { String content = "Subject: test" + i + "\r\n\r\n" + "testBody" + i; - mainStepdefs.jmapServer.serverProbe().appendMessage(userStepdefs.username, mailboxPath, + mainStepdefs.jmapServer.serverProbe().appendMessage(userStepdefs.lastConnectedUser, mailboxPath, new ByteArrayInputStream(content.getBytes()), new Date(), false, new Flags()); } @When("^renaming mailbox \"([^\"]*)\" to \"([^\"]*)\"") public void renamingMailbox(String actualMailboxName, String newMailboxName) throws Throwable { - Mailbox mailbox = mainStepdefs.jmapServer.serverProbe().getMailbox("#private", userStepdefs.username, actualMailboxName); + String username = userStepdefs.lastConnectedUser; + Mailbox mailbox = mainStepdefs.jmapServer.serverProbe().getMailbox("#private", userStepdefs.lastConnectedUser, actualMailboxName); String mailboxId = mailbox.getMailboxId().serialize(); String requestBody = "[" + @@ -95,16 +95,17 @@ public class SetMailboxesMethodStepdefs { "]"; with() - .header("Authorization", userStepdefs.accessToken.serialize()) + .header("Authorization", userStepdefs.tokenByUser.get(username).serialize()) .body(requestBody) .post("/jmap"); } @When("^moving mailbox \"([^\"]*)\" to \"([^\"]*)\"$") public void movingMailbox(String actualMailboxPath, String newParentMailboxPath) throws Throwable { - Mailbox mailbox = mainStepdefs.jmapServer.serverProbe().getMailbox("#private", userStepdefs.username, actualMailboxPath); + String username = userStepdefs.lastConnectedUser; + Mailbox mailbox = mainStepdefs.jmapServer.serverProbe().getMailbox("#private", username, actualMailboxPath); String mailboxId = mailbox.getMailboxId().serialize(); - Mailbox parent = mainStepdefs.jmapServer.serverProbe().getMailbox("#private", userStepdefs.username, newParentMailboxPath); + Mailbox parent = mainStepdefs.jmapServer.serverProbe().getMailbox("#private", username, newParentMailboxPath); String parentId = parent.getMailboxId().serialize(); String requestBody = @@ -122,17 +123,18 @@ public class SetMailboxesMethodStepdefs { "]"; with() - .header("Authorization", userStepdefs.accessToken.serialize()) + .header("Authorization", userStepdefs.tokenByUser.get(username).serialize()) .body(requestBody) .post("/jmap"); } @Then("^mailbox \"([^\"]*)\" contains (\\d+) messages$") public void mailboxContainsMessages(String mailboxName, int messageCount) throws Throwable { - Mailbox mailbox = mainStepdefs.jmapServer.serverProbe().getMailbox("#private", userStepdefs.username, mailboxName); + String username = userStepdefs.lastConnectedUser; + Mailbox mailbox = mainStepdefs.jmapServer.serverProbe().getMailbox("#private", username, mailboxName); String mailboxId = mailbox.getMailboxId().serialize(); given() - .header("Authorization", userStepdefs.accessToken.serialize()) + .header("Authorization", userStepdefs.tokenByUser.get(username).serialize()) .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + mailboxId + "\"]}}, \"#0\"]]") .when() .post("/jmap") http://git-wip-us.apache.org/repos/asf/james-project/blob/5f30b315/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/UserStepdefs.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/UserStepdefs.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/UserStepdefs.java index 01f0496..a8a5816 100644 --- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/UserStepdefs.java +++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/UserStepdefs.java @@ -19,11 +19,24 @@ package org.apache.james.jmap.methods.integration.cucumber; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + import javax.inject.Inject; import org.apache.james.jmap.JmapAuthentication; import org.apache.james.jmap.api.access.AccessToken; +import org.apache.james.mailbox.model.MailboxConstants; + +import com.github.fge.lambdas.Throwing; +import com.google.common.base.Charsets; +import com.google.common.base.Preconditions; +import com.google.common.hash.Hashing; +import cucumber.api.PendingException; import cucumber.api.java.en.Given; import cucumber.runtime.java.guice.ScenarioScoped; @@ -31,24 +44,72 @@ import cucumber.runtime.java.guice.ScenarioScoped; public class UserStepdefs { private final MainStepdefs mainStepdefs; - protected String username; - protected AccessToken accessToken; - + + protected Map<String, String> passwordByUser; + protected Set<String> domains; + protected Map<String, AccessToken> tokenByUser; + protected String lastConnectedUser; + @Inject private UserStepdefs(MainStepdefs mainStepdefs) { this.mainStepdefs = mainStepdefs; + this.domains = new HashSet<>(); + this.passwordByUser = new HashMap<>(); + this.tokenByUser = new HashMap<>(); } @Given("^a domain named \"([^\"]*)\"$") public void createDomain(String domain) throws Throwable { mainStepdefs.jmapServer.serverProbe().addDomain(domain); + domains.add(domain); } - @Given("^a current user with username \"([^\"]*)\" and password \"([^\"]*)\"$") - public void createUserWithPasswordAndAuthenticate(String username, String password) throws Throwable { - this.username = username; + @Given("^some users (.*)$") + public void createUsers(List<String> users) throws Throwable { + users.stream() + .map(this::unquote) + .forEach(Throwing.consumer(this::createUser)); + } + + private String unquote(String quotedString) { + return quotedString.substring(1, quotedString.length() - 1); + } + + @Given("^a user \"([^\"]*)\"$") + public void createUser(String username) throws Exception { + String password = generatePassword(username); mainStepdefs.jmapServer.serverProbe().addUser(username, password); - accessToken = JmapAuthentication.authenticateJamesUser(username, password); + passwordByUser.put(username, password); + } + + @Given("^a connected user \"([^\"]*)\"$") + public void createConnectedUser(String username) throws Throwable { + createUser(username); + connectUser(username); + } + + @Given("^\"([^\"]*)\" has a mailbox \"([^\"]*)\"$") + public void createMailbox(String username, String mailbox) throws Throwable { + mainStepdefs.jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, mailbox); + } + + + @Given("^\"([^\"]*)\" is connected$") + public void connectUser(String username) throws Throwable { + String password = passwordByUser.get(username); + Preconditions.checkState(password != null, "unknown user " + username); + AccessToken accessToken = JmapAuthentication.authenticateJamesUser(username, password); + tokenByUser.put(username, accessToken); + lastConnectedUser = username; + } + + @Given("^\"([^\"]*)\" shares its mailbox \"([^\"]*)\" with \"([^\"]*)\"$") + public void shareMailbox(String owner, String mailbox, String shareTo) throws Throwable { + throw new PendingException(); + } + + private String generatePassword(String username) { + return Hashing.murmur3_128().hashString(username, Charsets.UTF_8).toString(); } } http://git-wip-us.apache.org/repos/asf/james-project/blob/5f30b315/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/DownloadEndpoint.feature ---------------------------------------------------------------------- diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/DownloadEndpoint.feature b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/DownloadEndpoint.feature index 8d751a6..df26b7f 100644 --- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/DownloadEndpoint.feature +++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/DownloadEndpoint.feature @@ -4,27 +4,49 @@ Feature: Download endpoint Background: Given a domain named "domain.tld" - - Scenario: A known user should initiate the access to the download endpoint - Given a current user with username "usern...@domain.tld" and password "secret" - When checking for the availability of the attachment endpoint + And some users "us...@domain.tld", "us...@domain.tld", "us...@domain.tld" + And "us...@domain.tld" has a mailbox "INBOX" + And "us...@domain.tld" mailbox "INBOX" contains a message "m1" with an attachment "a1" + + Scenario: An authenticated user should initiate the access to the download endpoint + Given "us...@domain.tld" is connected + When "us...@domain.tld" checks for the availability of the attachment endpoint Then the user should be authorized Scenario: An unauthenticated user should initiate the access to the download endpoint - When checking for the availability of the attachment endpoint + When "us...@domain.tld" checks for the availability of the attachment endpoint Then the user should be authorized - Scenario: A known user should have access to the download endpoint - Given a current user with username "usern...@domain.tld" and password "secret" - When asking for an attachment + Scenario: An authenticated user should have access to the download endpoint + Given "us...@domain.tld" is connected + When "us...@domain.tld" downloads "a1" Then the user should be authorized @Ignore Scenario: An unauthenticated user should not have access to the download endpoint - When asking for an attachment + When "us...@domain.tld" downloads "a1" Then the user should not be authorized - Scenario: A known user should not have access to the download endpoint without a blobId - Given a current user with username "usern...@domain.tld" and password "secret" - When asking for an attachment without blobId parameter + Scenario: A authenticated user should not have access to the download endpoint without a blobId + Given "us...@domain.tld" is connected + When "us...@domain.tld" asks for an attachment without blobId parameter Then the user should receive a bad request response + + + Scenario: A user should not retrieve anything when using wrong blobId + Given "us...@domain.tld" is connected + When "us...@domain.tld" asks for an attachment with wrong blobId + Then the user should receive a not found response + + @Ignore + Scenario: A user should not have access to someone else attachment + Given "us...@domain.tld" is connected + When "us...@domain.tld" downloads "a1" + Then the user should receive a not found response + + @Ignore + Scenario: A user should have access to a shared attachment + Given "us...@domain.tld" shares its mailbox "INBOX" with "us...@domain.tld" + And "us...@domain.tld" is connected + When "us...@domain.tld" downloads "a1" + Then the user should be authorized http://git-wip-us.apache.org/repos/asf/james-project/blob/5f30b315/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/MailboxModification.feature ---------------------------------------------------------------------- diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/MailboxModification.feature b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/MailboxModification.feature index 2de6a18..8ec100c 100644 --- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/MailboxModification.feature +++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/MailboxModification.feature @@ -5,7 +5,7 @@ Feature: Mailbox modification Background: Given a domain named "domain.tld" - And a current user with username "usern...@domain.tld" and password "secret" + And a connected user "usern...@domain.tld" Scenario: Renaming a mailbox should keep messages Given mailbox "A" with 2 messages http://git-wip-us.apache.org/repos/asf/james-project/blob/5f30b315/server/protocols/jmap/doc/specs/spec/authentication.mdwn ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/doc/specs/spec/authentication.mdwn b/server/protocols/jmap/doc/specs/spec/authentication.mdwn index 50672ed..25e2c64 100644 --- a/server/protocols/jmap/doc/specs/spec/authentication.mdwn +++ b/server/protocols/jmap/doc/specs/spec/authentication.mdwn @@ -107,8 +107,7 @@ The response body will be a single JSON object with the following properties. The URL endpoint to use when uploading files (see the Upload section of this spec). - **download**: `String` <aside class="warning"> - Not implemented - </aside> + Download endpoint does not handle name, it's not secured by any access protection and any authenticated user can get any attachment without restriction.</aside> The URL endpoint to use when downloading files, in [RFC6570 URI Template](https://tools.ietf.org/html/rfc6570) (level 1) format. The URL MUST contain a variable called `blobId`. The URL SHOULD contain a variable called `name`. The client may use this template in combination with a blobId to download any binary data (files) referenced by other objects. Since a blob is not associated with a particular name, the template SHOULD allow a name to be substituted in as well; the server will return this as the filename if it sets a `Content-Disposition` header. To download the data the client MUST make an authenticated GET request (see below for how to authenticate requests) to the expanded URL, and then follow any redirects. URLs are returned only after logging in. This allows different URLs to be used for users located in different geographic datacentres within the same service. --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org