Author: matthieu
Date: Fri Dec 11 10:05:48 2015
New Revision: 1719307

URL: http://svn.apache.org/viewvc?rev=1719307&view=rev
Log:
JAMES-1644 JMAP Authentication Servlet Handle authentication response

Added:
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/BadRequestException.java
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/AccessTokenResponse.java
Modified:
    james/project/trunk/server/protocols/jmap/pom.xml
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/AuthenticationServlet.java
    
james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPAuthenticationTest.java

Modified: james/project/trunk/server/protocols/jmap/pom.xml
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/pom.xml?rev=1719307&r1=1719306&r2=1719307&view=diff
==============================================================================
--- james/project/trunk/server/protocols/jmap/pom.xml (original)
+++ james/project/trunk/server/protocols/jmap/pom.xml Fri Dec 11 10:05:48 2015
@@ -217,6 +217,11 @@
                     <scope>test</scope>
                 </dependency>
                 <dependency>
+                    <groupId>org.mockito</groupId>
+                    <artifactId>mockito-core</artifactId>
+                    <scope>test</scope>
+                </dependency>
+                <dependency>
                     <groupId>org.slf4j</groupId>
                     <artifactId>slf4j-api</artifactId>
                 </dependency>

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=1719307&r1=1719306&r2=1719307&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:48 2015
@@ -20,6 +20,7 @@ package org.apache.james.jmap;
 
 import java.io.IOException;
 
+import javax.inject.Inject;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
@@ -27,12 +28,16 @@ import javax.servlet.http.HttpServletRes
 
 import org.apache.james.jmap.json.MultipleObjectMapperBuilder;
 import org.apache.james.jmap.model.AccessTokenRequest;
