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");
+      }
+    }
+  }
+}