james-server-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From adup...@apache.org
Subject [2/2] james-project git commit: JAMES-1783 POSTing on download endpoint should return a token
Date Wed, 29 Jun 2016 13:01:21 GMT
JAMES-1783 POSTing on download endpoint should return a token


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/a5bd1c74
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/a5bd1c74
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/a5bd1c74

Branch: refs/heads/master
Commit: a5bd1c74335614090413cac7e3f587cb582f8651
Parents: 9963086
Author: Raphael Ouazana <raphael.ouazana@linagora.com>
Authored: Tue Jun 28 09:49:50 2016 +0200
Committer: Raphael Ouazana <raphael.ouazana@linagora.com>
Committed: Wed Jun 29 15:00:15 2016 +0200

----------------------------------------------------------------------
 .../org/apache/james/jmap/JMAPCommonModule.java |  11 +-
 .../integration/cucumber/DownloadStepdefs.java  |  32 +++-
 .../resources/cucumber/DownloadPost.feature     |  17 ++
 .../cucumber/MemoryDownloadCucumberTest.java    |   2 +-
 .../james/jmap/AuthenticationServlet.java       |  15 +-
 .../org/apache/james/jmap/DownloadServlet.java  |  41 ++++-
 .../java/org/apache/james/jmap/JMAPServer.java  |   3 +
 .../jmap/api/ContinuationTokenManager.java      |  37 ----
 .../james/jmap/api/SimpleTokenFactory.java      |  29 +++
 .../james/jmap/api/SimpleTokenManager.java      |  34 ++++
 .../crypto/SignedContinuationTokenManager.java  |  78 --------
 .../james/jmap/crypto/SignedTokenFactory.java   |  73 ++++++++
 .../james/jmap/crypto/SignedTokenManager.java   |  66 +++++++
 .../james/jmap/model/AttachmentAccessToken.java | 153 ++++++++++++++++
 .../james/jmap/model/ContinuationToken.java     |  14 +-
 .../james/jmap/model/SignedExpiringToken.java   |  32 ++++
 .../apache/james/jmap/DownloadServletTest.java  |   8 +-
 .../SignedContinuationTokenManagerTest.java     | 179 -------------------
 .../jmap/crypto/SignedTokenFactoryTest.java     |  79 ++++++++
 .../jmap/crypto/SignedTokenManagerTest.java     | 171 ++++++++++++++++++
 .../jmap/model/AttachmentAccessTokenTest.java   |  99 ++++++++++
 21 files changed, 858 insertions(+), 315 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
----------------------------------------------------------------------
diff --git a/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java b/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
index 71e0427..049f3b3 100644
--- a/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
+++ b/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
@@ -22,12 +22,14 @@ import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.james.jmap.api.AccessTokenManager;
-import org.apache.james.jmap.api.ContinuationTokenManager;
+import org.apache.james.jmap.api.SimpleTokenFactory;
+import org.apache.james.jmap.api.SimpleTokenManager;
 import org.apache.james.jmap.api.access.AccessTokenRepository;
 import org.apache.james.jmap.crypto.AccessTokenManagerImpl;
 import org.apache.james.jmap.crypto.JamesSignatureHandler;
 import org.apache.james.jmap.crypto.SignatureHandler;
-import org.apache.james.jmap.crypto.SignedContinuationTokenManager;
+import org.apache.james.jmap.crypto.SignedTokenFactory;
+import org.apache.james.jmap.crypto.SignedTokenManager;
 import org.apache.james.jmap.model.MessageFactory;
 import org.apache.james.jmap.model.MessagePreviewGenerator;
 import org.apache.james.jmap.send.MailFactory;
