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 2011/09/22 00:08:50 UTC

svn commit: r1173888 - in /incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf: CHANGES.txt README.txt build.xml src/java/org/apache/solr/mcf/ManifoldCFSecurityFilter.java

Author: kwright
Date: Wed Sep 21 22:08:49 2011
New Revision: 1173888

URL: http://svn.apache.org/viewvc?rev=1173888&view=rev
Log:
Add the original contribution.

Added:
    incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/CHANGES.txt   (with props)
    incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/README.txt   (with props)
    incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/build.xml   (with props)
    incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/src/java/org/apache/solr/mcf/ManifoldCFSecurityFilter.java   (with props)

Added: incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/CHANGES.txt
URL: http://svn.apache.org/viewvc/incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/CHANGES.txt?rev=1173888&view=auto
==============================================================================
--- incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/CHANGES.txt (added)
+++ incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/CHANGES.txt Wed Sep 21 22:08:49 2011
@@ -0,0 +1,22 @@
+Apache Solr ManifoldCFSecurityFilter Library
+		Release Notes
+
+This file describes changes to the Solr ManifoldCFSecurityFilter (contrib/auth) module. See SOLR-1895 for details.
+
+Introduction
+------------
+This module is intended to be used to restrict queries based on a user's authorization information.
+It includes a SearchComponent class org.apache.solr.auth.ManifoldCFSecurityFilter.  This search
+component requires the argument "AuthorityServiceBaseURL", which describes the URL needed to reach
+the ManifoldCF Authority Service.  Other optional parameters are:
+
+AllowAttributePrefix - the first part of the 'allow' family of fields, e.g. "allow_token_document" or
+	"allow_token_share", defaulting to "allow_token_".
+DenyAttributePrefix - the first part of the 'deny' family of fields, e.g. "deny_token_document" or
+	"deny_token_share", defaulting to "deny_token_".
+
+$Id$
+
+==================  4.0.0-dev ==============
+
+Initial Release

Propchange: incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/CHANGES.txt
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/CHANGES.txt
------------------------------------------------------------------------------
    svn:keywords = Id

Added: incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/README.txt
URL: http://svn.apache.org/viewvc/incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/README.txt?rev=1173888&view=auto
==============================================================================
--- incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/README.txt (added)
+++ incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/README.txt Wed Sep 21 22:08:49 2011
@@ -0,0 +1,31 @@
+Getting Started
+---------------
+To start using Solr ManifoldCFSecurityFilter, you first need ManifoldCF installed and running.  See:
+http://incubator.apache.org/incubator/connectors/how-to-build-and-deploy.html.
+
+Then, you will need to add fields to your Solr schema.xml file that can be used to contain document
+authorization information.  There will need to be four of these fields, an 'allow' field for both
+documents and shares, and a 'deny' field for both documents and shares.  For example:
+
+  <field name="allow_token_document" type="string" indexed="true" stored="false" multiValued="true" required="false"/>
+  <field name="allow_token_share" type="string" indexed="true" stored="false" multiValued="true" required="false"/>
+  <field name="deny_token_document" type="string" indexed="true" stored="false" multiValued="true" required="false"/>
+  <field name="deny_token_share" type="string" indexed="true" stored="false" multiValued="true" required="false"/>
+
+Next, modify your solrconfig.xml to add the search component:
+
+  <!-- ManifoldCF document security enforcement component -->
+  <searchComponent name="manifoldCFSecurity"
+    class="org.apache.solr.auth.ManifoldCFSecurityFilter">
+    <str name="AuthorityServiceBaseURL">http://localhost:8345/mcf-authority-service</str>
+  </searchComponent>
+
+Hook up the search component in the solrconfig.xml file wherever you want it, e.g.:
+
+<requestHandler name="search" class="solr.SearchHandler" default="true">
+  <arr name="last-components">
+    <str>manifoldCFSecurity</str>
+  </arr>
+  ...
+</requestHandler>
+

Propchange: incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/README.txt
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/README.txt
------------------------------------------------------------------------------
    svn:keywords = Id

