Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

pass-through authentication #151

Open
wants to merge 1 commit into from

2 participants

@jalpedersen

Hi again,

I've added a very simple database authentication step based on an earlier discussion we had. When a request hits C-L, a get request is sent to underlying CouchDB database to verify that the user indeed has read access to it. This works by copying all headers from the original request onto the request going to CouchDB. By blindly copying all headers it works both for basic auth, cookies and what not. Note, that this uses an HttpClient, which does not use the credentials provided in couchdb-lucene.ini.

The rest of C-L continues to use the original httpClient, so there is no real change to the underlying flow of things, only a simple authenticating step is added (which perhaps could be made optional?)

@rnewson
Owner

It's a bit ugly but I coded up my thought here: https://github.com/rnewson/couchdb-lucene/compare/authenticate_users.

@jalpedersen

Yes, but your approach would only verify that the user is in fact a user in the CouchDB instance - not that he/she has access to the database being searched in (in the event that read-access has been limited to certain users/roles), which is the problem I'm trying to solve.

Also, your approach assumes that basic auth is being used, which is not always the case, especially when dealing with CouchApps for instance. Also, it will not work when deployed as a war to another servlet-container.

Your approach would be good for more general things, such as limiting access to the admin functionality to certain roles for instance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 21, 2012
  1. @jalpedersen
