Author: matthieu
Date: Fri Dec 11 10:05:39 2015
New Revision: 1719306
URL: http://svn.apache.org/viewvc?rev=1719306&view=rev
Log:
JAMES-1644 JMAP Authentication Servlet Handle two different requests with
Jackson
Added:
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/MultipleClassesDeserializer.java
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/MultipleObjectMapperBuilder.java
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/AccessTokenRequest.java
james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/
james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/MultipleObjectMapperTest.java
Modified:
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/AuthenticationServlet.java
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/ContinuationTokenRequest.java
james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPAuthenticationTest.java
Modified:
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/AuthenticationServlet.java
URL:
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/AuthenticationServlet.java?rev=1719306&r1=1719305&r2=1719306&view=diff
==============================================================================
---
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/AuthenticationServlet.java
(original)
+++
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/AuthenticationServlet.java
Fri Dec 11 10:05:39 2015
@@ -25,6 +25,8 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.apache.james.jmap.json.MultipleObjectMapperBuilder;
+import org.apache.james.jmap.model.AccessTokenRequest;
import org.apache.james.jmap.model.ContinuationTokenRequest;
import org.apache.james.jmap.model.ContinuationTokenResponse;
import org.slf4j.Logger;
@@ -39,7 +41,10 @@ public class AuthenticationServlet exten
private static final Logger LOG =
LoggerFactory.getLogger(AuthenticationServlet.class);
- private ObjectMapper mapper = new ObjectMapper();
+ private ObjectMapper mapper = new MultipleObjectMapperBuilder()
+ .registerClass(ContinuationTokenRequest.UNIQUE_JSON_PATH,
ContinuationTokenRequest.class)
+ .registerClass(AccessTokenRequest.UNIQUE_JSON_PATH,
AccessTokenRequest.class)
+ .build();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
@@ -52,15 +57,33 @@ public class AuthenticationServlet exten
return;
}
- ContinuationTokenRequest continuationTokenRequest;
+ Object request;
try {
- continuationTokenRequest = mapper.readValue(req.getReader(),
ContinuationTokenRequest.class);
+ request = mapper.readValue(req.getReader(), Object.class);
} catch (Exception e) {
LOG.warn("Invalid authentication request received.", e);
resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
+ if (request instanceof ContinuationTokenRequest) {
+ handleContinuationTokenRequest((ContinuationTokenRequest)request,
resp);
+ } else if (request instanceof AccessTokenRequest) {
+ handleAccessTokenRequest((AccessTokenRequest)request, resp);
+ }
+ }
+
+ private boolean checkJsonContentType(HttpServletRequest req) {
+ return req.getContentType().equals(JSON_CONTENT_TYPE_UTF8);
+ }
+
+ private boolean checkAcceptJsonOnly(HttpServletRequest req) {
+ String accept = req.getHeader("Accept");
+ return accept != null && accept.contains(JSON_CONTENT_TYPE);
+ }
+
+
+ private void handleContinuationTokenRequest(ContinuationTokenRequest
request, HttpServletResponse resp) throws IOException {
resp.setContentType(JSON_CONTENT_TYPE_UTF8);
ContinuationTokenResponse continuationTokenResponse =
ContinuationTokenResponse
@@ -71,15 +94,10 @@ public class AuthenticationServlet exten
.build();
mapper.writeValue(resp.getOutputStream(), continuationTokenResponse);
-
}
- private boolean checkJsonContentType(HttpServletRequest req) {
- return req.getContentType().equals(JSON_CONTENT_TYPE_UTF8);
+ private void handleAccessTokenRequest(AccessTokenRequest request,
HttpServletResponse resp) throws IOException {
+ resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
- private boolean checkAcceptJsonOnly(HttpServletRequest req) {
- String accept = req.getHeader("Accept");
- return accept != null && accept.contains(JSON_CONTENT_TYPE);
- }
}
Added:
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/MultipleClassesDeserializer.java
URL:
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/MultipleClassesDeserializer.java?rev=1719306&view=auto
==============================================================================
---
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/MultipleClassesDeserializer.java
(added)
+++
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/MultipleClassesDeserializer.java
Fri Dec 11 10:05:39 2015
@@ -0,0 +1,67 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+package org.apache.james.jmap.json;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+
+public class MultipleClassesDeserializer extends StdDeserializer<Object> {
+
+ private Map<String, Class<?>> registry = new HashMap<String, Class<?>>();
+
+ MultipleClassesDeserializer() {
+ super(Object.class);
+ }
+
+ @Override
+ public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
+ ObjectMapper mapper = (ObjectMapper) p.getCodec();
+ final JsonNode root = mapper.readTree(p);
+
+ return registry.entrySet().stream()
+ .filter(req -> ! (root.at(req.getKey()).isMissingNode()))
+ .map(x -> readValue(mapper, root, x.getValue()))
+ .findFirst()
+ .orElseThrow(() -> new JsonMappingException("Can't map request
to a known registered class"));
+ }
+
+ private Object readValue(ObjectMapper mapper, final JsonNode root,
Class<?> clazz) {
+ try {
+ return mapper.treeToValue(root, clazz);
+ } catch (JsonProcessingException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ public void registerClass(String uniqueJsonPath, Class<?> clazz) {
+ Preconditions.checkArgument(! registry.containsKey(uniqueJsonPath),
"Path %s has already been registered", uniqueJsonPath);
+ registry.put(uniqueJsonPath, clazz);
+ }
+}
Added:
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/MultipleObjectMapperBuilder.java
URL:
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/MultipleObjectMapperBuilder.java?rev=1719306&view=auto
==============================================================================
---
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/MultipleObjectMapperBuilder.java
(added)
+++
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/MultipleObjectMapperBuilder.java
Fri Dec 11 10:05:39 2015
@@ -0,0 +1,40 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+package org.apache.james.jmap.json;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+public class MultipleObjectMapperBuilder {
+
+ private MultipleClassesDeserializer multipleClassesDeserializer = new
MultipleClassesDeserializer();
+
+ public MultipleObjectMapperBuilder registerClass(String uniqueJsonPath,
Class<?> clazz) {
+ multipleClassesDeserializer.registerClass(uniqueJsonPath, clazz);
+ return this;
+ }
+
+ public ObjectMapper build() {
+ ObjectMapper mapper = new ObjectMapper();
+ SimpleModule module = new SimpleModule();
+ module.addDeserializer(Object.class, multipleClassesDeserializer);
+ mapper.registerModule(module);
+ return mapper;
+ }
+}
Added:
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/AccessTokenRequest.java
URL:
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/AccessTokenRequest.java?rev=1719306&view=auto
==============================================================================
---
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/AccessTokenRequest.java
(added)
+++
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/AccessTokenRequest.java
Fri Dec 11 10:05:39 2015
@@ -0,0 +1,83 @@
+/****************************************************************
+ * 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 com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+
+@JsonDeserialize(builder=AccessTokenRequest.Builder.class)
+public class AccessTokenRequest {
+
+ public static final String UNIQUE_JSON_PATH = "/token";
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @JsonPOJOBuilder(withPrefix="")
+ public static class Builder {
+
+ private String token;
+ private String method;
+ private String password;
+
+ private Builder() {}
+
+ public Builder token(String token) {
+ this.token = token;
+ return this;
+ }
+
+ public Builder method(String method) {
+ this.method = method;
+ return this;
+ }
+
+ public Builder password(String password) {
+ this.password = password;
+ return this;
+ }
+
+ public AccessTokenRequest build() {
+ return new AccessTokenRequest(token, method, password);
+ }
+ }
+
+ private final String token;
+ private final String method;
+ private final String password;
+
+ private AccessTokenRequest(String token, String method, String password) {
+ this.token = token;
+ this.method = method;
+ this.password = password;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public String getMethod() {
+ return method;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+}
Modified:
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/ContinuationTokenRequest.java
URL:
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/ContinuationTokenRequest.java?rev=1719306&r1=1719305&r2=1719306&view=diff
==============================================================================
---
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/ContinuationTokenRequest.java
(original)
+++
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/ContinuationTokenRequest.java
Fri Dec 11 10:05:39 2015
@@ -24,6 +24,8 @@ import com.fasterxml.jackson.databind.an
@JsonDeserialize(builder=ContinuationTokenRequest.Builder.class)
public class ContinuationTokenRequest {
+ public static final String UNIQUE_JSON_PATH = "/username";
+
public static Builder builder() {
return new Builder();
}
Modified:
james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPAuthenticationTest.java
URL:
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPAuthenticationTest.java?rev=1719306&r1=1719305&r2=1719306&view=diff
==============================================================================
---
james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPAuthenticationTest.java
(original)
+++
james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPAuthenticationTest.java
Fri Dec 11 10:05:39 2015
@@ -19,6 +19,7 @@
package org.apache.james.jmap;
import static com.jayway.restassured.RestAssured.given;
+import static com.jayway.restassured.RestAssured.with;
import static com.jayway.restassured.config.EncoderConfig.encoderConfig;
import static com.jayway.restassured.config.RestAssuredConfig.newConfig;
import static org.hamcrest.Matchers.hasItem;
@@ -145,8 +146,21 @@ public class JMAPAuthenticationTest {
.contentType(ContentType.JSON);
}
+
+ @Test
+ public void mustReturnMalformedRequestWhenBodyIsNotAcceptable() {
+ given()
+ .contentType(ContentType.JSON)
+ .accept(ContentType.JSON)
+ .body("{\"badAttributeName\": \"value\"}")
+ .when()
+ .post("/authentication")
+ .then()
+ .statusCode(400);
+ }
+
@Test
- public void getContinuationTokenWhenValidResquest() {
+ public void methodShouldContainPasswordWhenValidResquest() {
given()
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
@@ -155,9 +169,43 @@ public class JMAPAuthenticationTest {
.post("/authentication")
.then()
.statusCode(200)
- .body("continuationToken", isA(String.class))
.body("methods", hasItem("password"));
}
+ @Test
+ public void mustReturnContinuationTokenWhenValidResquest() {
+ given()
+ .contentType(ContentType.JSON)
+ .accept(ContentType.JSON)
+ .body("{\"username\": \"[email protected]\", \"clientName\":
\"Mozilla Thunderbird\", \"clientVersion\": \"42.0\", \"deviceName\": \"Joe
Bloggâs iPhone\"}")
+ .when()
+ .post("/authentication")
+ .then()
+ .statusCode(200)
+ .body("continuationToken", isA(String.class));
+ }
+
+ @Test
+ public void mustReturnAuthenticationFailedWhenBadPassword() {
+ String continuationToken =
+ with()
+ .contentType(ContentType.JSON)
+ .accept(ContentType.JSON)
+ .body("{\"username\": \"[email protected]\", \"clientName\":
\"Mozilla Thunderbird\", \"clientVersion\": \"42.0\", \"deviceName\": \"Joe
Bloggâs iPhone\"}")
+ .post("/authentication")
+ .body()
+ .path("continuationToken")
+ .toString();
+
+ given()
+ .contentType(ContentType.JSON)
+ .accept(ContentType.JSON)
+ .body("{\"token\": \"" + continuationToken + "\", \"method\":
\"password\", \"password\": \"badpassword\"}")
+ .when()
+ .post("/authentication")
+ .then()
+ .statusCode(401);
+ }
+
}
Added:
james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/MultipleObjectMapperTest.java
URL:
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/MultipleObjectMapperTest.java?rev=1719306&view=auto
==============================================================================
---
james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/MultipleObjectMapperTest.java
(added)
+++
james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/MultipleObjectMapperTest.java
Fri Dec 11 10:05:39 2015
@@ -0,0 +1,90 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+package org.apache.james.jmap.json;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class MultipleObjectMapperTest {
+
+ private ObjectMapper mapper;
+
+ @SuppressWarnings("unused")
+ private static class First {
+ public String first;
+ public String other;
+ }
+
+ @SuppressWarnings("unused")
+ private static class Second {
+ public String second;
+ public String other;
+ }
+
+ @Before
+ public void setup() {
+ mapper = new MultipleObjectMapperBuilder()
+ .registerClass("/first", First.class)
+ .registerClass("/second", Second.class)
+ .build();
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void registeringSameUniquePathShouldThrowAnException() throws
Exception {
+ new MultipleObjectMapperBuilder()
+ .registerClass("/other", First.class)
+ .registerClass("/other", Second.class);
+ }
+
+ @Test
+ public void registeringSameClassTwoTimesIsOK() throws Exception {
+ ObjectMapper uselessMapper = new MultipleObjectMapperBuilder()
+ .registerClass("/first", First.class)
+ .registerClass("/other", First.class)
+ .build();
+ String json = "{\"first\": \"value\", \"other\": \"other\"}";
+ Object o = uselessMapper.readValue(json, Object.class);
+ assertThat(o).isInstanceOf(First.class);
+ }
+
+ @Test(expected=JsonMappingException.class)
+ public void badJsonShouldThrowException() throws Exception {
+ String json = "{\"bad\": \"value\"}";
+ mapper.readValue(json, Object.class);
+ }
+
+ @Test
+ public void firstJsonShouldReturnFirstClass() throws Exception {
+ String json = "{\"first\": \"value\", \"other\": \"other\"}";
+ Object o = mapper.readValue(json, Object.class);
+ assertThat(o).isInstanceOf(First.class);
+ }
+
+ @Test
+ public void secondJsonShouldReturnSecondClass() throws Exception {
+ String json = "{\"second\": \"value\", \"other\": \"other\"}";
+ Object o = mapper.readValue(json, Object.class);
+ assertThat(o).isInstanceOf(Second.class);
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]