All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.novartis.opensource.yada.plugin.Gatekeeper Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2016 Novartis Institutes for BioMedical Research Inc.
 * Licensed 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.
 */
/**

 * Copyright 2016 Novartis Institutes for BioMedical Research Inc.

 * Licensed 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 com.novartis.opensource.yada.plugin;

import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.novartis.opensource.yada.ConnectionFactory;
import com.novartis.opensource.yada.Finder;
import com.novartis.opensource.yada.JSONParams;
import com.novartis.opensource.yada.JSONParamsEntry;
import com.novartis.opensource.yada.QueryManager;
import com.novartis.opensource.yada.Service;
import com.novartis.opensource.yada.YADAConnectionException;
import com.novartis.opensource.yada.YADAException;
import com.novartis.opensource.yada.YADAFinderException;
import com.novartis.opensource.yada.YADAQuery;
import com.novartis.opensource.yada.YADAQueryConfigurationException;
import com.novartis.opensource.yada.YADARequest;
import com.novartis.opensource.yada.YADARequestException;
import com.novartis.opensource.yada.YADASQLException;
import com.novartis.opensource.yada.YADASecurityException;
import com.novartis.opensource.yada.YADASecuritySpec;
import com.novartis.opensource.yada.util.YADAUtils;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;

/**
 * A Preprocess plugin to evaluate user authorization for query execution.
 *
 * @author David Varon
 * @since 7.0.0
 *
 */

@SecurityPreprocessor
public class Gatekeeper extends AbstractPreprocessor {

  /**
   * Local logger handle
   */
  private static final Logger LOG = Logger.getLogger(Gatekeeper.class);

  /**
   * Constant equal to {@value}
   */
  protected static final String DEFAULT_AUTH_TOKEN_PROPERTY = "security.token";

  /**
   * Constant equal to {@value}
   */
  protected static final String EXECUTION_POLICY_COLUMNS = "execution.policy.columns";

  /**
   * Constant equal to {@value}
   */
  protected static final String EXECUTION_POLICY_INDICES = "execution.policy.indices";

  /**
   * Constant equal to {@value}
   */
  protected static final String EXECUTION_POLICY_INDEXES = "execution.policy.indexes";

  /**
   * Constant equal to {@value}
   */
  protected static final String CONTENT_POLICY_PREDICATE = "content.policy.predicate";
  
  /**
   * Constant equal to {@value}
   * @since 9.0.0
   */
  protected static final String COLUMNS = "columns";

  /**
   * Constant equal to {@value}
   * @since 9.0.0
   */
  protected static final String INDICES = "indices";

  /**
   * Constant equal to {@value}
   * @since 9.0.0
   */
  protected static final String INDEXES = "indexes";

  /**
   * Constant equal to {@value}
   * @since 9.0.0
   */
  protected static final String PREDICATE = "predicate";

  /**
   * Constant equal to {@value}
   * 
   * @since 8.1.0
   * @deprecated since 9.0.0 moved 
   */
  @Deprecated
  protected static final String RX_COL_INJECTION = "(([a-zA-Z0-9_]+):)?(get[A-Z][a-zA-Z0-9_]+\\([A-Za-z0-9_]*\\))";

  /**
   * Constant equal to {@value}
   * 
   * @since 8.1.0
   * @deprecated since 9.0.0 moved
   */
  @Deprecated
  protected static final String RX_IDX_INJECTION = "(([0-9]+):)?(get[A-Z][a-zA-Z0-9_]+\\([A-Za-z0-9_]*\\))";

  // --------------------------------------------------------------------------------
  // TODO: Change these to system properties
  // --------------------------------------------------------------------------------

  /**
   * Constant with value: {@value} SourceExchanger plugin reference
   * 
   * @since 1.0
   */
  private final static String SOURCE_EXCHANGER = "SourceExchanger";

  // --------------------------------------------------------------------------------

  /**
   * Contains the list of allow qualifiers from A11N
   */
  private ArrayList allowList = new ArrayList();

  /**
   * Contains the list of deny qualifiers from A11N
   */
  private ArrayList denyList = new ArrayList();

  /**
   * Contains the user identity data from authority
   */
  private Object identity = new Object();

  /**
   * Contains the synchronization token
   */
  private String syncToken = new String();

  /**
   * Contains the conditions specified in A11N.
   */
  private JSONObject locks = new JSONObject();

