test(invocation): add command.Covers and subject consistency tests
Also improve the maintainability of the tests by a) providing a set of fixed Personas and then generating a slew of valid delegation tokens, invalid delegation tokens and proof-chains thereof.
This commit is contained in:
149
did/didtest/crypto.go
Normal file
149
did/didtest/crypto.go
Normal file
@@ -0,0 +1,149 @@
|
||||
// Package didtest provides Personas that can be used for testing. Each
|
||||
// Persona has a name, crypto.PrivKey and associated crypto.PubKey and
|
||||
// did.DID.
|
||||
package didtest
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
)
|
||||
|
||||
const (
|
||||
alicePrivKeyCfg = "CAESQHdNJLBBiuc1AdwPHBkubB2KS1p0cv2JEF7m8tfwtrcm5ajaYPm+XmVCmtcHOF2lGDlmaiDA7emfwD3IrcyES0M="
|
||||
bobPrivKeyCfg = "CAESQHBz+AIop1g+9iBDj+ufUc/zm9/ry7c6kDFO8Wl/D0+H63V9hC6s9l4npf3pYEFCjBtlR0AMNWMoFQKSlYNKo20="
|
||||
carolPrivKeyCfg = "CAESQPrCgkcHnYFXDT9AlAydhPECBEivEuuVx9dJxLjVvDTmJIVNivfzg6H4mAiPfYS+5ryVVUZTHZBzvMuvvvG/Ks0="
|
||||
danPrivKeyCfg = "CAESQCgNhzofKhC+7hW6x+fNd7iMPtQHeEmKRhhlduf/I7/TeOEFYAEflbJ0sAhMeDJ/HQXaAvsWgHEbJ3ZLhP8q2B0="
|
||||
erinPrivKeyCfg = "CAESQKhCJo5UBpQcthko8DKMFsbdZ+qqQ5oc01CtLCqrE90dF2GfRlrMmot3WPHiHGCmEYi5ZMEHuiSI095e/6O4Bpw="
|
||||
frankPrivKeyCfg = "CAESQDlXPKsy3jHh7OWTWQqyZF95Ueac5DKo7xD0NOBE5F2BNr1ZVxRmJ2dBELbOt8KP9sOACcO9qlCB7uMA1UQc7sk="
|
||||
)
|
||||
|
||||
// Persona is a generic participant used for cryptographic testing.
|
||||
type Persona int
|
||||
|
||||
// The provided Personas were selected from the first few generic
|
||||
// participants listed in this [table].
|
||||
//
|
||||
// [table]: https://en.wikipedia.org/wiki/Alice_and_Bob#Cryptographic_systems
|
||||
const (
|
||||
PersonaAlice Persona = iota
|
||||
PersonaBob
|
||||
PersonaCarol
|
||||
PersonaDan
|
||||
PersonaErin
|
||||
PersonaFrank
|
||||
)
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
privKeys = make(map[Persona]crypto.PrivKey, 6)
|
||||
err error
|
||||
)
|
||||
|
||||
// DID returns a did.DID based on the Persona's Ed25519 public key.
|
||||
func (p Persona) DID(t *testing.T) did.DID {
|
||||
t.Helper()
|
||||
|
||||
did, err := did.FromPrivKey(p.PrivKey(t))
|
||||
require.NoError(t, err)
|
||||
|
||||
return did
|
||||
}
|
||||
|
||||
// Name returns the username of the Persona.
|
||||
func (p Persona) Name(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
name, ok := map[Persona]string{
|
||||
PersonaAlice: "Alice",
|
||||
PersonaBob: "Bob",
|
||||
PersonaCarol: "Carol",
|
||||
PersonaDan: "Dan",
|
||||
PersonaErin: "Erin",
|
||||
PersonaFrank: "Frank",
|
||||
}[p]
|
||||
if !ok {
|
||||
t.Fatal("Unknown persona:", p)
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
// PrivKey returns the Ed25519 private key for the Persona.
|
||||
func (p Persona) PrivKey(t *testing.T) crypto.PrivKey {
|
||||
t.Helper()
|
||||
|
||||
once.Do(func() {
|
||||
for persona, privKeyCfg := range privKeyCfgs(t) {
|
||||
privKeyMar, err := crypto.ConfigDecodeKey(privKeyCfg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
privKey, err := crypto.UnmarshalPrivateKey(privKeyMar)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
privKeys[persona] = privKey
|
||||
}
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return privKeys[p]
|
||||
}
|
||||
|
||||
// PrivKeyConfig returns the marshaled and encoded Ed25519 private key
|
||||
// for the Persona.
|
||||
func (p Persona) PrivKeyConfig(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
return privKeyCfgs(t)[p]
|
||||
}
|
||||
|
||||
// PubKey returns the Ed25519 public key for the Persona.
|
||||
func (p Persona) PubKey(t *testing.T) crypto.PubKey {
|
||||
t.Helper()
|
||||
|
||||
return p.PrivKey(t).GetPublic()
|
||||
}
|
||||
|
||||
// PubKeyConfig returns the marshaled and encoded Ed25519 public key
|
||||
// for the Persona.
|
||||
func (p Persona) PubKeyConfig(t *testing.T) string {
|
||||
pubKeyMar, err := crypto.MarshalPublicKey(p.PrivKey(t).GetPublic())
|
||||
require.NoError(t, err)
|
||||
|
||||
return crypto.ConfigEncodeKey(pubKeyMar)
|
||||
}
|
||||
|
||||
func privKeyCfgs(t *testing.T) map[Persona]string {
|
||||
t.Helper()
|
||||
|
||||
return map[Persona]string{
|
||||
PersonaAlice: alicePrivKeyCfg,
|
||||
PersonaBob: bobPrivKeyCfg,
|
||||
PersonaCarol: carolPrivKeyCfg,
|
||||
PersonaDan: danPrivKeyCfg,
|
||||
PersonaErin: erinPrivKeyCfg,
|
||||
PersonaFrank: frankPrivKeyCfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Personas returns an (alphabetically) ordered list of the defined
|
||||
// Persona values.
|
||||
func Personas(t *testing.T) []Persona {
|
||||
t.Helper()
|
||||
|
||||
return []Persona{
|
||||
PersonaAlice,
|
||||
PersonaBob,
|
||||
PersonaCarol,
|
||||
PersonaDan,
|
||||
PersonaErin,
|
||||
PersonaFrank,
|
||||
}
|
||||
}
|
||||
1
go.mod
1
go.mod
@@ -3,6 +3,7 @@ module github.com/ucan-wg/go-ucan
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/dave/jennifer v1.7.1
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
|
||||
github.com/ipfs/go-cid v0.4.1
|
||||
github.com/ipld/go-ipld-prime v0.21.0
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1,5 +1,7 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
|
||||
github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
||||
20
pkg/policy/policytest/policy.go
Normal file
20
pkg/policy/policytest/policy.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Package policytest provides values and functions that are useful when
|
||||
// testing code that relies on Policies.
|
||||
package policytest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
)
|
||||
|
||||
// EmptyPolicy provides a policy with no statements for testing purposes.
|
||||
func EmptyPolicy(t *testing.T) policy.Policy {
|
||||
t.Helper()
|
||||
|
||||
pol, err := policy.FromDagJson("[]")
|
||||
require.NoError(t, err)
|
||||
|
||||
return pol
|
||||
}
|
||||
5
token/delegation/delegationtest/README.md
Normal file
5
token/delegation/delegationtest/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# delegationtest
|
||||
|
||||
See the package documentation for instructions on how to use the generated
|
||||
tokens as well as information on how to regenerate the code if changes have
|
||||
been made.
|
||||
BIN
token/delegation/delegationtest/data/TokenAliceBob.dagcbor
Normal file
BIN
token/delegation/delegationtest/data/TokenAliceBob.dagcbor
Normal file
Binary file not shown.
BIN
token/delegation/delegationtest/data/TokenBobCarol.dagcbor
Normal file
BIN
token/delegation/delegationtest/data/TokenBobCarol.dagcbor
Normal file
Binary file not shown.
BIN
token/delegation/delegationtest/data/TokenCarolDan.dagcbor
Normal file
BIN
token/delegation/delegationtest/data/TokenCarolDan.dagcbor
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
token/delegation/delegationtest/data/TokenDanErin.dagcbor
Normal file
BIN
token/delegation/delegationtest/data/TokenDanErin.dagcbor
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
token/delegation/delegationtest/data/TokenErinFrank.dagcbor
Normal file
BIN
token/delegation/delegationtest/data/TokenErinFrank.dagcbor
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
33
token/delegation/delegationtest/doc.go
Normal file
33
token/delegation/delegationtest/doc.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Package delegationtest provides a set of pre-built delegation tokens
|
||||
// for a variety of test cases.
|
||||
//
|
||||
// For all delegation tokens, the name of the delegation token is the
|
||||
// Issuer appended with the Audience. The tokens are generated so that
|
||||
// an invocation can be created for any didtest.Persona.
|
||||
//
|
||||
// Delegation proof-chain names contain each didtest.Persona name in
|
||||
// order starting with the root delegation (which will always be generated
|
||||
// by Alice.) This is opposite of the list of cic.Cids that represent the
|
||||
// proof chain.
|
||||
//
|
||||
// For both the generated delegation tokens granted to Carol's Persona and
|
||||
// the proof chains containing Carol's delegations to Dan, if there is no
|
||||
// suffix, the proof chain will be deemed valid. If there is a suffix, it
|
||||
// will consist of either the word "Valid" or "Invalid" and the name of the
|
||||
// field that has been altered. Only optional fields will generate proof
|
||||
// chains with Valid suffixes.
|
||||
//
|
||||
// If changes are made to the list of Personas included in the chain, or
|
||||
// in the variants that are specified, the generated Go file and delegation
|
||||
// tokens stored in the data/ directory should be regenerated by running
|
||||
// the following command in this directory:
|
||||
//
|
||||
// go test . -update
|
||||
//
|
||||
// Generated delegation Tokens are stored in the data/ directory and loaded
|
||||
// into the DelegationLoader on the first call to GetDelegationLoader.
|
||||
// Generated references to these tokens and the tokens themselves are
|
||||
// created in the token_gen.go file. See /token/invocation/invocation_test.go
|
||||
// for an example of how these delegation tokens and proof-chains can
|
||||
// be used during testing.
|
||||
package delegationtest
|
||||
224
token/delegation/delegationtest/generator_test.go
Normal file
224
token/delegation/delegationtest/generator_test.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package delegationtest
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dave/jennifer/jen"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
"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"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
const (
|
||||
tokenNamePrefix = "Token"
|
||||
proorChainNamePrefix = "Proof"
|
||||
)
|
||||
|
||||
var constantNonce = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b}
|
||||
|
||||
// TestUpdate doesn't actually run a test but uses the Go testing library
|
||||
// to trigger generation of the delegation tokens and associated Go file.
|
||||
func TestUpdate(t *testing.T) {
|
||||
if golden.FlagUpdate() {
|
||||
update(t)
|
||||
}
|
||||
}
|
||||
|
||||
type newDelegationParams struct {
|
||||
privKey crypto.PrivKey
|
||||
aud did.DID
|
||||
sub did.DID
|
||||
cmd command.Command
|
||||
pol policy.Policy
|
||||
opts []delegation.Option
|
||||
}
|
||||
|
||||
type newDelegationParamsVariant func(*newDelegationParams)
|
||||
|
||||
type token struct {
|
||||
name string
|
||||
id cid.Cid
|
||||
}
|
||||
|
||||
type proof struct {
|
||||
name string
|
||||
prf []cid.Cid
|
||||
}
|
||||
|
||||
type generator struct {
|
||||
dlgs []token
|
||||
chains []proof
|
||||
}
|
||||
|
||||
type acc struct {
|
||||
name string
|
||||
chain []cid.Cid
|
||||
}
|
||||
|
||||
type variant struct {
|
||||
name string
|
||||
variant func(*newDelegationParams)
|
||||
}
|
||||
|
||||
func noopVariant() variant {
|
||||
return variant{
|
||||
name: "",
|
||||
variant: func(_ *newDelegationParams) {},
|
||||
}
|
||||
}
|
||||
|
||||
func update(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
gen := &generator{}
|
||||
gen.chainPersonas(t, didtest.Personas(t), acc{}, noopVariant())
|
||||
gen.writeGoFile(t)
|
||||
}
|
||||
|
||||
func (g *generator) chainPersonas(t *testing.T, personas []didtest.Persona, acc acc, vari variant) {
|
||||
t.Helper()
|
||||
|
||||
acc.name += personas[0].Name(t)
|
||||
g.createProofChain(t, acc.name+vari.name, acc.chain)
|
||||
|
||||
if len(personas) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
name := personas[0].Name(t) + personas[1].Name(t)
|
||||
|
||||
params := newDelegationParams{
|
||||
privKey: personas[0].PrivKey(t),
|
||||
aud: personas[1].DID(t),
|
||||
cmd: NominalCommand,
|
||||
pol: policytest.EmptyPolicy(t),
|
||||
opts: []delegation.Option{
|
||||
delegation.WithSubject(didtest.PersonaAlice.DID(t)),
|
||||
delegation.WithNonce(constantNonce),
|
||||
},
|
||||
}
|
||||
|
||||
// Create each nominal token and continue the chain
|
||||
id := g.createDelegation(t, params, name, vari)
|
||||
acc.chain = append(acc.chain, id)
|
||||
g.chainPersonas(t, personas[1:], acc, vari)
|
||||
|
||||
// If the user is Carol, create variants for each invalid and/or optional
|
||||
// parameter and also continue the chain
|
||||
if personas[0] == didtest.PersonaCarol {
|
||||
variants := []variant{
|
||||
{name: "InvalidExpandedCommand", variant: func(p *newDelegationParams) {
|
||||
p.cmd = ExpandedCommand
|
||||
}},
|
||||
{name: "ValidAttenuatedCommand", variant: func(p *newDelegationParams) {
|
||||
p.cmd = AttenuatedCommand
|
||||
}},
|
||||
{name: "InvalidSubject", variant: func(p *newDelegationParams) {
|
||||
p.opts = append(p.opts, delegation.WithSubject(didtest.PersonaBob.DID(t)))
|
||||
}},
|
||||
{name: "InvalidExpired", variant: func(p *newDelegationParams) {
|
||||
p.opts = append(p.opts, delegation.WithExpiration(time.Now().Add(time.Second)))
|
||||
}},
|
||||
{name: "InvalidInactive", variant: func(p *newDelegationParams) {
|
||||
nbf, err := time.Parse(time.RFC3339, "2070-01-01T00:00:00Z")
|
||||
require.NoError(t, err)
|
||||
|
||||
p.opts = append(p.opts, delegation.WithNotBefore(nbf))
|
||||
}},
|
||||
}
|
||||
|
||||
// Start a branch in the recursion for each of the variants
|
||||
for _, v := range variants {
|
||||
id := g.createDelegation(t, params, name, v)
|
||||
|
||||
// replace the previous Carol token id with the one from the variant
|
||||
acc.chain[len(acc.chain)-1] = id
|
||||
g.chainPersonas(t, personas[1:], acc, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *generator) createDelegation(t *testing.T, params newDelegationParams, name string, vari variant) cid.Cid {
|
||||
t.Helper()
|
||||
|
||||
vari.variant(¶ms)
|
||||
|
||||
tkn, err := delegation.New(params.privKey, params.aud, params.cmd, params.pol, params.opts...)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, id, err := tkn.ToSealed(params.privKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, os.WriteFile(filepath.Join(tokenDir, tokenNamePrefix+name+vari.name+tokenExt), data, 0o644))
|
||||
|
||||
g.dlgs = append(g.dlgs, token{
|
||||
name: tokenNamePrefix + name + vari.name,
|
||||
id: id,
|
||||
})
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func (g *generator) createProofChain(t *testing.T, name string, prf []cid.Cid) {
|
||||
t.Helper()
|
||||
|
||||
if len(prf) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
clone := make([]cid.Cid, len(prf))
|
||||
copy(clone, prf)
|
||||
|
||||
g.chains = append(g.chains, proof{
|
||||
name: proorChainNamePrefix + name,
|
||||
prf: clone,
|
||||
})
|
||||
}
|
||||
|
||||
func (g *generator) writeGoFile(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
file := jen.NewFile("delegationtest")
|
||||
file.HeaderComment("Code generated by delegationtest - DO NOT EDIT.")
|
||||
|
||||
refs := map[cid.Cid]string{}
|
||||
|
||||
for _, d := range g.dlgs {
|
||||
refs[d.id] = d.name + "CID"
|
||||
|
||||
file.Var().Defs(
|
||||
jen.Id(d.name+"CID").Op("=").Qual("github.com/ipfs/go-cid", "MustParse").Call(jen.Lit(d.id.String())),
|
||||
jen.Id(d.name).Op("=").Id("mustGetDelegation").Call(jen.Id(d.name+"CID")),
|
||||
)
|
||||
file.Line()
|
||||
}
|
||||
|
||||
for _, c := range g.chains {
|
||||
g := jen.CustomFunc(jen.Options{
|
||||
Multi: true,
|
||||
Separator: ",",
|
||||
Close: "\n",
|
||||
}, func(g *jen.Group) {
|
||||
slices.Reverse(c.prf)
|
||||
for _, p := range c.prf {
|
||||
g.Id(refs[p])
|
||||
}
|
||||
})
|
||||
|
||||
file.Var().Id(c.name).Op("=").Index().Qual("github.com/ipfs/go-cid", "Cid").Values(g)
|
||||
file.Line()
|
||||
}
|
||||
|
||||
require.NoError(t, file.Save("token_gen.go"))
|
||||
}
|
||||
120
token/delegation/delegationtest/token.go
Normal file
120
token/delegation/delegationtest/token.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package delegationtest
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||
"github.com/ucan-wg/go-ucan/token/invocation"
|
||||
)
|
||||
|
||||
const (
|
||||
tokenDir = "data"
|
||||
tokenExt = ".dagcbor"
|
||||
)
|
||||
|
||||
var (
|
||||
// ExpandedCommand is the parent of the NominalCommand and represents
|
||||
// the cases where the delegation proof-chain or invocation token tries
|
||||
// to increase the privileges granted by the root delegation token.
|
||||
// Execution of this command is generally prohibited in tests.
|
||||
ExpandedCommand = command.MustParse("/expanded")
|
||||
|
||||
// NominalCommand is the command used for most test tokens and proof-
|
||||
// chains. Execution of this command is generally allowed in tests.
|
||||
NominalCommand = ExpandedCommand.Join("nominal")
|
||||
|
||||
// AttenuatedCommand is a sub-command of the NominalCommand. Execution
|
||||
// of this command is generally allowed in tests.
|
||||
AttenuatedCommand = NominalCommand.Join("attenuated")
|
||||
)
|
||||
|
||||
// ProofEmpty provides an empty proof chain for testing purposes.
|
||||
var ProofEmpty = []cid.Cid{}
|
||||
|
||||
//go:embed data
|
||||
var fs embed.FS
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
ldr invocation.DelegationLoader
|
||||
err error
|
||||
)
|
||||
|
||||
var _ invocation.DelegationLoader = (*delegationLoader)(nil)
|
||||
|
||||
type delegationLoader struct {
|
||||
tokens map[cid.Cid]*delegation.Token
|
||||
}
|
||||
|
||||
// GetDelegationLoader returns a singleton instance of a test
|
||||
// DelegationLoader containing all the tokens present in the data/
|
||||
// directory.
|
||||
func GetDelegationLoader() (invocation.DelegationLoader, error) {
|
||||
once.Do(func() {
|
||||
ldr, err = loadDelegations()
|
||||
})
|
||||
|
||||
return ldr, err
|
||||
}
|
||||
|
||||
// GetDelegation implements invocation.DelegationLoader.
|
||||
func (l *delegationLoader) GetDelegation(id cid.Cid) (*delegation.Token, error) {
|
||||
tkn, ok := l.tokens[id]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: CID %s", invocation.ErrMissingDelegation, id.String())
|
||||
}
|
||||
|
||||
return tkn, nil
|
||||
}
|
||||
|
||||
func loadDelegations() (invocation.DelegationLoader, error) {
|
||||
dirEntries, err := fs.ReadDir("data")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tkns := make(map[cid.Cid]*delegation.Token, len(dirEntries))
|
||||
|
||||
for _, dirEntry := range dirEntries {
|
||||
data, err := fs.ReadFile(filepath.Join(tokenDir, dirEntry.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tkn, id, err := delegation.FromSealed(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tkns[id] = tkn
|
||||
}
|
||||
|
||||
return &delegationLoader{
|
||||
tokens: tkns,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetDelegation is a shortcut that gets (or creates) the DelegationLoader
|
||||
// and attempts to return the token referenced by the provided CID.
|
||||
func GetDelegation(id cid.Cid) (*delegation.Token, error) {
|
||||
ldr, err := GetDelegationLoader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ldr.GetDelegation(id)
|
||||
}
|
||||
|
||||
func mustGetDelegation(id cid.Cid) *delegation.Token {
|
||||
tkn, err := GetDelegation(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return tkn
|
||||
}
|
||||
240
token/delegation/delegationtest/token_gen.go
Normal file
240
token/delegation/delegationtest/token_gen.go
Normal file
@@ -0,0 +1,240 @@
|
||||
// Code generated by delegationtest - DO NOT EDIT.
|
||||
|
||||
package delegationtest
|
||||
|
||||
import gocid "github.com/ipfs/go-cid"
|
||||
|
||||
var (
|
||||
TokenAliceBobCID = gocid.MustParse("bafyreicidrwvmac5lvjypucgityrtjsknojraio7ujjli4r5eyby66wjzm")
|
||||
TokenAliceBob = mustGetDelegation(TokenAliceBobCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenBobCarolCID = gocid.MustParse("bafyreihxv2uhq43oxllzs2xfvxst7wtvvvl7pohb2chcz6hjvfv2ntea5u")
|
||||
TokenBobCarol = mustGetDelegation(TokenBobCarolCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenCarolDanCID = gocid.MustParse("bafyreihclsgiroazq3heqdswvj2cafwqbpboicq7immo65scl7ahktpsdq")
|
||||
TokenCarolDan = mustGetDelegation(TokenCarolDanCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenDanErinCID = gocid.MustParse("bafyreicja6ihewy64p3ake56xukotafjlkh4uqep2qhj52en46zzfwby3e")
|
||||
TokenDanErin = mustGetDelegation(TokenDanErinCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenErinFrankCID = gocid.MustParse("bafyreicjlx3lobxm6hl5s4htd4ydwkkqeiou6rft4rnvulfdyoew565vka")
|
||||
TokenErinFrank = mustGetDelegation(TokenErinFrankCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenCarolDanInvalidExpandedCommandCID = gocid.MustParse("bafyreid3m3pk53gqgp5rlzqhvpedbwsqbidqlp4yz64vknwbzj7bxrmsr4")
|
||||
TokenCarolDanInvalidExpandedCommand = mustGetDelegation(TokenCarolDanInvalidExpandedCommandCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenDanErinInvalidExpandedCommandCID = gocid.MustParse("bafyreifn4sy5onwajx3kqvot5mib6m6xarzrqjozqbzgmzpmc5ox3g2uzm")
|
||||
TokenDanErinInvalidExpandedCommand = mustGetDelegation(TokenDanErinInvalidExpandedCommandCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenErinFrankInvalidExpandedCommandCID = gocid.MustParse("bafyreidmpgd36jznmq42bs34o4qi3fcbrsh4idkg6ejahudejzwb76fwxe")
|
||||
TokenErinFrankInvalidExpandedCommand = mustGetDelegation(TokenErinFrankInvalidExpandedCommandCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenCarolDanValidAttenuatedCommandCID = gocid.MustParse("bafyreiekhtm237vyapk3c6voeb5lnz54crebqdqi3x4wn4u4cbrrhzsqfe")
|
||||
TokenCarolDanValidAttenuatedCommand = mustGetDelegation(TokenCarolDanValidAttenuatedCommandCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenDanErinValidAttenuatedCommandCID = gocid.MustParse("bafyreicrvzqferyy7rgo75l5rn6r2nl7zyeexxjmu3dm4ff7rn2coblj4y")
|
||||
TokenDanErinValidAttenuatedCommand = mustGetDelegation(TokenDanErinValidAttenuatedCommandCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenErinFrankValidAttenuatedCommandCID = gocid.MustParse("bafyreie6fhspk53kplcc2phla3e7z7fzldlbmmpuwk6nbow5q6s2zjmw2q")
|
||||
TokenErinFrankValidAttenuatedCommand = mustGetDelegation(TokenErinFrankValidAttenuatedCommandCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenCarolDanInvalidSubjectCID = gocid.MustParse("bafyreifgksz6756if42tnc6rqsnbaa2u3fdrveo7ek44lnj2d64d5sw26u")
|
||||
TokenCarolDanInvalidSubject = mustGetDelegation(TokenCarolDanInvalidSubjectCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenDanErinInvalidSubjectCID = gocid.MustParse("bafyreibdwew5nypsxrm4fq73wu6hw3lgwwiolj3bi33xdrbgcf3ogm6fty")
|
||||
TokenDanErinInvalidSubject = mustGetDelegation(TokenDanErinInvalidSubjectCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenErinFrankInvalidSubjectCID = gocid.MustParse("bafyreicr364mj3n7x4iyhcksxypelktcqkkw3ptg7ggxtqegw3p3mr6zc4")
|
||||
TokenErinFrankInvalidSubject = mustGetDelegation(TokenErinFrankInvalidSubjectCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenCarolDanInvalidExpiredCID = gocid.MustParse("bafyreibgtlioorouqpwr6olk6boc3pprl5tx5xs6zpfnv3pvxtggueofii")
|
||||
TokenCarolDanInvalidExpired = mustGetDelegation(TokenCarolDanInvalidExpiredCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenDanErinInvalidExpiredCID = gocid.MustParse("bafyreidhq3hjsfrucbecgcjf2nkcgmq3sh3m5gjxz23vzcaynozs5p3uh4")
|
||||
TokenDanErinInvalidExpired = mustGetDelegation(TokenDanErinInvalidExpiredCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenErinFrankInvalidExpiredCID = gocid.MustParse("bafyreido4om3y3ttkmp4c4gxm6pqug76vu3aekb666vdp6zewpvir5zs7u")
|
||||
TokenErinFrankInvalidExpired = mustGetDelegation(TokenErinFrankInvalidExpiredCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenCarolDanInvalidInactiveCID = gocid.MustParse("bafyreicea5y2nvlitvxijkupeavtg23i7ktjk3uejnaquguurzptiabk4u")
|
||||
TokenCarolDanInvalidInactive = mustGetDelegation(TokenCarolDanInvalidInactiveCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenDanErinInvalidInactiveCID = gocid.MustParse("bafyreifsgqzkmxj2vexuts3z766mwcjreiisjg2jykyzf7tbj5sclutpvq")
|
||||
TokenDanErinInvalidInactive = mustGetDelegation(TokenDanErinInvalidInactiveCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenErinFrankInvalidInactiveCID = gocid.MustParse("bafyreifbfegon24c6dndiqyktahzs65vhyasrygbw7nhsvojn6distsdre")
|
||||
TokenErinFrankInvalidInactive = mustGetDelegation(TokenErinFrankInvalidInactiveCID)
|
||||
)
|
||||
|
||||
var ProofAliceBob = []gocid.Cid{
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarol = []gocid.Cid{
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDan = []gocid.Cid{
|
||||
TokenCarolDanCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErin = []gocid.Cid{
|
||||
TokenDanErinCID,
|
||||
TokenCarolDanCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinFrank = []gocid.Cid{
|
||||
TokenErinFrankCID,
|
||||
TokenDanErinCID,
|
||||
TokenCarolDanCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanInvalidExpandedCommand = []gocid.Cid{
|
||||
TokenCarolDanInvalidExpandedCommandCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinInvalidExpandedCommand = []gocid.Cid{
|
||||
TokenDanErinInvalidExpandedCommandCID,
|
||||
TokenCarolDanInvalidExpandedCommandCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinFrankInvalidExpandedCommand = []gocid.Cid{
|
||||
TokenErinFrankInvalidExpandedCommandCID,
|
||||
TokenDanErinInvalidExpandedCommandCID,
|
||||
TokenCarolDanInvalidExpandedCommandCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanValidAttenuatedCommand = []gocid.Cid{
|
||||
TokenCarolDanValidAttenuatedCommandCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinValidAttenuatedCommand = []gocid.Cid{
|
||||
TokenDanErinValidAttenuatedCommandCID,
|
||||
TokenCarolDanValidAttenuatedCommandCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinFrankValidAttenuatedCommand = []gocid.Cid{
|
||||
TokenErinFrankValidAttenuatedCommandCID,
|
||||
TokenDanErinValidAttenuatedCommandCID,
|
||||
TokenCarolDanValidAttenuatedCommandCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanInvalidSubject = []gocid.Cid{
|
||||
TokenCarolDanInvalidSubjectCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinInvalidSubject = []gocid.Cid{
|
||||
TokenDanErinInvalidSubjectCID,
|
||||
TokenCarolDanInvalidSubjectCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinFrankInvalidSubject = []gocid.Cid{
|
||||
TokenErinFrankInvalidSubjectCID,
|
||||
TokenDanErinInvalidSubjectCID,
|
||||
TokenCarolDanInvalidSubjectCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanInvalidExpired = []gocid.Cid{
|
||||
TokenCarolDanInvalidExpiredCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinInvalidExpired = []gocid.Cid{
|
||||
TokenDanErinInvalidExpiredCID,
|
||||
TokenCarolDanInvalidExpiredCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinFrankInvalidExpired = []gocid.Cid{
|
||||
TokenErinFrankInvalidExpiredCID,
|
||||
TokenDanErinInvalidExpiredCID,
|
||||
TokenCarolDanInvalidExpiredCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanInvalidInactive = []gocid.Cid{
|
||||
TokenCarolDanInvalidInactiveCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinInvalidInactive = []gocid.Cid{
|
||||
TokenDanErinInvalidInactiveCID,
|
||||
TokenCarolDanInvalidInactiveCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinFrankInvalidInactive = []gocid.Cid{
|
||||
TokenErinFrankInvalidInactiveCID,
|
||||
TokenDanErinInvalidInactiveCID,
|
||||
TokenCarolDanInvalidInactiveCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
32
token/delegation/delegationtest/token_test.go
Normal file
32
token/delegation/delegationtest/token_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package delegationtest_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation/delegationtest"
|
||||
"github.com/ucan-wg/go-ucan/token/invocation"
|
||||
)
|
||||
|
||||
func TestGetDelegation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("passes with valid CID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tkn, err := delegationtest.GetDelegation(delegationtest.TokenAliceBobCID)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, tkn)
|
||||
})
|
||||
|
||||
t.Run("fails with unknown CID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tkn, err := delegationtest.GetDelegation(cid.Undef)
|
||||
require.ErrorIs(t, err, invocation.ErrMissingDelegation)
|
||||
require.ErrorContains(t, err, "CID b")
|
||||
assert.Nil(t, tkn)
|
||||
})
|
||||
}
|
||||
@@ -1,334 +1,143 @@
|
||||
package invocation_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
"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"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation/delegationtest"
|
||||
"github.com/ucan-wg/go-ucan/token/invocation"
|
||||
"github.com/ucan-wg/go-ucan/token/invocation/invocationtest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
rootPrivKeyCfg = "CAESQNMjVXyblHUrtmvdzjfkdrUlLkxh/cHZMFirki7dJLFjW7cCs74VxjO8Wh04f8Xg0uqyKw7N0wTUBiPy2h4hGPQ="
|
||||
rootDIDStr = "did:key:z6MkkdH136Kee5akqx1DiK3hE3WGx4SKmHo11tYw2cWmjjZV"
|
||||
rootTknCIDStr = "bafyreibk6lkgd32zldpqqqsdn7diyosokelrhder5lic4ujadtxl5blkei"
|
||||
|
||||
rootOnlyTknCIDStr = "bafyreidyknlfnsah63jujdkhe5vvjil2yoznlgoaezeq62qqhghaxzpfya"
|
||||
|
||||
dlg0PrivKeyCfg = "CAESQFIb3aD0lZzidUzTtwdpHtCyx1VJxe+Uq4x/S+XQFDDgz/NZIi/TR3rhgUn550RSBOSxNmw0QnR0FOPmAB7SXAg="
|
||||
dlg0DIDStr = "did:key:z6MktT1f5LXQ2MFSUwwTTY9DDU2QdBzZA11V5bjou4YDQY6K"
|
||||
dlg0TknCIDStr = "bafyreihocbstcdvgyeczjoijiyo2bdppyplm2aglqumwtutyapd2zlp2bi"
|
||||
|
||||
expiredTknDagJson = `[{"/":{"bytes":"U4IH1Q52UrdOyOHkHtXSkH0uf5ouk10k/LTOMz3UvP2k1kqv9/rbGXUwhQCy6JP3s8hc+U3h/lBXYFYzIlULAw"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/dlg@1.0.0-rc.1":{"aud":"did:key:z6MkqTkYrRV6WpFCWRcEHBXnTV3cVqzMtVZREhJKAAswrrGq","cmd":"/seg0","exp":1731439015,"iss":"did:key:z6MktT1f5LXQ2MFSUwwTTY9DDU2QdBzZA11V5bjou4YDQY6K","nonce":{"/":{"bytes":"AAECAwQFBgcICQoL"}},"pol":[],"sub":"did:key:z6MkkdH136Kee5akqx1DiK3hE3WGx4SKmHo11tYw2cWmjjZV"}}]`
|
||||
expiredDlg0TknCIDStr = "bafyreigezbrohmjmzv5nxdw2nhjabnrvs52vcv4uvbuscnbyr2jzep2vru"
|
||||
|
||||
inactiveDlg0TknCIDStr = "bafyreiffflxvtfiv722i3lomrqcogc6nncxai3voxeltvrphpuwecdfbgq"
|
||||
|
||||
dlg1PrivKeyCfg = "CAESQPHkXp4OatPxpJ1veMAxEYzP4rd3/sPUz9PRQuJaDKTco5DJTdJC5iXxjCe1VDYAmRwlJOBvBdbsvS3qFgV6zoI="
|
||||
dlg1DIDStr = "did:key:z6MkqTkYrRV6WpFCWRcEHBXnTV3cVqzMtVZREhJKAAswrrGq"
|
||||
dlg1TknCIDStr = "bafyreihjbkvom3tdivzolh6aieuwamz4x6bu3dh667bxytdc5vzo37obo4"
|
||||
|
||||
// dlg2PrivKeyCfg = "CAESQJryJWe3uGt+7BTH2doqsN+2H83rNXv7Yw0tMoKE+lBKTqByESll668G1Jiy9gW/8hm9/jVcrFD9Y1BWyokfNBE="
|
||||
// dlg2DIDStr = "did:key:z6MkjkBk9hzMhcr6rYMXHPEsXy1LAWK5dHniNdbaEPojGXr8"
|
||||
|
||||
// dlg3PrivKeyCfg = "CAESQPrLMlX2p+Dgz70YCyVXbHAJfVMT7lLUYAbuZ1rBKQmBLiD7WJ4Spc4VFZAsJ7HUnkneJWNTk/FFaN2z3pb/OZI="
|
||||
// dlg3DIDStr = "did:key:z6MkhZKy9X4sLtbH1fGQmPVMjXmBAEQ3vAN6DRSfebSBCqpu"
|
||||
|
||||
invPrivKeyCfg = "CAESQHJW8WYTZDRzxjLBjrFN35raIGvVsPoXAJB/5X+J8miboVWVLZFyQmxCAIXOMpwLqWW7R2I98qsCGvxgEJZ5qgY="
|
||||
invDIDStr = "did:key:z6MkqK3NgTnZZo77iptQdU9proJn1ozMmcTSKR98t8sZzJAq"
|
||||
invTknCIDStr = ""
|
||||
|
||||
missingPrivKeyCfg = "CAESQMjRvrEIjpPYRQKmkAGw/pV0XgE958rYa4vlnKJjl1zz/sdnGnyV1xKLJk8D39edyjhHWyqcpgFnozQK62SG16k="
|
||||
missingDIDStr = "bafyreigwypmw6eul6vadi6g6lnfbsfo2zck7gfzsbjoroqs3djhnzzc7mm"
|
||||
missingTknCIDStr = "did:key:z6MkwboxFsH3kEuehBZ5fLkRmxi68yv1u38swA4r9Jm2VRma"
|
||||
missingTknCIDStr = "bafyreigwypmw6eul6vadi6g6lnfbsfo2zck7gfzsbjoroqs3djhnzzc7mm"
|
||||
missingDIDStr = "did:key:z6MkwboxFsH3kEuehBZ5fLkRmxi68yv1u38swA4r9Jm2VRma"
|
||||
)
|
||||
|
||||
var emptyArguments = args.New()
|
||||
|
||||
func TestToken_ExecutionAllowed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("passes - only root", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := invocationtest.EmptyArguments
|
||||
prf := invocationtest.Proof(t, rootOnlyTknCIDStr)
|
||||
testPasses(t, []string{"seg0"}, args, prf)
|
||||
testPasses(t, didtest.PersonaBob, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBob)
|
||||
})
|
||||
|
||||
t.Run("passes - valid chain", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := invocationtest.EmptyArguments
|
||||
prf := invocationtest.Proof(t, dlg1TknCIDStr, dlg0TknCIDStr, rootTknCIDStr)
|
||||
testPasses(t, []string{"seg0"}, args, prf)
|
||||
testPasses(t, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank)
|
||||
})
|
||||
|
||||
t.Run("passes - proof chain attenuates command", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testPasses(t, didtest.PersonaFrank, delegationtest.AttenuatedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrankValidAttenuatedCommand)
|
||||
})
|
||||
|
||||
t.Run("passes - invocation attenuates command", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testPasses(t, didtest.PersonaFrank, delegationtest.AttenuatedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank)
|
||||
})
|
||||
|
||||
t.Run("fails - no proof", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := args.New()
|
||||
testFails(t, invocation.ErrNoProof, []string{"seg0"}, args, []cid.Cid{})
|
||||
testFails(t, invocation.ErrNoProof, didtest.PersonaCarol, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofEmpty)
|
||||
})
|
||||
|
||||
t.Run("fails - missing referenced delegation", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := invocationtest.EmptyArguments
|
||||
prf := invocationtest.Proof(t, missingDIDStr, rootTknCIDStr)
|
||||
testFails(t, invocation.ErrMissingDelegation, []string{"seg0"}, args, prf)
|
||||
missingTknCID, err := cid.Parse(missingTknCIDStr)
|
||||
require.NoError(t, err)
|
||||
|
||||
prf := []cid.Cid{missingTknCID, delegationtest.TokenAliceBobCID}
|
||||
testFails(t, invocation.ErrMissingDelegation, didtest.PersonaCarol, delegationtest.NominalCommand, emptyArguments, prf)
|
||||
})
|
||||
|
||||
t.Run("fails - referenced delegation expired", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := invocationtest.EmptyArguments
|
||||
prf := invocationtest.Proof(t, dlg1TknCIDStr, expiredDlg0TknCIDStr, rootTknCIDStr)
|
||||
testFails(t, invocation.ErrTokenInvalidNow, []string{"seg0"}, args, prf)
|
||||
testFails(t, invocation.ErrTokenInvalidNow, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrankInvalidExpired)
|
||||
|
||||
})
|
||||
|
||||
t.Run("fails - referenced delegation inactive", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := invocationtest.EmptyArguments
|
||||
prf := invocationtest.Proof(t, dlg1TknCIDStr, inactiveDlg0TknCIDStr, rootTknCIDStr)
|
||||
testFails(t, invocation.ErrTokenInvalidNow, []string{"seg0"}, args, prf)
|
||||
testFails(t, invocation.ErrTokenInvalidNow, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrankInvalidInactive)
|
||||
})
|
||||
|
||||
t.Run("fails - last (or only) delegation not root", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := args.New()
|
||||
prf := invocationtest.Proof(t, dlg1TknCIDStr)
|
||||
testFails(t, invocation.ErrLastNotRoot, []string{"seg0"}, args, prf)
|
||||
prf := []cid.Cid{delegationtest.TokenErinFrankCID, delegationtest.TokenDanErinCID, delegationtest.TokenCarolDanCID}
|
||||
testFails(t, invocation.ErrLastNotRoot, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, prf)
|
||||
})
|
||||
|
||||
t.Run("fails - broken chain", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := invocationtest.EmptyArguments
|
||||
prf := invocationtest.Proof(t, dlg1TknCIDStr, rootTknCIDStr)
|
||||
testFails(t, invocation.ErrBrokenChain, []string{"seg0"}, args, prf)
|
||||
prf := []cid.Cid{delegationtest.TokenCarolDanCID, delegationtest.TokenAliceBobCID}
|
||||
testFails(t, invocation.ErrBrokenChain, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, prf)
|
||||
})
|
||||
|
||||
t.Run("fails - first not issued to invoker", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := invocationtest.EmptyArguments
|
||||
prf := invocationtest.Proof(t, dlg0TknCIDStr, rootTknCIDStr)
|
||||
testFails(t, invocation.ErrBrokenChain, []string{"seg0"}, args, prf)
|
||||
prf := []cid.Cid{delegationtest.TokenBobCarolCID, delegationtest.TokenAliceBobCID}
|
||||
testFails(t, invocation.ErrBrokenChain, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, prf)
|
||||
})
|
||||
|
||||
t.Run("fails - proof chain expands command", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testFails(t, invocation.ErrCommandNotCovered, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrankInvalidExpandedCommand)
|
||||
})
|
||||
|
||||
t.Run("fails - invocation expands command", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testFails(t, invocation.ErrCommandNotCovered, didtest.PersonaFrank, delegationtest.ExpandedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank)
|
||||
})
|
||||
|
||||
t.Run("fails - inconsistent subject", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testFails(t, invocation.ErrWrongSub, didtest.PersonaFrank, delegationtest.ExpandedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrankInvalidSubject)
|
||||
})
|
||||
}
|
||||
|
||||
func test(t *testing.T, cmdSegs []string, args *args.Args, prf []cid.Cid, opts ...invocation.Option) (bool, error) {
|
||||
ldr := newTestDelegationLoader(t)
|
||||
tkn := testInvocation(t, invPrivKeyCfg, rootDIDStr, rootDIDStr, cmdSegs, args, prf, opts...)
|
||||
func test(t *testing.T, persona didtest.Persona, cmd command.Command, args *args.Args, prf []cid.Cid, opts ...invocation.Option) (bool, error) {
|
||||
t.Helper()
|
||||
|
||||
tkn, err := invocation.New(persona.DID(t), didtest.PersonaAlice.DID(t), cmd, prf, opts...)
|
||||
require.NoError(t, err)
|
||||
|
||||
ldr, err := delegationtest.GetDelegationLoader()
|
||||
require.NoError(t, err)
|
||||
|
||||
return tkn.ExecutionAllowed(ldr)
|
||||
}
|
||||
|
||||
func testFails(t *testing.T, expErr error, cmdSegs []string, args *args.Args, prf []cid.Cid, opts ...invocation.Option) {
|
||||
t.Helper()
|
||||
|
||||
ok, err := test(t, cmdSegs, args, prf, opts...)
|
||||
func testFails(t *testing.T, expErr error, persona didtest.Persona, cmd command.Command, args *args.Args, prf []cid.Cid, opts ...invocation.Option) {
|
||||
ok, err := test(t, persona, cmd, args, prf, opts...)
|
||||
require.ErrorIs(t, err, expErr)
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func testPasses(t *testing.T, cmdSegs []string, args *args.Args, prf []cid.Cid, opts ...invocation.Option) {
|
||||
ok, err := test(t, cmdSegs, args, prf, opts...)
|
||||
func testPasses(t *testing.T, persona didtest.Persona, cmd command.Command, args *args.Args, prf []cid.Cid, opts ...invocation.Option) {
|
||||
ok, err := test(t, persona, cmd, args, prf, opts...)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
var _ invocation.DelegationLoader = (*testDelegationLoader)(nil)
|
||||
|
||||
type testDelegationLoader struct {
|
||||
tkns map[cid.Cid]*delegation.Token
|
||||
}
|
||||
|
||||
func newTestDelegationLoader(t *testing.T) *testDelegationLoader {
|
||||
t.Helper()
|
||||
|
||||
cmdSegs := []string{"seg0"}
|
||||
ldr := &testDelegationLoader{
|
||||
tkns: map[cid.Cid]*delegation.Token{},
|
||||
}
|
||||
|
||||
// aud, err := did.Parse(dlg0DIDStr)
|
||||
// require.NoError(t, err)
|
||||
// cmd := command.New(cmdSegs...)
|
||||
|
||||
// pol, err := policy.FromDagJson("[]")
|
||||
// require.NoError(t, err)
|
||||
|
||||
// rootDlg, err := delegation.Root(rootPrivKey, aud, cmd, pol)
|
||||
// require.NoError(t, err)
|
||||
// _, rootCID, err := rootDlg.ToSealed(rootPrivKey)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// Do not add this one to the loader
|
||||
_, missingCID := testDelegation(t, missingPrivKeyCfg, dlg1DIDStr, rootDIDStr, cmdSegs, "[]")
|
||||
t.Log("Missing CID", missingCID)
|
||||
|
||||
rootTkn, rootDlgCID := testDelegation(t, rootPrivKeyCfg, dlg0DIDStr, rootDIDStr, cmdSegs, "[]")
|
||||
t.Log("Root chain CID:", rootDlgCID)
|
||||
ldr.tkns[rootDlgCID] = rootTkn
|
||||
|
||||
rootOnlyTkn, rootOnlyDlgCID := testDelegation(t, rootPrivKeyCfg, invDIDStr, rootDIDStr, cmdSegs, "[]")
|
||||
t.Log("Root only chain CID:", rootOnlyDlgCID)
|
||||
ldr.tkns[rootOnlyDlgCID] = rootOnlyTkn
|
||||
|
||||
dlg0Tkn, dlg0CID := testDelegation(t, dlg0PrivKeyCfg, dlg1DIDStr, rootDIDStr, cmdSegs, "[]")
|
||||
t.Log("Dlg0 CID:", dlg0CID)
|
||||
ldr.tkns[dlg0CID] = dlg0Tkn
|
||||
|
||||
// exp := time.Now().Add(time.Second)
|
||||
|
||||
// expiredDlg0Tkn, expiredDlg0CID := testDelegation(t, dlg0PrivKeyCfg, dlg1DIDStr, rootDIDStr, cmdSegs, "[]", delegation.WithExpiration(exp))
|
||||
// t.Log("Expired Dlg0 CID:", expiredDlg0CID)
|
||||
|
||||
// dlg0PrivKeyEnc, err := crypto.ConfigDecodeKey(dlg0PrivKeyCfg)
|
||||
// require.NoError(t, err)
|
||||
// dlg0PrivKey, err := crypto.UnmarshalPrivateKey(dlg0PrivKeyEnc)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// dlg0DagJson, err := expiredDlg0Tkn.ToDagJson(dlg0PrivKey)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// t.Log("Expired token DAGJSON:", string(dlg0DagJson))
|
||||
|
||||
expiredDlg0Tkn, err := delegation.FromDagJson([]byte(expiredTknDagJson))
|
||||
require.NoError(t, err)
|
||||
expiredDlg0TknCID, err := cid.Parse(expiredDlg0TknCIDStr)
|
||||
ldr.tkns[expiredDlg0TknCID] = expiredDlg0Tkn
|
||||
|
||||
nbf, err := time.Parse(time.RFC3339, "2500-01-01T00:00:00Z")
|
||||
require.NoError(t, err)
|
||||
|
||||
inactiveDlg0Tkn, inactiveDlg0CID := testDelegation(t, dlg0PrivKeyCfg, dlg1DIDStr, rootDIDStr, cmdSegs, "[]", delegation.WithNotBefore(nbf))
|
||||
t.Log("Inactive Dlg0 CID:", inactiveDlg0CID)
|
||||
ldr.tkns[inactiveDlg0CID] = inactiveDlg0Tkn
|
||||
|
||||
dlg1Tkn, dlg1CID := testDelegation(t, dlg1PrivKeyCfg, invDIDStr, rootDIDStr, cmdSegs, "[]")
|
||||
t.Log("Dlg1 CID:", dlg1CID)
|
||||
ldr.tkns[dlg1CID] = dlg1Tkn
|
||||
|
||||
// for i := 0; i < 5; i++ {
|
||||
// privKey, id, err := did.GenerateEd25519()
|
||||
// require.NoError(t, err)
|
||||
|
||||
// privKeyEnc, err := crypto.MarshalPrivateKey(privKey)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// t.Log("PrivKey:", crypto.ConfigEncodeKey(privKeyEnc), "DID:", id)
|
||||
|
||||
// }
|
||||
|
||||
t.Log("Delegation loader length:", len(ldr.tkns))
|
||||
|
||||
return ldr
|
||||
}
|
||||
|
||||
var ErrUnknownDelegationTokenCID = errors.New("unknown delegation token CID")
|
||||
|
||||
func (l *testDelegationLoader) GetDelegation(c cid.Cid) (*delegation.Token, error) {
|
||||
tkn, ok := l.tkns[c]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: %s", ErrUnknownDelegationTokenCID, c.String())
|
||||
}
|
||||
|
||||
return tkn, nil
|
||||
}
|
||||
|
||||
func testDelegation(t *testing.T, privKeyCfg string, audStr string, subStr string, cmdSegs []string, polDAGJSON string, opts ...delegation.Option) (*delegation.Token, cid.Cid) {
|
||||
t.Helper()
|
||||
|
||||
privKey, aud, sub, cmd := parseTokenArgs(t, privKeyCfg, audStr, subStr, cmdSegs)
|
||||
|
||||
pol, err := policy.FromDagJson(polDAGJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
opts = append(
|
||||
opts,
|
||||
delegation.WithNonce([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b}),
|
||||
delegation.WithSubject(sub),
|
||||
)
|
||||
|
||||
tkn, err := delegation.New(privKey, aud, cmd, pol, opts...)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, id, err := tkn.ToSealed(privKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
return tkn, id
|
||||
}
|
||||
|
||||
func testInvocation(t *testing.T, privKeyCfg string, audStr string, subStr string, cmdSegs []string, args *args.Args, prf []cid.Cid, opts ...invocation.Option) *invocation.Token {
|
||||
t.Helper()
|
||||
|
||||
privKey, _, sub, cmd := parseTokenArgs(t, privKeyCfg, audStr, subStr, cmdSegs)
|
||||
|
||||
iss, err := did.FromPrivKey(privKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
tkn, err := invocation.New(iss, sub, cmd, prf)
|
||||
require.NoError(t, err)
|
||||
|
||||
return tkn
|
||||
}
|
||||
|
||||
func testProof(t *testing.T, cidStrs ...string) []cid.Cid {
|
||||
var prf []cid.Cid
|
||||
|
||||
for cidStr := range cidStrs {
|
||||
id, err := cid.Parse(cidStr)
|
||||
require.NoError(t, err)
|
||||
|
||||
prf = append(prf, id)
|
||||
}
|
||||
|
||||
return prf
|
||||
}
|
||||
|
||||
func parseTokenArgs(
|
||||
t *testing.T,
|
||||
privKeyCfg string,
|
||||
audStr string,
|
||||
subStr string,
|
||||
cmdSegs []string,
|
||||
) (
|
||||
privKey crypto.PrivKey,
|
||||
aud did.DID,
|
||||
sub did.DID,
|
||||
cmd command.Command,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
var err error
|
||||
|
||||
privKeyEnc, err := crypto.ConfigDecodeKey(privKeyCfg)
|
||||
require.NoError(t, err)
|
||||
privKey, err = crypto.UnmarshalPrivateKey(privKeyEnc)
|
||||
require.NoError(t, err)
|
||||
|
||||
aud, err = did.Parse(audStr)
|
||||
require.NoError(t, err)
|
||||
|
||||
sub, err = did.Parse(subStr)
|
||||
require.NoError(t, err)
|
||||
|
||||
cmd = command.New(cmdSegs...)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package invocationtest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/pkg/args"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
)
|
||||
|
||||
var (
|
||||
EmptyArguments = args.New()
|
||||
EmptyPolicy = emptyPolicy()
|
||||
)
|
||||
|
||||
func emptyPolicy() policy.Policy {
|
||||
pol, err := policy.FromDagJson("[]")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return pol
|
||||
}
|
||||
|
||||
func Proof(t *testing.T, cidStrs ...string) []cid.Cid {
|
||||
// t.Helper()
|
||||
|
||||
prf := make([]cid.Cid, len(cidStrs))
|
||||
|
||||
for i, cidStr := range cidStrs {
|
||||
id, err := cid.Parse(cidStr)
|
||||
require.NoError(t, err)
|
||||
|
||||
prf[i] = id
|
||||
}
|
||||
|
||||
return prf
|
||||
}
|
||||
Reference in New Issue
Block a user