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