Files
ucan/token/invocation/invocation.go

283 lines
7.4 KiB
Go
Raw Normal View History

2024-10-15 15:41:14 +02:00
// Package invocation implements the UCAN [invocation] specification with
2024-10-02 10:53:30 +02:00
// an immutable Token type as well as methods to convert the Token to and
// from the [envelope]-enclosed, signed and DAG-CBOR-encoded form that
// should most commonly be used for transport and storage.
//
// [envelope]: https://github.com/ucan-wg/spec#envelope
// [invocation]: https://github.com/ucan-wg/invocation
package invocation
import (
"errors"
"fmt"
"time"
"github.com/ipfs/go-cid"
2024-11-05 17:39:39 +01:00
2024-10-02 10:53:30 +02:00
"github.com/ucan-wg/go-ucan/did"
"github.com/ucan-wg/go-ucan/pkg/args"
2024-10-02 10:53:30 +02:00
"github.com/ucan-wg/go-ucan/pkg/command"
"github.com/ucan-wg/go-ucan/pkg/meta"
2024-10-18 10:48:47 +02:00
"github.com/ucan-wg/go-ucan/token/delegation"
"github.com/ucan-wg/go-ucan/token/internal/nonce"
2024-11-05 17:39:39 +01:00
"github.com/ucan-wg/go-ucan/token/internal/parse"
2024-10-02 10:53:30 +02:00
)
// Token is an immutable type that holds the fields of a UCAN invocation.
type Token struct {
// The DID of the Invoker
2024-10-02 10:53:30 +02:00
issuer did.DID
// The DID of Subject being invoked
2024-10-02 10:53:30 +02:00
subject did.DID
// The DID of the intended Executor if different from the Subject
audience did.DID
// The Command
2024-10-02 10:53:30 +02:00
command command.Command
2024-10-18 10:48:47 +02:00
// The Command's arguments
arguments *args.Args
2024-10-18 10:48:47 +02:00
// CIDs of the delegation.Token that prove the chain of authority
2024-11-13 14:50:59 +01:00
// They need to form a strictly linear chain, and being ordered starting from the
// leaf Delegation (with aud matching the invocation's iss), in a strict sequence
// where the iss of the previous Delegation matches the aud of the next Delegation.
proof []cid.Cid
2024-10-02 10:53:30 +02:00
// Arbitrary Metadata
meta *meta.Meta
// A unique, random nonce
nonce []byte
2024-10-02 10:53:30 +02:00
// The timestamp at which the Invocation becomes invalid
expiration *time.Time
// The timestamp at which the Invocation was created
invokedAt *time.Time
// 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
// or the WithoutInvokedAt Option to clear the Token's invokedAt field.
//
2024-11-05 17:39:39 +01:00
// 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) {
iat := time.Now()
tkn := Token{
issuer: iss,
subject: sub,
command: cmd,
arguments: args.New(),
proof: prf,
2024-11-05 17:39:39 +01:00
meta: meta.NewMeta(),
nonce: nil,
invokedAt: &iat,
}
for _, opt := range opts {
if err := opt(&tkn); err != nil {
return nil, err
}
}
var err error
2024-11-05 17:39:39 +01:00
if len(tkn.nonce) == 0 {
tkn.nonce, err = nonce.Generate()
2024-11-05 17:39:39 +01:00
if err != nil {
return nil, err
}
}
if err := tkn.validate(); err != nil {
return nil, err
}
return &tkn, nil
2024-10-02 10:53:30 +02:00
}
func (t *Token) ExecutionAllowed(loader DelegationLoader) (bool, error) {
2024-10-18 10:48:47 +02:00
return t.executionAllowed(loader, t.arguments)
}
func (t *Token) ExecutionAllowedWithArgsHook(loader DelegationLoader, hook func(*args.Args) *args.Args) (bool, error) {
2024-10-18 10:48:47 +02:00
return t.executionAllowed(loader, hook(t.arguments))
}
func (t *Token) executionAllowed(loader DelegationLoader, arguments *args.Args) (bool, error) {
delegations, err := t.loadProofs(loader)
if err != nil {
// All referenced delegations must be available - 4b
return false, err
}
if err := t.verifyProofs(delegations); err != nil {
return false, err
}
if err := t.verifyTimeBound(delegations); err != nil {
return false, err
}
if err := t.verifyArgs(delegations, arguments); err != nil {
return false, err
}
return true, nil
2024-10-18 10:48:47 +02:00
}
2024-10-02 10:53:30 +02:00
// Issuer returns the did.DID representing the Token's issuer.
func (t *Token) Issuer() did.DID {
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.
func (t *Token) Audience() did.DID {
return t.audience
}
2024-10-02 10:53:30 +02:00
// Command returns the capability's command.Command.
func (t *Token) Command() command.Command {
return t.command
}
// Arguments returns the arguments to be used when the command is
// invoked.
func (t *Token) Arguments() *args.Args {
return t.arguments
}
2024-11-05 17:39:39 +01:00
// Proof() returns the ordered list of cid.Cid which reference the
// delegation Tokens that authorize this invocation.
func (t *Token) Proof() []cid.Cid {
return t.proof
2024-10-02 10:53:30 +02:00
}
// Meta returns the Token's metadata.
func (t *Token) Meta() meta.ReadOnly {
return t.meta.ReadOnly()
2024-10-02 10:53:30 +02:00
}
// Nonce returns the random Nonce encapsulated in this Token.
func (t *Token) Nonce() []byte {
return t.nonce
}
2024-10-02 10:53:30 +02:00
// Expiration returns the time at which the Token expires.
func (t *Token) Expiration() *time.Time {
return t.expiration
}
// InvokedAt returns the time.Time at which the invocation token was
// created.
func (t *Token) InvokedAt() *time.Time {
return t.invokedAt
}
// Cause returns the Token's (optional) cause field which may specify
// which describes the Receipt that requested the invocation.
func (t *Token) Cause() *cid.Cid {
return t.cause
}
2024-10-18 10:48:47 +02:00
// IsValidNow verifies that the token can be used at the current time, based on expiration or "not before" fields.
// This does NOT do any other kind of verifications.
func (t *Token) IsValidNow() bool {
return t.IsValidAt(time.Now())
}
// IsValidNow verifies that the token can be used at the given time, based on expiration or "not before" fields.
// This does NOT do any other kind of verifications.
func (t *Token) IsValidAt(ti time.Time) bool {
if t.expiration != nil && ti.After(*t.expiration) {
2024-10-18 10:48:47 +02:00
return false
}
return true
}
2024-10-02 10:53:30 +02:00
func (t *Token) validate() error {
var errs error
requiredDID := func(id did.DID, fieldname string) {
if !id.Defined() {
errs = errors.Join(errs, fmt.Errorf(`a valid did is required for %s: %s`, fieldname, id.String()))
}
}
requiredDID(t.issuer, "Issuer")
2024-11-05 17:39:39 +01:00
requiredDID(t.subject, "Subject")
2024-10-02 10:53:30 +02:00
if len(t.nonce) < 12 {
errs = errors.Join(errs, fmt.Errorf("token nonce too small"))
}
return errs
}
func (t *Token) loadProofs(loader DelegationLoader) (res []*delegation.Token, err error) {
res = make([]*delegation.Token, len(t.proof))
for i, c := range t.proof {
res[i], err = loader.GetDelegation(c)
if err != nil {
return nil, fmt.Errorf("%w: need %s", ErrMissingDelegation, c)
}
}
return res, nil
}
2024-10-02 10:53:30 +02:00
// tokenFromModel build a decoded view of the raw IPLD data.
// This function also serves as validation.
func tokenFromModel(m tokenPayloadModel) (*Token, error) {
var (
tkn Token
err error
2024-10-02 10:53:30 +02:00
)
if tkn.issuer, err = did.Parse(m.Iss); err != nil {
2024-11-05 17:39:39 +01:00
return nil, fmt.Errorf("parse iss: %w", err)
}
if tkn.subject, err = did.Parse(m.Sub); err != nil {
2024-11-05 17:39:39 +01:00
return nil, fmt.Errorf("parse subject: %w", err)
}
2024-11-05 17:39:39 +01:00
if tkn.audience, err = parse.OptionalDID(m.Aud); err != nil {
return nil, fmt.Errorf("parse audience: %w", err)
}
if tkn.command, err = command.Parse(m.Cmd); err != nil {
2024-11-05 17:39:39 +01:00
return nil, fmt.Errorf("parse command: %w", err)
}
if len(m.Nonce) == 0 {
return nil, fmt.Errorf("nonce is required")
}
2024-11-05 17:39:39 +01:00
tkn.nonce = m.Nonce
tkn.arguments = m.Args
tkn.proof = m.Prf
tkn.meta = m.Meta
2024-11-05 17:39:39 +01:00
tkn.expiration = parse.OptionalTimestamp(m.Exp)
tkn.invokedAt = parse.OptionalTimestamp(m.Iat)
2024-11-05 17:39:39 +01:00
tkn.cause = m.Cause
2024-11-05 17:39:39 +01:00
if err := tkn.validate(); err != nil {
return nil, err
}
2024-10-02 10:53:30 +02:00
return &tkn, nil
}