  /**
   * Contains the user grant from authority
   */
  private Object grant = new Object();

  /**
   * Contains the list of groups for Content Policy
   */
  private ArrayList ships = new ArrayList();

  /**
   * Validates the request host, user, security params, and security query
   * execution results
   *
   * @throws YADAPluginException   when plugin processing fails
   * @throws YADASecurityException when the user is unauthorized or there is an
   *                               error in policy processing
   * @see com.novartis.opensource.yada.plugin.AbstractPreprocessor#engage(com.novartis.opensource.yada.YADARequest,
   *      com.novartis.opensource.yada.YADAQuery)
   */

  @Override
  public void engage(YADARequest yadaReq, YADAQuery yq) throws YADAPluginException, YADASecurityException {

    super.engage(yadaReq, yq);

    // Make header available
    try
    {
      this.setHTTPHeaders(YADA_HDR_AUTH_NAMES);
      setSyncToken(obtainSyncToken(yadaReq));
    }
    catch (YADARequestException e)
    {
      // Gatekeeper prevents access on header setting or syncToken
      // obtaining/setting errors
      throw new YADASecurityException("Unauthorized.");
    }

    validateYADARequest();

  }

  /**
   * Checking header then cookie for token to set
   * 
   * @throws YADASecurityException when token cannot be successfully obtained
   */

  @Override
  public void obtainToken(YADARequest yadaReq) throws YADASecurityException {
    // Check header for token
    Pattern rxAuthTkn = Pattern.compile(RX_HDR_AUTH_TKN_PREFIX);
    if (this.hasHttpHeaders())
    {
      for (int i = 0; i < this.getHttpHeaders().names().length(); i++)
      {
        Matcher m1 = rxAuthTkn
            .matcher((CharSequence) this.getHttpHeaders().get(this.getHttpHeaders().names().getString(i)));
        if (m1.matches() && m1.groupCount() == 3)
        {// valid header
          this.setToken(m1.group(3));
        }
      }
    }
    else
    {
      // Check cookie for token
      this.setToken(getCookie(YADA_CK_TKN));
    }
    if (!hasToken())
    {
      // Always require a token for Gatekeeper access
      throw new YADASecurityException("Unauthorized.");
    }
  }

  /**
   * Checking header then cookie for token to set
   * 
   * @param yadaReq the {@link YADARequest} containing the token header to process
   * @return the value of the {@link Authorization#YADA_HDR_SYNC_TKN} header
   */

  public String obtainSyncToken(YADARequest yadaReq) {
    // Check header for sync token
    String result = new String();
    if (this.hasHttpHeaders())
    {
      JSONObject object = this.getHttpHeaders();
      JSONArray  keys   = object.names();
      for (int i = 0; i < keys.length(); ++i)
      {
        if (keys.getString(i).equalsIgnoreCase(YADA_HDR_SYNC_TKN))
        {
          result = object.getString(keys.getString(i));
        }
      }
    }
    return result;
  }

  /**
   * Overrides {@link TokenValidator#validateToken()}.
   *
   * @throws YADASecurityException when the {@link #DEFAULT_AUTH_TOKEN_PROPERTY}
   *                               is not set
   */

  @Override
  public void validateToken() throws YADASecurityException {
    // validate token as well-formed
    try
    {
      JWT.require(Algorithm.HMAC512(System.getProperty(JWSKEY))).withIssuer(System.getProperty(JWTISS)).build()
          .verify((String) this.getToken());
    }
    catch (JWTVerificationException | IllegalArgumentException exception)
    {
      // UTF-8 encoding not supported
      String msg = "Validation Error ";
      throw new YADASecurityException(msg, exception);
    }

  }

  /**
   * 
   * @return the identity object from the identity cache
   * @since 8.7.6
   */
  public Object obtainIdentity() {
    Object result = getCacheEntry(YADA_IDENTITY_CACHE, (String) this.getToken());
    return result;
  }

