/** * '$RCSfile$' * Purpose: A class that handles checking permssision for a document and subtree in a document * * Copyright: 2000 Regents of the University of California and the * National Center for Ecological Analysis and Synthesis * Authors: Chad Berkley * * '$Author: leinfelder $' * '$Date: 2016-05-02 16:50:30 +0000 (Mon, 02 May 2016) $' * '$Revision: 9705 $' * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.ucsb.nceas.metacat; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import org.apache.log4j.Logger; import org.dataone.service.types.v1.Identifier; import org.dataone.service.types.v1.Permission; import org.dataone.service.types.v1.Session; import org.dataone.service.types.v1.Subject; import org.dataone.service.types.v2.SystemMetadata; import edu.ucsb.nceas.metacat.accesscontrol.AccessControlList; import edu.ucsb.nceas.metacat.database.DBConnection; import edu.ucsb.nceas.metacat.database.DBConnectionPool; import edu.ucsb.nceas.metacat.dataone.D1NodeService; import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService; import edu.ucsb.nceas.metacat.properties.PropertyService; import edu.ucsb.nceas.metacat.service.SessionService; import edu.ucsb.nceas.metacat.shared.MetacatUtilException; import edu.ucsb.nceas.metacat.util.AuthUtil; import edu.ucsb.nceas.metacat.util.DocumentUtil; import edu.ucsb.nceas.metacat.util.MetacatUtil; import edu.ucsb.nceas.metacat.util.SessionData; import edu.ucsb.nceas.utilities.PropertyNotFoundException; import edu.ucsb.nceas.utilities.access.AccessControlInterface; public class PermissionController { // private String docId = null; // private int rev = -1; private String guid = null; private boolean hasSubTreeAccessControl = false; // flag if has a subtree // access for this docid private Vector subTreeList = new Vector(); private static final long TOPLEVELSTARTNODEID = 0; //if start node is 0, means it is top //level document private static Logger logMetacat = Logger.getLogger(PermissionController.class); /** * Constructor for PermissionController * * @param myDocid the docid need to access */ public PermissionController(String myDocid) throws McdbException { // find the guid if we can String docId = null; int rev = -1; // get the parts docId = DocumentUtil.getSmartDocId(myDocid); rev = DocumentUtil.getRevisionFromAccessionNumber(myDocid); // this is what we really want guid = IdentifierManager.getInstance().getGUID(docId, rev); } /** * Return if a document has subtree access control */ public boolean hasSubTreeAccessControl() { return hasSubTreeAccessControl; } public boolean hasPermission(String sessionId, String myPermission) throws SQLException { SessionData sessionData = null; sessionData = SessionService.getInstance().getRegisteredSession(sessionId); if (sessionData == null) { return false; } return hasPermission(sessionData.getUserName(), sessionData.getGroupNames(), myPermission); } /** * Check from db connection if at least one of the list of @principals * Administrators are allowed all permission * @param user the user name * @param groups the groups which the use is in * @param myPermission permission type to check for */ public boolean hasPermission(String user, String[]groups, String myPermission) throws SQLException //, Exception { boolean hasPermission=false; String [] userPackage=null; int permission = AccessControlList.intValue(myPermission); //for the command line invocation and replication if ((user==null) && (groups==null || groups.length==0)) { return true; } // for administrators //see http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4728 try { if (AuthUtil.isAdministrator(user, groups)) { return true; } } catch (MetacatUtilException e) { // not much we can do here, except treat them as normal logMetacat.warn("Error checking for administrator: " + e.getMessage(), e); } // for DataONE rightsHolder permission boolean isOwner = false; try { Session userSession = new Session(); Subject subject = new Subject(); subject.setValue(user); userSession.setSubject(subject); Identifier pid = new Identifier(); pid.setValue(guid); //isOwner = D1NodeService.userHasPermission(userSession, pid, Permission.CHANGE_PERMISSION); SystemMetadata sysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid); isOwner = (sysMeta.getRightsHolder().equals(subject)); } catch (Exception e) { logMetacat.warn("Error checking for DataONE permissions: " + e.getMessage(), e); isOwner = false; } if (isOwner) { return true; } logMetacat.debug("Checking permission on " + this.guid + " for user: " + user + " and groups: " + Arrays.toString(groups)); //create a userpackage including user, public and group member userPackage=createUsersPackage(user, groups); //if the requested document is access documents and requested permission //is "write", the user should have "all" right if (isAccessDocument() && (permission == AccessControlInterface.WRITE)) { hasPermission = hasPermission(userPackage, 7);// 7 is all permission }//if else //in other situation, just check the request permission { // Check for @permission on @docid for @user and/or @groups hasPermission = hasPermission(userPackage, permission); }//else return hasPermission; } /** * Check from db connection if the users in String array @principals has * @permission on @docid* * @param principals, names in userPakcage need to check for @permission * @param docid, document identifier to check on * @param permission, permission (write or all...) to check for */ private boolean hasPermission(String [] principals, int permission) throws SQLException { long startId = TOPLEVELSTARTNODEID;// this is for top level, so startid is 0 try { //first, if there is a docid owner in user package, return true //because doc owner has all permssion if (containDocumentOwner(principals)) { return true; } //If there is no owner in user package, checking the table //check perm_order if (isAllowFirst(principals, startId)) { if (hasExplicitDenyRule(principals, permission, startId)) { //if it is allowfirst and has deny rule(either explicit ) //deny access return false; }//if else if ( hasAllowRule(principals, permission, startId)) { //if it is allowfirst and hasn't deny rule and has allow rule //allow access return true; }//else if else { //other situation deny access return false; }//else }//if isAllowFirst else //denyFirst { if (hasAllowRule(principals, permission, startId)) { //if it is denyFirst and has allow rule, allow access return true; } else { //if it is denyfirst but no allow rule, deny access return false; } }//else denyfirst }//try catch (Exception e) { logMetacat.warn("PermissionController.hasPermission - There is a exception in hasPermission method: " +e.getMessage()); } return false; }//hasPermission /** * Method to check if a person has permission to a inline data file * @param user String * @param groups String[] * @param myPermission String * @param inlineDataId String * @throws McdbException * @return boolean */ private boolean hasPermissionForInlineData(String user, String[] groups, String myPermission, String inlineDataId) throws McdbException { // this method can call the public method - hasPermission(...) // the only difference is about the ownership, you couldn't find the owner // from inlinedataId directly. You should get it from eml document itself String []userPackage = createUsersPackage(user, groups); try { if (containDocumentOwner(userPackage)) { return true; } else { // is a funky inline data id with the extra part, so we set it manually PermissionController controller = new PermissionController(guid); controller.guid = inlineDataId; return controller.hasPermission(user, groups, myPermission); } } catch (SQLException e) { throw new McdbException(e.getMessage()); } } /** * The method to determine of a node can be access by a user just by subtree * access control */ public boolean hasPermissionForSubTreeNode(String user, String[] groups, String myPermission, long nodeId) throws McdbException { boolean flag = true; // Get unaccessble subtree for this user Hashtable unaccessableSubTree = hasUnaccessableSubTree(user, groups, myPermission); Enumeration en = unaccessableSubTree.elements(); while (en.hasMoreElements()) { SubTree tree = (SubTree)en.nextElement(); long start = tree.getStartNodeId(); long stop = tree.getEndNodeId(); // nodeid in unaccessablesubtree, return false if ( nodeId >= start && nodeId <= stop) { flag = false; break; } } return flag; } /** * This method will return a hasTable of subtree which user doesn't has the * permssion to access * @param user the user name * @param groups the groups which the use is in * @param myPermission permission type to check for */ public Hashtable hasUnaccessableSubTree(String user, String[] groups, String myPermission) throws McdbException { Hashtable resultUnaccessableSubTree = new Hashtable(); String [] principals=null; int permission =AccessControlList.intValue(myPermission); //for the commnad line invocation return null(no unaccessable subtree) if ((user==null) && (groups==null || groups.length==0)) { return resultUnaccessableSubTree; } //create a userpackage including user, public and group member principals=createUsersPackage(user, groups); //for the document owner return null(no unaccessable subtree) try { if (containDocumentOwner(principals)) { return resultUnaccessableSubTree; } } catch (SQLException ee) { throw new McdbException(ee); } // go through every subtree which has access control for (int i = 0; i< subTreeList.size(); i++) { SubTree tree = (SubTree)subTreeList.elementAt(i); long startId = tree.getStartNodeId(); try { if (isAllowFirst(principals, startId)) { if (hasExplicitDenyRule(principals, permission, startId )) { //if it is allowfirst and has deny rule // put the subtree into unaccessable vector if (!resultUnaccessableSubTree.containsKey(new Long(startId))) { resultUnaccessableSubTree.put(new Long(startId), tree); } }//if else if ( hasAllowRule(principals, permission, startId)) { //if it is allowfirst and hasn't deny rule and has allow rule //allow access do nothing }//else if else { //other situation deny access if (!resultUnaccessableSubTree.containsKey(new Long(startId))) { resultUnaccessableSubTree.put(new Long(startId), tree); } }//else }//if isAllowFirst else //denyFirst { if (hasAllowRule(principals, permission,startId)) { //if it is denyFirst and has allow rule, allow access, do nothing } else { //if it is denyfirst but no allow rule, deny access // add into vector if (!resultUnaccessableSubTree.containsKey(new Long(startId))) { resultUnaccessableSubTree.put(new Long(startId), tree); } } }//else denyfirst }//try catch( Exception e) { logMetacat.error("PermissionController.hasUnaccessableSubTree - error in PermissionControl.has" + "UnaccessableSubTree "+e.getMessage()); throw new McdbException(e); } }//for // merge the subtree if a subtree is another subtree'subtree resultUnaccessableSubTree = mergeEquivalentSubtree(resultUnaccessableSubTree); return resultUnaccessableSubTree; }//hasUnaccessableSubtree /* * A method to merge nested subtree into bigger one. For example subtree b * is a subtree of subtree a. And user doesn't have read permission for both * so we only use subtree a is enough. */ private Hashtable mergeEquivalentSubtree(Hashtable unAccessSubTree) { Hashtable newSubTreeHash = new Hashtable(); boolean needDelete = false; // check the parameters if (unAccessSubTree == null || unAccessSubTree.isEmpty()) { return newSubTreeHash; } else { // look every subtree start point and stop point, to see if it is embedded // in another one. If embedded, they are equavelent and we can use bigger // one to replace smaller one Enumeration en = unAccessSubTree.elements(); while (en.hasMoreElements()) { SubTree tree = (SubTree)en.nextElement(); String treeId = tree.getSubTreeId(); long startId = tree.getStartNodeId(); long endId = tree.getEndNodeId(); Enumeration enu = unAccessSubTree.elements(); while (enu.hasMoreElements()) { SubTree subTree = (SubTree)enu.nextElement(); String subTreeId= subTree.getSubTreeId(); long subTreeStartId = subTree.getStartNodeId(); long subTreeEndId = subTree.getEndNodeId(); //compare and if the first subtree is a subtree of the second // one, set neeDelete true if (startId > subTreeStartId && endId < subTreeEndId) { needDelete = true; logMetacat.info("PermissionController.mergeEquivalentSubtree - the subtree: "+ treeId + " need to be get rid of from unaccessable"+ " subtree list becuase it is a subtree of"+ " another subtree in the list"); break; }//if }//while // if not need to delete, put the subtree into hash if (!needDelete) { newSubTreeHash.put(new Long(startId), tree); } //reset needDelete needDelete = false; }//while return newSubTreeHash; }//else } /** * Check if a document id is a access document. Access document need user * has "all" permission to access it. * * @param docId, * the document id need to be checked */ private boolean isAccessDocument() throws SQLException { // get the docid from the guid String docId = null; try { docId = IdentifierManager.getInstance().getLocalId(guid); } catch (McdbDocNotFoundException e) { return false; //throw new SQLException(e); } docId = DocumentUtil.getDocIdFromString(docId); PreparedStatement pStmt = null; DBConnection conn = null; int serialNumber = -1; try { // check out DBConnection conn = DBConnectionPool.getDBConnection("PermissionControl.isAccessDoc"); serialNumber = conn.getCheckOutSerialNumber(); pStmt = conn.prepareStatement("select doctype from xml_documents where docid like ? "); pStmt.setString(1, docId); pStmt.execute(); ResultSet rs = pStmt.getResultSet(); boolean hasRow = rs.next(); String doctype = null; if (hasRow) { doctype = rs.getString(1); } pStmt.close(); // if it is an access document if (doctype != null && ((MetacatUtil.getOptionList(PropertyService .getProperty("xml.accessdoctype")).contains(doctype)))) { return true; } } catch (SQLException e) { throw new SQLException("PermissionControl.isAccessDocument " + "Error checking" + " on document " + docId + ". " + e.getMessage()); } catch (PropertyNotFoundException pnfe) { throw new SQLException("PermissionControl.isAccessDocument " + "Error checking" + " on document " + docId + ". " + pnfe.getMessage()); } finally { try { pStmt.close(); } finally { DBConnectionPool.returnDBConnection(conn, serialNumber); } } return false; }// isAccessDocument /** * Check if a stirng array contains a given documents' owner * * @param principals, * a string array storing the username, groups name and public. * @param docid, * the id of given documents */ private boolean containDocumentOwner(String[] principals) throws SQLException { // get the docid String docId = null; try { docId = IdentifierManager.getInstance().getLocalId(guid); docId = DocumentUtil.getDocIdFromString(docId); } catch (McdbDocNotFoundException e) { // should be true if we own the parent doc, but likely won't be checked in that case return false; } int lengthOfArray=principals.length; boolean hasRow; PreparedStatement pStmt=null; DBConnection conn = null; int serialNumber = -1; try { //check out DBConnection conn=DBConnectionPool.getDBConnection("PermissionControl.containDocOnwer"); serialNumber=conn.getCheckOutSerialNumber(); pStmt = conn.prepareStatement( "SELECT 'x' FROM xml_documents " + "WHERE docid = ? AND lower(user_owner) = ? " + "UNION ALL " + "SELECT 'x' FROM xml_revisions " + "WHERE docid = ? AND lower(user_owner) = ? "); //check every element in the string array too see if it conatains //the owner of document for (int i=0; i getUnReadableInlineDataIdList(String docid, String user, String[] groups) throws McdbException { Hashtable inlineDataList = getUnAccessableInlineDataIdList(docid, user, groups, AccessControlInterface.READSTRING); return inlineDataList; } /** * A static method to get Hashtable which cointains a inline data object list that * user can't overwrite it. The key is subtree id of inline data distrubition, * the value is internal file name for the inline data which is stored as docid * in xml_access table or data object doc id. * @param docidWithoutRev, metadata docid which should be the accessfileid * in the table * @param user , the name of user * @param groups, the group which the user belong to */ public static Hashtable getUnWritableInlineDataIdList(String docidWithoutRev, String user, String[] groups, boolean withRevision) throws Exception { Hashtable inlineDataList = getUnAccessableInlineDataIdList(docidWithoutRev, user, groups, AccessControlInterface.WRITESTRING); return inlineDataList; } /** * This method will get hashtable which contains a unaccessable distribution * inlinedata object list * */ private static Hashtable getUnAccessableInlineDataIdList(String docid, String user, String[] groups, String permission) throws McdbException { Hashtable unAccessibleIdList = new Hashtable(); if (user == null) { return unAccessibleIdList; } Hashtable allIdList; try { allIdList = getAllInlineDataIdList(docid); } catch (SQLException e) { throw new McdbException(e.getMessage()); } Enumeration en = allIdList.keys(); while (en.hasMoreElements()) { String subTreeId = (String) en.nextElement(); String fileId = (String) allIdList.get(subTreeId); //Here fileid is internal file id for line data. It stored in guid // field in xml_access table. PermissionController controller = new PermissionController(docid); if (!controller.hasPermissionForInlineData(user, groups, permission, fileId)) { logMetacat.info("PermissionController.getUnAccessableInlineDataIdList - Put subtree id " + subTreeId + " and " + "inline data file name " + fileId + " into " + "un" + permission + " hash"); unAccessibleIdList.put(subTreeId, fileId); } } return unAccessibleIdList; } /* * This method will get a hash table from xml_access table for all records * about the inline data. The key is subtree id and data is a inline internal * file name */ private static Hashtable getAllInlineDataIdList(String docid) throws SQLException { Hashtable inlineDataList = new Hashtable(); String sql = "SELECT subtreeid, guid " + "FROM xml_access " + "WHERE accessfileid = ? " + "AND subtreeid IS NOT NULL"; PreparedStatement pStmt=null; ResultSet rs=null; DBConnection conn=null; int serialNumber=-1; try { //check out DBConnection conn=DBConnectionPool.getDBConnection("PermissionControl.getDataSetId"); serialNumber=conn.getCheckOutSerialNumber(); pStmt=conn.prepareStatement(sql); //bind the value to query pStmt.setString(1, docid); //execute the query pStmt.execute(); rs=pStmt.getResultSet(); //process the result while(rs.next()) { String subTreeId = rs.getString(1); String inlineDataId = rs.getString(2); if (subTreeId != null && !subTreeId.trim().equals("") && inlineDataId != null && !inlineDataId.trim().equals("")) { inlineDataList.put(subTreeId, inlineDataId); } }//while }//try finally { try { pStmt.close(); } finally { DBConnectionPool.returnDBConnection(conn, serialNumber); } }//finally return inlineDataList; }//getAllInlineDataIdList }