| /* |
| * 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.common; |
| |
| import static org.apache.sentry.core.common.utils.SentryConstants.AUTHORIZABLE_JOINER; |
| import static org.apache.sentry.core.common.utils.SentryConstants.AUTHORIZABLE_SPLITTER; |
| import static org.apache.sentry.core.common.utils.SentryConstants.GRANT_OPTION; |
| import static org.apache.sentry.core.common.utils.SentryConstants.KV_JOINER; |
| import static org.apache.sentry.core.common.utils.SentryConstants.PRIVILEGE_NAME; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.apache.sentry.core.common.Action; |
| import org.apache.sentry.core.common.ActiveRoleSet; |
| import org.apache.sentry.core.common.Authorizable; |
| import org.apache.sentry.core.common.Model; |
| import org.apache.sentry.core.common.exception.SentryConfigurationException; |
| import org.apache.sentry.core.common.exception.SentryGroupNotFoundException; |
| import org.apache.sentry.core.common.Subject; |
| import org.apache.sentry.policy.common.PolicyEngine; |
| import org.apache.sentry.policy.common.Privilege; |
| import org.apache.sentry.policy.common.PrivilegeFactory; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| |
| public abstract class ResourceAuthorizationProvider implements AuthorizationProvider { |
| private static final Logger LOGGER = LoggerFactory |
| .getLogger(ResourceAuthorizationProvider.class); |
| private final static ThreadLocal<List<String>> lastFailedPrivileges = |
| new ThreadLocal<List<String>>() { |
| @Override |
| protected List<String> initialValue() { |
| return new ArrayList<String>(); |
| } |
| }; |
| |
| private final GroupMappingService groupService; |
| private final PolicyEngine policy; |
| private final PrivilegeFactory privilegeFactory; |
| private final Model model; |
| |
| public ResourceAuthorizationProvider(PolicyEngine policy, |
| GroupMappingService groupService, Model model) { |
| this.policy = policy; |
| this.groupService = groupService; |
| this.privilegeFactory = policy.getPrivilegeFactory(); |
| this.model = model; |
| } |
| |
| /*** |
| * @param subject: UserID to validate privileges |
| * @param authorizableHierarchy : List of object according to namespace hierarchy. |
| * eg. Server->Db->Table or Server->Function |
| * The privileges will be validated from the higher to lower scope |
| * @param actions : Privileges to validate |
| * @return |
| * True if the subject is authorized to perform requested action on the given object |
| */ |
| @Override |
| public boolean hasAccess(Subject subject, List<? extends Authorizable> authorizableHierarchy, |
| Set<? extends Action> actions, ActiveRoleSet roleSet) { |
| return hasAccess(subject, authorizableHierarchy, actions, false, roleSet); |
| } |
| |
| /*** |
| * @param subject: UserID to validate privileges |
| * @param authorizableHierarchy : List of object according to namespace hierarchy. |
| * eg. Server->Db->Table or Server->Function |
| * The privileges will be validated from the higher to lower scope |
| * @param actions : Privileges to validate |
| * @return |
| * True if the subject is authorized to perform requested action on the given object |
| */ |
| @Override |
| public boolean hasAccess(Subject subject, List<? extends Authorizable> authorizableHierarchy, |
| Set<? extends Action> actions, boolean requireGrantOption, ActiveRoleSet roleSet) { |
| if(LOGGER.isDebugEnabled()) { |
| LOGGER.debug("Authorization Request for " + subject + " " + |
| authorizableHierarchy + " and " + actions); |
| } |
| Preconditions.checkNotNull(subject, "Subject cannot be null"); |
| Preconditions.checkNotNull(authorizableHierarchy, "Authorizable cannot be null"); |
| Preconditions.checkArgument(!authorizableHierarchy.isEmpty(), "Authorizable cannot be empty"); |
| Preconditions.checkNotNull(actions, "Actions cannot be null"); |
| Preconditions.checkArgument(!actions.isEmpty(), "Actions cannot be empty"); |
| Preconditions.checkNotNull(roleSet, "ActiveRoleSet cannot be null"); |
| boolean hasAccess = false; |
| hasAccess = doHasAccess(subject, authorizableHierarchy, actions, requireGrantOption, roleSet); |
| return hasAccess; |
| } |
| |
| private boolean doHasAccess(Subject subject, |
| List<? extends Authorizable> authorizables, Set<? extends Action> actions, |
| boolean requireGrantOption, ActiveRoleSet roleSet) { |
| Set<String> groups; |
| try { |
| groups = getGroups(subject); |
| } catch (SentryGroupNotFoundException e) { |
| groups = Collections.emptySet(); |
| LOGGER.debug("Groups not found for " + subject); |
| } |
| Set<String> users = Sets.newHashSet(subject.getName()); |
| List<String> requestPrivileges = buildPermissions(authorizables, actions, requireGrantOption); |
| LOGGER.debug("requestPrivileges={}", requestPrivileges); |
| LOGGER.debug("PolicyEngine={}, PrivilegeFactory={}", policy.getClass().getName(), policy.getPrivilegeFactory().getClass().getName()); |
| LOGGER.debug("Get privileges for groups={}, users={}, roleSet={}", groups, users, roleSet); |
| |
| Iterable<Privilege> privileges = getPrivileges(groups, users, roleSet, |
| authorizables.toArray(new Authorizable[0])); |
| lastFailedPrivileges.get().clear(); |
| |
| for (String requestPrivilege : requestPrivileges) { |
| try { |
| Privilege priv = privilegeFactory.createPrivilege(requestPrivilege); |
| for (Privilege permission : privileges) { |
| /* |
| * Does the permission granted in the policy file imply the requested action? |
| */ |
| boolean result = permission.implies(priv, model); |
| LOGGER.debug("ProviderPrivilege {}, RequestPrivilege {}, RoleSet {}, Result {}", |
| new Object[]{ permission, requestPrivilege, roleSet, result}); |
| if (result) { |
| return true; |
| } |
| |
| } |
| } catch(Exception e) { |
| LOGGER.error("doHasAccess: Exception", e); |
| throw e; |
| } |
| } |
| |
| lastFailedPrivileges.get().addAll(requestPrivileges); |
| return false; |
| } |
| |
| private Iterable<Privilege> getPrivileges(Set<String> groups, Set<String> users, |
| ActiveRoleSet roleSet, Authorizable[] authorizables) { |
| ImmutableSet<Privilege> privilegeObjects = policy.getPrivilegeObjects(groups, users, roleSet, authorizables); |
| |
| if (privilegeObjects != null && privilegeObjects.size() > 0) { |
| return appendDefaultDBPrivObject(privilegeObjects, authorizables); |
| } |
| |
| ImmutableSet<String> privileges = policy.getPrivileges(groups, users, roleSet, authorizables); |
| return Iterables.transform(appendDefaultDBPriv(privileges, authorizables), |
| new Function<String, Privilege>() { |
| @Override |
| public Privilege apply(String privilege) { |
| return privilegeFactory.createPrivilege(privilege); |
| } |
| }); |
| } |
| |
| private ImmutableSet<String> appendDefaultDBPriv(ImmutableSet<String> privileges, Authorizable[] authorizables) { |
| // Only for switch db |
| if (authorizables != null && authorizables.length == 4 && authorizables[2].getName().equals("+") |
| && privileges.size() == 1 && hasOnlyServerPrivilege(privileges.asList().get(0))) { |
| // Assuming authorizable[0] will always be the server |
| // This Code is only reachable only when user fires a 'use default' |
| // and the user has a privilege on atleast 1 privilized Object |
| String defaultPriv = "Server=" + authorizables[0].getName() |
| + "->Db=default->Table=*->Column=*->action=select"; |
| Set<String> newPrivs = Sets.newHashSet(defaultPriv); |
| return ImmutableSet.copyOf(newPrivs); |
| } |
| return privileges; |
| } |
| |
| private boolean hasOnlyServerPrivilege(String priv) { |
| ArrayList<String> l = Lists.newArrayList(AUTHORIZABLE_SPLITTER.split(priv)); |
| if (l.size() == 1 && l.get(0).toLowerCase().startsWith("server")) { |
| return l.get(0).toLowerCase().split("=")[1].endsWith("+"); |
| } |
| return false; |
| } |
| |
| private ImmutableSet<Privilege> appendDefaultDBPrivObject(ImmutableSet<Privilege> privileges, Authorizable[] authorizables) { |
| // Only for switch db |
| if (authorizables != null && authorizables.length == 4 && authorizables[2].getName().equals("+") |
| && privileges.size() == 1 && hasOnlyServerPrivilege(privileges.asList().get(0))) { |
| // Assuming authorizable[0] will always be the server |
| // This Code is only reachable when user fires a 'use default' |
| // and the user has a privilege on atleast 1 privilized Object |
| String defaultPrivString = "Server=" + authorizables[0].getName() |
| + "->Db=default->Table=*->Column=*->action=select"; |
| Privilege defaultPriv = privilegeFactory.createPrivilege(defaultPrivString); |
| return ImmutableSet.of(defaultPriv); |
| } |
| return privileges; |
| } |
| |
| private boolean hasOnlyServerPrivilege(Privilege privObj) { |
| if(privObj.getParts().size() == 1 && privObj.getParts().get(0).getKey().equalsIgnoreCase("server")) { |
| return privObj.getParts().get(0).getValue().endsWith("+"); |
| } |
| return false; |
| } |
| |
| @Override |
| public GroupMappingService getGroupMapping() { |
| return groupService; |
| } |
| |
| private Set<String> getGroups(Subject subject) throws SentryGroupNotFoundException { |
| return groupService.getGroups(subject.getName()); |
| } |
| |
| @Override |
| public void validateResource(boolean strictValidation) throws SentryConfigurationException { |
| policy.validatePolicy(strictValidation); |
| } |
| |
| @Override |
| public Set<String> listPrivilegesForSubject(Subject subject) |
| throws SentryConfigurationException, SentryGroupNotFoundException { |
| return policy.getPrivileges(getGroups(subject), Sets.newHashSet(subject.getName()), |
| ActiveRoleSet.ALL, (Authorizable[]) null); |
| } |
| |
| @Override |
| public Set<String> listPrivilegesForGroup(String groupName) throws SentryConfigurationException { |
| return policy.getPrivileges(Sets.newHashSet(groupName), ActiveRoleSet.ALL); |
| } |
| |
| @Override |
| public List<String> getLastFailedPrivileges() { |
| return lastFailedPrivileges.get(); |
| } |
| |
| @Override |
| public void close() { |
| if (policy != null) { |
| policy.close(); |
| } |
| } |
| |
| private List<String> buildPermissions(List<? extends Authorizable> authorizables, |
| Set<? extends Action> actions, boolean requireGrantOption) { |
| List<String> hierarchy = new ArrayList<String>(); |
| List<String> requestedPermissions = new ArrayList<String>(); |
| |
| for (Authorizable authorizable : authorizables) { |
| hierarchy.add(KV_JOINER.join(authorizable.getTypeName(), authorizable.getName())); |
| } |
| |
| for (Action action : actions) { |
| String requestPermission = AUTHORIZABLE_JOINER.join(hierarchy); |
| requestPermission = AUTHORIZABLE_JOINER.join(requestPermission, |
| KV_JOINER.join(PRIVILEGE_NAME, action.getValue())); |
| requestPermission = AUTHORIZABLE_JOINER.join(requestPermission, |
| KV_JOINER.join(GRANT_OPTION, requireGrantOption)); |
| requestedPermissions.add(requestPermission); |
| } |
| return requestedPermissions; |
| } |
| |
| @Override |
| public PolicyEngine getPolicyEngine() { |
| return policy; |
| } |
| } |