diff --git a/models/conditionLangs/cedarConditions/map_hexa.go b/models/conditionLangs/cedarConditions/map_hexa.go index 4faf520..6aadccd 100644 --- a/models/conditionLangs/cedarConditions/map_hexa.go +++ b/models/conditionLangs/cedarConditions/map_hexa.go @@ -83,7 +83,7 @@ func (mapper *CedarConditionMapper) MapConditionToCedar(condition *conditions.Co if err != nil { return "", err } - root := *ast + root := ast err = checkCompatibility(root) if err != nil { @@ -94,7 +94,7 @@ func (mapper *CedarConditionMapper) MapConditionToCedar(condition *conditions.Co if condition.Action == conditions.ADeny { isUnless = true } - // This logic needs to decide whether to start with multiple whens/unlesses + // This logic needs to decide whether to start with multiple when/unless phrases // we have a lot of layers of ands/ors need to split into multiple clauses switch exp := root.(type) { @@ -210,7 +210,7 @@ func (mapper *CedarConditionMapper) mapFilterExpression(node hexaParser.Expressi } -func (mapper *CedarConditionMapper) mapFilterNot(notFilter *hexaParser.NotExpression, isChild bool) string { +func (mapper *CedarConditionMapper) mapFilterNot(notFilter *hexaParser.NotExpression, _ bool) string { subExpression := notFilter.Expression var clause string switch subFilter := subExpression.(type) { @@ -224,8 +224,8 @@ func (mapper *CedarConditionMapper) mapFilterNot(notFilter *hexaParser.NotExpres return fmt.Sprintf("!( %v )", clause) } -func (mapper *CedarConditionMapper) mapFilterPrecedence(pfilter *hexaParser.PrecedenceExpression, isChild bool) string { - subExpression := pfilter.Expression +func (mapper *CedarConditionMapper) mapFilterPrecedence(precedenceExpression *hexaParser.PrecedenceExpression, _ bool) string { + subExpression := precedenceExpression.Expression var clause string switch subFilter := subExpression.(type) { case hexaParser.LogicalExpression: diff --git a/models/conditionLangs/gcpcel/gcp_condition_mapper.go b/models/conditionLangs/gcpcel/gcp_condition_mapper.go index df7c99e..cf1d2d5 100644 --- a/models/conditionLangs/gcpcel/gcp_condition_mapper.go +++ b/models/conditionLangs/gcpcel/gcp_condition_mapper.go @@ -46,31 +46,28 @@ func (mapper *GoogleConditionMapper) MapConditionToProvider(condition conditions } -func (mapper *GoogleConditionMapper) MapFilter(ast *parser.Expression) (string, error) { - err := checkCompatibility(*ast) +func (mapper *GoogleConditionMapper) MapFilter(ast parser.Expression) (string, error) { + err := checkCompatibility(ast) if err != nil { return "", err } return mapper.mapFilterInternal(ast, false), nil } -func (mapper *GoogleConditionMapper) mapFilterInternal(ast *parser.Expression, isChild bool) string { +func (mapper *GoogleConditionMapper) mapFilterInternal(ast parser.Expression, isChild bool) string { - // dereference - deref := *ast - - switch element := deref.(type) { + switch element := ast.(type) { case parser.NotExpression: - return mapper.mapFilterNot(&element, isChild) + return mapper.mapFilterNot(element, isChild) case parser.PrecedenceExpression: - return mapper.mapFilterPrecedence(&element, true) + return mapper.mapFilterPrecedence(element, true) case parser.LogicalExpression: - return mapper.mapFilterLogical(&element, isChild) + return mapper.mapFilterLogical(element, isChild) default: - attrExpression := deref.(parser.AttributeExpression) - return mapper.mapFilterAttrExpr(&attrExpression) + attrExpression := ast.(parser.AttributeExpression) + return mapper.mapFilterAttrExpr(attrExpression) } // return mapTool.mapFilterValuePath(deref.(idqlCondition.ValuePathExpression)) } @@ -85,38 +82,38 @@ func (mapTool *GoogleConditionMapper) mapFilterValuePath(vpFilter idqlCondition. } */ -func (mapper *GoogleConditionMapper) mapFilterNot(notFilter *parser.NotExpression, isChild bool) string { +func (mapper *GoogleConditionMapper) mapFilterNot(notFilter parser.NotExpression, _ bool) string { subExpression := notFilter.Expression var celFilter string switch subFilter := subExpression.(type) { case parser.LogicalExpression: // For the purpose of a not idqlCondition, the logical expression is not a child - celFilter = mapper.mapFilterLogical(&subFilter, false) + celFilter = mapper.mapFilterLogical(subFilter, false) celFilter = "(" + celFilter + ")" break default: - celFilter = mapper.mapFilterInternal(&subFilter, false) + celFilter = mapper.mapFilterInternal(subFilter, false) } return fmt.Sprintf("!%v", celFilter) } -func (mapper *GoogleConditionMapper) mapFilterPrecedence(pfilter *parser.PrecedenceExpression, isChild bool) string { +func (mapper *GoogleConditionMapper) mapFilterPrecedence(pfilter parser.PrecedenceExpression, _ bool) string { subExpression := pfilter.Expression var celFilter string switch subFilter := subExpression.(type) { case parser.LogicalExpression: // For the purpose of a not idqlCondition, the logical expression is not a child - celFilter = mapper.mapFilterLogical(&subFilter, false) + celFilter = mapper.mapFilterLogical(subFilter, false) celFilter = "(" + celFilter + ")" break default: - celFilter = mapper.mapFilterInternal(&subFilter, false) + celFilter = mapper.mapFilterInternal(subFilter, false) } return fmt.Sprintf("%v", celFilter) } -func (mapper *GoogleConditionMapper) mapFilterLogical(logicFilter *parser.LogicalExpression, isChild bool) string { +func (mapper *GoogleConditionMapper) mapFilterLogical(logicFilter parser.LogicalExpression, isChild bool) string { isDouble := false var celLeft, celRight string switch subFilter := logicFilter.Left.(type) { @@ -126,9 +123,9 @@ func (mapper *GoogleConditionMapper) mapFilterLogical(logicFilter *parser.Logica } } - celLeft = mapper.mapFilterInternal(&logicFilter.Left, !isDouble) + celLeft = mapper.mapFilterInternal(logicFilter.Left, !isDouble) - celRight = mapper.mapFilterInternal(&logicFilter.Right, !isDouble) + celRight = mapper.mapFilterInternal(logicFilter.Right, !isDouble) switch logicFilter.Operator { default: @@ -143,7 +140,7 @@ func (mapper *GoogleConditionMapper) mapFilterLogical(logicFilter *parser.Logica } } -func (mapper *GoogleConditionMapper) mapFilterAttrExpr(attrExpr *parser.AttributeExpression) string { +func (mapper *GoogleConditionMapper) mapFilterAttrExpr(attrExpr parser.AttributeExpression) string { compareValue := prepareValue(attrExpr) mapPath := mapper.NameMapper.GetProviderAttributeName(attrExpr.AttributePath) @@ -179,7 +176,7 @@ func (mapper *GoogleConditionMapper) mapFilterAttrExpr(attrExpr *parser.Attribut /* If the value type is string, it needs to be quoted. */ -func prepareValue(attrExpr *parser.AttributeExpression) string { +func prepareValue(attrExpr parser.AttributeExpression) string { compValue := attrExpr.CompareValue if compValue == "" { return "" @@ -206,15 +203,20 @@ func (mapper *GoogleConditionMapper) MapProviderToCondition(expression string) ( if issues != nil { return conditions.ConditionInfo{}, errors.New("CEL Mapping Error: " + issues.String()) } - - idqlAst, err := mapper.mapCelExpr(celAst.Expr(), false) + parsedAst, err := cel.AstToParsedExpr(celAst) + if err != nil { + return conditions.ConditionInfo{ + Rule: "", + }, errors.New("IDQL condition mapTool error: " + err.Error()) + } + idqlAst, err := mapper.mapCelExpr(parsedAst.GetExpr(), false) if err != nil { return conditions.ConditionInfo{ Rule: "", }, errors.New("IDQL condition mapTool error: " + err.Error()) } - condString := conditions.SerializeExpression(&idqlAst) + condString := conditions.SerializeExpression(idqlAst) return conditions.ConditionInfo{ Rule: condString, @@ -390,7 +392,7 @@ func (mapper *GoogleConditionMapper) mapCelAttrCompare(expressions []*expr.Expr, return attrFilter, nil } -func (mapper *GoogleConditionMapper) mapCelNot(expressions []*expr.Expr, isChild bool) parser.Expression { +func (mapper *GoogleConditionMapper) mapCelNot(expressions []*expr.Expr, _ bool) parser.Expression { expression, _ := mapper.mapCelExpr(expressions[0], false) // ischild is ignored because of not @@ -400,7 +402,7 @@ func (mapper *GoogleConditionMapper) mapCelNot(expressions []*expr.Expr, isChild return notFilter } -func (mapper *GoogleConditionMapper) mapCelLogical(expressions []*expr.Expr, isAnd bool, isChild bool) (parser.Expression, error) { +func (mapper *GoogleConditionMapper) mapCelLogical(expressions []*expr.Expr, isAnd bool, _ bool) (parser.Expression, error) { filters := make([]parser.Expression, len(expressions)) var err error // collapse n clauses back into a series of nested pairwise and/or clauses diff --git a/pkg/hexapolicy/conditions/conditions.go b/pkg/hexapolicy/conditions/conditions.go index 6bb0af5..42b26bf 100644 --- a/pkg/hexapolicy/conditions/conditions.go +++ b/pkg/hexapolicy/conditions/conditions.go @@ -1,172 +1,231 @@ package conditions import ( - "fmt" + "fmt" + "sort" - "strings" + "strings" - conditionparser "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions/parser" + conditionparser "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions/parser" ) const ( - AAllow string = "allow" - ADeny string = "deny" - AAudit string = "audit" + AAllow string = "allow" + ADeny string = "deny" + AAudit string = "audit" ) type ConditionInfo struct { - Rule string `json:"Rule,omitempty" validate:"required"` // in RFC7644 idqlCondition form - Action string `json:"Action,omitempty"` // allow/deny/audit default is allow + Rule string `json:"Rule,omitempty" validate:"required"` // in RFC7644 idqlCondition form + Action string `json:"Action,omitempty"` // allow/deny/audit default is allow } -// Equals does a simple string compare (no semantic compare for equivalency) +// Equals performs an AST level compare to test filters are equivalent. NOTE: does not test equivalent attribute expressions at this time +// e.g. level < 5 vs. not(level >= 5) will return as unequal though logically equal. So while a true is always correct, some equivalent expressions will report false func (c *ConditionInfo) Equals(compare *ConditionInfo) bool { - // first just do a simple compare - if compare == nil { - return false - } - - if c.Action != compare.Action { - return false - } - if c.Rule == compare.Rule { - return true - } - - // try re-parsing to get consistent spacing etc. - expression, err := conditionparser.ParseFilter(c.Rule) - compareExpression, err2 := conditionparser.ParseFilter(compare.Rule) - if err != nil || err2 != nil { - return false - } - - ast1 := *expression - exp1 := ast1.String() - ast2 := *compareExpression - exp2 := ast2.String() + // first just do a simple compare + if compare == nil { + return false + } + + if c.Action != compare.Action { + return false + } + if c.Rule == compare.Rule { + return true + } + + // try re-parsing to get consistent spacing etc. + expression, err := conditionparser.ParseFilter(c.Rule) + compareExpression, err2 := conditionparser.ParseFilter(compare.Rule) + if err != nil || err2 != nil { + return false + } + + ch1 := make(chan string) + ch2 := make(chan string) + var seq1 []string + var seq2 []string + go compareWalk(expression, ch1) + go compareWalk(compareExpression, ch2) + + for item := range ch1 { + seq1 = append(seq1, item) + } + for item := range ch2 { + seq2 = append(seq2, item) + } + sort.Strings(seq1) + sort.Strings(seq2) + + return slicesAreEqual(seq1, seq2) + /* + exp1 := SerializeExpression(expression) + exp2 := SerializeExpression(compareExpression) + + return exp1 == exp2 + + */ +} - return exp1 == exp2 +func slicesAreEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if !strings.EqualFold(v, b[i]) { + return false + } + } + return true } type AttributeMap struct { - forward map[string]string - reverse map[string]string + forward map[string]string + reverse map[string]string } type NameMapper interface { - // GetProviderAttributeName returns a simple string representation of the mapped attribute name (usually in name[.sub-attribute] form). - GetProviderAttributeName(hexaName string) string + // GetProviderAttributeName returns a simple string representation of the mapped attribute name (usually in name[.sub-attribute] form). + GetProviderAttributeName(hexaName string) string - // GetHexaFilterAttributePath returns a filterAttributePath which is used to build a SCIM Filter AST - GetHexaFilterAttributePath(provName string) string + // GetHexaFilterAttributePath returns a filterAttributePath which is used to build a SCIM Filter AST + GetHexaFilterAttributePath(provName string) string } type ConditionMapper interface { - /* - MapConditionToProvider takes an IDQL Condition expression and converts it to a string - usable the target provider. For example from RFC7644, Section-3.4.2.2 to Google Common Expression Language - */ - MapConditionToProvider(condition ConditionInfo) interface{} - - /* - MapProviderToCondition take a string expression from a platform policy and converts it to RFC7644: Section-3.4.2.2. - */ - MapProviderToCondition(expression string) (ConditionInfo, error) + /* + MapConditionToProvider takes an IDQL Condition expression and converts it to a string + usable the target provider. For example from RFC7644, Section-3.4.2.2 to Google Common Expression Language + */ + MapConditionToProvider(condition ConditionInfo) interface{} + + /* + MapProviderToCondition take a string expression from a platform policy and converts it to RFC7644: Section-3.4.2.2. + */ + MapProviderToCondition(expression string) (ConditionInfo, error) } // NewNameMapper is called by a condition mapTool provider to instantiate an attribute name translator using interface NameMapper func NewNameMapper(attributeMap map[string]string) *AttributeMap { - reverse := make(map[string]string, len(attributeMap)) - forward := make(map[string]string, len(attributeMap)) - for k, v := range attributeMap { - reverse[strings.ToLower(v)] = k - forward[strings.ToLower(k)] = v - } - - return &AttributeMap{ - forward: forward, - reverse: reverse, - } + reverse := make(map[string]string, len(attributeMap)) + forward := make(map[string]string, len(attributeMap)) + for k, v := range attributeMap { + reverse[strings.ToLower(v)] = k + forward[strings.ToLower(k)] = v + } + + return &AttributeMap{ + forward: forward, + reverse: reverse, + } } func (n *AttributeMap) GetProviderAttributeName(hexaName string) string { - val, exists := n.forward[strings.ToLower(hexaName)] - if exists { - return val - } - return hexaName + val, exists := n.forward[strings.ToLower(hexaName)] + if exists { + return val + } + return hexaName } func (n *AttributeMap) GetHexaFilterAttributePath(provName string) string { - val, exists := n.reverse[provName] - if !exists { - val = provName - } - return val + val, exists := n.reverse[provName] + if !exists { + val = provName + } + return val } // ParseConditionRuleAst is used by mapping providers to get the IDQL condition rule AST tree -func ParseConditionRuleAst(condition ConditionInfo) (*conditionparser.Expression, error) { - return conditionparser.ParseFilter(condition.Rule) +func ParseConditionRuleAst(condition ConditionInfo) (conditionparser.Expression, error) { + return conditionparser.ParseFilter(condition.Rule) } -func ParseExpressionAst(expression string) (*conditionparser.Expression, error) { - return conditionparser.ParseFilter(expression) +func ParseExpressionAst(expression string) (conditionparser.Expression, error) { + return conditionparser.ParseFilter(expression) } // SerializeExpression walks the AST and emits the condition in string form. It preserves precedence over the normal idqlCondition.String() method -func SerializeExpression(ast *conditionparser.Expression) string { - - return walk(*ast, false) +func SerializeExpression(ast conditionparser.Expression) string { + return walk(ast, false) } func checkNestedLogic(e conditionparser.Expression, op conditionparser.LogicalOperator) string { - // if the child is a repeat of the parent eliminate brackets (e.g. a or b or c) - - switch v := e.(type) { - case conditionparser.PrecedenceExpression: - e = v.Expression - } + // if the child is a repeat of the parent eliminate brackets (e.g. a or b or c) + + switch v := e.(type) { + case conditionparser.PrecedenceExpression: + e = v.Expression + } + + switch v := e.(type) { + case conditionparser.LogicalExpression: + if v.Operator == op { + return walk(e, false) + } else { + return walk(e, true) + } + + default: + return walk(e, true) + } +} - switch v := e.(type) { - case conditionparser.LogicalExpression: - if v.Operator == op { - return walk(e, false) - } else { - return walk(e, true) - } +func compareWalk(e conditionparser.Expression, ch chan string) { + defer close(ch) + if e != nil { + compareWalkRecursively(e, ch) + } +} - default: - return walk(e, true) - } +func compareWalkRecursively(e conditionparser.Expression, ch chan string) { + if e != nil { + val := e.Dif() + if val != "" { + ch <- val + } + switch exp := e.(type) { + case conditionparser.PrecedenceExpression: + compareWalkRecursively(exp.Expression, ch) + case conditionparser.LogicalExpression: + compareWalkRecursively(exp.Left, ch) + compareWalkRecursively(exp.Right, ch) + case conditionparser.NotExpression: + compareWalkRecursively(exp.Expression, ch) + default: // conditionparser.AttributeExpression etc. + + } + } + return } func walk(e conditionparser.Expression, isChild bool) string { - switch v := e.(type) { - case conditionparser.LogicalExpression: - lhVal := checkNestedLogic(v.Left, v.Operator) - - rhVal := checkNestedLogic(v.Right, v.Operator) - - if isChild && v.Operator == conditionparser.OR { - return fmt.Sprintf("(%v or %v)", lhVal, rhVal) - } else { - return fmt.Sprintf("%v %v %v", lhVal, v.Operator, rhVal) - } - case conditionparser.NotExpression: - subExpression := v.Expression - // Note, because of not() brackets, can treat as top level - subExpressionString := walk(subExpression, false) - - return fmt.Sprintf("not(%v)", subExpressionString) - case conditionparser.PrecedenceExpression: - subExpressionString := walk(v.Expression, false) - - return fmt.Sprintf("(%v)", subExpressionString) - case conditionparser.ValuePathExpression: - return walk(v.VPathFilter, true) - // case idqlCondition.AttributeExpression: - default: - return v.String() - } + switch v := e.(type) { + case conditionparser.LogicalExpression: + lhVal := checkNestedLogic(v.Left, v.Operator) + + rhVal := checkNestedLogic(v.Right, v.Operator) + + if isChild && v.Operator == conditionparser.OR { + return fmt.Sprintf("(%v or %v)", lhVal, rhVal) + } else { + return fmt.Sprintf("%v %v %v", lhVal, v.Operator, rhVal) + } + case conditionparser.NotExpression: + subExpression := v.Expression + // Note, because of not() brackets, can treat as top level + subExpressionString := walk(subExpression, false) + + return fmt.Sprintf("not(%v)", subExpressionString) + case conditionparser.PrecedenceExpression: + subExpressionString := walk(v.Expression, false) + + return fmt.Sprintf("(%v)", subExpressionString) + case conditionparser.ValuePathExpression: + return walk(v.VPathFilter, true) + // case idqlCondition.AttributeExpression: + default: + return v.String() + } } diff --git a/pkg/hexapolicy/conditions/conditions_test.go b/pkg/hexapolicy/conditions/conditions_test.go index c187a31..18cd85c 100644 --- a/pkg/hexapolicy/conditions/conditions_test.go +++ b/pkg/hexapolicy/conditions/conditions_test.go @@ -92,3 +92,68 @@ func TestWalker(t *testing.T) { back2 := conditions.SerializeExpression(ast) fmt.Println(back2) } + +func TestEquals(t *testing.T) { + + tests := []struct { + Name string + R1 string + R2 string + Expected bool + }{ + { + "Same with Not", + "level gt 6 and not(expired eq true)", + "level gt 6 and not(expired eq true)", + true, + }, + { + "Same with precedence", + "level gt 6 and (expired eq true)", + "level gt 6 and expired eq true", + true, + }, + { + "Same but reverse", + "level gt 6 and not(expired eq true)", + " not(expired eq true) and level gt 6 ", + true, + }, + { + "Not same", + "level gt 6 and not(expired eq true)", + "(expired eq true) and level gt 6", + false, + }, + { + "Same different not", + "level gt 6 and not(expired eq true)", + "not(level gt 6) and expired eq true", + false, + }, + { + "Lots of logical", + "(level gt 5 or test eq \"abc\" or level lt 10) and (username sw \"emp\" or username eq \"guest\")", + "(username eq \"guest\" or username sw \"emp\") and (level gt 5 or test eq \"abc\" or level lt 10)", + true, + }, + { + "Switched operator ", + "(level gt 5 or test eq \"abc\" or level lt 10) and (username sw \"emp\" or username eq \"guest\")", + "(username sw \"emp\" or username eq \"guest\") or (level gt 5 or test eq \"abc\" or level lt 10)", + false, + }, + } + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + fmt.Println(fmt.Sprintf("R1:\t%s", test.R1)) + fmt.Println(fmt.Sprintf("R2:\t%s", test.R2)) + condition1 := conditions.ConditionInfo{Rule: test.R1} + condition2 := conditions.ConditionInfo{Rule: test.R2} + + assert.Equal(t, test.Expected, condition1.Equals(&condition2), "Check expected result matches: %s", test.Expected) + }) + } + +} diff --git a/pkg/hexapolicy/conditions/parser/parser.go b/pkg/hexapolicy/conditions/parser/parser.go index e6372f9..de9478b 100644 --- a/pkg/hexapolicy/conditions/parser/parser.go +++ b/pkg/hexapolicy/conditions/parser/parser.go @@ -1,404 +1,406 @@ package parser import ( - "errors" + "errors" - "strings" + "strings" ) -func ParseFilter(expression string) (*Expression, error) { - return parseFilterSub(expression, "") +// ParseFilter parses a SCIM-like (RFC7644) filter expression string and returns an AST as an Expression +func ParseFilter(expression string) (Expression, error) { + return parseFilterSub(expression, "") } -func parseFilterSub(expression string, parentAttr string) (*Expression, error) { - bracketCount := 0 - bracketIndex := -1 - valPathCnt := 0 - vPathStartIndex := -1 - wordIndex := -1 - var clauses []*Expression - cond := "" +// parseFilterSub is the main lexer for converting strings into Expressions +func parseFilterSub(expression string, parentAttr string) (Expression, error) { + bracketCount := 0 + bracketIndex := -1 + valPathCnt := 0 + vPathStartIndex := -1 + wordIndex := -1 + var clauses []Expression + cond := "" - isLogic := false - isAnd := false - isNot := false - isAttr := false - attr := "" - isExpr := false - isValue := false - value := "" - isQuote := false + isLogic := false + isAnd := false + isNot := false + isAttr := false + attr := "" + isExpr := false + isValue := false + value := "" + isQuote := false - expRunes := []rune(expression) - var charPos int - for charPos = 0; charPos < len(expRunes); charPos++ { + expRunes := []rune(expression) + var charPos int + for charPos = 0; charPos < len(expRunes); charPos++ { - c := expRunes[charPos] - switch c { - case '(': - if isQuote || isValue { - break - } - bracketCount++ - if bracketCount == 1 { - bracketIndex = charPos - } - charPos++ - quotedBracket := false - for charPos < len(expRunes) && bracketCount > 0 { - cc := expRunes[charPos] - switch cc { - case '"': - quotedBracket = !quotedBracket - break - case '(': - if quotedBracket { - break - } - bracketCount++ - break - case ')': - // ignore brackets in values - if quotedBracket { - break - } - bracketCount-- - if bracketCount == 0 { - subExpression := expression[bracketIndex+1 : charPos] - subFilter, err := parseFilterSub(subExpression, parentAttr) - if err != nil { - return nil, err - } - var filter Expression - sFilter := *subFilter - switch sFilter.(type) { - case AttributeExpression: + c := expRunes[charPos] + switch c { + case '(': + if isQuote || isValue { + break + } + bracketCount++ + if bracketCount == 1 { + bracketIndex = charPos + } + charPos++ + quotedBracket := false + for charPos < len(expRunes) && bracketCount > 0 { + cc := expRunes[charPos] + switch cc { + case '"': + quotedBracket = !quotedBracket + break + case '(': + if quotedBracket { + break + } + bracketCount++ + break + case ')': + // ignore brackets in values + if quotedBracket { + break + } + bracketCount-- + if bracketCount == 0 { + subExpression := expression[bracketIndex+1 : charPos] + subFilter, err := parseFilterSub(subExpression, parentAttr) + if err != nil { + return nil, err + } + var filter Expression + sFilter := subFilter + switch sFilter.(type) { + case AttributeExpression: - if isNot { - filter = NotExpression{ - Expression: sFilter, - } - } else { - filter = PrecedenceExpression{Expression: sFilter} - } - clauses = append(clauses, &filter) + if isNot { + filter = NotExpression{ + Expression: sFilter, + } + } else { + filter = PrecedenceExpression{Expression: sFilter} + } + clauses = append(clauses, filter) - default: - if isNot { - filter = NotExpression{Expression: sFilter} - clauses = append(clauses, &filter) - } else { - filter = PrecedenceExpression{Expression: sFilter} - clauses = append(clauses, &filter) - } - } - bracketIndex = -1 - } + default: + if isNot { + filter = NotExpression{Expression: sFilter} + clauses = append(clauses, filter) + } else { + filter = PrecedenceExpression{Expression: sFilter} + clauses = append(clauses, filter) + } + } + bracketIndex = -1 + } - } - if bracketCount > 0 { - charPos++ - } - } - break - case '[': - if isQuote || isValue { - break - } - valPathCnt++ - if valPathCnt == 1 { - vPathStartIndex = charPos - } - charPos++ - quotedSqBracket := false - for charPos < len(expression) && valPathCnt > 0 { - cc := expRunes[charPos] - switch cc { - case '"': - quotedSqBracket = !quotedSqBracket - break - case '[': - if quotedSqBracket { - break - } - if valPathCnt >= 1 { - return nil, errors.New("invalid IDQL idqlCondition: A second '[' was detected while looking for a ']' in a value path idqlCondition") - } - valPathCnt++ - break - case ']': - if quotedSqBracket { - break - } - valPathCnt-- - if valPathCnt == 0 { - name := expression[wordIndex:vPathStartIndex] - valueFilterStr := expression[vPathStartIndex+1 : charPos] - subExpression, err := parseFilterSub(valueFilterStr, "") - if err != nil { - return nil, err - } - var filter Expression - filter = ValuePathExpression{ - Attribute: name, - VPathFilter: *subExpression, - } - clauses = append(clauses, &filter) + } + if bracketCount > 0 { + charPos++ + } + } + break + case '[': + if isQuote || isValue { + break + } + valPathCnt++ + if valPathCnt == 1 { + vPathStartIndex = charPos + } + charPos++ + quotedSqBracket := false + for charPos < len(expression) && valPathCnt > 0 { + cc := expRunes[charPos] + switch cc { + case '"': + quotedSqBracket = !quotedSqBracket + break + case '[': + if quotedSqBracket { + break + } + if valPathCnt >= 1 { + return nil, errors.New("invalid IDQL idqlCondition: A second '[' was detected while looking for a ']' in a value path idqlCondition") + } + valPathCnt++ + break + case ']': + if quotedSqBracket { + break + } + valPathCnt-- + if valPathCnt == 0 { + name := expression[wordIndex:vPathStartIndex] + valueFilterStr := expression[vPathStartIndex+1 : charPos] + subExpression, err := parseFilterSub(valueFilterStr, "") + if err != nil { + return nil, err + } + var filter Expression + filter = ValuePathExpression{ + Attribute: name, + VPathFilter: subExpression, + } + clauses = append(clauses, filter) - // This code checks for text after ] ... in future attr[type eq value].subattr may be permissible - if charPos+1 < len(expression) && expRunes[charPos+1] != ' ' { - return nil, errors.New("invalid IDQL idqlCondition: expecting space after ']' in value path expression") - /* - charPos++ - for charPos < len(expression) && expRunes[charPos] != ' ' { - charPos++ - } - */ - } - // reset for the next phrase - vPathStartIndex = -1 - wordIndex = -1 - isAttr = false - } - default: - } - // only increment if we are still processing ( ) phrases - if valPathCnt > 0 { - charPos++ - } - } - if charPos == len(expression) && valPathCnt > 0 { - return nil, errors.New("invalid IDQL idqlCondition: Missing close ']' bracket") - } - break + // This code checks for text after ] ... in future attr[type eq value].subattr may be permissible + if charPos+1 < len(expression) && expRunes[charPos+1] != ' ' { + return nil, errors.New("invalid IDQL idqlCondition: expecting space after ']' in value path expression") + /* + charPos++ + for charPos < len(expression) && expRunes[charPos] != ' ' { + charPos++ + } + */ + } + // reset for the next phrase + vPathStartIndex = -1 + wordIndex = -1 + isAttr = false + } + default: + } + // only increment if we are still processing ( ) phrases + if valPathCnt > 0 { + charPos++ + } + } + if charPos == len(expression) && valPathCnt > 0 { + return nil, errors.New("invalid IDQL idqlCondition: Missing close ']' bracket") + } + break - case ' ': - if isQuote { - break - } - // end of phrase - if wordIndex > -1 { - phrase := expression[wordIndex:charPos] - if strings.EqualFold(phrase, "or") || strings.EqualFold(phrase, "and") { - isLogic = true - isAnd = strings.EqualFold(phrase, "and") - wordIndex = -1 - break - } - if isAttr && attr == "" { - attr = phrase - wordIndex = -1 - } else { - if isExpr && cond == "" { - cond = phrase - wordIndex = -1 - if strings.EqualFold(cond, "pr") { - var attrFilter Expression - attrFilter = AttributeExpression{ - AttributePath: attr, - Operator: CompareOperator("pr"), - } - attr = "" - isAttr = false - cond = "" - isExpr = false - isValue = false - clauses = append(clauses, &attrFilter) - } - } else { - if isValue { - value = phrase - if strings.HasSuffix(value, ")") && bracketCount == 0 { - return nil, errors.New("invalid IDQL idqlCondition: Missing open '(' bracket") - } - if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") { - value = value[1 : len(value)-1] - } - wordIndex = -1 - filterAttr := attr - if parentAttr != "" { - filterAttr = parentAttr + "." + attr - } + case ' ': + if isQuote { + break + } + // end of phrase + if wordIndex > -1 { + phrase := expression[wordIndex:charPos] + if strings.EqualFold(phrase, "or") || strings.EqualFold(phrase, "and") { + isLogic = true + isAnd = strings.EqualFold(phrase, "and") + wordIndex = -1 + break + } + if isAttr && attr == "" { + attr = phrase + wordIndex = -1 + } else { + if isExpr && cond == "" { + cond = phrase + wordIndex = -1 + if strings.EqualFold(cond, "pr") { + var attrFilter Expression + attrFilter = AttributeExpression{ + AttributePath: attr, + Operator: "pr", + } + attr = "" + isAttr = false + cond = "" + isExpr = false + isValue = false + clauses = append(clauses, attrFilter) + } + } else { + if isValue { + value = phrase + if strings.HasSuffix(value, ")") && bracketCount == 0 { + return nil, errors.New("invalid IDQL idqlCondition: Missing open '(' bracket") + } + if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") { + value = value[1 : len(value)-1] + } + wordIndex = -1 + filterAttr := attr + if parentAttr != "" { + filterAttr = parentAttr + "." + attr + } - var attrFilter Expression - attrFilter, err := createExpression(filterAttr, cond, value) - if err != nil { - return nil, err - } + var attrFilter Expression + attrFilter, err := createExpression(filterAttr, cond, value) + if err != nil { + return nil, err + } - attr = "" - isAttr = false - cond = "" - isExpr = false - isValue = false - clauses = append(clauses, &attrFilter) - break - } - } - } - } - break - case ')': - if isQuote || isValue { - break - } - if bracketCount == 0 { - return nil, errors.New("invalid IDQL idqlCondition: Missing open '(' bracket") - } - break - case ']': - if isQuote || isValue { - break - } - if valPathCnt == 0 { - return nil, errors.New("invalid IDQL idqlCondition: Missing open '[' bracket") - } - case 'n', 'N': - if !isValue { - if charPos+3 < len(expression) && - strings.EqualFold(expression[charPos:charPos+3], "not") { - isNot = true - charPos = charPos + 2 - break - } - } + attr = "" + isAttr = false + cond = "" + isExpr = false + isValue = false + clauses = append(clauses, attrFilter) + break + } + } + } + } + break + case ')': + if isQuote || isValue { + break + } + if bracketCount == 0 { + return nil, errors.New("invalid IDQL idqlCondition: Missing open '(' bracket") + } + break + case ']': + if isQuote || isValue { + break + } + if valPathCnt == 0 { + return nil, errors.New("invalid IDQL idqlCondition: Missing open '[' bracket") + } + case 'n', 'N': + if !isValue { + if charPos+3 < len(expression) && + strings.EqualFold(expression[charPos:charPos+3], "not") { + isNot = true + charPos = charPos + 2 + break + } + } - // we want this to fall through to default in case it is an attribute starting with n - if wordIndex == -1 { - wordIndex = charPos - } - if !isAttr { - isAttr = true - } else { - if !isExpr && attr != "" { - isExpr = true - } else { - if !isValue && cond != "" { - isValue = true - } - } - } - break - default: - if c == '"' { - isQuote = !isQuote - } - if wordIndex == -1 { - wordIndex = charPos - } - if !isAttr { - isAttr = true - } else { - if !isExpr && attr != "" { - isExpr = true - } else { - if !isValue && cond != "" { - isValue = true - } - } - } - } - // combine logic here - if isLogic && len(clauses) == 2 { - var oper LogicalOperator - if isAnd { - oper = "and" - } else { - oper = "or" - } - var filter Expression - filter = LogicalExpression{ - Operator: oper, - Left: *clauses[0], - Right: *clauses[1], - } - clauses = []*Expression{} - clauses = append(clauses, &filter) - isLogic = false - } - } + // we want this to fall through to default in case it is an attribute starting with n + if wordIndex == -1 { + wordIndex = charPos + } + if !isAttr { + isAttr = true + } else { + if !isExpr && attr != "" { + isExpr = true + } else { + if !isValue && cond != "" { + isValue = true + } + } + } + break + default: + if c == '"' { + isQuote = !isQuote + } + if wordIndex == -1 { + wordIndex = charPos + } + if !isAttr { + isAttr = true + } else { + if !isExpr && attr != "" { + isExpr = true + } else { + if !isValue && cond != "" { + isValue = true + } + } + } + } + // combine logic here + if isLogic && len(clauses) == 2 { + var oper LogicalOperator + if isAnd { + oper = "and" + } else { + oper = "or" + } + var filter Expression + filter = LogicalExpression{ + Operator: oper, + Left: clauses[0], + Right: clauses[1], + } + clauses = []Expression{} + clauses = append(clauses, filter) + isLogic = false + } + } - if bracketCount > 0 { - return nil, errors.New("invalid IDQL idqlCondition: Missing close ')' bracket") - } - if valPathCnt > 0 { - return nil, errors.New("invalid IDQL idqlCondition: Missing ']' bracket") - } - if wordIndex > -1 && charPos == len(expression) { - filterAttr := attr - if parentAttr != "" { - filterAttr = parentAttr + "." + attr - } - if filterAttr == "" { - return nil, errors.New("invalid IDQL idqlCondition: Incomplete expression") - } - if isAttr && cond != "" { - value = expression[wordIndex:] - if strings.HasSuffix(value, ")") && bracketCount == 0 { - return nil, errors.New("invalid IDQL idqlCondition: Missing open '(' bracket") - } - if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") { - value = value[1 : len(value)-1] - } - var filter Expression - filter, err := createExpression(filterAttr, cond, value) - if err != nil { - return nil, err - } - clauses = append(clauses, &filter) - } else { - // a presence match at the end of the idqlCondition string - if isAttr { - cond = expression[wordIndex:] - } - var filter Expression - filter = AttributeExpression{ - AttributePath: filterAttr, - Operator: CompareOperator("pr"), - } - clauses = append(clauses, &filter) + if bracketCount > 0 { + return nil, errors.New("invalid IDQL idqlCondition: Missing close ')' bracket") + } + if valPathCnt > 0 { + return nil, errors.New("invalid IDQL idqlCondition: Missing ']' bracket") + } + if wordIndex > -1 && charPos == len(expression) { + filterAttr := attr + if parentAttr != "" { + filterAttr = parentAttr + "." + attr + } + if filterAttr == "" { + return nil, errors.New("invalid IDQL idqlCondition: Incomplete expression") + } + if isAttr && cond != "" { + value = expression[wordIndex:] + if strings.HasSuffix(value, ")") && bracketCount == 0 { + return nil, errors.New("invalid IDQL idqlCondition: Missing open '(' bracket") + } + if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") { + value = value[1 : len(value)-1] + } + var filter Expression + filter, err := createExpression(filterAttr, cond, value) + if err != nil { + return nil, err + } + clauses = append(clauses, filter) + } else { + // a presence match at the end of the idqlCondition string + if isAttr { + cond = expression[wordIndex:] + } + var filter Expression + filter = AttributeExpression{ + AttributePath: filterAttr, + Operator: "pr", + } + clauses = append(clauses, filter) - } - } + } + } - if isLogic && len(clauses) == 2 { - var oper LogicalOperator - if isAnd { - oper = "and" - } else { - oper = "or" - } - var filter Expression - filter = LogicalExpression{ - Operator: oper, - Left: *clauses[0], - Right: *clauses[1], - } - clauses = []*Expression{} - clauses = append(clauses, &filter) + if isLogic && len(clauses) == 2 { + var oper LogicalOperator + if isAnd { + oper = "and" + } else { + oper = "or" + } + var filter Expression + filter = LogicalExpression{ + Operator: oper, + Left: clauses[0], + Right: clauses[1], + } + clauses = []Expression{} + clauses = append(clauses, filter) - return &filter, nil - } - if len(clauses) == 1 { - return clauses[0], nil - } + return filter, nil + } + if len(clauses) == 1 { + return clauses[0], nil + } - return nil, errors.New("invalid IDQL idqlCondition: Missing and/or clause") + return nil, errors.New("invalid IDQL idqlCondition: Missing and/or clause") } func createExpression(attribute string, cond string, value string) (AttributeExpression, error) { - lCond := strings.ToLower(cond) - var attrFilter AttributeExpression - switch CompareOperator(lCond) { - case EQ, NE, SW, EW, GT, LT, GE, LE, CO, IN: - attrFilter = AttributeExpression{ - AttributePath: attribute, - Operator: CompareOperator(strings.ToLower(cond)), - CompareValue: value, - } + lCond := strings.ToLower(cond) + var attrFilter AttributeExpression + switch CompareOperator(lCond) { + case EQ, NE, SW, EW, GT, LT, GE, LE, CO, IN: + attrFilter = AttributeExpression{ + AttributePath: attribute, + Operator: CompareOperator(strings.ToLower(cond)), + CompareValue: value, + } - default: - return AttributeExpression{}, errors.New("invalid IDQL idqlCondition: Unsupported comparison operator: " + cond) - } - return attrFilter, nil + default: + return AttributeExpression{}, errors.New("invalid IDQL idqlCondition: Unsupported comparison operator: " + cond) + } + return attrFilter, nil } diff --git a/pkg/hexapolicy/conditions/parser/parser_test.go b/pkg/hexapolicy/conditions/parser/parser_test.go index 44dd8ec..a46e4a8 100644 --- a/pkg/hexapolicy/conditions/parser/parser_test.go +++ b/pkg/hexapolicy/conditions/parser/parser_test.go @@ -47,9 +47,9 @@ func TestParseFilter(t *testing.T) { t.Run(example[0], func(t *testing.T) { var err error fmt.Println(fmt.Sprintf("Input:\t%s", example[0])) - ast, err := ParseFilter(example[0]) + element, err := ParseFilter(example[0]) assert.NoError(t, err, "Example not parsed: "+example[0]) - element := *ast + out := element.String() fmt.Println(fmt.Sprintf("Parsed:\t%s", out)) match := example[1] diff --git a/pkg/hexapolicy/conditions/parser/types.go b/pkg/hexapolicy/conditions/parser/types.go index df55444..7626411 100644 --- a/pkg/hexapolicy/conditions/parser/types.go +++ b/pkg/hexapolicy/conditions/parser/types.go @@ -46,6 +46,7 @@ type LogicalOperator string type Expression interface { exprNode() String() string + Dif() string } type LogicalExpression struct { @@ -54,9 +55,13 @@ type LogicalExpression struct { } func (LogicalExpression) exprNode() {} + func (e LogicalExpression) String() string { return fmt.Sprintf("%s %s %s", e.Left.String(), e.Operator, e.Right.String()) } +func (e LogicalExpression) Dif() string { + return string(e.Operator) +} type NotExpression struct { Expression Expression @@ -68,6 +73,16 @@ func (e NotExpression) String() string { func (NotExpression) exprNode() {} +func (e NotExpression) Dif() string { + + switch exp := e.Expression.(type) { + case AttributeExpression, ValuePathExpression: + return fmt.Sprintf("not(%s)", exp.String()) + default: + return "not" + } +} + type PrecedenceExpression struct { Expression Expression } @@ -78,6 +93,10 @@ func (e PrecedenceExpression) String() string { return fmt.Sprintf("(%s)", e.Expression.String()) } +func (PrecedenceExpression) Dif() string { + return "" +} + type AttributeExpression struct { AttributePath string Operator CompareOperator @@ -113,6 +132,10 @@ func (e AttributeExpression) String() string { return fmt.Sprintf("%s %s \"%s\"", e.AttributePath, e.Operator, e.CompareValue) } +func (e AttributeExpression) Dif() string { + return e.String() +} + type ValuePathExpression struct { Attribute string VPathFilter Expression @@ -122,3 +145,4 @@ func (ValuePathExpression) exprNode() {} func (e ValuePathExpression) String() string { return fmt.Sprintf("%s[%s]", e.Attribute, e.VPathFilter.String()) } +func (e ValuePathExpression) Dif() string { return e.String() } diff --git a/pkg/hexapolicy/hexa_policy.go b/pkg/hexapolicy/hexa_policy.go index 0320c45..43cb5eb 100644 --- a/pkg/hexapolicy/hexa_policy.go +++ b/pkg/hexapolicy/hexa_policy.go @@ -101,7 +101,7 @@ func (p *PolicyInfo) Equals(hexaPolicy PolicyInfo) bool { } // check for semantic equivalence. - if !(p.Subjects.equals(hexaPolicy.Subjects) && p.actionEquals(hexaPolicy.Actions) && p.Object.equals(&hexaPolicy.Object)) { + if !(p.Subjects.Equals(hexaPolicy.Subjects) && p.ActionsEqual(hexaPolicy.Actions) && p.Object.equals(&hexaPolicy.Object)) { return false } @@ -121,7 +121,7 @@ func (p *PolicyInfo) Equals(hexaPolicy PolicyInfo) bool { if hexaPolicy.Scope == nil { return false } - if !p.Scope.equals(hexaPolicy.Scope) { + if !p.Scope.Equals(hexaPolicy.Scope) { return false } } @@ -149,11 +149,11 @@ func (p *PolicyInfo) Compare(hexaPolicy PolicyInfo) []string { var difs = make([]string, 0) // Now do a semantic compare (e.g. things can be different order but the same) - if !p.Subjects.equals(hexaPolicy.Subjects) { + if !p.Subjects.Equals(hexaPolicy.Subjects) { difs = append(difs, CompareDifSubject) } - if !p.actionEquals(hexaPolicy.Actions) { + if !p.ActionsEqual(hexaPolicy.Actions) { difs = append(difs, CompareDifAction) } @@ -176,14 +176,14 @@ func (p *PolicyInfo) Compare(hexaPolicy PolicyInfo) []string { return difs } -func (p *PolicyInfo) actionEquals(actions []ActionInfo) bool { - if len(p.Actions) != len(actions) { +func (p *PolicyInfo) ActionsEqual(actions []ActionInfo) bool { + if actions == nil || len(p.Actions) != len(actions) { return false } for _, action := range p.Actions { isMatch := false - for _, caction := range actions { - if action.Equals(caction) { + for _, compareAction := range actions { + if action.Equals(compareAction) { isMatch = true break } @@ -223,7 +223,7 @@ func (s SubjectInfo) String() []string { return s } -func (s SubjectInfo) equals(subjects SubjectInfo) bool { +func (s SubjectInfo) Equals(subjects SubjectInfo) bool { if len(s) != len(subjects) { return false } @@ -268,9 +268,9 @@ const ( ScopeTypeUnassigned string = "na" ) -// ScopeInfo.equals returns equality based on string compare. This does not lexically compare filters. +// Equals returns equality based on string compare. This does not lexically compare filters. // This function is intended to determine if a policy element has changed. -func (s *ScopeInfo) equals(scope *ScopeInfo) bool { +func (s *ScopeInfo) Equals(scope *ScopeInfo) bool { if s.Type() != scope.Type() { return false } diff --git a/pkg/hexapolicy/hexa_policy_test.go b/pkg/hexapolicy/hexa_policy_test.go index 950a409..01858cb 100644 --- a/pkg/hexapolicy/hexa_policy_test.go +++ b/pkg/hexapolicy/hexa_policy_test.go @@ -82,11 +82,11 @@ func TestSubjectInfo_equals(t *testing.T) { p1 := policies.Policies[0] p2 := policies.Policies[1] assert.NotNil(t, p1.Subjects, "Subjects should not be nil") - assert.False(t, p1.Subjects.equals(p2.Subjects)) + assert.False(t, p1.Subjects.Equals(p2.Subjects)) p3 := p1 // check case sensitivity p3.Subjects = []string{"user:Accounting@Hexaindustries.io"} - assert.True(t, p1.Subjects.equals(p3.Subjects)) + assert.True(t, p1.Subjects.Equals(p3.Subjects)) } func TestPolicyInfo_actionEquals(t *testing.T) { @@ -94,23 +94,23 @@ func TestPolicyInfo_actionEquals(t *testing.T) { p1 := policies.Policies[0] p2 := policies.Policies[1] - assert.False(t, p1.actionEquals(p2.Actions)) + assert.False(t, p1.ActionsEqual(p2.Actions)) p3 := p2 // check that equivalence works with the same elements in the same order p3.Actions = p1.Actions - assert.True(t, p1.actionEquals(p3.Actions)) + assert.True(t, p1.ActionsEqual(p3.Actions)) // Check that equivalence works out of order p3.Actions = []ActionInfo{"http:POST:/accounting", "http:GET:/accounting"} - assert.True(t, p1.actionEquals(p3.Actions)) + assert.True(t, p1.ActionsEqual(p3.Actions)) p3.Actions = []ActionInfo{"http:POST:/accounting"} - assert.False(t, p1.actionEquals(p3.Actions)) + assert.False(t, p1.ActionsEqual(p3.Actions)) } func TestObjectInfo_equals(t *testing.T) { @@ -153,21 +153,21 @@ func TestScope_equals(t *testing.T) { assert.Equal(t, ScopeTypeIDQL, scope1.Type()) assert.Equal(t, "username eq smith", scope2.Value()) - assert.True(t, scope1.equals(&scope2)) + assert.True(t, scope1.Equals(&scope2)) filter = filter + "and surname eq smith" - assert.False(t, scope1.equals(&scope2)) + assert.False(t, scope1.Equals(&scope2)) filter = "idql:username eq smith" scope2.Attributes = []string{"username"} - assert.False(t, scope1.equals(&scope2)) + assert.False(t, scope1.Equals(&scope2)) scope2.Attributes = []string{"emails", "username"} - assert.True(t, scope1.equals(&scope2)) + assert.True(t, scope1.Equals(&scope2)) scope2.Attributes = []string{"emails", "xyz"} - assert.False(t, scope1.equals(&scope2)) + assert.False(t, scope1.Equals(&scope2)) filter = "dummy" scope2.Filter = &filter @@ -177,15 +177,15 @@ func TestScope_equals(t *testing.T) { scope2.Filter = nil assert.Equal(t, ScopeTypeUnassigned, scope2.Type()) - assert.False(t, scope1.equals(&scope2), "Test one filter is null") - assert.False(t, scope2.equals(scope1), "Test one filter is null") + assert.False(t, scope1.Equals(&scope2), "Test one filter is null") + assert.False(t, scope2.Equals(scope1), "Test one filter is null") filter = "sQl:where username is \"sam\"" scope2.Filter = &filter assert.Equal(t, "where username is \"sam\"", scope2.Value()) assert.Equal(t, ScopeTypeSQL, scope2.Type()) - assert.False(t, scope1.equals(&scope2), "Test filters of different types") + assert.False(t, scope1.Equals(&scope2), "Test filters of different types") } @@ -262,7 +262,7 @@ func TestPolicyInfo_Equals(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := &tt.fields.testPolicy - assert.Equalf(t, tt.want, p.Equals(tt.args.hexaPolicy), "equals(%v)", tt.args.hexaPolicy) + assert.Equalf(t, tt.want, p.Equals(tt.args.hexaPolicy), "Equals(%v)", tt.args.hexaPolicy) }) } }