Package org.apache.solr.mcf

Source Code of org.apache.solr.mcf.ManifoldCFQParserPlugin$ManifoldCFQueryParser

/**
* 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.QParserPlugin;
import org.apache.solr.search.QParser;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.request.SolrQueryRequest;
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.http.client.HttpClient;
import org.apache.http.HttpStatus;
import org.apache.http.HttpException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.HttpResponse;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.util.EntityUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.slf4j.*;

import java.io.*;
import java.util.*;
import java.net.*;

/**
* Query parser plugin for ManifoldCF-specific document-level access control.
*/
public class ManifoldCFQParserPlugin extends QParserPlugin
{
  /** The component name */
  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 */
  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(ManifoldCFQParserPlugin.class);

  // Member variables
  String authorityBaseURL = null;
  String fieldAllowDocument = null;
  String fieldDenyDocument = null;
  String fieldAllowShare = null;
  String fieldDenyShare = null;
  int socketTimeOut;
  Integer connectionManagerSynchronizer = new Integer(0);
  ThreadSafeClientConnManager httpConnectionManager = null;
  DefaultHttpClient client = null;
  int poolSize;
 
  public ManifoldCFQParserPlugin()
  {
    super();
  }

  @Override
  public void init(NamedList 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";
    Integer connectionPoolSize = (Integer)args.get("ConnectionPoolSize");
    poolSize = (connectionPoolSize==null)?50:connectionPoolSize.intValue();
  }
 
  protected void initializeClient(SolrCore core)
  {
    synchronized (connectionManagerSynchronizer)
    {
      if (client == null)
      {
        // Initialize the connection pool
        httpConnectionManager = new ThreadSafeClientConnManager();
        httpConnectionManager.setMaxTotal(poolSize);
        httpConnectionManager.setDefaultMaxPerRoute(poolSize);
        BasicHttpParams params = new BasicHttpParams();
        params.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY,true);
        params.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK,false);
        params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT,socketTimeOut);
        client = new DefaultHttpClient(httpConnectionManager,params);
        client.setRedirectStrategy(new DefaultRedirectStrategy());
        core.addCloseHook(new CloseHandler());
      }
    }
  }

  @Override
  public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req)
  {
    initializeClient(req.getCore());
    return new ManifoldCFQueryParser(qstr,localParams,params,req);
  }

  protected class ManifoldCFQueryParser extends QParser
  {
    public ManifoldCFQueryParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req)
    {
      super(qstr,localParams,params,req);
    }
       
    @Override
    /** Create and return the <code>Query</code> object represented by <code>qstr</code>
    * @see #getQuery()
    **/
    public Query parse()
    {
      SolrParams params = req.getParams();

      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.
        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
      {
        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)
        {
          throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error initializing ManifoldCFSecurityFilter component: 'AuthorityServiceBaseURL' init parameter required");
        }
        try
        {
          userAccessTokens = getAccessTokens(authenticatedUserName);
        }
        catch (IOException e)
        {
          LOG.error("IO exception communicating with MCF authority service: "+e.getMessage(),e);
          throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "IO exception communicating with MCF authority service: "+e.getMessage());
        }
      }

      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 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(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(fieldAllowDocument,fieldDenyDocument,allowDocumentOpen,denyDocumentOpen,userAccessTokens),
          BooleanClause.Occur.MUST);
      }

      return new ConstantScoreQuery(bq);
    }

    /** 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;
    }
   
    // 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.
      String theURL = authorityBaseURL + "/UserACLs?username="+URLEncoder.encode(authenticatedUserName,"utf-8");
       
      HttpGet method = new HttpGet(theURL);
      try
      {
        HttpResponse httpResponse = client.execute(method);
        int rval = httpResponse.getStatusLine().getStatusCode();
        if (rval != 200)
        {
          String response = EntityUtils.toString(httpResponse.getEntity(),null);
          throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,"Couldn't fetch user's access tokens from ManifoldCF authority service: "+Integer.toString(rval)+"; "+response);
        }
        InputStream is = httpResponse.getEntity().getContent();
        try
        {
          String charSet = EntityUtils.getContentCharSet(httpResponse.getEntity());
          if (charSet == null)
            charSet = "utf-8";
          Reader r = new InputStreamReader(is,charSet);
          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();
        }
      }
      finally
      {
        //method.releaseConnection();
      }
    }
  }

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

}
TOP

Related Classes of org.apache.solr.mcf.ManifoldCFQParserPlugin$ManifoldCFQueryParser

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.