  /**
   * Obtain specified GRANT(KEYS) from current identity
   * 
   * @param app the YADA app for which to obtain grants
   * @return a {@link JSONArray} as an {@link Object} containing the {@link Authorization#YADA_IDENTITY_KEYS}
   * @throws YADASecurityException when the user's identity is malformed, i.e., invalid json
   * @since 8.7.6
   */
  public Object obtainGrant(String app) throws YADASecurityException {
    JSONObject jo = null;
    try
    {
      jo = new JSONObject((String) getIdentity());
    }
    catch (JSONException e)
    {
      String msg = "Identity is malformed.";
      throw new YADASecurityException(msg, e);
    }
    
    JSONArray  ja = jo.getJSONArray(YADA_IDENTITY_GRANTS);
    // find the app
    JSONArray keys = new JSONArray();
    for (int i = 0; i < ja.length(); i++)
    {
      if (app.equals(ja.getJSONObject(i).getString(YADA_IDENTITY_APP).toString()))
      {
        for (int ii = 0; ii < ja.getJSONObject(i).getJSONArray(YADA_IDENTITY_KEYS).length(); ii++)
        {
          keys.put(ja.getJSONObject(i).getJSONArray(YADA_IDENTITY_KEYS).getJSONObject(ii).getString(YADA_IDENTITY_KEY));
        }
      }
    }
    return keys;
  }

  /**
   * Authorization of query use for given context
   * {@link Authorization#authorize()}
   * 
   * @since 8.7.6
   */
  @Override
  public void authorize() throws YADASecurityException {

    boolean authorized = false;
    boolean blacklist  = false;

    // Check authority for identity
    Object ident = obtainIdentity();
    setIdentity(ident);
    
    // Check a11n table for locks
    try
    {
      setLocks(obtainLocks());
    }
    catch (YADASecurityException e)
    {
      String msg = "Unauthorized. Unable to set query locks.";
      throw new YADASecurityException(msg, e);
    }
    
    //TODO there may need to be an array of "locks" i.e., qualifier:type pairs in the 
    //     authorization policy spec.  This would mimic multiple rows in the a11n table.
    if (hasLocks())
    {
      JSONArray key = getLocks().names();
      for (int i = 0; i < key.length(); ++i)
      {
        String grant    = key.getString(i);
        String listtype = getLocks().getString(grant);
        if (listtype.equals(AUTH_TYPE_WHITELIST))
        {
          // Add whitelist locks to allowList
          addAllowListEntry(grant);
        }
        else
        {
          // Remove blacklist locks and require key by setting blacklist to true
          removeAllowListEntry(grant);
          blacklist = true;
        }
      }
    }
    
    if (hasToken() && hasSyncToken())
    {
      // Check header.syncToken == identity.syncToken
      // https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern
      JSONObject jo = new JSONObject((String) getIdentity());
      if (jo.getString(YADA_HDR_SYNC_TKN).equals(getSyncToken()))
      {
        // What app (context) are we interested in?
        String      app = "";
        YADARequest ryq = this.getYADARequest();
        if (ryq.getPluginConfig().containsKey(SOURCE_EXCHANGER))
        {
          // connection source app - from SourceExchanger argument
          app = ryq.getPluginConfig().get(SOURCE_EXCHANGER).get(0);
        }
        else
        {
          // connection source app - from query
          YADAQuery ayq = this.getYADAQuery();
          app = ayq.getApp();
        }
        try
        {
          // Obtain a relevant GRANT if it exists within IDENTITY
          setGrant(obtainGrant(app));
        }
        catch (YADASecurityException e)
        {
          String msg = "User is not authorized";
          throw new YADASecurityException(msg);
        }
        // Is there a GRANT for this APP or is there a blacklist entry requiring
        // a valid lock?
        if (hasGrants() || blacklist == true)
        {
          if (getAllowList().size() > 0)
          {
            // Do we have a GRANT containing a KEY fitting the LOCK (specified
            // in a11n table 'A' row) protecting this query?
            for (int i = 0; i < ((JSONArray) getGrant()).length(); i++)
            {
              if (getAllowList().contains(((JSONArray) getGrant()).get(i).toString()))
              {
                authorized = true;
              }
            }
          }
          else
          {
            // GRANT exists for this APP and no LOCK specified
            authorized = true;
          }
        }
      }
    }
    // Check for failed authorization and throw error
    if (!authorized)
    {
      String msg = "Authorization Error.";
      throw new YADASecurityException(msg);
    }

  }

