Skip to content

Commit

Permalink
JAMES-1644 JMAP Authentication Servlet Handle authentication response
Browse files Browse the repository at this point in the history
git-svn-id: https://svn.apache.org/repos/asf/james/project/trunk@1719307 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
mbaechler committed Dec 11, 2015
1 parent c105adb commit 47257ee
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 26 deletions.
5 changes: 5 additions & 0 deletions server/protocols/jmap/pom.xml
Expand Up @@ -216,6 +216,11 @@
<version>9.3.2.v20150730</version>
<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>
Expand Down
Expand Up @@ -20,19 +20,24 @@

import java.io.IOException;

import javax.inject.Inject;
import javax.servlet.ServletException;
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.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 {

Expand All @@ -41,48 +46,60 @@ public class AuthenticationServlet extends HttpServlet {

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);

Expand All @@ -97,6 +114,38 @@ private void handleContinuationTokenRequest(ContinuationTokenRequest request, Ht
}

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);
}

Expand Down
@@ -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);
}
}
@@ -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;
}

}

0 comments on commit 47257ee

Please sign in to comment.