diff --git a/did/didtest/crypto.go b/did/didtest/crypto.go new file mode 100644 index 0000000..b06928e --- /dev/null +++ b/did/didtest/crypto.go @@ -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, + } +} diff --git a/go.mod b/go.mod index 2193ff4..51d0d0e 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 82932d5..b7534ef 100644 --- a/go.sum +++ b/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= diff --git a/pkg/policy/policytest/policy.go b/pkg/policy/policytest/policy.go new file mode 100644 index 0000000..4afd5f3 --- /dev/null +++ b/pkg/policy/policytest/policy.go @@ -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 +} diff --git a/token/delegation/delegationtest/README.md b/token/delegation/delegationtest/README.md new file mode 100644 index 0000000..406875d --- /dev/null +++ b/token/delegation/delegationtest/README.md @@ -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. diff --git a/token/delegation/delegationtest/data/TokenAliceBob.dagcbor b/token/delegation/delegationtest/data/TokenAliceBob.dagcbor new file mode 100644 index 0000000..2122c4d Binary files /dev/null and b/token/delegation/delegationtest/data/TokenAliceBob.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenBobCarol.dagcbor b/token/delegation/delegationtest/data/TokenBobCarol.dagcbor new file mode 100644 index 0000000..8d68e2b Binary files /dev/null and b/token/delegation/delegationtest/data/TokenBobCarol.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenCarolDan.dagcbor b/token/delegation/delegationtest/data/TokenCarolDan.dagcbor new file mode 100644 index 0000000..809ab4a Binary files /dev/null and b/token/delegation/delegationtest/data/TokenCarolDan.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenCarolDanInvalidExpandedCommand.dagcbor b/token/delegation/delegationtest/data/TokenCarolDanInvalidExpandedCommand.dagcbor new file mode 100644 index 0000000..c8bb4b3 Binary files /dev/null and b/token/delegation/delegationtest/data/TokenCarolDanInvalidExpandedCommand.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenCarolDanInvalidExpired.dagcbor b/token/delegation/delegationtest/data/TokenCarolDanInvalidExpired.dagcbor new file mode 100644 index 0000000..09d95dc Binary files /dev/null and b/token/delegation/delegationtest/data/TokenCarolDanInvalidExpired.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenCarolDanInvalidInactive.dagcbor b/token/delegation/delegationtest/data/TokenCarolDanInvalidInactive.dagcbor new file mode 100644 index 0000000..fae7e21 Binary files /dev/null and b/token/delegation/delegationtest/data/TokenCarolDanInvalidInactive.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenCarolDanInvalidSubject.dagcbor b/token/delegation/delegationtest/data/TokenCarolDanInvalidSubject.dagcbor new file mode 100644 index 0000000..7523f1a Binary files /dev/null and b/token/delegation/delegationtest/data/TokenCarolDanInvalidSubject.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenCarolDanValidAttenuatedCommand.dagcbor b/token/delegation/delegationtest/data/TokenCarolDanValidAttenuatedCommand.dagcbor new file mode 100644 index 0000000..6ddf106 Binary files /dev/null and b/token/delegation/delegationtest/data/TokenCarolDanValidAttenuatedCommand.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenDanErin.dagcbor b/token/delegation/delegationtest/data/TokenDanErin.dagcbor new file mode 100644 index 0000000..baaac2e Binary files /dev/null and b/token/delegation/delegationtest/data/TokenDanErin.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenDanErinInvalidExpandedCommand.dagcbor b/token/delegation/delegationtest/data/TokenDanErinInvalidExpandedCommand.dagcbor new file mode 100644 index 0000000..777c0aa Binary files /dev/null and b/token/delegation/delegationtest/data/TokenDanErinInvalidExpandedCommand.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenDanErinInvalidExpired.dagcbor b/token/delegation/delegationtest/data/TokenDanErinInvalidExpired.dagcbor new file mode 100644 index 0000000..ba90840 Binary files /dev/null and b/token/delegation/delegationtest/data/TokenDanErinInvalidExpired.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenDanErinInvalidInactive.dagcbor b/token/delegation/delegationtest/data/TokenDanErinInvalidInactive.dagcbor new file mode 100644 index 0000000..de938fd Binary files /dev/null and b/token/delegation/delegationtest/data/TokenDanErinInvalidInactive.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenDanErinInvalidSubject.dagcbor b/token/delegation/delegationtest/data/TokenDanErinInvalidSubject.dagcbor new file mode 100644 index 0000000..ba61f8d Binary files /dev/null and b/token/delegation/delegationtest/data/TokenDanErinInvalidSubject.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenDanErinValidAttenuatedCommand.dagcbor b/token/delegation/delegationtest/data/TokenDanErinValidAttenuatedCommand.dagcbor new file mode 100644 index 0000000..6399585 Binary files /dev/null and b/token/delegation/delegationtest/data/TokenDanErinValidAttenuatedCommand.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenErinFrank.dagcbor b/token/delegation/delegationtest/data/TokenErinFrank.dagcbor new file mode 100644 index 0000000..1c85482 Binary files /dev/null and b/token/delegation/delegationtest/data/TokenErinFrank.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenErinFrankInvalidExpandedCommand.dagcbor b/token/delegation/delegationtest/data/TokenErinFrankInvalidExpandedCommand.dagcbor new file mode 100644 index 0000000..ceccd46 Binary files /dev/null and b/token/delegation/delegationtest/data/TokenErinFrankInvalidExpandedCommand.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenErinFrankInvalidExpired.dagcbor b/token/delegation/delegationtest/data/TokenErinFrankInvalidExpired.dagcbor new file mode 100644 index 0000000..3939127 Binary files /dev/null and b/token/delegation/delegationtest/data/TokenErinFrankInvalidExpired.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenErinFrankInvalidInactive.dagcbor b/token/delegation/delegationtest/data/TokenErinFrankInvalidInactive.dagcbor new file mode 100644 index 0000000..ae90da1 Binary files /dev/null and b/token/delegation/delegationtest/data/TokenErinFrankInvalidInactive.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenErinFrankInvalidSubject.dagcbor b/token/delegation/delegationtest/data/TokenErinFrankInvalidSubject.dagcbor new file mode 100644 index 0000000..b8bac8a Binary files /dev/null and b/token/delegation/delegationtest/data/TokenErinFrankInvalidSubject.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenErinFrankValidAttenuatedCommand.dagcbor b/token/delegation/delegationtest/data/TokenErinFrankValidAttenuatedCommand.dagcbor new file mode 100644 index 0000000..822aa18 Binary files /dev/null and b/token/delegation/delegationtest/data/TokenErinFrankValidAttenuatedCommand.dagcbor differ diff --git a/token/delegation/delegationtest/doc.go b/token/delegation/delegationtest/doc.go new file mode 100644 index 0000000..be389d1 --- /dev/null +++ b/token/delegation/delegationtest/doc.go @@ -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 diff --git a/token/delegation/delegationtest/generator_test.go b/token/delegation/delegationtest/generator_test.go new file mode 100644 index 0000000..f5878c2 --- /dev/null +++ b/token/delegation/delegationtest/generator_test.go @@ -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")) +} diff --git a/token/delegation/delegationtest/token.go b/token/delegation/delegationtest/token.go new file mode 100644 index 0000000..07c27e6 --- /dev/null +++ b/token/delegation/delegationtest/token.go @@ -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 +} diff --git a/token/delegation/delegationtest/token_gen.go b/token/delegation/delegationtest/token_gen.go new file mode 100644 index 0000000..469f385 --- /dev/null +++ b/token/delegation/delegationtest/token_gen.go @@ -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, +} diff --git a/token/delegation/delegationtest/token_test.go b/token/delegation/delegationtest/token_test.go new file mode 100644 index 0000000..e9518ac --- /dev/null +++ b/token/delegation/delegationtest/token_test.go @@ -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) + }) +} diff --git a/token/invocation/invocation_test.go b/token/invocation/invocation_test.go index 921e085..6482606 100644 --- a/token/invocation/invocation_test.go +++ b/token/invocation/invocation_test.go @@ -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 -} diff --git a/token/invocation/invocationtest/field.go b/token/invocation/invocationtest/field.go deleted file mode 100644 index 1d31ccf..0000000 --- a/token/invocation/invocationtest/field.go +++ /dev/null @@ -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 -}