  /**
   * 
   * @return yadaauth {Role: [whitelist/blacklist]}
   * @throws YADASecurityException when a database-backed yada index request cannot be processed 
   * @since 8.7.6
   */
  public JSONObject obtainLocks() throws  YADASecurityException {
    JSONObject result = new JSONObject();
    String type;
    String qualifier;
    List qualifierList;
    YADASecuritySpec spec = this.getSecuritySpec();
    if(Finder.hasYADALib())        
    {
      if(null != spec && spec.hasAuthorizationPolicy())
      {
        Map policy = spec != null ? this.getSecuritySpec().getAuthorizationPolicy() : null;
        type = (String) policy.get(YADASecuritySpec.KEY_TYPE);
        qualifierList = (List) policy.get(YADASecuritySpec.KEY_QUALIFIER);
        for(Object q : qualifierList)
        {
          result.put((String) q, type);
        }
      }
    }
    else
    {
      // get the security params associated to the query
      // TODO: Replace prepared statement with YADA query
      String qname = getYADAQuery().getQname();
      try (ResultSet rs = YADAUtils.executePreparedStatement(YADA_A11N_QUERY, new Object[] { qname });)
      {
        while (rs.next())
        {
          // YADA_A11N.POLICY == "A"
          if ("A".equals(rs.getString(2)))
          {
            type      = rs.getString(3); // YADA_A11N.TYPE
            qualifier = rs.getString(4); // YADA_A11N.QNAME (a role name)
            result.put(qualifier, type);
          }
        }
        ConnectionFactory.releaseResources(rs);
      }
      catch (SQLException | YADAConnectionException | YADASQLException e)
      {
        String msg = "There was a problem executing the authorization query.";
        throw new YADASecurityException(msg, e);
      }
    }
    return result;
  }

  /**
   * Returns {@code true} if {@link #WHITELIST} or {@link #BLACKLIST} is stored in
   * the {@code YSEC_PARAMS} table corresponding to the security target
   *
   * @param policy the value of the {@code YSEC_PARAM_NAME} field in the
   *               {@code YSEC_PARAMS} table
   * @return {@code true} if {@link #WHITELIST} or {@link #BLACKLIST} is set
   */

  protected boolean hasValidPolicy(String policy) {
    return isWhitelist(policy) || isBlacklist(policy);
  }

  /**
   * Retrieves and processes the security query, and validates the results per the
   * security specification s *
   * 
   * @throws YADASecurityException when there is an issue retrieving or processing
   *                               the security query
   */

