package org.mitre.rhex.security;
import edu.umd.cs.findbugs.annotations.NonNull;
import org.apache.commons.lang.StringUtils;
import org.apache.http.*;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.mitre.test.ClientHelper;
import org.mitre.test.Context;
import org.mitre.test.HttpRequestChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
/**
* MITRE RHEX Patient Data Server HTTP request security handler
* implements simple authentication using developer callback URL.
*
* @author Jason Mathews, MITRE Corp.
* Date: 3/30/12 12:51 PM
*/
public class RhexHttpSecurityChecker implements HttpRequestChecker {
private static final Logger log = LoggerFactory.getLogger(RhexHttpSecurityChecker.class);
private HttpContext localContext;
/**
* Setups and initializes the HttpRequestChecker
*
* @param context Application context
* @throws IllegalArgumentException if setup/configuration fails
*/
@Override
public void setup(Context context) {
log.debug("XXX: try callback to enable authentication");
final URI uri = context.getPropertyAsURI("loginURL");
if (uri == null) {
throw new IllegalArgumentException("loginURL property not defined");
}
String loginEmail = context.getString("loginEmail");
String loginPassword = context.getString("loginPassword");
if (StringUtils.isBlank(loginEmail) || StringUtils.isBlank(loginPassword)) {
throw new IllegalArgumentException("loginEmail and loginPassword properties are empty or missing");
}
log.debug("POST auth URL: {}", uri);
HttpPost httppost = new HttpPost(uri);
httppost.setHeader("Cache-Control", "no-cache");
List<NameValuePair> formParams = new ArrayList<NameValuePair>(2);
formParams.add(new BasicNameValuePair("email", loginEmail));
formParams.add(new BasicNameValuePair("id", loginPassword));
final HttpResponse response;
HttpClient client = null;
HttpContext httpContext = localContext;
try {
httppost.setEntity(new UrlEncodedFormEntity(formParams));
client = context.getHttpClient();
if (httpContext == null) {
// Create a local instance of cookie store
CookieStore cookieStore = new BasicCookieStore();
// Create local HTTP context
httpContext = new BasicHttpContext();
// Bind custom cookie store to the local context
httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
}
response = client.execute(httppost, httpContext);
/*
<form method='post' action='/auth/developer/callback' noValidate='noValidate'>
<label for='name'>Name:</label>
<input type='text' id='name' name='name'/>
<label for='email'>Email:</label>
<input type='text' id='email' name='email'/>
<button type='submit'>Sign In</button> </form>
*/
if (log.isDebugEnabled() || !checkAuth(response)) {
ClientHelper.dumpResponse(httppost, response, true);
}
} catch (IOException e) {
log.error("", e);
return;
} finally {
if (client != null)
client.getConnectionManager().shutdown();
}
final StatusLine statusLine = response.getStatusLine();
if (statusLine != null && statusLine.getStatusCode() == 302) {
// HTTP/1.1 302 Moved Temporarily
Header cookie = response.getFirstHeader("Set-Cookie");
if (cookie != null) {
log.debug("XXX: set local context");
this.localContext = httpContext;
}
else log.error("Expected Set-Cookie header in response");
if (log.isDebugEnabled()) {
Header location = response.getFirstHeader("Location");
if (location != null) {
checkAuthentication(context, location.getValue());
//log.debug("XXX: set local context");
//this.localContext = httpContext;
}
else log.error("Expected Location header in response");
}
} else {
log.error("Expected 302 status code in response");
}
}
@Override
public void setUser(Context context, String userId, String userEmail, String userPassword) {
// not implemented
}
@Override
public String getCurrentUser(Context context) {
return null; // not implemented
}
private static boolean checkAuth(HttpResponse response) {
final StatusLine statusLine = response.getStatusLine();
if (statusLine == null || statusLine.getStatusCode() != 302) return false;
Header location = response.getFirstHeader("Location");
/*
* if successful; then Location should be web server root (e.g. http://rhex.mitre.org:3000/)
* otherwise redirects to /users/sign_up
*/
return location != null && !StringUtils.endsWith(location.getValue(), "/users/sign_up");
}
private void checkAuthentication(Context context, String target) {
HttpClient client = context.getHttpClient();
try {
log.debug("GET auth URL: {}", target);
HttpGet req = new HttpGet(target);
HttpResponse response = client.execute(req, localContext);
if (log.isTraceEnabled() || response.getStatusLine().getStatusCode() != 200) {
ClientHelper.dumpResponse(req, response, false);
}
//StatusLine statusLine = response.getStatusLine();
//return statusLine != null && statusLine.getStatusCode() == 200;
} catch (IOException e) {
log.debug("", e);
//return false;
} finally {
client.getConnectionManager().shutdown();
}
}
/**
* Wrap <tt>HttpClient.execute()</tt> to pre/post-test HTTP requests for
* any server specific implementation handling such as authentication.
*
* @param context Application context
* @param client the HttpClient, must never be null
* @param req the request to execute, must never be null
*
* @return the response to the request.
* @throws IOException in case of a problem or the connection was aborted
* @throws ClientProtocolException in case of an http protocol error
*/
@NonNull
public HttpResponse executeRequest(Context context, HttpClient client, HttpUriRequest req)
throws IOException
{
return client.execute(req, localContext);
}
}