/*
* (C) Copyright IBM Corp. 2009
*
* LICENSE: Eclipse Public License v1.0
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.ibm.gaiandb.plugins.wpml;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.derby.iapi.types.DataValueDescriptor;
import com.ibm.gaiandb.Logger;
import com.ibm.gaiandb.plugins.wpml.schema.AccessLogger;
import com.ibm.gaiandb.plugins.wpml.schema.DataSource;
import com.ibm.gaiandb.plugins.wpml.schema.IRow;
import com.ibm.gaiandb.plugins.wpml.schema.QueryContext;
import com.ibm.gaiandb.plugins.wpml.schema.Row;
import com.ibm.gaiandb.policyframework.SQLResultFilter;
import com.ibm.watson.pml.PMLException;
import com.ibm.watson.pml.pep.IObjectPEP;
import com.ibm.watson.pml.pep.StrictTwoStagePEP;
/**
* Policy-enabled filter of relational schema elements.
* Provides an implementation of the filter based on the interface definition
* made available by GaianDB for plug-in functionality.
* <P>
* Instantiates a Policy Decision Point (PDP) and a Policy Enforcement Point (PEP)
* for the policy evaluation and connects to the repository to access the policies
* that govern the filtering of the data. For now, default file for policies
* can be found in C:\PFGpolicies.spl. If no such file exists, then the PDP
* attempts to retrieve policies from a jdbc repository, using connection details
* found in the wpml.properties file.
* <P>
* Policy evaluation is performed in 2 steps: first, evaluation of authorization
* policies is performed, followed by evaluation of the obligation policies.
* <P>
* Notes/todo list:
* <ol>
* <li> Proper logging using the PMLLogger facility (com.ibm.watson.pml.util package)
* <li> Update (03/05/2009): the nextQueriedDataSource() method will
* be deprecated and possibly replaced with verifyDataSources() or something similar.
* Also, the way the QueryContext is passed to policies for evaluation needs to be changed
* accordingly
* <li> Get configuration for connecting to policy repository a properties file.
* </ol>
*
* @author pzerfos@us.ibm.com, drvyvyan@uk.ibm.com
*
*/
public class PolicyEnabledFilter implements SQLResultFilter {
// Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice.
public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2009";
// private final static String WPML_PFG_POLICIES_FILE = "C:\\PFGpolicies.spl";
private static final Logger logger = new Logger( "PolicyEnabledFilter", 30 );
private List<String> NON_RESTRICTED_LTS = Arrays.asList("LT1", "T1", "SUBQUERY", "GDB_LTNULL",
"POLICY", "POLICY_SET", "POLICY_SET_MEMBERSHIP", "PEP", "PEP_ATTRIBUTE", "POLICY_ATTRIBUTE",
"POLICY_SET_ATTRIBUTE", "INSTANCE", "PEP_HAS_INSTANCE", "TIMESTAMPS");
private boolean isLogicalTableRestricted = true;
/**
* metadata on the logical table of the query
*/
private ResultSetMetaData logicalTableRSMD = null;
/**
* Number of columns in a row
*/
private int logicalTableColumnCount = -1;
/**
* Array of flags to identify the columns of a row that have been
*/
private int[] queriedColumns = null;
private int rowCount = -1;
/**
* Object of {@link com.ibm.gaiandb.plugins.wpml.schema#QueryContext} anchor class to be used for policy evaluation
* All query context information needed in policy evaluation is held in this bean-like structure.
*/
protected final QueryContext queryContext;
/**
* Object of {@link com.ibm.gaiandb.plugins.wpml.schema#IRow} anchor class to be used for policy evaluation
*/
private IRow filterRowObject = null;
private static IObjectPEP pep = null;
private static Object pepLock = new Object();
private static AtomicBoolean isInitialised = new AtomicBoolean(false);
/**
* Allocate the object pep. Allow subclasses to override.
* @return
* @throws PMLException
*/
protected static IObjectPEP allocateObjectPEP() throws PMLException {
return new StrictTwoStagePEP("pfg-pep", true);
}
private static void refreshPolicyCache() {
synchronized( pepLock ) {
try {
if ( null == pep ) {
pep = allocateObjectPEP();
pep.addAlias("queryContext", QueryContext.class);
pep.addAlias("accessLogger", AccessLogger.class);
pep.addAlias("dataSource", DataSource.class);
} else {
pep.endEvaluations();
pep.clear();
}
} catch (PMLException e) {
System.err.println("PFG: refreshPolicyCache: Error in endEvaluations/beginEvaluations: " + e);
e.printStackTrace();
}
}
}
private static boolean evaluatePEP( ArrayList<Object> oa ) throws PMLException {
synchronized( pepLock ) {
pep.beginEvaluations();
return pep.evaluate(oa.toArray());
}
}
// In future for a more extensible base policy class, pass in the query context type (i.e. the object model)
public PolicyEnabledFilter() {
queryContext = new QueryContext();
if ( isInitialised.compareAndSet(false, true) ) {
// synchronized( pepLock ) { if ( null != pep ) return; }
refreshPolicyCache();
new Thread( new Runnable() {
public void run() {
while( true ) { try { Thread.sleep(5000); } catch (InterruptedException e) {} refreshPolicyCache(); }
}
}, "PEP refresher for " + this.getClass().getSimpleName()).start();
}
}
public boolean setForwardingNode( String forwardingNode ) {
if ( !isLogicalTableRestricted ) return true;
if ( null != forwardingNode ) queryContext.setForwardingNode(forwardingNode);
return true;
}
public boolean setUserCredentials(String credentialsStringBlock) {
if ( null == credentialsStringBlock ) {
logger.logInfo("Unable to retrieve/authenticate user: credentials block is null");
return false;
}
String user = credentialsStringBlock;
return setAuthenticatedUserCredentials( new String[] { user, "UK", "UK_RESTRICTED" } ); // no actual authentication - simplistic user fields passed straight through
}
private boolean setAuthenticatedUserCredentials( String[] userFields ) { // to be called from setUserCredentials() once byte[] is decrypted
if ( !isLogicalTableRestricted ) return true;
// System.out.println("SET AUTHENTICATED USR CREDS: " + (null==userFields ? null : Arrays.asList(userFields)) );
String user = "", affiliation = "", clearance = "";
if ( null != userFields ) {
user = userFields[0];
affiliation = userFields[1];
clearance = userFields[2];
}
// User info for QueryContext is set here
queryContext.setRequestor(user);
queryContext.setAffiliation(affiliation);
queryContext.setSecurityClearance(clearance);
if ( null == pep ) return true;
try {
// if ( null == pep ) {
// pep = allocateObjectPEP();
// pep.addAlias("queryContext", QueryContext.class);
// pep.addAlias("accessLogger", AccessLogger.class);
// pep.addAlias("dataSource", DataSource.class);
// }
//
// pep.beginEvaluations();
ArrayList<Object> oa = createObjectArray();
oa.add(new AccessLogger());
/*
* 2-step policy evaluation: first do the authorizations
* then apply the obligations
*/
// System.out.println("Evaluation of setAuthenticatedUserCredentials " + Arrays.asList(oa));
return evaluatePEP(oa);
} catch (PMLException e) {
System.err.println("PFG: setLogicalTable: policy evaluation error");
e.printStackTrace();
}
return true;
}
/* (non-Javadoc)
* @see com.ibm.watson.pml.pfg.plugin.RowFilter#setLogicalTable(java.lang.String, java.sql.ResultSetMetaData)
*/
public boolean setLogicalTable(String logicalTableName, ResultSetMetaData logicalTableResultSetMetaData) {
// System.out.println("Access to logical table: " + logicalTableName);
if ( NON_RESTRICTED_LTS.contains(logicalTableName.toUpperCase()) ) {
isLogicalTableRestricted = false;
return true;
}
// System.out.println("SET LOGICAL TABLE: " + logicalTableName);
// 1. Instantiate a PEP and allocate SingletonPolicyEvaluator.instance() for PDP
// try {
// pep = allocateObjectPEP();
// pep.addAlias("queryContext", QueryContext.class);
// pep.addAlias("accessLogger", AccessLogger.class);
// pep.addAlias("dataSource", DataSource.class);
// } catch (PMLException e) {
// e.printStackTrace();
// System.err.println("ERROR: PFG: error in loading policies from file: " + e);
// }
// 2. Obtain the schema to be filtered by policies
this.logicalTableRSMD = logicalTableResultSetMetaData;
try {
this.logicalTableColumnCount = logicalTableRSMD.getColumnCount();
} catch (SQLException sqle) {
System.err.println("ERROR: PFG: could not retrieve logical column count: " + sqle);
sqle.printStackTrace();
}
// 3. Set a default QueryContext
// setQueryContext("default", "default");
rowCount = 0;
if ( null != logicalTableName ) queryContext.setLogicalTable(logicalTableName);
// try {
// ArrayList<Object> oa = createObjectArray();
//
// /*
// * 2-step policy evaluation: first do the authorizations
// * then apply the obligations
// */
//// System.out.println("Evaluation of nextQueriedDataSource " + Arrays.asList(oa) + ": " + rc);
// return evaluatePEP(oa);
// } catch (PMLException e) {
// System.err.println("PFG: setLogicalTable: policy evaluation error");
// e.printStackTrace();
// }
return true;
}
/**
* Create an object array that will store the objects to be evaluated.
* If the QueryContext has been set, then add it as well.
*
* @return an arraylist for the objects that will be evaluated. It potentially
* includes the {@link com.ibm.gaiandb.plugins.wpml.schema.QueryContext} object
*/
private ArrayList<Object> createObjectArray() {
ArrayList<Object> objectArray = new ArrayList<Object>();
if (queryContext != null) {
// System.out.println("Adding Query Context!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!: " + queryContext);
objectArray.add(queryContext);
}
return objectArray;
}
// /**
// * Add the individual column objects to the array of objects that will be
// * used for evaluation in policies. Besides the composite Row object,
// * policies can be written directly for Columns.
// *
// * @param oa the array list with the objects that will be used for policy evaluation
// *
// * @param filterRowObj the {@link #Row} object with the queried columns
// *
// * @return an array list that includes the original objects for policy evaluation, as well
// * as new ones for the queried columns (one object for each column)
// */
// private ArrayList<Object> addColumnsObjectArray(ArrayList<Object> oa, Row filterRowObj) {
//
// if (filterRowObj == null)
// return oa;
//
// ArrayList<IColumn> ac = filterRowObj.getColumns();
// Iterator<IColumn> iter = ac.iterator();
// while (iter.hasNext()) {
// IColumn col = iter.next();
// if (col != null && oa != null)
// oa.add(col);
// }
//
// return oa;
// }
/* (non-Javadoc)
* @see com.ibm.watson.pml.pfg.plugin.RowFilter#setQueriedColumns(int[])
*/
public boolean setQueriedColumns(int[] queriedColumns) {
if ( !isLogicalTableRestricted ) return true;
this.queriedColumns = queriedColumns;
/*
* Construct the Row object to be used for policy evaluation.
* The actual DataValueDescriptors with the data of the fields of the
* queried columns are populated upon every call of the filterRow()
*/
filterRowObject = new Row(logicalTableRSMD, this.queriedColumns);
return true;
}
/* (non-Javadoc)
* @see com.ibm.watson.pml.pfg.plugin.RowFilter#nextQueriedDataSource(java.lang.String, int[], java.lang.String)
*/
public int nextQueriedDataSource(String dataSource, int[] columnMappings) {
if ( !isLogicalTableRestricted ) return -1;
// System.out.println("authenticated user: " + user + ", datasource: " + dataSource);
// if ( null != user && user.equals("ibmuser1") && -1 != dataSource.indexOf("datafile") ) {
// isLogicalTableRestricted = false; // no more restrictions after this one
// return 3; // simple test case, return 3 to signify only 3 rows may be extracted from this source
// }
if ( null == pep ) return -1; // allow all rows to be extracted
// return -1;
/*
* Perform a policy-based evaluation on whether the data source should
* be queried or not. Default is true. If evaluation fails, then do not
* query this data source.
*/
try {
ArrayList<Object> oa = createObjectArray();
oa.add(new DataSource(dataSource));
// oa.add(new AccessLogger());
/*
* 2-step policy evaluation: first do the authorizations
* then apply the obligations
*/
// System.out.println("Evaluation of nextQueriedDataSource " + Arrays.asList(oa));
return evaluatePEP(oa) ? -1 : 0;
} catch (PMLException e) {
System.err.println("PFG: nextQueriedDataSource: policy evaluation error");
e.printStackTrace();
}
return 0; // don't allow any rows to be extracted
}
/* (non-Javadoc)
* @see com.ibm.watson.pml.pfg.plugin.RowFilter#filterRow(org.apache.derby.iapi.types.DataValueDescriptor[])
*/
public boolean filterRow(DataValueDescriptor[] row) {
if ( !isLogicalTableRestricted ) return true;
/*
* Sanity: check for the number of columns in the record
*/
if (row.length < logicalTableColumnCount) {
System.err.println("ERROR: PFG: invalid fetched row: expecting " +
logicalTableColumnCount + " columns, instead of " + row.length);
return false;
}
rowCount++;
/*
* Set the data of the queried columns in the row that will
* be evaluated
*/
if (filterRowObject != null) {
((Row)filterRowObject).setRowData(row);
((Row)filterRowObject).setRowIndex(rowCount);
}
if ( null == pep ) return true;
/*
* Perform a policy-based filtering on the record.
*/
boolean decision = false;
try {
ArrayList<Object> oa = createObjectArray();
if (filterRowObject != null)
oa.add(filterRowObject);
// System.out.println("About to apply row filter for individual row using ObjectArray: " +
// Arrays.asList( oa.toArray(new Object[0])) );
/*
* 2-step policy evaluation: first do the authorizations
* then apply the obligations
*/
decision = evaluatePEP(oa);
// System.out.println("Evaluation for row filter " + Arrays.asList(oa) + ": " + decision);
} catch (PMLException e) {
System.err.println("PFG: policy evaluation error" + e.getMessage());
e.printStackTrace();
}
return decision;
}
/* (non-Javadoc)
* @see com.ibm.watson.pml.pfg.plugin.RowFilter#close()
*/
public void close() {
// try {
// if (null != pep) {
// pep.endEvaluations();
// pep = null;
// }
// } catch (PMLException e) {
// System.err.println("PEP call to endEvaluations() failed (ignored), cause: " + e);
// }
rowCount = 0;
}
}