(WIP) refine the token constructors:
- for invocation, reorder the parameters for a more "natural language" mental model - for delegation, make "subject" a required parameter to avoid make powerline by mistake - for delegation, implement powerline
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -44,16 +44,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 +80,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.
|
||||
|
||||
@@ -75,9 +75,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.PersonaAlice.DID(),
|
||||
delegation.WithNonce([]byte(nonce)),
|
||||
delegation.WithSubject(didtest.PersonaAlice.DID()),
|
||||
delegation.WithExpiration(exp),
|
||||
delegation.WithMeta("foo", "fooo"),
|
||||
delegation.WithMeta("bar", "barr"),
|
||||
@@ -106,6 +105,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 +169,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 +207,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())
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"]),
|
||||
|
||||
@@ -67,7 +67,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{
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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"]),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user