/* * 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.util; import org.apache.commons.collections.CollectionUtils; 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; import org.apache.ranger.plugin.policyresourcematcher.RangerPolicyResourceEvaluator; import org.apache.ranger.plugin.resourcematcher.RangerAbstractResourceMatcher; import org.apache.ranger.plugin.resourcematcher.RangerResourceMatcher; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; public class RangerResourceTrie<T extends RangerPolicyResourceEvaluator> { private static final Log LOG = LogFactory.getLog(RangerResourceTrie.class); private static final String DEFAULT_WILDCARD_CHARS = "*?"; private final String resourceName; private final boolean optIgnoreCase; private final boolean optWildcard; private final String wildcardChars; private final TrieNode root; public RangerResourceTrie(RangerServiceDef.RangerResourceDef resourceDef, List<T> evaluators) { if(LOG.isDebugEnabled()) { LOG.debug("==> RangerResourceTrie(" + resourceDef.getName() + ", evaluatorCount=" + evaluators.size() + ")"); } Map<String, String> matcherOptions = resourceDef.getMatcherOptions(); boolean optReplaceTokens = RangerAbstractResourceMatcher.getOptionReplaceTokens(matcherOptions); String tokenReplaceSpecialChars = ""; if(optReplaceTokens) { char delimiterStart = RangerAbstractResourceMatcher.getOptionDelimiterStart(matcherOptions); char delimiterEnd = RangerAbstractResourceMatcher.getOptionDelimiterEnd(matcherOptions); char delimiterEscape = RangerAbstractResourceMatcher.getOptionDelimiterEscape(matcherOptions); tokenReplaceSpecialChars += delimiterStart; tokenReplaceSpecialChars += delimiterEnd; tokenReplaceSpecialChars += delimiterEscape; } this.resourceName = resourceDef.getName(); this.optIgnoreCase = RangerAbstractResourceMatcher.getOptionIgnoreCase(matcherOptions); this.optWildcard = RangerAbstractResourceMatcher.getOptionWildCard(matcherOptions); this.wildcardChars = optWildcard ? DEFAULT_WILDCARD_CHARS + tokenReplaceSpecialChars : "" + tokenReplaceSpecialChars; this.root = new TrieNode(Character.valueOf((char)0)); for(T evaluator : evaluators) { Map<String, RangerPolicyResource> policyResources = evaluator.getPolicyResource(); RangerPolicyResource policyResource = policyResources != null ? policyResources.get(resourceName) : null; if(policyResource == null) { if(evaluator.getLeafResourceLevel() != null && resourceDef.getLevel() != null && evaluator.getLeafResourceLevel() < resourceDef.getLevel()) { root.addWildcardEvaluator(evaluator); } continue; } if(policyResource.getIsExcludes()) { root.addWildcardEvaluator(evaluator); } else { RangerResourceMatcher resourceMatcher = evaluator.getResourceMatcher(resourceName); if(resourceMatcher != null && (resourceMatcher.isMatchAny())) { root.addWildcardEvaluator(evaluator); } else { if(CollectionUtils.isNotEmpty(policyResource.getValues())) { for (String resource : policyResource.getValues()) { insert(resource, policyResource.getIsRecursive(), evaluator); } } } } } root.postSetup(null); LOG.info(toString()); if(LOG.isDebugEnabled()) { LOG.debug("<== RangerResourceTrie(" + resourceDef.getName() + ", evaluatorCount=" + evaluators.size() + "): " + toString()); } } public String getResourceName() { return resourceName; } public List<T> getEvaluatorsForResource(String resource) { if(LOG.isDebugEnabled()) { LOG.debug("==> RangerResourceTrie.getEvaluatorsForResource(" + resource + ")"); } List<T> ret = null; TrieNode curr = root; final int len = resource.length(); for(int i = 0; i < len; i++) { Character ch = getLookupChar(resource.charAt(i)); TrieNode child = curr.getChild(ch); if(child == null) { ret = curr.getWildcardEvaluators(); curr = null; // so that curr.getEvaluators() will not be called below break; } curr = child; } if(ret == null) { if(curr != null) { ret = curr.getEvaluators(); } } if(LOG.isDebugEnabled()) { LOG.debug("<== RangerResourceTrie.getEvaluatorsForResource(" + resource + "): evaluatorCount=" + (ret == null ? 0 : ret.size())); } return ret; } public TrieData getTrieData() { TrieData ret = new TrieData(); root.populateTrieData(ret); ret.maxDepth = getMaxDepth(); return ret; } public int getMaxDepth() { return root.getMaxDepth(); } public void reorderEvaluators() { root.reorderEvaluators(null); } private final Character getLookupChar(char ch) { if(optIgnoreCase) { ch = Character.toLowerCase(ch); } return Character.valueOf(ch); } private void insert(String resource, boolean isRecursive, T evaluator) { TrieNode curr = root; boolean isWildcard = false; final int len = resource.length(); for(int i = 0; i < len; i++) { Character ch = getLookupChar(resource.charAt(i)); if(optWildcard) { if (wildcardChars.indexOf(ch) != -1) { isWildcard = true; break; } } curr = curr.getOrCreateChild(ch); } if(isWildcard || isRecursive) { curr.addWildcardEvaluator(evaluator); } else { curr.addEvaluator(evaluator); } } @Override public String toString() { StringBuilder sb = new StringBuilder(); TrieData trieData = getTrieData(); sb.append("resourceName=").append(resourceName); sb.append("; optIgnoreCase=").append(optIgnoreCase); sb.append("; optWildcard=").append(optWildcard); sb.append("; wildcardChars=").append(wildcardChars); sb.append("; nodeCount=").append(trieData.nodeCount); sb.append("; leafNodeCount=").append(trieData.leafNodeCount); sb.append("; singleChildNodeCount=").append(trieData.singleChildNodeCount); sb.append("; maxDepth=").append(trieData.maxDepth); sb.append("; evaluatorListCount=").append(trieData.evaluatorListCount); sb.append("; wildcardEvaluatorListCount=").append(trieData.wildcardEvaluatorListCount); sb.append("; evaluatorListRefCount=").append(trieData.evaluatorListRefCount); sb.append("; wildcardEvaluatorListRefCount=").append(trieData.wildcardEvaluatorListRefCount); return sb.toString(); } public class TrieData { int nodeCount; int leafNodeCount; int singleChildNodeCount; int maxDepth; int evaluatorListCount; int wildcardEvaluatorListCount; int evaluatorListRefCount; int wildcardEvaluatorListRefCount; } } class TrieNode<T extends RangerPolicyResourceEvaluator> { private final Character c; private Map<Character, TrieNode> children; private List<T> evaluators; private List<T> wildcardEvaluators; private boolean isSharingParentWildcardEvaluators; TrieNode(Character c) { this.c = c; } Character getChar() { return c; } Map<Character, TrieNode> getChildren() { return children; } List<T> getEvaluators() { return evaluators; } List<T> getWildcardEvaluators() { return wildcardEvaluators; } TrieNode getChild(Character c) { TrieNode ret = children == null ? null : children.get(c); return ret; } void populateTrieData(RangerResourceTrie.TrieData trieData) { trieData.nodeCount++; if(wildcardEvaluators != null) { if(isSharingParentWildcardEvaluators) { trieData.wildcardEvaluatorListRefCount++; } else { trieData.wildcardEvaluatorListCount++; } } if(evaluators != null) { if(evaluators == wildcardEvaluators) { trieData.evaluatorListRefCount++; } else { trieData.evaluatorListCount++; } } if(children != null && !children.isEmpty()) { if(children.size() == 1) { trieData.singleChildNodeCount++; } for(Map.Entry<Character, TrieNode> entry : children.entrySet()) { TrieNode child = entry.getValue(); child.populateTrieData(trieData); } } else { trieData.leafNodeCount++; } } int getMaxDepth() { int ret = 0; if(children != null) { for(Map.Entry<Character, TrieNode> entry : children.entrySet()) { TrieNode child = entry.getValue(); int maxChildDepth = child.getMaxDepth(); if(maxChildDepth > ret) { ret = maxChildDepth; } } } return ret + 1; } TrieNode getOrCreateChild(Character c) { if(children == null) { children = new HashMap<>(); } TrieNode child = children.get(c); if(child == null) { child = new TrieNode(c); children.put(c, child); } return child; } void addEvaluator(T evaluator) { if(evaluators == null) { evaluators = new ArrayList<>(); } if(!evaluators.contains(evaluator)) { evaluators.add(evaluator); } } void addWildcardEvaluator(T evaluator) { if(wildcardEvaluators == null) { wildcardEvaluators = new ArrayList<>(); } if(!wildcardEvaluators.contains(evaluator)) { wildcardEvaluators.add(evaluator); } } void postSetup(List<T> parentWildcardEvaluators) { // finalize wildcard-evaluators list by including parent's wildcard evaluators if(parentWildcardEvaluators != null) { if(CollectionUtils.isEmpty(this.wildcardEvaluators)) { this.wildcardEvaluators = parentWildcardEvaluators; } else { for (T evaluator : parentWildcardEvaluators) { addWildcardEvaluator(evaluator); } } } this.isSharingParentWildcardEvaluators = wildcardEvaluators == parentWildcardEvaluators; // finalize evaluators list by including wildcard evaluators if(wildcardEvaluators != null) { if(CollectionUtils.isEmpty(this.evaluators)) { this.evaluators = wildcardEvaluators; } else { for (T evaluator : wildcardEvaluators) { addEvaluator(evaluator); } } } if(!isSharingParentWildcardEvaluators && CollectionUtils.isNotEmpty(wildcardEvaluators)) { Collections.sort(wildcardEvaluators); } if(evaluators != wildcardEvaluators && CollectionUtils.isNotEmpty(evaluators)) { Collections.sort(evaluators); } if(children != null) { for(Map.Entry<Character, TrieNode> entry : children.entrySet()) { TrieNode child = entry.getValue(); child.postSetup(wildcardEvaluators); } } } void reorderEvaluators(List<T> parentWildcardEvaluators) { boolean isEvaluatorsSameAsWildcardEvaluators = evaluators == wildcardEvaluators; if(isSharingParentWildcardEvaluators) { wildcardEvaluators = parentWildcardEvaluators; } else { wildcardEvaluators = getSortedCopy(wildcardEvaluators); } if(isEvaluatorsSameAsWildcardEvaluators) { evaluators = wildcardEvaluators; } else { evaluators = getSortedCopy(evaluators); } if(children != null) { for(Map.Entry<Character, TrieNode> entry : children.entrySet()) { TrieNode child = entry.getValue(); child.reorderEvaluators(wildcardEvaluators); } } } public void toString(String prefix, StringBuilder sb) { String nodeValue = prefix; if(c != 0) { nodeValue += c; } sb.append("nodeValue=").append(nodeValue); sb.append("; childCount=").append(children == null ? 0 : children.size()); sb.append("; evaluators=[ "); if(evaluators != null) { for(T evaluator : evaluators) { sb.append(evaluator.getId()).append(" "); } } sb.append("]"); sb.append("; wildcardEvaluators=[ "); if(wildcardEvaluators != null) { for(T evaluator : wildcardEvaluators) { sb.append(evaluator.getId()).append(" "); } } sb.append("]"); sb.append(Character.LINE_SEPARATOR); if(children != null) { for(Map.Entry<Character, TrieNode> entry : children.entrySet()) { TrieNode child = entry.getValue(); child.toString(nodeValue, sb); } } } public void clear() { children = null; evaluators = null; wildcardEvaluators = null; } private List<T> getSortedCopy(List<T> evaluators) { final List<T> ret; if(CollectionUtils.isNotEmpty(evaluators)) { ret = new ArrayList<>(evaluators); Collections.sort(ret); } else { ret = evaluators; } return ret; } }