  @SuppressWarnings("unchecked")
  @Override
  public void applyExecutionPolicy() throws YADASecurityException {

    // TODO the security query executes for every iteration of the qname

    // in the current request. a flag needs to be set somewhere to indicate

    // clearance has already been granted. This can't be in YADAQuery because of
    // caching.

    // TODO needs to support app targets as well as qname targets

    // TODO tests for auth failure, i.e., unauthorized

    // TODO tests for ignoring attempted plugin overrides

    // TODO make it impossible to execute a protector query as a primary query
    // without a server-side flag set, or

    // perhaps some authorization (i.e., for testing, maybe with a content
    // policy)

    // This will close an attack vector.

    // TODO support dependency injection for other methods in addition to token
    // for execution policy
    
    
    List spec = null;
    List prunedSpec = new ArrayList<>();

    // process security spec

    // query can be standard or json

    // if json, need name of column to map to token

    // if standard, need list of relevant indices
    String policyColumns = null;
    String policyIndices = null;
    
    if(this.getSecuritySpec() != null)
    {
      // this is a crap way to do it--store as list, convert to space-del string, then split later, but 
      // it's the quickest route to valhalla
      if(this.getSecuritySpec().hasExecutionPolicy())
      {
        List polcols = (List) this.getSecuritySpec().getExecutionPolicy().get(COLUMNS);
        List polind  = (List) this.getSecuritySpec().getExecutionPolicy().get(INDICES);
        if(null != polcols && polcols.size() > 0)
          policyColumns = String.join(" ", polcols).replaceAll("[\\[\\]\"]","");
        if(null != polind && polind.size() > 0)
          policyIndices = String.join(" ", polind).replaceAll("[\\[\\]\"]","");
        if(null == policyIndices)        
        {
          polind = (List) this.getSecuritySpec().getExecutionPolicy().get(INDEXES);
          if(null != polind && polind.size() > 0)
            policyIndices = String.join(" ", polind).replaceAll("[\\[\\]\"]","");
        }
        Map execPol = this.getSecuritySpec().getExecutionPolicy();
        spec = new ArrayList();
        String qname = getYADAQuery().getQname();
        String type = (String) execPol.get(YADASecuritySpec.KEY_TYPE);
        String protector = (String) execPol.get(YADASecuritySpec.KEY_PROTECTOR);
        spec.add(new SecurityPolicyRecord(qname,EXECUTION_POLICY_CODE,type,protector));
      }
    }
    else
    {
      policyColumns = getArgumentValue(EXECUTION_POLICY_COLUMNS);
      policyIndices = getArgumentValue(EXECUTION_POLICY_INDICES);
      if(policyIndices == null)
        policyIndices = getArgumentValue(EXECUTION_POLICY_INDEXES);
      spec = getSecurityPolicyRecords(EXECUTION_POLICY_CODE);
    }
    
    String  polColParams_rx     = YADASecuritySpec.RX_IDX;
    String  polColJSONParams_rx = YADASecuritySpec.RX_COL;
    String  result              = "";
    int     index               = -1;
    String  injectedIndex       = "";
    boolean policyHasParams     = false;
    boolean policyHasJSONParams = false;
    boolean reqHasParams        = getYADARequest().getParams() == null || getYADARequest().getParams().length == 0
        ? false
        : true;
    boolean reqHasJSONParams    = YADAUtils.hasJSONParams(getYADARequest());
    for (SecurityPolicyRecord secRec: spec)
    {

      // Are params required for security query?
      if (policyIndices != null && policyIndices.split(" ")[0].matches(polColParams_rx))
      {
        policyHasParams = true;
      }
      if (policyColumns != null && policyColumns.split(" ")[0].matches(polColJSONParams_rx))
      {
        policyHasJSONParams = true;
      }

      // request and policy must have syntax compatibility, i.e., matching param
      // syntax, or no params
      if ((policyHasParams && !reqHasJSONParams) || (policyHasJSONParams && !reqHasParams)
          || (!policyHasParams && reqHasJSONParams) || (!policyHasJSONParams && reqHasParams)
          || !(policyHasParams || reqHasParams || policyHasJSONParams || reqHasJSONParams))
      {

        // confirm sec spec is config properly
        if (hasValidPolicy(secRec.getType())) // whitelist or blacklist
        {
          if(!Finder.hasYADALib())
          {
            // confirm sec spec is mapped to requested query
            try
            {            
              new Finder().getQuery(secRec.getA11nQname());
            }
            catch (YADAFinderException e)
            {
              String msg = "Unauthorized. Authorization qname not found.";
              throw new YADASecurityException(msg);
            }
            catch (YADAConnectionException | YADAQueryConfigurationException e)
            {
              String msg = "Unauthorized. Unable to check for security query. This could be a temporary issue.";
              throw new YADASecurityException(msg, e);
            }
          }
          // security query exists
        }
        else
        {
          String msg = "Unauthorized, due to policy misconfiguration. Must be \"blacklist\" or \"whitelist.\"";
          throw new YADASecurityException(msg);
        }
        prunedSpec.add(secRec);
      }
    }

    // kill the query if there aren't any compatible specs
    if (prunedSpec.size() == 0)
    {
      String msg = "Unauthorized. Request parameter syntax is incompatible with policy.";
      throw new YADASecurityException(msg);
    }

    // process the relevant specs
    for (SecurityPolicyRecord secRec: prunedSpec) // policy code (E,C), policy
    // type (white,black), target
    // (qname),
    // A11nqname
    {
      String a11nQname  = secRec.getA11nQname();
      String policyType = secRec.getType();

      // policy has params and req has compatible params
      if (policyHasParams && !reqHasJSONParams)
      {
        // split on space to process each polCol separately
        String[]      polCols = policyIndices.split("\\s");
        StringBuilder polVals = new StringBuilder();
        if (reqHasParams)
        {
          for (int i = 0; i < polCols.length; i++)
          {

            // handle as params
            // 1. get params from query
            List vals = getYADAQuery().getVals(0);
            try
            {
              index = Integer.parseInt(polCols[i]);
            }
            catch (NumberFormatException e)
            {
              injectedIndex = polCols[i];
            }

            // 2. pass user column
            if (polVals.length() > 0)
              polVals.append(",");
            if (injectedIndex.equals("") && index > -1)
            {
              if (index >= vals.size())
                // insert token by default if current polCol index exceeds 
                // length of request's param list for this query
                polVals.append((String) getToken());
              else
                polVals.append(vals.get(index));
            }
            else
            {
              Pattern rxInjection = Pattern.compile(YADASecuritySpec.RX_IDX_INJECTION);
              Matcher m1          = rxInjection.matcher(injectedIndex);
              if (m1.matches() && m1.groupCount() == 3) // injection
              {
                // parse regex: this is where the method value is injected
                // String colIdx = m1.group(2);
                String colval = m1.group(2);
                String arg    = m1.group(3);

                // find and execute injected method
                String method = colval.substring(0, colval.indexOf('('));
//                String arg    = colval.substring(colval.indexOf('(') + 1, colval.indexOf(')'));
                Object val    = null;
                try
                {
                  if (arg.equals(""))
                    val = getClass().getMethod(method).invoke(this, new Object[] {});
                  else
                    val = getClass().getMethod(method, new Class[] { java.lang.String.class }).invoke(this,
                        new Object[] { arg });
                }
                catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                    | InvocationTargetException e)
                {
                  String msg = "Unathorized. Injected method invocation failed.";
                  throw new YADASecurityException(msg, e);
                }

                // add/replace item in dataRow
                polVals.append(val);
              }
            }
            index         = -1;
            injectedIndex = "";
          }

          // 3. execute the security query
          try
          {
            result = YADAUtils.executeYADAGet(new String[] { a11nQname }, new String[] { polVals.toString() });
          }
          catch (YADAException e)
          {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
        }
        else
        {
          for (int i = 0; i < polCols.length; i++)
          {
            injectedIndex = polCols[i];
            
            if(polVals.length() > 0)
            {
              polVals.append(",");
            }
            
            // insert token by default since here, polCol index exceeds 
            // zero-length of request's param list for this query
//            polVals.append((String) getToken());
            Pattern rxInjection = Pattern.compile(YADASecuritySpec.RX_IDX_INJECTION);
            Matcher m1          = rxInjection.matcher(injectedIndex);
            if (m1.matches() && m1.groupCount() == 3) // injection
            {
              // parse regex: this is where the method value is injected
              // String colIdx = m1.group(2);
              String colval = m1.group(2);
              String arg    = m1.group(3);

              // find and execute injected method
              String method = colval.substring(0, colval.indexOf('('));
//              String arg    = colval.substring(colval.indexOf('(') + 1, colval.indexOf(')'));
              Object val    = null;
              try
              {
                if (arg.equals(""))
                  val = getClass().getMethod(method).invoke(this, new Object[] {});
                else
                  val = getClass().getMethod(method, new Class[] { java.lang.String.class }).invoke(this,
                      new Object[] { arg });
              }
              catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                  | InvocationTargetException e)
              {
                String msg = "Unathorized. Injected method invocation failed.";
                throw new YADASecurityException(msg, e);
              }

              // add/replace item in dataRow
              polVals.append(val);
            }            
            else
            {
              // default to token, although this is probably never going to be called
              // and will fail if a real auth token is in use
              polVals.append((String) getToken());
            }
          }
          try
          {
            result = YADAUtils.executeYADAGet(new String[] { a11nQname }, new String[] { polVals.toString() });
          }
          catch (YADAException e)
          {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
        }
      }

      // policy has JSONParams and req has compatible JSONParams
      else if (policyHasJSONParams && reqHasJSONParams)
      {
        LOG.debug("Could not parse protector column value into integer, assuming it's a String");

        // handle as JSONParams
        // 1. get JSONParams from query (params)
        LinkedHashMap dataRow = getYADAQuery().getDataRow(0);

        // 2. add user column if necessary
        String[] polCols = policyColumns.split("\\s");
        for (String colspec: polCols)
        {

          // dataRow can look like, e.g.: {COL1:val1,COL2:val2}
          // polCols can look like, e.g.: COL2 APP:getValue(TARGET)
          Pattern rxInjection = Pattern.compile(YADASecuritySpec.RX_COL_INJECTION);
          Matcher m1          = rxInjection.matcher(colspec);
          if (m1.matches() && m1.groupCount() == 3) // injection
          {

            // parse regex: this is where the method value is injected
            String colname = m1.group(1);
            String colval  = m1.group(2);
            String arg     = m1.group(3);

            // find and execute injected method
            String method = colval.substring(0, colval.indexOf('('));
            Object val    = null;
            try
            {
              if (arg.equals(""))
                val = getClass().getMethod(method).invoke(this, new Object[] {});
              else
                val = getClass().getMethod(method, new Class[] { java.lang.String.class }).invoke(this,
                    new Object[] { arg });
            }
            catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e)
            {
              String msg = "Unathorized. Injected method invocation failed.";
              throw new YADASecurityException(msg, e);
            }

            // add/replace item in dataRow
            dataRow.put(colname, new String[] { (String) val });
          }
          else
          {
            if (!dataRow.containsKey(colspec)) // no injection AND no parameter
            {
              String msg = "Unathorized. Injected method invocation failed.";
              throw new YADASecurityException(msg);
            }
          }
        }

        // 3. execute the security query
        JSONParamsEntry jpe = new JSONParamsEntry();

        // dataRow now contains injected values () or passed values
        // if values were injected, they've overwritten the passed in version
        jpe.addData(dataRow);
        JSONParams jp = new JSONParams(a11nQname, jpe);
        try
        {
          result = YADAUtils.executeYADAGetWithJSONParamsNoStats(jp);
        }
        catch (YADAException e)
        {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }
      else
      {

        // no parameters to pass to execution.policy query
        try
        {
          result = YADAUtils.executeYADAGet(new String[] { a11nQname }, new String[0]);
        }
        catch (YADAException e)
        {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }

      // parse result
      int count = new JSONObject(result).getJSONObject("RESULTSET").getInt("records");

      // Reject if necessary
      if ((isWhitelist(policyType) && count == 0) || (isBlacklist(policyType) && count > 0))
        throw new YADASecurityException("Unauthorized.");
    }
    this.clearSecurityPolicy();
  }

  /**
   * Modifies the original query by appending a dynamic predicate
   * 

* Recall the {@link Service}{@code .engagePreprocess} method will recall * {@link QueryManager}{@code .endowQuery()} to reconform the code after this * {@link Preprocess} disengages. * * * @throws YADASecurityException when token retrieval fails */ @Override public void applyContentPolicy() throws YADASecurityException { String SPACE = " "; StringBuilder contentPolicy = new StringBuilder(); Pattern rxInjection = Pattern.compile(YADASecuritySpec.RX_COL_INJECTION); String rawPolicy; Matcher m1; int start = 0; if(this.getSecuritySpec() != null) { rawPolicy = this.getSecuritySpec().getContentPolicy().get(PREDICATE); } else { rawPolicy = getArgumentValue(CONTENT_POLICY_PREDICATE); } // TODO make it impossible to reset args and preargs dynamically if pl class // implements SecurityPolicy // this will close an attack vector // field = getToken // field = getCookie(string) // field = getHeader(string) // field = getUser() // field = getRandom(string) m1 = rxInjection.matcher(rawPolicy); if (!m1.find()) { String msg = "Unathorized. Injected method invocation failed."; throw new YADASecurityException(msg); } m1.reset(); while (m1.find()) { int rxStart = m1.start(); int rxEnd = m1.end(); contentPolicy.append(rawPolicy.substring(start, rxStart)); String frag = rawPolicy.substring(rxStart, rxEnd); String method = frag.substring(0, frag.indexOf('(')); String arg = frag.substring(frag.indexOf('(') + 1, frag.indexOf(')')); Object val = null; try { if (arg.equals("")) val = getClass().getMethod(method).invoke(this, new Object[] {}); else val = getClass().getMethod(method, new Class[] { java.lang.String.class }).invoke(this, new Object[] { arg }); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { String msg = "Unathorized. Injected method invocation failed."; throw new YADASecurityException(msg, e); } contentPolicy.append((String) val + SPACE); start = rxEnd; } Expression parsedContentPolicy; try { parsedContentPolicy = CCJSqlParserUtil.parseCondExpression(contentPolicy.toString()); } catch (JSQLParserException e) { String msg = "Unauthorized. Content policy is not valid."; throw new YADASecurityException(msg, e); } PlainSelect sql = (PlainSelect) ((Select) getYADAQuery().getStatement()).getSelectBody(); Expression where = sql.getWhere(); if (where != null) { AndExpression and = new AndExpression(where, parsedContentPolicy); sql.setWhere(and); } else { sql.setWhere(parsedContentPolicy); } try { CCJSqlParserManager parserManager = new CCJSqlParserManager(); sql = (PlainSelect) ((Select) parserManager.parse(new StringReader(sql.toString()))).getSelectBody(); } catch (JSQLParserException e) { String msg = "Unauthorized. Content policy is not valid."; throw new YADASecurityException(msg, e); } getYADAQuery().setYADACode(sql.toString()); this.clearSecurityPolicy(); } /** * Utility function for content policy * * @return the auth token wrapped in single quotes * @throws YADASecurityException when the token can't retrieved */ public String getQToken() throws YADASecurityException { String quote = "'"; return quote + getToken() + quote; } /* * @since 8.7.6 */ @Override public String getLoggedUser() throws YADASecurityException { String user = ""; try { user = new JSONObject(obtainIdentity().toString()).getString(Authorization.YADA_IDENTITY_SUB); } catch (JSONException e) { String msg = "There was a problem obtaining the user identity."; throw new YADASecurityException(msg, e); } return user; } /** * Utility function for content policy * * @return the auth token wrapped in single quotes * @throws YADASecurityException if the logged user value cannot be obtained * @since 8.1.0 */ public String getQLoggedUser() throws YADASecurityException { String user = ""; try { user = getLoggedUser(); } catch (YADASecurityException e) { String msg = "There was a problem obtaining the user identity."; throw new YADASecurityException(msg, e); } String quote = "'"; return quote + user + quote; } /** * Utility function for content policy * * @param cookie the desired HTTP request cookie * @return the value of {@code cookie} wrapped in single quotes */ public String getQCookie(String cookie) { String quote = "'"; String val = super.getCookie(cookie); return quote + val + quote; } /** * Utility function for content policy * * @param header the desired HTTP request header * @return the value of {@code header} wrapped in single quotes */ public String getQHeader(String header) { String quote = "'"; String val = super.getHeader(header); return quote + val + quote; } /** * Sets the local {@link TokenValidator} to {@code this} */ @Override public void setTokenValidator() throws YADASecurityException { setTokenValidator(this); } /** * @return the {@link #allowList} */ public ArrayList getAllowList() { return this.allowList; } /** * @param grant the privilege to allow */ public void addAllowListEntry(String grant) { this.allowList.add(grant); } /** * @param grant the privilege to deny */ public void removeAllowListEntry(String grant) { this.allowList.remove(grant); } /** * @return {@link #denyList} */ public ArrayList getDenyList() { return this.denyList; } /** * @param grant the privilege to deny */ public void addDenyListEntry(String grant) { this.denyList.add(grant); } /** * @return the identity TODO: To Authorization */ public Object getIdentity() { return this.identity; } /** * @param identity the identity to set */ public void setIdentity(Object identity) { this.identity = identity; } /** * @return the locks */ public JSONObject getLocks() { return this.locks; } /** * @param locks the locks to set */ public void setLocks(JSONObject locks) { this.locks = locks; } /** * @return the grant */ public Object getGrant() { return this.grant; } /** * @param grant the grant to set */ public void setGrant(Object grant) { this.grant = grant; } /** * @return the ships */ public ArrayList getShips() { return this.ships; } /** * @param ships the ships to set */ public void setShips(ArrayList ships) { this.ships = ships; } /** * @return {@code true} if {@link AbstractPreprocessor#getToken} returns a non-null, non-empty {@link String} * @since 8.7.6 */ public boolean hasToken() { if (null != this.getToken() && !"".equals(this.getToken())) { return true; } return false; } /** * @return {@code true} if the {@link #syncToken} variable is a non-null, non-empty {@link String} * @since 8.7.6 */ public boolean hasSyncToken() { if (null != this.getSyncToken() && !"".equals(this.getSyncToken())) { return true; } return false; } /** * @return {@code true} if {@link #locks} has at least 1 entry, otherwise * {@code false} * @since 8.7.6 */ public boolean hasLocks() { if (getLocks().length() > 0) { return true; } return false; } /** * @return {@code true} if {@link #grant} has at least 1 entry, otherwise * {@code false} * @since 8.7.6 */ public boolean hasGrants() { if (((JSONArray) getGrant()).length() > 0) { return true; } return false; } /** * @return {@code true} if {@link #identity} is set, otherwise {@code false} * @since 8.7.6 */ public boolean hasIdentity() { if (null != getIdentity() && !"".equals(getIdentity())) { return true; } return false; } /** * @return {@code true} if {@link #allowList} has at least 1 entry, otherwise * {@code false} * @since 8.7.6 */ public boolean hasAllowList() { if (getAllowList().size() > 0) { return true; } return false; } /** * @return the syncToken */ public String getSyncToken() { return syncToken; } /** * @param syncToken the syncToken to set */ public void setSyncToken(String syncToken) { this.syncToken = syncToken; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy