Repository: incubator-sentry Updated Branches: refs/heads/master 0619d8a1a -> e3b77f06e
SENTRY-1119: Allow data engines to specify the ActionFactory from configuration(Bhooshan Mogal via Colin Ma, reviewed by Colin Ma, Sravya Tirukkovalur) Project: http://git-wip-us.apache.org/repos/asf/incubator-sentry/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-sentry/commit/e3b77f06 Tree: http://git-wip-us.apache.org/repos/asf/incubator-sentry/tree/e3b77f06 Diff: http://git-wip-us.apache.org/repos/asf/incubator-sentry/diff/e3b77f06 Branch: refs/heads/master Commit: e3b77f06eeb9f64a413a51df1294d7e881d315f9 Parents: 0619d8a Author: Colin Ma <co...@apache.org> Authored: Fri Mar 11 15:27:55 2016 +0800 Committer: Colin Ma <co...@apache.org> Committed: Fri Mar 11 15:27:55 2016 +0800 ---------------------------------------------------------------------- .../service/persistent/DelegateSentryStore.java | 2 +- .../persistent/PrivilegeOperatePersistence.java | 56 ++++- .../sentry/service/thrift/ServiceConstants.java | 4 + .../persistent/SentryStoreIntegrationBase.java | 2 +- .../TestPrivilegeOperatePersistence.java | 214 +++++++++++++++---- 5 files changed, 227 insertions(+), 51 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/e3b77f06/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/DelegateSentryStore.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/DelegateSentryStore.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/DelegateSentryStore.java index 74c52fa..d51b3ba 100644 --- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/DelegateSentryStore.java +++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/DelegateSentryStore.java @@ -68,7 +68,7 @@ public class DelegateSentryStore implements SentryStoreLayer { public DelegateSentryStore(Configuration conf) throws SentryNoSuchObjectException, SentryAccessDeniedException, SentryConfigurationException, IOException { - this.privilegeOperator = new PrivilegeOperatePersistence(); + this.privilegeOperator = new PrivilegeOperatePersistence(conf); // The generic model doesn't turn on the thread that cleans hive privileges conf.set(ServerConfig.SENTRY_STORE_ORPHANED_PRIVILEGE_REMOVAL,"false"); this.conf = conf; http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/e3b77f06/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeOperatePersistence.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeOperatePersistence.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeOperatePersistence.java index 21e51cd..9a3a505 100644 --- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeOperatePersistence.java +++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeOperatePersistence.java @@ -17,6 +17,7 @@ */ package org.apache.sentry.provider.db.generic.service.persistent; +import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -26,6 +27,7 @@ import java.util.Set; import javax.jdo.PersistenceManager; import javax.jdo.Query; +import org.apache.hadoop.conf.Configuration; import org.apache.sentry.SentryUserException; import org.apache.sentry.core.common.Action; import org.apache.sentry.core.common.Authorizable; @@ -41,18 +43,28 @@ import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import org.apache.sentry.service.thrift.ServiceConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class used do some operations related privilege and make the results * persistence */ public class PrivilegeOperatePersistence { + private static final Logger LOGGER = LoggerFactory.getLogger(PrivilegeOperatePersistence.class); private static final Map<String, BitFieldActionFactory> actionFactories = Maps.newHashMap(); static{ actionFactories.put("solr", new SearchActionFactory()); actionFactories.put("sqoop", new SqoopActionFactory()); } + private final Configuration conf; + + public PrivilegeOperatePersistence(Configuration conf) { + this.conf = conf; + } + public boolean checkPrivilegeOption(Set<MSentryRole> roles, PrivilegeObject privilege, PersistenceManager pm) { MSentryGMPrivilege requestPrivilege = convertToPrivilege(privilege); boolean hasGrant = false; @@ -418,7 +430,7 @@ public class PrivilegeOperatePersistence { } } - public static BitFieldAction getAction(String component, String name) { + private BitFieldAction getAction(String component, String name) { BitFieldActionFactory actionFactory = getActionFactory(component); BitFieldAction action = actionFactory.getActionByName(name); if (action == null) { @@ -427,10 +439,44 @@ public class PrivilegeOperatePersistence { return action; } - public static BitFieldActionFactory getActionFactory(String component) { - BitFieldActionFactory actionFactory = actionFactories.get(component.toLowerCase()); - if (actionFactory == null) { - throw new RuntimeException("can't get actionFactory for component:" + component); + private BitFieldActionFactory getActionFactory(String component) { + String caseInsensitiveComponent = component.toLowerCase(); + if (actionFactories.containsKey(caseInsensitiveComponent)) { + return actionFactories.get(caseInsensitiveComponent); + } + BitFieldActionFactory actionFactory = createActionFactory(caseInsensitiveComponent); + actionFactories.put(caseInsensitiveComponent, actionFactory); + LOGGER.info("Action factory for component {} not found in cache. Loaded it from configuration as {}.", + component, actionFactory.getClass().getName()); + return actionFactory; + } + + private BitFieldActionFactory createActionFactory(String component) { + String actionFactoryClassName = + conf.get(String.format(ServiceConstants.ServerConfig.SENTRY_COMPONENT_ACTION_FACTORY_FORMAT, component)); + if (actionFactoryClassName == null) { + throw new RuntimeException("ActionFactory not defined for component " + component + + ". Please define the parameter " + + "sentry." + component + ".action.factory in configuration"); + } + Class<?> actionFactoryClass; + try { + actionFactoryClass = Class.forName(actionFactoryClassName); + } catch (ClassNotFoundException e) { + throw new RuntimeException("ActionFactory class " + actionFactoryClassName + " not found."); + } + if (!BitFieldActionFactory.class.isAssignableFrom(actionFactoryClass)) { + throw new RuntimeException("ActionFactory class " + actionFactoryClassName + " must extend " + + BitFieldActionFactory.class.getName()); + } + BitFieldActionFactory actionFactory; + try { + Constructor<?> actionFactoryConstructor = actionFactoryClass.getDeclaredConstructor(); + actionFactoryConstructor.setAccessible(true); + actionFactory = (BitFieldActionFactory) actionFactoryClass.newInstance(); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException("Could not instantiate actionFactory " + actionFactoryClassName + + " for component: " + component, e); } return actionFactory; } http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/e3b77f06/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/ServiceConstants.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/ServiceConstants.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/ServiceConstants.java index 32d813c..94bd2a9 100644 --- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/ServiceConstants.java +++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/ServiceConstants.java @@ -171,7 +171,11 @@ public class ServiceConstants { // max message size for thrift messages public static String SENTRY_POLICY_SERVER_THRIFT_MAX_MESSAGE_SIZE = "sentry.policy.server.thrift.max.message.size"; public static long SENTRY_POLICY_SERVER_THRIFT_MAX_MESSAGE_SIZE_DEFAULT = 100 * 1024 * 1024; + + // action factories for external components + public static final String SENTRY_COMPONENT_ACTION_FACTORY_FORMAT = "sentry.%s.action.factory"; } + public static class ClientConfig { public static final ImmutableMap<String, String> SASL_PROPERTIES = ServiceConstants.SASL_PROPERTIES; public static final String SERVER_RPC_PORT = "sentry.service.client.server.rpc-port"; http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/e3b77f06/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/persistent/SentryStoreIntegrationBase.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/persistent/SentryStoreIntegrationBase.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/persistent/SentryStoreIntegrationBase.java index 915a929..f14b586 100644 --- a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/persistent/SentryStoreIntegrationBase.java +++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/persistent/SentryStoreIntegrationBase.java @@ -33,7 +33,7 @@ public abstract class SentryStoreIntegrationBase { protected final static String[] adminGroups = { "adminGroup" }; private static File dataDir; private static File policyFilePath; - private static Configuration conf; + protected static Configuration conf; protected static DelegateSentryStore sentryStore; protected static PolicyFile policyFile; http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/e3b77f06/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/persistent/TestPrivilegeOperatePersistence.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/persistent/TestPrivilegeOperatePersistence.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/persistent/TestPrivilegeOperatePersistence.java index 6b3a5e2..7541cb7 100644 --- a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/persistent/TestPrivilegeOperatePersistence.java +++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/persistent/TestPrivilegeOperatePersistence.java @@ -21,15 +21,23 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import com.google.common.collect.Lists; +import org.apache.hadoop.conf.Configuration; +import org.apache.sentry.SentryUserException; import org.apache.sentry.core.common.Authorizable; +import org.apache.sentry.core.common.BitFieldAction; +import org.apache.sentry.core.common.BitFieldActionFactory; import org.apache.sentry.core.model.search.Collection; import org.apache.sentry.core.model.search.Field; import org.apache.sentry.core.model.search.SearchConstants; +import org.apache.sentry.core.model.sqoop.SqoopActionConstant; import org.apache.sentry.provider.db.SentryGrantDeniedException; import org.apache.sentry.provider.db.generic.service.persistent.PrivilegeObject.Builder; import org.apache.sentry.provider.file.PolicyFile; +import org.apache.sentry.service.thrift.ServiceConstants; import org.junit.Before; import org.junit.Test; @@ -68,50 +76,7 @@ public class TestPrivilegeOperatePersistence extends SentryStoreIntegrationBase */ @Test public void testGrantPrivilege() throws Exception { - String roleName = "r1"; - /** - * grantor is admin, there is no need to check grant option - */ - String grantor = ADMIN_USER; - PrivilegeObject queryPrivilege = new Builder() - .setComponent(SEARCH) - .setAction(SearchConstants.QUERY) - .setService(SERVICE) - .setAuthorizables(Arrays.asList(new Collection(COLLECTION_NAME))) - .withGrantOption(null) - .build(); - - sentryStore.createRole(SEARCH, roleName, grantor); - sentryStore.alterRoleGrantPrivilege(SEARCH, roleName, queryPrivilege, grantor); - - assertEquals(Sets.newHashSet(queryPrivilege), - sentryStore.getPrivilegesByRole(SEARCH, Sets.newHashSet(roleName))); - - PrivilegeObject queryPrivilegeWithOption = new Builder() - .setComponent(SEARCH) - .setAction(SearchConstants.QUERY) - .setService(SERVICE) - .setAuthorizables(Arrays.asList(new Collection(COLLECTION_NAME))) - .withGrantOption(true) - .build(); - - sentryStore.alterRoleGrantPrivilege(SEARCH, roleName, queryPrivilegeWithOption, grantor); - - assertEquals(Sets.newHashSet(queryPrivilege, queryPrivilegeWithOption), - sentryStore.getPrivilegesByRole(SEARCH, Sets.newHashSet(roleName))); - - PrivilegeObject queryPrivilegeWithNoOption = new Builder() - .setComponent(SEARCH) - .setAction(SearchConstants.QUERY) - .setService(SERVICE) - .setAuthorizables(Arrays.asList(new Collection(COLLECTION_NAME))) - .withGrantOption(false) - .build(); - - sentryStore.alterRoleGrantPrivilege(SEARCH, roleName, queryPrivilegeWithNoOption, grantor); - - assertEquals(Sets.newHashSet(queryPrivilege, queryPrivilegeWithOption, queryPrivilegeWithNoOption), - sentryStore.getPrivilegesByRole(SEARCH, Sets.newHashSet(roleName))); + testGrantPrivilege(sentryStore, SEARCH); } @Test @@ -1008,4 +973,165 @@ public class TestPrivilegeOperatePersistence extends SentryStoreIntegrationBase assertEquals(2, sentryStore.getPrivilegesByAuthorizable(SEARCH, service1, Sets.newHashSet(roleName1,roleName2, roleName3), null).size()); } + + @Test(expected = RuntimeException.class) + public void testGrantPrivilegeExternalComponentMissingConf() throws SentryUserException { + testGrantPrivilege(sentryStore, "externalComponent"); + } + + @Test(expected = RuntimeException.class) + public void testGrantPrivilegeExternalComponentInvalidConf() throws Exception { + String externalComponent = "mycomponent"; + Configuration confCopy = new Configuration(conf); + confCopy.set(String.format(ServiceConstants.ServerConfig.SENTRY_COMPONENT_ACTION_FACTORY_FORMAT, externalComponent), + InvalidActionFactory.class.getName()); + SentryStoreLayer store = new DelegateSentryStore(confCopy); + testGrantPrivilege(store, externalComponent); + } + + @Test + public void testGrantPrivilegeExternalComponent() throws Exception { + String externalComponent = "mycomponent"; + Configuration confCopy = new Configuration(conf); + confCopy.set(String.format(ServiceConstants.ServerConfig.SENTRY_COMPONENT_ACTION_FACTORY_FORMAT, externalComponent), + MyComponentActionFactory.class.getName()); + SentryStoreLayer store = new DelegateSentryStore(confCopy); + testGrantPrivilege(store, externalComponent); + } + + @Test + public void testGrantPrivilegeExternalComponentCaseInsensitivity() throws Exception { + String externalComponent = "MyCoMpOnEnT"; + Configuration confCopy = new Configuration(conf); + confCopy.set(String.format(ServiceConstants.ServerConfig.SENTRY_COMPONENT_ACTION_FACTORY_FORMAT, "mycomponent"), + MyComponentActionFactory.class.getName()); + SentryStoreLayer store = new DelegateSentryStore(confCopy); + testGrantPrivilege(store, externalComponent); + } + + private void testGrantPrivilege(SentryStoreLayer sentryStore, String component) throws SentryUserException { + String roleName = "r1"; + /** + * grantor is admin, there is no need to check grant option + */ + String grantor = ADMIN_USER; + PrivilegeObject queryPrivilege = new Builder() + .setComponent(component) + .setAction(SearchConstants.QUERY) + .setService(SERVICE) + .setAuthorizables(Collections.singletonList(new Collection(COLLECTION_NAME))) + .withGrantOption(null) + .build(); + + sentryStore.createRole(component, roleName, grantor); + sentryStore.alterRoleGrantPrivilege(component, roleName, queryPrivilege, grantor); + + assertEquals(Sets.newHashSet(queryPrivilege), + sentryStore.getPrivilegesByRole(component, Sets.newHashSet(roleName))); + + PrivilegeObject queryPrivilegeWithOption = new Builder() + .setComponent(component) + .setAction(SearchConstants.QUERY) + .setService(SERVICE) + .setAuthorizables(Collections.singletonList(new Collection(COLLECTION_NAME))) + .withGrantOption(true) + .build(); + + sentryStore.alterRoleGrantPrivilege(component, roleName, queryPrivilegeWithOption, grantor); + + assertEquals(Sets.newHashSet(queryPrivilege, queryPrivilegeWithOption), + sentryStore.getPrivilegesByRole(component, Sets.newHashSet(roleName))); + + PrivilegeObject queryPrivilegeWithNoOption = new Builder() + .setComponent(component) + .setAction(SearchConstants.QUERY) + .setService(SERVICE) + .setAuthorizables(Collections.singletonList(new Collection(COLLECTION_NAME))) + .withGrantOption(false) + .build(); + + sentryStore.alterRoleGrantPrivilege(component, roleName, queryPrivilegeWithNoOption, grantor); + + assertEquals(Sets.newHashSet(queryPrivilege, queryPrivilegeWithOption, queryPrivilegeWithNoOption), + sentryStore.getPrivilegesByRole(component, Sets.newHashSet(roleName))); + } + + public static final class InvalidActionFactory { + + } + + public static final class MyComponentActionFactory extends BitFieldActionFactory { + + public enum MyComponentActionType { + FOO("foo", 1), + BAR("bar", 2), + QUERY(SearchConstants.QUERY, 4), + ALL("*", FOO.getCode() | BAR.getCode() | QUERY.getCode()); + + private String name; + private int code; + MyComponentActionType(String name, int code) { + this.name = name; + this.code = code; + } + + public int getCode() { + return code; + } + + public String getName() { + return name; + } + + static MyComponentActionType getActionByName(String name) { + for (MyComponentActionType action : MyComponentActionType.values()) { + if (action.name.equalsIgnoreCase(name)) { + return action; + } + } + throw new RuntimeException("can't get MyComponentActionType by name:" + name); + } + + static List<MyComponentActionType> getActionByCode(int code) { + List<MyComponentActionType> actions = Lists.newArrayList(); + for (MyComponentActionType action : MyComponentActionType.values()) { + if ((action.code & code) == action.code && action != MyComponentActionType.ALL) { + //MyComponentActionType.ALL action should not return in the list + actions.add(action); + } + } + if (actions.isEmpty()) { + throw new RuntimeException("can't get sqoopActionType by code:" + code); + } + return actions; + } + } + + public static class MyComponentAction extends BitFieldAction { + public MyComponentAction(String name) { + this(MyComponentActionType.getActionByName(name)); + } + public MyComponentAction(MyComponentActionType myComponentActionType) { + super(myComponentActionType.name, myComponentActionType.code); + } + } + + @Override + public List<? extends BitFieldAction> getActionsByCode(int actionCode) { + List<MyComponentAction> actions = Lists.newArrayList(); + for (MyComponentActionType action : MyComponentActionType.getActionByCode(actionCode)) { + actions.add(new MyComponentAction(action)); + } + return actions; + } + + @Override + public BitFieldAction getActionByName(String name) { + // Check the name is All + if (SqoopActionConstant.ALL_NAME.equalsIgnoreCase(name)) { + return new MyComponentAction(MyComponentActionType.ALL); + } + return new MyComponentAction(name); + } + } }