test(invocation): verify arguments versus aggregated policies
This commit is contained in:
@@ -7,11 +7,8 @@ import (
|
||||
"github.com/ipfs/go-cid"
|
||||
"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/fluent/qp"
|
||||
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/pkg/policy/literal"
|
||||
@@ -904,55 +901,3 @@ func TestPartialMatch(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestInvocationValidation applies the example policy to the second
|
||||
// example arguments as defined in the [Validation] section of the
|
||||
// invocation specification.
|
||||
//
|
||||
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
|
||||
func TestInvocationValidationSpecExamples(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pol := MustConstruct(
|
||||
Equal(".from", literal.String("alice@example.com")),
|
||||
Any(".to", Like(".", "*@example.com")),
|
||||
)
|
||||
|
||||
t.Run("with passing args", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
argsNode, err := qp.BuildMap(basicnode.Prototype.Any, 2, func(ma datamodel.MapAssembler) {
|
||||
qp.MapEntry(ma, "from", qp.String("alice@example.com"))
|
||||
qp.MapEntry(ma, "to", qp.List(2, func(la datamodel.ListAssembler) {
|
||||
qp.ListEntry(la, qp.String("bob@example.com"))
|
||||
qp.ListEntry(la, qp.String("carol@not.example.com"))
|
||||
}))
|
||||
qp.MapEntry(ma, "title", qp.String("Coffee"))
|
||||
qp.MapEntry(ma, "body", qp.String("Still on for coffee"))
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
exec, stmt := pol.Match(argsNode)
|
||||
assert.True(t, exec)
|
||||
assert.Nil(t, stmt)
|
||||
})
|
||||
|
||||
t.Run("fails on recipients (second statement)", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
argsNode, err := qp.BuildMap(basicnode.Prototype.Any, 2, func(ma datamodel.MapAssembler) {
|
||||
qp.MapEntry(ma, "from", qp.String("alice@example.com"))
|
||||
qp.MapEntry(ma, "to", qp.List(2, func(la datamodel.ListAssembler) {
|
||||
qp.ListEntry(la, qp.String("bob@null.com"))
|
||||
qp.ListEntry(la, qp.String("carol@elsewhere.example.com"))
|
||||
}))
|
||||
qp.MapEntry(ma, "title", qp.String("Coffee"))
|
||||
qp.MapEntry(ma, "body", qp.String("Still on for coffee"))
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
exec, stmt := pol.Match(argsNode)
|
||||
assert.False(t, exec)
|
||||
assert.NotNil(t, stmt)
|
||||
})
|
||||
}
|
||||
|
||||
98
pkg/policy/policytest/example.go
Normal file
98
pkg/policy/policytest/example.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package policytest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ucan-wg/go-ucan/pkg/args"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||
)
|
||||
|
||||
// EmptyPolicy provides a Policy with no statements.
|
||||
var EmptyPolicy = policy.Policy{}
|
||||
|
||||
// ExampleValidationPolicy provides a instantiated Policy containing the
|
||||
// statements that are included in the second code block of the [Validation]
|
||||
// section of the delegation specification.
|
||||
//
|
||||
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
|
||||
var ExamplePolicy = policy.MustConstruct(
|
||||
policy.Equal(".from", literal.String("alice@example.com")),
|
||||
policy.Any(".to", policy.Like(".", "*@example.com")),
|
||||
)
|
||||
|
||||
// TODO: Replace the URL for [Validation] above when the delegation
|
||||
// specification has been finished/merged.
|
||||
|
||||
// ExampleValidArguments provides valid, instantiated Arguments containing
|
||||
// the key/value pairs that are included in portion of the the second code
|
||||
// block of the [Validation] section of the delegation specification.
|
||||
//
|
||||
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
|
||||
var ExampleValidArguments = newBuilder(nil).
|
||||
add("from", "alice@example.com").
|
||||
add("to", []string{
|
||||
"bob@example.com",
|
||||
"carol@not.example.com",
|
||||
}).
|
||||
add("title", "Coffee").
|
||||
add("body", "Still on for coffee").
|
||||
mustBuild()
|
||||
|
||||
var exampleValidArgumentsIPLD = mustIPLD(ExampleValidArguments)
|
||||
|
||||
// ExampleInvalidArguments provides invalid, instantiated Arguments containing
|
||||
// the key/value pairs that are included in portion of the the second code
|
||||
// block of the [Validation] section of the delegation specification.
|
||||
//
|
||||
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
|
||||
var ExampleInvalidArguments = newBuilder(nil).
|
||||
add("from", "alice@example.com").
|
||||
add("to", []string{
|
||||
"bob@null.com",
|
||||
"carol@elsewhere.example.com",
|
||||
}).
|
||||
add("title", "Coffee").
|
||||
add("body", "Still on for coffee").
|
||||
mustBuild()
|
||||
|
||||
var exampleInvalidArgumentsIPLD = mustIPLD(ExampleInvalidArguments)
|
||||
|
||||
type builder struct {
|
||||
args *args.Args
|
||||
errs error
|
||||
}
|
||||
|
||||
func newBuilder(a *args.Args) *builder {
|
||||
if a == nil {
|
||||
a = args.New()
|
||||
}
|
||||
|
||||
return &builder{
|
||||
args: a,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) add(key string, val any) *builder {
|
||||
b.errs = errors.Join(b.errs, b.args.Add(key, val))
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *builder) mustBuild() *args.Args {
|
||||
if b.errs != nil {
|
||||
panic(b.errs)
|
||||
}
|
||||
|
||||
return b.args
|
||||
}
|
||||
|
||||
func mustIPLD(args *args.Args) ipld.Node {
|
||||
node, err := args.ToIPLD()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
32
pkg/policy/policytest/example_test.go
Normal file
32
pkg/policy/policytest/example_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package policytest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestInvocationValidation applies the example policy to the second
|
||||
// example arguments as defined in the [Validation] section of the
|
||||
// invocation specification.
|
||||
//
|
||||
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
|
||||
func TestInvocationValidationSpecExamples(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("with passing args", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
exec, stmt := ExamplePolicy.Match(exampleValidArgumentsIPLD)
|
||||
assert.True(t, exec)
|
||||
assert.Nil(t, stmt)
|
||||
})
|
||||
|
||||
t.Run("fails on recipients (second statement)", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
exec, stmt := ExamplePolicy.Match(exampleInvalidArgumentsIPLD)
|
||||
assert.False(t, exec)
|
||||
assert.NotNil(t, stmt)
|
||||
})
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/ucan-wg/go-ucan/did/didtest"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/policytest"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation/delegationtest"
|
||||
)
|
||||
@@ -86,7 +87,7 @@ func (g *generator) chainPersonas(personas []didtest.Persona, acc acc, vari vari
|
||||
privKey: personas[0].PrivKey(),
|
||||
aud: personas[1].DID(),
|
||||
cmd: delegationtest.NominalCommand,
|
||||
pol: policy.Policy{},
|
||||
pol: policytest.EmptyPolicy,
|
||||
opts: []delegation.Option{
|
||||
delegation.WithSubject(didtest.PersonaAlice.DID()),
|
||||
delegation.WithNonce(constantNonce),
|
||||
@@ -128,6 +129,9 @@ func (g *generator) chainPersonas(personas []didtest.Persona, acc acc, vari vari
|
||||
}
|
||||
p.opts = append(p.opts, delegation.WithNotBefore(nbf))
|
||||
}},
|
||||
{name: "ValidExamplePolicy", variant: func(p *newDelegationParams) {
|
||||
p.pol = policytest.ExamplePolicy
|
||||
}},
|
||||
}
|
||||
|
||||
// Start a branch in the recursion for each of the variants
|
||||
|
||||
@@ -75,17 +75,17 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
TokenCarolDan_InvalidExpiredCID = gocid.MustParse("bafyreigenypixaxvhzlry5rjnywvjyl4xvzlzxz2ui74uzys7qdhos4bbu")
|
||||
TokenCarolDan_InvalidExpiredCID = gocid.MustParse("bafyreifrbm6bgyqdzhhcubbb7dnhq3aq6udvdbfs7mhqjs3d2ihraelufu")
|
||||
TokenCarolDan_InvalidExpired = mustGetDelegation(TokenCarolDan_InvalidExpiredCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenDanErin_InvalidExpiredCID = gocid.MustParse("bafyreifvnfb7zqocpdysedcvjkb4y7tqfuziuqjhbbdoay4zg33pwpbzqi")
|
||||
TokenDanErin_InvalidExpiredCID = gocid.MustParse("bafyreibbh5ujs6udphkl3exffohxsg5mdknoqzjb3gdhmuncg3qnomzemy")
|
||||
TokenDanErin_InvalidExpired = mustGetDelegation(TokenDanErin_InvalidExpiredCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenErinFrank_InvalidExpiredCID = gocid.MustParse("bafyreicvydzt3obkqx7krmoi3zu4tlirlksibxfks5jc7vlvjxjamv2764")
|
||||
TokenErinFrank_InvalidExpiredCID = gocid.MustParse("bafyreiggzczmqlybhxljmlfot5t7o4w6fhdv7fme77a466ku73dhxtqzdq")
|
||||
TokenErinFrank_InvalidExpired = mustGetDelegation(TokenErinFrank_InvalidExpiredCID)
|
||||
)
|
||||
|
||||
@@ -104,6 +104,21 @@ var (
|
||||
TokenErinFrank_InvalidInactive = mustGetDelegation(TokenErinFrank_InvalidInactiveCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenCarolDan_ValidExamplePolicyCID = gocid.MustParse("bafyreibtfrp2njnkjrcuhxd4ebaecmpcql5knek2h2j2fjzu2sij2tv6ei")
|
||||
TokenCarolDan_ValidExamplePolicy = mustGetDelegation(TokenCarolDan_ValidExamplePolicyCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenDanErin_ValidExamplePolicyCID = gocid.MustParse("bafyreidxfwbkzujpu7ivulkc7b6ff4cpbzrkeklmxqvyhhmkmym5b45e2e")
|
||||
TokenDanErin_ValidExamplePolicy = mustGetDelegation(TokenDanErin_ValidExamplePolicyCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenErinFrank_ValidExamplePolicyCID = gocid.MustParse("bafyreiatkvtvgakqcrdk6vgrv7tbq5rbeiqct52ep4plcftp2agffjyvp4")
|
||||
TokenErinFrank_ValidExamplePolicy = mustGetDelegation(TokenErinFrank_ValidExamplePolicyCID)
|
||||
)
|
||||
|
||||
var ProofAliceBob = []gocid.Cid{
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
@@ -238,3 +253,24 @@ var ProofAliceBobCarolDanErinFrank_InvalidInactive = []gocid.Cid{
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDan_ValidExamplePolicy = []gocid.Cid{
|
||||
TokenCarolDan_ValidExamplePolicyCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErin_ValidExamplePolicy = []gocid.Cid{
|
||||
TokenDanErin_ValidExamplePolicyCID,
|
||||
TokenCarolDan_ValidExamplePolicyCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinFrank_ValidExamplePolicy = []gocid.Cid{
|
||||
TokenErinFrank_ValidExamplePolicyCID,
|
||||
TokenDanErin_ValidExamplePolicyCID,
|
||||
TokenCarolDan_ValidExamplePolicyCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
@@ -35,3 +35,7 @@ var (
|
||||
// next delegation or invocation's command.
|
||||
ErrCommandNotCovered = errors.New("allowed command doesn't cover the next delegation or invocation")
|
||||
)
|
||||
|
||||
// ErrPolicyNotSatisfied is returned when the provided Arguments don't
|
||||
// satisfy one or more of the aggregated Policy Statements
|
||||
var ErrPolicyNotSatisfied = errors.New("the following UCAN policy is not satisfied")
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/ucan-wg/go-ucan/did/didtest"
|
||||
"github.com/ucan-wg/go-ucan/pkg/args"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/policytest"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation/delegationtest"
|
||||
"github.com/ucan-wg/go-ucan/token/invocation"
|
||||
)
|
||||
@@ -48,6 +49,18 @@ func TestToken_ExecutionAllowed(t *testing.T) {
|
||||
testPasses(t, didtest.PersonaFrank, delegationtest.AttenuatedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank)
|
||||
})
|
||||
|
||||
t.Run("passes - arguments satisfy empty policy", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testPasses(t, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.ExampleValidArguments, delegationtest.ProofAliceBobCarolDanErinFrank)
|
||||
})
|
||||
|
||||
t.Run("passes - arguments satify example policy", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testPasses(t, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.ExampleValidArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy)
|
||||
})
|
||||
|
||||
t.Run("fails - no proof", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -115,12 +128,19 @@ func TestToken_ExecutionAllowed(t *testing.T) {
|
||||
|
||||
testFails(t, invocation.ErrWrongSub, didtest.PersonaFrank, delegationtest.ExpandedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_InvalidSubject)
|
||||
})
|
||||
|
||||
t.Run("passes - arguments satify example policy", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testFails(t, invocation.ErrPolicyNotSatisfied, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.ExampleInvalidArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func test(t *testing.T, persona didtest.Persona, cmd command.Command, args *args.Args, prf []cid.Cid, opts ...invocation.Option) error {
|
||||
t.Helper()
|
||||
|
||||
// TODO: use the args and add minimal test to check that they are verified against the policy
|
||||
opts = append(opts, invocation.WithArguments(args))
|
||||
|
||||
tkn, err := invocation.New(persona.DID(), didtest.PersonaAlice.DID(), cmd, prf, opts...)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -134,7 +134,7 @@ func (t *Token) verifyArgs(delegations []*delegation.Token, arguments *args.Args
|
||||
|
||||
ok, statement := policies.Match(argsIpld)
|
||||
if !ok {
|
||||
return fmt.Errorf("the following UCAN policy is not satisfied: %v", statement.String())
|
||||
return fmt.Errorf("%w: %v", ErrPolicyNotSatisfied, statement.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user