Merge pull request #16 from ucan-wg/policy-serial
policy: IPLD encode/decode
This commit is contained in:
262
capability/policy/ipld.go
Normal file
262
capability/policy/ipld.go
Normal file
@@ -0,0 +1,262 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/must"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/v1/capability/policy/selector"
|
||||
)
|
||||
|
||||
func FromIPLD(node datamodel.Node) (Policy, error) {
|
||||
return statementsFromIPLD("/", node)
|
||||
}
|
||||
|
||||
func FromDagJson(json string) (Policy, error) {
|
||||
nodes, err := ipld.Decode([]byte(json), dagjson.Decode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FromIPLD(nodes)
|
||||
}
|
||||
|
||||
func statementFromIPLD(path string, node datamodel.Node) (Statement, error) {
|
||||
// sanity checks
|
||||
if node.Kind() != datamodel.Kind_List {
|
||||
return nil, ErrNotATuple(path)
|
||||
}
|
||||
if node.Length() != 2 && node.Length() != 3 {
|
||||
return nil, ErrUnrecognizedShape(path)
|
||||
}
|
||||
|
||||
// extract operator
|
||||
opNode, _ := node.LookupByIndex(0)
|
||||
if opNode.Kind() != datamodel.Kind_String {
|
||||
return nil, ErrNotAString(path)
|
||||
}
|
||||
op := must.String(opNode)
|
||||
|
||||
arg2AsSelector := func() (selector.Selector, error) {
|
||||
nd, _ := node.LookupByIndex(1)
|
||||
if nd.Kind() != datamodel.Kind_String {
|
||||
return nil, ErrNotAString(path + "1/")
|
||||
}
|
||||
sel, err := selector.Parse(must.String(nd))
|
||||
if err != nil {
|
||||
return nil, ErrInvalidSelector(path+"1/", err)
|
||||
}
|
||||
return sel, nil
|
||||
}
|
||||
|
||||
switch node.Length() {
|
||||
case 2:
|
||||
switch op {
|
||||
case KindNot:
|
||||
arg2, _ := node.LookupByIndex(1)
|
||||
statement, err := statementFromIPLD(path+"1/", arg2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Not(statement), nil
|
||||
|
||||
case KindAnd, KindOr:
|
||||
arg2, _ := node.LookupByIndex(1)
|
||||
statement, err := statementsFromIPLD(path+"1/", arg2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connective{kind: op, statements: statement}, nil
|
||||
|
||||
default:
|
||||
return nil, ErrUnrecognizedOperator(path, op)
|
||||
}
|
||||
case 3:
|
||||
switch op {
|
||||
case KindEqual, KindLessThan, KindLessThanOrEqual, KindGreaterThan, KindGreaterThanOrEqual:
|
||||
sel, err := arg2AsSelector()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arg3, _ := node.LookupByIndex(2)
|
||||
return equality{kind: op, selector: sel, value: arg3}, nil
|
||||
|
||||
case KindLike:
|
||||
sel, err := arg2AsSelector()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pattern, _ := node.LookupByIndex(2)
|
||||
if pattern.Kind() != datamodel.Kind_String {
|
||||
return nil, ErrNotAString(path + "2/")
|
||||
}
|
||||
res, err := Like(sel, must.String(pattern))
|
||||
if err != nil {
|
||||
return nil, ErrInvalidPattern(path+"2/", err)
|
||||
}
|
||||
return res, nil
|
||||
|
||||
case KindAll, KindAny:
|
||||
sel, err := arg2AsSelector()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statementsNodes, _ := node.LookupByIndex(2)
|
||||
statements, err := statementsFromIPLD(path+"1/", statementsNodes)
|
||||
return quantifier{kind: op, selector: sel, statements: statements}, nil
|
||||
|
||||
default:
|
||||
return nil, ErrUnrecognizedOperator(path, op)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, ErrUnrecognizedShape(path)
|
||||
}
|
||||
}
|
||||
|
||||
func statementsFromIPLD(path string, node datamodel.Node) ([]Statement, error) {
|
||||
// sanity checks
|
||||
if node.Kind() != datamodel.Kind_List {
|
||||
return nil, ErrNotATuple(path)
|
||||
}
|
||||
if node.Length() == 0 {
|
||||
return nil, ErrEmptyList(path)
|
||||
}
|
||||
|
||||
res := make([]Statement, node.Length())
|
||||
|
||||
for i := int64(0); i < node.Length(); i++ {
|
||||
nd, _ := node.LookupByIndex(i)
|
||||
statement, err := statementFromIPLD(fmt.Sprintf("%s%d/", path, i), nd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res[i] = statement
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (p Policy) ToIPLD() (datamodel.Node, error) {
|
||||
return statementsToIPLD(p)
|
||||
}
|
||||
|
||||
func statementsToIPLD(statements []Statement) (datamodel.Node, error) {
|
||||
list := basicnode.Prototype.List.NewBuilder()
|
||||
// can't error, we have the right builder.
|
||||
listBuilder, _ := list.BeginList(int64(len(statements)))
|
||||
for _, argStatement := range statements {
|
||||
node, err := statementToIPLD(argStatement)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = listBuilder.AssembleValue().AssignNode(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err := listBuilder.Finish()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list.Build(), nil
|
||||
}
|
||||
|
||||
func statementToIPLD(statement Statement) (datamodel.Node, error) {
|
||||
list := basicnode.Prototype.List.NewBuilder()
|
||||
|
||||
length := int64(3)
|
||||
switch statement.(type) {
|
||||
case negation, connective:
|
||||
length = 2
|
||||
}
|
||||
|
||||
// can't error, we have the right builder.
|
||||
listBuilder, _ := list.BeginList(length)
|
||||
|
||||
switch statement := statement.(type) {
|
||||
case equality:
|
||||
err := listBuilder.AssembleValue().AssignString(statement.kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = listBuilder.AssembleValue().AssignString(statement.selector.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = listBuilder.AssembleValue().AssignNode(statement.value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case negation:
|
||||
err := listBuilder.AssembleValue().AssignString(statement.Kind())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err := statementToIPLD(statement.statement)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = listBuilder.AssembleValue().AssignNode(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case connective:
|
||||
err := listBuilder.AssembleValue().AssignString(statement.kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args, err := statementsToIPLD(statement.statements)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = listBuilder.AssembleValue().AssignNode(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case wildcard:
|
||||
err := listBuilder.AssembleValue().AssignString(statement.Kind())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = listBuilder.AssembleValue().AssignString(statement.selector.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = listBuilder.AssembleValue().AssignString(statement.pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case quantifier:
|
||||
err := listBuilder.AssembleValue().AssignString(statement.kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = listBuilder.AssembleValue().AssignString(statement.selector.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args, err := statementsToIPLD(statement.statements)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = listBuilder.AssembleValue().AssignNode(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err := listBuilder.Finish()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return list.Build(), nil
|
||||
}
|
||||
47
capability/policy/ipld_errors.go
Normal file
47
capability/policy/ipld_errors.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package policy
|
||||
|
||||
import "fmt"
|
||||
|
||||
type errWithPath struct {
|
||||
path string
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e errWithPath) Error() string {
|
||||
return fmt.Sprintf("IPLD path '%s': %s", e.path, e.msg)
|
||||
}
|
||||
|
||||
func ErrInvalidSelector(path string, err error) error {
|
||||
return errWithPath{path: path, msg: fmt.Sprintf("invalid selector: %s", err)}
|
||||
}
|
||||
|
||||
func ErrInvalidPattern(path string, err error) error {
|
||||
return errWithPath{path: path, msg: fmt.Sprintf("invalid pattern: %s", err)}
|
||||
}
|
||||
|
||||
func ErrNotAString(path string) error {
|
||||
return errWithPath{path: path, msg: ""}
|
||||
}
|
||||
|
||||
func ErrUnrecognizedOperator(path string, op string) error {
|
||||
return errWithPath{path: path, msg: fmt.Sprintf("unrecognized operator '%s'", safeStr(op))}
|
||||
}
|
||||
|
||||
func ErrUnrecognizedShape(path string) error {
|
||||
return errWithPath{path: path, msg: "unrecognized shape"}
|
||||
}
|
||||
|
||||
func ErrNotATuple(path string) error {
|
||||
return errWithPath{path: path, msg: "not a tuple"}
|
||||
}
|
||||
|
||||
func ErrEmptyList(path string) error {
|
||||
return errWithPath{path: path, msg: "empty list"}
|
||||
}
|
||||
|
||||
func safeStr(str string) string {
|
||||
if len(str) > 10 {
|
||||
return str[:10]
|
||||
}
|
||||
return str
|
||||
}
|
||||
45
capability/policy/ipld_test.go
Normal file
45
capability/policy/ipld_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIpldRoundTrip(t *testing.T) {
|
||||
const illustrativeExample = `
|
||||
[
|
||||
["==", ".status", "draft"],
|
||||
["all", ".reviewer", [
|
||||
["like", ".email", "*@example.com"]]
|
||||
],
|
||||
["any", ".tags", [
|
||||
["or", [
|
||||
["==", ".", "news"],
|
||||
["==", ".", "press"]]
|
||||
]]
|
||||
]
|
||||
]`
|
||||
|
||||
for _, tc := range []struct {
|
||||
name, dagJsonStr string
|
||||
}{
|
||||
{"illustrativeExample", illustrativeExample},
|
||||
} {
|
||||
nodes, err := ipld.Decode([]byte(tc.dagJsonStr), dagjson.Decode)
|
||||
require.NoError(t, err)
|
||||
|
||||
pol, err := FromIPLD(nodes)
|
||||
require.NoError(t, err)
|
||||
|
||||
wroteIpld, err := pol.ToIPLD()
|
||||
require.NoError(t, err)
|
||||
|
||||
wroteAsDagJson, err := ipld.Encode(wroteIpld, dagjson.Encode)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.JSONEq(t, tc.dagJsonStr, string(wroteAsDagJson))
|
||||
}
|
||||
}
|
||||
@@ -25,52 +25,52 @@ func Match(policy Policy, node ipld.Node) bool {
|
||||
func matchStatement(statement Statement, node ipld.Node) bool {
|
||||
switch statement.Kind() {
|
||||
case KindEqual:
|
||||
if s, ok := statement.(EqualityStatement); ok {
|
||||
one, _, err := selector.Select(s.Selector(), node)
|
||||
if s, ok := statement.(equality); ok {
|
||||
one, _, err := selector.Select(s.selector, node)
|
||||
if err != nil || one == nil {
|
||||
return false
|
||||
}
|
||||
return datamodel.DeepEqual(s.Value(), one)
|
||||
return datamodel.DeepEqual(s.value, one)
|
||||
}
|
||||
case KindGreaterThan:
|
||||
if s, ok := statement.(InequalityStatement); ok {
|
||||
one, _, err := selector.Select(s.Selector(), node)
|
||||
if s, ok := statement.(equality); ok {
|
||||
one, _, err := selector.Select(s.selector, node)
|
||||
if err != nil || one == nil {
|
||||
return false
|
||||
}
|
||||
return isOrdered(s.Value(), one, gt)
|
||||
return isOrdered(s.value, one, gt)
|
||||
}
|
||||
case KindGreaterThanOrEqual:
|
||||
if s, ok := statement.(InequalityStatement); ok {
|
||||
one, _, err := selector.Select(s.Selector(), node)
|
||||
if s, ok := statement.(equality); ok {
|
||||
one, _, err := selector.Select(s.selector, node)
|
||||
if err != nil || one == nil {
|
||||
return false
|
||||
}
|
||||
return isOrdered(s.Value(), one, gte)
|
||||
return isOrdered(s.value, one, gte)
|
||||
}
|
||||
case KindLessThan:
|
||||
if s, ok := statement.(InequalityStatement); ok {
|
||||
one, _, err := selector.Select(s.Selector(), node)
|
||||
if s, ok := statement.(equality); ok {
|
||||
one, _, err := selector.Select(s.selector, node)
|
||||
if err != nil || one == nil {
|
||||
return false
|
||||
}
|
||||
return isOrdered(s.Value(), one, lt)
|
||||
return isOrdered(s.value, one, lt)
|
||||
}
|
||||
case KindLessThanOrEqual:
|
||||
if s, ok := statement.(InequalityStatement); ok {
|
||||
one, _, err := selector.Select(s.Selector(), node)
|
||||
if s, ok := statement.(equality); ok {
|
||||
one, _, err := selector.Select(s.selector, node)
|
||||
if err != nil || one == nil {
|
||||
return false
|
||||
}
|
||||
return isOrdered(s.Value(), one, lte)
|
||||
return isOrdered(s.value, one, lte)
|
||||
}
|
||||
case KindNot:
|
||||
if s, ok := statement.(NegationStatement); ok {
|
||||
return !matchStatement(s.Value(), node)
|
||||
if s, ok := statement.(negation); ok {
|
||||
return !matchStatement(s.statement, node)
|
||||
}
|
||||
case KindAnd:
|
||||
if s, ok := statement.(ConjunctionStatement); ok {
|
||||
for _, cs := range s.Value() {
|
||||
if s, ok := statement.(connective); ok {
|
||||
for _, cs := range s.statements {
|
||||
r := matchStatement(cs, node)
|
||||
if !r {
|
||||
return false
|
||||
@@ -79,11 +79,11 @@ func matchStatement(statement Statement, node ipld.Node) bool {
|
||||
return true
|
||||
}
|
||||
case KindOr:
|
||||
if s, ok := statement.(DisjunctionStatement); ok {
|
||||
if len(s.Value()) == 0 {
|
||||
if s, ok := statement.(connective); ok {
|
||||
if len(s.statements) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, cs := range s.Value() {
|
||||
for _, cs := range s.statements {
|
||||
r := matchStatement(cs, node)
|
||||
if r {
|
||||
return true
|
||||
@@ -92,8 +92,8 @@ func matchStatement(statement Statement, node ipld.Node) bool {
|
||||
return false
|
||||
}
|
||||
case KindLike:
|
||||
if s, ok := statement.(WildcardStatement); ok {
|
||||
one, _, err := selector.Select(s.Selector(), node)
|
||||
if s, ok := statement.(wildcard); ok {
|
||||
one, _, err := selector.Select(s.selector, node)
|
||||
if err != nil || one == nil {
|
||||
return false
|
||||
}
|
||||
@@ -101,16 +101,16 @@ func matchStatement(statement Statement, node ipld.Node) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return s.Value().Match(v)
|
||||
return s.glob.Match(v)
|
||||
}
|
||||
case KindAll:
|
||||
if s, ok := statement.(QuantifierStatement); ok {
|
||||
_, many, err := selector.Select(s.Selector(), node)
|
||||
if s, ok := statement.(quantifier); ok {
|
||||
_, many, err := selector.Select(s.selector, node)
|
||||
if err != nil || many == nil {
|
||||
return false
|
||||
}
|
||||
for _, n := range many {
|
||||
ok := Match(s.Value(), n)
|
||||
ok := Match(s.statements, n)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -118,13 +118,13 @@ func matchStatement(statement Statement, node ipld.Node) bool {
|
||||
return true
|
||||
}
|
||||
case KindAny:
|
||||
if s, ok := statement.(QuantifierStatement); ok {
|
||||
_, many, err := selector.Select(s.Selector(), node)
|
||||
if s, ok := statement.(quantifier); ok {
|
||||
_, many, err := selector.Select(s.selector, node)
|
||||
if err != nil || many == nil {
|
||||
return false
|
||||
}
|
||||
for _, n := range many {
|
||||
ok := Match(s.Value(), n)
|
||||
ok := Match(s.statements, n)
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
|
||||
@@ -294,8 +293,7 @@ func TestMatch(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("wildcard", func(t *testing.T) {
|
||||
glb, err := glob.Compile(`Alice\*, Bob*, Carol.`)
|
||||
require.NoError(t, err)
|
||||
pattern := `Alice\*, Bob*, Carol.`
|
||||
|
||||
for _, s := range []string{
|
||||
"Alice*, Bob, Carol.",
|
||||
@@ -310,7 +308,10 @@ func TestMatch(t *testing.T) {
|
||||
nb.AssignString(s)
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Like(selector.MustParse("."), glb)}
|
||||
statement, err := Like(selector.MustParse("."), pattern)
|
||||
require.NoError(t, err)
|
||||
|
||||
pol := Policy{statement}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
@@ -331,7 +332,10 @@ func TestMatch(t *testing.T) {
|
||||
nb.AssignString(s)
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Like(selector.MustParse("."), glb)}
|
||||
statement, err := Like(selector.MustParse("."), pattern)
|
||||
require.NoError(t, err)
|
||||
|
||||
pol := Policy{statement}
|
||||
ok := Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
@@ -10,68 +10,25 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
KindEqual = "=="
|
||||
KindGreaterThan = ">"
|
||||
KindGreaterThanOrEqual = ">="
|
||||
KindLessThan = "<"
|
||||
KindLessThanOrEqual = "<="
|
||||
KindNot = "not"
|
||||
KindAnd = "and"
|
||||
KindOr = "or"
|
||||
KindLike = "like"
|
||||
KindAll = "all"
|
||||
KindAny = "any"
|
||||
KindEqual = "==" // implemented by equality
|
||||
KindGreaterThan = ">" // implemented by equality
|
||||
KindGreaterThanOrEqual = ">=" // implemented by equality
|
||||
KindLessThan = "<" // implemented by equality
|
||||
KindLessThanOrEqual = "<=" // implemented by equality
|
||||
KindNot = "not" // implemented by negation
|
||||
KindAnd = "and" // implemented by connective
|
||||
KindOr = "or" // implemented by connective
|
||||
KindLike = "like" // implemented by wildcard
|
||||
KindAll = "all" // implemented by quantifier
|
||||
KindAny = "any" // implemented by quantifier
|
||||
)
|
||||
|
||||
type Policy = []Statement
|
||||
type Policy []Statement
|
||||
|
||||
type Statement interface {
|
||||
Kind() string
|
||||
}
|
||||
|
||||
type EqualityStatement interface {
|
||||
Statement
|
||||
Selector() selector.Selector
|
||||
Value() ipld.Node
|
||||
}
|
||||
|
||||
type InequalityStatement interface {
|
||||
Statement
|
||||
Selector() selector.Selector
|
||||
Value() ipld.Node
|
||||
}
|
||||
|
||||
type WildcardStatement interface {
|
||||
Statement
|
||||
Selector() selector.Selector
|
||||
Value() glob.Glob
|
||||
}
|
||||
|
||||
type ConnectiveStatement interface {
|
||||
Statement
|
||||
}
|
||||
|
||||
type NegationStatement interface {
|
||||
ConnectiveStatement
|
||||
Value() Statement
|
||||
}
|
||||
|
||||
type ConjunctionStatement interface {
|
||||
ConnectiveStatement
|
||||
Value() []Statement
|
||||
}
|
||||
|
||||
type DisjunctionStatement interface {
|
||||
ConnectiveStatement
|
||||
Value() []Statement
|
||||
}
|
||||
|
||||
type QuantifierStatement interface {
|
||||
Statement
|
||||
Selector() selector.Selector
|
||||
Value() Policy
|
||||
}
|
||||
|
||||
type equality struct {
|
||||
kind string
|
||||
selector selector.Selector
|
||||
@@ -82,31 +39,23 @@ func (e equality) Kind() string {
|
||||
return e.kind
|
||||
}
|
||||
|
||||
func (e equality) Value() ipld.Node {
|
||||
return e.value
|
||||
}
|
||||
|
||||
func (e equality) Selector() selector.Selector {
|
||||
return e.selector
|
||||
}
|
||||
|
||||
func Equal(selector selector.Selector, value ipld.Node) EqualityStatement {
|
||||
func Equal(selector selector.Selector, value ipld.Node) Statement {
|
||||
return equality{KindEqual, selector, value}
|
||||
}
|
||||
|
||||
func GreaterThan(selector selector.Selector, value ipld.Node) InequalityStatement {
|
||||
func GreaterThan(selector selector.Selector, value ipld.Node) Statement {
|
||||
return equality{KindGreaterThan, selector, value}
|
||||
}
|
||||
|
||||
func GreaterThanOrEqual(selector selector.Selector, value ipld.Node) InequalityStatement {
|
||||
func GreaterThanOrEqual(selector selector.Selector, value ipld.Node) Statement {
|
||||
return equality{KindGreaterThanOrEqual, selector, value}
|
||||
}
|
||||
|
||||
func LessThan(selector selector.Selector, value ipld.Node) InequalityStatement {
|
||||
func LessThan(selector selector.Selector, value ipld.Node) Statement {
|
||||
return equality{KindLessThan, selector, value}
|
||||
}
|
||||
|
||||
func LessThanOrEqual(selector selector.Selector, value ipld.Node) InequalityStatement {
|
||||
func LessThanOrEqual(selector selector.Selector, value ipld.Node) Statement {
|
||||
return equality{KindLessThanOrEqual, selector, value}
|
||||
}
|
||||
|
||||
@@ -118,89 +67,59 @@ func (n negation) Kind() string {
|
||||
return KindNot
|
||||
}
|
||||
|
||||
func (n negation) Value() Statement {
|
||||
return n.statement
|
||||
}
|
||||
|
||||
func Not(stmt Statement) NegationStatement {
|
||||
func Not(stmt Statement) Statement {
|
||||
return negation{stmt}
|
||||
}
|
||||
|
||||
type conjunction struct {
|
||||
type connective struct {
|
||||
kind string
|
||||
statements []Statement
|
||||
}
|
||||
|
||||
func (n conjunction) Kind() string {
|
||||
return KindAnd
|
||||
func (c connective) Kind() string {
|
||||
return c.kind
|
||||
}
|
||||
|
||||
func (n conjunction) Value() []Statement {
|
||||
return n.statements
|
||||
func And(stmts ...Statement) Statement {
|
||||
return connective{KindAnd, stmts}
|
||||
}
|
||||
|
||||
func And(stmts ...Statement) ConjunctionStatement {
|
||||
return conjunction{stmts}
|
||||
}
|
||||
|
||||
type disjunction struct {
|
||||
statements []Statement
|
||||
}
|
||||
|
||||
func (n disjunction) Kind() string {
|
||||
return KindOr
|
||||
}
|
||||
|
||||
func (n disjunction) Value() []Statement {
|
||||
return n.statements
|
||||
}
|
||||
|
||||
func Or(stmts ...Statement) DisjunctionStatement {
|
||||
return disjunction{stmts}
|
||||
func Or(stmts ...Statement) Statement {
|
||||
return connective{KindOr, stmts}
|
||||
}
|
||||
|
||||
type wildcard struct {
|
||||
selector selector.Selector
|
||||
glob glob.Glob
|
||||
pattern string
|
||||
glob glob.Glob // not serialized
|
||||
}
|
||||
|
||||
func (n wildcard) Kind() string {
|
||||
return KindLike
|
||||
}
|
||||
|
||||
func (n wildcard) Selector() selector.Selector {
|
||||
return n.selector
|
||||
}
|
||||
|
||||
func (n wildcard) Value() glob.Glob {
|
||||
return n.glob
|
||||
}
|
||||
|
||||
func Like(selector selector.Selector, glob glob.Glob) WildcardStatement {
|
||||
return wildcard{selector, glob}
|
||||
func Like(selector selector.Selector, pattern string) (Statement, error) {
|
||||
g, err := glob.Compile(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wildcard{selector, pattern, g}, nil
|
||||
}
|
||||
|
||||
type quantifier struct {
|
||||
kind string
|
||||
selector selector.Selector
|
||||
policy Policy
|
||||
kind string
|
||||
selector selector.Selector
|
||||
statements []Statement
|
||||
}
|
||||
|
||||
func (n quantifier) Kind() string {
|
||||
return n.kind
|
||||
}
|
||||
|
||||
func (n quantifier) Selector() selector.Selector {
|
||||
return n.selector
|
||||
}
|
||||
|
||||
func (n quantifier) Value() Policy {
|
||||
return n.policy
|
||||
}
|
||||
|
||||
func All(selector selector.Selector, policy ...Statement) QuantifierStatement {
|
||||
func All(selector selector.Selector, policy ...Statement) Statement {
|
||||
return quantifier{KindAll, selector, policy}
|
||||
}
|
||||
|
||||
func Any(selector selector.Selector, policy ...Statement) QuantifierStatement {
|
||||
func Any(selector selector.Selector, policy ...Statement) Statement {
|
||||
return quantifier{KindAny, selector, policy}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ type Payload struct {
|
||||
cmd String
|
||||
|
||||
# The delegation policy
|
||||
pol Policy
|
||||
# It doesn't seem possible to represent it with a schema.
|
||||
pol Any
|
||||
|
||||
# A unique, random nonce
|
||||
nonce Bytes
|
||||
@@ -26,7 +27,3 @@ type Payload struct {
|
||||
# The timestamp at which the Invocation becomes invalid
|
||||
exp nullable Int
|
||||
}
|
||||
|
||||
type Policy struct {
|
||||
|
||||
}
|
||||
@@ -46,7 +46,7 @@ type PayloadModel struct {
|
||||
Cmd string
|
||||
|
||||
// The delegation policy
|
||||
Pol PolicyModel
|
||||
Pol datamodel.Node
|
||||
|
||||
// A unique, random nonce
|
||||
Nonce []byte
|
||||
@@ -67,10 +67,3 @@ type MetaModel struct {
|
||||
Keys []string
|
||||
Values map[string]datamodel.Node
|
||||
}
|
||||
|
||||
type PolicyModel struct {
|
||||
}
|
||||
|
||||
func PointerTo[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
@@ -5,51 +5,62 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/node/bindnode"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSchemaRoundTrip(t *testing.T) {
|
||||
p := &PayloadModel{
|
||||
Iss: "did:key:abc123",
|
||||
Aud: "did:key:def456",
|
||||
Sub: PointerTo(""),
|
||||
Cmd: "/foo/bar",
|
||||
Pol: PolicyModel{}, // TODO: have something here
|
||||
Nonce: []byte("super-random"),
|
||||
Meta: MetaModel{
|
||||
Keys: []string{"foo", "bar"},
|
||||
Values: map[string]datamodel.Node{
|
||||
"foo": bindnode.Wrap(PointerTo("fooo"), nil),
|
||||
"bar": bindnode.Wrap(PointerTo("baaar"), nil),
|
||||
},
|
||||
},
|
||||
Nbf: PointerTo(int64(123456)),
|
||||
Exp: PointerTo(int64(123456)),
|
||||
}
|
||||
const delegationJson = `
|
||||
{
|
||||
"aud":"did:key:def456",
|
||||
"cmd":"/foo/bar",
|
||||
"exp":123456,
|
||||
"iss":"did:key:abc123",
|
||||
"meta":{
|
||||
"bar":"baaar",
|
||||
"foo":"fooo"
|
||||
},
|
||||
"nbf":123456,
|
||||
"nonce":{
|
||||
"/":{
|
||||
"bytes":"c3VwZXItcmFuZG9t"
|
||||
}
|
||||
},
|
||||
"pol":[
|
||||
["==", ".status", "draft"],
|
||||
["all", ".reviewer", [
|
||||
["like", ".email", "*@example.com"]]
|
||||
],
|
||||
["any", ".tags", [
|
||||
["or", [
|
||||
["==", ".", "news"],
|
||||
["==", ".", "press"]]
|
||||
]]
|
||||
]
|
||||
],
|
||||
"sub":""
|
||||
}
|
||||
`
|
||||
// format: dagJson --> PayloadModel --> dagCbor --> PayloadModel --> dagJson
|
||||
// function: DecodeDagJson() EncodeDagCbor() DecodeDagCbor() EncodeDagJson()
|
||||
|
||||
cborBytes, err := p.EncodeDagCbor()
|
||||
p1, err := DecodeDagJson([]byte(delegationJson))
|
||||
require.NoError(t, err)
|
||||
|
||||
cborBytes, err := p1.EncodeDagCbor()
|
||||
require.NoError(t, err)
|
||||
fmt.Println("cborBytes length", len(cborBytes))
|
||||
fmt.Println("cbor", string(cborBytes))
|
||||
|
||||
jsonBytes, err := p.EncodeDagJson()
|
||||
p2, err := DecodeDagCbor(cborBytes)
|
||||
require.NoError(t, err)
|
||||
fmt.Println("jsonBytes length", len(jsonBytes))
|
||||
fmt.Println("json: ", string(jsonBytes))
|
||||
fmt.Println("read Cbor", p2)
|
||||
|
||||
fmt.Println()
|
||||
|
||||
readCbor, err := DecodeDagCbor(cborBytes)
|
||||
readJson, err := p2.EncodeDagJson()
|
||||
require.NoError(t, err)
|
||||
fmt.Println("readCbor", readCbor)
|
||||
require.Equal(t, p, readCbor)
|
||||
fmt.Println("readJson length", len(readJson))
|
||||
fmt.Println("json: ", string(readJson))
|
||||
|
||||
readJson, err := DecodeDagJson(jsonBytes)
|
||||
require.NoError(t, err)
|
||||
fmt.Println("readJson", readJson)
|
||||
require.Equal(t, p, readJson)
|
||||
require.JSONEq(t, delegationJson, string(readJson))
|
||||
}
|
||||
|
||||
func BenchmarkSchemaLoad(b *testing.B) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/v1/capability/command"
|
||||
"github.com/ucan-wg/go-ucan/v1/capability/policy"
|
||||
"github.com/ucan-wg/go-ucan/v1/did"
|
||||
)
|
||||
@@ -18,7 +19,7 @@ type View struct {
|
||||
// Principal that the chain is about (the Subject)
|
||||
Subject did.DID
|
||||
// The Command to eventually invoke
|
||||
Command string
|
||||
Command *command.Command
|
||||
// The delegation policy
|
||||
Policy policy.Policy
|
||||
// A unique, random nonce
|
||||
@@ -56,11 +57,15 @@ func ViewFromModel(m PayloadModel) (*View, error) {
|
||||
view.Subject = did.Undef
|
||||
}
|
||||
|
||||
// TODO: make that a Command object, and validate it
|
||||
view.Command = m.Cmd
|
||||
view.Command, err = command.Parse(m.Cmd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse command: %w", err)
|
||||
}
|
||||
|
||||
// TODO: parsing + validation
|
||||
view.Policy = policy.Policy{}
|
||||
view.Policy, err = policy.FromIPLD(m.Pol)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse policy: %w", err)
|
||||
}
|
||||
|
||||
if len(m.Nonce) == 0 {
|
||||
return nil, fmt.Errorf("nonce is required")
|
||||
@@ -80,22 +85,3 @@ func ViewFromModel(m PayloadModel) (*View, error) {
|
||||
|
||||
return &view, nil
|
||||
}
|
||||
|
||||
func (view *View) Capability() *Capability {
|
||||
return &Capability{
|
||||
Subject: view.Subject,
|
||||
Command: view.Command,
|
||||
Policy: view.Policy,
|
||||
}
|
||||
}
|
||||
|
||||
// Capability is a subset of a delegation formed by the triple (subject, command, policy).
|
||||
// TODO: useful?
|
||||
type Capability struct {
|
||||
// Principal that the chain is about (the Subject)
|
||||
Subject did.DID
|
||||
// The Command to eventually invoke
|
||||
Command string
|
||||
// The delegation policy
|
||||
Policy policy.Policy
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user