/** * 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.atlas.authorize.simple; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.Map; import org.apache.atlas.ApplicationProperties; import org.apache.atlas.AtlasException; import org.apache.atlas.authorize.AtlasAccessRequest; import org.apache.atlas.authorize.AtlasActionTypes; import org.apache.atlas.authorize.AtlasAuthorizationException; import org.apache.atlas.authorize.AtlasAuthorizer; import org.apache.atlas.authorize.AtlasResourceTypes; import org.apache.atlas.utils.PropertiesUtil; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOCase; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.VisibleForTesting; public final class SimpleAtlasAuthorizer implements AtlasAuthorizer { public enum AtlasAccessorTypes { USER, GROUP } private static final Logger LOG = LoggerFactory.getLogger(SimpleAtlasAuthorizer.class); private boolean isDebugEnabled = LOG.isDebugEnabled(); private final static String WILDCARD_ASTERISK = "*"; private final static String WILDCARDS = "*?"; private boolean optIgnoreCase = false; private Map<String, Map<AtlasResourceTypes, List<String>>> userReadMap = null; private Map<String, Map<AtlasResourceTypes, List<String>>> userWriteMap = null; private Map<String, Map<AtlasResourceTypes, List<String>>> userUpdateMap = null; private Map<String, Map<AtlasResourceTypes, List<String>>> userDeleteMap = null; private Map<String, Map<AtlasResourceTypes, List<String>>> groupReadMap = null; private Map<String, Map<AtlasResourceTypes, List<String>>> groupWriteMap = null; private Map<String, Map<AtlasResourceTypes, List<String>>> groupUpdateMap = null; private Map<String, Map<AtlasResourceTypes, List<String>>> groupDeleteMap = null; public SimpleAtlasAuthorizer() { } @Override public void init() { if (isDebugEnabled) { LOG.debug("==> SimpleAtlasAuthorizer init"); } try { PolicyParser parser = new PolicyParser(); optIgnoreCase = Boolean.valueOf(PropertiesUtil.getProperty("optIgnoreCase", "false")); if (isDebugEnabled) { LOG.debug("Read from PropertiesUtil --> optIgnoreCase :: {}", optIgnoreCase); } InputStream policyStoreStream = ApplicationProperties.getFileAsInputStream(ApplicationProperties.get(), "atlas.auth.policy.file", "policy-store.txt"); List<String> policies = null; try { policies = FileReaderUtil.readFile(policyStoreStream); } finally { policyStoreStream.close(); } List<PolicyDef> policyDef = parser.parsePolicies(policies); userReadMap = PolicyUtil.createPermissionMap(policyDef, AtlasActionTypes.READ, AtlasAccessorTypes.USER); userWriteMap = PolicyUtil.createPermissionMap(policyDef, AtlasActionTypes.CREATE, AtlasAccessorTypes.USER); userUpdateMap = PolicyUtil.createPermissionMap(policyDef, AtlasActionTypes.UPDATE, AtlasAccessorTypes.USER); userDeleteMap = PolicyUtil.createPermissionMap(policyDef, AtlasActionTypes.DELETE, AtlasAccessorTypes.USER); groupReadMap = PolicyUtil.createPermissionMap(policyDef, AtlasActionTypes.READ, AtlasAccessorTypes.GROUP); groupWriteMap = PolicyUtil.createPermissionMap(policyDef, AtlasActionTypes.CREATE, AtlasAccessorTypes.GROUP); groupUpdateMap = PolicyUtil.createPermissionMap(policyDef, AtlasActionTypes.UPDATE, AtlasAccessorTypes.GROUP); groupDeleteMap = PolicyUtil.createPermissionMap(policyDef, AtlasActionTypes.DELETE, AtlasAccessorTypes.GROUP); if (isDebugEnabled) { LOG.debug("\n\nUserReadMap :: {}\nGroupReadMap :: {}", userReadMap, groupReadMap); LOG.debug("\n\nUserWriteMap :: {}\nGroupWriteMap :: {}", userWriteMap, groupWriteMap); LOG.debug("\n\nUserUpdateMap :: {}\nGroupUpdateMap :: {}", userUpdateMap, groupUpdateMap); LOG.debug("\n\nUserDeleteMap :: {}\nGroupDeleteMap :: {}", userDeleteMap, groupDeleteMap); } } catch (IOException | AtlasException e) { if (LOG.isErrorEnabled()) { LOG.error("SimpleAtlasAuthorizer could not be initialized properly due to : ", e); } throw new RuntimeException(e); } } @Override public boolean isAccessAllowed(AtlasAccessRequest request) throws AtlasAuthorizationException { if (isDebugEnabled) { LOG.debug("==> SimpleAtlasAuthorizer isAccessAllowed"); LOG.debug("isAccessAllowd({})", request); } String user = request.getUser(); Set<String> groups = request.getUserGroups(); AtlasActionTypes action = request.getAction(); String resource = request.getResource(); Set<AtlasResourceTypes> resourceTypes = request.getResourceTypes(); if (isDebugEnabled) LOG.debug("Checking for :: \nUser :: {}\nGroups :: {}\nAction :: {}\nResource :: {}", user, groups, action, resource); boolean isAccessAllowed = false; boolean isUser = user != null; boolean isGroup = groups != null; if ((!isUser && !isGroup) || action == null || resource == null) { if (isDebugEnabled) { LOG.debug("Please check the formation AtlasAccessRequest."); } return isAccessAllowed; } else { if (isDebugEnabled) { LOG.debug("checkAccess for Operation :: {} on Resource {}:{}", action, resourceTypes, resource); } switch (action) { case READ: isAccessAllowed = checkAccess(user, resourceTypes, resource, userReadMap); isAccessAllowed = isAccessAllowed || checkAccessForGroups(groups, resourceTypes, resource, groupReadMap); break; case CREATE: isAccessAllowed = checkAccess(user, resourceTypes, resource, userWriteMap); isAccessAllowed = isAccessAllowed || checkAccessForGroups(groups, resourceTypes, resource, groupWriteMap); break; case UPDATE: isAccessAllowed = checkAccess(user, resourceTypes, resource, userUpdateMap); isAccessAllowed = isAccessAllowed || checkAccessForGroups(groups, resourceTypes, resource, groupUpdateMap); break; case DELETE: isAccessAllowed = checkAccess(user, resourceTypes, resource, userDeleteMap); isAccessAllowed = isAccessAllowed || checkAccessForGroups(groups, resourceTypes, resource, groupDeleteMap); break; default: if (isDebugEnabled) { LOG.debug("Invalid Action {}\nRaising AtlasAuthorizationException!!!", action); } throw new AtlasAuthorizationException("Invalid Action :: " + action); } } if (isDebugEnabled) { LOG.debug("<== SimpleAtlasAuthorizer isAccessAllowed = {}", isAccessAllowed); } return isAccessAllowed; } private boolean checkAccess(String accessor, Set<AtlasResourceTypes> resourceTypes, String resource, Map<String, Map<AtlasResourceTypes, List<String>>> map) { if (isDebugEnabled) { LOG.debug("==> SimpleAtlasAuthorizer checkAccess"); LOG.debug("Now checking access for accessor : {}\nResource Types : {}\nResource : {}\nMap : {}", accessor, resourceTypes, resource, map); } boolean result = true; Map<AtlasResourceTypes, List<String>> rescMap = map.get(accessor); if (rescMap != null) { for (AtlasResourceTypes resourceType : resourceTypes) { List<String> accessList = rescMap.get(resourceType); if (isDebugEnabled) { LOG.debug("\nChecking for resource : {} in list : {}\n", resource, accessList); } if (accessList != null) { result = result && isMatch(resource, accessList); } else { result = false; } } } else { result = false; if (isDebugEnabled) LOG.debug("Key {} missing. Returning with result : {}", accessor, result); } if (isDebugEnabled) { LOG.debug("Check for {} :: {}", accessor, result); LOG.debug("<== SimpleAtlasAuthorizer checkAccess"); } return result; } private boolean checkAccessForGroups(Set<String> groups, Set<AtlasResourceTypes> resourceType, String resource, Map<String, Map<AtlasResourceTypes, List<String>>> map) { boolean isAccessAllowed = false; if (isDebugEnabled) { LOG.debug("==> SimpleAtlasAuthorizer checkAccessForGroups"); } if(CollectionUtils.isNotEmpty(groups)) { for (String group : groups) { isAccessAllowed = checkAccess(group, resourceType, resource, map); if (isAccessAllowed) { break; } } } if (isDebugEnabled) { LOG.debug("<== SimpleAtlasAuthorizer checkAccessForGroups"); } return isAccessAllowed; } private boolean resourceMatchHelper(List<String> policyResource) { boolean isMatchAny = false; if (isDebugEnabled) { LOG.debug("==> SimpleAtlasAuthorizer resourceMatchHelper"); } boolean optWildCard = true; List<String> policyValues = new ArrayList<>(); if (policyResource != null) { boolean isWildCardPresent = !optWildCard; for (String policyValue : policyResource) { if (StringUtils.isEmpty(policyValue)) { continue; } if (StringUtils.containsOnly(policyValue, WILDCARD_ASTERISK)) { isMatchAny = true; } else if (!isWildCardPresent && StringUtils.containsAny(policyValue, WILDCARDS)) { isWildCardPresent = true; } policyValues.add(policyValue); } optWildCard = optWildCard && isWildCardPresent; } else { isMatchAny = false; } if (isDebugEnabled) { LOG.debug("<== SimpleAtlasAuthorizer resourceMatchHelper"); } return isMatchAny; } private boolean isMatch(String resource, List<String> policyValues) { if (isDebugEnabled) { LOG.debug("==> SimpleAtlasAuthorizer isMatch"); } boolean isMatchAny = resourceMatchHelper(policyValues); boolean isMatch = false; boolean allValuesRequested = isAllValuesRequested(resource); if (allValuesRequested || isMatchAny) { isMatch = isMatchAny; } else { for (String policyValue : policyValues) { if (policyValue.contains("*")) { isMatch = optIgnoreCase ? FilenameUtils.wildcardMatch(resource, policyValue, IOCase.INSENSITIVE) : FilenameUtils.wildcardMatch(resource, policyValue, IOCase.SENSITIVE); } else { isMatch = optIgnoreCase ? StringUtils.equalsIgnoreCase(resource, policyValue) : StringUtils.equals( resource, policyValue); } if (isMatch) { break; } } } if (!isMatch) { if (isDebugEnabled) { StringBuilder sb = new StringBuilder(); sb.append("["); for (String policyValue : policyValues) { sb.append(policyValue); sb.append(" "); } sb.append("]"); LOG.debug("AtlasDefaultResourceMatcher.isMatch returns FALSE, (resource={}, policyValues={})", resource, sb.toString()); } } if (isDebugEnabled) { LOG.debug("<== SimpleAtlasAuthorizer isMatch({}): {}", resource, isMatch); } return isMatch; } private boolean isAllValuesRequested(String resource) { return StringUtils.isEmpty(resource) || WILDCARD_ASTERISK.equals(resource); } @Override public void cleanUp() { if (isDebugEnabled) { LOG.debug("==> +SimpleAtlasAuthorizer cleanUp"); } userReadMap = null; userWriteMap = null; userUpdateMap = null; userDeleteMap = null; groupReadMap = null; groupWriteMap = null; groupUpdateMap = null; groupDeleteMap = null; if (isDebugEnabled) { LOG.debug("<== +SimpleAtlasAuthorizer cleanUp"); } } /* * NOTE :: This method is added for setting the maps for testing purpose. */ @VisibleForTesting public void setResourcesForTesting(Map<String, Map<AtlasResourceTypes, List<String>>> userMap, Map<String, Map<AtlasResourceTypes, List<String>>> groupMap, AtlasActionTypes actionTypes) { switch (actionTypes) { case READ: this.userReadMap = userMap; this.groupReadMap = groupMap; break; case CREATE: this.userWriteMap = userMap; this.groupWriteMap = groupMap; break; case UPDATE: this.userUpdateMap = userMap; this.groupUpdateMap = groupMap; break; case DELETE: this.userDeleteMap = userMap; this.groupDeleteMap = groupMap; break; default: if (isDebugEnabled) { LOG.debug("No such action available"); } break; } } }