From 8c09024003b78224e52399d743229dada3a37104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Sun, 1 Sep 2024 20:27:54 +0200 Subject: [PATCH 1/5] policy: IPLD encode/decode Still WIP --- capability/policy/ipld.go | 252 +++++++++++++++++++++++++++++++ capability/policy/ipld_errors.go | 47 ++++++ capability/policy/ipld_test.go | 48 ++++++ capability/policy/match_test.go | 14 +- capability/policy/policy.go | 48 +++--- 5 files changed, 377 insertions(+), 32 deletions(-) create mode 100644 capability/policy/ipld.go create mode 100644 capability/policy/ipld_errors.go create mode 100644 capability/policy/ipld_test.go diff --git a/capability/policy/ipld.go b/capability/policy/ipld.go new file mode 100644 index 0000000..d11620c --- /dev/null +++ b/capability/policy/ipld.go @@ -0,0 +1,252 @@ +package policy + +import ( + "fmt" + + "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 PolicyFromIPLD(node datamodel.Node) (Policy, error) { + return statementsFromIPLD("/", node) +} + +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 +} diff --git a/capability/policy/ipld_errors.go b/capability/policy/ipld_errors.go new file mode 100644 index 0000000..303e10b --- /dev/null +++ b/capability/policy/ipld_errors.go @@ -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 +} diff --git a/capability/policy/ipld_test.go b/capability/policy/ipld_test.go new file mode 100644 index 0000000..dfb3216 --- /dev/null +++ b/capability/policy/ipld_test.go @@ -0,0 +1,48 @@ +package policy + +import ( + "fmt" + "strings" + "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, dagjson string + }{ + {"illustrativeExample", illustrativeExample}, + } { + // strip all spaces and carriage return + asDagJson := strings.Join(strings.Fields(tc.dagjson), "") + + nodes, err := ipld.Decode([]byte(asDagJson), dagjson.Decode) + require.NoError(t, err) + + pol, err := PolicyFromIPLD(nodes) + require.NoError(t, err) + + fmt.Println(pol) + + wroteIpld, err := pol.ToIPLD() + require.NoError(t, err) + + wroteAsDagJson, err := ipld.Encode(wroteIpld, dagjson.Encode) + require.NoError(t, err) + + require.Equal(t, asDagJson, string(wroteAsDagJson)) + } +} diff --git a/capability/policy/match_test.go b/capability/policy/match_test.go index 7704e62..e41650d 100644 --- a/capability/policy/match_test.go +++ b/capability/policy/match_test.go @@ -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) }) diff --git a/capability/policy/policy.go b/capability/policy/policy.go index 1825407..33e1a02 100644 --- a/capability/policy/policy.go +++ b/capability/policy/policy.go @@ -23,7 +23,7 @@ const ( KindAny = "any" ) -type Policy = []Statement +type Policy []Statement type Statement interface { Kind() string @@ -126,41 +126,31 @@ func Not(stmt Statement) NegationStatement { 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 (c connective) Value() []Statement { + return c.statements } 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 + return connective{KindAnd, stmts} } func Or(stmts ...Statement) DisjunctionStatement { - return disjunction{stmts} + 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 { @@ -175,14 +165,18 @@ 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) (WildcardStatement, 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 { @@ -194,7 +188,7 @@ func (n quantifier) Selector() selector.Selector { } func (n quantifier) Value() Policy { - return n.policy + return n.statements } func All(selector selector.Selector, policy ...Statement) QuantifierStatement { From 20efcebab08e2e1a3f968a06b0a738010a99063a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Mon, 2 Sep 2024 02:24:13 +0200 Subject: [PATCH 2/5] policy: remove superfluous interfaces Those interfaces were only used inside the package, where private concrete work just as well. --- capability/policy/match.go | 62 +++++++++---------- capability/policy/policy.go | 119 +++++++----------------------------- 2 files changed, 53 insertions(+), 128 deletions(-) diff --git a/capability/policy/match.go b/capability/policy/match.go index 46f525e..3f9b1a9 100644 --- a/capability/policy/match.go +++ b/capability/policy/match.go @@ -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 } diff --git a/capability/policy/policy.go b/capability/policy/policy.go index 33e1a02..7f435c9 100644 --- a/capability/policy/policy.go +++ b/capability/policy/policy.go @@ -10,17 +10,17 @@ 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 @@ -29,49 +29,6 @@ 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,11 +67,7 @@ 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} } @@ -135,15 +80,11 @@ func (c connective) Kind() string { return c.kind } -func (c connective) Value() []Statement { - return c.statements -} - -func And(stmts ...Statement) ConjunctionStatement { +func And(stmts ...Statement) Statement { return connective{KindAnd, stmts} } -func Or(stmts ...Statement) DisjunctionStatement { +func Or(stmts ...Statement) Statement { return connective{KindOr, stmts} } @@ -157,15 +98,7 @@ 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, pattern string) (WildcardStatement, error) { +func Like(selector selector.Selector, pattern string) (Statement, error) { g, err := glob.Compile(pattern) if err != nil { return nil, err @@ -183,18 +116,10 @@ func (n quantifier) Kind() string { return n.kind } -func (n quantifier) Selector() selector.Selector { - return n.selector -} - -func (n quantifier) Value() Policy { - return n.statements -} - -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} } From ab2f074b22e032a2f4448b49d4da10f44471e019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Mon, 2 Sep 2024 02:25:34 +0200 Subject: [PATCH 3/5] policy: working IPLD round-trip --- capability/policy/ipld.go | 12 +++++++++++- capability/policy/ipld_test.go | 31 ++++++++++++++----------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/capability/policy/ipld.go b/capability/policy/ipld.go index d11620c..9ee02c1 100644 --- a/capability/policy/ipld.go +++ b/capability/policy/ipld.go @@ -3,6 +3,8 @@ 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" @@ -10,10 +12,18 @@ import ( "github.com/ucan-wg/go-ucan/v1/capability/policy/selector" ) -func PolicyFromIPLD(node datamodel.Node) (Policy, error) { +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 { diff --git a/capability/policy/ipld_test.go b/capability/policy/ipld_test.go index dfb3216..c3aa729 100644 --- a/capability/policy/ipld_test.go +++ b/capability/policy/ipld_test.go @@ -1,8 +1,6 @@ package policy import ( - "fmt" - "strings" "testing" "github.com/ipld/go-ipld-prime" @@ -11,38 +9,37 @@ import ( ) func TestIpldRoundTrip(t *testing.T) { - const illustrativeExample = `[ + const illustrativeExample = ` +[ ["==", ".status", "draft"], - ["all", ".reviewer", [["like", ".email", "*@example.com"]]], - ["any", ".tags", - ["or", [ - ["==", ".", "news"], - ["==", ".", "press"]] + ["all", ".reviewer", [ + ["like", ".email", "*@example.com"]] + ], + ["any", ".tags", [ + ["or", [ + ["==", ".", "news"], + ["==", ".", "press"]] ]] + ] ]` for _, tc := range []struct { - name, dagjson string + name, dagJsonStr string }{ {"illustrativeExample", illustrativeExample}, } { - // strip all spaces and carriage return - asDagJson := strings.Join(strings.Fields(tc.dagjson), "") - - nodes, err := ipld.Decode([]byte(asDagJson), dagjson.Decode) + nodes, err := ipld.Decode([]byte(tc.dagJsonStr), dagjson.Decode) require.NoError(t, err) - pol, err := PolicyFromIPLD(nodes) + pol, err := FromIPLD(nodes) require.NoError(t, err) - fmt.Println(pol) - wroteIpld, err := pol.ToIPLD() require.NoError(t, err) wroteAsDagJson, err := ipld.Encode(wroteIpld, dagjson.Encode) require.NoError(t, err) - require.Equal(t, asDagJson, string(wroteAsDagJson)) + require.JSONEq(t, tc.dagJsonStr, string(wroteAsDagJson)) } } From cfd16fb0d2838c21535c7bac7d07e26dc3885fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Mon, 2 Sep 2024 02:26:48 +0200 Subject: [PATCH 4/5] delegation: working IPLD round-trip --- delegation/delegation.ipldsch | 7 +--- delegation/schema.go | 9 +---- delegation/schema_test.go | 75 ++++++++++++++++++++--------------- 3 files changed, 46 insertions(+), 45 deletions(-) diff --git a/delegation/delegation.ipldsch b/delegation/delegation.ipldsch index 0e8e489..c65988a 100644 --- a/delegation/delegation.ipldsch +++ b/delegation/delegation.ipldsch @@ -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 { - -} \ No newline at end of file diff --git a/delegation/schema.go b/delegation/schema.go index f2c7c38..4615c6b 100644 --- a/delegation/schema.go +++ b/delegation/schema.go @@ -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 -} diff --git a/delegation/schema_test.go b/delegation/schema_test.go index 6abad79..bb7daed 100644 --- a/delegation/schema_test.go +++ b/delegation/schema_test.go @@ -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) { From 2038925476a680a8c4df01c5cdc8194cdf6ab266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Mon, 2 Sep 2024 02:34:00 +0200 Subject: [PATCH 5/5] delegation: complete view setup --- delegation/view.go | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/delegation/view.go b/delegation/view.go index 87f2893..0c6dcd5 100644 --- a/delegation/view.go +++ b/delegation/view.go @@ -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 -}