You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@manifoldcf.apache.org by kw...@apache.org on 2013/03/07 03:36:48 UTC
svn commit: r1453662 - in
/manifoldcf/integration/elasticsearch/trunk/src/main/java/org/apache/manifoldcf/elasticsearch:
ConfigurationParameters.java QueryModifier.java
Author: kwright
Date: Thu Mar 7 02:36:48 2013
New Revision: 1453662
URL: http://svn.apache.org/r1453662
Log:
Flesh out code for java API part
Modified:
manifoldcf/integration/elasticsearch/trunk/src/main/java/org/apache/manifoldcf/elasticsearch/ConfigurationParameters.java
manifoldcf/integration/elasticsearch/trunk/src/main/java/org/apache/manifoldcf/elasticsearch/QueryModifier.java
Modified: manifoldcf/integration/elasticsearch/trunk/src/main/java/org/apache/manifoldcf/elasticsearch/ConfigurationParameters.java
URL: http://svn.apache.org/viewvc/manifoldcf/integration/elasticsearch/trunk/src/main/java/org/apache/manifoldcf/elasticsearch/ConfigurationParameters.java?rev=1453662&r1=1453661&r2=1453662&view=diff
==============================================================================
--- manifoldcf/integration/elasticsearch/trunk/src/main/java/org/apache/manifoldcf/elasticsearch/ConfigurationParameters.java (original)
+++ manifoldcf/integration/elasticsearch/trunk/src/main/java/org/apache/manifoldcf/elasticsearch/ConfigurationParameters.java Thu Mar 7 02:36:48 2013
@@ -23,5 +23,53 @@ package org.apache.manifoldcf.elasticsea
*/
public class ConfigurationParameters
{
- // MHL
+ /** Base URL, e.g. "http://localhost:8345/mcf-authority-service" */
+ public String authorityServiceBaseURL = "http://localhost:8345/mcf-authority-service";
+ /** Connection timeout, e.g. 60000 */
+ public int connectionTimeout = 60000;
+ /** Socket timeout, e.g. 300000 */
+ public int socketTimeout = 300000;
+ /** Allow field prefix, e.g. "allow_token_" */
+ public String allowFieldPrefix = "allow_token_";
+ /** Deny field prefix, e.g. "deny_token_" */
+ public String denyFieldPrefix = "deny_token_";
+ /** Connection pool size, e.g. 50 */
+ public int connectionPoolSize = 50;
+
+ public ConfigurationParameters setBaseURL(String baseURL)
+ {
+ this.authorityServiceBaseURL = baseURL;
+ return this;
+ }
+
+ public ConfigurationParameters setConnectionTimeout(int timeout)
+ {
+ this.connectionTimeout = timeout;
+ return this;
+ }
+
+ public ConfigurationParameters setSocketTimeout(int timeout)
+ {
+ this.socketTimeout = timeout;
+ return this;
+ }
+
+ public ConfigurationParameters setAllowFieldPrefix(String prefix)
+ {
+ this.allowFieldPrefix = prefix;
+ return this;
+ }
+
+ public ConfigurationParameters setDenyFieldPrefix(String prefix)
+ {
+ this.denyFieldPrefix = prefix;
+ return this;
+ }
+
+ public ConfigurationParameters setConnectionPoolSize(int size)
+ {
+ this.connectionPoolSize = size;
+ return this;
+ }
+
}
\ No newline at end of file
Modified: manifoldcf/integration/elasticsearch/trunk/src/main/java/org/apache/manifoldcf/elasticsearch/QueryModifier.java
URL: http://svn.apache.org/viewvc/manifoldcf/integration/elasticsearch/trunk/src/main/java/org/apache/manifoldcf/elasticsearch/QueryModifier.java?rev=1453662&r1=1453661&r2=1453662&view=diff
==============================================================================
--- manifoldcf/integration/elasticsearch/trunk/src/main/java/org/apache/manifoldcf/elasticsearch/QueryModifier.java (original)
+++ manifoldcf/integration/elasticsearch/trunk/src/main/java/org/apache/manifoldcf/elasticsearch/QueryModifier.java Thu Mar 7 02:36:48 2013
@@ -18,29 +18,249 @@
*/
package org.apache.manifoldcf.elasticsearch;
+import java.util.*;
+import java.net.*;
+
import org.apache.lucene.search.*;
+import org.apache.http.client.HttpClient;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpException;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.HttpResponse;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.CoreConnectionPNames;
+import org.apache.http.util.EntityUtils;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.DefaultRedirectStrategy;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
+import org.slf4j.*;
+
/** This class represents the main Java API for modifying Lucene queries
* within ElasticSearch. It is a singleton class whose main public method
* is thread-safe.
*/
public class QueryModifier
{
- protected final ConfigurationParameters configurationParameters;
+
+ /** Special token for null security fields */
+ static final public String NOSECURITY_TOKEN = "__nosecurity__";
+
+ /** A logger we can use */
+ private static final Logger LOG = LoggerFactory.getLogger(QueryModifier.class);
+
+ // Member variables
+
+ protected final String authorityBaseURL;
+ protected final String fieldAllowDocument;
+ protected final String fieldDenyDocument;
+ protected final String fieldAllowShare;
+ protected final String fieldDenyShare;
+ protected final int connectionTimeout;
+ protected final int socketTimeout;
+ protected final int poolSize;
+
+ protected final ClientConnectionManager connectionManager;
+ protected final HttpClient httpClient;
/** Constructor, which includes configuration information */
public QueryModifier(ConfigurationParameters cp)
{
- this.configurationParameters = cp;
+ authorityBaseURL = cp.authorityBaseURL;
+ fieldAllowDocument = cp.allowFieldPrefix+"document";
+ fieldDenyDocument = cp.denyFieldPrefix+"document";
+ fieldAllowShare = cp.allowFieldPrefix+"share";
+ fieldDenyShare = cp.denyFieldPrefix+"share";
+ connectionTimeout = cp.connectionTimeout;
+ socketTimeout = cp.socketTimeout;
+ poolSize = cp.connectionPoolSize;
+
+ // Set up client pool etc, if there's indication that we should do that
+ if (authorityBaseURL != null)
+ {
+ PoolingClientConnectionManager localConnectionManager = new PoolingClientConnManager();
+ localConnectionManager.setMaxTotal(poolSize);
+ localConnectionManager.setDefaultMaxPerRoute(poolSize);
+ connectionManager = localConnectionManager;
+
+ BasicHttpParams params = new BasicHttpParams();
+ params.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY,true);
+ params.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK,false);
+ params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT,socketTimeout);
+ params.setIntParameter(CoreConnectionPNames.CONNECT_TIMEOUT,connectionTimeout);
+ DefaultHttpClient localClient = new DefaultHttpClient(connectionManager,params);
+ localClient.setRedirectStrategy(new DefaultRedirectStrategy());
+ httpClient = localClient;
+ }
+ }
+
+ /** Shut down the pool etc.
+ */
+ public void shutdown()
+ {
+ if (authorityBaseURL != null)
+ connectionManager.shutdown();
}
/** Main method for wrapping a query with appropriate security.
+ *@param userQuery is the user query to wrap.
+ *@param authenticatedUserName is a user name in the form "user@domain".
+ *@return the wrapped query enforcing ManifoldCF security.
*/
public Query wrapQuery(Query userQuery, String authenticatedUserName)
throws QueryModifierException
{
- // MHL
- return null;
+ if (authorityBaseURL == null)
+ throw new IllegalStateException("Authority base URL required for finding access tokens for a user");
+
+ if (authenticatedUserName == null)
+ throw new IllegalArgumentException("Cannot find user tokens for null user");
+
+ LOG.info("Trying to match docs for user '"+authenticatedUserName+"'");
+
+ return wrapQuery(userQuery,getAccessTokens(authenticatedUserName));
}
-
+
+ /** Main method for wrapping a query with appropriate security.
+ *@param userQuery is the user query to wrap.
+ *@param userAccessTokens are a set of tokens to use to wrap the query (presumably from mod_authz_annotate, upstream)
+ *@return the wrapped query enforcing ManifoldCF security.
+ */
+ public Query wrapQuery(Query userQuery, List<String> userAccessTokens)
+ throws QueryModifierException
+ {
+ BooleanQuery bq = new BooleanQuery();
+
+ Query allowShareOpen = new TermQuery(new Term(fieldAllowShare,NOSECURITY_TOKEN));
+ Query denyShareOpen = new TermQuery(new Term(fieldDenyShare,NOSECURITY_TOKEN));
+ Query allowDocumentOpen = new TermQuery(new Term(fieldAllowDocument,NOSECURITY_TOKEN));
+ Query denyDocumentOpen = new TermQuery(new Term(fieldDenyDocument,NOSECURITY_TOKEN));
+
+ if (userAccessTokens == null || userAccessTokens.size() == 0)
+ {
+ // Only open documents can be included.
+ // That query is:
+ // (fieldAllowShare is empty AND fieldDenyShare is empty AND fieldAllowDocument is empty AND fieldDenyDocument is empty)
+ // We're trying to map to: -(fieldAllowShare:*) , which should be pretty efficient in Solr because it is negated. If this turns out not to be so, then we should
+ // have the SolrConnector inject a special token into these fields when they otherwise would be empty, and we can trivially match on that token.
+ bq.add(allowShareOpen,BooleanClause.Occur.MUST);
+ bq.add(denyShareOpen,BooleanClause.Occur.MUST);
+ bq.add(allowDocumentOpen,BooleanClause.Occur.MUST);
+ bq.add(denyDocumentOpen,BooleanClause.Occur.MUST);
+ }
+ else
+ {
+ // Extend the query appropriately for each user access token.
+ bq.add(calculateCompleteSubquery(fieldAllowShare,fieldDenyShare,allowShareOpen,denyShareOpen,userAccessTokens),
+ BooleanClause.Occur.MUST);
+ bq.add(calculateCompleteSubquery(fieldAllowDocument,fieldDenyDocument,allowDocumentOpen,denyDocumentOpen,userAccessTokens),
+ BooleanClause.Occur.MUST);
+ }
+
+ // Concatenate with the user's original query.
+ BooleanQuery rval = new BooleanQuery();
+ rval.add(new ConstantScoreQuery(bq),BooleanClause.Occur.MUST);
+ rval.add(userQuery,BooleanClause.Occur.MUST);
+ return rval;
+ }
+
+ /** Calculate a complete subclause, representing something like:
+ * ((fieldAllowShare is empty AND fieldDenyShare is empty) OR fieldAllowShare HAS token1 OR fieldAllowShare HAS token2 ...)
+ * AND fieldDenyShare DOESN'T_HAVE token1 AND fieldDenyShare DOESN'T_HAVE token2 ...
+ */
+ protected static Query calculateCompleteSubquery(String allowField, String denyField, Query allowOpen, Query denyOpen, List<String> userAccessTokens)
+ {
+ BooleanQuery bq = new BooleanQuery();
+ bq.setMaxClauseCount(1000000);
+
+ // Add the empty-acl case
+ BooleanQuery subUnprotectedClause = new BooleanQuery();
+ subUnprotectedClause.add(allowOpen,BooleanClause.Occur.MUST);
+ subUnprotectedClause.add(denyOpen,BooleanClause.Occur.MUST);
+ bq.add(subUnprotectedClause,BooleanClause.Occur.SHOULD);
+ for (String accessToken : userAccessTokens)
+ {
+ bq.add(new TermQuery(new Term(allowField,accessToken)),BooleanClause.Occur.SHOULD);
+ bq.add(new TermQuery(new Term(denyField,accessToken)),BooleanClause.Occur.MUST_NOT);
+ }
+ return bq;
+ }
+
+ /** Get access tokens given a username */
+ protected List<String> getAccessTokens(String authenticatedUserName)
+ throws QueryModificationException
+ {
+ try
+ {
+ String theURL = authorityBaseURL + "/UserACLs?username="+URLEncoder.encode(authenticatedUserName,"utf-8");
+ HttpGet method = new HttpGet(theURL);
+ try
+ {
+ HttpResponse httpResponse = httpClient.execute(method);
+ int rval = httpResponse.getStatusLine().getStatusCode();
+ if (rval != 200)
+ {
+ String response = EntityUtils.toString(httpResponse.getEntity(),null);
+ throw new QueryModificationException("Couldn't fetch user's access tokens from ManifoldCF authority service: "+Integer.toString(rval)+"; "+response);
+ }
+ InputStream is = httpResponse.getEntity().getContent();
+ try
+ {
+ String charSet = EntityUtils.getContentCharSet(httpResponse.getEntity());
+ if (charSet == null)
+ charSet = "utf-8";
+ Reader r = new InputStreamReader(is,charSet);
+ try
+ {
+ BufferedReader br = new BufferedReader(r);
+ try
+ {
+ // Read the tokens, one line at a time. If any authorities are down, we have no current way to note that, but someday we will.
+ List<String> tokenList = new ArrayList<String>();
+ while (true)
+ {
+ String line = br.readLine();
+ if (line == null)
+ break;
+ if (line.startsWith("TOKEN:"))
+ {
+ tokenList.add(line.substring("TOKEN:".length()));
+ }
+ else
+ {
+ // It probably says something about the state of the authority(s) involved, so log it
+ LOG.info("For user '"+authenticatedUserName+"', saw authority response "+line);
+ }
+ }
+ return tokenList;
+ }
+ finally
+ {
+ br.close();
+ }
+ }
+ finally
+ {
+ r.close();
+ }
+ }
+ finally
+ {
+ is.close();
+ }
+ }
+ finally
+ {
+ method.abort();
+ }
+ }
+ catch (IOException e)
+ {
+ throw new QueryModificationException("IO exception: "+e.getMessage(),e);
+ }
+ }
+
}