+import org.apache.james.jmap.model.AccessTokenResponse;
 import org.apache.james.jmap.model.ContinuationTokenRequest;
 import org.apache.james.jmap.model.ContinuationTokenResponse;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.api.UsersRepositoryException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.annotations.VisibleForTesting;
 
 public class AuthenticationServlet extends HttpServlet {
 
@@ -41,48 +46,60 @@ public class AuthenticationServlet exten
 
     private static final Logger LOG = 
LoggerFactory.getLogger(AuthenticationServlet.class);
 
-    private ObjectMapper mapper = new MultipleObjectMapperBuilder()
+    private final ObjectMapper mapper;
+    private final UsersRepository usersRepository;
+
+    @Inject
+    @VisibleForTesting AuthenticationServlet(UsersRepository usersRepository) {
+        this.usersRepository = usersRepository;
+        this.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 {
-        if (!checkJsonContentType(req)) {
-            resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
-            return;
-        }
-        if (!checkAcceptJsonOnly(req)) {
+        try {
+            assertJsonContentType(req);
+            assertAcceptJsonOnly(req);
+
+            Object request = deserialize(req);
+
+            if (request instanceof ContinuationTokenRequest) {
+                
handleContinuationTokenRequest((ContinuationTokenRequest)request, resp);
+            } else if (request instanceof AccessTokenRequest) {
+                handleAccessTokenRequest((AccessTokenRequest)request, resp);
+            }
+        } catch (BadRequestException e) {
+            LOG.warn("Invalid authentication request received.", e);
             resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
-            return;
         }
+    }
 
+    private Object deserialize(HttpServletRequest req) throws 
BadRequestException {
         Object request;
         try {
             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);
+        } catch (IOException e) {
+            throw new BadRequestException("Request can't be deserialized", e);
         }
+        return request;
     }
 
-    private boolean checkJsonContentType(HttpServletRequest req) {
-        return req.getContentType().equals(JSON_CONTENT_TYPE_UTF8);
+    private void assertJsonContentType(HttpServletRequest req) {
+        if (! req.getContentType().equals(JSON_CONTENT_TYPE_UTF8)) {
+            throw new BadRequestException("Request ContentType header must be 
set to: " + JSON_CONTENT_TYPE_UTF8);
+        }
     }
 
-    private boolean checkAcceptJsonOnly(HttpServletRequest req) {
+    private void assertAcceptJsonOnly(HttpServletRequest req) {
         String accept = req.getHeader("Accept");
-        return accept != null && accept.contains(JSON_CONTENT_TYPE);
+        if (accept == null || ! accept.contains(JSON_CONTENT_TYPE)) {
+            throw new BadRequestException("Request Accept header must be set 
to JSON content type");
+        }
     }
 
-
     private void handleContinuationTokenRequest(ContinuationTokenRequest 
request, HttpServletResponse resp) throws IOException {
         resp.setContentType(JSON_CONTENT_TYPE_UTF8);
 
@@ -97,6 +114,38 @@ public class AuthenticationServlet exten
     }
 
     private void handleAccessTokenRequest(AccessTokenRequest request, 
HttpServletResponse resp) throws IOException {
+        // TODO get username from continuationToken
+        String username = "username";
+        if (authenticate(request, username)) {
+            returnAccessTokenResponse(resp);
+        } else {
+            returnUnauthorizedResponse(resp);
+        }
+    }
+
+    private boolean authenticate(AccessTokenRequest request, String username) {
+        boolean authenticated = false;
+        try {
+            authenticated = usersRepository.test(username, 
request.getPassword());
+        } catch (UsersRepositoryException e) {
+            LOG.error("Error while trying to validate authentication for user 
'{}'", username, e);
+        }
+        return authenticated;
+    }
+
+    private void returnAccessTokenResponse(HttpServletResponse resp) throws 
IOException {
+        resp.setContentType(JSON_CONTENT_TYPE_UTF8);
+        resp.setStatus(HttpServletResponse.SC_CREATED);
+        AccessTokenResponse response = AccessTokenResponse
+                .builder()
+                // TODO Answer a real token
+                .accessToken("token")
+                // TODO Send API endpoints
+                .build();
+        mapper.writeValue(resp.getOutputStream(), response);
+    }
+
+    private void returnUnauthorizedResponse(HttpServletResponse resp) throws 
IOException {
         resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
     }
 

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/BadRequestException.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/BadRequestException.java?rev=1719307&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/BadRequestException.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/BadRequestException.java
 Fri Dec 11 10:05:48 2015
@@ -0,0 +1,30 @@
+/****************************************************************
+ * 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;
+
+public class BadRequestException extends RuntimeException {
+
+    public BadRequestException(String message) {
+        super(message);
+    }
+
+    public BadRequestException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/AccessTokenResponse.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/AccessTokenResponse.java?rev=1719307&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/AccessTokenResponse.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/AccessTokenResponse.java
 Fri Dec 11 10:05:48 2015
@@ -0,0 +1,100 @@
+/****************************************************************
+ * 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;
+
+public class AccessTokenResponse {
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+        private String accessToken;
+        private String api;
+        private String eventSource;
+        private String upload;
+        private String download;
+
+        private Builder() {}
+
+        public Builder accessToken(String accessToken) {
+            this.accessToken = accessToken;
+            return this;
+        }
+
+        public Builder api(String api) {
+            this.api = api;
+            return this;
+        }
+
+        public Builder eventSource(String eventSource) {
+            this.eventSource = eventSource;
+            return this;
+        }
+
+        public Builder upload(String upload) {
+            this.upload = upload;
+            return this;
+        }
+
+        public Builder download(String download) {
+            this.download = download;
+            return this;
+        }
+
+        public AccessTokenResponse build() {
+            return new AccessTokenResponse(accessToken, api, eventSource, 
upload, download);
+        }
+    }
+
+    private final String accessToken;
+    private final String api;
+    private final String eventSource;
+    private final String upload;
+    private final String download;
+
+    private AccessTokenResponse(String accessToken, String api, String 
eventSource, String upload, String download) {
+        this.accessToken = accessToken;
+        this.api = api;
+        this.eventSource = eventSource;
+        this.upload = upload;
+        this.download = download;
+    }
+
+    public String getAccessToken() {
+        return accessToken;
+    }
+
+    public String getApi() {
+        return api;
+    }
+
+    public String getEventSource() {
+        return eventSource;
+    }
+
+    public String getUpload() {
+        return upload;
+    }
+
+    public String getDownload() {
+        return download;
+    }
+
+}
\ No newline at end of file

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=1719307&r1=1719306&r2=1719307&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:48 2015
@@ -24,9 +24,13 @@ import static com.jayway.restassured.con
 import static com.jayway.restassured.config.RestAssuredConfig.newConfig;
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import org.apache.james.http.jetty.Configuration;
 import org.apache.james.http.jetty.JettyHttpServer;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.api.UsersRepositoryException;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -38,13 +42,17 @@ import com.jayway.restassured.http.Conte
 public class JMAPAuthenticationTest {
 
     private JettyHttpServer server;
-
+    private UsersRepository mockedUsersRepository;
+    
     @Before
     public void setup() throws Exception {
+        mockedUsersRepository = mock(UsersRepository.class);
+        AuthenticationServlet authenticationServlet = new 
AuthenticationServlet(mockedUsersRepository);
+        
         server = JettyHttpServer.create(
                 Configuration.builder()
                 .serve("/*")
-                .with(AuthenticationServlet.class)
+                .with(authenticationServlet)
                 .randomPort()
                 .build());
 
@@ -53,7 +61,6 @@ public class JMAPAuthenticationTest {
         RestAssured.config = 
newConfig().encoderConfig(encoderConfig().defaultContentCharset(Charsets.UTF_8));
     }
 
-
     @After
     public void teardown() throws Exception {
         server.stop();
@@ -207,5 +214,79 @@ public class JMAPAuthenticationTest {
             .statusCode(401);
     }
 
+    @Test
+    public void mustReturnAuthenticationFailedWhenUsersRepositoryException() 
throws Exception {
+        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();
+
+        when(mockedUsersRepository.test("username", "password"))
+            .thenThrow(new UsersRepositoryException("test"));
+
+        given()
+            .contentType(ContentType.JSON)
+            .accept(ContentType.JSON)
+            .body("{\"token\": \"" + continuationToken + "\", \"method\": 
\"password\", \"password\": \"password\"}")
+        .when()
+            .post("/authentication")
+        .then()
+            .statusCode(401);
+    }
 
+    @Test
+    public void mustReturnCreatedWhenGoodPassword() throws Exception {
+        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();
+
+        when(mockedUsersRepository.test("username", "password"))
+            .thenReturn(true);
+
+        given()
+            .contentType(ContentType.JSON)
+            .accept(ContentType.JSON)
+            .body("{\"token\": \"" + continuationToken + "\", \"method\": 
\"password\", \"password\": \"password\"}")
+        .when()
+            .post("/authentication")
+        .then()
+            .statusCode(201);
+    }
+
+    @Test
+    public void mustSendJsonContainingAccessTokenWhenGoodPassword() throws 
Exception {
+        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();
+
+        when(mockedUsersRepository.test("username", "password"))
+            .thenReturn(true);
+
+        given()
+            .contentType(ContentType.JSON)
+            .accept(ContentType.JSON)
+            .body("{\"token\": \"" + continuationToken + "\", \"method\": 
\"password\", \"password\": \"password\"}")
+        .when()
+            .post("/authentication")
+        .then()
+            .contentType(ContentType.JSON)
+            .body("accessToken", isA(String.class));
+    }
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to