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 <[email protected]> Authored: Tue Jun 28 09:49:50 2016 +0200 Committer: Raphael Ouazana <[email protected]> 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 "[email protected]" + And "[email protected]" has a mailbox "inbox" + + Scenario: Asking for an attachment access token with an unknown blobId + When "[email protected]" 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 "[email protected]" mailbox "inbox" contains a message "1" with an attachment "2" + When "[email protected]" 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: [email protected] For additional commands, e-mail: [email protected]
