blob: 26b9866b5bee453df155912127c146a51fa2fa9e [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.ranger.plugin.resourcematcher;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyResource;
import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef;
import org.apache.ranger.plugin.util.ServiceDefUtil;
public abstract class RangerAbstractResourceMatcher implements RangerResourceMatcher {
private static final Log LOG = LogFactory.getLog(RangerAbstractResourceMatcher.class);
public final static String WILDCARD_ASTERISK = "*";
public final static String OPTION_IGNORE_CASE = "ignoreCase";
public final static String OPTION_WILD_CARD = "wildCard";
public final static String OPTION_REPLACE_TOKENS = "replaceTokens";
public final static String OPTION_TOKEN_DELIMITER_START = "tokenDelimiterStart";
public final static String OPTION_TOKEN_DELIMITER_END = "tokenDelimiterEnd";
public final static String OPTION_TOKEN_DELIMITER_ESCAPE = "tokenDelimiterEscape";
public final static String OPTION_TOKEN_DELIMITER_PREFIX = "tokenDelimiterPrefix";
protected RangerResourceDef resourceDef = null;
protected RangerPolicyResource policyResource = null;
protected boolean optIgnoreCase = false;
protected boolean optWildCard = false;
protected List<String> policyValues = null;
protected boolean policyIsExcludes = false;
protected boolean isMatchAny = false;
protected ResourceMatcherWrapper resourceMatchers = null;
protected boolean optReplaceTokens = false;
protected char startDelimiterChar = '{';
protected char endDelimiterChar = '}';
protected char escapeChar = '\\';
protected String tokenPrefix = "";
@Override
public void setResourceDef(RangerResourceDef resourceDef) {
this.resourceDef = resourceDef;
}
@Override
public void setPolicyResource(RangerPolicyResource policyResource) {
this.policyResource = policyResource;
}
@Override
public void init() {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerAbstractResourceMatcher.init()");
}
Map<String, String> options = resourceDef != null ? resourceDef.getMatcherOptions() : null;
optIgnoreCase = getOptionIgnoreCase(options);
optWildCard = getOptionWildCard(options);
policyValues = new ArrayList<String>();
policyIsExcludes = policyResource == null ? false : policyResource.getIsExcludes();
if (policyResource != null && policyResource.getValues() != null) {
for (String policyValue : policyResource.getValues()) {
if (StringUtils.isEmpty(policyValue)) {
continue;
}
policyValues.add(policyValue);
}
}
optReplaceTokens = getOptionReplaceTokens(options);
if(optReplaceTokens) {
startDelimiterChar = getOptionDelimiterStart(options);
endDelimiterChar = getOptionDelimiterEnd(options);
escapeChar = getOptionDelimiterEscape(options);
tokenPrefix = getOptionDelimiterPrefix(options);
if(escapeChar == startDelimiterChar || escapeChar == endDelimiterChar ||
tokenPrefix.indexOf(escapeChar) != -1 || tokenPrefix.indexOf(startDelimiterChar) != -1 ||
tokenPrefix.indexOf(endDelimiterChar) != -1) {
String resouceName = resourceDef == null ? "" : resourceDef.getName();
String msg = "Invalid token-replacement parameters for resource '" + resouceName + "': { ";
msg += (OPTION_TOKEN_DELIMITER_START + "='" + startDelimiterChar + "'; ");
msg += (OPTION_TOKEN_DELIMITER_END + "='" + endDelimiterChar + "'; ");
msg += (OPTION_TOKEN_DELIMITER_ESCAPE + "='" + escapeChar + "'; ");
msg += (OPTION_TOKEN_DELIMITER_PREFIX + "='" + tokenPrefix + "' }. ");
msg += "Token replacement disabled";
LOG.error(msg);
optReplaceTokens = false;
}
}
resourceMatchers = buildResourceMatchers();
isMatchAny = resourceMatchers == null || CollectionUtils.isEmpty(resourceMatchers.getResourceMatchers());
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerAbstractResourceMatcher.init()");
}
}
@Override
public boolean isMatchAny() { return isMatchAny; }
public boolean getNeedsDynamicEval() {
return resourceMatchers != null && resourceMatchers.getNeedsDynamicEval();
}
public static boolean getOptionIgnoreCase(Map<String, String> options) {
return ServiceDefUtil.getBooleanOption(options, OPTION_IGNORE_CASE, true);
}
public static boolean getOptionWildCard(Map<String, String> options) {
return ServiceDefUtil.getBooleanOption(options, OPTION_WILD_CARD, true);
}
public static boolean getOptionReplaceTokens(Map<String, String> options) {
return ServiceDefUtil.getBooleanOption(options, OPTION_REPLACE_TOKENS, true);
}
public static char getOptionDelimiterStart(Map<String, String> options) {
return ServiceDefUtil.getCharOption(options, OPTION_TOKEN_DELIMITER_START, '{');
}
public static char getOptionDelimiterEnd(Map<String, String> options) {
return ServiceDefUtil.getCharOption(options, OPTION_TOKEN_DELIMITER_END, '}');
}
public static char getOptionDelimiterEscape(Map<String, String> options) {
return ServiceDefUtil.getCharOption(options, OPTION_TOKEN_DELIMITER_ESCAPE, '\\');
}
public static String getOptionDelimiterPrefix(Map<String, String> options) {
return ServiceDefUtil.getOption(options, OPTION_TOKEN_DELIMITER_PREFIX, "");
}
protected ResourceMatcherWrapper buildResourceMatchers() {
List<ResourceMatcher> resourceMatchers = new ArrayList<ResourceMatcher>();
boolean needsDynamicEval = false;
for (String policyValue : policyValues) {
ResourceMatcher matcher = getMatcher(policyValue);
if (matcher != null) {
if (matcher.isMatchAny()) {
resourceMatchers.clear();
break;
}
if (!needsDynamicEval && matcher.getNeedsDynamicEval()) {
needsDynamicEval = true;
}
resourceMatchers.add(matcher);
}
}
Collections.sort(resourceMatchers);
return CollectionUtils.isNotEmpty(resourceMatchers) ?
new ResourceMatcherWrapper(needsDynamicEval, resourceMatchers) : null;
}
@Override
public boolean isCompleteMatch(String resource, Map<String, Object> evalContext) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerAbstractResourceMatcher.isCompleteMatch(" + resource + ", " + evalContext + ")");
}
boolean ret = false;
if(CollectionUtils.isEmpty(policyValues)) {
ret = StringUtils.isEmpty(resource);
} else if(policyValues.size() == 1) {
String policyValue = policyValues.get(0);
if(isMatchAny) {
ret = StringUtils.containsOnly(resource, WILDCARD_ASTERISK);
} else {
ret = optIgnoreCase ? StringUtils.equalsIgnoreCase(resource, policyValue) : StringUtils.equals(resource, policyValue);
}
if(policyIsExcludes) {
ret = !ret;
}
}
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerAbstractResourceMatcher.isCompleteMatch(" + resource + ", " + evalContext + "): " + ret);
}
return ret;
}
@Override
public String toString( ) {
StringBuilder sb = new StringBuilder();
toString(sb);
return sb.toString();
}
public StringBuilder toString(StringBuilder sb) {
sb.append("RangerAbstractResourceMatcher={");
sb.append("resourceDef={");
if(resourceDef != null) {
resourceDef.toString(sb);
}
sb.append("} ");
sb.append("policyResource={");
if(policyResource != null) {
policyResource.toString(sb);
}
sb.append("} ");
sb.append("optIgnoreCase={").append(optIgnoreCase).append("} ");
sb.append("optWildCard={").append(optWildCard).append("} ");
sb.append("policyValues={");
if(policyValues != null) {
for(String value : policyValues) {
sb.append(value).append(",");
}
}
sb.append("} ");
sb.append("policyIsExcludes={").append(policyIsExcludes).append("} ");
sb.append("isMatchAny={").append(isMatchAny).append("} ");
sb.append("options={");
if(resourceDef != null && resourceDef.getMatcherOptions() != null) {
for(Map.Entry<String, String> e : resourceDef.getMatcherOptions().entrySet()) {
sb.append(e.getKey()).append("=").append(e.getValue()).append(';');
}
}
sb.append("} ");
sb.append("}");
return sb;
}
boolean isAllValuesRequested(String resource) {
boolean result = StringUtils.isEmpty(resource) || WILDCARD_ASTERISK.equals(resource);
if (LOG.isDebugEnabled()) {
LOG.debug("isAllValuesRequested(" + resource + "): " + result);
}
return result;
}
/**
* The only case where excludes flag does NOT change the result is the following:
* - Resource denotes all possible values (i.e. resource in (null, "", "*")
* - where as policy does not allow all possible values (i.e. policy.values().contains("*")
*
*/
public boolean applyExcludes(boolean allValuesRequested, boolean resultWithoutExcludes) {
if (!policyIsExcludes) return resultWithoutExcludes; // not an excludes policy!
if (allValuesRequested && !isMatchAny) return resultWithoutExcludes; // one case where excludes has no effect
return !resultWithoutExcludes; // all other cases flip it
}
ResourceMatcher getMatcher(String policyValue) {
final int len = policyValue != null ? policyValue.length() : 0;
if (len == 0) {
return null;
}
final ResourceMatcher ret;
int wildcardStartIdx = -1;
int wildcardEndIdx = -1;
boolean needWildcardMatch = false;
// If optWildcard is true
// If ('?' found or non-contiguous '*'s found in policyValue)
// needWildcardMatch = true
// End
//
// wildcardStartIdx is set to index of first '*' in policyValue or -1 if '*' is not found in policyValue, and
// wildcardEndIdx is set to index of last '*' in policyValue or -1 if '*' is not found in policyValue
// Else
// needWildcardMatch is set to false
// End
if (optWildCard) {
for (int i = 0; i < len; i++) {
final char c = policyValue.charAt(i);
if (c == '?') {
needWildcardMatch = true;
break;
} else if (c == '*') {
if (wildcardEndIdx == -1 || wildcardEndIdx == (i - 1)) {
wildcardEndIdx = i;
if (wildcardStartIdx == -1) {
wildcardStartIdx = i;
}
} else {
needWildcardMatch = true;
break;
}
}
}
}
if (needWildcardMatch) { // test?, test*a*, test*a*b, *test*a
ret = optIgnoreCase ? new CaseInsensitiveWildcardMatcher(policyValue) : new CaseSensitiveWildcardMatcher(policyValue);
} else if (wildcardStartIdx == -1) { // test, testa, testab
ret = optIgnoreCase ? new CaseInsensitiveStringMatcher(policyValue) : new CaseSensitiveStringMatcher(policyValue);
} else if (wildcardStartIdx == 0) { // *test, **test, *testa, *testab
String matchStr = policyValue.substring(wildcardEndIdx + 1);
ret = optIgnoreCase ? new CaseInsensitiveEndsWithMatcher(matchStr) : new CaseSensitiveEndsWithMatcher(matchStr);
} else if (wildcardEndIdx != (len - 1)) { // test*a, test*ab
ret = optIgnoreCase ? new CaseInsensitiveWildcardMatcher(policyValue) : new CaseSensitiveWildcardMatcher(policyValue);
} else { // test*, test**, testa*, testab*
String matchStr = policyValue.substring(0, wildcardStartIdx);
ret = optIgnoreCase ? new CaseInsensitiveStartsWithMatcher(matchStr) : new CaseSensitiveStartsWithMatcher(matchStr);
}
if(optReplaceTokens) {
ret.setDelimiters(startDelimiterChar, endDelimiterChar, escapeChar, tokenPrefix);
}
return ret;
}
}
final class CaseSensitiveStringMatcher extends ResourceMatcher {
CaseSensitiveStringMatcher(String value) {
super(value);
}
@Override
boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
return StringUtils.equals(resourceValue, getExpandedValue(evalContext));
}
int getPriority() { return 1 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);}
}
final class CaseInsensitiveStringMatcher extends ResourceMatcher {
CaseInsensitiveStringMatcher(String value) { super(value); }
@Override
boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
return StringUtils.equalsIgnoreCase(resourceValue, getExpandedValue(evalContext));
}
int getPriority() {return 2 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
}
final class CaseSensitiveStartsWithMatcher extends ResourceMatcher {
CaseSensitiveStartsWithMatcher(String value) {
super(value);
}
@Override
boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
return StringUtils.startsWith(resourceValue, getExpandedValue(evalContext));
}
int getPriority() { return 3 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);}
}
final class CaseInsensitiveStartsWithMatcher extends ResourceMatcher {
CaseInsensitiveStartsWithMatcher(String value) { super(value); }
@Override
boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
return StringUtils.startsWithIgnoreCase(resourceValue, getExpandedValue(evalContext));
}
int getPriority() { return 4 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
}
final class CaseSensitiveEndsWithMatcher extends ResourceMatcher {
CaseSensitiveEndsWithMatcher(String value) {
super(value);
}
@Override
boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
return StringUtils.endsWith(resourceValue, getExpandedValue(evalContext));
}
int getPriority() { return 3 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
}
final class CaseInsensitiveEndsWithMatcher extends ResourceMatcher {
CaseInsensitiveEndsWithMatcher(String value) {
super(value);
}
@Override
boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
return StringUtils.endsWithIgnoreCase(resourceValue, getExpandedValue(evalContext));
}
int getPriority() { return 4 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
}
final class CaseSensitiveWildcardMatcher extends ResourceMatcher {
CaseSensitiveWildcardMatcher(String value) {
super(value);
}
@Override
boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
return FilenameUtils.wildcardMatch(resourceValue, getExpandedValue(evalContext), IOCase.SENSITIVE);
}
int getPriority() { return 5 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
}
final class CaseInsensitiveWildcardMatcher extends ResourceMatcher {
CaseInsensitiveWildcardMatcher(String value) {
super(value);
}
@Override
boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
return FilenameUtils.wildcardMatch(resourceValue, getExpandedValue(evalContext), IOCase.INSENSITIVE);
}
int getPriority() {return 6 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); }
}
final class ResourceMatcherWrapper {
private final boolean needsDynamicEval;
private final List<ResourceMatcher> resourceMatchers;
ResourceMatcherWrapper() {
this(false, null);
}
ResourceMatcherWrapper(boolean needsDynamicEval, List<ResourceMatcher> resourceMatchers) {
this.needsDynamicEval = needsDynamicEval;
this.resourceMatchers = resourceMatchers;
}
boolean getNeedsDynamicEval() {
return needsDynamicEval;
}
List<ResourceMatcher> getResourceMatchers() {
return resourceMatchers;
}
}