@@ -51,7 +53,7 @@ public class JMAPCommonModule extends AbstractModule {
     protected void configure() {
         bind(JamesSignatureHandler.class).in(Scopes.SINGLETON);
         bind(DefaultZonedDateTimeProvider.class).in(Scopes.SINGLETON);
-        bind(SignedContinuationTokenManager.class).in(Scopes.SINGLETON);
+        bind(SignedTokenManager.class).in(Scopes.SINGLETON);
         bind(AccessTokenManagerImpl.class).in(Scopes.SINGLETON);
         bind(MailSpool.class).in(Scopes.SINGLETON);
         bind(MailFactory.class).in(Scopes.SINGLETON);
@@ -61,7 +63,8 @@ public class JMAPCommonModule extends AbstractModule {
 
         bind(SignatureHandler.class).to(JamesSignatureHandler.class);
         bind(ZonedDateTimeProvider.class).to(DefaultZonedDateTimeProvider.class);
-        bind(ContinuationTokenManager.class).to(SignedContinuationTokenManager.class);
+        bind(SimpleTokenManager.class).to(SignedTokenManager.class);
+        bind(SimpleTokenFactory.class).to(SignedTokenFactory.class);
         bind(AutomaticallySentMailDetector.class).to(AutomaticallySentMailDetectorImpl.class);
 
         bindConstant().annotatedWith(Names.named(AccessTokenRepository.TOKEN_EXPIRATION_IN_MS)).to(DEFAULT_TOKEN_EXPIRATION_IN_MS);

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/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 caea495..74b2d49 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
@@ -36,7 +36,9 @@ import org.apache.james.mailbox.model.MailboxPath;
 
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Multimap;
+import com.jayway.restassured.http.ContentType;
 import com.jayway.restassured.response.Response;
+import com.jayway.restassured.specification.RequestSpecification;
 
 import cucumber.api.java.en.Given;
 import cucumber.api.java.en.Then;
@@ -74,21 +76,23 @@ public class DownloadStepdefs {
     @When("^\"([^\"]*)\" checks for the availability of the attachment endpoint$")
     public void optionDownload(String username) throws Throwable {
         AccessToken accessToken = userStepdefs.tokenByUser.get(username);
+        RequestSpecification with = with();
         if (accessToken != null) {
-            with().header("Authorization", accessToken.serialize());
+            with.header("Authorization", accessToken.serialize());
         }
 
-        response = with().options("/download/myBlob");
+        response = with.options("/download/myBlob");
     }
 
     @When("^\"([^\"]*)\" downloads \"([^\"]*)\"$")
     public void downloads(String username, String attachmentId) throws Throwable {
         String blobId = blobIdByAttachmentId.get(attachmentId);
         AccessToken accessToken = userStepdefs.tokenByUser.get(username);
+        RequestSpecification with = with();
         if (accessToken != null) {
-            with().header("Authorization", accessToken.serialize());
+            with.header("Authorization", accessToken.serialize());
         }
-        response = with().get("/download/" + blobId);
+        response = with.get("/download/" + blobId);
     }
     
 
@@ -109,6 +113,18 @@ public class DownloadStepdefs {
                 .get("/download/badbadbadbadbadbadbadbadbadbadbadbadbadb");
     }
 
+    @When("^\"([^\"]*)\" asks for a token for attachment \"([^\"]*)\"$")
+    public void postDownload(String username, String attachmentId) throws Throwable {
+        String blobId = blobIdByAttachmentId.get(attachmentId);
+        AccessToken accessToken = userStepdefs.tokenByUser.get(username);
+        RequestSpecification with = with();
+        if (accessToken != null) {
+            with = with.header("Authorization", accessToken.serialize());
+        }
+        response = with
+                .post("/download/" + blobId);
+    }
+
     @Then("^the user should be authorized$")
     public void httpStatusDifferentFromUnauthorized() throws Exception {
         response.then()
@@ -139,4 +155,12 @@ public class DownloadStepdefs {
         response.then()
             .statusCode(404);
     }
+
+    @Then("^the user should receive an attachment access token$")
+    public void accessTokenResponse() throws Throwable {
+        response.then()
+            .statusCode(200)
+            .contentType(ContentType.TEXT)
+            .content(notNullValue());
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/DownloadPost.feature
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/DownloadPost.feature b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/DownloadPost.feature
new file mode 100644
index 0000000..c748014
--- /dev/null
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/DownloadPost.feature
@@ -0,0 +1,17 @@
+Feature: Alternative authentication mechanism for getting attachment via a POST request returning a specific authentication token
+  As a James user
+  I want to retrieve my attachments without an alternative authentication mechanim
+
+  Background:
+    Given a domain named "domain.tld"
+    And a connected user "username@domain.tld"
+    And "username@domain.tld" has a mailbox "inbox"
+
+  Scenario: Asking for an attachment access token with an unknown blobId
+    When "username@domain.tld" asks for a token for attachment "123"
+    Then the user should receive a not found response
+
+  Scenario: Asking for an attachment access token with a previously stored blobId
+    Given "username@domain.tld" mailbox "inbox" contains a message "1" with an attachment "2"
+    When "username@domain.tld" asks for a token for attachment "2"
+    Then the user should receive an attachment access token

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/java/org/apache/james/jmap/memory/cucumber/MemoryDownloadCucumberTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/java/org/apache/james/jmap/memory/cucumber/MemoryDownloadCucumberTest.java b/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/java/org/apache/james/jmap/memory/cucumber/MemoryDownloadCucumberTest.java
index 85a0cdd..3580c53 100644
--- a/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/java/org/apache/james/jmap/memory/cucumber/MemoryDownloadCucumberTest.java
+++ b/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/java/org/apache/james/jmap/memory/cucumber/MemoryDownloadCucumberTest.java
@@ -25,7 +25,7 @@ import cucumber.api.CucumberOptions;
 import cucumber.api.junit.Cucumber;
 
 @RunWith(Cucumber.class)
-@CucumberOptions(features={"classpath:cucumber/DownloadEndpoint.feature", "classpath:cucumber/DownloadGet.feature"},
+@CucumberOptions(features={"classpath:cucumber/DownloadEndpoint.feature", "classpath:cucumber/DownloadGet.feature", "classpath:cucumber/DownloadPost.feature"},
                 glue={"org.apache.james.jmap.methods.integration", "org.apache.james.jmap.memory.cucumber"},
                 tags = {"~@Ignore"},
                 strict = true)

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/main/java/org/apache/james/jmap/AuthenticationServlet.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/AuthenticationServlet.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/AuthenticationServlet.java
index a8ce762..06c5395 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/AuthenticationServlet.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/AuthenticationServlet.java
@@ -27,7 +27,8 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.james.jmap.api.AccessTokenManager;
-import org.apache.james.jmap.api.ContinuationTokenManager;
+import org.apache.james.jmap.api.SimpleTokenFactory;
+import org.apache.james.jmap.api.SimpleTokenManager;
 import org.apache.james.jmap.api.access.AccessToken;
 import org.apache.james.jmap.exceptions.BadRequestException;
 import org.apache.james.jmap.exceptions.InternalErrorException;
@@ -54,13 +55,15 @@ public class AuthenticationServlet extends HttpServlet {
 
     private final ObjectMapper mapper;
     private final UsersRepository usersRepository;
-    private final ContinuationTokenManager continuationTokenManager;
+    private final SimpleTokenManager simpleTokenManager;
     private final AccessTokenManager accessTokenManager;
+    private final SimpleTokenFactory simpleTokenFactory;
     
     @Inject
-    @VisibleForTesting AuthenticationServlet(UsersRepository usersRepository, ContinuationTokenManager continuationTokenManager, AccessTokenManager accessTokenManager) {
+    @VisibleForTesting AuthenticationServlet(UsersRepository usersRepository, SimpleTokenManager simpleTokenManager, SimpleTokenFactory simpleTokenFactory, AccessTokenManager accessTokenManager) {
         this.usersRepository = usersRepository;
-        this.continuationTokenManager = continuationTokenManager;
+        this.simpleTokenManager = simpleTokenManager;
+        this.simpleTokenFactory = simpleTokenFactory;
         this.accessTokenManager = accessTokenManager;
         this.mapper = new MultipleObjectMapperBuilder()
             .registerClass(ContinuationTokenRequest.UNIQUE_JSON_PATH, ContinuationTokenRequest.class)
@@ -129,7 +132,7 @@ public class AuthenticationServlet extends HttpServlet {
         try {
             ContinuationTokenResponse continuationTokenResponse = ContinuationTokenResponse
                 .builder()
-                .continuationToken(continuationTokenManager.generateToken(request.getUsername()))
+                .continuationToken(simpleTokenFactory.generateContinuationToken(request.getUsername()))
                 .methods(ContinuationTokenResponse.AuthenticationMethod.PASSWORD)
                 .build();
             mapper.writeValue(resp.getOutputStream(), continuationTokenResponse);
@@ -139,7 +142,7 @@ public class AuthenticationServlet extends HttpServlet {
     }
 
     private void handleAccessTokenRequest(AccessTokenRequest request, HttpServletResponse resp) throws IOException {
-        switch (continuationTokenManager.getValidity(request.getToken())) {
+        switch (simpleTokenManager.getValidity(request.getToken())) {
         case EXPIRED:
             returnRestartAuthentication(resp);
             break;

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/main/java/org/apache/james/jmap/DownloadServlet.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/DownloadServlet.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/DownloadServlet.java
index e0c1e98..ccd8d86 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/DownloadServlet.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/DownloadServlet.java
@@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.james.jmap.api.SimpleTokenFactory;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.exception.AttachmentNotFoundException;
 import org.apache.james.mailbox.exception.MailboxException;
@@ -49,12 +50,50 @@ public class DownloadServlet extends HttpServlet {
 
     private static final String ROOT_URL = "/";
     private static final Logger LOGGER = LoggerFactory.getLogger(DownloadServlet.class);
+    private static final String TEXT_PLAIN_CONTENT_TYPE = "text/plain";
 
     private final MailboxSessionMapperFactory mailboxSessionMapperFactory;
+    private final SimpleTokenFactory simpleTokenFactory;
 
     @Inject
-    @VisibleForTesting DownloadServlet(MailboxSessionMapperFactory mailboxSessionMapperFactory) {
+    @VisibleForTesting DownloadServlet(MailboxSessionMapperFactory mailboxSessionMapperFactory, SimpleTokenFactory simpleTokenFactory) {
         this.mailboxSessionMapperFactory = mailboxSessionMapperFactory;
+        this.simpleTokenFactory = simpleTokenFactory;
+    }
+
+    @Override
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
+        String pathInfo = req.getPathInfo();
+        if (Strings.isNullOrEmpty(pathInfo) || pathInfo.equals(ROOT_URL)) {
+            resp.setStatus(SC_BAD_REQUEST);
+        } else {
+            respondAttachmentAccessToken(getMailboxSession(req), blobIdFrom(pathInfo), resp);
+        }
+    }
+
+    private void respondAttachmentAccessToken(MailboxSession mailboxSession, String blobId, HttpServletResponse resp) {
+        try {
+            if (! attachmentExists(mailboxSession, blobId)) {
+                resp.setStatus(SC_NOT_FOUND);
+                return;
+            }
+            resp.setContentType(TEXT_PLAIN_CONTENT_TYPE);
+            resp.getOutputStream().print(simpleTokenFactory.generateAttachmentAccessToken(mailboxSession.getUser().getUserName(), blobId).serialize());
+            resp.setStatus(SC_OK);
+        } catch (MailboxException | IOException e) {
+            LOGGER.error("Error while asking attachment access token", e);
+            resp.setStatus(SC_INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    private boolean attachmentExists(MailboxSession mailboxSession, String blobId) throws MailboxException {
+        AttachmentMapper attachmentMapper = mailboxSessionMapperFactory.createAttachmentMapper(mailboxSession);
+        try {
+            attachmentMapper.getAttachment(AttachmentId.from(blobId));
+            return true;
+        } catch (AttachmentNotFoundException e) {
+            return false;
+        }
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
index 0e6b616..1728d0a 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
@@ -57,6 +57,9 @@ public class JMAPServer implements Configurable {
                             .only()
                         .serveAsOneLevelTemplate(JMAPUrls.DOWNLOAD)
                             .with(downloadServlet)
+                        .filterAsOneLevelTemplate(JMAPUrls.DOWNLOAD)
+                            .with(new AllowAllCrossOriginRequests(bypass(authenticationFilter).on("GET").and("OPTIONS").only()))
+                            .only()
                         .build());
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/main/java/org/apache/james/jmap/api/ContinuationTokenManager.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/api/ContinuationTokenManager.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/api/ContinuationTokenManager.java
deleted file mode 100644
index d70b2d6..0000000
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/api/ContinuationTokenManager.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/****************************************************************
- * 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.api;
-
-import org.apache.james.jmap.model.ContinuationToken;
-
-public interface ContinuationTokenManager {
-    enum ContinuationTokenStatus {
-        OK,
-        INVALID,
-        EXPIRED
-    }
-
-    ContinuationToken generateToken(String username);
-    
-    ContinuationTokenStatus getValidity(ContinuationToken token);
-
-    boolean isValid(ContinuationToken token);
-
-}

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/main/java/org/apache/james/jmap/api/SimpleTokenFactory.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/api/SimpleTokenFactory.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/api/SimpleTokenFactory.java
new file mode 100644
index 0000000..be990a5
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/api/SimpleTokenFactory.java
@@ -0,0 +1,29 @@
+/****************************************************************
+ * 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.api;
+
+import org.apache.james.jmap.model.AttachmentAccessToken;
+import org.apache.james.jmap.model.ContinuationToken;
+
+public interface SimpleTokenFactory {
+    ContinuationToken generateContinuationToken(String username);
+
+    AttachmentAccessToken generateAttachmentAccessToken(String username, String blobId);
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/main/java/org/apache/james/jmap/api/SimpleTokenManager.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/api/SimpleTokenManager.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/api/SimpleTokenManager.java
new file mode 100644
index 0000000..9899cbb
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/api/SimpleTokenManager.java
@@ -0,0 +1,34 @@
+/****************************************************************
+ * 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.api;
+
+import org.apache.james.jmap.model.SignedExpiringToken;
+
+public interface SimpleTokenManager {
+    enum TokenStatus {
+        OK,
+        INVALID,
+        EXPIRED
+    }
+
+    TokenStatus getValidity(SignedExpiringToken token);
+
+    boolean isValid(SignedExpiringToken token);
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SignedContinuationTokenManager.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SignedContinuationTokenManager.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SignedContinuationTokenManager.java
deleted file mode 100644
index f9ff554..0000000
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SignedContinuationTokenManager.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/****************************************************************
- * 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.crypto;
-
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-
-import javax.inject.Inject;
-
-import org.apache.james.jmap.api.ContinuationTokenManager;
-import org.apache.james.jmap.model.ContinuationToken;
-import org.apache.james.util.date.ZonedDateTimeProvider;
-
-import com.google.common.base.Preconditions;
-
-public class SignedContinuationTokenManager implements ContinuationTokenManager {
-
-    private final SignatureHandler signatureHandler;
-    private final ZonedDateTimeProvider zonedDateTimeProvider;
-
-    @Inject
-    public SignedContinuationTokenManager(SignatureHandler signatureHandler, ZonedDateTimeProvider zonedDateTimeProvider) {
-        this.signatureHandler = signatureHandler;
-        this.zonedDateTimeProvider = zonedDateTimeProvider;
-    }
-
-    @Override
-    public ContinuationToken generateToken(String username) {
-        Preconditions.checkNotNull(username);
-        ZonedDateTime expirationTime = zonedDateTimeProvider.get().plusMinutes(15);
-        return new ContinuationToken(username,
-            expirationTime,
-            signatureHandler.sign(username + ContinuationToken.SEPARATOR + DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(expirationTime)));
-    }
-
-    @Override
-    public ContinuationTokenStatus getValidity(ContinuationToken token) {
-        Preconditions.checkNotNull(token);
-        if (! isCorrectlySigned(token)) {
-            return ContinuationTokenStatus.INVALID;
-        }
-        if (isExpired(token)) {
-            return ContinuationTokenStatus.EXPIRED;
-        }
-        return ContinuationTokenStatus.OK;
-    }
-    
-    @Override
-    public boolean isValid(ContinuationToken token) {
-        Preconditions.checkNotNull(token);
-        return ContinuationTokenStatus.OK.equals(getValidity(token));
-    }
-
-    private boolean isCorrectlySigned(ContinuationToken token) {
-        return signatureHandler.verify(token.getContent(), token.getSignature());
-    }
-
-    private boolean isExpired(ContinuationToken token) {
-        return token.getExpirationDate().isBefore(zonedDateTimeProvider.get());
-    }
-}

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SignedTokenFactory.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SignedTokenFactory.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SignedTokenFactory.java
new file mode 100644
index 0000000..7154a32
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SignedTokenFactory.java
@@ -0,0 +1,73 @@
+/****************************************************************
+ * 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.crypto;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import javax.inject.Inject;
+
+import org.apache.james.jmap.api.SimpleTokenFactory;
+import org.apache.james.jmap.model.AttachmentAccessToken;
+import org.apache.james.jmap.model.ContinuationToken;
+import org.apache.james.util.date.ZonedDateTimeProvider;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+public class SignedTokenFactory implements SimpleTokenFactory {
+
+    private final SignatureHandler signatureHandler;
+    private final ZonedDateTimeProvider zonedDateTimeProvider;
+
+    @Inject
+    public SignedTokenFactory(SignatureHandler signatureHandler, ZonedDateTimeProvider zonedDateTimeProvider) {
+        this.signatureHandler = signatureHandler;
+        this.zonedDateTimeProvider = zonedDateTimeProvider;
+    }
+
+    @Override
+    public ContinuationToken generateContinuationToken(String username) {
+        Preconditions.checkNotNull(username);
+        ZonedDateTime expirationTime = zonedDateTimeProvider.get().plusMinutes(15);
+        return new ContinuationToken(username,
+            expirationTime,
+            signatureHandler.sign(
+                    Joiner.on(ContinuationToken.SEPARATOR)
+                        .join(username,
+                            DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(expirationTime))));
+    }
+
+    @Override
+    public AttachmentAccessToken generateAttachmentAccessToken(String username, String blobId) {
+        Preconditions.checkArgument(! Strings.isNullOrEmpty(blobId));
+        ZonedDateTime expirationTime = zonedDateTimeProvider.get().plusMinutes(5);
+        return AttachmentAccessToken.builder()
+                .username(username)
+                .blobId(blobId)
+                .expirationDate(expirationTime)
+                .signature(signatureHandler.sign(Joiner.on(AttachmentAccessToken.SEPARATOR)
+                                                    .join(username, 
+                                                            blobId,
+                                                            DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(expirationTime))))
+                .build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SignedTokenManager.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SignedTokenManager.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SignedTokenManager.java
new file mode 100644
index 0000000..baa2244
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SignedTokenManager.java
@@ -0,0 +1,66 @@
+/****************************************************************
+ * 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.crypto;
+
+import javax.inject.Inject;
+
+import org.apache.james.jmap.api.SimpleTokenManager;
+import org.apache.james.jmap.model.SignedExpiringToken;
+import org.apache.james.util.date.ZonedDateTimeProvider;
+
+import com.google.common.base.Preconditions;
+
+public class SignedTokenManager implements SimpleTokenManager {
+
+    private final SignatureHandler signatureHandler;
+    private final ZonedDateTimeProvider zonedDateTimeProvider;
+
+    @Inject
+    public SignedTokenManager(SignatureHandler signatureHandler, ZonedDateTimeProvider zonedDateTimeProvider) {
+        this.signatureHandler = signatureHandler;
+        this.zonedDateTimeProvider = zonedDateTimeProvider;
+    }
+
+    @Override
+    public TokenStatus getValidity(SignedExpiringToken token) {
+        Preconditions.checkNotNull(token);
+        if (! isCorrectlySigned(token)) {
+            return TokenStatus.INVALID;
+        }
+        if (isExpired(token)) {
+            return TokenStatus.EXPIRED;
+        }
+        return TokenStatus.OK;
+    }
+    
+    @Override
+    public boolean isValid(SignedExpiringToken token) {
+        Preconditions.checkNotNull(token);
+        return TokenStatus.OK.equals(getValidity(token));
+    }
+
+    private boolean isCorrectlySigned(SignedExpiringToken token) {
+        return signatureHandler.verify(token.getSignedContent(), token.getSignature());
+    }
+
+    private boolean isExpired(SignedExpiringToken token) {
+        return token.getExpirationDate().isBefore(zonedDateTimeProvider.get());
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/AttachmentAccessToken.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/AttachmentAccessToken.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/AttachmentAccessToken.java
new file mode 100644
index 0000000..4d13c75
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/AttachmentAccessToken.java
@@ -0,0 +1,153 @@
+/****************************************************************
+ * 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.model;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Objects;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class AttachmentAccessToken implements SignedExpiringToken {
+
+    public static final String SEPARATOR = "_";
+
+    public static Builder builder() {
+        return new Builder();
+    }
+    
+    public static class Builder {
+        private String username;
+        private String blobId;
+        private ZonedDateTime expirationDate;
+        private String signature;
+
+        private Builder() {}
+        
+        public Builder blobId(String blobId) {
+            this.blobId = blobId;
+            return this;
+        }
+
+        public Builder username(String username) {
+            this.username = username;
+            return this;
+        }
+
+        public Builder expirationDate(ZonedDateTime expirationDate) {
+            this.expirationDate = expirationDate;
+            return this;
+        }
+
+        public Builder signature(String signature) {
+            this.signature = signature;
+            return this;
+        }
+
+        public AttachmentAccessToken build() {
+            Preconditions.checkNotNull(username);
+            Preconditions.checkNotNull(blobId);
+            Preconditions.checkArgument(! blobId.isEmpty());
+            Preconditions.checkNotNull(expirationDate);
+            Preconditions.checkNotNull(signature);
+            return new AttachmentAccessToken(username, blobId, expirationDate, signature);
+        }
+    }
+    
+    private final String username;
+    private final String blobId;
+    private final ZonedDateTime expirationDate;
+    private final String signature;
+
+    @VisibleForTesting
+    AttachmentAccessToken(String username, String blobId, ZonedDateTime expirationDate, String signature) {
+        this.username = username;
+        this.blobId = blobId;
+        this.expirationDate = expirationDate;
+        this.signature = signature;
+    }
+
+    public String getBlobId() {
+        return blobId;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    @Override
+    public ZonedDateTime getExpirationDate() {
+        return expirationDate;
+    }
+
+    @Override
+    public String getSignature() {
+        return signature;
+    }
+
+    public String serialize() {
+        return getPayload()
+            + SEPARATOR
+            + signature;
+    }
+    
+    @Override
+    public String getPayload() {
+        return username
+            + SEPARATOR
+            + DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(expirationDate);
+    }
+    
+    @Override
+    public String getSignedContent() {
+        return blobId
+            + SEPARATOR
+            + getPayload();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof AttachmentAccessToken) {
+            AttachmentAccessToken attachmentAccessToken = (AttachmentAccessToken) other;
+            return Objects.equals(username, attachmentAccessToken.username)
+                    && Objects.equals(blobId, attachmentAccessToken.blobId)
+                    && Objects.equals(expirationDate, attachmentAccessToken.expirationDate)
+                    && Objects.equals(signature, attachmentAccessToken.signature);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(username, blobId, expirationDate, signature);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("username", username)
+                .add("blobId", blobId)
+                .add("expirationDate", expirationDate)
+                .add("signature", signature)
+                .toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/ContinuationToken.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/ContinuationToken.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/ContinuationToken.java
index eadaf12..35cb32d 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/ContinuationToken.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/ContinuationToken.java
@@ -34,7 +34,7 @@ import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 
-public class ContinuationToken {
+public class ContinuationToken implements SignedExpiringToken {
 
     public static final String SEPARATOR = "_";
 
@@ -103,27 +103,35 @@ public class ContinuationToken {
         return username;
     }
 
+    @Override
     public ZonedDateTime getExpirationDate() {
         return expirationDate;
     }
 
+    @Override
     public String getSignature() {
         return signature;
     }
 
     public String serialize() {
-        return getContent()
+        return getPayload()
             + SEPARATOR
             + signature;
     }
     
-    public String getContent() {
+    @Override
+    public String getPayload() {
         return username
             + SEPARATOR
             + DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(expirationDate);
     }
 
     @Override
+    public String getSignedContent() {
+        return getPayload();
+    }
+
+    @Override
     public boolean equals(Object other) {
         if (other == null || getClass() != other.getClass()) {
             return false;

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SignedExpiringToken.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SignedExpiringToken.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SignedExpiringToken.java
new file mode 100644
index 0000000..2a2ef74
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SignedExpiringToken.java
@@ -0,0 +1,32 @@
+/****************************************************************
+ * 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.model;
+
+import java.time.ZonedDateTime;
+
+public interface SignedExpiringToken {
+
+    ZonedDateTime getExpirationDate();
+
+    String getSignedContent();
+
+    String getPayload();
+
+    String getSignature();
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/test/java/org/apache/james/jmap/DownloadServletTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/DownloadServletTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/DownloadServletTest.java
index ccf4992..de9058b 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/DownloadServletTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/DownloadServletTest.java
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.when;
 
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.james.jmap.api.SimpleTokenFactory;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.store.MailboxSessionMapperFactory;
@@ -35,7 +36,9 @@ public class DownloadServletTest {
 
     @Test
     public void blobIdFromShouldSkipTheFirstCharacter() {
-        String blobId = new DownloadServlet(null).blobIdFrom("1234");
+        MailboxSessionMapperFactory nullMailboxSessionMapperFactory = null;
+        SimpleTokenFactory nullSimpleTokenFactory = null;
+        String blobId = new DownloadServlet(nullMailboxSessionMapperFactory, nullSimpleTokenFactory).blobIdFrom("1234");
         assertThat(blobId).isEqualTo("234");
     }
 
@@ -45,8 +48,9 @@ public class DownloadServletTest {
         MailboxSessionMapperFactory mailboxSessionMapperFactory = mock(MailboxSessionMapperFactory.class);
         when(mailboxSessionMapperFactory.createAttachmentMapper(mailboxSession))
             .thenThrow(new MailboxException());
+        SimpleTokenFactory nullSimpleTokenFactory = null;
 
-        DownloadServlet testee = new DownloadServlet(mailboxSessionMapperFactory);
+        DownloadServlet testee = new DownloadServlet(mailboxSessionMapperFactory, nullSimpleTokenFactory);
 
         String blobId = null;
         HttpServletResponse resp = mock(HttpServletResponse.class);

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SignedContinuationTokenManagerTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SignedContinuationTokenManagerTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SignedContinuationTokenManagerTest.java
deleted file mode 100644
index d853c3b..0000000
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SignedContinuationTokenManagerTest.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/****************************************************************
- * 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.crypto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-
-import org.apache.james.jmap.FixedDateZonedDateTimeProvider;
-import org.apache.james.jmap.api.ContinuationTokenManager.ContinuationTokenStatus;
-import org.apache.james.jmap.model.ContinuationToken;
-import org.junit.Before;
-import org.junit.Test;
-
-public class SignedContinuationTokenManagerTest {
-
-    private static final String EXPIRATION_DATE_STRING = "2011-12-03T10:15:30+01:00";
-    private static final String FAKE_SIGNATURE = "MeIFNei4p6vn085wCEw0pbEwJ+Oak5yEIRLZsDcRVzT9rWWOcLvDFUA3S6awi/bxPiFxqJFreVz6xqzehnUI4tUBupk3sIsqeXShhFWBpaV+m58mC41lT/A0RJa3GgCvg6kmweCRf3tOo0+gvwOQJdwCL2B21GjDCKqBHaiK+OHcsSjrQW0xuew5z84EAz3ErdH4MMNjITksxK5FG/cGQ9V6LQgwcPk0RrprVC4eY7FFHw/sQNlJpZKsSFLnn5igPQkQtjiQ4ay1/xoB7FU7aJLakxRhYOnTKgper/Ur7UWOZJaE+4EjcLwCFLF9GaCILwp9W+mf/f7j92PVEU50Vg==";
-    private static final ZonedDateTime DATE = ZonedDateTime.parse(EXPIRATION_DATE_STRING, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
-
-    private SignedContinuationTokenManager toKenManager;
-    private FixedDateZonedDateTimeProvider zonedDateTimeProvider;
-
-    @Before
-    public void setUp() throws Exception {
-        JamesSignatureHandler signatureHandler = new JamesSignatureHandlerProvider().provide();
-        zonedDateTimeProvider = new FixedDateZonedDateTimeProvider();
-        toKenManager = new SignedContinuationTokenManager(signatureHandler, zonedDateTimeProvider);
-    }
-
-    @Test(expected = NullPointerException.class)
-    public void isValidShouldThrowWhenTokenIsNull() throws Exception {
-        toKenManager.isValid(null);
-    }
-
-    @Test
-    public void isValidShouldRecognizeValidTokens() throws Exception {
-        zonedDateTimeProvider.setFixedDateTime(DATE);
-        assertThat(
-            toKenManager.isValid(
-                toKenManager.generateToken("user")))
-            .isTrue();
-    }
-
-    @Test
-    public void isValidShouldRecognizeTokenWhereUsernameIsModified() throws Exception {
-        zonedDateTimeProvider.setFixedDateTime(DATE);
-        ContinuationToken continuationToken = toKenManager.generateToken("user");
-        ContinuationToken pirateContinuationToken = new ContinuationToken("pirate",
-            continuationToken.getExpirationDate(),
-            continuationToken.getSignature());
-        assertThat(toKenManager.isValid(pirateContinuationToken)).isFalse();
-    }
-
-    @Test
-    public void isValidShouldRecognizeTokenWhereExpirationDateIsModified() throws Exception {
-        zonedDateTimeProvider.setFixedDateTime(DATE);
-        ContinuationToken continuationToken = toKenManager.generateToken("user");
-        ContinuationToken pirateContinuationToken = new ContinuationToken(continuationToken.getUsername(),
-            continuationToken.getExpirationDate().plusHours(1),
-            continuationToken.getSignature());
-        assertThat(toKenManager.isValid(pirateContinuationToken)).isFalse();
-    }
-
-    @Test
-    public void isValidShouldRecognizeTokenWhereSignatureIsModified() throws Exception {
-        zonedDateTimeProvider.setFixedDateTime(DATE);
-        ContinuationToken continuationToken = toKenManager.generateToken("user");
-        ContinuationToken pirateContinuationToken = new ContinuationToken(continuationToken.getUsername(),
-            continuationToken.getExpirationDate(),
-            FAKE_SIGNATURE);
-        assertThat(toKenManager.isValid(pirateContinuationToken)).isFalse();
-    }
-
-    @Test
-    public void isValidShouldReturnFalseWhenTokenIsOutdated() throws Exception {
-        zonedDateTimeProvider.setFixedDateTime(DATE);
-        ContinuationToken continuationToken = toKenManager.generateToken("user");
-        zonedDateTimeProvider.setFixedDateTime(DATE.plusHours(1));
-        assertThat(toKenManager.isValid(continuationToken)).isFalse();
-    }
-
-    @Test
-    public void isValidShouldReturnFalseOnNonValidSignatures() throws Exception {
-        zonedDateTimeProvider.setFixedDateTime(DATE);
-        ContinuationToken pirateContinuationToken = new ContinuationToken("user", DATE.plusMinutes(15), "fake");
-        assertThat(toKenManager.isValid(pirateContinuationToken)).isFalse();
-    }
-
-    @Test(expected = NullPointerException.class)
-    public void getValidityShouldThrowWhenTokenIsNull() throws Exception {
-        toKenManager.getValidity(null);
-    }
-
-    @Test
-    public void getValidityShouldRecognizeValidTokens() throws Exception {
-        zonedDateTimeProvider.setFixedDateTime(DATE);
-        assertThat(
-            toKenManager.getValidity(
-                toKenManager.generateToken("user")))
-            .isEqualTo(ContinuationTokenStatus.OK);
-    }
-
-    @Test
-    public void getValidityShouldRecognizeTokenWhereUsernameIsModified() throws Exception {
-        zonedDateTimeProvider.setFixedDateTime(DATE);
-        ContinuationToken continuationToken = toKenManager.generateToken("user");
-        ContinuationToken pirateContinuationToken = new ContinuationToken("pirate",
-            continuationToken.getExpirationDate(),
-            continuationToken.getSignature());
-        assertThat(toKenManager.getValidity(pirateContinuationToken)).isEqualTo(ContinuationTokenStatus.INVALID);
-    }
-
-    @Test
-    public void getValidityhouldRecognizeTokenWhereExpirationDateIsModified() throws Exception {
-        zonedDateTimeProvider.setFixedDateTime(DATE);
-        ContinuationToken continuationToken = toKenManager.generateToken("user");
-        ContinuationToken pirateContinuationToken = new ContinuationToken(continuationToken.getUsername(),
-            continuationToken.getExpirationDate().plusHours(1),
-            continuationToken.getSignature());
-        assertThat(toKenManager.getValidity(pirateContinuationToken)).isEqualTo(ContinuationTokenStatus.INVALID);
-    }
-
-    @Test
-    public void getValidityShouldRecognizeTokenWhereSignatureIsModified() throws Exception {
-        zonedDateTimeProvider.setFixedDateTime(DATE);
-        ContinuationToken continuationToken = toKenManager.generateToken("user");
-        ContinuationToken pirateContinuationToken = new ContinuationToken(continuationToken.getUsername(),
-            continuationToken.getExpirationDate(),
-            FAKE_SIGNATURE);
-        assertThat(toKenManager.getValidity(pirateContinuationToken)).isEqualTo(ContinuationTokenStatus.INVALID);
-    }
-
-    @Test
-    public void getValidityShouldReturnFalseWhenTokenIsOutdated() throws Exception {
-        zonedDateTimeProvider.setFixedDateTime(DATE);
-        ContinuationToken continuationToken = toKenManager.generateToken("user");
-        zonedDateTimeProvider.setFixedDateTime(DATE.plusHours(1));
-        assertThat(toKenManager.getValidity(continuationToken)).isEqualTo(ContinuationTokenStatus.EXPIRED);
-    }
-
-    @Test
-    public void getValidityShouldReturnFalseOnNonValidSignatures() throws Exception {
-        zonedDateTimeProvider.setFixedDateTime(DATE);
-        ContinuationToken pirateContinuationToken = new ContinuationToken("user", DATE.plusMinutes(15), "fake");
-        assertThat(toKenManager.getValidity(pirateContinuationToken)).isEqualTo(ContinuationTokenStatus.INVALID);
-    }
-
-    @Test(expected = NullPointerException.class)
-    public void generateTokenShouldThrowWhenUsernameIsNull() throws Exception {
-        toKenManager.generateToken(null);
-    }
-
-    @Test
-    public void generateTokenShouldHaveTheRightOutPut() throws Exception {
-        zonedDateTimeProvider.setFixedDateTime(DATE);
-        assertThat(toKenManager.generateToken("user").serialize())
-            .isEqualTo("user_2011-12-03T10:30:30+01:00_eOvOqTmV3dPrhIkbuQSj2sno3YJMxWl6J1sH1JhwYcaNgMX9twm98/WSF9uyDkvJgvBxFokDr53AbxQ3DsJysB2dAzCC0tUM4u8ZMvl/hQrFXhVCdpVMyHRvixKCxnHsVXAr9g3WMn2vbIVq5i3HPgA6/p9FB1+N4WA06B8ueoCrdxT2w1ITEm8p+QZvje3n1F344SgrqgIYqvt0yUvzxnB24f3ccjAKidlBj4wZkcXgUTMbZ7MdnCbDGbp10+tgJqxiv1S0rXZMeJLJ+vBt5TyqEhsJUmUQ84qctlB4yR5FS+ncbAOyZAxs2dWsHqiQjedb3IR77N7CASzqO2mmVw==");
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SignedTokenFactoryTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SignedTokenFactoryTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SignedTokenFactoryTest.java
new file mode 100644
index 0000000..371083f
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SignedTokenFactoryTest.java
@@ -0,0 +1,79 @@
+/****************************************************************
+ * 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.crypto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.apache.james.jmap.FixedDateZonedDateTimeProvider;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SignedTokenFactoryTest {
+
+    private static final String EXPIRATION_DATE_STRING = "2011-12-03T10:15:30+01:00";
+    private static final ZonedDateTime DATE = ZonedDateTime.parse(EXPIRATION_DATE_STRING, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+
+    private SignedTokenFactory toKenFactory;
+    private FixedDateZonedDateTimeProvider zonedDateTimeProvider;
+
+    @Before
+    public void setUp() throws Exception {
+        JamesSignatureHandler signatureHandler = new JamesSignatureHandlerProvider().provide();
+        zonedDateTimeProvider = new FixedDateZonedDateTimeProvider();
+        toKenFactory = new SignedTokenFactory(signatureHandler, zonedDateTimeProvider);
+    }
+
+    @Test
+    public void generateContinuationTokenShouldThrowWhenUsernameIsNull() throws Exception {
+        assertThatThrownBy(() -> toKenFactory.generateContinuationToken(null))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    public void generateContinuationTokenShouldHaveTheRightOutPut() throws Exception {
+        zonedDateTimeProvider.setFixedDateTime(DATE);
+        assertThat(toKenFactory.generateContinuationToken("user").serialize())
+            .isEqualTo("user_2011-12-03T10:30:30+01:00_eOvOqTmV3dPrhIkbuQSj2sno3YJMxWl6J1sH1JhwYcaNgMX9twm98/WSF9uyDkvJgvBxFokDr53AbxQ3DsJysB2dAzCC0tUM4u8ZMvl/hQrFXhVCdpVMyHRvixKCxnHsVXAr9g3WMn2vbIVq5i3HPgA6/p9FB1+N4WA06B8ueoCrdxT2w1ITEm8p+QZvje3n1F344SgrqgIYqvt0yUvzxnB24f3ccjAKidlBj4wZkcXgUTMbZ7MdnCbDGbp10+tgJqxiv1S0rXZMeJLJ+vBt5TyqEhsJUmUQ84qctlB4yR5FS+ncbAOyZAxs2dWsHqiQjedb3IR77N7CASzqO2mmVw==");
+    }
+
+    @Test
+    public void generateAttachmentAccessTokenShouldThrowWhenUsernameIsNull() throws Exception {
+        assertThatThrownBy(() -> toKenFactory.generateAttachmentAccessToken(null, "blobId"))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    public void generateAttachmentAccessTokenShouldThrowWhenBlobIdIsNull() throws Exception {
+        assertThatThrownBy(() -> toKenFactory.generateAttachmentAccessToken("username", null))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void generateAttachmentAccessTokenShouldHaveTheRightOutPut() throws Exception {
+        zonedDateTimeProvider.setFixedDateTime(DATE);
+        assertThat(toKenFactory.generateAttachmentAccessToken("user", "blobId").serialize())
+            .isEqualTo("user_2011-12-03T10:20:30+01:00_UAmjTzvmmIwvE1Yw54tE7jC1Q2nCJ1l3XX1703kYmLIeOZe7fNSLM6V8CzPFEvZ+Y4H+UD4UTkNHbmgcPbxesITnby+UfT/tIiTppJhXJvtTxSoTy9vuAJrW9/kJh6CruqtSM+BUEkLKuuzJySmvDkaHSaXwot4egGXaJ9yHgjEh2PT3uA0O0JjRNB2x8oa370fFSZsT2QgXrqeqHWWO1j6IrAf4UcyhvjNkJBK9TVNubfqGKuCZ4dz2Rm/CUvp13CpzUoVqBS1nJ1VaIw94L2rX8RkAMTlV7AXKB3kPiBX7MdGp2NBiAUlYlOLjflYl8plnv/QrRCmfGxnsvv4WVQ==");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SignedTokenManagerTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SignedTokenManagerTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SignedTokenManagerTest.java
new file mode 100644
index 0000000..c371f0b
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SignedTokenManagerTest.java
@@ -0,0 +1,171 @@
+/****************************************************************
+ * 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.crypto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.apache.james.jmap.FixedDateZonedDateTimeProvider;
+import org.apache.james.jmap.api.SimpleTokenManager.TokenStatus;
+import org.apache.james.jmap.model.ContinuationToken;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SignedTokenManagerTest {
+
+    private static final String EXPIRATION_DATE_STRING = "2011-12-03T10:15:30+01:00";
+    private static final String FAKE_SIGNATURE = "MeIFNei4p6vn085wCEw0pbEwJ+Oak5yEIRLZsDcRVzT9rWWOcLvDFUA3S6awi/bxPiFxqJFreVz6xqzehnUI4tUBupk3sIsqeXShhFWBpaV+m58mC41lT/A0RJa3GgCvg6kmweCRf3tOo0+gvwOQJdwCL2B21GjDCKqBHaiK+OHcsSjrQW0xuew5z84EAz3ErdH4MMNjITksxK5FG/cGQ9V6LQgwcPk0RrprVC4eY7FFHw/sQNlJpZKsSFLnn5igPQkQtjiQ4ay1/xoB7FU7aJLakxRhYOnTKgper/Ur7UWOZJaE+4EjcLwCFLF9GaCILwp9W+mf/f7j92PVEU50Vg==";
+    private static final ZonedDateTime DATE = ZonedDateTime.parse(EXPIRATION_DATE_STRING, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+
+    private SignedTokenManager tokenManager;
+    private SignedTokenFactory tokenFactory;
+    private FixedDateZonedDateTimeProvider zonedDateTimeProvider;
+
+    @Before
+    public void setUp() throws Exception {
+        JamesSignatureHandler signatureHandler = new JamesSignatureHandlerProvider().provide();
+        zonedDateTimeProvider = new FixedDateZonedDateTimeProvider();
+        tokenManager = new SignedTokenManager(signatureHandler, zonedDateTimeProvider);
+        tokenFactory = new SignedTokenFactory(signatureHandler, zonedDateTimeProvider);
+
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void isValidShouldThrowWhenTokenIsNull() throws Exception {
+        tokenManager.isValid(null);
+    }
+
+    @Test
+    public void isValidShouldRecognizeValidTokens() throws Exception {
+        zonedDateTimeProvider.setFixedDateTime(DATE);
+        assertThat(
+            tokenManager.isValid(
+                tokenFactory.generateContinuationToken("user")))
+            .isTrue();
+    }
+
+    @Test
+    public void isValidShouldRecognizeTokenWhereUsernameIsModified() throws Exception {
+        zonedDateTimeProvider.setFixedDateTime(DATE);
+        ContinuationToken continuationToken = tokenFactory.generateContinuationToken("user");
+        ContinuationToken pirateContinuationToken = new ContinuationToken("pirate",
+            continuationToken.getExpirationDate(),
+            continuationToken.getSignature());
+        assertThat(tokenManager.isValid(pirateContinuationToken)).isFalse();
+    }
+
+    @Test
+    public void isValidShouldRecognizeTokenWhereExpirationDateIsModified() throws Exception {
+        zonedDateTimeProvider.setFixedDateTime(DATE);
+        ContinuationToken continuationToken = tokenFactory.generateContinuationToken("user");
+        ContinuationToken pirateContinuationToken = new ContinuationToken(continuationToken.getUsername(),
+            continuationToken.getExpirationDate().plusHours(1),
+            continuationToken.getSignature());
+        assertThat(tokenManager.isValid(pirateContinuationToken)).isFalse();
+    }
+
+    @Test
+    public void isValidShouldRecognizeTokenWhereSignatureIsModified() throws Exception {
+        zonedDateTimeProvider.setFixedDateTime(DATE);
+        ContinuationToken continuationToken = tokenFactory.generateContinuationToken("user");
+        ContinuationToken pirateContinuationToken = new ContinuationToken(continuationToken.getUsername(),
+            continuationToken.getExpirationDate(),
+            FAKE_SIGNATURE);
+        assertThat(tokenManager.isValid(pirateContinuationToken)).isFalse();
+    }
+
+    @Test
+    public void isValidShouldReturnFalseWhenTokenIsOutdated() throws Exception {
+        zonedDateTimeProvider.setFixedDateTime(DATE);
+        ContinuationToken continuationToken = tokenFactory.generateContinuationToken("user");
+        zonedDateTimeProvider.setFixedDateTime(DATE.plusHours(1));
+        assertThat(tokenManager.isValid(continuationToken)).isFalse();
+    }
+
+    @Test
+    public void isValidShouldReturnFalseOnNonValidSignatures() throws Exception {
+        zonedDateTimeProvider.setFixedDateTime(DATE);
+        ContinuationToken pirateContinuationToken = new ContinuationToken("user", DATE.plusMinutes(15), "fake");
+        assertThat(tokenManager.isValid(pirateContinuationToken)).isFalse();
+    }
+
+    @Test
+    public void getValidityShouldThrowWhenTokenIsNull() throws Exception {
+        assertThatThrownBy(() -> tokenManager.getValidity(null))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    public void getValidityShouldRecognizeValidTokens() throws Exception {
+        zonedDateTimeProvider.setFixedDateTime(DATE);
+        assertThat(
+            tokenManager.getValidity(
+                tokenFactory.generateContinuationToken("user")))
+            .isEqualTo(TokenStatus.OK);
+    }
+
+    @Test
+    public void getValidityShouldRecognizeTokenWhereUsernameIsModified() throws Exception {
+        zonedDateTimeProvider.setFixedDateTime(DATE);
+        ContinuationToken continuationToken = tokenFactory.generateContinuationToken("user");
+        ContinuationToken pirateContinuationToken = new ContinuationToken("pirate",
+            continuationToken.getExpirationDate(),
+            continuationToken.getSignature());
+        assertThat(tokenManager.getValidity(pirateContinuationToken)).isEqualTo(TokenStatus.INVALID);
+    }
+
+    @Test
+    public void getValidityhouldRecognizeTokenWhereExpirationDateIsModified() throws Exception {
+        zonedDateTimeProvider.setFixedDateTime(DATE);
+        ContinuationToken continuationToken = tokenFactory.generateContinuationToken("user");
+        ContinuationToken pirateContinuationToken = new ContinuationToken(continuationToken.getUsername(),
+            continuationToken.getExpirationDate().plusHours(1),
+            continuationToken.getSignature());
+        assertThat(tokenManager.getValidity(pirateContinuationToken)).isEqualTo(TokenStatus.INVALID);
+    }
+
+    @Test
+    public void getValidityShouldRecognizeTokenWhereSignatureIsModified() throws Exception {
+        zonedDateTimeProvider.setFixedDateTime(DATE);
+        ContinuationToken continuationToken = tokenFactory.generateContinuationToken("user");
+        ContinuationToken pirateContinuationToken = new ContinuationToken(continuationToken.getUsername(),
+            continuationToken.getExpirationDate(),
+            FAKE_SIGNATURE);
+        assertThat(tokenManager.getValidity(pirateContinuationToken)).isEqualTo(TokenStatus.INVALID);
+    }
+
+    @Test
+    public void getValidityShouldReturnFalseWhenTokenIsOutdated() throws Exception {
+        zonedDateTimeProvider.setFixedDateTime(DATE);
+        ContinuationToken continuationToken = tokenFactory.generateContinuationToken("user");
+        zonedDateTimeProvider.setFixedDateTime(DATE.plusHours(1));
+        assertThat(tokenManager.getValidity(continuationToken)).isEqualTo(TokenStatus.EXPIRED);
+    }
+
+    @Test
+    public void getValidityShouldReturnFalseOnNonValidSignatures() throws Exception {
+        zonedDateTimeProvider.setFixedDateTime(DATE);
+        ContinuationToken pirateContinuationToken = new ContinuationToken("user", DATE.plusMinutes(15), "fake");
+        assertThat(tokenManager.getValidity(pirateContinuationToken)).isEqualTo(TokenStatus.INVALID);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/a5bd1c74/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/AttachmentAccessTokenTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/AttachmentAccessTokenTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/AttachmentAccessTokenTest.java
new file mode 100644
index 0000000..5772e88
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/AttachmentAccessTokenTest.java
@@ -0,0 +1,99 @@
+/****************************************************************
+ * 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.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.junit.Test;
+
+public class AttachmentAccessTokenTest {
+
+    private static final String USERNAME = "username";
+    private static final String BLOB_ID = "blobId";
+    private static final String EXPIRATION_DATE_STRING = "2011-12-03T10:15:30+01:00";
+    private static final ZonedDateTime EXPIRATION_DATE = ZonedDateTime.parse(EXPIRATION_DATE_STRING, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+    private static final String SIGNATURE = "signature";
+
+    @Test
+    public void getAsStringShouldNotContainBlobId() throws Exception {
+        assertThat(new AttachmentAccessToken(USERNAME, BLOB_ID, EXPIRATION_DATE, SIGNATURE).serialize())
+            .isEqualTo(USERNAME + AttachmentAccessToken.SEPARATOR + EXPIRATION_DATE_STRING + AttachmentAccessToken.SEPARATOR + SIGNATURE);
+    }
+
+    @Test
+    public void getPayloadShouldNotContainBlobId() throws Exception {
+        assertThat(new AttachmentAccessToken(USERNAME, BLOB_ID, EXPIRATION_DATE, SIGNATURE).getPayload())
+            .isEqualTo(USERNAME + AttachmentAccessToken.SEPARATOR + EXPIRATION_DATE_STRING);
+    }
+
+    @Test
+    public void getSignedContentShouldContainBlobId() throws Exception {
+        assertThat(new AttachmentAccessToken(USERNAME, BLOB_ID, EXPIRATION_DATE, SIGNATURE).getSignedContent())
+            .isEqualTo(BLOB_ID + AttachmentAccessToken.SEPARATOR + USERNAME + AttachmentAccessToken.SEPARATOR + EXPIRATION_DATE_STRING);
+    }
+    
+    public void buildWithNullUsernameShouldThrow() {
+        assertThatThrownBy(() -> AttachmentAccessToken.builder()
+            .username(null)
+            .build()
+        ).isInstanceOf(NullPointerException.class);
+    }
+
+    public void buildWithNullBlobIdShouldThrow() {
+        assertThatThrownBy(() -> AttachmentAccessToken.builder()
+            .username(USERNAME)
+            .blobId(null)
+            .build()
+        ).isInstanceOf(NullPointerException.class);
+    }
+
+    public void buildWithNullExpirationDateShouldThrow() {
+        assertThatThrownBy(() -> AttachmentAccessToken.builder()
+            .username(USERNAME)
+            .blobId(BLOB_ID)
+            .expirationDate(null)
+            .build()
+        ).isInstanceOf(NullPointerException.class);
+    }
+
+    public void buildWithNullSignatureShouldThrow() {
+        assertThatThrownBy(() -> AttachmentAccessToken.builder()
+            .username(USERNAME)
+            .blobId(BLOB_ID)
+            .expirationDate(EXPIRATION_DATE)
+            .signature(null)
+            .build()
+        ).isInstanceOf(NullPointerException.class);
+    }
+
+    public void buildWithValidArgumentsShouldBuild() {
+        AttachmentAccessToken expected = new AttachmentAccessToken(USERNAME, BLOB_ID, EXPIRATION_DATE, SIGNATURE);
+        AttachmentAccessToken actual = AttachmentAccessToken.builder()
+            .username(USERNAME)
+            .blobId(BLOB_ID)
+            .expirationDate(EXPIRATION_DATE)
+            .signature(SIGNATURE)
+            .build();
+        assertThat(actual).isEqualToComparingFieldByField(expected);
+    }
+}


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


Mime
View raw message