feat(invocation): provide New constructor and encoding to wire-format

This commit is contained in:
Steve Moyer
2024-10-24 10:44:38 -04:00
parent 884d63a689
commit 31d16ac468
5 changed files with 295 additions and 52 deletions

View File

@@ -13,6 +13,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ucan-wg/go-ucan/did" "github.com/ucan-wg/go-ucan/did"
"github.com/ucan-wg/go-ucan/pkg/command" "github.com/ucan-wg/go-ucan/pkg/command"
"github.com/ucan-wg/go-ucan/pkg/meta" "github.com/ucan-wg/go-ucan/pkg/meta"
@@ -20,25 +22,70 @@ import (
// Token is an immutable type that holds the fields of a UCAN invocation. // Token is an immutable type that holds the fields of a UCAN invocation.
type Token struct { type Token struct {
// Issuer DID (invoker) // The DID of the Invoker
issuer did.DID issuer did.DID
// Audience DID (receiver/executor) // The DID of Subject being invoked
audience did.DID
// Subject DID (subject being invoked)
subject did.DID subject did.DID
// The Command to invoke // The DID of the intended Executor if different from the Subject
audience did.DID
// The Command
command command.Command command command.Command
// TODO: args // The Command's Arguments
// TODO: prf arguments map[string]datamodel.Node
// A unique, random nonce // Delegations that prove the chain of authority
nonce []byte proof []cid.Cid
// Arbitrary Metadata // Arbitrary Metadata
meta *meta.Meta meta *meta.Meta
// A unique, random nonce
nonce []byte
// The timestamp at which the Invocation becomes invalid // The timestamp at which the Invocation becomes invalid
expiration *time.Time expiration *time.Time
// The timestamp at which the Invocation was created // The timestamp at which the Invocation was created
invokedAt *time.Time invokedAt *time.Time
// TODO: cause
// An optional CID of the Receipt that enqueued the Task
cause *cid.Cid
}
// New creates an invocation Token with the provided options.
//
// If no nonce is provided, a random 12-byte nonce is generated. Use the
// WithNonce or WithEmptyNonce options to specify provide your own nonce
// or to leave the nonce empty respectively.
//
// If no invokedAt is provided, the current time is used. Use the
// WithInvokedAt or WithInvokedAtIn options to specify a different time.
//
// With the exception of the WithMeta option, all other 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) {
nonce := make([]byte, 12)
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
iat := time.Now()
tkn := Token{
issuer: iss,
subject: sub,
command: cmd,
proof: prf,
nonce: nonce,
invokedAt: &iat,
}
for _, opt := range opts {
if err := opt(&tkn); err != nil {
return nil, err
}
}
return &tkn, nil
} }
// Issuer returns the did.DID representing the Token's issuer. // Issuer returns the did.DID representing the Token's issuer.
@@ -46,28 +93,27 @@ func (t *Token) Issuer() did.DID {
return t.issuer return t.issuer
} }
// Subject returns the did.DID representing the Token's subject.
func (t *Token) Subject() did.DID {
return t.subject
}
// Audience returns the did.DID representing the Token's audience. // Audience returns the did.DID representing the Token's audience.
func (t *Token) Audience() did.DID { func (t *Token) Audience() did.DID {
return t.audience return t.audience
} }
// Subject returns the did.DID representing the Token's subject.
//
// This field may be did.Undef for delegations that are [Powerlined] but
// must be equal to the value returned by the Issuer method for root
// tokens.
func (t *Token) Subject() did.DID {
return t.subject
}
// Command returns the capability's command.Command. // Command returns the capability's command.Command.
func (t *Token) Command() command.Command { func (t *Token) Command() command.Command {
return t.command return t.command
} }
// Nonce returns the random Nonce encapsulated in this Token. func (t *Token) Arguments() map[string]datamodel.Node {
func (t *Token) Nonce() []byte { return t.arguments
return t.nonce }
func (t *Token) Proof() []cid.Cid {
return t.proof
} }
// Meta returns the Token's metadata. // Meta returns the Token's metadata.
@@ -75,11 +121,24 @@ func (t *Token) Meta() meta.ReadOnly {
return t.meta.ReadOnly() return t.meta.ReadOnly()
} }
// Nonce returns the random Nonce encapsulated in this Token.
func (t *Token) Nonce() []byte {
return t.nonce
}
// Expiration returns the time at which the Token expires. // Expiration returns the time at which the Token expires.
func (t *Token) Expiration() *time.Time { func (t *Token) Expiration() *time.Time {
return t.expiration return t.expiration
} }
func (t *Token) InvokedAt() *time.Time {
return t.invokedAt
}
func (t *Token) Cause() *cid.Cid {
return t.cause
}
func (t *Token) validate() error { func (t *Token) validate() error {
var errs error var errs error

View File

@@ -1,23 +1,32 @@
type DID string type DID string
# The Invocation Payload attaches sender, receiver, and provenance to the Task. # The Invocation Payload attaches sender, receiver, and provenance to the Task.
type Payload struct { type Payload struct {
# Issuer DID (sender) # The DID of the invoker
iss DID iss DID
# Audience DID (receiver) # The Subject being invoked
aud DID sub DID
# Principal that the chain is about (the Subject) # The DID of the intended Executor if different from the Subject
sub optional DID aud optional DID
# The Command to eventually invoke # The Command
cmd String cmd String
# The Command's Arguments
# A unique, random nonce args { String : Any}
nonce Bytes # Delegations that prove the chain of authority
prf [ Link ]
# Arbitrary Metadata # Arbitrary Metadata
meta {String : Any} meta optional { String : Any }
# A unique, random nonce
nonce optional Bytes
# The timestamp at which the Invocation becomes invalid # The timestamp at which the Invocation becomes invalid
exp nullable Int exp nullable Int
# The Timestamp at which the Invocation was created
iat optional Int
# An optional CID of the Receipt that enqueued the Task
cause optional Link
} }

View File

@@ -193,29 +193,58 @@ func FromIPLD(node datamodel.Node) (*Token, error) {
} }
func (t *Token) toIPLD(privKey crypto.PrivKey) (datamodel.Node, error) { func (t *Token) toIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
var sub *string var aud *string
if t.subject != did.Undef { if t.audience != did.Undef {
s := t.subject.String() a := t.audience.String()
sub = &s aud = &a
} }
// TODO
var exp *int64 var exp *int64
if t.expiration != nil { if t.expiration != nil {
u := t.expiration.Unix() u := t.expiration.Unix()
exp = &u exp = &u
} }
var iat *int64
if t.invokedAt != nil {
i := t.invokedAt.Unix()
iat = &i
}
argsKey := make([]string, len(t.arguments))
i := 0
for k := range t.arguments {
argsKey[i] = k
i++
}
args := struct {
Keys []string
Values map[string]datamodel.Node
}{
Keys: argsKey,
Values: t.arguments,
}
prf := make([]cid.Cid, len(t.proof))
for i, c := range t.proof {
prf[i] = c
}
model := &tokenPayloadModel{ model := &tokenPayloadModel{
Iss: t.issuer.String(), Iss: t.issuer.String(),
Aud: t.audience.String(), Aud: aud,
Sub: sub, Sub: t.subject.String(),
Cmd: t.command.String(), Cmd: t.command.String(),
Args: args,
Prf: prf,
Meta: t.meta,
Nonce: t.nonce, Nonce: t.nonce,
Meta: *t.meta,
Exp: exp, Exp: exp,
Iat: iat,
Cause: t.cause,
} }
return envelope.ToIPLD(privKey, model) return envelope.ToIPLD(privKey, model)

134
token/invocation/options.go Normal file
View File

@@ -0,0 +1,134 @@
package invocation
import (
"time"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ucan-wg/go-ucan/did"
)
// Option is a type that allows optional fields to be set during the
// creation of a Token.
type Option func(*Token) error
// WithArgument adds a key/value pair to the Token's Arguments field.
func WithArgument(key string, val datamodel.Node) Option {
return func(t *Token) error {
t.arguments[key] = val
return nil
}
}
// WithArguments sets the Token's Arguments field to the provided map.
//
// Note that this will overwrite any existing Arguments whether provided
// by a previous call to this function or by one or more calls to
// WithArgument.
func WithArguments(args map[string]datamodel.Node) Option {
return func(t *Token) error {
t.arguments = args
return nil
}
}
// WithAudience sets the Token's audience to the provided did.DID.
//
// 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() {
t.audience = aud
}
return nil
}
}
// WithMeta adds a key/value pair in the "meta" field.
//
// WithMeta can be used multiple times in the same call.
// Accepted types for the value are: bool, string, int, int32, int64, []byte,
// and ipld.Node.
func WithMeta(key string, val any) Option {
return func(t *Token) error {
return t.meta.Add(key, val)
}
}
// 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 require.
func WithNonce(nonce []byte) Option {
return func(t *Token) error {
t.nonce = nonce
return nil
}
}
// WithEmptyNonce sets the Token's nonce to an empty byte slice as
// suggested by the UCAN spec for invocation tokens that represent
// idem
func WithEmptyNonce() Option {
return func(t *Token) error {
t.nonce = []byte{}
return nil
}
}
// WithExpiration set's the Token's optional "expiration" field to the
// value of the provided time.Time.
func WithExpiration(exp time.Time) Option {
return func(t *Token) error {
t.expiration = &exp
return nil
}
}
// WithExpirationIn set's the Token's optional "expiration" field to
// Now() plus the given duration.
func WithExpirationIn(exp time.Duration) Option {
return func(t *Token) error {
expTime := time.Now().Add(exp)
t.expiration = &expTime
return nil
}
}
// WithInvokedAt sets the Token's invokedAt field to the provided
// time.Time.
func WithInvokedAt(iat time.Time) Option {
return func(t *Token) error {
t.invokedAt = &iat
return nil
}
}
// WithInvokedAtIn sets the Token's invokedAt field to Now() plus the
// given duration.
func WithInvokedAtIn(after time.Duration) Option {
return func(t *Token) error {
iat := time.Now().Add(after)
t.invokedAt = &iat
return nil
}
}
// WithCause sets the Token's cause field to the provided cid.Cid.
func WithCause(cause *cid.Cid) Option {
return func(t *Token) error {
t.cause = cause
return nil
}
}

