blob: 2eb0cd509d7d460addfd312ae93424d662e86c38 [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.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;
}
}
}