Added: incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/build.xml
URL: http://svn.apache.org/viewvc/incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/build.xml?rev=1173888&view=auto
==============================================================================
--- incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/build.xml (added)
+++ incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/build.xml Wed Sep 21 22:08:49 2011
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+ -->
+
+<project name="solr-mcf" default="default">
+
+  <description>
+    Solr Integration with ManifoldCF, for repository document authorization
+  </description>
+
+  <import file="../contrib-build.xml"/>
+
+  <path id="classpath">
+    <pathelement path="${queries.jar}"/>
+    <path refid="solr.base.classpath"/>
+  </path>
+
+  <target name="module-jars-to-solr" depends="jar-queries">
+    <mkdir dir="${build.dir}/lucene-libs"/>
+    <copy todir="${build.dir}/lucene-libs" preservelastmodified="true" flatten="true" failonerror="true" overwrite="true">
+      <fileset file="${queries.jar}"/>
+    </copy>
+  </target>
+
+  <target name="compile-core" depends="jar-queries, solr-contrib-build.compile-core"/>
+  <target name="dist" depends="module-jars-to-solr, common-solr.dist"/>
+
+</project>

Propchange: incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/build.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/build.xml
------------------------------------------------------------------------------
    svn:keywords = Id

Added: incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/src/java/org/apache/solr/mcf/ManifoldCFSecurityFilter.java
URL: http://svn.apache.org/viewvc/incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/src/java/org/apache/solr/mcf/ManifoldCFSecurityFilter.java?rev=1173888&view=auto
==============================================================================
--- incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/src/java/org/apache/solr/mcf/ManifoldCFSecurityFilter.java (added)
+++ incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/src/java/org/apache/solr/mcf/ManifoldCFSecurityFilter.java Wed Sep 21 22:08:49 2011
@@ -0,0 +1,329 @@
+/* $Id$ */
+
+/**
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package org.apache.solr.mcf;
+
+import org.apache.lucene.index.*;
+import org.apache.lucene.search.*;
+import org.apache.lucene.queries.*;
+import org.apache.solr.search.*;
+import org.apache.solr.client.solrj.*;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.*;
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.*;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.core.*;
+import org.apache.solr.handler.component.*;
+import org.apache.solr.request.*;
+import org.apache.solr.util.*;
+import org.apache.solr.util.plugin.*;
+import org.apache.commons.httpclient.*;
+import org.apache.commons.httpclient.methods.*;
+import org.slf4j.*;
+import org.slf4j.Logger;
+
+import java.io.*;
+import java.util.*;
+import java.net.*;
+
+/**
+* SearchComponent plugin for ManifoldCF-specific document-level access control.
+* Configuration is under the SolrACLSecurity name.
+*/
+public class ManifoldCFSecurityFilter extends SearchComponent
+{
+  /** The component name */
+  static final public String COMPONENT_NAME = "ManifoldCFSecurityFilter";
+  /** The parameter that is supposed to contain the authenticated user name, possibly including the domain */
+  static final public String AUTHENTICATED_USER_NAME = "AuthenticatedUserName";
+  /** This parameter is an array of strings, which contain the tokens to use if there is no authenticated user name.  It's meant to work with mod_authz_annotate,
+  * running under Apache */
+  static final public String USER_TOKENS = "UserTokens";
+  
+  /** The queries that we will not attempt to interfere with */
+  static final private String[] globalAllowed = { "solrpingquery" };
+  
+  /** A logger we can use */
+  private static final Logger LOG = LoggerFactory.getLogger(ManifoldCFSecurityFilter.class);
+
+  // Member variables
+  private boolean security = false;
+  private String authorityBaseURL = null;
+  private String fieldAllowDocument = null;
+  private String fieldDenyDocument = null;
+  private String fieldAllowShare = null;
+  private String fieldDenyShare = null;
+  
+  public ManifoldCFSecurityFilter()
+  {
+    super();
+  }
+
+  @Override
+  public void init(NamedList args)
+  {
+    super.init(args);
+    String securityString = (String)args.get("Security");
+    security = ((securityString==null)?true:securityString.equals("on"));
+    authorityBaseURL = (String)args.get("AuthorityServiceBaseURL");
+    if (authorityBaseURL == null)
+      authorityBaseURL = "http://localhost:8345/mcf-authority-service";
+    String allowAttributePrefix = (String)args.get("AllowAttributePrefix");
+    String denyAttributePrefix = (String)args.get("DenyAttributePrefix");
+    if (allowAttributePrefix == null)
+      allowAttributePrefix = "allow_token_";
+    if (denyAttributePrefix == null)
+      denyAttributePrefix = "deny_token_";
+    fieldAllowDocument = allowAttributePrefix+"document";
+    fieldDenyDocument = denyAttributePrefix+"document";
+    fieldAllowShare = allowAttributePrefix+"share";
+    fieldDenyShare = denyAttributePrefix+"share";
+  }
+
+  @Override
+  public void prepare(ResponseBuilder rb) throws IOException
+  {
+    if (!security)
+      return;
+    
+    SolrParams params = rb.req.getParams();
+
+    // Log that we got here
+    LOG.info("prepare() entry params:\n" + params + "\ncontext: " + rb.req.getContext());
+		
+    String qry = (String)params.get(CommonParams.Q);
+    if (qry != null)
+    {
+      //Check global allowed searches
+      for (String ga : globalAllowed)
+      {
+	if (qry.equalsIgnoreCase(ga.trim()))
+	  // Allow this query through unchanged
+	  return;
+      }
+    }
+
+    List<String> userAccessTokens;
+    
+    // Get the authenticated user name from the parameters
+    String authenticatedUserName = params.get(AUTHENTICATED_USER_NAME);
+    
+    // If this parameter is empty or does not exist, we have to presume this is a guest, and treat them accordingly
+    if (authenticatedUserName == null || authenticatedUserName.length() == 0)
+    {
+      // No authenticated user name.
+      // mod_authz_annotate may be in use upstream, so look for tokens from it.
+      String[] passedTokens = params.getParams(USER_TOKENS);
+      if (passedTokens == null)
+      {
+        // Only return 'public' documents (those with no security tokens at all)
+        LOG.info("ManifoldCFSecurityFilter: Default no-user response (open documents only)");
+        userAccessTokens = new ArrayList<String>();
+      }
+      else
+      {
+        // Only return 'public' documents (those with no security tokens at all)
+        LOG.info("ManifoldCFSecurityFilter: Group tokens received from caller");
+        userAccessTokens = new ArrayList<String>();
+        for (int i = 0; i < passedTokens.length; i++)
+        {
+          userAccessTokens.add(passedTokens[i]);
+        }
+      }
+    }
+    else
+    {
+      LOG.info("ManifoldCFSecurityFilter: Trying to match docs for user '"+authenticatedUserName+"'");
+      // Valid authenticated user name.  Look up access tokens for the user.
+      // Check the configuration arguments for validity
+      if (authorityBaseURL == null)
+      {
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error initializing ManifoldCFSecurityFilter component: 'AuthorityServiceBaseURL' init parameter required");
+      }
+      userAccessTokens = getAccessTokens(authenticatedUserName);
+    }
+
+    BooleanFilter bf = new BooleanFilter();
+    
+    if (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.
+      bf.add(new FilterClause(new QueryWrapperFilter(new WildcardQuery(new Term(fieldAllowShare,"*"))),BooleanClause.Occur.MUST_NOT));
+      bf.add(new FilterClause(new QueryWrapperFilter(new WildcardQuery(new Term(fieldDenyShare,"*"))),BooleanClause.Occur.MUST_NOT));
+      bf.add(new FilterClause(new QueryWrapperFilter(new WildcardQuery(new Term(fieldAllowDocument,"*"))),BooleanClause.Occur.MUST_NOT));
+      bf.add(new FilterClause(new QueryWrapperFilter(new WildcardQuery(new Term(fieldDenyDocument,"*"))),BooleanClause.Occur.MUST_NOT));
+    }
+    else
+    {
+      // Extend the query appropriately for each user access token.
+      bf.add(new FilterClause(calculateCompleteSubfilter(fieldAllowShare,fieldDenyShare,userAccessTokens),BooleanClause.Occur.MUST));
+      bf.add(new FilterClause(calculateCompleteSubfilter(fieldAllowDocument,fieldDenyDocument,userAccessTokens),BooleanClause.Occur.MUST));
+    }
+
+    // Concatenate with the user's original query.
+    //FilteredQuery query = new FilteredQuery(rb.getQuery(),bf);
+    //rb.setQuery(query);
+    List<Query> list = rb.getFilters();
+    if (list == null)
+    {
+      list = new ArrayList<Query>();
+      rb.setFilters(list);
+    }
+    list.add(new ConstantScoreQuery(bf));
+  }
+
+  @Override
+  public void process(ResponseBuilder rb) throws IOException
+  {
+    LOG.info("ManifoldCFSecurityFilter: process() called");
+  }
+
+  /** 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 Filter calculateCompleteSubfilter(String allowField, String denyField, List<String> userAccessTokens)
+  {
+    BooleanFilter bf = new BooleanFilter();
+    
+    // Add a clause for each token.  This will be added directly to the main filter (as a deny test), as well as to an OR's subclause (as an allow test).
+    BooleanFilter orFilter = new BooleanFilter();
+    // Add the empty-acl case
+    BooleanFilter subUnprotectedClause = new BooleanFilter();
+    subUnprotectedClause.add(new FilterClause(new QueryWrapperFilter(new WildcardQuery(new Term(allowField,"*"))),BooleanClause.Occur.MUST_NOT));
+    subUnprotectedClause.add(new FilterClause(new QueryWrapperFilter(new WildcardQuery(new Term(denyField,"*"))),BooleanClause.Occur.MUST_NOT));
+    orFilter.add(new FilterClause(subUnprotectedClause,BooleanClause.Occur.SHOULD));
+    for (int i = 0; i < userAccessTokens.size(); i++)
+    {
+      String accessToken = userAccessTokens.get(i);
+      TermsFilter tf = new TermsFilter();
+      tf.addTerm(new Term(allowField,accessToken));
+      orFilter.add(new FilterClause(tf,BooleanClause.Occur.SHOULD));
+      tf = new TermsFilter();
+      tf.addTerm(new Term(denyField,accessToken));
+      bf.add(new FilterClause(tf,BooleanClause.Occur.MUST_NOT));
+    }
+    bf.add(new FilterClause(orFilter,BooleanClause.Occur.MUST));
+    return bf;
+  }
+  
+  //---------------------------------------------------------------------------------
+  // SolrInfoMBean
+  //---------------------------------------------------------------------------------
+  @Override
+  public String getDescription()
+  {
+    return "ManifoldCF Solr security enforcement plugin";
+  }
+
+  @Override
+  public String getVersion()
+  {
+    return "$Revision: 02.05.10096 $";
+  }
+
+  @Override
+  public String getSourceId()
+  {
+    return "$Id$";
+  }
+
+  @Override
+  public String getSource()
+  {
+    return "ManifoldCFSecurityFilter.java $";
+  }
+	
+  // Protected methods
+  
+  /** Get access tokens given a username */
+  protected List<String> getAccessTokens(String authenticatedUserName)
+    throws IOException
+  {
+    // We can make this more complicated later, with support for https etc., but this is enough to demonstrate how it all should work.
+    HttpClient client = new HttpClient();
+    String theURL = authorityBaseURL + "/UserACLs?username="+URLEncoder.encode(authenticatedUserName,"utf-8");
+      
+    GetMethod method = new GetMethod(theURL);
+    try
+    {
+      method.getParams().setParameter("http.socket.timeout", new Integer(300000));
+      method.setFollowRedirects(true);
+      int rval = client.executeMethod(method);
+      if (rval != 200)
+      {
+	String response = method.getResponseBodyAsString();
+	throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,"Couldn't fetch user's access tokens from ManifoldCF authority service: "+Integer.toString(rval)+"; "+response);
+      }
+      InputStream is = method.getResponseBodyAsStream();
+      try
+      {
+	Reader r = new InputStreamReader(is,"utf-8");
+	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("ManifoldCFSecurityFilter: For user '"+authenticatedUserName+"', saw authority response "+line);
+	      }
+	    }
+	    return tokenList;
+	  }
+	  finally
+	  {
+	    br.close();
+	  }
+	}
+	finally
+	{
+	  r.close();
+	}
+      }
+      finally
+      {
+	is.close();
+      }
+    }
+    finally
+    {
+      method.releaseConnection();
+    }
+  }
+  
+}
\ No newline at end of file

Propchange: incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/src/java/org/apache/solr/mcf/ManifoldCFSecurityFilter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/lcf/upstream/solr/SOLR-1895/solr/contrib/mcf/src/java/org/apache/solr/mcf/ManifoldCFSecurityFilter.java
------------------------------------------------------------------------------
    svn:keywords = Id