package org.fluxtream.core.api;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.fluxtream.core.Configuration;
import org.fluxtream.core.aspects.FlxLogger;
import org.fluxtream.core.auth.AuthHelper;
import org.fluxtream.core.connectors.Connector;
import org.fluxtream.core.domain.ApiKey;
import org.fluxtream.core.services.GuestService;
import org.fluxtream.core.utils.UnexpectedHttpResponseCodeException;
import org.fluxtream.core.utils.Utils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* User: candide
* Date: 23/09/14
* Time: 15:39
*/
@Path("/v1/couch")
@Component("RESTCouchDBController")
@Scope("request")
public class CouchDBController {
FlxLogger logger = FlxLogger.getLogger(CouchDBController.class);
private final String COUCH_DB_USER_TOKEN_ATTRIBUTE_KEY = "couchDB.userToken";
@Autowired
GuestService guestService;
@Autowired
Configuration env;
static Pattern legalCouchDbNamePattern = Pattern.compile("^[a-z]([a-z0-9,_$()+\\-/])*$");
public static void main(String[] args) {
System.out.println(maybeHash("candide4@me.com"));
System.out.println(maybeHash(" candide4@me.com"));
System.out.println(maybeHash("candide4@me.com "));
}
public static String maybeHash(String username) {
Matcher matcher = legalCouchDbNamePattern.matcher(username);
if (!matcher.matches())
return Utils.hash(username);
return username;
}
class CouchDBCredentials {
public String user_login, user_token;
public int status;
public String statusMessage;
}
@PUT
@Path("/")
@Produces({ MediaType.APPLICATION_JSON })
public Response init()
throws InstantiationException, IllegalAccessException,
ClassNotFoundException, NoSuchAlgorithmException, UnsupportedEncodingException {
try {
final StringBuffer userTokenBuffer = new StringBuffer();
final int status = getFluxtreamCaptureCouchDBUserToken(userTokenBuffer, true);
final CouchDBCredentials couchDBCredentials = new CouchDBCredentials();
couchDBCredentials.user_login = maybeHash(AuthHelper.getGuest().username);
couchDBCredentials.user_token = userTokenBuffer.toString();
couchDBCredentials.status = status;
couchDBCredentials.statusMessage = "OK";
createCouchUser(maybeHash(AuthHelper.getGuest().username), couchDBCredentials.user_token);
createUserDatabases(maybeHash(AuthHelper.getGuest().username));
// If user already created need different strategy
return Response.ok().entity(couchDBCredentials).build();
} catch (UnexpectedHttpResponseCodeException e) {
final CouchDBCredentials couchDBCredentials = new CouchDBCredentials();
couchDBCredentials.status = e.getHttpResponseCode();
couchDBCredentials.statusMessage = e.getHttpResponseMessage();
return Response.serverError().entity(couchDBCredentials).build();
}
catch (Throwable t) {
final CouchDBCredentials couchDBCredentials = new CouchDBCredentials();
couchDBCredentials.status = 2;
return Response.serverError().entity(couchDBCredentials).build();
}
}
@GET
@Path("/")
@Produces({ MediaType.APPLICATION_JSON })
public Response read()
throws InstantiationException, IllegalAccessException,
ClassNotFoundException, NoSuchAlgorithmException, UnsupportedEncodingException {
try {
final StringBuffer userTokenBuffer = new StringBuffer();
final int status = getFluxtreamCaptureCouchDBUserToken(userTokenBuffer, false);
final CouchDBCredentials couchDBCredentials = new CouchDBCredentials();
couchDBCredentials.user_login = maybeHash(AuthHelper.getGuest().username);
couchDBCredentials.user_token = userTokenBuffer.toString();
couchDBCredentials.status = status;
createUserDatabases(maybeHash(AuthHelper.getGuest().username));
return Response.ok().entity(couchDBCredentials).build();
} catch (Throwable t) {
final CouchDBCredentials couchDBCredentials = new CouchDBCredentials();
couchDBCredentials.status = 2;
return Response.serverError().entity(couchDBCredentials).build();
}
}
@POST
@Path("/reset")
@Produces({ MediaType.APPLICATION_JSON })
public Response reset()
throws InstantiationException, IllegalAccessException,
ClassNotFoundException, NoSuchAlgorithmException, UnsupportedEncodingException {
try {
final StringBuffer userTokenBuffer = new StringBuffer();
final ApiKey apiKey = getFluxtreamCaptureApiKey(AuthHelper.getGuestId());
final String couchDBUserToken = guestService.getApiKeyAttribute(apiKey, COUCH_DB_USER_TOKEN_ATTRIBUTE_KEY);
final CouchDBCredentials couchDBCredentials = new CouchDBCredentials();
couchDBCredentials.user_login = maybeHash(AuthHelper.getGuest().username);
if (couchDBUserToken==null) {
couchDBCredentials.status = 1;
return Response.ok().entity(couchDBCredentials).build();
} else {
destroyCouchDatabase(maybeHash(AuthHelper.getGuest().username));
guestService.removeApiKeyAttribute(apiKey.getId(), COUCH_DB_USER_TOKEN_ATTRIBUTE_KEY);
couchDBCredentials.status = getFluxtreamCaptureCouchDBUserToken(userTokenBuffer, true);
return Response.ok().entity(couchDBCredentials).build();
}
} catch (Throwable t) {
final CouchDBCredentials couchDBCredentials = new CouchDBCredentials();
couchDBCredentials.status = 2;
return Response.serverError().entity(couchDBCredentials).build();
}
}
final ApiKey getFluxtreamCaptureApiKey(final long guestId) {
Connector connector = Connector.getConnector("fluxtream_capture");
final ApiKey apiKey;
List<ApiKey> apiKeys = guestService.getApiKeys(guestId, connector);
if (apiKeys != null && !apiKeys.isEmpty()) {
apiKey = apiKeys.get(0);
}
else {
apiKey = guestService.createApiKey(guestId, connector);
}
return apiKey;
}
private int getFluxtreamCaptureCouchDBUserToken(final StringBuffer saltBuffer, final boolean createIfNotExists) {
final ApiKey apiKey = getFluxtreamCaptureApiKey(AuthHelper.getGuestId());
final String couchDBUserToken = guestService.getApiKeyAttribute(apiKey, COUCH_DB_USER_TOKEN_ATTRIBUTE_KEY);
if (couchDBUserToken==null && createIfNotExists) {
byte[] b = new byte[20];
new Random().nextBytes(b);
final String userToken = Base64.encodeBase64URLSafeString(b);
guestService.setApiKeyAttribute(apiKey, COUCH_DB_USER_TOKEN_ATTRIBUTE_KEY, userToken);
saltBuffer.append(userToken);
try {
createUserDatabases(maybeHash(AuthHelper.getGuest().username));
} catch (UnexpectedHttpResponseCodeException e) {
return 2;
}
return 0;
}
saltBuffer.append(couchDBUserToken);
return createIfNotExists ? 1 : 0;
}
private void destroyCouchDatabase(final String dbUsername) throws UnexpectedHttpResponseCodeException {
HttpClient client = new DefaultHttpClient();
try {
final String couchdbHost = env.get("couchdb.host");
final String couchdbPort = env.get("couchdb.port");
HttpDelete delete = new HttpDelete(String.format("http://%s:%s/", couchdbHost, couchdbPort) + dbUsername);
HttpResponse response = client.execute(delete);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
logger.warn("Error communicating with CouchDB: " + response.getStatusLine().getReasonPhrase());
throw new UnexpectedHttpResponseCodeException(response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase());
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
client.getConnectionManager().shutdown();
}
}
private void createUserDatabases(final String dbUsername) throws UnexpectedHttpResponseCodeException {
int statusObservations, statusTopics, statusDeleteObservations, statusDeleteTopics;
// Create Observations database and add rights for the user
statusObservations = createCouchDB(dbUsername, "self_report_db_observations_");
// Create Topics database and add rights for the user
statusTopics = createCouchDB(dbUsername, "self_report_db_topics_");
// Create database of deleted Observations and add rights for the user
statusDeleteObservations = createCouchDB(dbUsername, "self_report_db_deleted_observations_");
// Create database of deleted Topics and add rights for the user
statusDeleteTopics = createCouchDB(dbUsername, "self_report_db_deleted_topics_");
}
private int createCouchUser(final String username, final String password) throws UnexpectedHttpResponseCodeException {
HttpClient client = new DefaultHttpClient();
int status = 0;
try {
final String couchdbHost = env.get("couchdb.host");
final String couchdbPort = env.get("couchdb.port");
final String couchdbAdminLogin = env.get("couchdb.admin_login");
final String couchdbAdminPasword = env.get("couchdb.admin_password");
String userPassword = couchdbAdminLogin + ":" + couchdbAdminPasword;
byte[] encodedCredentials = Base64.encodeBase64(userPassword.getBytes());
final String request = String.format("http://%s:%s/_users/org.couchdb.user:%s", couchdbHost, couchdbPort, username);
HttpPut put = new HttpPut(request);
put.addHeader("Authorization", "Basic " + new String(encodedCredentials));
put.addHeader("Content-Type", "application/json");
put.addHeader("Accept", "application/json");
JSONObject keyArg = new JSONObject();
keyArg.put("_id", "org.couchdb.user:"+ username);
keyArg.put("name", username);
keyArg.put("password", password);
keyArg.put("roles", new JSONArray());
keyArg.put("type", "user");
StringEntity input;
input = new StringEntity(keyArg.toString());
put.setEntity(input);
HttpResponse response = client.execute(put);
HttpEntity entity = response.getEntity();
// TODO check if the user already exists
String responseString = EntityUtils.toString(entity, "UTF-8");
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_CONFLICT) {
status = HttpStatus.SC_CONFLICT;
return status;
}
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
throw new UnexpectedHttpResponseCodeException(response.getStatusLine().getStatusCode(), responseString);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
// Error handler should be here
e.printStackTrace();
}
return status;
}
private int createCouchDB (final String dbUsername, final String dbName) throws UnexpectedHttpResponseCodeException {
HttpClient client = new DefaultHttpClient();
int status = 0;
try {
final String couchdbHost = env.get("couchdb.host");
final String couchdbPort = env.get("couchdb.port");
final String couchdbAdminLogin = env.get("couchdb.admin_login");
final String couchdbAdminPasword = env.get("couchdb.admin_password");
String userPassword = couchdbAdminLogin + ":" + couchdbAdminPasword;
byte[] encodedCredentials = Base64.encodeBase64(userPassword.getBytes());
// Create Database
final String requestObservations = String.format("http://%s:%s/", couchdbHost, couchdbPort) + dbName + dbUsername;
HttpPut put = new HttpPut(requestObservations);
put.addHeader("Authorization", "Basic " + new String(encodedCredentials));
HttpResponse response = client.execute(put);
HttpEntity entity = response.getEntity();
String responseString = EntityUtils.toString(entity, "UTF-8");
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
throw new UnexpectedHttpResponseCodeException(response.getStatusLine().getStatusCode(), responseString);
}
// Add user if Database was created
final String userRequestObservations = String.format("http://%s:%s/%s/_security", couchdbHost, couchdbPort, dbName + dbUsername);
put = new HttpPut(userRequestObservations);
put.addHeader("Authorization", "Basic " + new String(encodedCredentials));
put.addHeader("Content-Type", "application/json");
put.addHeader("Accept", "application/json");
JSONObject keyArgMembers = new JSONObject();
JSONObject keyArgAdmins = new JSONObject();
JSONObject keyArg = new JSONObject();
JSONArray namesArray = new JSONArray();
namesArray.put(dbUsername);
keyArgMembers.put("names", namesArray);
JSONArray rolesArray = new JSONArray();
rolesArray.put("producer");
rolesArray.put("consumer");
keyArgMembers.put("roles", rolesArray);
keyArg.put("admins", keyArgAdmins);
keyArg.put("members", keyArgMembers);
StringEntity input;
input = new StringEntity(keyArg.toString());
put.setEntity(input);
response = client.execute(put);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
throw new UnexpectedHttpResponseCodeException(response.getStatusLine().getStatusCode(), responseString);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
return status;
}
}
}