blob: 40f3df98f062a5b8fa729a40be2187526efe006e [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 org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ranger.plugin.util.ServiceDefUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RangerURLResourceMatcher extends RangerDefaultResourceMatcher {
private static final Log LOG = LogFactory.getLog(RangerURLResourceMatcher.class);
public static final String OPTION_PATH_SEPARATOR = "pathSeparatorChar";
public static final char DEFAULT_PATH_SEPARATOR_CHAR = org.apache.hadoop.fs.Path.SEPARATOR_CHAR;
boolean policyIsRecursive;
char pathSeparatorChar = DEFAULT_PATH_SEPARATOR_CHAR;
@Override
public void init() {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerURLResourceMatcher.init()");
}
Map<String, String> options = resourceDef == null ? null : resourceDef.getMatcherOptions();
policyIsRecursive = policyResource != null && policyResource.getIsRecursive();
pathSeparatorChar = ServiceDefUtil.getCharOption(options, OPTION_PATH_SEPARATOR, DEFAULT_PATH_SEPARATOR_CHAR);
super.init();
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerURLResourceMatcher.init()");
}
}
@Override
protected ResourceMatcherWrapper buildResourceMatchers() {
List<ResourceMatcher> resourceMatchers = new ArrayList<>();
boolean needsDynamicEval = false;
for (String policyValue : policyValues) {
if (optWildCard && policyIsRecursive) {
if (policyValue.charAt(policyValue.length() - 1) == pathSeparatorChar) {
policyValue += WILDCARD_ASTERISK;
}
}
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, new ResourceMatcher.PriorityComparator());
return CollectionUtils.isNotEmpty(resourceMatchers) ?
new ResourceMatcherWrapper(needsDynamicEval, resourceMatchers) : null;
}
@Override
ResourceMatcher getMatcher(String policyValue) {
if(! policyIsRecursive) {
return super.getMatcher(policyValue);
}
final int len = policyValue != null ? policyValue.length() : 0;
if (len == 0) {
return null;
}
// To ensure that when policyValue is single '*', ResourceMatcher created here returns true for isMatchAny()
if (optWildCard && WILDCARD_ASTERISK.equals(policyValue)) {
return new CaseInsensitiveStringMatcher("");
}
boolean isWildcardPresent = false;
if (optWildCard) {
for (int i = 0; i < len; i++) {
final char c = policyValue.charAt(i);
if (c == '?' || c == '*') {
isWildcardPresent = true;
break;
}
}
}
final ResourceMatcher ret;
if (isWildcardPresent) {
ret = optIgnoreCase ? new CaseInsensitiveURLRecursiveWildcardMatcher(policyValue, pathSeparatorChar)
: new CaseSensitiveURLRecursiveWildcardMatcher(policyValue, pathSeparatorChar);
} else {
ret = optIgnoreCase ? new CaseInsensitiveURLRecursiveMatcher(policyValue, pathSeparatorChar) : new CaseSensitiveURLRecursiveMatcher(policyValue, pathSeparatorChar);
}
if (optReplaceTokens) {
ret.setDelimiters(startDelimiterChar, endDelimiterChar, escapeChar, tokenPrefix);
}
return ret;
}
static boolean isRecursiveWildCardMatch(String pathToCheck, String wildcardPath, char pathSeparatorChar, IOCase caseSensitivity) {
boolean ret = false;
String url = StringUtils.trim(pathToCheck);
if (!StringUtils.isEmpty(url) && isPathURLType(url)) {
String scheme = getScheme(url);
if (StringUtils.isEmpty(scheme)) {
return ret;
}
String path = getPathWithOutScheme(url);
String[] pathElements = StringUtils.split(path, pathSeparatorChar);
if (!ArrayUtils.isEmpty(pathElements)) {
StringBuilder sb = new StringBuilder();
sb.append(scheme);
if (pathToCheck.charAt(0) == pathSeparatorChar) {
sb.append(pathSeparatorChar); // preserve the initial pathSeparatorChar
}
for (String p : pathElements) {
sb.append(p);
ret = FilenameUtils.wildcardMatch(sb.toString(), wildcardPath, caseSensitivity);
if (ret) {
break;
}
sb.append(pathSeparatorChar);
}
sb = null;
} else { // pathToCheck consists of only pathSeparatorChar
ret = FilenameUtils.wildcardMatch(pathToCheck, wildcardPath, caseSensitivity);
}
}
return ret;
}
public StringBuilder toString(StringBuilder sb) {
sb.append("RangerURLResourceMatcher={");
super.toString(sb);
sb.append("policyIsRecursive={").append(policyIsRecursive).append("} ");
sb.append("}");
return sb;
}
static boolean isPathURLType(String url) {
Pattern p1 = Pattern.compile(":/{2}");
Matcher m1 = p1.matcher(url);
Pattern p2 = Pattern.compile(":/{3,}");
Matcher m2 = p2.matcher(url);
return (m1.find() && !(m2.find()));
}
static String getScheme(String url){
return StringUtils.substring(url,0,(StringUtils.indexOf(url,":") + 3));
}
static String getPathWithOutScheme(String url) {
return StringUtils.substring(url,(StringUtils.indexOf(url,":") + 2));
}
}
final class CaseSensitiveURLRecursiveWildcardMatcher extends ResourceMatcher {
private final char levelSeparatorChar;
CaseSensitiveURLRecursiveWildcardMatcher(String value, char levelSeparatorChar) {
super(value);
this.levelSeparatorChar = levelSeparatorChar;
}
@Override
boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
return RangerURLResourceMatcher.isRecursiveWildCardMatch(resourceValue, getExpandedValue(evalContext), levelSeparatorChar, IOCase.SENSITIVE);
}
int getPriority() { return 7 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);}
}
final class CaseInsensitiveURLRecursiveWildcardMatcher extends ResourceMatcher {
private final char levelSeparatorChar;
CaseInsensitiveURLRecursiveWildcardMatcher(String value, char levelSeparatorChar) {
super(value);
this.levelSeparatorChar = levelSeparatorChar;
}
@Override
boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
return RangerURLResourceMatcher.isRecursiveWildCardMatch(resourceValue, getExpandedValue(evalContext), levelSeparatorChar, IOCase.INSENSITIVE);
}
int getPriority() { return 8 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);}
}
final class CaseSensitiveURLRecursiveMatcher extends RecursiveMatcher {
CaseSensitiveURLRecursiveMatcher(String value, char levelSeparatorChar) {
super(value, levelSeparatorChar);
}
@Override
boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
final String noSeparator;
if (getNeedsDynamicEval()) {
String expandedPolicyValue = getExpandedValue(evalContext);
noSeparator = expandedPolicyValue != null ? getStringToCompare(expandedPolicyValue) : null;
} else {
if (valueWithoutSeparator == null && value != null) {
valueWithoutSeparator = getStringToCompare(value);
valueWithSeparator = valueWithoutSeparator + Character.toString(levelSeparatorChar);
}
noSeparator = valueWithoutSeparator;
}
boolean ret = StringUtils.equals(resourceValue, noSeparator);
if (!ret && noSeparator != null) {
final String withSeparator = getNeedsDynamicEval() ? noSeparator + Character.toString(levelSeparatorChar) : valueWithSeparator;
ret = StringUtils.startsWith(resourceValue, withSeparator);
}
return ret;
}
int getPriority() { return 7 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);}
}
final class CaseInsensitiveURLRecursiveMatcher extends RecursiveMatcher {
CaseInsensitiveURLRecursiveMatcher(String value, char levelSeparatorChar) {
super(value, levelSeparatorChar);
}
@Override
boolean isMatch(String resourceValue, Map<String, Object> evalContext) {
final String noSeparator;
if (getNeedsDynamicEval()) {
String expandedPolicyValue = getExpandedValue(evalContext);
noSeparator = expandedPolicyValue != null ? getStringToCompare(expandedPolicyValue) : null;
} else {
if (valueWithoutSeparator == null && value != null) {
valueWithoutSeparator = getStringToCompare(value);
valueWithSeparator = valueWithoutSeparator + Character.toString(levelSeparatorChar);
}
noSeparator = valueWithoutSeparator;
}
boolean ret = StringUtils.equalsIgnoreCase(resourceValue, noSeparator);
if (!ret && noSeparator != null) {
final String withSeparator = getNeedsDynamicEval() ? noSeparator + Character.toString(levelSeparatorChar) : valueWithSeparator;
ret = StringUtils.startsWithIgnoreCase(resourceValue, withSeparator);
}
return ret;
}
int getPriority() { return 8 + (getNeedsDynamicEval() ? DYNAMIC_EVALUATION_PENALTY : 0);}
}