This page is out of date. Refresh to see the latest.
View
79 src/main/java/com/github/rnewson/couchdb/lucene/AuthenticatingHttpClient.java
@@ -0,0 +1,79 @@
+package com.github.rnewson.couchdb.lucene;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import org.apache.http.Header;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+
+public class AuthenticatingHttpClient implements HttpClient {
+ private final HttpClient delegate;
+ private final Collection<Header> headers;
+
+ public AuthenticatingHttpClient(HttpClient delegate, Collection<Header> headers) {
+ this.delegate = delegate;
+ this.headers = headers;
+ }
+
+ public HttpParams getParams() {
+ return delegate.getParams();
+ }
+
+ public ClientConnectionManager getConnectionManager() {
+ return delegate.getConnectionManager();
+ }
+
+ public HttpResponse execute(HttpUriRequest request) throws IOException, ClientProtocolException {
+ return delegate.execute(addHeaders(request));
+ }
+
+ public HttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException,
+ ClientProtocolException {
+ return delegate.execute(addHeaders(request), context);
+ }
+
+ public HttpResponse execute(HttpHost target, HttpRequest request) throws IOException, ClientProtocolException {
+ return delegate.execute(target, addHeaders(request));
+ }
+
+ public HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) throws IOException,
+ ClientProtocolException {
+ return delegate.execute(target, addHeaders(request), context);
+ }
+
+ public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler) throws IOException,
+ ClientProtocolException {
+ return delegate.execute(addHeaders(request), responseHandler);
+ }
+
+ public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context)
+ throws IOException, ClientProtocolException {
+ return delegate.execute(addHeaders(request), responseHandler, context);
+ }
+
+ public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler)
+ throws IOException, ClientProtocolException {
+ return delegate.execute(target, addHeaders(request), responseHandler);
+ }
+
+ public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler,
+ HttpContext context) throws IOException, ClientProtocolException {
+ return delegate.execute(target, addHeaders(request), responseHandler, context);
+ }
+
+ public <T extends HttpRequest> T addHeaders(T request) {
+ for (Header h: headers) {
+ request.addHeader(h);
+ }
+ return request;
+ }
+}
View
113 src/main/java/com/github/rnewson/couchdb/lucene/HttpClientFactory.java
@@ -19,10 +19,16 @@
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.Collection;
+import java.util.Enumeration;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
+import javax.servlet.http.HttpServletRequest;
+
import org.apache.commons.configuration.HierarchicalINIConfiguration;
+import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
@@ -49,6 +55,7 @@
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.message.BasicHeader;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
@@ -129,49 +136,85 @@ public void shutdown() {
}
- private static DefaultHttpClient instance;
+ private static HttpClient instance;
+ private static HttpClient unauthenticatedClient;
private static HierarchicalINIConfiguration INI;
+ /**
+ * Returns a client where each request is responsible for authentication
+ *
+ * @return
+ * @throws MalformedURLException
+ */
+ public static synchronized HttpClient getAuthenticatingInstance(HttpServletRequest request) throws MalformedURLException {
+ if (unauthenticatedClient == null) {
+ unauthenticatedClient = createInstance(false);
+ }
+ final Collection<Header> headers = new LinkedList<Header>();
+ @SuppressWarnings("unchecked")
+ final Enumeration<String> headerEnum = request.getHeaderNames();
+ while (headerEnum.hasMoreElements()) {
+ final String name = headerEnum.nextElement();
+ @SuppressWarnings("unchecked")
+ final Enumeration<String> values = request.getHeaders(name);
+ while (values.hasMoreElements()) {
+ headers.add(new BasicHeader(name, values.nextElement()));
+ }
+ }
+ return new AuthenticatingHttpClient(unauthenticatedClient, headers);
+ }
+
+ /**
+ * Returns a client where the credentials in the configuration file are used for authentication
+ *
+ * @return
+ * @throws MalformedURLException
+ */
public static synchronized HttpClient getInstance() throws MalformedURLException {
if (instance == null) {
- final HttpParams params = new BasicHttpParams();
- // protocol params.
- HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
- HttpProtocolParams.setUseExpectContinue(params, false);
- // connection params.
- HttpConnectionParams.setTcpNoDelay(params, true);
- HttpConnectionParams.setStaleCheckingEnabled(params, false);
- ConnManagerParams.setMaxTotalConnections(params, 1000);
- ConnManagerParams.setMaxConnectionsPerRoute(params, new ConnPerRouteBean(1000));
-
- final SchemeRegistry schemeRegistry = new SchemeRegistry();
- schemeRegistry
- .register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 5984));
- schemeRegistry
- .register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
- final ClientConnectionManager cm = new ShieldedClientConnManager(
- new ThreadSafeClientConnManager(params, schemeRegistry));
-
- instance = new DefaultHttpClient(cm, params);
-
- if (INI != null) {
- final CredentialsProvider credsProvider = new BasicCredentialsProvider();
- final Iterator<?> it = INI.getKeys();
- while (it.hasNext()) {
- final String key = (String) it.next();
- if (!key.startsWith("lucene.") && key.endsWith(".url")) {
- final URL url = new URL(INI.getString(key));
- if (url.getUserInfo() != null) {
- credsProvider.setCredentials(
- new AuthScope(url.getHost(), url.getPort()),
- new UsernamePasswordCredentials(url.getUserInfo()));
- }
+ instance = createInstance(true);
+ }
+ return instance;
+ }
+
+ private static HttpClient createInstance(boolean authenticate) throws MalformedURLException {
+ final HttpParams params = new BasicHttpParams();
+ // protocol params.
+ HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
+ HttpProtocolParams.setUseExpectContinue(params, false);
+ // connection params.
+ HttpConnectionParams.setTcpNoDelay(params, true);
+ HttpConnectionParams.setStaleCheckingEnabled(params, false);
+ ConnManagerParams.setMaxTotalConnections(params, 1000);
+ ConnManagerParams.setMaxConnectionsPerRoute(params, new ConnPerRouteBean(1000));
+
+ final SchemeRegistry schemeRegistry = new SchemeRegistry();
+ schemeRegistry
+ .register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 5984));
+ schemeRegistry
+ .register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
+ final ClientConnectionManager cm = new ShieldedClientConnManager(
+ new ThreadSafeClientConnManager(params, schemeRegistry));
+
+ final DefaultHttpClient instance = new DefaultHttpClient(cm, params);
+
+ if (INI != null) {
+ final CredentialsProvider credsProvider = new BasicCredentialsProvider();
+ final Iterator<?> it = INI.getKeys();
+ while (it.hasNext()) {
+ final String key = (String) it.next();
+ if (!key.startsWith("lucene.") && key.endsWith(".url")) {
+ final URL url = new URL(INI.getString(key));
+ if (authenticate && url.getUserInfo() != null) {
+ credsProvider.setCredentials(
+ new AuthScope(url.getHost(), url.getPort()),
+ new UsernamePasswordCredentials(url.getUserInfo()));
}
}
- instance.setCredentialsProvider(credsProvider);
- instance.addRequestInterceptor(new PreemptiveAuthenticationRequestInterceptor(), 0);
}
+ instance.setCredentialsProvider(credsProvider);
+ instance.addRequestInterceptor(new PreemptiveAuthenticationRequestInterceptor(), 0);
}
return instance;
}
View
28 src/main/java/com/github/rnewson/couchdb/lucene/LuceneServlet.java
@@ -36,6 +36,7 @@
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.HttpClient;
+import org.apache.http.client.HttpResponseException;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
@@ -121,7 +122,7 @@ private void cleanup(final HttpServletRequest req,
ServletUtils.sendJsonSuccess(req, resp);
}
- private Couch getCouch(final HttpServletRequest req) throws IOException {
+ private Couch getCouch(final HttpServletRequest req, HttpClient client) throws IOException {
final String sectionName = new PathParts(req).getKey();
final Configuration section = ini.getSection(sectionName);
if (!section.containsKey("url")) {
@@ -130,6 +131,14 @@ private Couch getCouch(final HttpServletRequest req) throws IOException {
return new Couch(client, section.getString("url"));
}
+ private Couch getAuthenticatingCouch(final HttpServletRequest req) throws IOException {
+ return getCouch(req, HttpClientFactory.getAuthenticatingInstance(req));
+ }
+
+ private Couch getCouch(final HttpServletRequest req) throws IOException {
+ return getCouch(req, client);
+ }
+
private synchronized DatabaseIndexer getIndexer(final Database database)
throws IOException, JSONException {
DatabaseIndexer result = indexers.get(database);
@@ -186,6 +195,9 @@ private void doGetInternal(final HttpServletRequest req, final HttpServletRespon
handleWelcomeReq(req, resp);
return;
case 5:
+ if ( ! validateDatabaseRequest(req, resp)) {
+ return;
+ }
final DatabaseIndexer indexer = getIndexer(req);
if (indexer == null) {
ServletUtils.sendJsonError(req, resp, 500, "error_creating_index");
@@ -228,7 +240,19 @@ private void doPostInternal(final HttpServletRequest req, final HttpServletRespo
indexer.admin(req, resp);
return;
}
- ServletUtils.sendJsonError(req, resp, 400, "bad_request");
+ ServletUtils.sendJsonError(req, resp, 400, "bad_request");
+ }
+
+ private boolean validateDatabaseRequest(HttpServletRequest req, HttpServletResponse resp) throws IOException, JSONException {
+ final Couch couch = getAuthenticatingCouch(req);
+ final Database db = couch.getDatabase(new PathParts(req).getDatabaseName());
+ try {
+ db.getInfo(); //This will throw an HttpResponseException if not authenticated
+ return true;
+ } catch (HttpResponseException e) {
+ ServletUtils.sendJsonError(req, resp, e.getStatusCode(), new JSONObject(e.getMessage()));
+ }
+ return false;
}
}
Something went wrong with that request. Please try again.