Repository: sentry Updated Branches: refs/heads/sentry-ha-redesign 7c3456020 -> 2a812120c
SENTRY-1476: SentryStore is subject to JDQL injection (Alexander Kolbasov via Vamsee Yarlagadda) Project: http://git-wip-us.apache.org/repos/asf/sentry/repo Commit: http://git-wip-us.apache.org/repos/asf/sentry/commit/2a812120 Tree: http://git-wip-us.apache.org/repos/asf/sentry/tree/2a812120 Diff: http://git-wip-us.apache.org/repos/asf/sentry/diff/2a812120 Branch: refs/heads/sentry-ha-redesign Commit: 2a812120c88e99964b1166626ebfab55a16db91c Parents: 7c34560 Author: Vamsee Yarlagadda <vam...@cloudera.com> Authored: Wed Dec 14 16:05:25 2016 -0800 Committer: Vamsee Yarlagadda <vam...@cloudera.com> Committed: Wed Dec 14 18:14:55 2016 -0800 ---------------------------------------------------------------------- pom.xml | 2 + .../db/service/persistent/SentryStore.java | 674 ++++++++++++++----- .../db/service/persistent/TestSentryStore.java | 68 ++ 3 files changed, 576 insertions(+), 168 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/sentry/blob/2a812120/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 2ea4735..df26edf 100644 --- a/pom.xml +++ b/pom.xml @@ -844,6 +844,8 @@ limitations under the License. <!-- Exclude SSL .crtand .jks files --> <exclude>**/*.crt</exclude> <exclude>**/*.jks</exclude> + <!-- Exclude Apache reviewboard config--> + <exclude>**/.reviewboardrc</exclude> </excludes> </configuration> </execution> http://git-wip-us.apache.org/repos/asf/sentry/blob/2a812120/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java index 6ba040d..849aace 100644 --- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java +++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java @@ -90,6 +90,16 @@ public class SentryStore { public static int INDEX_GROUP_ROLES_MAP = 0; public static int INDEX_USER_ROLES_MAP = 1; + // String constants for field names + public static final String SERVER_NAME = "serverName"; + public static final String DB_NAME = "dbName"; + public static final String TABLE_NAME = "tableName"; + public static final String COLUMN_NAME = "columnName"; + public static final String ACTION = "action"; + public static final String URI = "URI"; + public static final String GRANT_OPTION = "grantOption"; + public static final String ROLE_NAME = "roleName"; + // For counters, representation of the "unknown value" private static final long COUNT_VALUE_UNKNOWN = -1; @@ -435,9 +445,8 @@ public class SentryStore { MSentryPrivilege mPrivilege = null; MSentryRole mRole = getRole(pm, roleName); if (mRole == null) { - throw new SentryNoSuchObjectException("Role: " + roleName + " doesn't exist"); + throw noSuchRole(roleName); } else { - if (!isNULL(privilege.getColumnName()) || !isNULL(privilege.getTableName()) || !isNULL(privilege.getDbName())) { // If Grant is for ALL and Either INSERT/SELECT already exists.. @@ -515,7 +524,7 @@ public class SentryStore { throws SentryNoSuchObjectException, SentryInvalidInputException { MSentryRole mRole = getRole(pm, roleName); if (mRole == null) { - throw new SentryNoSuchObjectException("Role: " + roleName + " doesn't exist"); + throw noSuchRole(roleName); } MSentryPrivilege mPrivilege = getMSentryPrivilege(tPrivilege, pm); if (mPrivilege == null) { @@ -627,6 +636,9 @@ public class SentryStore { || !isNULL(priv.getTableName())) { // Get all TableLevel Privs Set<MSentryPrivilege> childPrivs = getChildPrivileges(pm, roleNames, priv); + if (childPrivs == null) { + return; + } for (MSentryPrivilege childPriv : childPrivs) { // Only recurse for table level privs.. if (!isNULL(childPriv.getDbName()) && !isNULL(childPriv.getTableName()) @@ -661,44 +673,47 @@ public class SentryStore { MSentryPrivilege parent) throws SentryInvalidInputException { // Column and URI do not have children if (!isNULL(parent.getColumnName()) || !isNULL(parent.getURI())) { - return new HashSet<MSentryPrivilege>(); + return null; } Query query = pm.newQuery(MSentryPrivilege.class); - query.declareVariables("org.apache.sentry.provider.db.service.model.MSentryRole role"); - List<String> rolesFiler = new LinkedList<String>(); - for (String rName : roleNames) { - rolesFiler.add("role.roleName == \"" + trimAndLower(rName) + "\""); - } - StringBuilder filters = new StringBuilder("roles.contains(role) " - + "&& (" + Joiner.on(" || ").join(rolesFiler) + ")"); - filters.append(" && serverName == \"" + parent.getServerName() + "\""); + QueryParamBuilder paramBuilder = addRolesFilter(query, null, roleNames) + .add(SERVER_NAME, parent.getServerName()); + if (!isNULL(parent.getDbName())) { - filters.append(" && dbName == \"" + parent.getDbName() + "\""); + paramBuilder.add(DB_NAME, parent.getDbName()); if (!isNULL(parent.getTableName())) { - filters.append(" && tableName == \"" + parent.getTableName() + "\""); - filters.append(" && columnName != \"__NULL__\""); + paramBuilder.add(TABLE_NAME, parent.getTableName()) + .addNotNull(COLUMN_NAME); } else { - filters.append(" && tableName != \"__NULL__\""); + paramBuilder.addNotNull(TABLE_NAME); } } else { - filters.append(" && (dbName != \"__NULL__\" || URI != \"__NULL__\")"); + // Andd condition dbName != NULL || URI != NULL + paramBuilder.newChild() + .addNotNull(DB_NAME) + .addNotNull(URI); } - query.setFilter(filters.toString()); + LOGGER.debug("getChildPrivileges() Query: " + paramBuilder.toString()); + + query.setFilter(paramBuilder.toString()); query.setResult("privilegeScope, serverName, dbName, tableName, columnName," + " URI, action, grantOption"); - Set<MSentryPrivilege> privileges = new HashSet<MSentryPrivilege>(); - for (Object[] privObj : (List<Object[]>) query.execute()) { - MSentryPrivilege priv = new MSentryPrivilege(); - priv.setPrivilegeScope((String) privObj[0]); - priv.setServerName((String) privObj[1]); - priv.setDbName((String) privObj[2]); - priv.setTableName((String) privObj[3]); - priv.setColumnName((String) privObj[4]); - priv.setURI((String) privObj[5]); - priv.setAction((String) privObj[6]); - priv.setGrantOption((Boolean) privObj[7]); + Set<MSentryPrivilege> privileges = new HashSet<>(); + for (Object[] privObj : + (List<Object[]>)query.executeWithMap(paramBuilder.getArguments())) { + String scope = (String)privObj[0]; + String serverName = (String)privObj[1]; + String dbName = (String)privObj[2]; + String tableName = (String) privObj[3]; + String columnName = (String) privObj[4]; + String URI = (String) privObj[5]; + String action = (String) privObj[6]; + Boolean grantOption = (Boolean) privObj[7]; + MSentryPrivilege priv = + new MSentryPrivilege(scope, serverName, dbName, tableName, + columnName, URI, action, grantOption); privileges.add(priv); } return privileges; @@ -707,45 +722,52 @@ public class SentryStore { @SuppressWarnings("unchecked") private List<MSentryPrivilege> getMSentryPrivileges(TSentryPrivilege tPriv, PersistenceManager pm) { Query query = pm.newQuery(MSentryPrivilege.class); - StringBuilder filters = new StringBuilder("this.serverName == \"" - + toNULLCol(safeTrimLower(tPriv.getServerName())) + "\" "); + QueryParamBuilder paramBuilder = new QueryParamBuilder(); + paramBuilder + .add(SERVER_NAME, tPriv.getServerName()) + .add("action", tPriv.getAction()); + if (!isNULL(tPriv.getDbName())) { - filters.append("&& this.dbName == \"" + toNULLCol(safeTrimLower(tPriv.getDbName())) + "\" "); + paramBuilder.add(DB_NAME, tPriv.getDbName()); if (!isNULL(tPriv.getTableName())) { - filters.append("&& this.tableName == \"" + toNULLCol(safeTrimLower(tPriv.getTableName())) + "\" "); + paramBuilder.add(TABLE_NAME, tPriv.getTableName()); if (!isNULL(tPriv.getColumnName())) { - filters.append("&& this.columnName == \"" + toNULLCol(safeTrimLower(tPriv.getColumnName())) + "\" "); + paramBuilder.add(COLUMN_NAME, tPriv.getColumnName()); } } + } else if (!isNULL(tPriv.getURI())) { + // if db is null, uri is not null + paramBuilder.add(URI, tPriv.getURI(), true); } - // if db is null, uri is not null - else if (!isNULL(tPriv.getURI())){ - filters.append("&& this.URI == \"" + toNULLCol(safeTrim(tPriv.getURI())) + "\" "); - } - filters.append("&& this.action == \"" + toNULLCol(safeTrimLower(tPriv.getAction())) + "\""); + // LOGGER.debug("getMSentryPrivileges() Query: " + paramBuilder.toString()); - query.setFilter(filters.toString()); - return (List<MSentryPrivilege>) query.execute(); + query.setFilter(paramBuilder.toString()); + return (List<MSentryPrivilege>) query.executeWithMap(paramBuilder.getArguments()); } private MSentryPrivilege getMSentryPrivilege(TSentryPrivilege tPriv, PersistenceManager pm) { - Query query = pm.newQuery(MSentryPrivilege.class); - query.setFilter("this.serverName == \"" + toNULLCol(safeTrimLower(tPriv.getServerName())) + "\" " - + "&& this.dbName == \"" + toNULLCol(safeTrimLower(tPriv.getDbName())) + "\" " - + "&& this.tableName == \"" + toNULLCol(safeTrimLower(tPriv.getTableName())) + "\" " - + "&& this.columnName == \"" + toNULLCol(safeTrimLower(tPriv.getColumnName())) + "\" " - + "&& this.URI == \"" + toNULLCol(safeTrim(tPriv.getURI())) + "\" " - + "&& this.grantOption == grantOption " - + "&& this.action == \"" + toNULLCol(safeTrimLower(tPriv.getAction())) + "\""); - query.declareParameters("Boolean grantOption"); - query.setUnique(true); Boolean grantOption = null; if (tPriv.getGrantOption().equals(TSentryGrantOption.TRUE)) { grantOption = true; } else if (tPriv.getGrantOption().equals(TSentryGrantOption.FALSE)) { grantOption = false; } - return (MSentryPrivilege)query.execute(grantOption); + + QueryParamBuilder paramBuilder = new QueryParamBuilder(); + paramBuilder.add(SERVER_NAME, tPriv.getServerName()) + .add(DB_NAME, tPriv.getDbName()) + .add(TABLE_NAME, tPriv.getTableName()) + .add(COLUMN_NAME, tPriv.getColumnName()) + .add(URI, tPriv.getURI(), true) + .addObject(GRANT_OPTION, grantOption) + .add(ACTION, tPriv.getAction()); + + LOGGER.debug("getMSentryPrivilege() Query: " + paramBuilder.toString()); + + Query query = pm.newQuery(MSentryPrivilege.class); + query.setUnique(true); + query.setFilter(paramBuilder.toString()); + return (MSentryPrivilege)query.executeWithMap(paramBuilder.getArguments()); } public void dropSentryRole(final String roleName) throws Exception { @@ -763,7 +785,7 @@ public class SentryStore { String lRoleName = trimAndLower(roleName); MSentryRole sentryRole = getRole(pm, lRoleName); if (sentryRole == null) { - throw new SentryNoSuchObjectException("Role: " + lRoleName + " doesn't exist"); + throw noSuchRole(lRoleName); } int numPrivs = sentryRole.getPrivileges().size(); sentryRole.removePrivileges(); @@ -789,11 +811,10 @@ public class SentryStore { String lRoleName = trimAndLower(roleName); MSentryRole role = getRole(pm, lRoleName); if (role == null) { - throw new SentryNoSuchObjectException("Role: " + lRoleName + " doesn't exist"); + throw noSuchRole(lRoleName); } Query query = pm.newQuery(MSentryGroup.class); - query.setFilter("this.groupName == t"); - query.declareParameters("java.lang.String t"); + query.setFilter("this.groupName == :groupName"); query.setUnique(true); List<MSentryGroup> groups = Lists.newArrayList(); for (TSentryGroup tGroup : groupNames) { @@ -824,11 +845,10 @@ public class SentryStore { String trimmedRoleName = trimAndLower(roleName); MSentryRole role = getRole(pm, trimmedRoleName); if (role == null) { - throw new SentryNoSuchObjectException("Role: " + trimmedRoleName); + throw noSuchRole(trimmedRoleName); } Query query = pm.newQuery(MSentryUser.class); - query.setFilter("this.userName == t"); - query.declareParameters("java.lang.String t"); + query.setFilter("this.userName == :userName"); query.setUnique(true); List<MSentryUser> users = Lists.newArrayList(); for (String userName : userNames) { @@ -851,11 +871,10 @@ public class SentryStore { String trimmedRoleName = trimAndLower(roleName); MSentryRole role = getRole(pm, trimmedRoleName); if (role == null) { - throw new SentryNoSuchObjectException("Role: " + trimmedRoleName); + throw noSuchRole(trimmedRoleName); } else { Query query = pm.newQuery(MSentryUser.class); - query.setFilter("this.userName == t"); - query.declareParameters("java.lang.String t"); + query.setFilter("this.userName == :userName"); query.setUnique(true); List<MSentryUser> users = Lists.newArrayList(); for (String userName : userNames) { @@ -881,11 +900,10 @@ public class SentryStore { String trimmedRoleName = trimAndLower(roleName); MSentryRole role = getRole(pm, trimmedRoleName); if (role == null) { - throw new SentryNoSuchObjectException("Role: " + trimmedRoleName + " doesn't exist"); + throw noSuchRole(trimmedRoleName); } Query query = pm.newQuery(MSentryGroup.class); - query.setFilter("this.groupName == t"); - query.declareParameters("java.lang.String t"); + query.setFilter("this.groupName == :groupName"); query.setUnique(true); List<MSentryGroup> groups = Lists.newArrayList(); for (TSentryGroup tGroup : groupNames) { @@ -910,8 +928,7 @@ public class SentryStore { String trimmedRoleName = trimAndLower(roleName); MSentryRole sentryRole = getRole(pm, trimmedRoleName); if (sentryRole == null) { - throw new SentryNoSuchObjectException("Role: " + trimmedRoleName + - " doesn't exist"); + throw noSuchRole(trimmedRoleName); } return sentryRole; } @@ -928,17 +945,11 @@ public class SentryStore { new TransactionBlock() { public Object execute(PersistenceManager pm) throws Exception { Query query = pm.newQuery(MSentryPrivilege.class); - query.declareVariables("org.apache.sentry.provider.db.service.model.MSentryRole role"); - List<String> rolesFiler = new LinkedList<String>(); - for (String rName : roleNames) { - rolesFiler.add("role.roleName == \"" + trimAndLower(rName) + "\""); - } - StringBuilder filters = new StringBuilder("roles.contains(role) " - + "&& (" + Joiner.on(" || ").join(rolesFiler) + ") "); - filters.append("&& serverName == \"" + trimAndLower(serverName) + "\""); - query.setFilter(filters.toString()); + QueryParamBuilder paramBuilder = addRolesFilter(query,null, roleNames); + paramBuilder.add(SERVER_NAME, serverName); + query.setFilter(paramBuilder.toString()); query.setResult("count(this)"); - Long numPrivs = (Long) query.execute(); + Long numPrivs = (Long) query.executeWithMap(paramBuilder.getArguments()); return numPrivs > 0; } }); @@ -951,7 +962,7 @@ public class SentryStore { @SuppressWarnings("unchecked") private List<MSentryPrivilege> getMSentryPrivileges(final Set<String> roleNames, final TSentryAuthorizable authHierarchy) { - List<MSentryPrivilege> result = new ArrayList<MSentryPrivilege>(); + List<MSentryPrivilege> result = new ArrayList<>(); if (roleNames == null || roleNames.isEmpty()) { return result; } @@ -961,35 +972,46 @@ public class SentryStore { new TransactionBlock() { public Object execute(PersistenceManager pm) throws Exception { Query query = pm.newQuery(MSentryPrivilege.class); - query.declareVariables("MSentryRole role"); - List<String> rolesFiler = new LinkedList<String>(); - for (String rName : roleNames) { - rolesFiler.add("role.roleName == \"" + trimAndLower(rName) + "\""); - } - StringBuilder filters = new StringBuilder("roles.contains(role) " - + "&& (" + Joiner.on(" || ").join(rolesFiler) + ") "); + QueryParamBuilder paramBuilder = addRolesFilter(query, null, roleNames); + if (authHierarchy != null && authHierarchy.getServer() != null) { - filters.append("&& serverName == \"" + authHierarchy.getServer().toLowerCase() + "\""); + paramBuilder.add(SERVER_NAME, authHierarchy.getServer()); if (authHierarchy.getDb() != null) { - filters.append(" && ((dbName == \"" + authHierarchy.getDb().toLowerCase() + "\") || (dbName == \"__NULL__\")) && (URI == \"__NULL__\")"); + paramBuilder.addNull(URI) + .newChild() + .add(DB_NAME, authHierarchy.getDb()) + .addNull(DB_NAME); if (authHierarchy.getTable() != null && !AccessConstants.ALL.equalsIgnoreCase(authHierarchy.getTable())) { if (!AccessConstants.SOME.equalsIgnoreCase(authHierarchy.getTable())) { - filters.append(" && ((tableName == \"" + authHierarchy.getTable().toLowerCase() + "\") || (tableName == \"__NULL__\")) && (URI == \"__NULL__\")"); + paramBuilder.addNull(URI) + .newChild() + .add(TABLE_NAME, authHierarchy.getTable()) + .addNull(TABLE_NAME); } if (authHierarchy.getColumn() != null && !AccessConstants.ALL.equalsIgnoreCase(authHierarchy.getColumn()) && !AccessConstants.SOME.equalsIgnoreCase(authHierarchy.getColumn())) { - filters.append(" && ((columnName == \"" + authHierarchy.getColumn().toLowerCase() + "\") || (columnName == \"__NULL__\")) && (URI == \"__NULL__\")"); + paramBuilder.addNull(URI) + .newChild() + .add(COLUMN_NAME, authHierarchy.getColumn()) + .addNull(COLUMN_NAME); } } } if (authHierarchy.getUri() != null) { - filters.append(" && ((URI != \"__NULL__\") && (\"" + authHierarchy.getUri() + "\".startsWith(URI)) || (URI == \"__NULL__\")) && (dbName == \"__NULL__\")"); + paramBuilder.addNull(DB_NAME) + .newChild() + .addNull(URI) + .newChild() + .addNotNull(URI) + .addCustomParam("\"" + authHierarchy.getUri() + + "\".startsWith(:URI)", URI, authHierarchy.getUri()); } } - query.setFilter(filters.toString()); - return (List<MSentryPrivilege>) query.execute(); + LOGGER.debug("getMSentryPrivileges1() Query: " + paramBuilder.toString()); + query.setFilter(paramBuilder.toString()); + return query.executeWithMap(paramBuilder.getArguments()); } }); } catch (Exception e) { @@ -1007,35 +1029,28 @@ public class SentryStore { new TransactionBlock() { public Object execute(PersistenceManager pm) throws Exception { Query query = pm.newQuery(MSentryPrivilege.class); - StringBuilder filters = new StringBuilder(); + QueryParamBuilder paramBuilder = new QueryParamBuilder(); if (roleNames == null || roleNames.isEmpty()) { - filters.append(" !roles.isEmpty() "); + paramBuilder.addString("!roles.isEmpty()"); } else { - query.declareVariables("MSentryRole role"); - List<String> rolesFiler = new LinkedList<String>(); - for (String rName : roleNames) { - rolesFiler.add("role.roleName == \"" + trimAndLower(rName) + "\""); - } - filters.append("roles.contains(role) " - + "&& (" + Joiner.on(" || ").join(rolesFiler) + ") "); + addRolesFilter(query, paramBuilder, roleNames); } if (authHierarchy.getServer() != null) { - filters.append("&& serverName == \"" + - authHierarchy.getServer().toLowerCase() + "\""); + paramBuilder.add(SERVER_NAME, authHierarchy.getServer()); if (authHierarchy.getDb() != null) { - filters.append(" && (dbName == \"" + - authHierarchy.getDb().toLowerCase() + "\") && (URI == \"__NULL__\")"); + paramBuilder.add(DB_NAME, authHierarchy.getDb()).addNull(URI); if (authHierarchy.getTable() != null) { - filters.append(" && (tableName == \"" + - authHierarchy.getTable().toLowerCase() + "\")"); + paramBuilder.add(TABLE_NAME, authHierarchy.getTable()); } else { - filters.append(" && (tableName == \"__NULL__\")"); + paramBuilder.addNull(TABLE_NAME); } } else if (authHierarchy.getUri() != null) { - filters.append(" && (URI != \"__NULL__\") && (\"" + authHierarchy.getUri() + - "\".startsWith(URI)) && (dbName == \"__NULL__\")"); + paramBuilder.addNotNull(URI) + .addNull(DB_NAME) + .addCustomParam("(:authURI.startsWith(URI))", "authURI", authHierarchy.getUri()); } else { - filters.append(" && (dbName == \"__NULL__\") && (URI == \"__NULL__\")"); + paramBuilder.addNull(DB_NAME) + .addNull(URI); } } else { // if no server, then return empty resultset @@ -1044,8 +1059,9 @@ public class SentryStore { FetchGroup grp = pm.getFetchGroup(MSentryPrivilege.class, "fetchRole"); grp.addMember("roles"); pm.getFetchPlan().addGroup("fetchRole"); - query.setFilter(filters.toString()); - return (List<MSentryPrivilege>) query.execute(); + // LOGGER.debug("XXX: " + paramBuilder.toString()); + query.setFilter(paramBuilder.toString()); + return query.executeWithMap(paramBuilder.getArguments()); } }); } catch (Exception e) { @@ -1149,7 +1165,7 @@ public class SentryStore { String trimmedGroupName = groupName.trim(); MSentryGroup sentryGroup = getGroup(pm, trimmedGroupName); if (sentryGroup == null) { - throw new SentryNoSuchObjectException("Group: " + trimmedGroupName + " doesn't exist"); + throw noSuchGroup(trimmedGroupName); } roles = sentryGroup.getRoles(); } @@ -1776,48 +1792,48 @@ public class SentryStore { ServerConfig.ADMIN_GROUPS, new String[]{})); } - /** - * @return Mapping of AuthZObj(db/table) -> (Role -> permission) - */ @SuppressWarnings("unchecked") public Map<String, HashMap<String, String>> retrieveFullPrivilegeImage() { Map<String, HashMap<String, String>> result = new HashMap<>(); try { result = (Map<String, HashMap<String, String>>) tm.executeTransaction( - new TransactionBlock() { - public Object execute(PersistenceManager pm) throws Exception { - Map<String, HashMap<String, String>> retVal = new HashMap<>(); - Query query = pm.newQuery(MSentryPrivilege.class); - String filters = "(serverName != \"__NULL__\") " - + "&& (dbName != \"__NULL__\") " + "&& (URI == \"__NULL__\")"; - query.setFilter(filters.toString()); - query - .setOrdering("serverName ascending, dbName ascending, tableName ascending"); - List<MSentryPrivilege> privileges = (List<MSentryPrivilege>) query - .execute(); - for (MSentryPrivilege mPriv : privileges) { - String authzObj = mPriv.getDbName(); - if (!isNULL(mPriv.getTableName())) { - authzObj = authzObj + "." + mPriv.getTableName(); - } - HashMap<String, String> pUpdate = retVal.get(authzObj); - if (pUpdate == null) { - pUpdate = new HashMap<String, String>(); - retVal.put(authzObj, pUpdate); - } - for (MSentryRole mRole : mPriv.getRoles()) { - String existingPriv = pUpdate.get(mRole.getRoleName()); - if (existingPriv == null) { - pUpdate.put(mRole.getRoleName(), mPriv.getAction().toUpperCase()); - } else { - pUpdate.put(mRole.getRoleName(), existingPriv + "," - + mPriv.getAction().toUpperCase()); + new TransactionBlock() { + public Object execute(PersistenceManager pm) throws Exception { + Map<String, HashMap<String, String>> retVal = new HashMap<>(); + Query query = pm.newQuery(MSentryPrivilege.class); + QueryParamBuilder paramBuilder = new QueryParamBuilder(); + paramBuilder + .addNotNull(SERVER_NAME) + .addNotNull(DB_NAME) + .addNull(URI); + + query.setFilter(paramBuilder.toString()); + query.setOrdering("serverName ascending, dbName ascending, tableName ascending"); + List<MSentryPrivilege> privileges = + (List<MSentryPrivilege>) query.executeWithMap(paramBuilder.getArguments()); + for (MSentryPrivilege mPriv : privileges) { + String authzObj = mPriv.getDbName(); + if (!isNULL(mPriv.getTableName())) { + authzObj = authzObj + "." + mPriv.getTableName(); + } + HashMap<String, String> pUpdate = retVal.get(authzObj); + if (pUpdate == null) { + pUpdate = new HashMap<String, String>(); + retVal.put(authzObj, pUpdate); + } + for (MSentryRole mRole : mPriv.getRoles()) { + String existingPriv = pUpdate.get(mRole.getRoleName()); + if (existingPriv == null) { + pUpdate.put(mRole.getRoleName(), mPriv.getAction().toUpperCase()); + } else { + pUpdate.put(mRole.getRoleName(), existingPriv + "," + + mPriv.getAction().toUpperCase()); + } + } } + return retVal; } - } - return retVal; - } - }); + }); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } @@ -2119,18 +2135,16 @@ public class SentryStore { new TransactionBlock() { public Object execute(PersistenceManager pm) throws Exception { Query query = pm.newQuery(MSentryRole.class); - - List<String> rolesFiler = new LinkedList<String>(); - if (roleNames != null) { - for (String rName : roleNames) { - rolesFiler.add("(roleName == \"" + rName.trim().toLowerCase() + "\")"); - } - } - if (rolesFiler.size() > 0) { - query.setFilter(Joiner.on(" || ").join(rolesFiler)); + List<MSentryRole> mSentryRoles; + if (roleNames == null || roleNames.isEmpty()) { + mSentryRoles = (List<MSentryRole>)query.execute(); + } else { + QueryParamBuilder paramBuilder = new QueryParamBuilder(QueryParamBuilder.Op.OR); + paramBuilder.addSet("roleName == ", roleNames); + query.setFilter(paramBuilder.toString()); + mSentryRoles = + (List<MSentryRole>) query.executeWithMap(paramBuilder.getArguments()); } - - List<MSentryRole> mSentryRoles = (List<MSentryRole>) query.execute(); Map<String, Set<String>> groupRolesMap = getGroupRolesMap(mSentryRoles); Map<String, Set<String>> userRolesMap = getUserRolesMap(mSentryRoles); List<Map<String, Set<String>>> mapsList = new ArrayList<>(); @@ -2202,19 +2216,17 @@ public class SentryStore { new TransactionBlock() { public Object execute(PersistenceManager pm) throws Exception { Query query = pm.newQuery(MSentryPrivilege.class); + QueryParamBuilder paramBuilder = new QueryParamBuilder(); - List<String> privilegeFiler = new LinkedList<String>(); if (!StringUtils.isEmpty(dbName)) { - privilegeFiler.add("(dbName == \"" + dbName.trim().toLowerCase() + "\") "); + paramBuilder.add(DB_NAME, dbName); } if (!StringUtils.isEmpty(tableName)) { - privilegeFiler.add("(tableName == \"" + tableName.trim().toLowerCase() + "\") "); - } - if (privilegeFiler.size() > 0) { - query.setFilter(Joiner.on(" && ").join(privilegeFiler)); + paramBuilder.add(TABLE_NAME, tableName); } - - List<MSentryPrivilege> mSentryPrivileges = (List<MSentryPrivilege>) query.execute(); + query.setFilter(paramBuilder.toString()); + List<MSentryPrivilege> mSentryPrivileges = + (List<MSentryPrivilege>) query.executeWithMap(paramBuilder.getArguments()); return getRolePrivilegesMap(mSentryPrivileges); } }); @@ -2608,4 +2620,330 @@ public class SentryStore { pm.makePersistent(new MSentryRole(trimAndLower(roleName))); } } + + /** + * Return exception for nonexistent role + * @param roleName Role name + * @return SentryNoSuchObjectException with appropriate message + */ + private SentryNoSuchObjectException noSuchRole(String roleName) { + return new SentryNoSuchObjectException("nonexistent role " + roleName); + } + + /** + * Return exception for nonexistent group + * @param groupName Group name + * @return SentryNoSuchObjectException with appropriate message + */ + private SentryNoSuchObjectException noSuchGroup(String groupName) { + return new SentryNoSuchObjectException("nonexistent group + " + groupName); + } + + /** + * Add common filter for set of roles + * @param query Query used for search + * @param paramBuilder paramBuilder for parameters + * @param roleNames set of role names + * @return paramBuilder supplied or a new one + */ + private QueryParamBuilder addRolesFilter(Query query, QueryParamBuilder paramBuilder, + Set<String> roleNames) { + query.declareVariables(MSentryRole.class.getName() + " role"); + if (paramBuilder == null) { + paramBuilder = new QueryParamBuilder(); + } + if (roleNames == null || roleNames.isEmpty()) { + return paramBuilder; + } + paramBuilder.newChild().addSet("role.roleName == ", roleNames); + paramBuilder.addString("roles.contains(role)"); + return paramBuilder; + } + + /** + * QueryParamBuilder is a helper class assisting in creating parameters for + * JDOQL queries. + * <p> + * There are many places where we want to construct a non-trivial parametrized query, + * often with sub-queries. A simple query expression is just a list of conditions joined + * by operation (either AND or OR). For the whole expression we keep a dictionary of + * parameter names to parameter values that is passed to <em>query.executeWithMap()</em>. + * <p> + * For sub-expressions we create a child <em>QueryParamBuilder</em> which shares the + * dictionary with the parent. Then when we construct the final query string using + * <em>toString()</em> operator we recursively convert each child to a string and add each + * of the resulting strings to the top-level expression list. + * <p> + * The class also provides useful common methods for checking that some field is or + * isn't <em>NULL</em> and method <em>addSet()</em> to add dynamic set of key/values + * <p> + * Most class methods return <em>this</em> so it is possible to chain calls together. + * <p> + * Examples: + * <ol> + * <li> + * <pre> + * QueryParamBuilder p + * .add("key1", "val1") + * .add("key2", "val2") + * + * // Returns "(this.key1 == :key1 && this.key2 == :key2)" + * String queryStr = p.toString(); + * + * // Returns map {"key1": "val1", "key2": "val2"} + * Map<String, Object> args = p.getArguments(); + * + * Query query = pm.newQuery(Foo.class); + * query.setFilter(queryStr); + * query.executeWIthMap(args); + * </pre></li> + * <li> + * <pre> + * QueryParamBuilder p = new QueryParamBuilder(); + * p.add("key1", "val1") + * .add("key2", "val2") + * .newChild() + * .add("key3", "val3") + * .add("key4", "val4"). + * + * // Returns "(this.key1 == :val1 && this.key2 == :val2 && \ + * // (this.key3 == :val4 || this.key4 == val4))" + * String queryStr1 = p.toString() + * </pre></li> + * </ol> + */ + static class QueryParamBuilder { + + // Query is built by joining all parts with the specified Op + enum Op { + AND(" && "), + OR(" || "); + + public String toString() { + return value; + } + + private final String value; + Op(String val) { + this.value = val; + } + }; + + // Query parts that will be joined with Op + private final List<String> queryParts = new LinkedList<>(); + // List of children - allocated lazily when children are added + private List<QueryParamBuilder> children; + // Query Parameters + private final Map<String, Object> arguments; + // Join operation + private final String operation; + private final String THIS = "this."; + + /** + * Construct a default QueryParamBuilder joining arguments with AND + */ + QueryParamBuilder() { + this(Op.AND); + } + + /** + * Construct generic QueryParamBuilder + * @param operation join operation (AND or OR) + */ + QueryParamBuilder(Op operation) { + this.arguments = new HashMap<>(); + this.operation = operation.toString(); + } + + /** + * Internal constructor used for children - reuses arguments from parent + * @param parent parent element + * @param operation join operation + */ + private QueryParamBuilder(QueryParamBuilder parent, Op operation) { + this.arguments = parent.getArguments(); + this.operation = operation.toString(); + } + + /** + * Create a new child builder attached to this one + * @param operation - join operation + * @return new child of the QueryBuilder + */ + QueryParamBuilder newChild(Op operation) { + QueryParamBuilder child = new QueryParamBuilder(this, operation); + if (children == null) { + children = new LinkedList<>(); + } + children.add(child); + return child; + } + + /** + * Create a new child builder and attach to this one. Child's join operation is the + * inverse of the parent + * @return new child of the QueryBuilder + */ + QueryParamBuilder newChild() { + // Reverse operation of this builder + Op operation = this.operation.equals(Op.AND.toString()) ? Op.OR : Op.AND; + return this.newChild(operation); + } + + /** + * Get query arguments + * @return query arguments as a map of arg name and arg value suitable for + * query.executeWithMap + */ + public Map<String, Object> getArguments() { + return arguments; + } + + /** + * Get query string - reconstructs the query string from all the parts and children. + * @return Query string which can be matched with arguments to execute a query. + */ + @Override + public String toString() { + if (children == null && queryParts.isEmpty()) { + return ""; + } + if (children == null) { + return "(" + Joiner.on(operation).join(queryParts) + ")"; + } + // Concatenate our query parts with all children + List<String> result = new LinkedList<>(queryParts); + for (Object child: children) { + result.add(child.toString()); + } + return "(" + Joiner.on(operation).join(result) + ")"; + } + + /** + * Add parameter for field fieldName with given value where value is any Object + * @param fieldName Field name to query for + * @param value Field value (can be any Object) + */ + public QueryParamBuilder addObject(String fieldName, Object value) { + return addCustomParam(THIS + fieldName + " == :" + fieldName, + fieldName, value); + } + + /** + * Add string parameter for field fieldName + * @param fieldName name of the field + * @param value String value. Value is normalized - converted to lower case and trimmed + * @return this + */ + public QueryParamBuilder add(String fieldName, String value) { + return addCommon(fieldName, value, false); + } + + /** + * Add string parameter to field value with or without normalization + * @param fieldName field name of the field + * @param value String value, inserted as is if preserveCase is true, normalized otherwise + * @param preserveCase if true, trm and lowercase the value. + * @return this + */ + public QueryParamBuilder add(String fieldName, String value, boolean preserveCase) { + return addCommon(fieldName, value, preserveCase); + } + + /** + * Add condition that fieldName is not equal to NULL + * @param fieldName field name to compare to NULL + * @return this + */ + public QueryParamBuilder addNotNull(String fieldName) { + queryParts.add(String.format("this.%s != \"%s\"", fieldName, NULL_COL)); + return this; + } + + /** + * Add condition that fieldName is equal to NULL + * @param fieldName field name to compare to NULL + * @return this + */ + public QueryParamBuilder addNull(String fieldName) { + queryParts.add(String.format("this.%s == \"%s\"", fieldName, NULL_COL)); + return this; + } + + /** + * Add custom string for evaluation together with a single parameter. + * This is used in cases where we need expression different from this.name == value + * @param expr String expression containing ':< paramName>' somewhere + * @param paramName parameter name + * @param value parameter value + * @return this + */ + public QueryParamBuilder addCustomParam(String expr, String paramName, Object value) { + arguments.put(paramName, value); + queryParts.add(expr); + return this; + } + + /** + * Add arbitrary query string without parameters + * @param expr String expression + * @return this + */ + public QueryParamBuilder addString(String expr) { + queryParts.add(expr); + return this; + } + + /** + * Add multiple conditions for set of values. + * <p> + * Example: + * <pre> + * Set<String>names = new HashSet<>(); + * names.add("foo"); + * names.add("bar"); + * names.add("bob"); + * paramBuilder.addSet("prefix == ", names); + * // Expect:"(prefix == :var0 && prefix == :var1 && prefix == :var2)" + * paramBuilder.toString()); + * // paramBuilder(getArguments()) contains mapping for var0, var1 and var2 + * </pre> + * @param prefix common prefix to use for expression + * @param values + * @return this + */ + public QueryParamBuilder addSet(String prefix, Set<String> values) { + if (values == null) { + return this; + } + int index = 0; + + // Add expressions of the form 'prefix :var$i' + for(String name: values) { + // Append index to the varName + String vName = String.format("var%d", index); + addCustomParam(prefix + ":" + vName, vName, name.trim().toLowerCase()); + index++; + } + return this; + } + + /** + * common code for adding string values + * @param fieldName field name to add + * @param value field value + * @param preserveCase if true, do not trim and lower + * @return this + */ + private QueryParamBuilder addCommon(String fieldName, String value, + boolean preserveCase) { + if (preserveCase) { + arguments.put(fieldName, toNULLCol(safeTrim(value))); + } else { + arguments.put(fieldName, toNULLCol(safeTrimLower(value))); + } + queryParts.add("this." + fieldName + " == :" + fieldName); + return this; + } + } } http://git-wip-us.apache.org/repos/asf/sentry/blob/2a812120/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java index 14b5e8e..a35c8d7 100644 --- a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java +++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java @@ -2194,6 +2194,74 @@ public class TestSentryStore extends org.junit.Assert { assertEquals(Sets.newHashSet("/user/hive/warehouse/db1.db/table1"), pathsImage.get("db1.table1")); } + public void testQueryParamBuilder() { + SentryStore.QueryParamBuilder paramBuilder; + paramBuilder = new SentryStore.QueryParamBuilder(); + // Try single parameter + paramBuilder.add("key", "val"); + assertEquals("(this.key == :key)", paramBuilder.toString()); + // Try adding second parameter plus add trimming and conversion + // to lower case + paramBuilder.add("key1", " Val1 ", true); + assertEquals("(this.key == :key && this.key1 == :key1)", + paramBuilder.toString()); + Map<String, Object> params = paramBuilder.getArguments(); + assertEquals("val", params.get("key")); + assertEquals("Val1", params.get("key1")); + + paramBuilder = new SentryStore.QueryParamBuilder(SentryStore.QueryParamBuilder.Op.OR); + paramBuilder.add("key", " Val ", true); + paramBuilder.addNotNull("notNullField"); + paramBuilder.addNull("nullField"); + assertEquals("(this.key == :key || this.notNullField != \"__NULL__\" || this.nullField == \"__NULL__\")", + paramBuilder.toString()); + params = paramBuilder.getArguments(); + assertEquals("Val", params.get("key")); + + paramBuilder = new SentryStore.QueryParamBuilder() + .addNull("var1") + .addNotNull("var2"); + assertEquals("(this.var1 == \"__NULL__\" && this.var2 != \"__NULL__\")", + paramBuilder.toString()); + + // Test newChild() + paramBuilder = new SentryStore.QueryParamBuilder(); + paramBuilder + .addString("e1") + .addString("e2") + .newChild() + .add("v3", "e3") + .add("v4", "e4") + .newChild() + .addString("e5") + .addString("e6") + ; + assertEquals("(e1 && e2 && (this.v3 == :v3 || this.v4 == :v4 || (e5 && e6)))", + paramBuilder.toString()); + + params = paramBuilder.getArguments(); + assertEquals("e3", params.get("v3")); + assertEquals("e4", params.get("v4")); + + // Test addSet + paramBuilder = new SentryStore.QueryParamBuilder(); + Set<String>names = new HashSet<>(); + names.add("foo"); + names.add("bar"); + names.add("bob"); + + paramBuilder.addSet("prefix == ", names); + assertEquals("(prefix == :var0 && prefix == :var1 && prefix == :var2)", + paramBuilder.toString()); + params = paramBuilder.getArguments(); + + Set<String>result = new HashSet<>(); + result.add((String)params.get("var0")); + result.add((String)params.get("var1")); + result.add((String)params.get("var2")); + assertTrue(result.containsAll(names)); + assertTrue(names.containsAll(result)); + } protected static void addGroupsToUser(String user, String... groupNames) { policyFile.addGroupsToUser(user, groupNames);