Upgrade dependent version: github.com/open-policy-agent/opa v0.18.0 -> v0.45.0 Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io> Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>
264 lines
6.3 KiB
Go
264 lines
6.3 KiB
Go
// Copyright 2022 The OPA Authors. All rights reserved.
|
|
// Use of this source code is governed by an Apache2
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package topdown
|
|
|
|
import (
|
|
"github.com/open-policy-agent/opa/ast"
|
|
"github.com/open-policy-agent/opa/topdown/builtins"
|
|
)
|
|
|
|
func bothObjects(t1, t2 *ast.Term) (bool, ast.Object, ast.Object) {
|
|
if (t1 == nil) || (t2 == nil) {
|
|
return false, nil, nil
|
|
}
|
|
|
|
obj1, ok := t1.Value.(ast.Object)
|
|
if !ok {
|
|
return false, nil, nil
|
|
}
|
|
|
|
obj2, ok := t2.Value.(ast.Object)
|
|
if !ok {
|
|
return false, nil, nil
|
|
}
|
|
|
|
return true, obj1, obj2
|
|
}
|
|
|
|
func bothSets(t1, t2 *ast.Term) (bool, ast.Set, ast.Set) {
|
|
if (t1 == nil) || (t2 == nil) {
|
|
return false, nil, nil
|
|
}
|
|
|
|
set1, ok := t1.Value.(ast.Set)
|
|
if !ok {
|
|
return false, nil, nil
|
|
}
|
|
|
|
set2, ok := t2.Value.(ast.Set)
|
|
if !ok {
|
|
return false, nil, nil
|
|
}
|
|
|
|
return true, set1, set2
|
|
}
|
|
|
|
func bothArrays(t1, t2 *ast.Term) (bool, *ast.Array, *ast.Array) {
|
|
if (t1 == nil) || (t2 == nil) {
|
|
return false, nil, nil
|
|
}
|
|
|
|
array1, ok := t1.Value.(*ast.Array)
|
|
if !ok {
|
|
return false, nil, nil
|
|
}
|
|
|
|
array2, ok := t2.Value.(*ast.Array)
|
|
if !ok {
|
|
return false, nil, nil
|
|
}
|
|
|
|
return true, array1, array2
|
|
}
|
|
|
|
func arraySet(t1, t2 *ast.Term) (bool, *ast.Array, ast.Set) {
|
|
if (t1 == nil) || (t2 == nil) {
|
|
return false, nil, nil
|
|
}
|
|
|
|
array, ok := t1.Value.(*ast.Array)
|
|
if !ok {
|
|
return false, nil, nil
|
|
}
|
|
|
|
set, ok := t2.Value.(ast.Set)
|
|
if !ok {
|
|
return false, nil, nil
|
|
}
|
|
|
|
return true, array, set
|
|
}
|
|
|
|
// objectSubset implements the subset operation on a pair of objects.
|
|
//
|
|
// This function will try to recursively apply the subset operation where it
|
|
// can, such as if both super and sub have an object or set as the value
|
|
// associated with a key.
|
|
func objectSubset(super ast.Object, sub ast.Object) bool {
|
|
var superTerm *ast.Term
|
|
isSubset := true
|
|
|
|
sub.Until(func(key, subTerm *ast.Term) bool {
|
|
// This really wants to be a for loop, hence the somewhat
|
|
// weird internal structure. However, using Until() in this
|
|
// was is a performance optimization, as it avoids performing
|
|
// any key hashing on the sub-object.
|
|
|
|
superTerm = super.Get(key)
|
|
|
|
// subTerm is can't be nil because we got it from Until(), so
|
|
// we only need to verify that super is non-nil.
|
|
if superTerm == nil {
|
|
isSubset = false
|
|
return true // break, not a subset
|
|
}
|
|
|
|
if subTerm.Equal(superTerm) {
|
|
return false // continue
|
|
}
|
|
|
|
// If both of the terms are objects then we want to apply
|
|
// the subset operation recursively, otherwise we just compare
|
|
// them normally. If only one term is an object, then we
|
|
// do a normal comparison which will come up false.
|
|
if ok, superObj, subObj := bothObjects(superTerm, subTerm); ok {
|
|
if !objectSubset(superObj, subObj) {
|
|
isSubset = false
|
|
return true // break, not a subset
|
|
}
|
|
|
|
return false // continue
|
|
}
|
|
|
|
if ok, superSet, subSet := bothSets(superTerm, subTerm); ok {
|
|
if !setSubset(superSet, subSet) {
|
|
isSubset = false
|
|
return true // break, not a subset
|
|
}
|
|
|
|
return false // continue
|
|
}
|
|
|
|
if ok, superArray, subArray := bothArrays(superTerm, subTerm); ok {
|
|
if !arraySubset(superArray, subArray) {
|
|
isSubset = false
|
|
return true // break, not a subset
|
|
}
|
|
|
|
return false // continue
|
|
}
|
|
|
|
// We have already checked for exact equality, as well as for
|
|
// all of the types of nested subsets we care about, so if we
|
|
// get here it means this isn't a subset.
|
|
isSubset = false
|
|
return true // break, not a subset
|
|
})
|
|
|
|
return isSubset
|
|
}
|
|
|
|
// setSubset implements the subset operation on sets.
|
|
//
|
|
// Unlike in the object case, this is not recursive, we just compare values
|
|
// using ast.Set.Contains() because we have no well defined way to "match up"
|
|
// objects that are in different sets.
|
|
func setSubset(super ast.Set, sub ast.Set) bool {
|
|
isSubset := true
|
|
sub.Until(func(t *ast.Term) bool {
|
|
if !super.Contains(t) {
|
|
isSubset = false
|
|
return true
|
|
}
|
|
return false
|
|
})
|
|
|
|
return isSubset
|
|
}
|
|
|
|
// arraySubset implements the subset operation on arrays.
|
|
//
|
|
// This is defined to mean that the entire "sub" array must appear in
|
|
// the "super" array. For the same rationale as setSubset(), we do not attempt
|
|
// to recurse into values.
|
|
func arraySubset(super, sub *ast.Array) bool {
|
|
// Notice that this is essentially string search. The naive approach
|
|
// used here is O(n^2). This should probably be rewritten later to use
|
|
// Boyer-Moore or something.
|
|
|
|
if sub.Len() > super.Len() {
|
|
return false
|
|
}
|
|
|
|
if sub.Equal(super) {
|
|
return true
|
|
}
|
|
|
|
superCursor := 0
|
|
subCursor := 0
|
|
for {
|
|
if subCursor == sub.Len() {
|
|
return true
|
|
}
|
|
|
|
if superCursor == super.Len() {
|
|
return false
|
|
}
|
|
|
|
subElem := sub.Elem(subCursor)
|
|
superElem := sub.Elem(superCursor + subCursor)
|
|
if superElem == nil {
|
|
return false
|
|
}
|
|
|
|
if superElem.Value.Compare(subElem.Value) == 0 {
|
|
subCursor++
|
|
} else {
|
|
superCursor++
|
|
subCursor = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
// arraySetSubset implements the subset operation on array and set.
|
|
//
|
|
// This is defined to mean that the entire "sub" set must appear in
|
|
// the "super" array with no consideration of ordering.
|
|
// For the same rationale as setSubset(), we do not attempt
|
|
// to recurse into values.
|
|
func arraySetSubset(super *ast.Array, sub ast.Set) bool {
|
|
unmatched := sub.Len()
|
|
return super.Until(func(t *ast.Term) bool {
|
|
if sub.Contains(t) {
|
|
unmatched--
|
|
}
|
|
if unmatched == 0 {
|
|
return true
|
|
}
|
|
return false
|
|
})
|
|
}
|
|
|
|
func builtinObjectSubset(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
|
superTerm := operands[0]
|
|
subTerm := operands[1]
|
|
|
|
if ok, superObj, subObj := bothObjects(superTerm, subTerm); ok {
|
|
// Both operands are objects.
|
|
return iter(ast.BooleanTerm(objectSubset(superObj, subObj)))
|
|
}
|
|
|
|
if ok, superSet, subSet := bothSets(superTerm, subTerm); ok {
|
|
// Both operands are sets.
|
|
return iter(ast.BooleanTerm(setSubset(superSet, subSet)))
|
|
}
|
|
|
|
if ok, superArray, subArray := bothArrays(superTerm, subTerm); ok {
|
|
// Both operands are sets.
|
|
return iter(ast.BooleanTerm(arraySubset(superArray, subArray)))
|
|
}
|
|
|
|
if ok, superArray, subSet := arraySet(superTerm, subTerm); ok {
|
|
// Super operand is array and sub operand is set
|
|
return iter(ast.BooleanTerm(arraySetSubset(superArray, subSet)))
|
|
}
|
|
|
|
return builtins.ErrOperand("both arguments object.subset must be of the same type or array and set")
|
|
}
|
|
|
|
func init() {
|
|
RegisterBuiltinFunc(ast.ObjectSubset.Name, builtinObjectSubset)
|
|
}
|