View File

@@ -5,7 +5,9 @@ import (
"fmt" "fmt"
"sync" "sync"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime" "github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/bindnode" "github.com/ipld/go-ipld-prime/node/bindnode"
"github.com/ipld/go-ipld-prime/schema" "github.com/ipld/go-ipld-prime/schema"
@@ -46,26 +48,36 @@ var _ envelope.Tokener = (*tokenPayloadModel)(nil)
// TODO // TODO
type tokenPayloadModel struct { type tokenPayloadModel struct {
// Issuer DID (sender) // The DID of the Invoker
Iss string Iss string
// Audience DID (receiver) // The DID of Subject being invoked
Aud string Sub string
// Principal that the chain is about (the Subject) // The DID of the intended Executor if different from the Subject
// optional: can be nil Aud *string
Sub *string
// The Command to eventually invoke // The Command
Cmd string Cmd string
// The Command's Arguments
Args struct {
Keys []string
Values map[string]datamodel.Node
}
// Delegations that prove the chain of authority
Prf []cid.Cid
// Arbitrary Metadata
Meta *meta.Meta
// A unique, random nonce // A unique, random nonce
Nonce []byte Nonce []byte
// Arbitrary Metadata
Meta meta.Meta
// The timestamp at which the Invocation becomes invalid // The timestamp at which the Invocation becomes invalid
// optional: can be nil // optional: can be nil
Exp *int64 Exp *int64
// The timestamp at which the Invocation was created
Iat *int64
// An optional CID of the Receipt that enqueued the Task
Cause *cid.Cid
} }
func (e *tokenPayloadModel) Prototype() schema.TypedPrototype { func (e *tokenPayloadModel) Prototype() schema.TypedPrototype {