Compare commits
9 Commits
feat/calcu
...
v1-policy-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfd5f00618 | ||
|
|
d66b8e40ec | ||
|
|
4a655506f9 | ||
|
|
93dd3ef719 | ||
|
|
6075c19957 | ||
|
|
6161f2e440 | ||
|
|
5202056cc7 | ||
|
|
18820f5e9d | ||
|
|
ddaa67ed7d |
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/capability/command"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
)
|
||||
|
||||
func TestTop(t *testing.T) {
|
||||
@@ -2,12 +2,16 @@ package meta
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
)
|
||||
|
||||
var ErrUnsupported = errors.New("failure adding unsupported type to meta")
|
||||
|
||||
var ErrNotFound = errors.New("key-value not found in meta")
|
||||
|
||||
// Meta is a container for meta key-value pairs in a UCAN token.
|
||||
@@ -113,8 +117,20 @@ func (m *Meta) Add(key string, val any) error {
|
||||
case datamodel.Node:
|
||||
m.Values[key] = val
|
||||
default:
|
||||
panic("invalid value type")
|
||||
return fmt.Errorf("%w: %s", ErrUnsupported, fqtn(val))
|
||||
}
|
||||
m.Keys = append(m.Keys, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func fqtn(val any) string {
|
||||
var name string
|
||||
|
||||
t := reflect.TypeOf(val)
|
||||
for t.Kind() == reflect.Pointer {
|
||||
name += "*"
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
return name + t.PkgPath() + "." + t.Name()
|
||||
}
|
||||
|
||||
23
pkg/meta/meta_test.go
Normal file
23
pkg/meta/meta_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package meta_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestMeta_Add(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type Unsupported struct{}
|
||||
|
||||
t.Run("error if not primative or Node", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := (&meta.Meta{}).Add("invalid", &Unsupported{})
|
||||
require.ErrorIs(t, err, meta.ErrUnsupported)
|
||||
assert.ErrorContains(t, err, "*github.com/ucan-wg/go-ucan/pkg/meta_test.Unsupported")
|
||||
})
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/ipld/go-ipld-prime/must"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/capability/policy/selector"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||
)
|
||||
|
||||
func FromIPLD(node datamodel.Node) (Policy, error) {
|
||||
@@ -8,17 +8,35 @@ import (
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/must"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/capability/policy/selector"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||
)
|
||||
|
||||
func (p Policy) Filter(sel selector.Selector) Policy {
|
||||
return p.FilterWithMatcher(sel, selector.SegmentEquals)
|
||||
}
|
||||
|
||||
// FilterWithMatcher extracts a subset of the policy related to the specified selector,
|
||||
// by matching each segment using the given selector.SegmentMatcher.
|
||||
func (p Policy) FilterWithMatcher(sel selector.Selector, matcher selector.SegmentMatcher) Policy {
|
||||
var filtered Policy
|
||||
for _, stmt := range p {
|
||||
if stmt.Selector().Matches(sel, matcher) {
|
||||
filtered = append(filtered, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
// Match determines if the IPLD node matches the policy document.
|
||||
func Match(policy Policy, node ipld.Node) bool {
|
||||
for _, stmt := range policy {
|
||||
func (p Policy) Match(node datamodel.Node) bool {
|
||||
for _, stmt := range p {
|
||||
ok := matchStatement(stmt, node)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
@@ -9,10 +11,11 @@ import (
|
||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/capability/policy/literal"
|
||||
"github.com/ucan-wg/go-ucan/capability/policy/selector"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||
)
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
@@ -24,15 +27,15 @@ func TestMatch(t *testing.T) {
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Equal(selector.MustParse("."), literal.String("test"))}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.String("test2"))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.Int(138))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
@@ -43,15 +46,15 @@ func TestMatch(t *testing.T) {
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Equal(selector.MustParse("."), literal.Int(138))}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.Int(1138))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.String("138"))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
@@ -62,15 +65,15 @@ func TestMatch(t *testing.T) {
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Equal(selector.MustParse("."), literal.Float(1.138))}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.Float(11.38))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.String("138"))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
@@ -84,15 +87,15 @@ func TestMatch(t *testing.T) {
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Equal(selector.MustParse("."), literal.Link(l0))}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.Link(l1))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.String("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq"))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
@@ -106,19 +109,19 @@ func TestMatch(t *testing.T) {
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Equal(selector.MustParse(".foo"), literal.String("bar"))}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse(".[\"foo\"]"), literal.String("bar"))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse(".foo"), literal.String("baz"))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse(".foobar"), literal.String("bar"))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
@@ -131,11 +134,11 @@ func TestMatch(t *testing.T) {
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Equal(selector.MustParse(".[0]"), literal.String("foo"))}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse(".[1]"), literal.String("foo"))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
})
|
||||
@@ -148,7 +151,7 @@ func TestMatch(t *testing.T) {
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{GreaterThan(selector.MustParse("."), literal.Int(1))}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
@@ -159,11 +162,11 @@ func TestMatch(t *testing.T) {
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Int(1))}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Int(138))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
@@ -174,7 +177,7 @@ func TestMatch(t *testing.T) {
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{GreaterThan(selector.MustParse("."), literal.Float(1))}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
@@ -185,11 +188,11 @@ func TestMatch(t *testing.T) {
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Float(1))}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Float(1.38))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
@@ -200,7 +203,7 @@ func TestMatch(t *testing.T) {
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{LessThan(selector.MustParse("."), literal.Int(1138))}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
@@ -211,11 +214,11 @@ func TestMatch(t *testing.T) {
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{LessThanOrEqual(selector.MustParse("."), literal.Int(1138))}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{LessThanOrEqual(selector.MustParse("."), literal.Int(138))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
})
|
||||
@@ -227,11 +230,11 @@ func TestMatch(t *testing.T) {
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Not(Equal(selector.MustParse("."), literal.Bool(true)))}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Not(Equal(selector.MustParse("."), literal.Bool(false)))}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
@@ -247,7 +250,7 @@ func TestMatch(t *testing.T) {
|
||||
LessThan(selector.MustParse("."), literal.Int(1138)),
|
||||
),
|
||||
}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{
|
||||
@@ -256,11 +259,11 @@ func TestMatch(t *testing.T) {
|
||||
Equal(selector.MustParse("."), literal.Int(1138)),
|
||||
),
|
||||
}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
|
||||
pol = Policy{And()}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
@@ -276,7 +279,7 @@ func TestMatch(t *testing.T) {
|
||||
LessThan(selector.MustParse("."), literal.Int(1138)),
|
||||
),
|
||||
}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{
|
||||
@@ -285,11 +288,11 @@ func TestMatch(t *testing.T) {
|
||||
Equal(selector.MustParse("."), literal.Int(1138)),
|
||||
),
|
||||
}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
|
||||
pol = Policy{Or()}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
@@ -313,7 +316,7 @@ func TestMatch(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
pol := Policy{statement}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
}(s)
|
||||
@@ -337,7 +340,7 @@ func TestMatch(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
pol := Policy{statement}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
}(s)
|
||||
@@ -373,7 +376,7 @@ func TestMatch(t *testing.T) {
|
||||
GreaterThan(selector.MustParse(".value"), literal.Int(2)),
|
||||
),
|
||||
}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{
|
||||
@@ -382,7 +385,7 @@ func TestMatch(t *testing.T) {
|
||||
GreaterThan(selector.MustParse(".value"), literal.Int(20)),
|
||||
),
|
||||
}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
@@ -404,7 +407,7 @@ func TestMatch(t *testing.T) {
|
||||
GreaterThan(selector.MustParse(".value"), literal.Int(60)),
|
||||
),
|
||||
}
|
||||
ok := Match(pol, nd)
|
||||
ok := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{
|
||||
@@ -413,7 +416,7 @@ func TestMatch(t *testing.T) {
|
||||
GreaterThan(selector.MustParse(".value"), literal.Int(100)),
|
||||
),
|
||||
}
|
||||
ok = Match(pol, nd)
|
||||
ok = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
})
|
||||
@@ -432,7 +435,7 @@ func TestPolicyExamples(t *testing.T) {
|
||||
|
||||
pol, err := FromDagJson(policy)
|
||||
require.NoError(t, err)
|
||||
return Match(pol, data)
|
||||
return pol.Match(data)
|
||||
}
|
||||
|
||||
t.Run("And", func(t *testing.T) {
|
||||
@@ -536,6 +539,104 @@ func FuzzMatch(f *testing.F) {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
Match(policy, dataNode)
|
||||
policy.Match(dataNode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPolicyFilter(t *testing.T) {
|
||||
sel1 := selector.Selector{selector.NewFieldSegment("http")}
|
||||
sel2 := selector.Selector{selector.NewFieldSegment("jsonrpc")}
|
||||
|
||||
stmt1 := Equal(sel1, basicnode.NewString("value1"))
|
||||
stmt2 := Equal(sel2, basicnode.NewString("value2"))
|
||||
|
||||
p := Policy{stmt1, stmt2}
|
||||
|
||||
filtered := p.Filter(sel1)
|
||||
assert.Len(t, filtered, 1)
|
||||
assert.Equal(t, stmt1, filtered[0])
|
||||
|
||||
filtered = p.Filter(sel2)
|
||||
assert.Len(t, filtered, 1)
|
||||
assert.Equal(t, stmt2, filtered[0])
|
||||
|
||||
sel3 := selector.Selector{selector.NewFieldSegment("nonexistent")}
|
||||
filtered = p.Filter(sel3)
|
||||
assert.Len(t, filtered, 0)
|
||||
|
||||
stmt1 = Equal(
|
||||
selector.Selector{selector.NewFieldSegment(".http.host")},
|
||||
basicnode.NewString("mainnet.infura.io"),
|
||||
)
|
||||
stmt2, err := Like(
|
||||
selector.Selector{selector.NewFieldSegment(".jsonrpc.method")},
|
||||
"eth_*",
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
p = Policy{stmt1, stmt2}
|
||||
filtered = p.FilterWithMatcher(selector.Selector{selector.NewFieldSegment(".http")}, selector.SegmentStartsWith)
|
||||
assert.Len(t, filtered, 1)
|
||||
assert.Equal(t, stmt1, filtered[0])
|
||||
}
|
||||
|
||||
func FuzzPolicyFilter(f *testing.F) {
|
||||
f.Add([]byte(`{"selector": [{"field": "http"}], "value": "value1"}`))
|
||||
f.Add([]byte(`{"selector": [{"field": "jsonrpc"}], "value": "value2"}`))
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
var input struct {
|
||||
Selector []struct {
|
||||
Field string `json:"field"` // because selector.segment is not public
|
||||
} `json:"selector"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &input); err != nil {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
var sel selector.Selector
|
||||
for _, seg := range input.Selector {
|
||||
sel = append(sel, selector.NewFieldSegment(seg.Field))
|
||||
}
|
||||
stmt := Equal(sel, basicnode.NewString(input.Value))
|
||||
|
||||
// create a policy and filter it based on the fuzzy input selector
|
||||
p := Policy{stmt}
|
||||
filtered := p.Filter(sel)
|
||||
|
||||
// verify that the filtered policy contains the statement
|
||||
if len(filtered) != 1 || !reflect.DeepEqual(filtered[0], stmt) {
|
||||
t.Errorf("filtered policy does not contain the expected statement")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkPolicyFilter(b *testing.B) {
|
||||
sel1 := selector.Selector{selector.NewFieldSegment("http")}
|
||||
sel2 := selector.Selector{selector.NewFieldSegment("jsonrpc")}
|
||||
|
||||
stmt1 := Equal(sel1, basicnode.NewString("value1"))
|
||||
stmt2 := Equal(sel2, basicnode.NewString("value2"))
|
||||
|
||||
p := Policy{stmt1, stmt2}
|
||||
|
||||
b.Run("Filter by sel1", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
p.Filter(sel1)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Filter by sel2", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
p.Filter(sel2)
|
||||
}
|
||||
})
|
||||
|
||||
sel3 := selector.Selector{selector.NewFieldSegment("nonexistent")}
|
||||
b.Run("Filter by sel3", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
p.Filter(sel3)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -5,7 +5,7 @@ package policy
|
||||
import (
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/capability/policy/selector"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -26,6 +26,7 @@ type Policy []Statement
|
||||
|
||||
type Statement interface {
|
||||
Kind() string
|
||||
Selector() selector.Selector
|
||||
}
|
||||
|
||||
type equality struct {
|
||||
@@ -38,6 +39,10 @@ func (e equality) Kind() string {
|
||||
return e.kind
|
||||
}
|
||||
|
||||
func (e equality) Selector() selector.Selector {
|
||||
return e.selector
|
||||
}
|
||||
|
||||
func Equal(selector selector.Selector, value ipld.Node) Statement {
|
||||
return equality{kind: KindEqual, selector: selector, value: value}
|
||||
}
|
||||
@@ -66,6 +71,10 @@ func (n negation) Kind() string {
|
||||
return KindNot
|
||||
}
|
||||
|
||||
func (n negation) Selector() selector.Selector {
|
||||
return n.statement.Selector()
|
||||
}
|
||||
|
||||
func Not(stmt Statement) Statement {
|
||||
return negation{statement: stmt}
|
||||
}
|
||||
@@ -79,6 +88,15 @@ func (c connective) Kind() string {
|
||||
return c.kind
|
||||
}
|
||||
|
||||
func (c connective) Selector() selector.Selector {
|
||||
// assuming the first statement's selector is representative
|
||||
if len(c.statements) > 0 {
|
||||
return c.statements[0].Selector()
|
||||
}
|
||||
|
||||
return selector.Selector{}
|
||||
}
|
||||
|
||||
func And(stmts ...Statement) Statement {
|
||||
return connective{kind: KindAnd, statements: stmts}
|
||||
}
|
||||
@@ -96,6 +114,10 @@ func (n wildcard) Kind() string {
|
||||
return KindLike
|
||||
}
|
||||
|
||||
func (n wildcard) Selector() selector.Selector {
|
||||
return n.selector
|
||||
}
|
||||
|
||||
func Like(selector selector.Selector, pattern string) (Statement, error) {
|
||||
g, err := parseGlob(pattern)
|
||||
if err != nil {
|
||||
@@ -115,6 +137,10 @@ func (n quantifier) Kind() string {
|
||||
return n.kind
|
||||
}
|
||||
|
||||
func (n quantifier) Selector() selector.Selector {
|
||||
return n.selector
|
||||
}
|
||||
|
||||
func All(selector selector.Selector, statement Statement) Statement {
|
||||
return quantifier{kind: KindAll, selector: selector, statement: statement}
|
||||
}
|
||||
@@ -23,6 +23,21 @@ func (s Selector) String() string {
|
||||
return res.String()
|
||||
}
|
||||
|
||||
// Matches checks if the selector matches another selector.
|
||||
func (s Selector) Matches(other Selector, matcher SegmentMatcher) bool {
|
||||
if len(s) != len(other) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, seg := range s {
|
||||
if !matcher(seg, other[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
var Identity = segment{".", true, false, false, nil, "", 0}
|
||||
|
||||
var (
|
||||
@@ -41,6 +56,48 @@ type segment struct {
|
||||
index int
|
||||
}
|
||||
|
||||
// NewFieldSegment creates a new segment for a field.
|
||||
func NewFieldSegment(field string) segment {
|
||||
return segment{
|
||||
str: fmt.Sprintf(".%s", field),
|
||||
field: field,
|
||||
}
|
||||
}
|
||||
|
||||
// NewIndexSegment creates a new segment for an index.
|
||||
func NewIndexSegment(index int) segment {
|
||||
return segment{
|
||||
str: fmt.Sprintf("[%d]", index),
|
||||
index: index,
|
||||
}
|
||||
}
|
||||
|
||||
// NewSliceSegment creates a new segment for a slice.
|
||||
func NewSliceSegment(slice []int) segment {
|
||||
return segment{
|
||||
str: fmt.Sprintf("[%d:%d]", slice[0], slice[1]),
|
||||
slice: slice,
|
||||
}
|
||||
}
|
||||
|
||||
// NewIteratorSegment creates a new segment for an iterator.
|
||||
func NewIteratorSegment() segment {
|
||||
return segment{
|
||||
str: "*",
|
||||
iterator: true,
|
||||
}
|
||||
}
|
||||
|
||||
type SegmentMatcher func(s1, s2 segment) bool
|
||||
|
||||
var SegmentEquals SegmentMatcher = func(s1, s2 segment) bool {
|
||||
return s1.str == s2.str
|
||||
}
|
||||
|
||||
var SegmentStartsWith SegmentMatcher = func(s1, s2 segment) bool {
|
||||
return strings.HasPrefix(s1.str, s2.str)
|
||||
}
|
||||
|
||||
// String returns the segment's string representation.
|
||||
func (s segment) String() string {
|
||||
return s.str
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/capability/policy/selector"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||
)
|
||||
|
||||
// TestSupported Forms runs tests against the Selector according to the
|
||||
@@ -18,10 +18,10 @@ import (
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/capability/command"
|
||||
"github.com/ucan-wg/go-ucan/capability/policy"
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
)
|
||||
|
||||
// Token is an immutable type that holds the fields of a UCAN delegation.
|
||||
@@ -172,73 +172,6 @@ func (t *Token) validate() error {
|
||||
return errs
|
||||
}
|
||||
|
||||
// Option is a type that allows optional fields to be set during the
|
||||
// creation of a Token.
|
||||
type Option func(*Token) error
|
||||
|
||||
// WithExpiration set's the Token's optional "expiration" field to the
|
||||
// value of the provided time.Time.
|
||||
func WithExpiration(exp time.Time) Option {
|
||||
return func(t *Token) error {
|
||||
if exp.Before(time.Now()) {
|
||||
return fmt.Errorf("a Token's expiration should be set to a time in the future: %s", exp.String())
|
||||
}
|
||||
|
||||
t.expiration = &exp
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithMeta adds a key/value pair in the "meta" field.
|
||||
//
|
||||
// WithMeta can be used multiple times in the same call.
|
||||
// Accepted types for the value are: bool, string, int, int32, int64, []byte,
|
||||
// and ipld.Node.
|
||||
func WithMeta(key string, val any) Option {
|
||||
return func(t *Token) error {
|
||||
return t.meta.Add(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
// WithNotBefore set's the Token's optional "notBefore" field to the value
|
||||
// of the provided time.Time.
|
||||
func WithNotBefore(nbf time.Time) Option {
|
||||
return func(t *Token) error {
|
||||
if nbf.Before(time.Now()) {
|
||||
return fmt.Errorf("a Token's \"not before\" field should be set to a time in the future: %s", nbf.String())
|
||||
}
|
||||
|
||||
t.notBefore = &nbf
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSubject sets the Tokens's optional "subject" field to the value of
|
||||
// provided did.DID.
|
||||
//
|
||||
// This Option should only be used with the New constructor - since
|
||||
// Subject is a required parameter when creating a Token via the Root
|
||||
// constructor, any value provided via this Option will be silently
|
||||
// overwritten.
|
||||
func WithSubject(sub did.DID) Option {
|
||||
return func(t *Token) error {
|
||||
t.subject = sub
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithNonce sets the Token's nonce with the given value.
|
||||
// If this option is not used, a random 12-byte nonce is generated for this required field.
|
||||
func WithNonce(nonce []byte) Option {
|
||||
return func(t *Token) error {
|
||||
t.nonce = nonce
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tokenFromModel build a decoded view of the raw IPLD data.
|
||||
// This function also serves as validation.
|
||||
func tokenFromModel(m tokenPayloadModel) (*Token, error) {
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/v3/golden"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/capability/command"
|
||||
"github.com/ucan-wg/go-ucan/capability/policy"
|
||||
"github.com/ucan-wg/go-ucan/delegation"
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
"github.com/ucan-wg/go-ucan/tokens/delegation"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
"github.com/ucan-wg/go-ucan/internal/envelope"
|
||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||
)
|
||||
|
||||
// ToSealed wraps the delegation token in an envelope, generates the
|
||||
72
tokens/delegation/options.go
Normal file
72
tokens/delegation/options.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package delegation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
)
|
||||
|
||||
// Option is a type that allows optional fields to be set during the
|
||||
// creation of a Token.
|
||||
type Option func(*Token) error
|
||||
|
||||
// WithExpiration set's the Token's optional "expiration" field to the
|
||||
// value of the provided time.Time.
|
||||
func WithExpiration(exp time.Time) Option {
|
||||
return func(t *Token) error {
|
||||
if exp.Before(time.Now()) {
|
||||
return fmt.Errorf("a Token's expiration should be set to a time in the future: %s", exp.String())
|
||||
}
|
||||
|
||||
t.expiration = &exp
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithMeta adds a key/value pair in the "meta" field.
|
||||
//
|
||||
// WithMeta can be used multiple times in the same call.
|
||||
// Accepted types for the value are: bool, string, int, int32, int64, []byte,
|
||||
// and ipld.Node.
|
||||
func WithMeta(key string, val any) Option {
|
||||
return func(t *Token) error {
|
||||
return t.meta.Add(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
// WithNotBefore set's the Token's optional "notBefore" field to the value
|
||||
// of the provided time.Time.
|
||||
func WithNotBefore(nbf time.Time) Option {
|
||||
return func(t *Token) error {
|
||||
if nbf.Before(time.Now()) {
|
||||
return fmt.Errorf("a Token's \"not before\" field should be set to a time in the future: %s", nbf.String())
|
||||
}
|
||||
|
||||
t.notBefore = &nbf
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSubject sets the Tokens's optional "subject" field to the value of
|
||||
// provided did.DID.
|
||||
//
|
||||
// This Option should only be used with the New constructor - since
|
||||
// Subject is a required parameter when creating a Token via the Root
|
||||
// constructor, any value provided via this Option will be silently
|
||||
// overwritten.
|
||||
func WithSubject(sub did.DID) Option {
|
||||
return func(t *Token) error {
|
||||
t.subject = sub
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithNonce sets the Token's nonce with the given value.
|
||||
// If this option is not used, a random 12-byte nonce is generated for this required field.
|
||||
func WithNonce(nonce []byte) Option {
|
||||
return func(t *Token) error {
|
||||
t.nonce = nonce
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"github.com/ipld/go-ipld-prime/node/bindnode"
|
||||
"github.com/ipld/go-ipld-prime/schema"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/internal/envelope"
|
||||
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||
)
|
||||
|
||||
// [Tag] is the string used as a key within the SigPayload that identifies
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/v3/golden"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/delegation"
|
||||
"github.com/ucan-wg/go-ucan/internal/envelope"
|
||||
"github.com/ucan-wg/go-ucan/tokens/delegation"
|
||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||
)
|
||||
|
||||
//go:embed delegation.ipldsch
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/v3/golden"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/internal/envelope"
|
||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||
)
|
||||
|
||||
func TestCidFromBytes(t *testing.T) {
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/ipld/go-ipld-prime/schema"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/internal/envelope"
|
||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ import (
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
"github.com/ucan-wg/go-ucan/internal/varsig"
|
||||
"github.com/ucan-wg/go-ucan/tokens/internal/varsig"
|
||||
)
|
||||
|
||||
const varsigHeaderKey = "h"
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/internal/envelope"
|
||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/libp2p/go-libp2p/core/crypto/pb"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/internal/varsig"
|
||||
"github.com/ucan-wg/go-ucan/tokens/internal/varsig"
|
||||
)
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
Reference in New Issue
Block a user