2024-08-19 23:16:36 +02:00
|
|
|
package policy
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"cmp"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"github.com/ipld/go-ipld-prime"
|
2024-08-20 22:27:56 +02:00
|
|
|
"github.com/ipld/go-ipld-prime/datamodel"
|
|
|
|
|
"github.com/ipld/go-ipld-prime/must"
|
2024-08-19 23:16:36 +02:00
|
|
|
)
|
|
|
|
|
|
2024-10-16 11:18:02 +02:00
|
|
|
// Match determines if the IPLD node satisfies the policy.
|
2024-10-15 16:37:24 +02:00
|
|
|
func (p Policy) Match(node datamodel.Node) bool {
|
|
|
|
|
for _, stmt := range p {
|
2024-10-24 13:50:56 +02:00
|
|
|
res, _ := matchStatement(stmt, node)
|
|
|
|
|
switch res {
|
|
|
|
|
case matchResultNoData, matchResultFalse:
|
2024-08-21 08:44:17 +02:00
|
|
|
return false
|
2024-10-24 13:50:56 +02:00
|
|
|
case matchResultTrue:
|
|
|
|
|
// continue
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-08-20 22:27:56 +02:00
|
|
|
return true
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-24 13:50:56 +02:00
|
|
|
// PartialMatch returns false IIF one of the Statement has the corresponding data and doesn't match.
|
|
|
|
|
// If the data is missing or the Statement is matching, true is returned.
|
|
|
|
|
//
|
|
|
|
|
// This allows performing the policy checking in multiple steps, and find immediately if a Statement already failed.
|
|
|
|
|
// A final call to Match is necessary to make sure that the policy is fully matched, with no missing data
|
|
|
|
|
// (apart from optional values).
|
|
|
|
|
//
|
|
|
|
|
// The first Statement failing to match is returned as well.
|
|
|
|
|
func (p Policy) PartialMatch(node datamodel.Node) (bool, Statement) {
|
|
|
|
|
for _, stmt := range p {
|
|
|
|
|
res, leaf := matchStatement(stmt, node)
|
|
|
|
|
switch res {
|
|
|
|
|
case matchResultFalse:
|
|
|
|
|
return false, leaf
|
|
|
|
|
case matchResultNoData, matchResultTrue:
|
|
|
|
|
// continue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type matchResult int8
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
matchResultTrue matchResult = iota
|
|
|
|
|
matchResultFalse
|
|
|
|
|
matchResultNoData
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// matchStatement evaluate the policy against the given ipld.Node and returns:
|
|
|
|
|
// - matchResultTrue: if the selector matched and the statement evaluated to true.
|
|
|
|
|
// - matchResultFalse: if the selector matched and the statement evaluated to false.
|
|
|
|
|
// - matchResultNoData: if the selector didn't match the expected data.
|
|
|
|
|
// For matchResultTrue and matchResultNoData, the leaf-most (innermost) statement failing to be true is returned,
|
|
|
|
|
// as well as the corresponding root-most encompassing statement.
|
|
|
|
|
func matchStatement(cur Statement, node ipld.Node) (_ matchResult, leafMost Statement) {
|
|
|
|
|
var boolToRes = func(v bool) (matchResult, Statement) {
|
|
|
|
|
if v {
|
|
|
|
|
return matchResultTrue, nil
|
|
|
|
|
} else {
|
|
|
|
|
return matchResultFalse, cur
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch cur.Kind() {
|
2024-09-01 17:06:21 +02:00
|
|
|
case KindEqual:
|
2024-10-24 13:50:56 +02:00
|
|
|
if s, ok := cur.(equality); ok {
|
2024-10-22 16:27:01 +02:00
|
|
|
res, err := s.selector.Select(node)
|
2024-11-04 10:56:06 +01:00
|
|
|
if err != nil {
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultNoData, cur
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-11-04 10:56:06 +01:00
|
|
|
if res == nil { // Optional selector that didn't match
|
|
|
|
|
return matchResultTrue, nil
|
|
|
|
|
}
|
2024-10-24 13:50:56 +02:00
|
|
|
return boolToRes(datamodel.DeepEqual(s.value, res))
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-09-01 17:06:21 +02:00
|
|
|
case KindGreaterThan:
|
2024-10-24 13:50:56 +02:00
|
|
|
if s, ok := cur.(equality); ok {
|
2024-10-22 16:27:01 +02:00
|
|
|
res, err := s.selector.Select(node)
|
2024-11-04 10:56:06 +01:00
|
|
|
if err != nil {
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultNoData, cur
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-11-04 10:56:06 +01:00
|
|
|
if res == nil {
|
|
|
|
|
return matchResultTrue, nil
|
|
|
|
|
}
|
2024-10-24 13:50:56 +02:00
|
|
|
return boolToRes(isOrdered(s.value, res, gt))
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-09-01 17:06:21 +02:00
|
|
|
case KindGreaterThanOrEqual:
|
2024-10-24 13:50:56 +02:00
|
|
|
if s, ok := cur.(equality); ok {
|
2024-10-22 16:27:01 +02:00
|
|
|
res, err := s.selector.Select(node)
|
2024-11-04 10:56:06 +01:00
|
|
|
if err != nil {
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultNoData, cur
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-11-04 10:56:06 +01:00
|
|
|
if res == nil {
|
|
|
|
|
return matchResultTrue, nil
|
|
|
|
|
}
|
2024-10-24 13:50:56 +02:00
|
|
|
return boolToRes(isOrdered(s.value, res, gte))
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-09-01 17:06:21 +02:00
|
|
|
case KindLessThan:
|
2024-10-24 13:50:56 +02:00
|
|
|
if s, ok := cur.(equality); ok {
|
2024-10-22 16:27:01 +02:00
|
|
|
res, err := s.selector.Select(node)
|
2024-11-04 10:56:06 +01:00
|
|
|
if err != nil {
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultNoData, cur
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-11-04 10:56:06 +01:00
|
|
|
if res == nil {
|
|
|
|
|
return matchResultTrue, nil
|
|
|
|
|
}
|
2024-10-24 13:50:56 +02:00
|
|
|
return boolToRes(isOrdered(s.value, res, lt))
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-09-01 17:06:21 +02:00
|
|
|
case KindLessThanOrEqual:
|
2024-10-24 13:50:56 +02:00
|
|
|
if s, ok := cur.(equality); ok {
|
2024-10-22 16:27:01 +02:00
|
|
|
res, err := s.selector.Select(node)
|
2024-11-04 10:56:06 +01:00
|
|
|
if err != nil {
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultNoData, cur
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-11-04 10:56:06 +01:00
|
|
|
if res == nil {
|
|
|
|
|
return matchResultTrue, nil
|
|
|
|
|
}
|
2024-10-24 13:50:56 +02:00
|
|
|
return boolToRes(isOrdered(s.value, res, lte))
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-09-01 17:06:21 +02:00
|
|
|
case KindNot:
|
2024-10-24 13:50:56 +02:00
|
|
|
if s, ok := cur.(negation); ok {
|
|
|
|
|
res, leaf := matchStatement(s.statement, node)
|
|
|
|
|
switch res {
|
|
|
|
|
case matchResultNoData:
|
|
|
|
|
return matchResultNoData, leaf
|
|
|
|
|
case matchResultTrue:
|
|
|
|
|
return matchResultFalse, leaf
|
|
|
|
|
case matchResultFalse:
|
|
|
|
|
return matchResultTrue, leaf
|
|
|
|
|
}
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-09-01 17:06:21 +02:00
|
|
|
case KindAnd:
|
2024-10-24 13:50:56 +02:00
|
|
|
if s, ok := cur.(connective); ok {
|
2024-09-02 02:24:13 +02:00
|
|
|
for _, cs := range s.statements {
|
2024-10-24 13:50:56 +02:00
|
|
|
res, leaf := matchStatement(cs, node)
|
|
|
|
|
switch res {
|
|
|
|
|
case matchResultNoData:
|
|
|
|
|
return matchResultNoData, leaf
|
|
|
|
|
case matchResultTrue:
|
|
|
|
|
// continue
|
|
|
|
|
case matchResultFalse:
|
|
|
|
|
return matchResultFalse, leaf
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultTrue, nil
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-09-01 17:06:21 +02:00
|
|
|
case KindOr:
|
2024-10-24 13:50:56 +02:00
|
|
|
if s, ok := cur.(connective); ok {
|
2024-09-02 02:24:13 +02:00
|
|
|
if len(s.statements) == 0 {
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultTrue, nil
|
2024-08-20 15:55:04 +02:00
|
|
|
}
|
2024-09-02 02:24:13 +02:00
|
|
|
for _, cs := range s.statements {
|
2024-10-24 13:50:56 +02:00
|
|
|
res, leaf := matchStatement(cs, node)
|
|
|
|
|
switch res {
|
|
|
|
|
case matchResultNoData:
|
|
|
|
|
return matchResultNoData, leaf
|
|
|
|
|
case matchResultTrue:
|
|
|
|
|
return matchResultTrue, leaf
|
|
|
|
|
case matchResultFalse:
|
|
|
|
|
// continue
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultFalse, cur
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-09-01 17:06:21 +02:00
|
|
|
case KindLike:
|
2024-10-24 13:50:56 +02:00
|
|
|
if s, ok := cur.(wildcard); ok {
|
2024-10-22 16:27:01 +02:00
|
|
|
res, err := s.selector.Select(node)
|
2024-11-04 10:56:06 +01:00
|
|
|
if err != nil {
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultNoData, cur
|
2024-08-21 08:13:44 +02:00
|
|
|
}
|
2024-11-04 10:56:06 +01:00
|
|
|
if res == nil {
|
|
|
|
|
return matchResultTrue, nil
|
|
|
|
|
}
|
2024-10-22 16:27:01 +02:00
|
|
|
v, err := res.AsString()
|
2024-08-21 08:13:44 +02:00
|
|
|
if err != nil {
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultFalse, cur // not a string
|
2024-08-21 08:13:44 +02:00
|
|
|
}
|
2024-10-24 13:50:56 +02:00
|
|
|
return boolToRes(s.pattern.Match(v))
|
2024-08-21 08:13:44 +02:00
|
|
|
}
|
2024-09-01 17:06:21 +02:00
|
|
|
case KindAll:
|
2024-10-24 13:50:56 +02:00
|
|
|
if s, ok := cur.(quantifier); ok {
|
2024-10-22 16:27:01 +02:00
|
|
|
res, err := s.selector.Select(node)
|
2024-11-04 10:56:06 +01:00
|
|
|
if err != nil {
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultNoData, cur
|
2024-08-21 08:44:17 +02:00
|
|
|
}
|
2024-11-04 10:56:06 +01:00
|
|
|
if res == nil {
|
|
|
|
|
return matchResultTrue, nil
|
|
|
|
|
}
|
2024-10-22 16:27:01 +02:00
|
|
|
it := res.ListIterator()
|
|
|
|
|
if it == nil {
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultFalse, cur // not a list
|
2024-10-22 16:27:01 +02:00
|
|
|
}
|
|
|
|
|
for !it.Done() {
|
|
|
|
|
_, v, err := it.Next()
|
|
|
|
|
if err != nil {
|
2024-10-24 13:50:56 +02:00
|
|
|
panic("should never happen")
|
2024-10-22 16:27:01 +02:00
|
|
|
}
|
2024-10-24 13:50:56 +02:00
|
|
|
matchRes, leaf := matchStatement(s.statement, v)
|
|
|
|
|
switch matchRes {
|
|
|
|
|
case matchResultNoData:
|
|
|
|
|
return matchResultNoData, leaf
|
|
|
|
|
case matchResultTrue:
|
|
|
|
|
// continue
|
|
|
|
|
case matchResultFalse:
|
|
|
|
|
return matchResultFalse, leaf
|
2024-08-21 08:44:17 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultTrue, nil
|
2024-08-21 08:44:17 +02:00
|
|
|
}
|
2024-09-01 17:06:21 +02:00
|
|
|
case KindAny:
|
2024-10-24 13:50:56 +02:00
|
|
|
if s, ok := cur.(quantifier); ok {
|
2024-10-22 16:27:01 +02:00
|
|
|
res, err := s.selector.Select(node)
|
2024-11-04 10:56:06 +01:00
|
|
|
if err != nil {
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultNoData, cur
|
2024-08-21 08:44:17 +02:00
|
|
|
}
|
2024-11-04 10:56:06 +01:00
|
|
|
if res == nil {
|
|
|
|
|
return matchResultTrue, nil
|
|
|
|
|
}
|
2024-10-22 16:27:01 +02:00
|
|
|
it := res.ListIterator()
|
|
|
|
|
if it == nil {
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultFalse, cur // not a list
|
2024-10-22 16:27:01 +02:00
|
|
|
}
|
|
|
|
|
for !it.Done() {
|
|
|
|
|
_, v, err := it.Next()
|
|
|
|
|
if err != nil {
|
2024-10-24 13:50:56 +02:00
|
|
|
panic("should never happen")
|
2024-10-22 16:27:01 +02:00
|
|
|
}
|
2024-10-24 13:50:56 +02:00
|
|
|
matchRes, leaf := matchStatement(s.statement, v)
|
|
|
|
|
switch matchRes {
|
|
|
|
|
case matchResultNoData:
|
|
|
|
|
return matchResultNoData, leaf
|
|
|
|
|
case matchResultTrue:
|
|
|
|
|
return matchResultTrue, nil
|
|
|
|
|
case matchResultFalse:
|
|
|
|
|
// continue
|
2024-08-21 08:44:17 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-10-24 13:50:56 +02:00
|
|
|
return matchResultFalse, cur
|
2024-08-21 08:44:17 +02:00
|
|
|
}
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-10-24 13:50:56 +02:00
|
|
|
panic(fmt.Errorf("unimplemented statement kind: %s", cur.Kind()))
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-20 22:27:56 +02:00
|
|
|
func isOrdered(expected ipld.Node, actual ipld.Node, satisfies func(order int) bool) bool {
|
2024-08-20 15:55:04 +02:00
|
|
|
if expected.Kind() == ipld.Kind_Int && actual.Kind() == ipld.Kind_Int {
|
2024-08-20 22:27:56 +02:00
|
|
|
a := must.Int(actual)
|
|
|
|
|
b := must.Int(expected)
|
|
|
|
|
return satisfies(cmp.Compare(a, b))
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-20 15:55:04 +02:00
|
|
|
if expected.Kind() == ipld.Kind_Float && actual.Kind() == ipld.Kind_Float {
|
|
|
|
|
a, err := actual.AsFloat()
|
2024-08-19 23:16:36 +02:00
|
|
|
if err != nil {
|
2024-08-20 22:27:56 +02:00
|
|
|
panic(fmt.Errorf("extracting node float: %w", err))
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-08-20 15:55:04 +02:00
|
|
|
b, err := expected.AsFloat()
|
2024-08-19 23:16:36 +02:00
|
|
|
if err != nil {
|
2024-08-20 22:27:56 +02:00
|
|
|
panic(fmt.Errorf("extracting selector float: %w", err))
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
2024-08-20 22:27:56 +02:00
|
|
|
return satisfies(cmp.Compare(a, b))
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-20 22:27:56 +02:00
|
|
|
return false
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func gt(order int) bool { return order == 1 }
|
|
|
|
|
func gte(order int) bool { return order == 0 || order == 1 }
|
|
|
|
|
func lt(order int) bool { return order == -1 }
|
|
|
|
|
func lte(order int) bool { return order == 0 || order == -1 }
|