github.com/aporeto-inc/trireme-lib@v10.358.0+incompatible/controller/pkg/urisearch/urisearch.go (about)

     1  package urisearch
     2  
     3  import (
     4  	"go.aporeto.io/enforcerd/trireme-lib/policy"
     5  )
     6  
     7  type node struct {
     8  	children map[string]*node
     9  	leaf     bool
    10  	data     interface{}
    11  }
    12  
    13  // APICache represents an API cache.
    14  type APICache struct {
    15  	methodRoots map[string]*node
    16  	ID          string
    17  	External    bool
    18  }
    19  
    20  type scopeRule struct {
    21  	rule *policy.HTTPRule
    22  }
    23  
    24  // NewAPICache creates a new API cache
    25  func NewAPICache(rules []*policy.HTTPRule, id string, external bool) *APICache {
    26  	a := &APICache{
    27  		methodRoots: map[string]*node{},
    28  		ID:          id,
    29  		External:    external,
    30  	}
    31  
    32  	for _, rule := range rules {
    33  		sc := &scopeRule{
    34  			rule: rule,
    35  		}
    36  		for _, method := range rule.Methods {
    37  			if _, ok := a.methodRoots[method]; !ok {
    38  				a.methodRoots[method] = &node{}
    39  			}
    40  			for _, uri := range rule.URIs {
    41  				insert(a.methodRoots[method], uri, sc)
    42  			}
    43  		}
    44  	}
    45  
    46  	return a
    47  }
    48  
    49  // FindRule finds a rule in the APICache without validating scopes
    50  func (c *APICache) FindRule(verb, uri string) (bool, *policy.HTTPRule) {
    51  	found, rule := c.Find(verb, uri)
    52  	if rule == nil {
    53  		return found, nil
    54  	}
    55  	if policyRule, ok := rule.(*scopeRule); ok {
    56  		return found, policyRule.rule
    57  	}
    58  	return false, nil
    59  }
    60  
    61  // FindAndMatchScope finds the rule and returns true only if the scope matches
    62  // as well. It also returns true of this was a public rule, allowing the callers
    63  // to decide how to present the data or potentially what to do if authorization
    64  // fails.
    65  func (c *APICache) FindAndMatchScope(verb, uri string, attributes []string) (bool, bool) {
    66  	found, rule := c.Find(verb, uri)
    67  	if !found || rule == nil {
    68  		return false, false
    69  	}
    70  	policyRule, ok := rule.(*scopeRule)
    71  	if !ok {
    72  		return false, false
    73  	}
    74  	if policyRule.rule.Public {
    75  		return true, true
    76  	}
    77  	return c.MatchClaims(policyRule.rule.ClaimMatchingRules, attributes), false
    78  }
    79  
    80  // MatchClaims receives a set of claim matchibg rules and a set of claims
    81  // and returns true of the claims match the rules.
    82  func (c *APICache) MatchClaims(rules [][]string, claims []string) bool {
    83  
    84  	claimsMap := map[string]struct{}{}
    85  	for _, claim := range claims {
    86  		claimsMap[claim] = struct{}{}
    87  	}
    88  
    89  	var matched int
    90  	for _, clause := range rules {
    91  		matched = len(clause)
    92  		for _, claim := range clause {
    93  			if _, ok := claimsMap[claim]; ok {
    94  				matched--
    95  			}
    96  			if matched == 0 {
    97  				return true
    98  			}
    99  		}
   100  	}
   101  	return false
   102  }
   103  
   104  // Find finds a URI in the cache and returns true and the data if found.
   105  // If not found it returns false.
   106  func (c *APICache) Find(verb, uri string) (bool, interface{}) {
   107  	root, ok := c.methodRoots[verb]
   108  	if !ok {
   109  		return false, nil
   110  	}
   111  	return search(root, uri)
   112  }
   113  
   114  // parse parses a URI and splits into prefix, suffix
   115  func parse(s string) (string, string) {
   116  	if s == "/" {
   117  		return s, ""
   118  	}
   119  	for i := 1; i < len(s); i++ {
   120  		if s[i] == '/' {
   121  			return s[0:i], s[i:]
   122  		}
   123  	}
   124  
   125  	return s, ""
   126  }
   127  
   128  // insert adds an api to the api cache
   129  func insert(n *node, api string, data interface{}) {
   130  	if len(api) == 0 {
   131  		n.data = data
   132  		n.leaf = true
   133  		return
   134  	}
   135  
   136  	prefix, suffix := parse(api)
   137  
   138  	// root node or terminal node
   139  	if prefix == "/" {
   140  		n.data = data
   141  		n.leaf = true
   142  		return
   143  	}
   144  
   145  	if n.children == nil {
   146  		n.children = map[string]*node{}
   147  	}
   148  
   149  	// If there is no child, add the new child.
   150  	next, ok := n.children[prefix]
   151  	if !ok {
   152  		next = &node{}
   153  		n.children[prefix] = next
   154  	}
   155  
   156  	insert(next, suffix, data)
   157  }
   158  
   159  func search(n *node, api string) (found bool, data interface{}) {
   160  
   161  	prefix, suffix := parse(api)
   162  
   163  	if prefix == "/" {
   164  		if n.leaf {
   165  			return true, n.data
   166  		}
   167  	}
   168  
   169  	next, foundPrefix := n.children[prefix]
   170  	// We found either an exact match or a * match
   171  	if foundPrefix {
   172  		matchedChildren, data := search(next, suffix)
   173  		if matchedChildren {
   174  			return true, data
   175  		}
   176  	}
   177  
   178  	// If not found, try the ignore operator.
   179  	next, foundPrefix = n.children["/?"]
   180  	if foundPrefix {
   181  		matchedChildren, data := search(next, suffix)
   182  		if matchedChildren {
   183  			return true, data
   184  		}
   185  	}
   186  
   187  	// If not found, try the * operator and ignore the rest of path.
   188  	next, foundPrefix = n.children["/*"]
   189  	if foundPrefix {
   190  		for len(suffix) > 0 {
   191  			matchedChildren, data := search(next, suffix)
   192  			if matchedChildren {
   193  				return true, data
   194  			}
   195  			prefix, suffix = parse(suffix)
   196  		}
   197  		matchedChildren, data := search(next, "/")
   198  		if matchedChildren {
   199  			return true, data
   200  		}
   201  	}
   202  
   203  	if n.leaf && len(prefix) == 0 {
   204  		return true, n.data
   205  	}
   206  
   207  	return false, nil
   208  }