Koji's patch, moved to contrib/mcf
git-svn-id: https://svn.apache.org/repos/asf/incubator/lcf/upstream/solr/SOLR-1895@1173893 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/solr/contrib/mcf/src/java/org/apache/solr/mcf/ManifoldCFSecurityFilter.java b/solr/contrib/mcf/src/java/org/apache/solr/mcf/ManifoldCFSecurityFilter.java
index 1a96db2..270fcf1 100644
--- a/solr/contrib/mcf/src/java/org/apache/solr/mcf/ManifoldCFSecurityFilter.java
+++ b/solr/contrib/mcf/src/java/org/apache/solr/mcf/ManifoldCFSecurityFilter.java
@@ -1,5 +1,3 @@
-/* $Id$ */
-
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
@@ -21,24 +19,16 @@
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.ShardParams;
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.solr.handler.component.ResponseBuilder;
+import org.apache.solr.handler.component.SearchComponent;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.slf4j.*;
-import org.slf4j.Logger;
import java.io.*;
import java.util.*;
@@ -51,11 +41,12 @@
public class ManifoldCFSecurityFilter extends SearchComponent
{
/** The component name */
- static final public String COMPONENT_NAME = "ManifoldCFSecurityFilter";
+ static final public String COMPONENT_NAME = "mcf";
/** 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 */
+ /** 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 */
@@ -65,12 +56,12 @@
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;
+ String authorityBaseURL = null;
+ String fieldAllowDocument = null;
+ String fieldDenyDocument = null;
+ String fieldAllowShare = null;
+ String fieldDenyShare = null;
+ int socketTimeOut;
public ManifoldCFSecurityFilter()
{
@@ -81,11 +72,11 @@
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";
+ Integer timeOut = (Integer)args.get("SocketTimeOut");
+ socketTimeOut = timeOut == null ? 300000 : timeOut;
String allowAttributePrefix = (String)args.get("AllowAttributePrefix");
String denyAttributePrefix = (String)args.get("DenyAttributePrefix");
if (allowAttributePrefix == null)
@@ -101,13 +92,12 @@
@Override
public void prepare(ResponseBuilder rb) throws IOException
{
- if (!security)
- return;
-
SolrParams params = rb.req.getParams();
+ if (!params.getBool(COMPONENT_NAME, true) || params.getBool(ShardParams.IS_SHARD, false))
+ return;
// Log that we got here
- LOG.info("prepare() entry params:\n" + params + "\ncontext: " + rb.req.getContext());
+ //LOG.info("prepare() entry params:\n" + params + "\ncontext: " + rb.req.getContext());
String qry = (String)params.get(CommonParams.Q);
if (qry != null)
@@ -115,9 +105,9 @@
//Check global allowed searches
for (String ga : globalAllowed)
{
- if (qry.equalsIgnoreCase(ga.trim()))
- // Allow this query through unchanged
- return;
+ if (qry.equalsIgnoreCase(ga.trim()))
+ // Allow this query through unchanged
+ return;
}
}
@@ -131,27 +121,26 @@
{
// No authenticated user name.
// mod_authz_annotate may be in use upstream, so look for tokens from it.
+ userAccessTokens = new ArrayList<String>();
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>();
+ LOG.info("Default no-user response (open documents only)");
}
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++)
+ LOG.info("Group tokens received from caller");
+ for (String passedToken : passedTokens)
{
- userAccessTokens.add(passedTokens[i]);
+ userAccessTokens.add(passedToken);
}
}
}
else
{
- LOG.info("ManifoldCFSecurityFilter: Trying to match docs for user '"+authenticatedUserName+"'");
+ LOG.info("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)
@@ -197,7 +186,7 @@
@Override
public void process(ResponseBuilder rb) throws IOException
{
- LOG.info("ManifoldCFSecurityFilter: process() called");
+ //LOG.info("process() called");
}
/** Calculate a complete subclause, representing something like:
@@ -215,9 +204,8 @@
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++)
+ for (String accessToken : userAccessTokens)
{
- String accessToken = userAccessTokens.get(i);
TermsFilter tf = new TermsFilter();
tf.addTerm(new Term(allowField,accessToken));
orFilter.add(new FilterClause(tf,BooleanClause.Occur.SHOULD));
@@ -241,7 +229,7 @@
@Override
public String getVersion()
{
- return "$Revision: 02.05.10096 $";
+ return "$Revision$";
}
@Override
@@ -253,7 +241,7 @@
@Override
public String getSource()
{
- return "ManifoldCFSecurityFilter.java $";
+ return "$URL$";
}
// Protected methods
@@ -269,55 +257,55 @@
GetMethod method = new GetMethod(theURL);
try
{
- method.getParams().setParameter("http.socket.timeout", new Integer(300000));
+ method.getParams().setParameter("http.socket.timeout", socketTimeOut);
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);
+ 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();
- }
+ 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("For user '"+authenticatedUserName+"', saw authority response "+line);
+ }
+ }
+ return tokenList;
+ }
+ finally
+ {
+ br.close();
+ }
+ }
+ finally
+ {
+ r.close();
+ }
}
finally
{
- is.close();
+ is.close();
}
}
finally
diff --git a/solr/contrib/mcf/src/test-files/solr/conf/schema-auth.xml b/solr/contrib/mcf/src/test-files/solr/conf/schema-auth.xml
new file mode 100644
index 0000000..fba04f0
--- /dev/null
+++ b/solr/contrib/mcf/src/test-files/solr/conf/schema-auth.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ 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.
+-->
+<schema name="auth" version="1.4">
+ <types>
+ <fieldType name="string" class="solr.StrField"/>
+ </types>
+ <fields>
+ <field name="id" type="string" indexed="true" stored="true" required="true"/>
+ <!-- MCF Security fields -->
+ <field name="allow_token_document" type="string" indexed="true" stored="false" multiValued="true"/>
+ <field name="deny_token_document" type="string" indexed="true" stored="false" multiValued="true"/>
+ <field name="allow_token_share" type="string" indexed="true" stored="false" multiValued="true"/>
+ <field name="deny_token_share" type="string" indexed="true" stored="false" multiValued="true"/>
+ </fields>
+ <defaultSearchField>id</defaultSearchField>
+ <uniqueKey>id</uniqueKey>
+</schema>
diff --git a/solr/contrib/mcf/src/test-files/solr/conf/solrconfig-auth.xml b/solr/contrib/mcf/src/test-files/solr/conf/solrconfig-auth.xml
new file mode 100644
index 0000000..df92d65
--- /dev/null
+++ b/solr/contrib/mcf/src/test-files/solr/conf/solrconfig-auth.xml
@@ -0,0 +1,63 @@
+<?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.
+-->
+
+<!-- $Id$
+ $Source$
+ $Name$
+ -->
+
+<config>
+
+ <luceneMatchVersion>${tests.luceneMatchVersion:LUCENE_CURRENT}</luceneMatchVersion>
+ <jmx />
+
+ <dataDir>${solr.data.dir:}</dataDir>
+
+ <directoryFactory name="DirectoryFactory" class="${solr.directoryFactory:solr.RAMDirectoryFactory}"/>
+
+ <updateHandler class="solr.DirectUpdateHandler2">
+ </updateHandler>
+
+ <requestHandler name="/update" class="solr.XmlUpdateRequestHandler" />
+
+ <!-- test MCF Security Filter settings -->
+ <searchComponent name="mcf-param" class="org.apache.solr.handler.auth.ManifoldCFSecurityFilter" >
+ <str name="AuthorityServiceBaseURL">http://localhost:8345/mcf-as</str>
+ <int name="SocketTimeOut">3000</int>
+ <str name="AllowAttributePrefix">aap-</str>
+ <str name="DenyAttributePrefix">dap-</str>
+ </searchComponent>
+
+ <searchComponent name="mcf" class="org.apache.solr.handler.auth.ManifoldCFSecurityFilter" >
+ </searchComponent>
+
+ <requestHandler name="/mcf" class="solr.SearchHandler" startup="lazy">
+ <lst name="invariants">
+ <bool name="mcf">true</bool>
+ </lst>
+ <lst name="defaults">
+ <str name="echoParams">all</str>
+ </lst>
+ <arr name="components">
+ <str>query</str>
+ <str>mcf</str>
+ </arr>
+ </requestHandler>
+
+</config>
diff --git a/solr/contrib/mcf/src/test/org/apache/solr/mcf/ManifoldCFSecurityFilterTest.java b/solr/contrib/mcf/src/test/org/apache/solr/mcf/ManifoldCFSecurityFilterTest.java
new file mode 100644
index 0000000..cfc8632
--- /dev/null
+++ b/solr/contrib/mcf/src/test/org/apache/solr/mcf/ManifoldCFSecurityFilterTest.java
@@ -0,0 +1,189 @@
+/**
+* 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 java.io.IOException;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.servlet.Context;
+import org.mortbay.jetty.servlet.ServletHolder;
+
+public class ManifoldCFSecurityFilterTest extends SolrTestCaseJ4 {
+
+ static MockMCFAuthorityService service;
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ initCore("solrconfig-auth.xml","schema-auth.xml");
+ service = new MockMCFAuthorityService();
+ service.start();
+
+ // | share | document
+ // |--------------|--------------
+ // | allow | deny | allow | deny
+ // ------------+-------+------+-------+------
+ // da12 | | | 1, 2 |
+ // ------------+-------+------+-------+------
+ // da13-dd3 | | | 1,3 | 3
+ // ------------+-------+------+-------+------
+ // sa123-sd13 | 1,2,3 | 1, 3 | |
+ // ------------+-------+------+-------+------
+ // sa3-sd1-da23| 3 | 1 | 2,3 |
+ // ------------+-------+------+-------+------
+ // notoken | | | |
+ // ------------+-------+------+-------+------
+ //
+ assertU(adoc("id", "da12", "allow_token_document", "token1", "allow_token_document", "token2"));
+ assertU(adoc("id", "da13-dd3", "allow_token_document", "token1", "allow_token_document", "token3", "deny_token_document", "token3"));
+ assertU(adoc("id", "sa123-sd13", "allow_token_share", "token1", "allow_token_share", "token2", "allow_token_share", "token3", "deny_token_share", "token1", "deny_token_share", "token3"));
+ assertU(adoc("id", "sa3-sd1-da23", "allow_token_document", "token2", "allow_token_document", "token3", "allow_token_share", "token3", "deny_token_share", "token1"));
+ assertU(adoc("id", "notoken"));
+ assertU(commit());
+ }
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ service.stop();
+ }
+
+ @Test
+ public void testParameters() throws Exception {
+ ManifoldCFSecurityFilter mcfFilter = (ManifoldCFSecurityFilter)h.getCore().getSearchComponent("mcf-param");
+ assertEquals("http://localhost:8345/mcf-as", mcfFilter.authorityBaseURL);
+ assertEquals(3000, mcfFilter.socketTimeOut);
+ assertEquals("aap-document", mcfFilter.fieldAllowDocument);
+ assertEquals("dap-document", mcfFilter.fieldDenyDocument);
+ assertEquals("aap-share", mcfFilter.fieldAllowShare);
+ assertEquals("dap-share", mcfFilter.fieldDenyShare);
+ }
+
+ @Test
+ public void testNullUsers() throws Exception {
+ assertQ(req("qt", "/mcf", "q", "*:*", "fl", "id"),
+ "//*[@numFound='1']",
+ "//result/doc[1]/str[@name='id'][.='notoken']");
+ assertQ(req("qt", "/mcf", "q", "*:*", "fl", "id", "AuthenticatedUserName", "anonymous"),
+ "//*[@numFound='1']",
+ "//result/doc[1]/str[@name='id'][.='notoken']");
+ }
+
+ // da12
+ // da13-dd3
+ // sa123-sd13
+ // sa3-sd1-da23
+ // notoken
+ @Test
+ public void testAuthUsers() throws Exception {
+ assertQ(req("qt", "/mcf", "q", "*:*", "fl", "id", "AuthenticatedUserName", "user1"),
+ "//*[@numFound='3']",
+ "//result/doc[1]/str[@name='id'][.='da12']",
+ "//result/doc[2]/str[@name='id'][.='da13-dd3']",
+ "//result/doc[3]/str[@name='id'][.='notoken']");
+
+ assertQ(req("qt", "/mcf", "q", "*:*", "fl", "id", "AuthenticatedUserName", "user2"),
+ "//*[@numFound='3']",
+ "//result/doc[1]/str[@name='id'][.='da12']",
+ "//result/doc[2]/str[@name='id'][.='da13-dd3']",
+ "//result/doc[3]/str[@name='id'][.='notoken']");
+
+ assertQ(req("qt", "/mcf", "q", "*:*", "fl", "id", "AuthenticatedUserName", "user3"),
+ "//*[@numFound='2']",
+ "//result/doc[1]/str[@name='id'][.='da12']",
+ "//result/doc[2]/str[@name='id'][.='notoken']");
+ }
+
+ // da12
+ // da13-dd3
+ // sa123-sd13
+ // sa3-sd1-da23
+ // notoken
+ @Test
+ public void testUserTokens() throws Exception {
+
+ assertQ(req("qt", "/mcf", "q", "*:*", "fl", "id", "UserTokens", "token1"),
+ "//*[@numFound='3']",
+ "//result/doc[1]/str[@name='id'][.='da12']",
+ "//result/doc[2]/str[@name='id'][.='da13-dd3']",
+ "//result/doc[3]/str[@name='id'][.='notoken']");
+
+ assertQ(req("qt", "/mcf", "q", "*:*", "fl", "id", "UserTokens", "token2"),
+ "//*[@numFound='3']",
+ "//result/doc[1]/str[@name='id'][.='da12']",
+ "//result/doc[2]/str[@name='id'][.='sa123-sd13']",
+ "//result/doc[3]/str[@name='id'][.='notoken']");
+
+ assertQ(req("qt", "/mcf", "q", "*:*", "fl", "id", "UserTokens", "token3"),
+ "//*[@numFound='2']",
+ "//result/doc[1]/str[@name='id'][.='sa3-sd1-da23']",
+ "//result/doc[2]/str[@name='id'][.='notoken']");
+
+ assertQ(req("qt", "/mcf", "q", "*:*", "fl", "id", "UserTokens", "token2", "UserTokens", "token3"),
+ "//*[@numFound='3']",
+ "//result/doc[1]/str[@name='id'][.='da12']",
+ "//result/doc[2]/str[@name='id'][.='sa3-sd1-da23']",
+ "//result/doc[3]/str[@name='id'][.='notoken']");
+ }
+
+ static class MockMCFAuthorityService {
+
+ Server server;
+
+ public MockMCFAuthorityService() {
+ server = new Server(8345);
+ Context asContext = new Context(server,"/mcf-authority-service",Context.SESSIONS);
+ asContext.addServlet(new ServletHolder(new UserACLServlet()), "/UserACLs");
+ }
+
+ public void start() throws Exception {
+ server.start();
+ }
+
+ public void stop() throws Exception {
+ server.stop();
+ }
+
+ // username | tokens rewarded
+ // ---------+-------------------------------
+ // null | (no tokens)
+ // user1 | token1
+ // user2 | token1, token2
+ // user3 | token1, token2, token3
+ public static class UserACLServlet extends HttpServlet {
+ @Override
+ public void service(HttpServletRequest req, HttpServletResponse res)
+ throws IOException {
+ String user = req.getParameter("username");
+ res.setStatus(HttpServletResponse.SC_OK);
+ if(user.equals("user1") || user.equals("user2") || user.equals("user3"))
+ res.getWriter().printf("TOKEN:token1\n");
+ if(user.equals("user2") || user.equals("user3"))
+ res.getWriter().printf("TOKEN:token2\n");
+ if(user.equals("user3"))
+ res.getWriter().printf("TOKEN:token3\n");
+ }
+ }
+ }
+}