blob: d8b48874bfe4045ac3c032f4db7839ccdf470101 [file] [log] [blame]
/**
* 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.generic.service.persistent;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import org.apache.hadoop.conf.Configuration;
import org.apache.sentry.core.common.exception.SentryUserException;
import org.apache.sentry.core.common.Action;
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.kafka.KafkaActionFactory;
import org.apache.sentry.core.model.search.SearchActionFactory;
import org.apache.sentry.core.model.sqoop.SqoopActionFactory;
import org.apache.sentry.provider.db.generic.service.persistent.PrivilegeObject.Builder;
import org.apache.sentry.provider.db.service.model.MSentryGMPrivilege;
import org.apache.sentry.provider.db.service.model.MSentryRole;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.sentry.provider.db.service.persistent.QueryParamBuilder;
import org.apache.sentry.provider.db.service.persistent.SentryStore;
import org.apache.sentry.service.thrift.ServiceConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.sentry.provider.db.service.persistent.SentryStore.toNULLCol;
/**
* Sentry Generic model privilege persistence support.
* <p>
* This class is similar to {@link SentryStore} but operates on generic
* privileges.
*/
public class PrivilegeOperatePersistence {
private static final String SERVICE_NAME = "serviceName";
private static final String COMPONENT_NAME = "componentName";
private static final String SCOPE = "scope";
private static final String ACTION = "action";
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());
actionFactories.put("kafka", KafkaActionFactory.getInstance());
}
private final Configuration conf;
PrivilegeOperatePersistence(Configuration conf) {
this.conf = conf;
}
/**
* Return query builder to execute in JDO for search the given privilege
* @param privilege Privilege to extract
* @return query builder suitable for executing the query
*/
private static QueryParamBuilder toQueryParam(MSentryGMPrivilege privilege) {
QueryParamBuilder paramBuilder = QueryParamBuilder.newQueryParamBuilder();
paramBuilder.add(SERVICE_NAME, toNULLCol(privilege.getServiceName()), true)
.add(COMPONENT_NAME, toNULLCol(privilege.getComponentName()), true)
.add(SCOPE, toNULLCol(privilege.getScope()), true)
.add(ACTION, toNULLCol(privilege.getAction()), true);
Boolean grantOption = privilege.getGrantOption();
paramBuilder.addObject(SentryStore.GRANT_OPTION, grantOption);
List<? extends Authorizable> authorizables = privilege.getAuthorizables();
int nAuthorizables = authorizables.size();
for (int i = 0; i < MSentryGMPrivilege.AUTHORIZABLE_LEVEL; i++) {
String resourceName = MSentryGMPrivilege.PREFIX_RESOURCE_NAME + String.valueOf(i);
String resourceType = MSentryGMPrivilege.PREFIX_RESOURCE_TYPE + String.valueOf(i);
if (i >= nAuthorizables) {
paramBuilder.addNull(resourceName);
paramBuilder.addNull(resourceType);
} else {
paramBuilder.add(resourceName, authorizables.get(i).getName(), true);
paramBuilder.add(resourceType, authorizables.get(i).getTypeName(), true);
}
}
return paramBuilder;
}
/**
* Create a query template tha includes information from the input privilege:
* <ul>
* <li>Service name</li>
* <li>Component name</li>
* <li>Name and type for each authorizable present</li>
* </ul>
* For exmaple, for Solr may configure the following privileges:
* <ul>
* <li>{@code p1:Collection=c1->action=query}</li>
* <li>{@code p2:Collection=c1->Field=f1->action=query}</li>
* <li>{@code p3:Collection=c1->Field=f2->action=query}</li>
* </ul>
* When the request for privilege revoke has
* {@code p4:Collection=c1->action=query}
* all privileges matching {@code Collection=c1} should be revoke which means that p1, p2 and p3
* should all be revoked.
*
* @param privilege Source privilege
* @return ParamBuilder suitable for executing the query
*/
private static QueryParamBuilder populateIncludePrivilegesParams(MSentryGMPrivilege privilege) {
QueryParamBuilder paramBuilder = QueryParamBuilder.newQueryParamBuilder();
paramBuilder.add(SERVICE_NAME, toNULLCol(privilege.getServiceName()), true);
paramBuilder.add(COMPONENT_NAME, toNULLCol(privilege.getComponentName()), true);
List<? extends Authorizable> authorizables = privilege.getAuthorizables();
int i = 0;
for(Authorizable auth: authorizables) {
String resourceName = MSentryGMPrivilege.PREFIX_RESOURCE_NAME + String.valueOf(i);
String resourceType = MSentryGMPrivilege.PREFIX_RESOURCE_TYPE + String.valueOf(i);
paramBuilder.add(resourceName, auth.getName(), true);
paramBuilder.add(resourceType, auth.getTypeName(), true);
i++;
}
return paramBuilder;
}
/**
* Verify whether specified privilege can be granted
* @param roles set of roles for the privilege
* @param privilege privilege being checked
* @param pm Persistentence manager instance
* @return true iff at least one privilege within the role allows for the
* requested privilege
*/
boolean checkPrivilegeOption(Set<MSentryRole> roles, PrivilegeObject privilege, PersistenceManager pm) {
MSentryGMPrivilege requestPrivilege = convertToPrivilege(privilege);
if (roles.isEmpty()) {
return false;
}
// get persistent privileges by roles
// Find all GM privileges for all the input roles
Query query = pm.newQuery(MSentryGMPrivilege.class);
QueryParamBuilder paramBuilder = QueryParamBuilder.addRolesFilter(query, null,
SentryStore.rolesToRoleNames(roles));
query.setFilter(paramBuilder.toString());
List<MSentryGMPrivilege> tPrivileges =
(List<MSentryGMPrivilege>)query.executeWithMap(paramBuilder.getArguments());
for (MSentryGMPrivilege tPrivilege : tPrivileges) {
if (tPrivilege.getGrantOption() && tPrivilege.implies(requestPrivilege)) {
return true;
}
}
return false;
}
public void grantPrivilege(PrivilegeObject privilege,MSentryRole role, PersistenceManager pm) throws SentryUserException {
MSentryGMPrivilege mPrivilege = convertToPrivilege(privilege);
grantRolePartial(mPrivilege, role, pm);
}
private void grantRolePartial(MSentryGMPrivilege grantPrivilege,
MSentryRole role,PersistenceManager pm) throws SentryUserException {
/*
* If Grant is for ALL action and other actions belongs to ALL action already exists..
* need to remove it and GRANT ALL action
*/
String component = grantPrivilege.getComponentName();
BitFieldAction action = getAction(component, grantPrivilege.getAction());
BitFieldAction allAction = getAction(component, Action.ALL);
if (action.implies(allAction)) {
/*
* ALL action is a multi-bit set action that includes some actions such as INSERT,SELECT and CREATE.
*/
List<? extends BitFieldAction> actions = getActionFactory(component).getActionsByCode(allAction.getActionCode());
for (BitFieldAction ac : actions) {
grantPrivilege.setAction(ac.getValue());
MSentryGMPrivilege existPriv = getPrivilege(grantPrivilege, pm);
if (existPriv != null && role.getGmPrivileges().contains(existPriv)) {
/*
* force to load all roles related this privilege
* avoid the lazy-loading risk,such as:
* if the roles field of privilege aren't loaded, then the roles is a empty set
* privilege.removeRole(role) and pm.makePersistent(privilege)
* will remove other roles that shouldn't been removed
*/
pm.retrieve(existPriv);
existPriv.removeRole(role);
pm.makePersistent(existPriv);
}
}
} else {
/*
* If ALL Action already exists..
* do nothing.
*/
grantPrivilege.setAction(allAction.getValue());
MSentryGMPrivilege allPrivilege = getPrivilege(grantPrivilege, pm);
if (allPrivilege != null && role.getGmPrivileges().contains(allPrivilege)) {
return;
}
}
/*
* restore the action
*/
grantPrivilege.setAction(action.getValue());
/*
* check the privilege is exist or not
*/
MSentryGMPrivilege mPrivilege = getPrivilege(grantPrivilege, pm);
if (mPrivilege == null) {
mPrivilege = grantPrivilege;
}
mPrivilege.appendRole(role);
pm.makePersistent(mPrivilege);
}
public void revokePrivilege(PrivilegeObject privilege,MSentryRole role, PersistenceManager pm) throws SentryUserException {
MSentryGMPrivilege mPrivilege = getPrivilege(convertToPrivilege(privilege), pm);
if (mPrivilege == null) {
mPrivilege = convertToPrivilege(privilege);
} else {
mPrivilege = pm.detachCopy(mPrivilege);
}
Set<MSentryGMPrivilege> privilegeGraph = Sets.newHashSet();
privilegeGraph.addAll(populateIncludePrivileges(Sets.newHashSet(role), mPrivilege, pm));
/*
* Get the privilege graph
* populateIncludePrivileges will get the privileges that needed revoke
*/
for (MSentryGMPrivilege persistedPriv : privilegeGraph) {
/*
* force to load all roles related this privilege
* avoid the lazy-loading risk,such as:
* if the roles field of privilege aren't loaded, then the roles is a empty set
* privilege.removeRole(role) and pm.makePersistent(privilege)
* will remove other roles that shouldn't been removed
*/
revokeRolePartial(mPrivilege, persistedPriv, role, pm);
}
pm.makePersistent(role);
}
private Set<MSentryGMPrivilege> populateIncludePrivileges(Set<MSentryRole> roles,
MSentryGMPrivilege parent, PersistenceManager pm) {
Set<MSentryGMPrivilege> childrens = Sets.newHashSet();
Query query = pm.newQuery(MSentryGMPrivilege.class);
QueryParamBuilder paramBuilder = populateIncludePrivilegesParams(parent);
// add filter for role names
if ((roles != null) && !roles.isEmpty()) {
QueryParamBuilder.addRolesFilter(query, paramBuilder, SentryStore.rolesToRoleNames(roles));
}
query.setFilter(paramBuilder.toString());
List<MSentryGMPrivilege> privileges =
(List<MSentryGMPrivilege>)query.executeWithMap(paramBuilder.getArguments());
childrens.addAll(privileges);
return childrens;
}
/**
* Roles can be granted multi-bit set action like ALL action on resource object.
* Take solr component for example, When a role has been granted ALL action but
* QUERY or UPDATE or CREATE are revoked, we need to remove the ALL
* privilege and add left privileges like UPDATE and CREATE(QUERY was revoked) or
* QUERY and UPDATE(CREATEE was revoked).
*/
private void revokeRolePartial(MSentryGMPrivilege revokePrivilege,
MSentryGMPrivilege persistedPriv, MSentryRole role,
PersistenceManager pm) throws SentryUserException {
String component = revokePrivilege.getComponentName();
BitFieldAction revokeaction = getAction(component, revokePrivilege.getAction());
BitFieldAction persistedAction = getAction(component, persistedPriv.getAction());
BitFieldAction allAction = getAction(component, Action.ALL);
if (revokeaction.implies(allAction)) {
/*
* if revoke action is ALL, directly revoke its children privileges and itself
*/
persistedPriv.removeRole(role);
pm.makePersistent(persistedPriv);
} else {
/*
* if persisted action is ALL, it only revoke the requested action and left partial actions
* like the requested action is SELECT, the UPDATE and CREATE action are left
*/
if (persistedAction.implies(allAction)) {
/*
* revoke the ALL privilege
*/
persistedPriv.removeRole(role);
pm.makePersistent(persistedPriv);
List<? extends BitFieldAction> actions = getActionFactory(component).getActionsByCode(allAction.getActionCode());
for (BitFieldAction ac: actions) {
if (ac.getActionCode() != revokeaction.getActionCode()) {
/*
* grant the left privileges to role
*/
MSentryGMPrivilege tmpPriv = new MSentryGMPrivilege(persistedPriv);
tmpPriv.setAction(ac.getValue());
MSentryGMPrivilege leftPersistedPriv = getPrivilege(tmpPriv, pm);
if (leftPersistedPriv == null) {
//leftPersistedPriv isn't exist
leftPersistedPriv = tmpPriv;
role.appendGMPrivilege(leftPersistedPriv);
}
leftPersistedPriv.appendRole(role);
pm.makePersistent(leftPersistedPriv);
}
}
} else if (revokeaction.implies(persistedAction)) {
/*
* if the revoke action is equal to the persisted action and they aren't ALL action
* directly remove the role from privilege
*/
persistedPriv.removeRole(role);
pm.makePersistent(persistedPriv);
}
/*
* if the revoke action is not equal to the persisted action,
* do nothing
*/
}
}
/**
* Drop any role related to the requested privilege and its children privileges
*/
public void dropPrivilege(PrivilegeObject privilege,PersistenceManager pm) throws SentryUserException {
MSentryGMPrivilege requestPrivilege = convertToPrivilege(privilege);
if (Strings.isNullOrEmpty(privilege.getAction())) {
requestPrivilege.setAction(getAction(privilege.getComponent(), Action.ALL).getValue());
}
/*
* Get the privilege graph
* populateIncludePrivileges will get the privileges that need dropped,
*/
Set<MSentryGMPrivilege> privilegeGraph = Sets.newHashSet();
privilegeGraph.addAll(populateIncludePrivileges(null, requestPrivilege, pm));
for (MSentryGMPrivilege mPrivilege : privilegeGraph) {
/*
* force to load all roles related this privilege
* avoid the lazy-loading
*/
pm.retrieve(mPrivilege);
Set<MSentryRole> roles = mPrivilege.getRoles();
for (MSentryRole role : roles) {
revokeRolePartial(requestPrivilege, mPrivilege, role, pm);
}
}
}
private MSentryGMPrivilege convertToPrivilege(PrivilegeObject privilege) {
return new MSentryGMPrivilege(privilege.getComponent(),
privilege.getService(), privilege.getAuthorizables(),
privilege.getAction(), privilege.getGrantOption());
}
private MSentryGMPrivilege getPrivilege(MSentryGMPrivilege privilege, PersistenceManager pm) {
Query query = pm.newQuery(MSentryGMPrivilege.class);
QueryParamBuilder paramBuilder = toQueryParam(privilege);
query.setFilter(paramBuilder.toString());
query.setUnique(true);
MSentryGMPrivilege result = (MSentryGMPrivilege)query.executeWithMap(paramBuilder.getArguments());
return result;
}
/**
* Get all privileges associated with a given roles
* @param roles Set of roles
* @param pm Persistence manager instance
* @return Set (potentially empty) of privileges associated with roles
*/
Set<PrivilegeObject> getPrivilegesByRole(Set<MSentryRole> roles, PersistenceManager pm) {
if (roles == null || roles.isEmpty()) {
return Collections.emptySet();
}
Query query = pm.newQuery(MSentryGMPrivilege.class);
// Find privileges matching all roles
QueryParamBuilder paramBuilder = QueryParamBuilder.addRolesFilter(query, null,
SentryStore.rolesToRoleNames(roles));
query.setFilter(paramBuilder.toString());
List<MSentryGMPrivilege> mPrivileges =
(List<MSentryGMPrivilege>)query.executeWithMap(paramBuilder.getArguments());
if (mPrivileges.isEmpty()) {
return Collections.emptySet();
}
Set<PrivilegeObject> privileges = new HashSet<>(mPrivileges.size());
for (MSentryGMPrivilege mPrivilege : mPrivileges) {
privileges.add(new Builder()
.setComponent(mPrivilege.getComponentName())
.setService(mPrivilege.getServiceName())
.setAction(mPrivilege.getAction())
.setAuthorizables(mPrivilege.getAuthorizables())
.withGrantOption(mPrivilege.getGrantOption())
.build());
}
return privileges;
}
Set<PrivilegeObject> getPrivilegesByProvider(String component,
String service, Set<MSentryRole> roles,
List<? extends Authorizable> authorizables, PersistenceManager pm) {
Set<PrivilegeObject> privileges = Sets.newHashSet();
if (roles == null || roles.isEmpty()) {
return privileges;
}
MSentryGMPrivilege parentPrivilege = new MSentryGMPrivilege(component, service, authorizables, null, null);
Set<MSentryGMPrivilege> privilegeGraph = Sets.newHashSet();
privilegeGraph.addAll(populateIncludePrivileges(roles, parentPrivilege, pm));
for (MSentryGMPrivilege mPrivilege : privilegeGraph) {
privileges.add(new Builder()
.setComponent(mPrivilege.getComponentName())
.setService(mPrivilege.getServiceName())
.setAction(mPrivilege.getAction())
.setAuthorizables(mPrivilege.getAuthorizables())
.withGrantOption(mPrivilege.getGrantOption())
.build());
}
return privileges;
}
Set<MSentryGMPrivilege> getPrivilegesByAuthorizable(String component,
String service, Set<MSentryRole> roles,
List<? extends Authorizable> authorizables, PersistenceManager pm) {
Set<MSentryGMPrivilege> privilegeGraph = Sets.newHashSet();
if (roles == null || roles.isEmpty()) {
return privilegeGraph;
}
MSentryGMPrivilege parentPrivilege = new MSentryGMPrivilege(component, service, authorizables, null, null);
privilegeGraph.addAll(populateIncludePrivileges(roles, parentPrivilege, pm));
return privilegeGraph;
}
public void renamePrivilege(String component, String service,
List<? extends Authorizable> oldAuthorizables, List<? extends Authorizable> newAuthorizables,
String grantorPrincipal, PersistenceManager pm)
throws SentryUserException {
MSentryGMPrivilege oldPrivilege = new MSentryGMPrivilege(component, service, oldAuthorizables, null, null);
oldPrivilege.setAction(getAction(component,Action.ALL).getValue());
/*
* Get the privilege graph
* populateIncludePrivileges will get the old privileges that need dropped
*/
Set<MSentryGMPrivilege> privilegeGraph = Sets.newHashSet();
privilegeGraph.addAll(populateIncludePrivileges(null, oldPrivilege, pm));
for (MSentryGMPrivilege dropPrivilege : privilegeGraph) {
/*
* construct the new privilege needed to add
*/
List<Authorizable> authorizables = new ArrayList<Authorizable>(
dropPrivilege.getAuthorizables());
for (int i = 0; i < newAuthorizables.size(); i++) {
authorizables.set(i, newAuthorizables.get(i));
}
MSentryGMPrivilege newPrivilge = new MSentryGMPrivilege(
component,service, authorizables, dropPrivilege.getAction(),
dropPrivilege.getGrantOption());
/*
* force to load all roles related this privilege
* avoid the lazy-loading
*/
pm.retrieve(dropPrivilege);
Set<MSentryRole> roles = dropPrivilege.getRoles();
for (MSentryRole role : roles) {
revokeRolePartial(oldPrivilege, dropPrivilege, role, pm);
grantRolePartial(newPrivilge, role, pm);
}
}
}
private BitFieldAction getAction(String component, String name) throws SentryUserException {
BitFieldActionFactory actionFactory = getActionFactory(component);
BitFieldAction action = actionFactory.getActionByName(name);
if (action == null) {
throw new SentryUserException("Can not get BitFieldAction for name: " + name);
}
return action;
}
private BitFieldActionFactory getActionFactory(String component) throws SentryUserException {
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 {} is not found in cache. Loaded it from configuration as {}.",
component, actionFactory.getClass().getName());
return actionFactory;
}
private BitFieldActionFactory createActionFactory(String component) throws SentryUserException {
String actionFactoryClassName =
conf.get(String.format(ServiceConstants.ServerConfig.SENTRY_COMPONENT_ACTION_FACTORY_FORMAT, component));
if (actionFactoryClassName == null) {
throw new SentryUserException("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 SentryUserException("ActionFactory class " + actionFactoryClassName + " not found.");
}
if (!BitFieldActionFactory.class.isAssignableFrom(actionFactoryClass)) {
throw new SentryUserException("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 SentryUserException("Could not instantiate actionFactory " + actionFactoryClassName +
" for component: " + component, e);
}
return actionFactory;
}
}