package com.fourspaces.featherdb.httpd;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.json.JSONException;
import org.json.JSONWriter;
import org.mortbay.jetty.HttpConnection;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.handler.AbstractHandler;
import com.fourspaces.featherdb.FeatherDB;
import com.fourspaces.featherdb.auth.Credentials;
import com.fourspaces.featherdb.auth.SACredentials;
import com.fourspaces.featherdb.utils.Logger;
/**
* This is very <strike>messy</strike> <b>ugly</b> and should be refactored into multiple handlers...
*
* @author mbreese
*
*/
public class FeatherDBHandler extends AbstractHandler {
public static final String JSON_MIMETYPE = "application/javascript";
public static final String COOKIE_ID = "FEATHERDB_ID";
final protected FeatherDB featherDB;
final protected boolean allowAnonymous;
protected int timeout;
protected List<BaseRequestHandler> baseRequestHandlers = new ArrayList<BaseRequestHandler>();
protected Logger log = Logger.get(getClass());
public FeatherDBHandler(FeatherDB featherDB) {
this.featherDB = featherDB;
baseRequestHandlers.add(new GetDocument());
baseRequestHandlers.add(new AdHocView());
baseRequestHandlers.add(new GetView());
baseRequestHandlers.add(new Auth());
baseRequestHandlers.add(new InvalidateAuth());
baseRequestHandlers.add(new GetDatabaseNames());
baseRequestHandlers.add(new GetDBStats());
baseRequestHandlers.add(new AddDB());
baseRequestHandlers.add(new UpdateDocument());
baseRequestHandlers.add(new Sessions());
baseRequestHandlers.add(new Shutdown());
for (BaseRequestHandler handler:baseRequestHandlers) {
handler.setFeatherDB(featherDB);
}
this.allowAnonymous = featherDB.getProperty("auth.anonymous","false").toLowerCase().equals("true");
this.timeout=Integer.parseInt(featherDB.getProperty("auth.timeout.seconds","300"));
}
public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException
{
Request base_request = (request instanceof Request) ? (Request)request:HttpConnection.getCurrentConnection().getRequest();
log.debug("Requested URI: {}",request.getRequestURI());
String path = request.getRequestURI().substring(1);
if (path.startsWith("_sys")) {
return;
}
Credentials cred=getCredentials(request,response);
if (response.isCommitted() || cred == null) {
return;
}
if (path.endsWith("/")) {
path=path.substring(0,path.length()-1);
}
String db = null;
String id = null;
String rev = null;
int slashIndex = path.indexOf("/");
if (slashIndex>-1) {
db = path.substring(0,slashIndex);
if (slashIndex<path.length()) {
id = path.substring(slashIndex+1);
rev = request.getParameter("revision");
}
} else {
db=path;
}
// String[] fields = null;
// String[] split = path.split("/");
//
// if (split.length>1) {
// db = split[0];
// id = split[1];
// } else if (split.length>2) {
// db = split[0];
// id = split[1];
// revision = split[split.length-1];
// if (revision.equals("_current")) {
// revision = null;
// }
// } else {
// db = path;
// }
//
// if (split.length>3) {
// fields = new String[split.length-3];
// for (int i=3;i<split.length;i++) {
// fields[i-3]=split[i];
// }
// }
boolean handled = false;
for (BaseRequestHandler handler:baseRequestHandlers) {
if (handler.match(cred, request, db, id)) {
handler.handle(cred, request, response, db, id, rev);
handled = true;
base_request.setHandled(true);
}
}
if (!handled) {
sendError(response,"Could not process request");
}
return;
}
protected void sendNoAuthError(HttpServletResponse response, String string) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(JSON_MIMETYPE);
try {
new JSONWriter(response.getWriter())
.object()
.key("error")
.value(true)
.key("message")
.value(string)
.endObject();
} catch (JSONException e) {
throw new IOException(e);
}
}
protected void sendError(HttpServletResponse response, String string) throws IOException {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.setContentType(JSON_MIMETYPE);
try {
new JSONWriter(response.getWriter())
.object()
.key("error")
.value(true)
.key("message")
.value(string)
.endObject();
} catch (JSONException e) {
throw new IOException(e);
}
}
protected Credentials getCredentials(HttpServletRequest request, HttpServletResponse response) throws IOException {
Credentials cred=null;
if (allowAnonymous) {
return new SACredentials("anonymous", "",timeout);
}
if (request.getRequestURI().equals("/_auth")) {
String username = request.getParameter("username");
String password = request.getParameter("password");
if (username!=null) {
if (password == null) {
password = "";
}
cred = featherDB.getAuthentication().authenticate(username, password);
if (cred!=null) {
if (request.getParameter("setcookie")!=null && request.getParameter("setcookie").equals("1")) {
Cookie cookie = new Cookie(COOKIE_ID, cred.getToken());
cookie.setMaxAge(timeout);
response.addCookie(cookie);
}
return cred;
} else {
sendNoAuthError(response, "Bad username / password combination");
return null;
}
}
}
Cookie[] cookies = request.getCookies();
if (cookies !=null) {
for (Cookie cookie: cookies) {
if (cookie.getName().equals(COOKIE_ID)) {
cred = featherDB.getAuthentication().getCredentialsFromToken(cookie.getValue());
if (cred!=null) {
log.debug("Got credentials from cookie token: {}", cookie.getValue());
return cred;
}
}
}
}
String param = request.getParameter("token");
if (param!=null && !param.equals("")) {
cred = featherDB.getAuthentication().getCredentialsFromToken(param);
if (cred!=null) {
log.debug("Authenticated as {} => {} via Req param",cred.getUsername(), cred.getToken());
addCredentialedCookie(response,cred);
return cred;
}
}
String headerparam = request.getHeader("FeatherDB-Token");
if (headerparam!=null && !headerparam.equals("")) {
cred = featherDB.getAuthentication().getCredentialsFromToken(headerparam);
if (cred!=null) {
log.debug("Authenticated as {} => {} via HTTP-Header",cred.getUsername(), cred.getToken());
addCredentialedCookie(response,cred);
return cred;
}
}
String authHeader = request.getHeader("Authorization");
if (authHeader!=null) {
String[] authSplit = authHeader.split(" ");
if (authSplit.length==2) {
String userpass = new String(Base64.decodeBase64(authSplit[1].getBytes()));
if (userpass!=null) {
String[] ar = userpass.split(":");
String u = ar[0];
String p="";
if (ar.length>1) {
p = ar[1];
}
cred = featherDB.getAuthentication().authenticate(u,p);
if (cred!=null) {
log.debug("Authenticated as {} => {} via HTTP-AUTH",cred.getUsername(), cred.getToken());
addCredentialedCookie(response,cred);
}
return cred;
}
}
}
log.warn("Error authenticating");
response.addHeader("WWW-Authenticate"," Basic realm=\"FeatherDB\"");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"You need a username and password");
return null;
}
private void addCredentialedCookie(HttpServletResponse response, Credentials cred) {
Cookie cookie = new Cookie(COOKIE_ID, cred.getToken());
cookie.setMaxAge(24*60*60); // max time is one day... afterwhich, it needs to reauth.
response.addCookie(cookie);
}
}