Package org.apache.solr.mcf

Source Code of org.apache.solr.mcf.ManifoldCFSearchComponent$CloseHandler

/**
* 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.solr.common.SolrException;
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.NamedList;
import org.apache.solr.handler.component.ResponseBuilder;
import org.apache.solr.handler.component.SearchComponent;
import org.apache.solr.core.CloseHook;
import org.apache.solr.util.plugin.SolrCoreAware;
import org.apache.solr.core.SolrCore;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.params.*;
import org.slf4j.*;

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 ManifoldCFSearchComponent extends SearchComponent implements SolrCoreAware
{
  /** The component name */
  static final public String COMPONENT_NAME = "mcf";
  /** The parameter that is supposed to contain the authenticated user name, possibly including the AD domain */
  static final public String AUTHENTICATED_USER_NAME = "AuthenticatedUserName";
  /** The parameter that is supposed to contain the MCF authorization domain, if any */
  static final public String AUTHENTICATED_USER_DOMAIN = "AuthenticatedUserDomain";
  /** If there are more than one user/domain, this prefix will allow us to get the users... */
  static final public String AUTHENTICATED_USER_NAME_PREFIX = "AuthenticatedUserName_";
  /** If there are more than one user/domain, this prefix will allow us to get the authorization domains... */
  static final public String AUTHENTICATED_USER_DOMAIN_PREFIX = "AuthenticatedUserDomain_";

  /** 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";
 
  /** Special token for null security fields */
  static final public String NOSECURITY_TOKEN = "__nosecurity__";
 
  /** 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(ManifoldCFSearchComponent.class);

  // Member variables
  String authorityBaseURL = null;
  String fieldAllowDocument = null;
  String fieldDenyDocument = null;
  String fieldAllowShare = null;
  String fieldDenyShare = null;
  String fieldAllowParent = null;
  String fieldDenyParent = null;
  int socketTimeOut;
  MultiThreadedHttpConnectionManager httpConnectionManager = null;
  HttpClient client = null;
  int poolSize;
 
  public ManifoldCFSearchComponent()
  {
    super();
  }

  @Override
  protected void finalize()
    throws Throwable
  {
    super.finalize();
    httpConnectionManager.shutdown();
  }
 
  @Override
  public void init(NamedList args)
  {
    super.init(args);
    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)
      allowAttributePrefix = "allow_token_";
    if (denyAttributePrefix == null)
      denyAttributePrefix = "deny_token_";
    fieldAllowDocument = allowAttributePrefix+"document";
    fieldDenyDocument = denyAttributePrefix+"document";
    fieldAllowShare = allowAttributePrefix+"share";
    fieldDenyShare = denyAttributePrefix+"share";
    fieldAllowParent = allowAttributePrefix+"parent";
    fieldDenyParent = denyAttributePrefix+"parent";
    Integer connectionPoolSize = (Integer)args.get("ConnectionPoolSize");
    poolSize = (connectionPoolSize==null)?50:connectionPoolSize.intValue();

    // Initialize the connection pool
    HttpConnectionManagerParams params = new HttpConnectionManagerParams();
    params.setTcpNoDelay(true);
    params.setStaleCheckingEnabled(false);
    params.setDefaultMaxConnectionsPerHost(poolSize);
    params.setMaxTotalConnections(poolSize);
    httpConnectionManager = new MultiThreadedHttpConnectionManager();
    httpConnectionManager.setParams(params);
    client = new HttpClient(httpConnectionManager);
  }

  @Override
  public void prepare(ResponseBuilder rb) throws IOException
  {
    SolrParams params = rb.req.getParams();
    if (!params.getBool(COMPONENT_NAME, true) || params.get(ShardParams.SHARDS) != null)
      return;
   
    // 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;
   
    // Map from domain to user
    Map<String,String> domainMap = new HashMap<String,String>();
     
    // Get the authenticated user name from the parameters
    String authenticatedUserName = params.get(AUTHENTICATED_USER_NAME);
    if (authenticatedUserName != null)
    {
      String authenticatedUserDomain = params.get(AUTHENTICATED_USER_DOMAIN);
      if (authenticatedUserDomain == null)
        authenticatedUserDomain = "";
      domainMap.put(authenticatedUserDomain, authenticatedUserName);
    }
    else
    {
      // Look for user names/domains using the prefix
      int i = 0;
      while (true)
      {
        String userName = params.get(AUTHENTICATED_USER_NAME_PREFIX+i);
        String domain = params.get(AUTHENTICATED_USER_DOMAIN+i);
        if (userName == null)
          break;
        if (domain == null)
          domain = "";
        domainMap.put(domain,userName);
        i++;
      }
    }
     
    // If this parameter is empty or does not exist, we have to presume this is a guest, and treat them accordingly
    if (domainMap.size() == 0)
    {
      // 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("Default no-user response (open documents only)");
      }
      else
      {
        // Only return 'public' documents (those with no security tokens at all)
        LOG.info("Group tokens received from caller");
        for (String passedToken : passedTokens)
        {
          userAccessTokens.add(passedToken);
        }
      }
    }
    else
    {
      StringBuilder sb = new StringBuilder("[");
      boolean first = true;
      for (String domain : domainMap.keySet())
      {
        if (!first)
          sb.append(",");
        else
          first = false;
        sb.append(domain).append(":").append(domainMap.get(domain));
      }
      sb.append("]");
      LOG.info("Trying to match docs for user '"+sb.toString()+"'");
      // 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(domainMap);
    }

    BooleanQuery bq = new BooleanQuery();
    //bf.setMaxClauseCount(100000);
   
    Query allowShareOpen = new TermQuery(new Term(fieldAllowShare,NOSECURITY_TOKEN));
    Query denyShareOpen = new TermQuery(new Term(fieldDenyShare,NOSECURITY_TOKEN));
    Query allowParentOpen = new TermQuery(new Term(fieldAllowParent,NOSECURITY_TOKEN));
    Query denyParentOpen = new TermQuery(new Term(fieldDenyParent,NOSECURITY_TOKEN));
    Query allowDocumentOpen = new TermQuery(new Term(fieldAllowDocument,NOSECURITY_TOKEN));
    Query denyDocumentOpen = new TermQuery(new Term(fieldDenyDocument,NOSECURITY_TOKEN));
   
    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.
      bq.add(allowShareOpen,BooleanClause.Occur.MUST);
      bq.add(denyShareOpen,BooleanClause.Occur.MUST);
      bq.add(allowParentOpen,BooleanClause.Occur.MUST);
      bq.add(denyParentOpen,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(fieldAllowParent,fieldDenyParent,allowParentOpen,denyParentOpen,userAccessTokens),
        BooleanClause.Occur.MUST);
      bq.add(calculateCompleteSubquery(fieldAllowDocument,fieldDenyDocument,allowDocumentOpen,denyDocumentOpen,userAccessTokens),
        BooleanClause.Occur.MUST);
    }

    // Concatenate with the user's original query.
    List<Query> list = rb.getFilters();
    if (list == null)
    {
      list = new ArrayList<Query>();
      rb.setFilters(list);
    }
    list.add(new ConstantScoreQuery(bq));
  }

  @Override
  public void process(ResponseBuilder rb) throws IOException
  {
    //LOG.info("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 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;
  }
 
  //---------------------------------------------------------------------------------
  // SolrInfoMBean
  //---------------------------------------------------------------------------------
  @Override
  public String getDescription()
  {
    return "ManifoldCF Solr security enforcement plugin";
  }

  @Override
  public String getVersion()
  {
    return "$Revision$";
  }

  @Override
  public String getSourceId()
  {
    return "$Id: ManifoldCFSearchComponent.java 1571007 2014-02-23 13:40:13Z kwright $";
  }

  @Override
  public String getSource()
  {
    return "$URL$";
  }

  public void inform(SolrCore core)
  {
    core.addCloseHook(new CloseHandler());
  }
 
  // Protected methods
 
  /** Get access tokens given a username */
  protected List<String> getAccessTokens(Map<String,String> domainMap)
    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.
    StringBuilder urlBuffer = new StringBuilder(authorityBaseURL);
    urlBuffer.append("/UserACLs");
    int i = 0;
    for (String domain : domainMap.keySet())
    {
      if (i == 0)
        urlBuffer.append("?");
      else
        urlBuffer.append("&");
      // For backwards compatibility, handle the singleton case specially
      if (domainMap.size() == 1 && domain.length() == 0)
      {
        urlBuffer.append("username=").append(URLEncoder.encode(domainMap.get(domain),"utf-8"));
      }
      else
      {
        urlBuffer.append("username_").append(Integer.toString(i)).append("=").append(URLEncoder.encode(domainMap.get(domain),"utf-8")).append("&")
          .append("domain_").append(Integer.toString(i)).append("=").append(URLEncoder.encode(domain,"utf-8"));
      }
      i++;
    }
    String theURL = urlBuffer.toString();
     
    GetMethod method = new GetMethod(theURL);
    try
    {
      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);
      }
      InputStream is = method.getResponseBodyAsStream();
      try
      {
        Reader r = new InputStreamReader(is,method.getResponseCharSet());
        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("Saw authority response "+line);
              }
            }
            return tokenList;
          }
          finally
          {
            br.close();
          }
        }
        finally
        {
          r.close();
        }
      }
      finally
      {
        is.close();
      }
    }
    finally
    {
      method.releaseConnection();
    }
  }

  /** CloseHook implementation.
  */
  protected class CloseHandler extends CloseHook
  {
    public CloseHandler()
    {
    }
   
    @Override
    public void preClose(SolrCore core)
    {
    }
   
    @Override
    public void postClose(SolrCore core)
    {
      // Close the connection pool
      if (httpConnectionManager != null)
      {
        httpConnectionManager.shutdown();
        httpConnectionManager = null;
        client = null;
      }
    }
   
  }

}
TOP

Related Classes of org.apache.solr.mcf.ManifoldCFSearchComponent$CloseHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.