diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcher.java b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcher.java index cb8d46adfc..5eee8d11ad 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcher.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcher.java @@ -30,15 +30,18 @@ import org.apache.commons.lang.StringUtils; import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyResource; import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef; +import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType; import org.apache.ranger.plugin.util.ServiceDefUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType.*; public abstract class RangerAbstractResourceMatcher implements RangerResourceMatcher { private static final Logger LOG = LoggerFactory.getLogger(RangerAbstractResourceMatcher.class); - public final static String WILDCARD_ASTERISK = "*"; + public final static String WILDCARD_ASTERISK = "*"; + public final static String WILDCARD_QUESTION_MARK = "?"; public final static String OPTION_IGNORE_CASE = "ignoreCase"; public final static String OPTION_QUOTED_CASE_SENSITIVE = "quotedCaseSensitive"; @@ -324,6 +327,12 @@ public boolean applyExcludes(boolean allValuesRequested, boolean resultWithoutEx return !resultWithoutExcludes; // all other cases flip it } + public ResourceElementMatchType applyExcludes(boolean allValuesRequested, ResourceElementMatchType resultWithoutExcludes) { + if (!policyIsExcludes) return resultWithoutExcludes; // not an excludes policy! + if (allValuesRequested && !isMatchAny) return resultWithoutExcludes; // one case where excludes has no effect + return resultWithoutExcludes == NONE ? SELF : NONE; // all other cases flip it + } + ResourceMatcher getMatcher(String policyValue) { final int len = policyValue != null ? policyValue.length() : 0; @@ -391,18 +400,8 @@ ResourceMatcher getMatcher(String policyValue) { } abstract class AbstractStringResourceMatcher extends ResourceMatcher { - private final boolean isCaseSensitive; - - protected AbstractStringResourceMatcher(String value, Map options, boolean isCaseSensitive) { + protected AbstractStringResourceMatcher(String value, Map options) { super(value, options); - - this.isCaseSensitive = isCaseSensitive; - } - - @Override - public boolean isPrefixMatch(String resourceValue, Map evalContext) { - return isCaseSensitive ? StringUtils.startsWith(getExpandedValue(evalContext), resourceValue) - : StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue); } @Override @@ -413,23 +412,35 @@ public boolean isChildMatch(String resourceValue, Map evalContex final class CaseSensitiveStringMatcher extends AbstractStringResourceMatcher { CaseSensitiveStringMatcher(String value, Map options) { - super(value, options, true); + super(value, options); } @Override boolean isMatch(String resourceValue, Map evalContext) { return StringUtils.equals(resourceValue, getExpandedValue(evalContext)); } + + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + return StringUtils.startsWith(getExpandedValue(evalContext), resourceValue); + } + int getPriority() { return 1 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);} } final class CaseInsensitiveStringMatcher extends AbstractStringResourceMatcher { - CaseInsensitiveStringMatcher(String value, Map options) { super(value, options, false); } + CaseInsensitiveStringMatcher(String value, Map options) { super(value, options); } @Override boolean isMatch(String resourceValue, Map evalContext) { return StringUtils.equalsIgnoreCase(resourceValue, getExpandedValue(evalContext)); } + + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + return StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue); + } + int getPriority() {return 2 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); } } @@ -437,7 +448,7 @@ final class QuotedCaseSensitiveStringMatcher extends AbstractStringResourceMatch private final String quoteChars; QuotedCaseSensitiveStringMatcher(String value, Map options, String quoteChars) { - super(value, options, true); + super(value, options); this.quoteChars = quoteChars; } @@ -451,28 +462,49 @@ boolean isMatch(String resourceValue, Map evalContext) { } } + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + if (startsWithAnyChar(resourceValue, quoteChars)) { + return StringUtils.startsWith(getExpandedValue(evalContext), resourceValue); + } else { + return StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue); + } + } + int getPriority() {return 2 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); } } final class CaseSensitiveStartsWithMatcher extends AbstractStringResourceMatcher { CaseSensitiveStartsWithMatcher(String value, Map options) { - super(value, options, true); + super(value, options); } @Override boolean isMatch(String resourceValue, Map evalContext) { return StringUtils.startsWith(resourceValue, getExpandedValue(evalContext)); } + + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + return StringUtils.startsWith(getExpandedValue(evalContext), resourceValue); + } + int getPriority() { return 3 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);} } final class CaseInsensitiveStartsWithMatcher extends AbstractStringResourceMatcher { - CaseInsensitiveStartsWithMatcher(String value, Map options) { super(value, options, false); } + CaseInsensitiveStartsWithMatcher(String value, Map options) { super(value, options); } @Override boolean isMatch(String resourceValue, Map evalContext) { return StringUtils.startsWithIgnoreCase(resourceValue, getExpandedValue(evalContext)); } + + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + return StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue); + } + int getPriority() { return 4 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); } } @@ -480,7 +512,7 @@ final class QuotedCaseSensitiveStartsWithMatcher extends AbstractStringResourceM private final String quoteChars; QuotedCaseSensitiveStartsWithMatcher(String value, Map options, String quoteChars) { - super(value, options, true); + super(value, options); this.quoteChars = quoteChars; } @@ -494,30 +526,51 @@ boolean isMatch(String resourceValue, Map evalContext) { } } + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + if (startsWithAnyChar(resourceValue, quoteChars)) { + return StringUtils.startsWith(getExpandedValue(evalContext), resourceValue); + } else { + return StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue); + } + } + int getPriority() { return 4 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); } } final class CaseSensitiveEndsWithMatcher extends AbstractStringResourceMatcher { CaseSensitiveEndsWithMatcher(String value, Map options) { - super(value, options, true); + super(value, options); } @Override boolean isMatch(String resourceValue, Map evalContext) { return StringUtils.endsWith(resourceValue, getExpandedValue(evalContext)); } + + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + return true; // isPrefixMatch() is always true for endsWith + } + int getPriority() { return 3 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); } } final class CaseInsensitiveEndsWithMatcher extends AbstractStringResourceMatcher { CaseInsensitiveEndsWithMatcher(String value, Map options) { - super(value, options, false); + super(value, options); } @Override boolean isMatch(String resourceValue, Map evalContext) { return StringUtils.endsWithIgnoreCase(resourceValue, getExpandedValue(evalContext)); } + + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + return true; // isPrefixMatch() is always true for endsWith + } + int getPriority() { return 4 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); } } @@ -525,7 +578,7 @@ final class QuotedCaseSensitiveEndsWithMatcher extends AbstractStringResourceMat private final String quoteChars; QuotedCaseSensitiveEndsWithMatcher(String value, Map options, String quoteChars) { - super(value, options, true); + super(value, options); this.quoteChars = quoteChars; } @@ -539,31 +592,48 @@ boolean isMatch(String resourceValue, Map evalContext) { } } + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + return true; // isPrefixMatch() is always true for endsWith + } + int getPriority() { return 4 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); } } final class CaseSensitiveWildcardMatcher extends AbstractStringResourceMatcher { CaseSensitiveWildcardMatcher(String value, Map options) { - super(value, options, true); + super(value, options); } @Override boolean isMatch(String resourceValue, Map evalContext) { return FilenameUtils.wildcardMatch(resourceValue, getExpandedValue(evalContext), IOCase.SENSITIVE); } + + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + return ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), IOCase.SENSITIVE); + } + int getPriority() { return 5 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); } } final class CaseInsensitiveWildcardMatcher extends AbstractStringResourceMatcher { CaseInsensitiveWildcardMatcher(String value, Map options) { - super(value, options, false); + super(value, options); } @Override boolean isMatch(String resourceValue, Map evalContext) { return FilenameUtils.wildcardMatch(resourceValue, getExpandedValue(evalContext), IOCase.INSENSITIVE); } + + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + return ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), IOCase.INSENSITIVE); + } + int getPriority() {return 6 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); } } @@ -571,7 +641,7 @@ final class QuotedCaseSensitiveWildcardMatcher extends AbstractStringResourceMat private final String quoteChars; QuotedCaseSensitiveWildcardMatcher(String value, Map options, String quoteChars) { - super(value, options, true); + super(value, options); this.quoteChars = quoteChars; } @@ -583,6 +653,13 @@ boolean isMatch(String resourceValue, Map evalContext) { return FilenameUtils.wildcardMatch(resourceValue, getExpandedValue(evalContext), caseSensitivity); } + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + IOCase caseSensitivity = startsWithAnyChar(resourceValue, quoteChars) ? IOCase.SENSITIVE : IOCase.INSENSITIVE; + + return ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), caseSensitivity); + } + int getPriority() {return 6 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); } } diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcher.java b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcher.java index 702cb272fb..977fd49414 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcher.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcher.java @@ -21,12 +21,15 @@ import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchingScope; +import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.Map; +import static org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType.NONE; + public class RangerDefaultResourceMatcher extends RangerAbstractResourceMatcher { private static final Logger LOG = LoggerFactory.getLogger(RangerDefaultResourceMatcher.class); @@ -37,20 +40,54 @@ public boolean isMatch(Object resource, ResourceElementMatchingScope matchingSco LOG.debug("==> RangerDefaultResourceMatcher.isMatch(" + resource + ", " + evalContext + ")"); } - boolean ret = false; - boolean allValuesRequested = isAllValuesRequested(resource); - boolean isPrefixMatch = matchingScope == ResourceElementMatchingScope.SELF_OR_PREFIX; + ResourceElementMatchType matchType = getMatchType(resource, matchingScope, evalContext); + boolean ret = ResourceMatcher.isMatch(matchType, matchingScope); + + if (ret == false) { + if(LOG.isDebugEnabled()) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (String policyValue: policyValues) { + sb.append(policyValue); + sb.append(" "); + } + sb.append("]"); + + LOG.debug("RangerDefaultResourceMatcher.isMatch returns FALSE, (resource=" + resource + ", policyValues=" + sb.toString() + ")"); + } + } + + if(LOG.isDebugEnabled()) { + LOG.debug("<== RangerDefaultResourceMatcher.isMatch(" + resource + ", " + evalContext + "): " + ret); + } + + return ret; + } + + @Override + public ResourceElementMatchType getMatchType(Object resource, ResourceElementMatchingScope matchingScope, Map evalContext) { + if(LOG.isDebugEnabled()) { + LOG.debug("==> RangerDefaultResourceMatcher.getMatchType(" + resource + ", " + evalContext + ")"); + } + + ResourceElementMatchType ret = NONE; + boolean allValuesRequested = isAllValuesRequested(resource); + boolean isPrefixMatch = matchingScope == ResourceElementMatchingScope.SELF_OR_PREFIX; if (isMatchAny || (allValuesRequested && !isPrefixMatch)) { - ret = isMatchAny; + ret = isMatchAny ? ResourceElementMatchType.SELF : NONE; } else { if (resource instanceof String) { String strValue = (String) resource; for (ResourceMatcher resourceMatcher : resourceMatchers.getResourceMatchers()) { - ret = resourceMatcher.isMatch(strValue, matchingScope, evalContext); + ResourceElementMatchType matchType = resourceMatcher.getMatchType(strValue, matchingScope, evalContext); - if (ret) { + if (matchType != NONE) { + ret = matchType; + } + + if (ret == ResourceElementMatchType.SELF) { break; } } @@ -60,13 +97,18 @@ public boolean isMatch(Object resource, ResourceElementMatchingScope matchingSco for (ResourceMatcher resourceMatcher : resourceMatchers.getResourceMatchers()) { for (String resourceValue : resourceValues) { - ret = resourceMatcher.isMatch(resourceValue, matchingScope, evalContext); + ResourceElementMatchType matchType = resourceMatcher.getMatchType(resourceValue, matchingScope, evalContext); + + if (matchType != NONE) { + ret = matchType; + } - if (ret) { + if (ret == ResourceElementMatchType.SELF) { break; } } - if (ret) { + + if (ret == ResourceElementMatchType.SELF) { break; } } @@ -75,22 +117,8 @@ public boolean isMatch(Object resource, ResourceElementMatchingScope matchingSco ret = applyExcludes(allValuesRequested, ret); - if (ret == false) { - if(LOG.isDebugEnabled()) { - StringBuilder sb = new StringBuilder(); - sb.append("["); - for (String policyValue: policyValues) { - sb.append(policyValue); - sb.append(" "); - } - sb.append("]"); - - LOG.debug("RangerDefaultResourceMatcher.isMatch returns FALSE, (resource=" + resource + ", policyValues=" + sb.toString() + ")"); - } - } - if(LOG.isDebugEnabled()) { - LOG.debug("<== RangerDefaultResourceMatcher.isMatch(" + resource + ", " + evalContext + "): " + ret); + LOG.debug("<== RangerDefaultResourceMatcher.getMatchType(" + resource + ", " + evalContext + "): " + ret); } return ret; diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcher.java b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcher.java index 3c1523c254..d376e90250 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcher.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcher.java @@ -130,7 +130,7 @@ ResourceMatcher getMatcher(String policyValue) { if (isWildcardPresent) { ret = new RecursiveWildcardResourceMatcher(policyValue, getOptions(), pathSeparatorChar, optIgnoreCase, RangerPathResourceMatcher::isRecursiveWildCardMatch, optIgnoreCase ? 8 : 7); } else { - ret = new RecursivePathResourceMatcher(policyValue, getOptions(), pathSeparatorChar, optIgnoreCase ? StringUtils::equalsIgnoreCase : StringUtils::equals, optIgnoreCase ? StringUtils::startsWithIgnoreCase : StringUtils::startsWith, optIgnoreCase ? 8 : 7); + ret = new RecursivePathResourceMatcher(policyValue, getOptions(), pathSeparatorChar, optIgnoreCase, optIgnoreCase ? 8 : 7); } if (optReplaceTokens) { @@ -263,15 +263,15 @@ private ResourceMatcher getPathMatcher(String policyValue) { if (needWildcardMatch) { // test?, test*a*, test*a*b, *test*a ret = new WildcardResourceMatcher(policyValue, getOptions(), pathSeparatorChar, optIgnoreCase, FilenameUtils::wildcardMatch, 6); } else if (wildcardStartIdx == -1) { // test, testa, testab - ret = new PathResourceMatcher(policyValue, getOptions(), pathSeparatorChar, optIgnoreCase ? StringUtils::equalsIgnoreCase : StringUtils::equals, optIgnoreCase ? 2 : 1, !optIgnoreCase); + ret = new PathResourceMatcher(policyValue, getOptions(), pathSeparatorChar, optIgnoreCase ? StringUtils::equalsIgnoreCase : StringUtils::equals, !optIgnoreCase, optIgnoreCase ? 2 : 1); } else if (wildcardStartIdx == 0) { // *test, **test, *testa, *testab String matchStr = policyValue.substring(wildcardEndIdx + 1); - ret = new PathResourceMatcher(matchStr, getOptions(), pathSeparatorChar, optIgnoreCase ? StringUtils::endsWithIgnoreCase : StringUtils::endsWith, optIgnoreCase ? 4 : 3, !optIgnoreCase); + ret = new PathEndsWithResourceMatcher(matchStr, getOptions(), pathSeparatorChar, !optIgnoreCase, optIgnoreCase ? 4 : 3); } else if (wildcardEndIdx != (len - 1)) { // test*a, test*ab ret = new WildcardResourceMatcher(policyValue, getOptions(), pathSeparatorChar, optIgnoreCase, FilenameUtils::wildcardMatch, 6); } else { // test*, test**, testa*, testab* String matchStr = policyValue.substring(0, wildcardStartIdx); - ret = new PathResourceMatcher(matchStr, getOptions(), pathSeparatorChar, optIgnoreCase ? StringUtils::startsWithIgnoreCase : StringUtils::startsWith, optIgnoreCase ? 4 : 3, !optIgnoreCase); + ret = new PathStartsWithResourceMatcher(matchStr, getOptions(), pathSeparatorChar, !optIgnoreCase, optIgnoreCase ? 4 : 3); } if (optReplaceTokens) { @@ -298,7 +298,7 @@ static abstract class AbstractPathResourceMatcher extends ResourceMatcher { final int priority; final boolean isCaseSensitive; - AbstractPathResourceMatcher(String value, Map options, char pathSeparatorChar, int priority, boolean isCaseSensitive) { + AbstractPathResourceMatcher(String value, Map options, char pathSeparatorChar, boolean isCaseSensitive, int priority) { super(value, options); this.pathSeparatorChar = pathSeparatorChar; @@ -308,19 +308,13 @@ static abstract class AbstractPathResourceMatcher extends ResourceMatcher { int getPriority() { return priority + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0); } - - @Override - public boolean isPrefixMatch(String resourceValue, Map evalContext) { - return isCaseSensitive ? StringUtils.startsWith(getExpandedValue(evalContext), resourceValue) - : StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue); - } } static class PathResourceMatcher extends AbstractPathResourceMatcher { final BiFunction function; - PathResourceMatcher(String value, Map options, char pathSeparatorChar, BiFunction function, int priority, boolean isCaseSensitive) { - super(value, options, pathSeparatorChar, priority, isCaseSensitive); + PathResourceMatcher(String value, Map options, char pathSeparatorChar, BiFunction function, boolean isCaseSensitive, int priority) { + super(value, options, pathSeparatorChar, isCaseSensitive, priority); this.function = function; } @@ -328,21 +322,42 @@ static class PathResourceMatcher extends AbstractPathResourceMatcher { @Override boolean isMatch(String resourceValue, Map evalContext) { if (LOG.isDebugEnabled()) { - LOG.debug("==> PathResourceMatcher.isMatch(resourceValue=" + resourceValue + ", evalContext=" + evalContext + ")"); + LOG.debug("==> PathResourceMatcher.isMatch(resourceValue={}, evalContext={})", resourceValue, evalContext); } String expandedValue = getExpandedValue(evalContext); boolean ret = function.apply(resourceValue, expandedValue); if (LOG.isDebugEnabled()) { - LOG.debug("<== PathResourceMatcher.isMatch(resourceValue=" + resourceValue + ", expandedValue=" + expandedValue + ") : result:[" + ret + "]"); + LOG.debug("<== PathResourceMatcher.isMatch(resourceValue={}, expandedValue={}): ret={}", resourceValue, getExpandedValue(evalContext) , ret ); + } + + return ret; + } + + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + if (LOG.isDebugEnabled()) { + LOG.debug("==> PathResourceMatcher.isPrefixMatch(resourceValue={}, evalContext={})", resourceValue, evalContext); + } + + boolean ret = isCaseSensitive ? StringUtils.startsWith(getExpandedValue(evalContext), resourceValue) + : StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue); + + if (LOG.isDebugEnabled()) { + LOG.debug("<== PathResourceMatcher.isPrefixMatch(resourceValue={}, expandedValue={}): ret={}", resourceValue, getExpandedValue(evalContext) , ret ); } return ret; + } @Override public boolean isChildMatch(String resourceValue, Map evalContext) { + if (LOG.isDebugEnabled()) { + LOG.debug("==> PathResourceMatcher.isChildMatch(resourceValue={}, evalContext={})", resourceValue, evalContext); + } + boolean ret = false; String expandedValue = getExpandedValue(evalContext); int lastLevelSeparatorIndex = expandedValue.lastIndexOf(pathSeparatorChar); @@ -357,9 +372,143 @@ public boolean isChildMatch(String resourceValue, Map evalContex ret = function.apply(resourceValue, shorterExpandedValue); } + if (LOG.isDebugEnabled()) { + LOG.debug("<== PathResourceMatcher.isChildMatch(resourceValue={}, lastLevelSeparatorIndex={}): ret={}", resourceValue, lastLevelSeparatorIndex, ret ); + } + + return ret; + } + } + + static class PathStartsWithResourceMatcher extends AbstractPathResourceMatcher { + PathStartsWithResourceMatcher(String value, Map options, char pathSeparatorChar, boolean isCaseSensitive, int priority) { + super(value, options, pathSeparatorChar, isCaseSensitive, priority); + } + + @Override + boolean isMatch(String resourceValue, Map evalContext) { + if (LOG.isDebugEnabled()) { + LOG.debug("==> PathStartsWithResourceMatcher.isMatch(resourceValue={}, evalContext={})", resourceValue, evalContext); + } + + boolean ret = isCaseSensitive ? StringUtils.startsWith(resourceValue, getExpandedValue(evalContext)) + : StringUtils.startsWithIgnoreCase(resourceValue, getExpandedValue(evalContext)); + + if (LOG.isDebugEnabled()) { + LOG.debug("<== PathStartsWithResourceMatcher.isMatch(resourceValue={}, expandedValue={}): ret={}", resourceValue, getExpandedValue(evalContext) , ret ); + } + + return ret; + } + + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + if (LOG.isDebugEnabled()) { + LOG.debug("==> PathStartsWithResourceMatcher.isPrefixMatch(resourceValue={}, evalContext={})", resourceValue, evalContext); + } + + boolean ret = isCaseSensitive ? StringUtils.startsWith(getExpandedValue(evalContext), resourceValue) + : StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue); + + if (LOG.isDebugEnabled()) { + LOG.debug("<== PathStartsWithResourceMatcher.isPrefixMatch(resourceValue={}, expandedValue={}): ret={}", resourceValue, getExpandedValue(evalContext) , ret ); + } + + return ret; + } + + @Override + public boolean isChildMatch(String resourceValue, Map evalContext) { + if (LOG.isDebugEnabled()) { + LOG.debug("==> PathStartsWithResourceMatcher.isChildMatch(resourceValue={}, evalContext={})", resourceValue, evalContext); + } + + boolean ret = false; + String expandedValue = getExpandedValue(evalContext); + int lastLevelSeparatorIndex = expandedValue.lastIndexOf(pathSeparatorChar); + + if (lastLevelSeparatorIndex != -1) { + String shorterExpandedValue = expandedValue.substring(0, lastLevelSeparatorIndex); + + if (resourceValue.charAt(resourceValue.length()-1) == pathSeparatorChar) { + resourceValue = resourceValue.substring(0, resourceValue.length()-1); + } + + ret = isCaseSensitive ? StringUtils.startsWith(resourceValue, shorterExpandedValue) + : StringUtils.startsWithIgnoreCase(resourceValue, shorterExpandedValue); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("<== PathStartsWithResourceMatcher.isChildMatch(resourceValue={}, lastLevelSeparatorIndex={}): ret={}", resourceValue, lastLevelSeparatorIndex, ret ); + } + + return ret; + } + } + + static class PathEndsWithResourceMatcher extends AbstractPathResourceMatcher { + PathEndsWithResourceMatcher(String value, Map options, char pathSeparatorChar, boolean isCaseSensitive, int priority) { + super(value, options, pathSeparatorChar, isCaseSensitive, priority); + } + + @Override + boolean isMatch(String resourceValue, Map evalContext) { + if (LOG.isDebugEnabled()) { + LOG.debug("==> PathEndsWithResourceMatcher.isMatch(resourceValue={}, evalContext={})", resourceValue, evalContext); + } + + boolean ret = isCaseSensitive ? StringUtils.endsWith(resourceValue, getExpandedValue(evalContext)) + : StringUtils.endsWithIgnoreCase(resourceValue, getExpandedValue(evalContext)); + + if (LOG.isDebugEnabled()) { + LOG.debug("<== PathEndsWithResourceMatcher.isMatch(resourceValue={}, expandedValue={}): ret={}", resourceValue, getExpandedValue(evalContext) , ret ); + } + + return ret; + } + + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + if (LOG.isDebugEnabled()) { + LOG.debug("==> PathEndsWithResourceMatcher.isPrefixMatch(resourceValue={}, evalContext={})", resourceValue, evalContext); + } + + boolean ret = true; // isPrefixMatch() is always true for endsWith + + if (LOG.isDebugEnabled()) { + LOG.debug("<== PathEndsWithResourceMatcher.isPrefixMatch(resourceValue={}, expandedValue={}): ret={}", resourceValue, getExpandedValue(evalContext) , ret ); + } + return ret; } + @Override + public boolean isChildMatch(String resourceValue, Map evalContext) { + if (LOG.isDebugEnabled()) { + LOG.debug("==> PathEndsWithResourceMatcher.isChildMatch(resourceValue={}, evalContext={})", resourceValue, evalContext); + } + + boolean ret = false; + String expandedValue = getExpandedValue(evalContext); + int lastLevelSeparatorIndex = expandedValue.lastIndexOf(pathSeparatorChar); + + if (lastLevelSeparatorIndex != -1) { + String shorterExpandedValue = expandedValue.substring(0, lastLevelSeparatorIndex); + + if (resourceValue.charAt(resourceValue.length()-1) == pathSeparatorChar) { + resourceValue = resourceValue.substring(0, resourceValue.length()-1); + } + + ret = isCaseSensitive ? StringUtils.endsWith(resourceValue, shorterExpandedValue) + : StringUtils.endsWithIgnoreCase(resourceValue, shorterExpandedValue); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("<== PathEndsWithResourceMatcher.isChildMatch(resourceValue={}, lastLevelSeparatorIndex={}): ret={}", resourceValue, lastLevelSeparatorIndex, ret ); + } + + return ret; + } } static class WildcardResourceMatcher extends AbstractPathResourceMatcher { @@ -367,14 +516,14 @@ static class WildcardResourceMatcher extends AbstractPathResourceMatcher { final IOCase ioCase; WildcardResourceMatcher(String value, Map options, char pathSeparatorChar, boolean optIgnoreCase, TriFunction function, int priority) { - super(value, options, pathSeparatorChar, priority, !optIgnoreCase); + super(value, options, pathSeparatorChar, !optIgnoreCase, priority); this.function = function; this.ioCase = optIgnoreCase ? IOCase.INSENSITIVE : IOCase.SENSITIVE; } @Override - boolean isMatch(String resourceValue, Map evalContext) { + public boolean isMatch(String resourceValue, Map evalContext) { if (LOG.isDebugEnabled()) { LOG.debug("==> WildcardResourceMatcher.isMatch(resourceValue=" + resourceValue + ", evalContext=" + evalContext + ")"); } @@ -388,6 +537,20 @@ boolean isMatch(String resourceValue, Map evalContext) { return ret; } + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + if (LOG.isDebugEnabled()) { + LOG.debug("==> WildcardResourceMatcher.isPrefixMatch(resourceValue=" + resourceValue + ", evalContext=" + evalContext + ")"); + } + + boolean ret = ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), ioCase); + + if (LOG.isDebugEnabled()) { + LOG.debug("<== WildcardResourceMatcher.isPrefixMatch(resourceValue=" + resourceValue + ", expandedValue=" + getExpandedValue(evalContext) + ") : result:[" + ret + "]"); + } + return ret; + } + @Override public boolean isChildMatch(String resourceValue, Map evalContext) { boolean ret = false; @@ -414,7 +577,7 @@ static class RecursiveWildcardResourceMatcher extends AbstractPathResourceMatche String[] wildcardPathElements; RecursiveWildcardResourceMatcher(String value, Map options, char pathSeparatorChar, boolean optIgnoreCase, QuintFunction function, int priority) { - super(value, options, pathSeparatorChar, priority, !optIgnoreCase); + super(value, options, pathSeparatorChar, !optIgnoreCase, priority); this.function = function; this.ioCase = optIgnoreCase ? IOCase.INSENSITIVE : IOCase.SENSITIVE; @@ -444,6 +607,20 @@ boolean isMatch(String resourceValue, Map evalContext) { return ret; } + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + if (LOG.isDebugEnabled()) { + LOG.debug("==> RecursiveWildcardResourceMatcher.isPrefixMatch(resourceValue=" + resourceValue + ", evalContext=" + evalContext + ")"); + } + + boolean ret = ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), ioCase); + + if (LOG.isDebugEnabled()) { + LOG.debug("<== RecursiveWildcardResourceMatcher.isPrefixMatch(resourceValue=" + resourceValue + ", expandedValue=" + getExpandedValue(evalContext) + ") : result:[" + ret + "]"); + } + return ret; + } + @Override public boolean isChildMatch(String resourceValue, Map evalContext) { boolean ret = false; @@ -470,14 +647,16 @@ static class RecursivePathResourceMatcher extends AbstractPathResourceMatcher { String valueWithoutSeparator; String valueWithSeparator; + final IOCase ioCase; final BiFunction primaryFunction; final BiFunction fallbackFunction; - RecursivePathResourceMatcher(String value, Map options, char pathSeparatorChar, BiFunction primaryFunction, BiFunction fallbackFunction, int priority) { - super(value, options, pathSeparatorChar, priority, true); + RecursivePathResourceMatcher(String value, Map options, char pathSeparatorChar, boolean optIgnoreCase, int priority) { + super(value, options, pathSeparatorChar, true, priority); - this.primaryFunction = primaryFunction; - this.fallbackFunction = fallbackFunction; + this.ioCase = optIgnoreCase ? IOCase.INSENSITIVE : IOCase.SENSITIVE; + this.primaryFunction = optIgnoreCase ? StringUtils::equalsIgnoreCase : StringUtils::equals; + this.fallbackFunction = optIgnoreCase ? StringUtils::startsWithIgnoreCase : StringUtils::startsWith; } String getStringToCompare(String policyValue) { @@ -520,6 +699,20 @@ boolean isMatch(String resourceValue, Map evalContext) { return ret; } + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + if (LOG.isDebugEnabled()) { + LOG.debug("==> RecursiveWildcardResourceMatcher.isPrefixMatch(resourceValue=" + resourceValue + ", evalContext=" + evalContext + ")"); + } + + boolean ret = ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), ioCase); + + if (LOG.isDebugEnabled()) { + LOG.debug("<== RecursiveWildcardResourceMatcher.isPrefixMatch(resourceValue=" + resourceValue + ", expandedValue=" + getExpandedValue(evalContext) + ") : result:[" + ret + "]"); + } + return ret; + } + @Override public boolean isChildMatch(String resourceValue, Map evalContext) { boolean ret = false; @@ -534,7 +727,7 @@ public boolean isChildMatch(String resourceValue, Map evalContex } noSeparator = valueWithoutSeparator; } - final int lastLevelSeparatorIndex = noSeparator.lastIndexOf(pathSeparatorChar); + final int lastLevelSeparatorIndex = noSeparator != null ? noSeparator.lastIndexOf(pathSeparatorChar) : -1; if (lastLevelSeparatorIndex != -1) { final String shorterExpandedValue = noSeparator.substring(0, lastLevelSeparatorIndex); diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerResourceMatcher.java b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerResourceMatcher.java index c4ce30d368..ec22e01bfa 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerResourceMatcher.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerResourceMatcher.java @@ -22,6 +22,7 @@ import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyResource; import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef; import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchingScope; +import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType; import java.util.Map; @@ -34,6 +35,8 @@ public interface RangerResourceMatcher { boolean isMatchAny(); + ResourceElementMatchType getMatchType(Object resource, ResourceElementMatchingScope matchingScope, Map evalContext); + boolean isMatch(Object resource, ResourceElementMatchingScope matchingScope, Map evalContext); boolean isCompleteMatch(String resource, Map evalContext); diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcher.java b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcher.java index ce73b3006a..0575636960 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcher.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcher.java @@ -237,7 +237,7 @@ static String getPathWithOutScheme(String url) { final class CaseSensitiveURLRecursiveWildcardMatcher extends AbstractStringResourceMatcher { private final char levelSeparatorChar; CaseSensitiveURLRecursiveWildcardMatcher(String value, Map options, char levelSeparatorChar) { - super(value, options, true); + super(value, options); this.levelSeparatorChar = levelSeparatorChar; } @@ -245,13 +245,19 @@ final class CaseSensitiveURLRecursiveWildcardMatcher extends AbstractStringResou boolean isMatch(String resourceValue, Map evalContext) { return RangerURLResourceMatcher.isRecursiveWildCardMatch(resourceValue, getExpandedValue(evalContext), levelSeparatorChar, IOCase.SENSITIVE); } + + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + return ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), IOCase.SENSITIVE); + } + int getPriority() { return 7 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);} } final class CaseInsensitiveURLRecursiveWildcardMatcher extends AbstractStringResourceMatcher { private final char levelSeparatorChar; CaseInsensitiveURLRecursiveWildcardMatcher(String value, Map options, char levelSeparatorChar) { - super(value, options, false); + super(value, options); this.levelSeparatorChar = levelSeparatorChar; } @@ -259,6 +265,12 @@ final class CaseInsensitiveURLRecursiveWildcardMatcher extends AbstractStringRes boolean isMatch(String resourceValue, Map evalContext) { return RangerURLResourceMatcher.isRecursiveWildCardMatch(resourceValue, getExpandedValue(evalContext), levelSeparatorChar, IOCase.INSENSITIVE); } + + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + return ResourceMatcher.wildcardPrefixMatch(resourceValue, getExpandedValue(evalContext), IOCase.INSENSITIVE); + } + int getPriority() { return 8 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);} } @@ -268,8 +280,8 @@ abstract class RecursiveMatcher extends AbstractStringResourceMatcher { String valueWithoutSeparator; String valueWithSeparator; - RecursiveMatcher(String value, Map options, char levelSeparatorChar, boolean isCaseSensitive) { - super(value, options, isCaseSensitive); + RecursiveMatcher(String value, Map options, char levelSeparatorChar) { + super(value, options); this.levelSeparatorChar = levelSeparatorChar; } @@ -284,7 +296,7 @@ String getStringToCompare(String policyValue) { final class CaseSensitiveURLRecursiveMatcher extends RecursiveMatcher { CaseSensitiveURLRecursiveMatcher(String value, Map options, char levelSeparatorChar) { - super(value, options, levelSeparatorChar, true); + super(value, options, levelSeparatorChar); } @Override @@ -311,12 +323,18 @@ boolean isMatch(String resourceValue, Map evalContext) { return ret; } + + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + return StringUtils.startsWith(getExpandedValue(evalContext), resourceValue); + } + int getPriority() { return 7 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);} } final class CaseInsensitiveURLRecursiveMatcher extends RecursiveMatcher { CaseInsensitiveURLRecursiveMatcher(String value, Map options, char levelSeparatorChar) { - super(value, options, levelSeparatorChar, false); + super(value, options, levelSeparatorChar); } @Override @@ -344,5 +362,10 @@ boolean isMatch(String resourceValue, Map evalContext) { return ret; } + @Override + public boolean isPrefixMatch(String resourceValue, Map evalContext) { + return StringUtils.startsWithIgnoreCase(getExpandedValue(evalContext), resourceValue); + } + int getPriority() { return 8 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);} } diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/ResourceMatcher.java b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/ResourceMatcher.java index ad0942fbe7..77245eae1e 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/ResourceMatcher.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/resourcematcher/ResourceMatcher.java @@ -19,6 +19,7 @@ package org.apache.ranger.plugin.resourcematcher; +import org.apache.commons.io.IOCase; import org.apache.commons.lang.StringUtils; import org.apache.ranger.plugin.policyengine.RangerAccessRequest; import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType; @@ -30,8 +31,12 @@ import org.slf4j.LoggerFactory; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; +import java.util.List; import java.util.Map; +import java.util.Stack; abstract class ResourceMatcher { private static final Logger LOG = LoggerFactory.getLogger(ResourceMatcher.class); @@ -60,19 +65,8 @@ abstract class ResourceMatcher { final boolean isMatch(String resourceValue, ResourceElementMatchingScope matchingScope, Map evalContext) { final ResourceElementMatchType matchType = getMatchType(resourceValue, matchingScope, evalContext); - final boolean ret; - - if (matchType == ResourceElementMatchType.SELF) { - ret = true; - } else if (matchType == ResourceElementMatchType.PREFIX) { - ret = matchingScope == ResourceElementMatchingScope.SELF_OR_PREFIX; - } else if (matchType == ResourceElementMatchType.CHILD) { - ret = matchingScope == ResourceElementMatchingScope.SELF_OR_CHILD; - } else { - ret = false; - } - return ret; + return isMatch(matchType, matchingScope); } final ResourceElementMatchType getMatchType(String resourceValue, ResourceElementMatchingScope matchingScope, Map evalContext) { @@ -152,6 +146,153 @@ public static boolean startsWithAnyChar(String value, String startChars) { return ret; } + public static boolean isMatch(ResourceElementMatchType matchType, ResourceElementMatchingScope matchingScope) { + final boolean ret; + + switch (matchType) { + case SELF: + ret = true; + break; + + case CHILD: + ret = matchingScope == ResourceElementMatchingScope.SELF_OR_CHILD; + break; + + case PREFIX: + ret = matchingScope == ResourceElementMatchingScope.SELF_OR_PREFIX; + break; + + default: + LOG.error("invalid ResourceElementMatchType: {}}", matchType); + + ret = false; + } + + return ret; + } + + // modified version of FilenameUtils.wildcardMatch(), to check if value is a prefix match for wildcardMatcher + public static boolean wildcardPrefixMatch(String value, String wildcardMatcher, IOCase caseSensitivity) { + if (value == null && wildcardMatcher == null) { + return true; + } else if (value == null || wildcardMatcher == null) { + return false; + } + + if (caseSensitivity == null) { + caseSensitivity = IOCase.SENSITIVE; + } + + List wcsTokens = splitOnTokens(wildcardMatcher); + boolean anyChars = false; + int textIdx = 0; + int wcsIdx = 0; + Stack backtrack = new Stack<>(); + + do { + if (backtrack.size() > 0) { + int[] array = backtrack.pop(); + + wcsIdx = array[0]; + textIdx = array[1]; + anyChars = true; + } + + for(; wcsIdx < wcsTokens.size(); ++wcsIdx) { + String wcsToken = wcsTokens.get(wcsIdx); + + if (wcsToken.equals("?")) { + ++textIdx; + + if (textIdx > value.length()) { + break; + } + + anyChars = false; + } else if (wcsToken.equals("*")) { + anyChars = true; + + if (wcsIdx == wcsTokens.size() - 1) { + textIdx = value.length(); + } + } else { + // changes from FilenameUtils.wildcardMatch(): added following 3 lines to check if value is a prefix match for wildcardMatcher + if (wcsToken.length() > (value.length() - textIdx)) { + wcsToken = wcsToken.substring(0, value.length() - textIdx); + } + + if (anyChars) { + textIdx = caseSensitivity.checkIndexOf(value, textIdx, wcsToken); + + if (textIdx == -1) { + break; + } + + int repeat = caseSensitivity.checkIndexOf(value, textIdx + 1, wcsToken); + + if (repeat >= 0) { + backtrack.push(new int[]{wcsIdx, repeat}); + } + } else if (!caseSensitivity.checkRegionMatches(value, textIdx, wcsToken)) { + break; + } + + textIdx += wcsToken.length(); + + anyChars = false; + } + } + + // changes from FilenameUtils.wildcardMatch(): replaced the condition in 'if' below to check if value is a prefix match for wildcardMatcher + // original if: if (wcsIdx == wcsTokens.size() && textIdx == value.length()) + if (wcsIdx == wcsTokens.size() || textIdx == value.length()) { + return true; + } + } while (backtrack.size() > 0); + + return anyChars; + } + + static List splitOnTokens(String text) { + if (text.indexOf(63) == -1 && text.indexOf(42) == -1) { + return Collections.singletonList(text); + } else { + char[] array = text.toCharArray(); + List list = new ArrayList<>(2); + StringBuilder buffer = new StringBuilder(); + char prevChar = 0; + char[] arr$ = array; + int len$ = array.length; + + for(int i$ = 0; i$ < len$; ++i$) { + char ch = arr$[i$]; + + if (ch != '?' && ch != '*') { + buffer.append(ch); + } else { + if (buffer.length() != 0) { + list.add(buffer.toString()); + buffer.setLength(0); + } + + if (ch == '?') { + list.add("?"); + } else if (prevChar != '*') { + list.add("*"); + } + } + + prevChar = ch; + } + + if (buffer.length() != 0) { + list.add(buffer.toString()); + } + + return list; + } + } + public static class PriorityComparator implements Comparator, Serializable { @Override public int compare(ResourceMatcher me, ResourceMatcher other) { diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcherTest.java b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcherTest.java index 767795e81a..b6864183de 100644 --- a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcherTest.java +++ b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerAbstractResourceMatcherTest.java @@ -19,7 +19,9 @@ package org.apache.ranger.plugin.resourcematcher; +import org.apache.ranger.plugin.policyengine.RangerAccessRequest; import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchingScope; +import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType; import org.junit.Test; import java.util.Map; @@ -47,5 +49,12 @@ public boolean isMatch(Object resource, ResourceElementMatchingScope matchingSco fail("This method is not expected to be used by test!"); return false; } + + @Override + public ResourceElementMatchType getMatchType(Object resource, ResourceElementMatchingScope matchingScope, Map evalContext) { + fail("This method is not expected to be used by test!"); + return RangerAccessRequest.ResourceElementMatchType.NONE; + } + } } diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcherTest.java b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcherTest.java index e00bc834d9..8a297bde06 100644 --- a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcherTest.java +++ b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerDefaultResourceMatcherTest.java @@ -23,6 +23,7 @@ import org.apache.ranger.plugin.model.RangerPolicy; import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef; import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchingScope; +import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType; import org.apache.ranger.plugin.util.RangerAccessRequestUtil; import org.junit.Test; @@ -30,43 +31,132 @@ import java.util.Map; import static org.junit.Assert.*; +import static org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchType.*; public class RangerDefaultResourceMatcherTest { Object[][] data = { - // { resource, policy, excludes, result - { "*", "*", false, true, "user" }, // resource is all values - { "*", "*", true, false, "user" }, - { "*", "a*", false, false, "user" }, // but, policy is not match any - { "*", "a*", true, false, "user" }, // ==> compare with above: exclude flag has no effect here - { "a*", "a", false, false, "user" }, // resource has regex marker! - { "a*", "a", true, true, "user" }, - { "a", "a", false, true, "user" }, // exact match - { "a", "a", true, false, "user" }, - { "a1", "a*", false, true, "user" }, // trivial regex match - { "a1", "a*", true, false, "user" }, + // { resource, policy, excludes, matchType, result, user } + { "*", "*", false, SELF, true, "user" }, // resource is all values + { "*", "*", true, NONE, false, "user" }, + { "*", "a*", false, NONE, false, "user" }, // but, policy is not match any + { "*", "a*", true, NONE, false, "user" }, // ==> compare with above: exclude flag has no effect here + { "a*", "a", false, NONE, false, "user" }, // resource has regex marker! + { "a*", "a", true, SELF, true, "user" }, + { "a", "a", false, SELF, true, "user" }, // exact match + { "a", "a", true, NONE, false, "user" }, + { "a1", "a*", false, SELF, true, "user" }, // trivial regex match + { "a1", "a*", true, NONE, false, "user" }, + + // matchScope=SELF, excludes=false + { "*", "*", false, SELF, true, "user" }, // resource is all values + { "a*", "*", false, SELF, true, "user" }, // resource has regex, policy matches + { "a*", "a", false, NONE, false, "user" }, // resource has regex, policy does not match + { "*", "a*", false, NONE, false, "user" }, // resource has regex, policy does not match + { "a*", "a*", false, SELF, true, "user" }, // resource has regex, policy matches + { "a?", "a*", false, SELF, true, "user" }, // resource has regex, policy matches + { "a*b", "a*", false, SELF, true, "user" }, // resource has regex, policy matches + { "a?b", "a*", false, SELF, true, "user" }, // resource has regex, policy matches + { "a*b", "a*b", false, SELF, true, "user" }, // resource has regex, policy matches + { "a?b", "a?b", false, SELF, true, "user" }, // resource has regex, policy matches + { "a1b", "a1b", false, SELF, true, "user" }, // exact match + { "a1b", "a*", false, SELF, true, "user" }, // regex match - suffix + { "a1b", "*b", false, SELF, true, "user" }, // regex match - prefix + { "a1b", "*1*", false, SELF, true, "user" }, // regex match + { "a1b", "a?b", false, SELF, true, "user" }, // regex match - single char + { "a", "abc", false, NONE, false, "user" }, // policy has more than resource + { "ab", "abc", false, NONE, false, "user" }, // policy has more than resource + { "ab", "*c", false, NONE, false, "user" }, // policy has more than resource + { "*b", "a*bc", false, NONE, false, "user" }, // policy has more than resource + { "a*b", "a*bc", false, NONE, false, "user" }, // policy has more than resource + + // matchScope=SELF, excludes=true + { "*", "*", true, NONE, false, "user" }, + { "a*", "*", true, NONE, false, "user" }, + { "a*", "a", true, SELF, true, "user" }, + { "*", "a*", true, NONE, false, "user" }, // ==> compare with above: exclude flag has no effect here + { "a*", "a*", true, NONE, false, "user" }, + { "a?", "a*", true, NONE, false, "user" }, + { "a*b", "a*", true, NONE, false, "user" }, + { "a?b", "a*", true, NONE, false, "user" }, + { "a*b", "a*b", true, NONE, false, "user" }, + { "a?b", "a?b", true, NONE, false, "user" }, + { "a1b", "a1b", true, NONE, false, "user" }, + { "a1b", "a*", true, NONE, false, "user" }, + { "a1b", "*b", true, NONE, false, "user" }, + { "a1b", "*1*", true, NONE, false, "user" }, + { "a1b", "a?b", true, NONE, false, "user" }, + { "a", "abc", true, SELF, true, "user" }, + { "ab", "abc", true, SELF, true, "user" }, + { "ab", "*c", true, SELF, true, "user" }, + { "*b", "a*bc", true, SELF, true, "user" }, + { "a*b", "a*bc", true, SELF, true, "user" }, + }; + + Object[][] dataForPrefixMatch = { + // { resource, policy, excludes, matchType, result, user } + { "a", "abc", false, PREFIX, true, "user" }, + { "ab", "abc", false, PREFIX, true, "user" }, + { "ab", "*c", false, PREFIX, true, "user" }, + { "a", "a*c", false, PREFIX, true, "user" }, + { "ab", "a*c", false, PREFIX, true, "user" }, + { "abc", "a*c", false, SELF, true, "user" }, + { "abcd", "a*c", false, PREFIX, true, "user" }, + { "abcd", "a*c*d", false, SELF, true, "user" }, + { "abcd", "a*c*de", false, PREFIX, true, "user" }, + { "acbd", "ab*c", false, NONE, false, "user" }, + { "b", "ab*c", false, NONE, false, "user" }, + { "a", "ab*", false, PREFIX, true, "user" }, + { "b", "ab*", false, NONE, false, "user" }, }; @Test public void testIsMatch() throws Exception { + ResourceElementMatchingScope matchScope = ResourceElementMatchingScope.SELF; + for (Object[] row : data) { - String resource = (String)row[0]; - String policyValue = (String)row[1]; - boolean excludes = (boolean)row[2]; - boolean result = (boolean)row[3]; - String user = (String) row[4]; + String resource = (String)row[0]; + String policyValue = (String)row[1]; + boolean excludes = (boolean)row[2]; + ResourceElementMatchType matchType = (ResourceElementMatchType) row[3]; + boolean result = (boolean)row[4]; + String user = (String) row[5]; + Map evalContext = new HashMap<>(); - Map evalContext = new HashMap<>(); RangerAccessRequestUtil.setCurrentUserInContext(evalContext, user); MatcherWrapper matcher = new MatcherWrapper(policyValue, excludes); - assertEquals(getMessage(row), result, matcher.isMatch(resource, ResourceElementMatchingScope.SELF, evalContext)); + + assertEquals(getMessage(row), matchType, matcher.getMatchType(resource, matchScope, evalContext)); + assertEquals(getMessage(row), result, matcher.isMatch(resource, matchScope, evalContext)); + } + } + + @Test + public void testIsPrefixMatch() { + ResourceElementMatchingScope matchScope = ResourceElementMatchingScope.SELF_OR_PREFIX; + + for (Object[] row : dataForPrefixMatch) { + String resource = (String)row[0]; + String policyValue = (String)row[1]; + boolean excludes = (boolean)row[2]; + ResourceElementMatchType matchType = (ResourceElementMatchType) row[3]; + boolean result = (boolean)row[4]; + String user = (String) row[5]; + Map evalContext = new HashMap<>(); + + RangerAccessRequestUtil.setCurrentUserInContext(evalContext, user); + + MatcherWrapper matcher = new MatcherWrapper(policyValue, excludes); + + assertEquals(getMessage(row), matchType, matcher.getMatchType(resource, matchScope, evalContext)); + assertEquals(getMessage(row), result, matcher.isMatch(resource, matchScope, evalContext)); } } String getMessage(Object[] row) { - return String.format("Resource=%s, Policy=%s, excludes=%s, result=%s", - (String)row[0], (String)row[1], (boolean)row[2], (boolean)row[3]); + return String.format("Resource=%s, Policy=%s, excludes=%s, matchScope=%s, matchType=%s, result=%s", + row[0], row[1], row[2], row[3], row[4], row[5]); } static class MatcherWrapper extends RangerDefaultResourceMatcher { @@ -74,7 +164,7 @@ static class MatcherWrapper extends RangerDefaultResourceMatcher { RangerResourceDef resourceDef = new RangerResourceDef(); Map matcherOptions = new HashMap<>(); - matcherOptions.put(OPTION_WILD_CARD, Boolean.toString(policyValue.contains(WILDCARD_ASTERISK))); + matcherOptions.put(OPTION_WILD_CARD, Boolean.toString(policyValue.contains(WILDCARD_ASTERISK) || policyValue.contains(WILDCARD_QUESTION_MARK))); matcherOptions.put(OPTION_IGNORE_CASE, Boolean.toString(false)); resourceDef.setMatcherOptions(matcherOptions); diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcherTest.java b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcherTest.java index ed02be6748..3727b30d42 100644 --- a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcherTest.java +++ b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerPathResourceMatcherTest.java @@ -94,6 +94,59 @@ public class RangerPathResourceMatcherTest { { "/app/hbase/test.db", "/app/hbase/test.db/tmp/test.t*", true, false, false, "user" }, }; + Object[][] dataForSelfOrPrefixScope = { + // { resource, policy, optWildcard, recursive, result + { "/", "/app/hive/test.db", true, false, true, "user" }, + { "/app", "/app/hive/test.db", true, false, true, "user" }, + { "/app/", "/app/hive/test.db", true, false, true, "user" }, + { "/app/hive", "/app/hive/test.db", true, false, true, "user" }, + { "/app/hive/", "/app/hive/test.db", true, false, true, "user" }, + { "/app/hive/test.db", "/app/hive/test.db", true, false, true, "user" }, + { "/", "/app/*/test.db", true, false, true, "user" }, + { "/app", "/app/*/test.db", true, false, true, "user" }, + { "/app/", "/app/*/test.db", true, false, true, "user" }, + { "/app/hive", "/app/*/test.db", true, false, true, "user" }, + { "/app/hive/", "/app/*/test.db", true, false, true, "user" }, + { "/app/hive/test.db", "/app/*/test.db", true, false, true, "user" }, + { "/", "*/hive/test.db", true, false, true, "user" }, + { "/app", "*/hive/test.db", true, false, true, "user" }, + { "/app/", "*/hive/test.db", true, false, true, "user" }, + { "/app/hive", "*/hive/test.db", true, false, true, "user" }, + { "/app/hive/", "*/hive/test.db", true, false, true, "user" }, + { "/app/hive/test.db", "*/hive/test.db", true, false, true, "user" }, + { "/", "/*", true, false, true, "user" }, + { "/app", "/*", true, false, true, "user" }, + { "/app/", "/*", true, false, true, "user" }, + { "/app/hive", "/*", true, false, true, "user" }, + { "/app/hive/", "/*", true, false, true, "user" }, + { "/app/hive/test.db", "/*", true, false, true, "user" }, + + { "/", "/app/hive/test.db", true, true, true, "user" }, + { "/app", "/app/hive/test.db", true, true, true, "user" }, + { "/app/", "/app/hive/test.db", true, true, true, "user" }, + { "/app/hive", "/app/hive/test.db", true, true, true, "user" }, + { "/app/hive/", "/app/hive/test.db", true, true, true, "user" }, + { "/app/hive/test.db", "/app/hive/test.db", true, true, true, "user" }, + { "/", "/app/*/test.db", true, true, true, "user" }, + { "/app", "/app/*/test.db", true, true, true, "user" }, + { "/app/", "/app/*/test.db", true, true, true, "user" }, + { "/app/hive", "/app/*/test.db", true, true, true, "user" }, + { "/app/hive/", "/app/*/test.db", true, true, true, "user" }, + { "/app/hive/test.db", "/app/*/test.db", true, true, true, "user" }, + { "/", "*/hive/test.db", true, true, true, "user" }, + { "/app", "*/hive/test.db", true, true, true, "user" }, + { "/app/", "*/hive/test.db", true, true, true, "user" }, + { "/app/hive", "*/hive/test.db", true, true, true, "user" }, + { "/app/hive/", "*/hive/test.db", true, true, true, "user" }, + { "/app/hive/test.db", "*/hive/test.db", true, true, true, "user" }, + { "/", "/", true, true, true, "user" }, + { "/app", "/", true, true, true, "user" }, + { "/app/", "/", true, true, true, "user" }, + { "/app/hive", "/", true, true, true, "user" }, + { "/app/hive/", "/", true, true, true, "user" }, + { "/app/hive/test.db", "/", true, true, true, "user" }, + }; + @Test public void testIsMatch() throws Exception { for (Object[] row : data) { @@ -130,6 +183,26 @@ public void testIsMatchForSelfOrChildScope() throws Exception { } } + @Test + public void testIsMatchForSelfOrPrefixScope() { + ResourceElementMatchingScope matchScope = ResourceElementMatchingScope.SELF_OR_PREFIX; + + for (Object[] row : dataForSelfOrPrefixScope) { + String resource = (String)row[0]; + String policyValue = (String)row[1]; + boolean optWildcard = (boolean)row[2]; + boolean isRecursive = (boolean)row[3]; + boolean result = (boolean)row[4]; + String user = (String) row[5]; + Map evalContext = new HashMap<>(); + + RangerAccessRequestUtil.setCurrentUserInContext(evalContext, user); + + MatcherWrapper matcher = new MatcherWrapper(policyValue, optWildcard, isRecursive); + assertEquals(getMessage(row), result, matcher.isMatch(resource, matchScope, evalContext)); + } + } + String getMessage(Object[] row) { return String.format("Resource=%s, Policy=%s, optWildcard=%s, recursive=%s, result=%s", (String)row[0], (String)row[1], (boolean)row[2], (boolean)row[3], (boolean)row[4]); diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcherTest.java b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcherTest.java index 904cb61b28..cc73076154 100644 --- a/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcherTest.java +++ b/agents-common/src/test/java/org/apache/ranger/plugin/resourcematcher/RangerURLResourceMatcherTest.java @@ -53,6 +53,60 @@ public class RangerURLResourceMatcherTest { { "://apps/warehouse/data/emp.db", "hdfs://app/*", true, true, false, "user" } }; + + Object[][] dataForSelfOrPrefixScope = { + // { resource, policy, optWildcard, recursive, result + { "hdfs://hostname:8020/", "hdfs://hostname:8020/app/hive/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app", "hdfs://hostname:8020/app/hive/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app/", "hdfs://hostname:8020/app/hive/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app/hive", "hdfs://hostname:8020/app/hive/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app/hive/", "hdfs://hostname:8020/app/hive/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020/app/hive/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/", "hdfs://hostname:8020/app/*/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app", "hdfs://hostname:8020/app/*/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app/", "hdfs://hostname:8020/app/*/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app/hive", "hdfs://hostname:8020/app/*/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app/hive/", "hdfs://hostname:8020/app/*/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020/app/*/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/", "hdfs://hostname:8020*/hive/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app", "hdfs://hostname:8020*/hive/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app/", "hdfs://hostname:8020*/hive/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app/hive", "hdfs://hostname:8020*/hive/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app/hive/", "hdfs://hostname:8020*/hive/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020*/hive/test.db", true, false, true, "user" }, + { "hdfs://hostname:8020/", "hdfs://hostname:8020/*", true, false, true, "user" }, + { "hdfs://hostname:8020/app", "hdfs://hostname:8020/*", true, false, true, "user" }, + { "hdfs://hostname:8020/app/", "hdfs://hostname:8020/*", true, false, true, "user" }, + { "hdfs://hostname:8020/app/hive", "hdfs://hostname:8020/*", true, false, true, "user" }, + { "hdfs://hostname:8020/app/hive/", "hdfs://hostname:8020/*", true, false, true, "user" }, + { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020/*", true, false, true, "user" }, + + { "hdfs://hostname:8020/", "hdfs://hostname:8020/app/hive/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app", "hdfs://hostname:8020/app/hive/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app/", "hdfs://hostname:8020/app/hive/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app/hive", "hdfs://hostname:8020/app/hive/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app/hive/", "hdfs://hostname:8020/app/hive/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020/app/hive/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/", "hdfs://hostname:8020/app/*/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app", "hdfs://hostname:8020/app/*/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app/", "hdfs://hostname:8020/app/*/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app/hive", "hdfs://hostname:8020/app/*/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app/hive/", "hdfs://hostname:8020/app/*/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020/app/*/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/", "hdfs://hostname:8020*/hive/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app", "hdfs://hostname:8020*/hive/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app/", "hdfs://hostname:8020*/hive/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app/hive", "hdfs://hostname:8020*/hive/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app/hive/", "hdfs://hostname:8020*/hive/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020*/hive/test.db", true, true, true, "user" }, + { "hdfs://hostname:8020/", "hdfs://hostname:8020/", true, true, true, "user" }, + { "hdfs://hostname:8020/app", "hdfs://hostname:8020/", true, true, true, "user" }, + { "hdfs://hostname:8020/app/", "hdfs://hostname:8020/", true, true, true, "user" }, + { "hdfs://hostname:8020/app/hive", "hdfs://hostname:8020/", true, true, true, "user" }, + { "hdfs://hostname:8020/app/hive/", "hdfs://hostname:8020/", true, true, true, "user" }, + { "hdfs://hostname:8020/app/hive/test.db", "hdfs://hostname:8020/", true, true, true, "user" }, + }; + @Test public void testIsMatch() throws Exception { for (Object[] row : data) { @@ -71,6 +125,25 @@ public void testIsMatch() throws Exception { } } + @Test + public void testIsPrefixMatch() { + for (Object[] row : dataForSelfOrPrefixScope) { + String resource = (String)row[0]; + String policyValue = (String)row[1]; + boolean optWildcard = (boolean)row[2]; + boolean isRecursive = (boolean)row[3]; + boolean result = (boolean)row[4]; + String user = (String) row[5]; + + Map evalContext = new HashMap<>(); + + RangerAccessRequestUtil.setCurrentUserInContext(evalContext, user); + + MatcherWrapper matcher = new MatcherWrapper(policyValue, optWildcard, isRecursive); + assertEquals(getMessage(row), result, matcher.isMatch(resource, ResourceElementMatchingScope.SELF_OR_PREFIX, evalContext)); + } + } + String getMessage(Object[] row) { return String.format("Resource=%s, Policy=%s, optWildcard=%s, recursive=%s, result=%s", (String)row[0], (String)row[1], (boolean)row[2], (boolean)row[3], (boolean)row[4]);