| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.sentry.provider.db.service.persistent; |
| |
| import static org.apache.sentry.provider.common.ProviderConstants.AUTHORIZABLE_JOINER; |
| import static org.apache.sentry.provider.common.ProviderConstants.KV_JOINER; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.concurrent.locks.Condition; |
| import java.util.concurrent.locks.Lock; |
| import java.util.concurrent.locks.ReentrantLock; |
| |
| import javax.jdo.FetchGroup; |
| import javax.jdo.JDODataStoreException; |
| import javax.jdo.JDOHelper; |
| import javax.jdo.PersistenceManager; |
| import javax.jdo.PersistenceManagerFactory; |
| import javax.jdo.Query; |
| import javax.jdo.Transaction; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.sentry.SentryUserException; |
| import org.apache.sentry.core.model.db.AccessConstants; |
| import org.apache.sentry.core.model.db.DBModelAuthorizable.AuthorizableType; |
| import org.apache.sentry.provider.common.ProviderConstants; |
| import org.apache.sentry.provider.db.SentryAccessDeniedException; |
| import org.apache.sentry.provider.db.SentryAlreadyExistsException; |
| import org.apache.sentry.provider.db.SentryGrantDeniedException; |
| import org.apache.sentry.provider.db.SentryInvalidInputException; |
| import org.apache.sentry.provider.db.SentryNoSuchObjectException; |
| import org.apache.sentry.provider.db.service.model.MSentryGroup; |
| import org.apache.sentry.provider.db.service.model.MSentryPrivilege; |
| import org.apache.sentry.provider.db.service.model.MSentryRole; |
| import org.apache.sentry.provider.db.service.model.MSentryVersion; |
| import org.apache.sentry.provider.db.service.thrift.SentryPolicyStoreProcessor; |
| import org.apache.sentry.provider.db.service.thrift.TSentryActiveRoleSet; |
| import org.apache.sentry.provider.db.service.thrift.TSentryAuthorizable; |
| import org.apache.sentry.provider.db.service.thrift.TSentryGrantOption; |
| import org.apache.sentry.provider.db.service.thrift.TSentryGroup; |
| import org.apache.sentry.provider.db.service.thrift.TSentryPrivilege; |
| import org.apache.sentry.provider.db.service.thrift.TSentryPrivilegeMap; |
| import org.apache.sentry.provider.db.service.thrift.TSentryRole; |
| import org.apache.sentry.provider.db.service.thrift.TStoreSnapshot; |
| import org.apache.sentry.service.thrift.ServiceConstants.PrivilegeScope; |
| import org.apache.sentry.service.thrift.ServiceConstants.ServerConfig; |
| import org.datanucleus.store.rdbms.exceptions.MissingTableException; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.codahale.metrics.Gauge; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| |
| /** |
| * SentryStore is the data access object for Sentry data. Strings |
| * such as role and group names will be normalized to lowercase |
| * in addition to starting and ending whitespace. |
| */ |
| public class DbSentryStore implements SentryStore { |
| private static final UUID SERVER_UUID = UUID.randomUUID(); |
| private static final Logger LOGGER = LoggerFactory |
| .getLogger(SentryStore.class); |
| |
| public static String NULL_COL = "__NULL__"; |
| static final String DEFAULT_DATA_DIR = "sentry_policy_db"; |
| |
| private static final Set<String> ALL_ACTIONS = Sets.newHashSet(AccessConstants.ALL, |
| AccessConstants.SELECT, AccessConstants.INSERT, AccessConstants.ALTER, |
| AccessConstants.CREATE, AccessConstants.DROP, AccessConstants.INDEX, |
| AccessConstants.LOCK); |
| |
| // Now partial revoke just support action with SELECT,INSERT and ALL. |
| // e.g. If we REVOKE SELECT from a privilege with action ALL, it will leads to INSERT |
| // Otherwise, if we revoke other privilege(e.g. ALTER,DROP...), we will remove it from a role directly. |
| private static final Set<String> PARTIAL_REVOKE_ACTIONS = Sets.newHashSet(AccessConstants.ALL, |
| AccessConstants.ACTION_ALL.toLowerCase(), AccessConstants.SELECT, AccessConstants.INSERT); |
| |
| /** |
| * Commit order sequence id. This is used by notification handlers |
| * to know the order in which events where committed to the database. |
| * This instance variable is incremented in incrementGetSequenceId |
| * and read in commitUpdateTransaction. Synchronization on this |
| * is required to read commitSequenceId. |
| */ |
| private long commitSequenceId; |
| private final PersistenceManagerFactory pmf; |
| private Configuration conf; |
| private PrivCleaner privCleaner = null; |
| private Thread privCleanerThread = null; |
| |
| public DbSentryStore(Configuration conf) throws SentryNoSuchObjectException, |
| SentryAccessDeniedException { |
| commitSequenceId = 0; |
| this.conf = conf; |
| Properties prop = new Properties(); |
| prop.putAll(ServerConfig.SENTRY_STORE_DEFAULTS); |
| String jdbcUrl = conf.get(ServerConfig.SENTRY_STORE_JDBC_URL, "").trim(); |
| Preconditions.checkArgument(!jdbcUrl.isEmpty(), "Required parameter " + |
| ServerConfig.SENTRY_STORE_JDBC_URL + " missing"); |
| String user = conf.get(ServerConfig.SENTRY_STORE_JDBC_USER, ServerConfig. |
| SENTRY_STORE_JDBC_USER_DEFAULT).trim(); |
| String pass = conf.get(ServerConfig.SENTRY_STORE_JDBC_PASS, ServerConfig. |
| SENTRY_STORE_JDBC_PASS_DEFAULT).trim(); |
| String driverName = conf.get(ServerConfig.SENTRY_STORE_JDBC_DRIVER, |
| ServerConfig.SENTRY_STORE_JDBC_DRIVER_DEFAULT); |
| prop.setProperty(ServerConfig.JAVAX_JDO_URL, jdbcUrl); |
| prop.setProperty(ServerConfig.JAVAX_JDO_USER, user); |
| prop.setProperty(ServerConfig.JAVAX_JDO_PASS, pass); |
| prop.setProperty(ServerConfig.JAVAX_JDO_DRIVER_NAME, driverName); |
| for (Map.Entry<String, String> entry : conf) { |
| String key = entry.getKey(); |
| if (key.startsWith(ServerConfig.SENTRY_JAVAX_JDO_PROPERTY_PREFIX) || |
| key.startsWith(ServerConfig.SENTRY_DATANUCLEUS_PROPERTY_PREFIX)) { |
| key = StringUtils.removeStart(key, ServerConfig.SENTRY_DB_PROPERTY_PREFIX); |
| prop.setProperty(key, entry.getValue()); |
| } |
| } |
| |
| |
| boolean checkSchemaVersion = conf.get( |
| ServerConfig.SENTRY_VERIFY_SCHEM_VERSION, |
| ServerConfig.SENTRY_VERIFY_SCHEM_VERSION_DEFAULT).equalsIgnoreCase( |
| "true"); |
| if (!checkSchemaVersion) { |
| prop.setProperty("datanucleus.autoCreateSchema", "true"); |
| prop.setProperty("datanucleus.fixedDatastore", "false"); |
| } |
| |
| // Disallow operations outside of transactions |
| prop.setProperty("datanucleus.NontransactionalRead", "false"); |
| prop.setProperty("datanucleus.NontransactionalWrite", "false"); |
| |
| pmf = JDOHelper.getPersistenceManagerFactory(prop); |
| verifySentryStoreSchema(conf, checkSchemaVersion); |
| |
| // Kick off the thread that cleans orphaned privileges (unless told not to) |
| privCleaner = this.new PrivCleaner(); |
| if (conf.get(ServerConfig.SENTRY_STORE_ORPHANED_PRIVILEGE_REMOVAL, |
| ServerConfig.SENTRY_STORE_ORPHANED_PRIVILEGE_REMOVAL_DEFAULT) |
| .equalsIgnoreCase("true")) { |
| privCleanerThread = new Thread(privCleaner); |
| privCleanerThread.start(); |
| } |
| } |
| |
| // ensure that the backend DB schema is set |
| private void verifySentryStoreSchema(Configuration serverConf, |
| boolean checkVersion) |
| throws SentryNoSuchObjectException, SentryAccessDeniedException { |
| if (!checkVersion) { |
| setSentryVersion(SentryStoreSchemaInfo.getSentryVersion(), |
| "Schema version set implicitly"); |
| } else { |
| String currentVersion = getSentryVersion(); |
| if (!SentryStoreSchemaInfo.getSentryVersion().equals(currentVersion)) { |
| throw new SentryAccessDeniedException( |
| "The Sentry store schema version " + currentVersion |
| + " is different from distribution version " |
| + SentryStoreSchemaInfo.getSentryVersion()); |
| } |
| } |
| } |
| |
| public synchronized void stop() { |
| if (privCleanerThread != null) { |
| privCleaner.exit(); |
| try { |
| privCleanerThread.join(); |
| } catch (InterruptedException e) { |
| // Ignore... |
| } |
| } |
| if (pmf != null) { |
| pmf.close(); |
| } |
| } |
| |
| /** |
| * PersistenceManager object and Transaction object have a one to one |
| * correspondence. Each PersistenceManager object is associated with a |
| * transaction object and vice versa. Hence we create a persistence manager |
| * instance when we create a new transaction. We create a new transaction |
| * for every store API since we want that unit of work to behave as a |
| * transaction. |
| * |
| * Note that there's only one instance of PersistenceManagerFactory object |
| * for the service. |
| * |
| * Synchronized because we obtain persistence manager |
| */ |
| public synchronized PersistenceManager openTransaction() { |
| PersistenceManager pm = pmf.getPersistenceManager(); |
| Transaction currentTransaction = pm.currentTransaction(); |
| currentTransaction.begin(); |
| return pm; |
| } |
| |
| /** |
| * Synchronized due to sequence id generation |
| */ |
| public synchronized CommitContext commitUpdateTransaction(PersistenceManager pm) { |
| commitTransaction(pm); |
| return new CommitContext(SERVER_UUID, incrementGetSequenceId()); |
| } |
| |
| /** |
| * Increments commitSequenceId which should not be modified outside |
| * this method. |
| * |
| * @return sequence id |
| */ |
| private synchronized long incrementGetSequenceId() { |
| return ++commitSequenceId; |
| } |
| |
| public void commitTransaction(PersistenceManager pm) { |
| Transaction currentTransaction = pm.currentTransaction(); |
| try { |
| Preconditions.checkState(currentTransaction.isActive(), "Transaction is not active"); |
| currentTransaction.commit(); |
| } finally { |
| pm.close(); |
| } |
| } |
| |
| public void rollbackTransaction(PersistenceManager pm) { |
| if (pm == null || pm.isClosed()) { |
| return; |
| } |
| Transaction currentTransaction = pm.currentTransaction(); |
| if (currentTransaction.isActive()) { |
| try { |
| currentTransaction.rollback(); |
| } finally { |
| pm.close(); |
| } |
| } |
| } |
| /** |
| Get the MSentry object from roleName |
| Note: Should be called inside a transaction |
| */ |
| public MSentryRole getMSentryRole(PersistenceManager pm, String roleName) { |
| Query query = pm.newQuery(MSentryRole.class); |
| query.setFilter("this.roleName == t"); |
| query.declareParameters("java.lang.String t"); |
| query.setUnique(true); |
| MSentryRole sentryRole = (MSentryRole) query.execute(roleName); |
| return sentryRole; |
| } |
| |
| /** |
| * Normalize the string values |
| */ |
| private String trimAndLower(String input) { |
| return input.trim().toLowerCase(); |
| } |
| /** |
| * Create a sentry role and persist it. |
| * @param roleName: Name of the role being persisted |
| * @returns commit context used for notification handlers |
| * @throws SentryAlreadyExistsException |
| */ |
| public CommitContext createSentryRole(String roleName) |
| throws SentryAlreadyExistsException { |
| roleName = trimAndLower(roleName); |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| try { |
| pm = openTransaction(); |
| MSentryRole mSentryRole = getMSentryRole(pm, roleName); |
| if (mSentryRole == null) { |
| MSentryRole mRole = new MSentryRole(roleName, System.currentTimeMillis()); |
| pm.makePersistent(mRole); |
| CommitContext commit = commitUpdateTransaction(pm); |
| rollbackTransaction = false; |
| return commit; |
| } else { |
| throw new SentryAlreadyExistsException("Role: " + roleName); |
| } |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| private <T> Long getCount(Class<T> tClass) { |
| PersistenceManager pm = null; |
| Long size = new Long(-1); |
| try { |
| pm = openTransaction(); |
| Query query = pm.newQuery(); |
| query.setClass(tClass); |
| query.setResult("count(this)"); |
| size = (Long)query.execute(); |
| |
| } finally { |
| commitTransaction(pm); |
| } |
| return size; |
| } |
| public Gauge<Long> getRoleCountGauge() { |
| return new Gauge< Long >() { |
| @Override |
| public Long getValue() { |
| return getCount(MSentryRole.class); |
| } |
| }; |
| } |
| |
| public Gauge<Long> getPrivilegeCountGauge() { |
| return new Gauge< Long >() { |
| @Override |
| public Long getValue() { |
| return getCount(MSentryPrivilege.class); |
| } |
| }; |
| } |
| public Gauge<Long> getGroupCountGauge() { |
| return new Gauge< Long >() { |
| @Override |
| public Long getValue() { |
| return getCount(MSentryGroup.class); |
| } |
| }; |
| } |
| |
| /** |
| * Lets the test code know how many privs are in the db, so that we know |
| * if they are in fact being cleaned up when not being referenced any more. |
| * @return The number of rows in the db priv table. |
| */ |
| @VisibleForTesting |
| long countMSentryPrivileges() { |
| return getCount(MSentryPrivilege.class); |
| } |
| |
| public CommitContext alterSentryRoleGrantPrivilege(String grantorPrincipal, |
| String roleName, TSentryPrivilege privilege) |
| throws SentryUserException { |
| return alterSentryRoleGrantPrivileges(grantorPrincipal, |
| roleName, Sets.newHashSet(privilege)); |
| } |
| |
| public CommitContext alterSentryRoleGrantPrivileges(String grantorPrincipal, |
| String roleName, Set<TSentryPrivilege> privileges) |
| throws SentryUserException { |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| roleName = trimAndLower(roleName); |
| try { |
| pm = openTransaction(); |
| for (TSentryPrivilege privilege : privileges) { |
| // first do grant check |
| grantOptionCheck(pm, grantorPrincipal, privilege); |
| |
| MSentryPrivilege mPrivilege = alterSentryRoleGrantPrivilegeCore(pm, roleName, privilege); |
| |
| if (mPrivilege != null) { |
| convertToTSentryPrivilege(mPrivilege, privilege); |
| } |
| } |
| CommitContext commit = commitUpdateTransaction(pm); |
| rollbackTransaction = false; |
| return commit; |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| private MSentryPrivilege alterSentryRoleGrantPrivilegeCore(PersistenceManager pm, |
| String roleName, TSentryPrivilege privilege) |
| throws SentryNoSuchObjectException, SentryInvalidInputException { |
| MSentryPrivilege mPrivilege = null; |
| MSentryRole mRole = getMSentryRole(pm, roleName); |
| if (mRole == null) { |
| throw new SentryNoSuchObjectException("Role: " + roleName); |
| } else { |
| |
| if ((!isNULL(privilege.getColumnName())) || (!isNULL(privilege.getTableName())) |
| || (!isNULL(privilege.getDbName()))) { |
| // If Grant is for ALL and Either INSERT/SELECT already exists.. |
| // need to remove it and GRANT ALL.. |
| if (privilege.getAction().equalsIgnoreCase("*")) { |
| TSentryPrivilege tNotAll = new TSentryPrivilege(privilege); |
| tNotAll.setAction(AccessConstants.SELECT); |
| MSentryPrivilege mSelect = getMSentryPrivilege(tNotAll, pm); |
| tNotAll.setAction(AccessConstants.INSERT); |
| MSentryPrivilege mInsert = getMSentryPrivilege(tNotAll, pm); |
| if ((mSelect != null) && (mRole.getPrivileges().contains(mSelect))) { |
| mSelect.removeRole(mRole); |
| privCleaner.incPrivRemoval(); |
| pm.makePersistent(mSelect); |
| } |
| if ((mInsert != null) && (mRole.getPrivileges().contains(mInsert))) { |
| mInsert.removeRole(mRole); |
| privCleaner.incPrivRemoval(); |
| pm.makePersistent(mInsert); |
| } |
| } else { |
| // If Grant is for Either INSERT/SELECT and ALL already exists.. |
| // do nothing.. |
| TSentryPrivilege tAll = new TSentryPrivilege(privilege); |
| tAll.setAction(AccessConstants.ALL); |
| MSentryPrivilege mAll = getMSentryPrivilege(tAll, pm); |
| if ((mAll != null) && (mRole.getPrivileges().contains(mAll))) { |
| return null; |
| } |
| } |
| } |
| |
| mPrivilege = getMSentryPrivilege(privilege, pm); |
| if (mPrivilege == null) { |
| mPrivilege = convertToMSentryPrivilege(privilege); |
| } |
| mPrivilege.appendRole(mRole); |
| pm.makePersistent(mRole); |
| pm.makePersistent(mPrivilege); |
| } |
| return mPrivilege; |
| } |
| |
| public CommitContext alterSentryRoleRevokePrivilege(String grantorPrincipal, |
| String roleName, TSentryPrivilege tPrivilege) throws SentryUserException { |
| return alterSentryRoleRevokePrivileges(grantorPrincipal, |
| roleName, Sets.newHashSet(tPrivilege)); |
| } |
| |
| public CommitContext alterSentryRoleRevokePrivileges(String grantorPrincipal, |
| String roleName, Set<TSentryPrivilege> tPrivileges) throws SentryUserException { |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| roleName = safeTrimLower(roleName); |
| try { |
| pm = openTransaction(); |
| for (TSentryPrivilege tPrivilege : tPrivileges) { |
| // first do revoke check |
| grantOptionCheck(pm, grantorPrincipal, tPrivilege); |
| |
| alterSentryRoleRevokePrivilegeCore(pm, roleName, tPrivilege); |
| } |
| |
| CommitContext commit = commitUpdateTransaction(pm); |
| rollbackTransaction = false; |
| return commit; |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| private void alterSentryRoleRevokePrivilegeCore(PersistenceManager pm, |
| String roleName, TSentryPrivilege tPrivilege) |
| throws SentryNoSuchObjectException, SentryInvalidInputException { |
| Query query = pm.newQuery(MSentryRole.class); |
| query.setFilter("this.roleName == t"); |
| query.declareParameters("java.lang.String t"); |
| query.setUnique(true); |
| MSentryRole mRole = (MSentryRole) query.execute(roleName); |
| if (mRole == null) { |
| throw new SentryNoSuchObjectException("Role: " + roleName); |
| } else { |
| query = pm.newQuery(MSentryPrivilege.class); |
| MSentryPrivilege mPrivilege = getMSentryPrivilege(tPrivilege, pm); |
| if (mPrivilege == null) { |
| mPrivilege = convertToMSentryPrivilege(tPrivilege); |
| } else { |
| mPrivilege = (MSentryPrivilege) pm.detachCopy(mPrivilege); |
| } |
| |
| Set<MSentryPrivilege> privilegeGraph = Sets.newHashSet(); |
| if (mPrivilege.getGrantOption() != null) { |
| privilegeGraph.add(mPrivilege); |
| } else { |
| MSentryPrivilege mTure = new MSentryPrivilege(mPrivilege); |
| mTure.setGrantOption(true); |
| privilegeGraph.add(mTure); |
| MSentryPrivilege mFalse = new MSentryPrivilege(mPrivilege); |
| mFalse.setGrantOption(false); |
| privilegeGraph.add(mFalse); |
| } |
| // Get the privilege graph |
| populateChildren(pm, Sets.newHashSet(roleName), mPrivilege, privilegeGraph); |
| for (MSentryPrivilege childPriv : privilegeGraph) { |
| revokePrivilegeFromRole(pm, tPrivilege, mRole, childPriv); |
| } |
| pm.makePersistent(mRole); |
| } |
| } |
| |
| /** |
| * Roles can be granted ALL, SELECT, and INSERT on tables. When |
| * a role has ALL and SELECT or INSERT are revoked, we need to remove the ALL |
| * privilege and add SELECT (INSERT was revoked) or INSERT (SELECT was revoked). |
| */ |
| private void revokePartial(PersistenceManager pm, |
| TSentryPrivilege requestedPrivToRevoke, MSentryRole mRole, |
| MSentryPrivilege currentPrivilege) throws SentryInvalidInputException { |
| MSentryPrivilege persistedPriv = getMSentryPrivilege(convertToTSentryPrivilege(currentPrivilege), pm); |
| if (persistedPriv == null) { |
| persistedPriv = convertToMSentryPrivilege(convertToTSentryPrivilege(currentPrivilege)); |
| } |
| |
| if (requestedPrivToRevoke.getAction().equalsIgnoreCase("ALL") || requestedPrivToRevoke.getAction().equalsIgnoreCase("*")) { |
| persistedPriv.removeRole(mRole); |
| privCleaner.incPrivRemoval(); |
| pm.makePersistent(persistedPriv); |
| } else if (requestedPrivToRevoke.getAction().equalsIgnoreCase(AccessConstants.SELECT) |
| && (!currentPrivilege.getAction().equalsIgnoreCase(AccessConstants.INSERT))) { |
| revokeRolePartial(pm, mRole, currentPrivilege, persistedPriv, AccessConstants.INSERT); |
| } else if (requestedPrivToRevoke.getAction().equalsIgnoreCase(AccessConstants.INSERT) |
| && (!currentPrivilege.getAction().equalsIgnoreCase(AccessConstants.SELECT))) { |
| revokeRolePartial(pm, mRole, currentPrivilege, persistedPriv, AccessConstants.SELECT); |
| } |
| } |
| |
| private void revokeRolePartial(PersistenceManager pm, MSentryRole mRole, |
| MSentryPrivilege currentPrivilege, MSentryPrivilege persistedPriv, String addAction) |
| throws SentryInvalidInputException { |
| // If table / URI, remove ALL |
| persistedPriv.removeRole(mRole); |
| privCleaner.incPrivRemoval(); |
| pm.makePersistent(persistedPriv); |
| |
| currentPrivilege.setAction(AccessConstants.ALL); |
| persistedPriv = getMSentryPrivilege(convertToTSentryPrivilege(currentPrivilege), pm); |
| if ((persistedPriv != null)&&(mRole.getPrivileges().contains(persistedPriv))) { |
| persistedPriv.removeRole(mRole); |
| privCleaner.incPrivRemoval(); |
| pm.makePersistent(persistedPriv); |
| |
| currentPrivilege.setAction(addAction); |
| persistedPriv = getMSentryPrivilege(convertToTSentryPrivilege(currentPrivilege), pm); |
| if (persistedPriv == null) { |
| persistedPriv = convertToMSentryPrivilege(convertToTSentryPrivilege(currentPrivilege)); |
| mRole.appendPrivilege(persistedPriv); |
| } |
| persistedPriv.appendRole(mRole); |
| pm.makePersistent(persistedPriv); |
| } |
| } |
| |
| /** |
| * Revoke privilege from role |
| */ |
| private void revokePrivilegeFromRole(PersistenceManager pm, TSentryPrivilege tPrivilege, |
| MSentryRole mRole, MSentryPrivilege mPrivilege) throws SentryInvalidInputException { |
| if (PARTIAL_REVOKE_ACTIONS.contains(mPrivilege.getAction())) { |
| // if this privilege is in {ALL,SELECT,INSERT} |
| // we will do partial revoke |
| revokePartial(pm, tPrivilege, mRole, mPrivilege); |
| } else { |
| // if this privilege is not ALL, SELECT nor INSERT, |
| // we will revoke it from role directly |
| mPrivilege.removeRole(mRole); |
| pm.makePersistent(mPrivilege); |
| } |
| } |
| |
| /** |
| * Explore Privilege graph and collect child privileges. |
| * The responsibility to commit/rollback the transaction should be handled by the caller. |
| */ |
| private void populateChildren(PersistenceManager pm, Set<String> roleNames, MSentryPrivilege priv, |
| Set<MSentryPrivilege> children) throws SentryInvalidInputException { |
| Preconditions.checkNotNull(pm); |
| if ((!isNULL(priv.getServerName())) || (!isNULL(priv.getDbName())) |
| || (!isNULL(priv.getTableName()))) { |
| // Get all TableLevel Privs |
| Set<MSentryPrivilege> childPrivs = getChildPrivileges(pm, roleNames, priv); |
| for (MSentryPrivilege childPriv : childPrivs) { |
| // Only recurse for table level privs.. |
| if ((!isNULL(childPriv.getDbName())) && (!isNULL(childPriv.getTableName())) |
| && (!isNULL(childPriv.getColumnName()))) { |
| populateChildren(pm, roleNames, childPriv, children); |
| } |
| // The method getChildPrivileges() didn't do filter on "action", |
| // if the action is not "All", it should judge the action of children privilege. |
| // For example: a user has a privilege “All on Col1”, |
| // if the operation is “REVOKE INSERT on table” |
| // the privilege should be the child of table level privilege. |
| // but the privilege may still have other meaning, likes "SELECT on Col1". |
| // and the privileges like "SELECT on Col1" should not be revoke. |
| if (!priv.isActionALL()) { |
| if (childPriv.isActionALL()) { |
| // If the child privilege is All, we should convert it to the same |
| // privilege with parent |
| childPriv.setAction(priv.getAction()); |
| } |
| // Only include privilege that imply the parent privilege. |
| if (!priv.implies(childPriv)) { |
| continue; |
| } |
| } |
| children.add(childPriv); |
| } |
| } |
| } |
| |
| private Set<MSentryPrivilege> getChildPrivileges(PersistenceManager pm, Set<String> roleNames, |
| MSentryPrivilege parent) throws SentryInvalidInputException { |
| // Column and URI do not have children |
| if ((!isNULL(parent.getColumnName())) || (!isNULL(parent.getURI()))) { |
| return new HashSet<MSentryPrivilege>(); |
| } |
| |
| 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 == \"" + rName.trim().toLowerCase() + "\""); |
| } |
| StringBuilder filters = new StringBuilder("roles.contains(role) " |
| + "&& (" + Joiner.on(" || ").join(rolesFiler) + ")"); |
| filters.append(" && serverName == \"" + parent.getServerName() + "\""); |
| if (!isNULL(parent.getDbName())) { |
| filters.append(" && dbName == \"" + parent.getDbName() + "\""); |
| if (!isNULL(parent.getTableName())) { |
| filters.append(" && tableName == \"" + parent.getTableName() + "\""); |
| filters.append(" && columnName != \"__NULL__\""); |
| } else { |
| filters.append(" && tableName != \"__NULL__\""); |
| } |
| } else { |
| filters.append(" && (dbName != \"__NULL__\" || URI != \"__NULL__\")"); |
| } |
| |
| query.setFilter(filters.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]); |
| privileges.add(priv); |
| } |
| return privileges; |
| } |
| |
| private List<MSentryPrivilege> getMSentryPrivileges(TSentryPrivilege tPriv, PersistenceManager pm) { |
| Query query = pm.newQuery(MSentryPrivilege.class); |
| StringBuilder filters = new StringBuilder("this.serverName == \"" |
| + toNULLCol(safeTrimLower(tPriv.getServerName())) + "\" "); |
| if (!isNULL(tPriv.getDbName())) { |
| filters.append("&& this.dbName == \"" + toNULLCol(safeTrimLower(tPriv.getDbName())) + "\" "); |
| if (!isNULL(tPriv.getTableName())) { |
| filters.append("&& this.tableName == \"" + toNULLCol(safeTrimLower(tPriv.getTableName())) + "\" "); |
| if (!isNULL(tPriv.getColumnName())) { |
| filters.append("&& this.columnName == \"" + toNULLCol(safeTrimLower(tPriv.getColumnName())) + "\" "); |
| } |
| } |
| } |
| // 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())) + "\""); |
| |
| query.setFilter(filters.toString()); |
| List<MSentryPrivilege> privileges = (List<MSentryPrivilege>) query.execute(); |
| return privileges; |
| } |
| |
| 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; |
| } |
| Object obj = query.execute(grantOption); |
| if (obj != null) |
| return (MSentryPrivilege) obj; |
| return null; |
| } |
| |
| public CommitContext dropSentryRole(String roleName) |
| throws SentryNoSuchObjectException { |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| roleName = roleName.trim().toLowerCase(); |
| try { |
| pm = openTransaction(); |
| Query query = pm.newQuery(MSentryRole.class); |
| query.setFilter("this.roleName == t"); |
| query.declareParameters("java.lang.String t"); |
| query.setUnique(true); |
| MSentryRole sentryRole = (MSentryRole) query.execute(roleName); |
| if (sentryRole == null) { |
| throw new SentryNoSuchObjectException("Role " + roleName); |
| } else { |
| pm.retrieve(sentryRole); |
| int numPrivs = sentryRole.getPrivileges().size(); |
| sentryRole.removePrivileges(); |
| //with SENTRY-398 generic model |
| sentryRole.removeGMPrivileges(); |
| privCleaner.incPrivRemoval(numPrivs); |
| pm.deletePersistent(sentryRole); |
| } |
| CommitContext commit = commitUpdateTransaction(pm); |
| rollbackTransaction = false; |
| return commit; |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| public CommitContext alterSentryRoleAddGroups( String grantorPrincipal, String roleName, |
| Set<TSentryGroup> groupNames) |
| throws SentryNoSuchObjectException { |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| roleName = roleName.trim().toLowerCase(); |
| try { |
| pm = openTransaction(); |
| Query query = pm.newQuery(MSentryRole.class); |
| query.setFilter("this.roleName == t"); |
| query.declareParameters("java.lang.String t"); |
| query.setUnique(true); |
| MSentryRole role = (MSentryRole) query.execute(roleName); |
| if (role == null) { |
| throw new SentryNoSuchObjectException("Role: " + roleName); |
| } else { |
| query = pm.newQuery(MSentryGroup.class); |
| query.setFilter("this.groupName == t"); |
| query.declareParameters("java.lang.String t"); |
| query.setUnique(true); |
| List<MSentryGroup> groups = Lists.newArrayList(); |
| for (TSentryGroup tGroup : groupNames) { |
| String groupName = tGroup.getGroupName().trim(); |
| MSentryGroup group = (MSentryGroup) query.execute(groupName); |
| if (group == null) { |
| group = new MSentryGroup(groupName, System.currentTimeMillis(), |
| Sets.newHashSet(role)); |
| } |
| group.appendRole(role); |
| groups.add(group); |
| } |
| pm.makePersistentAll(groups); |
| CommitContext commit = commitUpdateTransaction(pm); |
| rollbackTransaction = false; |
| return commit; |
| } |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| public CommitContext alterSentryRoleDeleteGroups(String roleName, |
| Set<TSentryGroup> groupNames) |
| throws SentryNoSuchObjectException { |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| roleName = roleName.trim().toLowerCase(); |
| try { |
| pm = openTransaction(); |
| Query query = pm.newQuery(MSentryRole.class); |
| query.setFilter("this.roleName == t"); |
| query.declareParameters("java.lang.String t"); |
| query.setUnique(true); |
| MSentryRole role = (MSentryRole) query.execute(roleName); |
| if (role == null) { |
| throw new SentryNoSuchObjectException("Role: " + roleName); |
| } else { |
| query = pm.newQuery(MSentryGroup.class); |
| query.setFilter("this.groupName == t"); |
| query.declareParameters("java.lang.String t"); |
| query.setUnique(true); |
| List<MSentryGroup> groups = Lists.newArrayList(); |
| for (TSentryGroup tGroup : groupNames) { |
| String groupName = tGroup.getGroupName().trim(); |
| MSentryGroup group = (MSentryGroup) query.execute(groupName); |
| if (group != null) { |
| group.removeRole(role); |
| groups.add(group); |
| } |
| } |
| pm.makePersistentAll(groups); |
| CommitContext commit = commitUpdateTransaction(pm); |
| rollbackTransaction = false; |
| return commit; |
| } |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| MSentryRole getMSentryRoleByName(String roleName) |
| throws SentryNoSuchObjectException { |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| roleName = roleName.trim().toLowerCase(); |
| try { |
| pm = openTransaction(); |
| Query query = pm.newQuery(MSentryRole.class); |
| query.setFilter("this.roleName == t"); |
| query.declareParameters("java.lang.String t"); |
| query.setUnique(true); |
| MSentryRole sentryRole = (MSentryRole) query.execute(roleName); |
| if (sentryRole == null) { |
| throw new SentryNoSuchObjectException("Role " + roleName); |
| } else { |
| pm.retrieve(sentryRole); |
| } |
| rollbackTransaction = false; |
| commitTransaction(pm); |
| return sentryRole; |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| private boolean hasAnyServerPrivileges(Set<String> roleNames, String serverName) { |
| if ((roleNames.size() == 0)||(roleNames == null)) return false; |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| try { |
| pm = openTransaction(); |
| 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 == \"" + rName.trim().toLowerCase() + "\""); |
| } |
| StringBuilder filters = new StringBuilder("roles.contains(role) " |
| + "&& (" + Joiner.on(" || ").join(rolesFiler) + ") "); |
| filters.append("&& serverName == \"" + serverName.trim().toLowerCase() + "\""); |
| query.setFilter(filters.toString()); |
| query.setResult("count(this)"); |
| |
| Long numPrivs = (Long) query.execute(); |
| rollbackTransaction = false; |
| commitTransaction(pm); |
| return (numPrivs > 0); |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| List<MSentryPrivilege> getMSentryPrivileges(Set<String> roleNames, TSentryAuthorizable authHierarchy) { |
| if ((roleNames.size() == 0)||(roleNames == null)) return new ArrayList<MSentryPrivilege>(); |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| try { |
| pm = openTransaction(); |
| 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 == \"" + rName.trim().toLowerCase() + "\""); |
| } |
| StringBuilder filters = new StringBuilder("roles.contains(role) " |
| + "&& (" + Joiner.on(" || ").join(rolesFiler) + ") "); |
| if ((authHierarchy != null) && (authHierarchy.getServer() != null)) { |
| filters.append("&& serverName == \"" + authHierarchy.getServer().toLowerCase() + "\""); |
| if (authHierarchy.getDb() != null) { |
| filters.append(" && ((dbName == \"" + authHierarchy.getDb().toLowerCase() + "\") || (dbName == \"__NULL__\")) && (URI == \"__NULL__\")"); |
| if ((authHierarchy.getTable() != null) |
| && !AccessConstants.ALL |
| .equalsIgnoreCase(authHierarchy.getTable())) { |
| filters.append(" && ((tableName == \"" + authHierarchy.getTable().toLowerCase() + "\") || (tableName == \"__NULL__\")) && (URI == \"__NULL__\")"); |
| if ((authHierarchy.getColumn() != null) |
| && !AccessConstants.ALL |
| .equalsIgnoreCase(authHierarchy.getColumn())) { |
| filters.append(" && ((columnName == \"" + authHierarchy.getColumn().toLowerCase() + "\") || (columnName == \"__NULL__\")) && (URI == \"__NULL__\")"); |
| } |
| } |
| } |
| if (authHierarchy.getUri() != null) { |
| filters.append(" && ((URI != \"__NULL__\") && (\"" + authHierarchy.getUri() + "\".startsWith(URI)) || (URI == \"__NULL__\")) && (dbName == \"__NULL__\")"); |
| } |
| } |
| query.setFilter(filters.toString()); |
| List<MSentryPrivilege> privileges = (List<MSentryPrivilege>) query.execute(); |
| rollbackTransaction = false; |
| commitTransaction(pm); |
| return privileges; |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| List<MSentryPrivilege> getMSentryPrivilegesByAuth(Set<String> roleNames, TSentryAuthorizable authHierarchy) { |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| try { |
| pm = openTransaction(); |
| Query query = pm.newQuery(MSentryPrivilege.class); |
| StringBuilder filters = new StringBuilder(); |
| if ((roleNames.size() == 0)||(roleNames == null)) { |
| filters.append(" !roles.isEmpty() "); |
| } else { |
| 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 == \"" + rName.trim().toLowerCase() + "\""); |
| } |
| filters.append("roles.contains(role) " |
| + "&& (" + Joiner.on(" || ").join(rolesFiler) + ") "); |
| } |
| if ((authHierarchy.getServer() != null)) { |
| filters.append("&& serverName == \"" + |
| authHierarchy.getServer().toLowerCase() + "\""); |
| if (authHierarchy.getDb() != null) { |
| filters.append(" && (dbName == \"" + |
| authHierarchy.getDb().toLowerCase() + "\") && (URI == \"__NULL__\")"); |
| if (authHierarchy.getTable() != null) { |
| filters.append(" && (tableName == \"" + |
| authHierarchy.getTable().toLowerCase() + "\")"); |
| } else { |
| filters.append(" && (tableName == \"__NULL__\")"); |
| } |
| } else if (authHierarchy.getUri() != null) { |
| filters.append(" && (URI != \"__NULL__\") && (\"" + authHierarchy.getUri() + |
| "\".startsWith(URI)) && (dbName == \"__NULL__\")"); |
| } else { |
| filters.append(" && (dbName == \"__NULL__\") && (URI == \"__NULL__\")"); |
| } |
| } else { |
| // if no server, then return empty resultset |
| return new ArrayList<MSentryPrivilege>(); |
| } |
| FetchGroup grp = pm.getFetchGroup( |
| org.apache.sentry.provider.db.service.model.MSentryPrivilege.class, |
| "fetchRole"); |
| grp.addMember("roles"); |
| pm.getFetchPlan().addGroup("fetchRole"); |
| query.setFilter(filters.toString()); |
| List<MSentryPrivilege> privileges = (List<MSentryPrivilege>) query.execute(); |
| rollbackTransaction = false; |
| commitTransaction(pm); |
| return privileges; |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| public TSentryPrivilegeMap listSentryPrivilegesByAuthorizable( |
| Set<String> groups, TSentryActiveRoleSet activeRoles, |
| TSentryAuthorizable authHierarchy, boolean isAdmin) |
| throws SentryInvalidInputException { |
| Map<String, Set<TSentryPrivilege>> resultPrivilegeMap = Maps.newTreeMap(); |
| Set<String> roles = Sets.newHashSet(); |
| if (groups != null && !groups.isEmpty()) { |
| roles = getRolesToQuery(groups, new TSentryActiveRoleSet(true, null)); |
| } |
| if (activeRoles != null && !activeRoles.isAll()) { |
| // need to check/convert to lowercase here since this is from user input |
| for (String aRole : activeRoles.getRoles()) { |
| roles.add(aRole.toLowerCase()); |
| } |
| } |
| |
| // An empty 'roles' is a treated as a wildcard (in case of admin role).. |
| // so if not admin, don't return anything if 'roles' is empty.. |
| if (isAdmin || !roles.isEmpty()) { |
| List<MSentryPrivilege> mSentryPrivileges = getMSentryPrivilegesByAuth(roles, |
| authHierarchy); |
| for (MSentryPrivilege priv : mSentryPrivileges) { |
| for (MSentryRole role : priv.getRoles()) { |
| TSentryPrivilege tPriv = convertToTSentryPrivilege(priv); |
| if (resultPrivilegeMap.containsKey(role.getRoleName())) { |
| resultPrivilegeMap.get(role.getRoleName()).add(tPriv); |
| } else { |
| Set<TSentryPrivilege> tPrivSet = Sets.newTreeSet(); |
| tPrivSet.add(tPriv); |
| resultPrivilegeMap.put(role.getRoleName(), tPrivSet); |
| } |
| } |
| } |
| } |
| return new TSentryPrivilegeMap(resultPrivilegeMap); |
| } |
| |
| private Set<MSentryPrivilege> getMSentryPrivilegesByRoleName(String roleName) |
| throws SentryNoSuchObjectException { |
| MSentryRole mSentryRole = getMSentryRoleByName(roleName); |
| return mSentryRole.getPrivileges(); |
| } |
| |
| /** |
| * Gets sentry privilege objects for a given roleName from the persistence layer |
| * @param roleName : roleName to look up |
| * @return : Set of thrift sentry privilege objects |
| * @throws SentryNoSuchObjectException |
| */ |
| |
| public Set<TSentryPrivilege> getAllTSentryPrivilegesByRoleName(String roleName) |
| throws SentryNoSuchObjectException { |
| return convertToTSentryPrivileges(getMSentryPrivilegesByRoleName(roleName)); |
| } |
| |
| |
| /** |
| * Gets sentry privilege objects for criteria from the persistence layer |
| * @param roleNames : roleNames to look up (required) |
| * @param authHierarchy : filter push down based on auth hierarchy (optional) |
| * @return : Set of thrift sentry privilege objects |
| * @throws SentryNoSuchObjectException |
| */ |
| |
| public Set<TSentryPrivilege> getTSentryPrivileges(Set<String> roleNames, TSentryAuthorizable authHierarchy) throws SentryInvalidInputException { |
| if (authHierarchy.getServer() == null) { |
| throw new SentryInvalidInputException("serverName cannot be null !!"); |
| } |
| if ((authHierarchy.getTable() != null) && (authHierarchy.getDb() == null)) { |
| throw new SentryInvalidInputException("dbName cannot be null when tableName is present !!"); |
| } |
| if ((authHierarchy.getColumn() != null) && (authHierarchy.getTable() == null)) { |
| throw new SentryInvalidInputException("tableName cannot be null when columnName is present !!"); |
| } |
| if ((authHierarchy.getUri() == null) && (authHierarchy.getDb() == null)) { |
| throw new SentryInvalidInputException("One of uri or dbName must not be null !!"); |
| } |
| return convertToTSentryPrivileges(getMSentryPrivileges(roleNames, authHierarchy)); |
| } |
| |
| |
| private Set<MSentryRole> getMSentryRolesByGroupName(String groupName) |
| throws SentryNoSuchObjectException { |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| try { |
| Set<MSentryRole> roles; |
| pm = openTransaction(); |
| |
| //If no group name was specified, return all roles |
| if (groupName == null) { |
| Query query = pm.newQuery(MSentryRole.class); |
| roles = new HashSet<MSentryRole>((List<MSentryRole>)query.execute()); |
| } else { |
| Query query = pm.newQuery(MSentryGroup.class); |
| MSentryGroup sentryGroup; |
| groupName = groupName.trim(); |
| query.setFilter("this.groupName == t"); |
| query.declareParameters("java.lang.String t"); |
| query.setUnique(true); |
| sentryGroup = (MSentryGroup) query.execute(groupName); |
| if (sentryGroup == null) { |
| throw new SentryNoSuchObjectException("Group " + groupName); |
| } else { |
| pm.retrieve(sentryGroup); |
| } |
| roles = sentryGroup.getRoles(); |
| } |
| for ( MSentryRole role: roles) { |
| pm.retrieve(role); |
| } |
| commitTransaction(pm); |
| rollbackTransaction = false; |
| return roles; |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| /** |
| * Gets sentry role objects for a given groupName from the persistence layer |
| * @param groupName : groupName to look up ( if null returns all roles for all groups) |
| * @return : Set of thrift sentry role objects |
| * @throws SentryNoSuchObjectException |
| */ |
| public Set<TSentryRole> getTSentryRolesByGroupName(Set<String> groupNames, |
| boolean checkAllGroups) throws SentryNoSuchObjectException { |
| Set<MSentryRole> roleSet = Sets.newHashSet(); |
| for (String groupName : groupNames) { |
| try { |
| roleSet.addAll(getMSentryRolesByGroupName(groupName)); |
| } catch (SentryNoSuchObjectException e) { |
| // if we are checking for all the given groups, then continue searching |
| if (!checkAllGroups) { |
| throw e; |
| } |
| } |
| } |
| return convertToTSentryRoles(roleSet); |
| } |
| |
| public Set<String> getRoleNamesForGroups(Set<String> groups) { |
| Set<String> result = new HashSet<String>(); |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| try { |
| pm = openTransaction(); |
| Query query = pm.newQuery(MSentryGroup.class); |
| query.setFilter("this.groupName == t"); |
| query.declareParameters("java.lang.String t"); |
| query.setUnique(true); |
| for (String group : groups) { |
| MSentryGroup sentryGroup = (MSentryGroup) query.execute(group.trim()); |
| if (sentryGroup != null) { |
| for (MSentryRole role : sentryGroup.getRoles()) { |
| result.add(role.getRoleName()); |
| } |
| } |
| } |
| rollbackTransaction = false; |
| commitTransaction(pm); |
| return result; |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| public Set<MSentryRole> getRolesForGroups(PersistenceManager pm, Set<String> groups) { |
| Set<MSentryRole> result = new HashSet<MSentryRole>(); |
| Query query = pm.newQuery(MSentryGroup.class); |
| query.setFilter("this.groupName == t"); |
| query.declareParameters("java.lang.String t"); |
| query.setUnique(true); |
| for (String group : groups) { |
| MSentryGroup sentryGroup = (MSentryGroup) query.execute(group.trim()); |
| if (sentryGroup != null) { |
| result.addAll(sentryGroup.getRoles()); |
| } |
| } |
| return result; |
| } |
| |
| public Set<String> listAllSentryPrivilegesForProvider(Set<String> groups, TSentryActiveRoleSet roleSet) throws SentryInvalidInputException { |
| return listSentryPrivilegesForProvider(groups, roleSet, null); |
| } |
| |
| |
| public Set<String> listSentryPrivilegesForProvider(Set<String> groups, |
| TSentryActiveRoleSet roleSet, TSentryAuthorizable authHierarchy) throws SentryInvalidInputException { |
| Set<String> result = Sets.newHashSet(); |
| Set<String> rolesToQuery = getRolesToQuery(groups, roleSet); |
| List<MSentryPrivilege> mSentryPrivileges = getMSentryPrivileges(rolesToQuery, authHierarchy); |
| |
| for (MSentryPrivilege priv : mSentryPrivileges) { |
| result.add(toAuthorizable(priv)); |
| } |
| |
| return result; |
| } |
| |
| |
| public boolean hasAnyServerPrivileges(Set<String> groups, TSentryActiveRoleSet roleSet, String server) { |
| Set<String> rolesToQuery = getRolesToQuery(groups, roleSet); |
| return hasAnyServerPrivileges(rolesToQuery, server); |
| } |
| |
| |
| |
| private Set<String> getRolesToQuery(Set<String> groups, |
| TSentryActiveRoleSet roleSet) { |
| Set<String> activeRoleNames = toTrimedLower(roleSet.getRoles()); |
| |
| Set<String> roleNamesForGroups = toTrimedLower(getRoleNamesForGroups(groups)); |
| Set<String> rolesToQuery = roleSet.isAll() ? roleNamesForGroups : Sets.intersection(activeRoleNames, roleNamesForGroups); |
| return rolesToQuery; |
| } |
| |
| @VisibleForTesting |
| static String toAuthorizable(MSentryPrivilege privilege) { |
| List<String> authorizable = new ArrayList<String>(4); |
| authorizable.add(KV_JOINER.join(AuthorizableType.Server.name().toLowerCase(), |
| privilege.getServerName())); |
| if (isNULL(privilege.getURI())) { |
| if (!isNULL(privilege.getDbName())) { |
| authorizable.add(KV_JOINER.join(AuthorizableType.Db.name().toLowerCase(), |
| privilege.getDbName())); |
| if (!isNULL(privilege.getTableName())) { |
| authorizable.add(KV_JOINER.join(AuthorizableType.Table.name().toLowerCase(), |
| privilege.getTableName())); |
| if (!isNULL(privilege.getColumnName())) { |
| authorizable.add(KV_JOINER.join(AuthorizableType.Column.name().toLowerCase(), |
| privilege.getColumnName())); |
| } |
| } |
| } |
| } else { |
| authorizable.add(KV_JOINER.join(AuthorizableType.URI.name().toLowerCase(), |
| privilege.getURI())); |
| } |
| if (!isNULL(privilege.getAction()) |
| && !privilege.getAction().equalsIgnoreCase(AccessConstants.ALL)) { |
| authorizable |
| .add(KV_JOINER.join(ProviderConstants.PRIVILEGE_NAME.toLowerCase(), |
| privilege.getAction())); |
| } |
| return AUTHORIZABLE_JOINER.join(authorizable); |
| } |
| |
| @VisibleForTesting |
| static Set<String> toTrimedLower(Set<String> s) { |
| if (null == s) return new HashSet<String>(); |
| Set<String> result = Sets.newHashSet(); |
| for (String v : s) { |
| result.add(v.trim().toLowerCase()); |
| } |
| return result; |
| } |
| |
| |
| /** |
| * Converts model object(s) to thrift object(s). |
| * Additionally does normalization |
| * such as trimming whitespace and setting appropriate case. Also sets the create |
| * time. |
| */ |
| |
| private Set<TSentryPrivilege> convertToTSentryPrivileges(Collection<MSentryPrivilege> mSentryPrivileges) { |
| Set<TSentryPrivilege> privileges = new HashSet<TSentryPrivilege>(); |
| for(MSentryPrivilege mSentryPrivilege:mSentryPrivileges) { |
| privileges.add(convertToTSentryPrivilege(mSentryPrivilege)); |
| } |
| return privileges; |
| } |
| |
| private Set<TSentryRole> convertToTSentryRoles(Set<MSentryRole> mSentryRoles) { |
| Set<TSentryRole> roles = new HashSet<TSentryRole>(); |
| for(MSentryRole mSentryRole:mSentryRoles) { |
| roles.add(convertToTSentryRole(mSentryRole)); |
| } |
| return roles; |
| } |
| |
| private TSentryRole convertToTSentryRole(MSentryRole mSentryRole) { |
| TSentryRole role = new TSentryRole(); |
| role.setRoleName(mSentryRole.getRoleName()); |
| role.setGrantorPrincipal("--"); |
| Set<TSentryGroup> sentryGroups = new HashSet<TSentryGroup>(); |
| for(MSentryGroup mSentryGroup:mSentryRole.getGroups()) { |
| TSentryGroup group = convertToTSentryGroup(mSentryGroup); |
| sentryGroups.add(group); |
| } |
| |
| role.setGroups(sentryGroups); |
| return role; |
| } |
| |
| private TSentryGroup convertToTSentryGroup(MSentryGroup mSentryGroup) { |
| TSentryGroup group = new TSentryGroup(); |
| group.setGroupName(mSentryGroup.getGroupName()); |
| return group; |
| } |
| |
| private TSentryPrivilege convertToTSentryPrivilege(MSentryPrivilege mSentryPrivilege) { |
| TSentryPrivilege privilege = new TSentryPrivilege(); |
| convertToTSentryPrivilege(mSentryPrivilege, privilege); |
| return privilege; |
| } |
| |
| private void convertToTSentryPrivilege(MSentryPrivilege mSentryPrivilege, |
| TSentryPrivilege privilege) { |
| privilege.setCreateTime(mSentryPrivilege.getCreateTime()); |
| privilege.setAction(fromNULLCol(mSentryPrivilege.getAction())); |
| privilege.setPrivilegeScope(mSentryPrivilege.getPrivilegeScope()); |
| privilege.setServerName(fromNULLCol(mSentryPrivilege.getServerName())); |
| privilege.setDbName(fromNULLCol(mSentryPrivilege.getDbName())); |
| privilege.setTableName(fromNULLCol(mSentryPrivilege.getTableName())); |
| privilege.setColumnName(fromNULLCol(mSentryPrivilege.getColumnName())); |
| privilege.setURI(fromNULLCol(mSentryPrivilege.getURI())); |
| if (mSentryPrivilege.getGrantOption() != null) { |
| privilege.setGrantOption(TSentryGrantOption.valueOf(mSentryPrivilege.getGrantOption().toString().toUpperCase())); |
| } else { |
| privilege.setGrantOption(TSentryGrantOption.UNSET); |
| } |
| } |
| |
| /** |
| * Converts thrift object to model object. Additionally does normalization |
| * such as trimming whitespace and setting appropriate case. |
| * @throws SentryInvalidInputException |
| */ |
| private MSentryPrivilege convertToMSentryPrivilege(TSentryPrivilege privilege) |
| throws SentryInvalidInputException { |
| MSentryPrivilege mSentryPrivilege = new MSentryPrivilege(); |
| mSentryPrivilege.setServerName(toNULLCol(safeTrimLower(privilege.getServerName()))); |
| mSentryPrivilege.setDbName(toNULLCol(safeTrimLower(privilege.getDbName()))); |
| mSentryPrivilege.setTableName(toNULLCol(safeTrimLower(privilege.getTableName()))); |
| mSentryPrivilege.setColumnName(toNULLCol(safeTrimLower(privilege.getColumnName()))); |
| mSentryPrivilege.setPrivilegeScope(safeTrim(privilege.getPrivilegeScope())); |
| mSentryPrivilege.setAction(toNULLCol(safeTrimLower(privilege.getAction()))); |
| mSentryPrivilege.setCreateTime(System.currentTimeMillis()); |
| mSentryPrivilege.setURI(toNULLCol(safeTrim(privilege.getURI()))); |
| if ( !privilege.getGrantOption().equals(TSentryGrantOption.UNSET) ) { |
| mSentryPrivilege.setGrantOption(Boolean.valueOf(privilege.getGrantOption().toString())); |
| } else { |
| mSentryPrivilege.setGrantOption(null); |
| } |
| return mSentryPrivilege; |
| } |
| private static String safeTrim(String s) { |
| if (s == null) { |
| return null; |
| } |
| return s.trim(); |
| } |
| private static String safeTrimLower(String s) { |
| if (s == null) { |
| return null; |
| } |
| return s.trim().toLowerCase(); |
| } |
| |
| public String getSentryVersion() throws SentryNoSuchObjectException, |
| SentryAccessDeniedException { |
| MSentryVersion mVersion = getMSentryVersion(); |
| return mVersion.getSchemaVersion(); |
| } |
| |
| public void setSentryVersion(String newVersion, String verComment) |
| throws SentryNoSuchObjectException, SentryAccessDeniedException { |
| MSentryVersion mVersion; |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| |
| try { |
| mVersion = getMSentryVersion(); |
| if (newVersion.equals(mVersion.getSchemaVersion())) { |
| // specified version already in there |
| return; |
| } |
| } catch (SentryNoSuchObjectException e) { |
| // if the version doesn't exist, then create it |
| mVersion = new MSentryVersion(); |
| } |
| mVersion.setSchemaVersion(newVersion); |
| mVersion.setVersionComment(verComment); |
| try { |
| pm = openTransaction(); |
| pm.makePersistent(mVersion); |
| rollbackTransaction = false; |
| commitTransaction(pm); |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private MSentryVersion getMSentryVersion() |
| throws SentryNoSuchObjectException, SentryAccessDeniedException { |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| try { |
| pm = openTransaction(); |
| Query query = pm.newQuery(MSentryVersion.class); |
| List<MSentryVersion> mSentryVersions = (List<MSentryVersion>) query |
| .execute(); |
| pm.retrieveAll(mSentryVersions); |
| rollbackTransaction = false; |
| commitTransaction(pm); |
| if (mSentryVersions.isEmpty()) { |
| throw new SentryNoSuchObjectException("No matching version found"); |
| } |
| if (mSentryVersions.size() > 1) { |
| throw new SentryAccessDeniedException( |
| "Metastore contains multiple versions"); |
| } |
| return mSentryVersions.get(0); |
| } catch (JDODataStoreException e) { |
| if (e.getCause() instanceof MissingTableException) { |
| throw new SentryAccessDeniedException("Version table not found. " |
| + "The sentry store is not set or corrupt "); |
| } else { |
| throw e; |
| } |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| /** |
| * Drop given privilege from all roles |
| */ |
| public void dropPrivilege(TSentryAuthorizable tAuthorizable) |
| throws SentryNoSuchObjectException, SentryInvalidInputException { |
| PersistenceManager pm = null; |
| boolean rollbackTransaction = true; |
| |
| TSentryPrivilege tPrivilege = toSentryPrivilege(tAuthorizable); |
| try { |
| pm = openTransaction(); |
| |
| if (isMultiActionsSupported(tPrivilege)) { |
| for (String privilegeAction : ALL_ACTIONS) { |
| tPrivilege.setAction(privilegeAction); |
| dropPrivilegeForAllRoles(pm, new TSentryPrivilege(tPrivilege)); |
| } |
| } else { |
| dropPrivilegeForAllRoles(pm, new TSentryPrivilege(tPrivilege)); |
| } |
| rollbackTransaction = false; |
| commitTransaction(pm); |
| } catch (JDODataStoreException e) { |
| throw new SentryInvalidInputException("Failed to get privileges: " |
| + e.getMessage()); |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| /** |
| * Rename given privilege from all roles drop the old privilege and create the new one |
| * @param tAuthorizable |
| * @param newTAuthorizable |
| * @throws SentryNoSuchObjectException |
| * @throws SentryInvalidInputException |
| */ |
| public void renamePrivilege(TSentryAuthorizable tAuthorizable, |
| TSentryAuthorizable newTAuthorizable) |
| throws SentryNoSuchObjectException, SentryInvalidInputException { |
| PersistenceManager pm = null; |
| boolean rollbackTransaction = true; |
| |
| TSentryPrivilege tPrivilege = toSentryPrivilege(tAuthorizable); |
| TSentryPrivilege newPrivilege = toSentryPrivilege(newTAuthorizable); |
| |
| try { |
| pm = openTransaction(); |
| // In case of tables or DBs, check all actions |
| if (isMultiActionsSupported(tPrivilege)) { |
| for (String privilegeAction : ALL_ACTIONS) { |
| tPrivilege.setAction(privilegeAction); |
| newPrivilege.setAction(privilegeAction); |
| renamePrivilegeForAllRoles(pm, tPrivilege, newPrivilege); |
| } |
| } else { |
| renamePrivilegeForAllRoles(pm, tPrivilege, newPrivilege); |
| } |
| rollbackTransaction = false; |
| commitTransaction(pm); |
| } catch (JDODataStoreException e) { |
| throw new SentryInvalidInputException("Failed to get privileges: " |
| + e.getMessage()); |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| // Currently INSERT/SELECT/ALL are supported for Table and DB level privileges |
| private boolean isMultiActionsSupported(TSentryPrivilege tPrivilege) { |
| return tPrivilege.getDbName() != null; |
| |
| } |
| // wrapper for dropOrRename |
| private void renamePrivilegeForAllRoles(PersistenceManager pm, |
| TSentryPrivilege tPrivilege, |
| TSentryPrivilege newPrivilege) throws SentryNoSuchObjectException, |
| SentryInvalidInputException { |
| dropOrRenamePrivilegeForAllRoles(pm, tPrivilege, newPrivilege); |
| } |
| |
| /** |
| * Drop given privilege from all roles |
| * @param tPrivilege |
| * @throws SentryNoSuchObjectException |
| * @throws SentryInvalidInputException |
| */ |
| private void dropPrivilegeForAllRoles(PersistenceManager pm, |
| TSentryPrivilege tPrivilege) |
| throws SentryNoSuchObjectException, SentryInvalidInputException { |
| dropOrRenamePrivilegeForAllRoles(pm, tPrivilege, null); |
| } |
| |
| /** |
| * Drop given privilege from all roles Create the new privilege if asked |
| * @param tPrivilege |
| * @param pm |
| * @throws SentryNoSuchObjectException |
| * @throws SentryInvalidInputException |
| */ |
| private void dropOrRenamePrivilegeForAllRoles(PersistenceManager pm, |
| TSentryPrivilege tPrivilege, |
| TSentryPrivilege newTPrivilege) throws SentryNoSuchObjectException, |
| SentryInvalidInputException { |
| HashSet<MSentryRole> roleSet = Sets.newHashSet(); |
| |
| List<MSentryPrivilege> mPrivileges = getMSentryPrivileges(tPrivilege, pm); |
| if (mPrivileges != null && !mPrivileges.isEmpty()) { |
| for (MSentryPrivilege mPrivilege : mPrivileges) { |
| roleSet.addAll(ImmutableSet.copyOf((mPrivilege.getRoles()))); |
| } |
| } |
| |
| MSentryPrivilege parent = getMSentryPrivilege(tPrivilege, pm); |
| for (MSentryRole role : roleSet) { |
| // 1. get privilege and child privileges |
| Set<MSentryPrivilege> privilegeGraph = Sets.newHashSet(); |
| if (parent != null) { |
| privilegeGraph.add(parent); |
| populateChildren(pm, Sets.newHashSet(role.getRoleName()), parent, privilegeGraph); |
| } else { |
| populateChildren(pm, Sets.newHashSet(role.getRoleName()), convertToMSentryPrivilege(tPrivilege), |
| privilegeGraph); |
| } |
| // 2. revoke privilege and child privileges |
| alterSentryRoleRevokePrivilegeCore(pm, role.getRoleName(), tPrivilege); |
| // 3. add new privilege and child privileges with new tableName |
| if (newTPrivilege != null) { |
| for (MSentryPrivilege m : privilegeGraph) { |
| TSentryPrivilege t = convertToTSentryPrivilege(m); |
| if (newTPrivilege.getPrivilegeScope().equals(PrivilegeScope.DATABASE.name())) { |
| t.setDbName(newTPrivilege.getDbName()); |
| } else if (newTPrivilege.getPrivilegeScope().equals(PrivilegeScope.TABLE.name())) { |
| t.setTableName(newTPrivilege.getTableName()); |
| } |
| alterSentryRoleGrantPrivilegeCore(pm, role.getRoleName(), t); |
| } |
| } |
| } |
| } |
| |
| private TSentryPrivilege toSentryPrivilege(TSentryAuthorizable tAuthorizable) |
| throws SentryInvalidInputException { |
| TSentryPrivilege tSentryPrivilege = new TSentryPrivilege(); |
| tSentryPrivilege.setDbName(fromNULLCol(tAuthorizable.getDb())); |
| tSentryPrivilege.setServerName(fromNULLCol(tAuthorizable.getServer())); |
| tSentryPrivilege.setTableName(fromNULLCol(tAuthorizable.getTable())); |
| tSentryPrivilege.setColumnName(fromNULLCol(tAuthorizable.getColumn())); |
| tSentryPrivilege.setURI(fromNULLCol(tAuthorizable.getUri())); |
| PrivilegeScope scope; |
| if (!isNULL(tSentryPrivilege.getColumnName())) { |
| scope = PrivilegeScope.COLUMN; |
| } else if (!isNULL(tSentryPrivilege.getTableName())) { |
| scope = PrivilegeScope.TABLE; |
| } else if (!isNULL(tSentryPrivilege.getDbName())) { |
| scope = PrivilegeScope.DATABASE; |
| } else if (!isNULL(tSentryPrivilege.getURI())) { |
| scope = PrivilegeScope.URI; |
| } else { |
| scope = PrivilegeScope.SERVER; |
| } |
| tSentryPrivilege.setPrivilegeScope(scope.name()); |
| tSentryPrivilege.setAction(AccessConstants.ALL); |
| return tSentryPrivilege; |
| } |
| |
| public static String toNULLCol(String s) { |
| return Strings.isNullOrEmpty(s) ? NULL_COL : s; |
| } |
| |
| public static String fromNULLCol(String s) { |
| return isNULL(s) ? "" : s; |
| } |
| |
| public static boolean isNULL(String s) { |
| return Strings.isNullOrEmpty(s) || s.equals(NULL_COL); |
| } |
| |
| /** |
| * Grant option check |
| * @param pm |
| * @param privilege |
| * @throws SentryUserException |
| */ |
| private void grantOptionCheck(PersistenceManager pm, String grantorPrincipal, TSentryPrivilege privilege) |
| throws SentryUserException { |
| MSentryPrivilege mPrivilege = convertToMSentryPrivilege(privilege); |
| if (grantorPrincipal == null) { |
| throw new SentryInvalidInputException("grantorPrincipal should not be null"); |
| } |
| Set<String> groups = SentryPolicyStoreProcessor.getGroupsFromUserName(conf, grantorPrincipal); |
| if (groups == null || groups.isEmpty()) { |
| throw new SentryGrantDeniedException(grantorPrincipal |
| + " has no grant!"); |
| } |
| |
| // if grantor is in adminGroup, don't need to do check |
| Set<String> admins = getAdminGroups(); |
| boolean isAdminGroup = false; |
| if (admins != null && !admins.isEmpty()) { |
| for (String g : groups) { |
| if (admins.contains(g)) { |
| isAdminGroup = true; |
| break; |
| } |
| } |
| } |
| |
| if (!isAdminGroup) { |
| boolean hasGrant = false; |
| Set<MSentryRole> roles = getRolesForGroups(pm, groups); |
| if (roles != null && !roles.isEmpty()) { |
| for (MSentryRole role: roles) { |
| Set<MSentryPrivilege> privilegeSet = role.getPrivileges(); |
| if (privilegeSet != null && !privilegeSet.isEmpty()) { |
| // if role has a privilege p with grant option |
| // and mPrivilege is a child privilege of p |
| for (MSentryPrivilege p : privilegeSet) { |
| if (p.getGrantOption() && p.implies(mPrivilege)) { |
| hasGrant = true; |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| if (!hasGrant) { |
| throw new SentryGrantDeniedException(grantorPrincipal |
| + " has no grant!"); |
| } |
| } |
| } |
| |
| // get adminGroups from conf |
| private Set<String> getAdminGroups() { |
| return Sets.newHashSet(conf.getStrings( |
| ServerConfig.ADMIN_GROUPS, new String[]{})); |
| } |
| |
| /** |
| * This returns a Mapping of AuthZObj(db/table) -> (Role -> permission) |
| */ |
| public Map<String, HashMap<String, String>> retrieveFullPrivilegeImage() { |
| Map<String, HashMap<String, String>> retVal = new HashMap<String, HashMap<String,String>>(); |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| try { |
| pm = openTransaction(); |
| 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(); |
| rollbackTransaction = false; |
| 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()); |
| } |
| } |
| } |
| commitTransaction(pm); |
| return retVal; |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| /** |
| * This returns a Mapping of Role -> [Groups] |
| */ |
| public Map<String, LinkedList<String>> retrieveFullRoleImage() { |
| Map<String, LinkedList<String>> retVal = new HashMap<String, LinkedList<String>>(); |
| boolean rollbackTransaction = true; |
| PersistenceManager pm = null; |
| try { |
| pm = openTransaction(); |
| Query query = pm.newQuery(MSentryGroup.class); |
| List<MSentryGroup> groups = (List<MSentryGroup>) query.execute(); |
| for (MSentryGroup mGroup : groups) { |
| for (MSentryRole role : mGroup.getRoles()) { |
| LinkedList<String> rUpdate = retVal.get(role.getRoleName()); |
| if (rUpdate == null) { |
| rUpdate = new LinkedList<String>(); |
| retVal.put(role.getRoleName(), rUpdate); |
| } |
| rUpdate.add(mGroup.getGroupName()); |
| } |
| } |
| commitTransaction(pm); |
| return retVal; |
| } finally { |
| if (rollbackTransaction) { |
| rollbackTransaction(pm); |
| } |
| } |
| } |
| |
| // TODO : Fix this : START |
| |
| @Override |
| public Configuration getConfiguration() { |
| // TODO Auto-generated method stub |
| return null; |
| } |
| |
| @Override |
| public Set<String> getGroupsForRole(String roleName) { |
| // TODO Auto-generated method stub |
| return null; |
| } |
| |
| @Override |
| public long getRoleCount() { |
| // TODO Auto-generated method stub |
| return 0; |
| } |
| |
| @Override |
| public long getPrivilegeCount() { |
| // TODO Auto-generated method stub |
| return 0; |
| } |
| |
| @Override |
| public long getGroupCount() { |
| // TODO Auto-generated method stub |
| return 0; |
| } |
| |
| @Override |
| public TStoreSnapshot toSnapshot() { |
| // TODO Auto-generated method stub |
| return null; |
| } |
| |
| @Override |
| public void fromSnapshot(TStoreSnapshot snapshot) { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| // TODO : Fix this : END |
| |
| /** |
| * This thread exists to clean up "orphaned" privilege rows in the database. |
| * These rows aren't removed automatically due to the fact that there is |
| * a many-to-many mapping between the roles and privileges, and the |
| * detection and removal of orphaned privileges is a wee bit involved. |
| * This thread hangs out until notified by the parent (the outer class) |
| * and then runs a custom SQL statement that detects and removes orphans. |
| */ |
| private class PrivCleaner implements Runnable { |
| // Kick off priv orphan removal after this many notifies |
| private static final int NOTIFY_THRESHOLD = 50; |
| |
| // How many times we've been notified; reset to zero after orphan removal |
| private int currentNotifies = 0; |
| |
| // Internal state for threads |
| private boolean exitRequired = false; |
| |
| // This lock and condition are needed to implement a way to drop the |
| // lock inside a while loop, and not hold the lock across the orphan |
| // removal. |
| private final Lock lock = new ReentrantLock(); |
| private final Condition cond = lock.newCondition(); |
| |
| /** |
| * Waits in a loop, running the orphan removal function when notified. |
| * Will exit after exitRequired is set to true by exit(). We are careful |
| * to not hold our lock while removing orphans; that operation might |
| * take a long time. There's also the matter of lock ordering. Other |
| * threads start a transaction first, and then grab our lock; this thread |
| * grabs the lock and then starts a transaction. Handling this correctly |
| * requires explicit locking/unlocking through the loop. |
| */ |
| public void run() { |
| while (true) { |
| lock.lock(); |
| try { |
| // Check here in case this was set during removeOrphanedPrivileges() |
| if (exitRequired) { |
| return; |
| } |
| while (currentNotifies <= NOTIFY_THRESHOLD) { |
| try { |
| cond.await(); |
| } catch (InterruptedException e) { |
| // Interrupted |
| } |
| // Check here in case this was set while waiting |
| if (exitRequired) { |
| return; |
| } |
| } |
| currentNotifies = 0; |
| } finally { |
| lock.unlock(); |
| } |
| try { |
| removeOrphanedPrivileges(); |
| } catch (Exception e) { |
| LOGGER.warn("Privilege cleaning thread encountered an error: " + |
| e.getMessage()); |
| } |
| } |
| } |
| |
| /** |
| * This is called when a privilege is removed from a role. This may |
| * or may not mean that the privilege needs to be removed from the |
| * database; there may be more references to it from other roles. |
| * As a result, we'll lazily run the orphan cleaner every |
| * NOTIFY_THRESHOLD times this routine is called. |
| * @param numDeletions The number of potentially orphaned privileges |
| */ |
| public void incPrivRemoval(int numDeletions) { |
| if (privCleanerThread != null) { |
| lock.lock(); |
| currentNotifies += numDeletions; |
| if (currentNotifies > NOTIFY_THRESHOLD) { |
| cond.signal(); |
| } |
| lock.unlock(); |
| } |
| } |
| |
| /** |
| * Simple form of incPrivRemoval when only one privilege is deleted. |
| */ |
| public void incPrivRemoval() { |
| incPrivRemoval(1); |
| } |
| |
| /** |
| * Tell this thread to exit. Safe to call multiple times, as it just |
| * notifies the run() loop to finish up. |
| */ |
| public void exit() { |
| if (privCleanerThread != null) { |
| lock.lock(); |
| try { |
| exitRequired = true; |
| cond.signal(); |
| } finally { |
| lock.unlock(); |
| } |
| } |
| } |
| |
| /** |
| * Run a SQL query to detect orphaned privileges, and then delete |
| * each one. This is complicated by the fact that datanucleus does |
| * not seem to play well with the mix between a direct SQL query |
| * and operations on the database. The solution that seems to work |
| * is to split the operation into two transactions: the first is |
| * just a read for privileges that look like they're orphans, the |
| * second transaction will go and get each of those privilege objects, |
| * verify that there are no roles attached, and then delete them. |
| */ |
| private void removeOrphanedPrivileges() { |
| final String privDB = "SENTRY_DB_PRIVILEGE"; |
| final String privId = "DB_PRIVILEGE_ID"; |
| final String mapDB = "SENTRY_ROLE_DB_PRIVILEGE_MAP"; |
| final String privFilter = |
| "select " + privId + |
| " from " + privDB + " p" + |
| " where not exists (" + |
| " select 1 from " + mapDB + " d" + |
| " where p." + privId + " != d." + privId + |
| " )"; |
| boolean rollback = true; |
| int orphansRemoved = 0; |
| ArrayList<Object> idList = new ArrayList<Object>(); |
| PersistenceManager pm = pmf.getPersistenceManager(); |
| |
| // Transaction 1: Perform a SQL query to get things that look like orphans |
| try { |
| Transaction transaction = pm.currentTransaction(); |
| transaction.begin(); |
| transaction.setRollbackOnly(); // Makes the tx read-only |
| Query query = pm.newQuery("javax.jdo.query.SQL", privFilter); |
| query.setClass(MSentryPrivilege.class); |
| List<MSentryPrivilege> results = (List<MSentryPrivilege>) query.execute(); |
| for (MSentryPrivilege orphan : results) { |
| idList.add(pm.getObjectId(orphan)); |
| } |
| transaction.rollback(); |
| rollback = false; |
| } finally { |
| if (rollback && pm.currentTransaction().isActive()) { |
| pm.currentTransaction().rollback(); |
| } else { |
| LOGGER.debug("Found {} potential orphans", idList.size()); |
| } |
| } |
| |
| if (idList.isEmpty()) { |
| pm.close(); |
| return; |
| } |
| |
| Preconditions.checkState(!rollback); |
| |
| // Transaction 2: For each potential orphan, verify it's really an |
| // orphan and delete it if so |
| rollback = true; |
| try { |
| Transaction transaction = pm.currentTransaction(); |
| transaction.begin(); |
| pm.refreshAll(); // Try to ensure we really have correct objects |
| for (Object id : idList) { |
| MSentryPrivilege priv = (MSentryPrivilege) pm.getObjectById(id); |
| if (priv.getRoles().isEmpty()) { |
| pm.deletePersistent(priv); |
| orphansRemoved++; |
| } |
| } |
| transaction.commit(); |
| pm.close(); |
| rollback = false; |
| } finally { |
| if (rollback) { |
| rollbackTransaction(pm); |
| } else { |
| LOGGER.debug("Cleaned up {} orphaned privileges", orphansRemoved); |
| } |
| } |
| } |
| } |
| } |