diff --git a/did/did.go b/did/did.go index 19042bf..2d77684 100644 --- a/did/did.go +++ b/did/did.go @@ -102,6 +102,9 @@ func (d DID) PubKey() (crypto.PubKey, error) { // String formats the decentralized identity document (DID) as a string. func (d DID) String() string { + if d == Undef { + return "(undefined)" + } key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.bytes)) return "did:key:" + key } diff --git a/pkg/container/serial_test.go b/pkg/container/serial_test.go index be828fb..3894e7a 100644 --- a/pkg/container/serial_test.go +++ b/pkg/container/serial_test.go @@ -160,13 +160,12 @@ func randToken() (*delegation.Token, cid.Cid, []byte) { opts := []delegation.Option{ delegation.WithExpiration(time.Now().Add(time.Hour)), - delegation.WithSubject(iss), } for i := 0; i < 3; i++ { opts = append(opts, delegation.WithMeta(randomString(8), randomString(10))) } - t, err := delegation.New(iss, aud, cmd, pol, opts...) + t, err := delegation.Root(iss, aud, cmd, pol, opts...) if err != nil { panic(err) } diff --git a/token/delegation/delegation.go b/token/delegation/delegation.go index 1d6208a..6bafe49 100644 --- a/token/delegation/delegation.go +++ b/token/delegation/delegation.go @@ -10,8 +10,10 @@ package delegation // TODO: change the "delegation" link above when the specification is merged import ( + "encoding/base64" "errors" "fmt" + "strings" "time" "github.com/ucan-wg/go-ucan/did" @@ -44,16 +46,15 @@ type Token struct { expiration *time.Time } -// New creates a validated Token from the provided parameters and options. +// New creates a validated delegation Token from the provided parameters and options. +// This is typically used to delegate a given power to another agent. // -// When creating a delegated token, the Issuer's (iss) DID is assembled -// using the public key associated with the private key sent as the first -// parameter. -func New(iss, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) { +// You can read it as "(issuer) allows (audience) to perform (cmd+pol) on (subject)". +func New(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, sub did.DID, opts ...Option) (*Token, error) { tkn := &Token{ issuer: iss, audience: aud, - subject: did.Undef, + subject: sub, command: cmd, policy: pol, meta: meta.NewMeta(), @@ -81,16 +82,27 @@ func New(iss, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Optio return tkn, nil } -// Root creates a validated UCAN delegation Token from the provided -// parameters and options. +// Root creates a validated UCAN delegation Token from the provided parameters and options. +// This is typically used to create and give a power to an agent. // -// When creating a root token, both the Issuer's (iss) and Subject's -// (sub) DIDs are assembled from the public key associated with the -// private key passed as the first argument. -func Root(iss, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) { - opts = append(opts, WithSubject(iss)) +// You can read it as "(issuer) allows (audience) to perform (cmd+pol) on itself". +func Root(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) { + return New(iss, aud, cmd, pol, iss, opts...) +} - return New(iss, aud, cmd, pol, opts...) +// Powerline creates a validated UCAN delegation Token from the provided parameters and options. +// +// Powerline is a pattern for automatically delegating all future delegations to another agent regardless of Subject. +// This is a very powerful pattern, use it only if you understand it. +// Powerline delegations MUST NOT be used as the root delegation to a resource +// +// A very common use case for Powerline is providing a stable DID across multiple agents (e.g. representing a user with +// multiple devices). This enables the automatic sharing of authority across their devices without needing to share keys +// or set up a threshold scheme. It is also flexible, since a Powerline delegation MAY be revoked. +// +// You can read it as "(issuer) allows (audience) to perform (cmd+pol) on anything". +func Powerline(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) { + return New(iss, aud, cmd, pol, did.Undef, opts...) } // Issuer returns the did.DID representing the Token's issuer. @@ -160,6 +172,32 @@ func (t *Token) IsValidAt(ti time.Time) bool { return true } +func (t *Token) String() string { + var res strings.Builder + + var kind string + switch { + case t.issuer == t.subject: + kind = " (root delegation)" + case t.subject == did.Undef: + kind = " (powerline delegation)" + default: + kind = " (normal delegation)" + } + + res.WriteString(fmt.Sprintf("Issuer: %s\n", t.Issuer())) + res.WriteString(fmt.Sprintf("Audience: %s\n", t.Audience())) + res.WriteString(fmt.Sprintf("Subject: %s%s\n", t.Subject(), kind)) + res.WriteString(fmt.Sprintf("Command: %s\n", t.Command())) + res.WriteString(fmt.Sprintf("Policy: %s\n", t.Policy())) + res.WriteString(fmt.Sprintf("Nonce: %s\n", base64.StdEncoding.EncodeToString(t.Nonce()))) + res.WriteString(fmt.Sprintf("Meta: %s\n", t.Meta())) + res.WriteString(fmt.Sprintf("NotBefore: %v\n", t.NotBefore())) + res.WriteString(fmt.Sprintf("Expiration: %v", t.Expiration())) + + return res.String() +} + func (t *Token) validate() error { var errs error diff --git a/token/delegation/delegation_test.go b/token/delegation/delegation_test.go index 8da08b4..7ce3497 100644 --- a/token/delegation/delegation_test.go +++ b/token/delegation/delegation_test.go @@ -56,9 +56,6 @@ const ( ] ` - newCID = "zdpuAwa4qv3ncMDPeDoqVxjZy3JoyWsbqUzm94rdA1AvRFkkw" - rootCID = "zdpuAkgGmUp5JrXvehGuuw9JA8DLQKDaxtK3R8brDQQVC2i5X" - aesKey = "xQklMmNTnVrmaPBq/0pwV5fEwuv/iClF5HWak9MsgI8=" ) @@ -75,9 +72,8 @@ func TestConstructors(t *testing.T) { require.NoError(t, err) t.Run("New", func(t *testing.T) { - tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, + tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, didtest.PersonaCarol.DID(), delegation.WithNonce([]byte(nonce)), - delegation.WithSubject(didtest.PersonaAlice.DID()), delegation.WithExpiration(exp), delegation.WithMeta("foo", "fooo"), delegation.WithMeta("bar", "barr"), @@ -106,6 +102,23 @@ func TestConstructors(t *testing.T) { golden.Assert(t, string(data), "root.dagjson") }) + + t.Run("Powerline", func(t *testing.T) { + t.Parallel() + + tkn, err := delegation.Powerline(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, + delegation.WithNonce([]byte(nonce)), + delegation.WithExpiration(exp), + delegation.WithMeta("foo", "fooo"), + delegation.WithMeta("bar", "barr"), + ) + require.NoError(t, err) + + data, err := tkn.ToDagJson(didtest.PersonaAlice.PrivKey()) + require.NoError(t, err) + + golden.Assert(t, string(data), "powerline.dagjson") + }) } func TestEncryptedMeta(t *testing.T) { @@ -153,7 +166,7 @@ func TestEncryptedMeta(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, + tkn, err := delegation.Root(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, delegation.WithEncryptedMetaString(tt.key, tt.value, encryptionKey), ) require.NoError(t, err) @@ -191,7 +204,7 @@ func TestEncryptedMeta(t *testing.T) { } // Create token with multiple encrypted values - tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, opts...) + tkn, err := delegation.Root(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, opts...) require.NoError(t, err) data, err := tkn.ToDagCbor(didtest.PersonaAlice.PrivKey()) diff --git a/token/delegation/delegationtest/data/TokenCarolDan_InvalidExpired.dagcbor b/token/delegation/delegationtest/data/TokenCarolDan_InvalidExpired.dagcbor index e6777d3..37f437e 100644 Binary files a/token/delegation/delegationtest/data/TokenCarolDan_InvalidExpired.dagcbor and b/token/delegation/delegationtest/data/TokenCarolDan_InvalidExpired.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenDanErin_InvalidExpired.dagcbor b/token/delegation/delegationtest/data/TokenDanErin_InvalidExpired.dagcbor index b74feda..ace6298 100644 Binary files a/token/delegation/delegationtest/data/TokenDanErin_InvalidExpired.dagcbor and b/token/delegation/delegationtest/data/TokenDanErin_InvalidExpired.dagcbor differ diff --git a/token/delegation/delegationtest/data/TokenErinFrank_InvalidExpired.dagcbor b/token/delegation/delegationtest/data/TokenErinFrank_InvalidExpired.dagcbor index 64b9328..d4aa37d 100644 Binary files a/token/delegation/delegationtest/data/TokenErinFrank_InvalidExpired.dagcbor and b/token/delegation/delegationtest/data/TokenErinFrank_InvalidExpired.dagcbor differ diff --git a/token/delegation/delegationtest/generator/generator.go b/token/delegation/delegationtest/generator/generator.go index 771346f..1f4a4ef 100644 --- a/token/delegation/delegationtest/generator/generator.go +++ b/token/delegation/delegationtest/generator/generator.go @@ -30,10 +30,11 @@ const ( var constantNonce = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b} type newDelegationParams struct { - privKey crypto.PrivKey + privKey crypto.PrivKey // iss aud did.DID cmd command.Command pol policy.Policy + sub did.DID opts []delegation.Option } @@ -89,8 +90,8 @@ func (g *generator) chainPersonas(personas []didtest.Persona, acc acc, vari vari aud: personas[1].DID(), cmd: delegationtest.NominalCommand, pol: policytest.EmptyPolicy, + sub: didtest.PersonaAlice.DID(), opts: []delegation.Option{ - delegation.WithSubject(didtest.PersonaAlice.DID()), delegation.WithNonce(constantNonce), }, } @@ -117,7 +118,7 @@ func (g *generator) chainPersonas(personas []didtest.Persona, acc acc, vari vari p.cmd = delegationtest.AttenuatedCommand }}, {name: "InvalidSubject", variant: func(p *newDelegationParams) { - p.opts = append(p.opts, delegation.WithSubject(didtest.PersonaBob.DID())) + p.sub = didtest.PersonaBob.DID() }}, {name: "InvalidExpired", variant: func(p *newDelegationParams) { // Note: this makes the generator not deterministic @@ -161,7 +162,7 @@ func (g *generator) createDelegation(params newDelegationParams, name string, va return cid.Undef, err } - tkn, err := delegation.New(issDID, params.aud, params.cmd, params.pol, params.opts...) + tkn, err := delegation.New(issDID, params.aud, params.cmd, params.pol, params.sub, params.opts...) if err != nil { return cid.Undef, err } diff --git a/token/delegation/delegationtest/token_gen.go b/token/delegation/delegationtest/token_gen.go index 5983c6a..e5a924e 100644 --- a/token/delegation/delegationtest/token_gen.go +++ b/token/delegation/delegationtest/token_gen.go @@ -107,21 +107,21 @@ var ( ) var ( - TokenCarolDan_InvalidExpiredCID = cid.MustParse("bafyreigvdcarp6426e5aenal3q3c4uyhncwfbmt3fsmb4qzspkpjy57gti") + TokenCarolDan_InvalidExpiredCID = cid.MustParse("bafyreifyzm5jkx2sfu5awyndg3dn5zlg7sq5hssgfatafk62kiiilapnqe") TokenCarolDan_InvalidExpiredSealed = mustGetBundle(TokenCarolDan_InvalidExpiredCID).Sealed TokenCarolDan_InvalidExpiredBundle = mustGetBundle(TokenCarolDan_InvalidExpiredCID) TokenCarolDan_InvalidExpired = mustGetBundle(TokenCarolDan_InvalidExpiredCID).Decoded ) var ( - TokenDanErin_InvalidExpiredCID = cid.MustParse("bafyreifa265mung4nfpjjuiir5nytzgbrhfl66ye2dqmbq4p6dm7bzp4he") + TokenDanErin_InvalidExpiredCID = cid.MustParse("bafyreihhnisabmkofuk3qaw37leijxqjaz5or6v2cufjxwzdkvuvv2dzbq") TokenDanErin_InvalidExpiredSealed = mustGetBundle(TokenDanErin_InvalidExpiredCID).Sealed TokenDanErin_InvalidExpiredBundle = mustGetBundle(TokenDanErin_InvalidExpiredCID) TokenDanErin_InvalidExpired = mustGetBundle(TokenDanErin_InvalidExpiredCID).Decoded ) var ( - TokenErinFrank_InvalidExpiredCID = cid.MustParse("bafyreicde2per7ynjruvstuae67dxzdg2iepid3nonrmgr6tvodzpbqomu") + TokenErinFrank_InvalidExpiredCID = cid.MustParse("bafyreigeokaziviwm5kzmkpwesj3gta5k7zrd62x4a746fnrnkhvatwbna") TokenErinFrank_InvalidExpiredSealed = mustGetBundle(TokenErinFrank_InvalidExpiredCID).Sealed TokenErinFrank_InvalidExpiredBundle = mustGetBundle(TokenErinFrank_InvalidExpiredCID) TokenErinFrank_InvalidExpired = mustGetBundle(TokenErinFrank_InvalidExpiredCID).Decoded diff --git a/token/delegation/examples_test.go b/token/delegation/examples_test.go index 1ea6f55..9848ed9 100644 --- a/token/delegation/examples_test.go +++ b/token/delegation/examples_test.go @@ -41,8 +41,7 @@ func ExampleNew() { )), ) - tkn, err := delegation.New(didtest.PersonaBob.DID(), didtest.PersonaCarol.DID(), cmd, pol, - delegation.WithSubject(didtest.PersonaAlice.DID()), + tkn, err := delegation.New(didtest.PersonaBob.DID(), didtest.PersonaCarol.DID(), cmd, pol, didtest.PersonaAlice.DID(), delegation.WithExpirationIn(time.Hour), delegation.WithNotBeforeIn(time.Minute), delegation.WithMeta("foo", "bar"), diff --git a/token/delegation/options.go b/token/delegation/options.go index 3348760..bd26744 100644 --- a/token/delegation/options.go +++ b/token/delegation/options.go @@ -3,8 +3,6 @@ package delegation import ( "fmt" "time" - - "github.com/ucan-wg/go-ucan/did" ) // Option is a type that allows optional fields to be set during the @@ -85,20 +83,6 @@ func WithNotBeforeIn(nbf time.Duration) Option { } } -// WithSubject sets the Tokens's optional "subject" field to the value of -// provided did.DID. -// -// This Option should only be used with the New constructor - since -// Subject is a required parameter when creating a Token via the Root -// constructor, any value provided via this Option will be silently -// overwritten. -func WithSubject(sub did.DID) Option { - return func(t *Token) error { - t.subject = sub - return nil - } -} - // WithNonce sets the Token's nonce with the given value. // If this option is not used, a random 12-byte nonce is generated for this required field. func WithNonce(nonce []byte) Option { diff --git a/token/delegation/schema_test.go b/token/delegation/schema_test.go index d88c0a1..f0f22dd 100644 --- a/token/delegation/schema_test.go +++ b/token/delegation/schema_test.go @@ -33,9 +33,12 @@ func TestSchemaRoundTrip(t *testing.T) { p1, err := delegation.FromDagJson(delegationJson) require.NoError(t, err) + _, newCID, err := p1.ToSealed(privKey) + require.NoError(t, err) + cborBytes, id, err := p1.ToSealed(privKey) require.NoError(t, err) - assert.Equal(t, newCID, envelope.CIDToBase58BTC(id)) + assert.Equal(t, envelope.CIDToBase58BTC(newCID), envelope.CIDToBase58BTC(id)) p2, c2, err := delegation.FromSealed(cborBytes) require.NoError(t, err) @@ -58,10 +61,13 @@ func TestSchemaRoundTrip(t *testing.T) { p1, err := delegation.FromDagJsonReader(buf) require.NoError(t, err) + _, newCID, err := p1.ToSealed(privKey) + require.NoError(t, err) + cborBytes := &bytes.Buffer{} id, err := p1.ToSealedWriter(cborBytes, privKey) require.NoError(t, err) - assert.Equal(t, newCID, envelope.CIDToBase58BTC(id)) + assert.Equal(t, envelope.CIDToBase58BTC(newCID), envelope.CIDToBase58BTC(id)) // buf = bytes.NewBuffer(cborBytes.Bytes()) p2, c2, err := delegation.FromSealedReader(cborBytes) diff --git a/token/delegation/testdata/new.dagjson b/token/delegation/testdata/new.dagjson index 618c90f..af02131 100644 --- a/token/delegation/testdata/new.dagjson +++ b/token/delegation/testdata/new.dagjson @@ -1 +1 @@ -[{"/":{"bytes":"BBabgnWqd+cjwG1td0w9BudNocmUwoR89RMZTqZHk3osCXEI/bOkko0zTvlusaE4EMBBeSzZDKzjvunLBfdiBg"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/dlg@1.0.0-rc.1":{"aud":"did:key:z6MkvJPmEZZYbgiw1ouT1oouTsTFBHJSts9ophVsNgcRmYxU","cmd":"/foo/bar","exp":7258118400,"iss":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p","meta":{"bar":"barr","foo":"fooo"},"nonce":{"/":{"bytes":"NnJvRGhHaTBraU5yaVFBejdKM2QrYk9lb0kvdGo4RU5pa21RTmJ0am5EMA"}},"pol":[["==",".status","draft"],["all",".reviewer",["like",".email","*@example.com"]],["any",".tags",["or",[["==",".","news"],["==",".","press"]]]]],"sub":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p"}}] \ No newline at end of file +[{"/":{"bytes":"YJsl8EMLnXSFE/nKKjMxz9bHHo+Y7QeLEzukEzW1TB+m53TTiY1aOt+qUO8JaTcOKsOHt/a4Vn+YiOd5CkLdAQ"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/dlg@1.0.0-rc.1":{"aud":"did:key:z6MkvJPmEZZYbgiw1ouT1oouTsTFBHJSts9ophVsNgcRmYxU","cmd":"/foo/bar","exp":7258118400,"iss":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p","meta":{"bar":"barr","foo":"fooo"},"nonce":{"/":{"bytes":"NnJvRGhHaTBraU5yaVFBejdKM2QrYk9lb0kvdGo4RU5pa21RTmJ0am5EMA"}},"pol":[["==",".status","draft"],["all",".reviewer",["like",".email","*@example.com"]],["any",".tags",["or",[["==",".","news"],["==",".","press"]]]]],"sub":"did:key:z6Mkgupchh5HwuHahS7YsyE8bLua1Mr8p2iKNRhyvSvRAs9n"}}] \ No newline at end of file diff --git a/token/delegation/testdata/powerline.dagjson b/token/delegation/testdata/powerline.dagjson new file mode 100644 index 0000000..49e746f --- /dev/null +++ b/token/delegation/testdata/powerline.dagjson @@ -0,0 +1 @@ +[{"/":{"bytes":"i3YkPDvNSU4V8XYEluZhLH0b+NDcW/6+PtPSUHC17cmXXqgelG0K4EzWQQkS9UsYCHfkZSCn9NjGSXYMMFhaAQ"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/dlg@1.0.0-rc.1":{"aud":"did:key:z6MkvJPmEZZYbgiw1ouT1oouTsTFBHJSts9ophVsNgcRmYxU","cmd":"/foo/bar","exp":7258118400,"iss":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p","meta":{"bar":"barr","foo":"fooo"},"nonce":{"/":{"bytes":"NnJvRGhHaTBraU5yaVFBejdKM2QrYk9lb0kvdGo4RU5pa21RTmJ0am5EMA"}},"pol":[["==",".status","draft"],["all",".reviewer",["like",".email","*@example.com"]],["any",".tags",["or",[["==",".","news"],["==",".","press"]]]]]}}] \ No newline at end of file diff --git a/token/invocation/examples_test.go b/token/invocation/examples_test.go index d3255d0..e505a7d 100644 --- a/token/invocation/examples_test.go +++ b/token/invocation/examples_test.go @@ -27,7 +27,7 @@ func ExampleNew() { return } - inv, err := invocation.New(iss, sub, cmd, prf, + inv, err := invocation.New(iss, cmd, sub, prf, invocation.WithArgument("uri", args["uri"]), invocation.WithArgument("headers", args["headers"]), invocation.WithArgument("payload", args["payload"]), diff --git a/token/invocation/invocation.go b/token/invocation/invocation.go index 577f4d8..2535e10 100644 --- a/token/invocation/invocation.go +++ b/token/invocation/invocation.go @@ -8,8 +8,10 @@ package invocation import ( + "encoding/base64" "errors" "fmt" + "strings" "time" "github.com/ipfs/go-cid" @@ -67,7 +69,9 @@ type Token struct { // // With the exception of the WithMeta option, all others will overwrite // the previous contents of their target field. -func New(iss, sub did.DID, cmd command.Command, prf []cid.Cid, opts ...Option) (*Token, error) { +// +// You can read it as "(Issuer - I) executes (command) on (subject)". +func New(iss did.DID, cmd command.Command, sub did.DID, prf []cid.Cid, opts ...Option) (*Token, error) { iat := time.Now() tkn := Token{ @@ -212,6 +216,24 @@ func (t *Token) IsValidAt(ti time.Time) bool { return true } +func (t *Token) String() string { + var res strings.Builder + + res.WriteString(fmt.Sprintf("Issuer: %s\n", t.Issuer())) + res.WriteString(fmt.Sprintf("Audience: %s\n", t.Audience())) + res.WriteString(fmt.Sprintf("Subject: %v\n", t.Subject())) + res.WriteString(fmt.Sprintf("Command: %s\n", t.Command())) + res.WriteString(fmt.Sprintf("Args: %s\n", t.Arguments())) + res.WriteString(fmt.Sprintf("Proof: %v\n", t.Proof())) + res.WriteString(fmt.Sprintf("Nonce: %s\n", base64.StdEncoding.EncodeToString(t.Nonce()))) + res.WriteString(fmt.Sprintf("Meta: %s\n", t.Meta())) + res.WriteString(fmt.Sprintf("Expiration: %v\n", t.Expiration())) + res.WriteString(fmt.Sprintf("Invoked At: %v\n", t.InvokedAt())) + res.WriteString(fmt.Sprintf("Cause: %v", t.Cause())) + + return res.String() +} + func (t *Token) validate() error { var errs error diff --git a/token/invocation/invocation_test.go b/token/invocation/invocation_test.go index a8d2455..16ec980 100644 --- a/token/invocation/invocation_test.go +++ b/token/invocation/invocation_test.go @@ -129,7 +129,7 @@ func TestToken_ExecutionAllowed(t *testing.T) { testFails(t, invocation.ErrWrongSub, didtest.PersonaFrank, delegationtest.ExpandedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_InvalidSubject) }) - t.Run("passes - arguments satify example policy", func(t *testing.T) { + t.Run("passes - arguments satisfy example policy", func(t *testing.T) { t.Parallel() testFails(t, invocation.ErrPolicyNotSatisfied, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.SpecInvalidArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy) @@ -142,7 +142,7 @@ func test(t *testing.T, persona didtest.Persona, cmd command.Command, args *args opts = append(opts, invocation.WithArguments(args)) - tkn, err := invocation.New(persona.DID(), didtest.PersonaAlice.DID(), cmd, prf, opts...) + tkn, err := invocation.New(persona.DID(), cmd, didtest.PersonaAlice.DID(), prf, opts...) require.NoError(t, err) return tkn.ExecutionAllowed(delegationtest.GetDelegationLoader()) diff --git a/token/invocation/ipld_test.go b/token/invocation/ipld_test.go index 9754c16..4e822e3 100644 --- a/token/invocation/ipld_test.go +++ b/token/invocation/ipld_test.go @@ -16,7 +16,7 @@ func TestSealUnsealRoundtrip(t *testing.T) { privKey, iss, sub, cmd, args, prf, meta, err := setupExampleNew() require.NoError(t, err) - tkn1, err := invocation.New(iss, sub, cmd, prf, + tkn1, err := invocation.New(iss, cmd, sub, prf, invocation.WithArgument("uri", args["uri"]), invocation.WithArgument("headers", args["headers"]), invocation.WithArgument("payload", args["payload"]), diff --git a/token/invocation/options.go b/token/invocation/options.go index 1715383..dcf7fc8 100644 --- a/token/invocation/options.go +++ b/token/invocation/options.go @@ -38,11 +38,15 @@ func WithArguments(args *args.Args) Option { // WithAudience sets the Token's audience to the provided did.DID. // +// This can be used if the resource on which the token operates on is different +// from the subject. In that situation, the subject is akin to the "service" and +// the audience is akin to the resource. +// // If the provided did.DID is the same as the Token's subject, the // audience is not set. func WithAudience(aud did.DID) Option { return func(t *Token) error { - if t.subject.String() != aud.String() { + if t.subject != aud { t.audience = aud } diff --git a/token/invocation/proof.go b/token/invocation/proof.go index 0d130a2..fe3e8ac 100644 --- a/token/invocation/proof.go +++ b/token/invocation/proof.go @@ -40,7 +40,7 @@ import ( // c. The first proof must be issued to the Invoker (audience DID). // d. The Issuer of each delegation must be the Audience in the next one. // e. The last token must be a root delegation. -// f. The Subject of each delegation must equal the invocation's Audience field. +// f. The Subject of each delegation must equal the invocation's Subject (or Audience if defined) // g. The command of each delegation must "allow" the one before it. // // 5. If steps 1-4 pass: @@ -58,18 +58,18 @@ func (t *Token) verifyProofs(delegations []*delegation.Token) error { cmd := t.command iss := t.issuer - aud := t.audience - if !aud.Defined() { - aud = t.subject + sub := t.subject + if t.audience.Defined() { + sub = t.audience } // control from the invocation to the root for i, dlgCid := range t.proof { dlg := delegations[i] - // The Subject of each delegation must equal the invocation's Audience field. - 4f - if dlg.Subject() != aud { - return fmt.Errorf("%w: delegation %s, expected %s, got %s", ErrWrongSub, dlgCid, aud, dlg.Subject()) + // The Subject of each delegation must equal the invocation's Subject (or Audience if defined). - 4f + if dlg.Subject() != sub { + return fmt.Errorf("%w: delegation %s, expected %s, got %s", ErrWrongSub, dlgCid, sub, dlg.Subject()) } // The first proof must be issued to the Invoker (audience DID). - 4c