invocation: round of cleanups/fixes
This commit is contained in:
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
"github.com/ucan-wg/go-ucan/token/internal/parse"
|
||||
)
|
||||
|
||||
// Token is an immutable type that holds the fields of a UCAN delegation.
|
||||
@@ -184,27 +185,19 @@ func tokenFromModel(m tokenPayloadModel) (*Token, error) {
|
||||
return nil, fmt.Errorf("parse iss: %w", err)
|
||||
}
|
||||
|
||||
tkn.audience, err = did.Parse(m.Aud)
|
||||
if err != nil {
|
||||
if tkn.audience, err = did.Parse(m.Aud); err != nil {
|
||||
return nil, fmt.Errorf("parse audience: %w", err)
|
||||
}
|
||||
|
||||
if m.Sub != nil {
|
||||
tkn.subject, err = did.Parse(*m.Sub)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse subject: %w", err)
|
||||
}
|
||||
} else {
|
||||
tkn.subject = did.Undef
|
||||
if tkn.subject, err = parse.OptionalDID(m.Sub); err != nil {
|
||||
return nil, fmt.Errorf("parse subject: %w", err)
|
||||
}
|
||||
|
||||
tkn.command, err = command.Parse(m.Cmd)
|
||||
if err != nil {
|
||||
if tkn.command, err = command.Parse(m.Cmd); err != nil {
|
||||
return nil, fmt.Errorf("parse command: %w", err)
|
||||
}
|
||||
|
||||
tkn.policy, err = policy.FromIPLD(m.Pol)
|
||||
if err != nil {
|
||||
if tkn.policy, err = policy.FromIPLD(m.Pol); err != nil {
|
||||
return nil, fmt.Errorf("parse policy: %w", err)
|
||||
}
|
||||
|
||||
@@ -215,15 +208,8 @@ func tokenFromModel(m tokenPayloadModel) (*Token, error) {
|
||||
|
||||
tkn.meta = &m.Meta
|
||||
|
||||
if m.Nbf != nil {
|
||||
t := time.Unix(*m.Nbf, 0)
|
||||
tkn.notBefore = &t
|
||||
}
|
||||
|
||||
if m.Exp != nil {
|
||||
t := time.Unix(*m.Exp, 0)
|
||||
tkn.expiration = &t
|
||||
}
|
||||
tkn.notBefore = parse.OptionalTimestamp(m.Nbf)
|
||||
tkn.expiration = parse.OptionalTimestamp(m.Exp)
|
||||
|
||||
if err := tkn.validate(); err != nil {
|
||||
return nil, err
|
||||
|
||||
22
token/internal/parse/parse.go
Normal file
22
token/internal/parse/parse.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
)
|
||||
|
||||
func OptionalDID(s *string) (did.DID, error) {
|
||||
if s == nil {
|
||||
return did.Undef, nil
|
||||
}
|
||||
return did.Parse(*s)
|
||||
}
|
||||
|
||||
func OptionalTimestamp(sec *int64) *time.Time {
|
||||
if sec == nil {
|
||||
return nil
|
||||
}
|
||||
t := time.Unix(*sec, 0)
|
||||
return &t
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/ipld/go-ipld-prime/fluent/qp"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/token/invocation"
|
||||
@@ -28,11 +29,7 @@ func ExampleNew() {
|
||||
return
|
||||
}
|
||||
|
||||
inv, err := invocation.New(
|
||||
iss,
|
||||
sub,
|
||||
cmd,
|
||||
prf,
|
||||
inv, err := invocation.New(iss, sub, cmd, prf,
|
||||
invocation.WithArguments(args),
|
||||
invocation.WithMeta("env", "development"),
|
||||
invocation.WithMeta("tags", meta["tags"]),
|
||||
|
||||
@@ -15,9 +15,11 @@ import (
|
||||
|
||||
"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/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||
"github.com/ucan-wg/go-ucan/token/internal/parse"
|
||||
)
|
||||
|
||||
// Token is an immutable type that holds the fields of a UCAN invocation.
|
||||
@@ -60,24 +62,18 @@ type Token struct {
|
||||
// WithInvokedAt or WithInvokedAtIn Options to specify a different time
|
||||
// or the WithoutInvokedAt Option to clear the Token's invokedAt field.
|
||||
//
|
||||
// With the exception of the WithMeta option, all other will overwrite
|
||||
// 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) {
|
||||
nonce, err := generateNonce()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iat := time.Now()
|
||||
metadata := meta.NewMeta()
|
||||
|
||||
tkn := Token{
|
||||
issuer: iss,
|
||||
subject: sub,
|
||||
command: cmd,
|
||||
proof: prf,
|
||||
meta: metadata,
|
||||
nonce: nonce,
|
||||
meta: meta.NewMeta(),
|
||||
nonce: nil,
|
||||
invokedAt: &iat,
|
||||
}
|
||||
|
||||
@@ -87,8 +83,15 @@ func New(iss, sub did.DID, cmd command.Command, prf []cid.Cid, opts ...Option) (
|
||||
}
|
||||
}
|
||||
|
||||
if len(tkn.meta.Keys) == 0 {
|
||||
tkn.meta = nil
|
||||
if len(tkn.nonce) == 0 {
|
||||
tkn.nonce, err = generateNonce()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := tkn.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tkn, nil
|
||||
@@ -120,7 +123,7 @@ func (t *Token) Arguments() map[string]datamodel.Node {
|
||||
return t.arguments
|
||||
}
|
||||
|
||||
// Proof() returns the ordered list of cid.Cids which referenced the
|
||||
// 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
|
||||
@@ -163,8 +166,7 @@ func (t *Token) validate() error {
|
||||
}
|
||||
|
||||
requiredDID(t.issuer, "Issuer")
|
||||
|
||||
// TODO
|
||||
requiredDID(t.subject, "Subject")
|
||||
|
||||
if len(t.nonce) < 12 {
|
||||
errs = errors.Join(errs, fmt.Errorf("token nonce too small"))
|
||||
@@ -182,35 +184,36 @@ func tokenFromModel(m tokenPayloadModel) (*Token, error) {
|
||||
)
|
||||
|
||||
if tkn.issuer, err = did.Parse(m.Iss); err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("parse iss: %w", err)
|
||||
}
|
||||
|
||||
if tkn.subject, err = did.Parse(m.Sub); err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("parse subject: %w", err)
|
||||
}
|
||||
|
||||
if tkn.audience, err = parseOptionalDID(m.Aud); err != nil {
|
||||
return nil, err
|
||||
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 {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("parse command: %w", err)
|
||||
}
|
||||
|
||||
if len(m.Nonce) == 0 {
|
||||
return nil, fmt.Errorf("nonce is required")
|
||||
}
|
||||
tkn.nonce = m.Nonce
|
||||
|
||||
tkn.arguments = m.Args.Values
|
||||
tkn.proof = m.Prf
|
||||
tkn.meta = m.Meta
|
||||
tkn.nonce = m.Nonce
|
||||
|
||||
if tkn.expiration, err = parseOptionalTimestamp(m.Exp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tkn.expiration = parse.OptionalTimestamp(m.Exp)
|
||||
tkn.invokedAt = parse.OptionalTimestamp(m.Iat)
|
||||
|
||||
if tkn.invokedAt, err = parseOptionalTimestamp(m.Iat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tkn.cause = m.Cause
|
||||
|
||||
if tkn.cause, err = parseOptionalCID(m.Cause); err != nil {
|
||||
if err := tkn.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -227,29 +230,3 @@ func generateNonce() ([]byte, error) {
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func parseOptionalCID(c *cid.Cid) (*cid.Cid, error) {
|
||||
if c == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func parseOptionalDID(s *string) (did.DID, error) {
|
||||
if s == nil {
|
||||
return did.Undef, nil
|
||||
}
|
||||
|
||||
return did.Parse(*s)
|
||||
}
|
||||
|
||||
func parseOptionalTimestamp(sec *int64) (*time.Time, error) {
|
||||
if sec == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
t := time.Unix(*sec, 0)
|
||||
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
@@ -218,8 +218,9 @@ func (t *Token) toIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
|
||||
}
|
||||
|
||||
argsKey := make([]string, len(t.arguments))
|
||||
i := 0
|
||||
|
||||
// TODO: make specialized type and builder?
|
||||
i := 0
|
||||
for k := range t.arguments {
|
||||
argsKey[i] = k
|
||||
i++
|
||||
@@ -230,6 +231,7 @@ func (t *Token) toIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
|
||||
Values: t.arguments,
|
||||
}
|
||||
|
||||
// TODO: reuse instead of copy? it's immutable
|
||||
prf := make([]cid.Cid, len(t.proof))
|
||||
for i, c := range t.proof {
|
||||
prf[i] = c
|
||||
@@ -249,5 +251,10 @@ func (t *Token) toIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
|
||||
Cause: t.cause,
|
||||
}
|
||||
|
||||
// seems like it's a requirement to have a null meta if there are no values?
|
||||
if len(model.Meta.Keys) == 0 {
|
||||
model.Meta = nil
|
||||
}
|
||||
|
||||
return envelope.ToIPLD(privKey, model)
|
||||
}
|
||||
|
||||
@@ -4,13 +4,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/token/invocation"
|
||||
)
|
||||
|
||||
@@ -20,16 +16,13 @@ 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, sub, cmd, prf,
|
||||
invocation.WithArguments(args),
|
||||
invocation.WithMeta("env", "development"),
|
||||
invocation.WithMeta("tags", meta["tags"]),
|
||||
invocation.WithExpirationIn(time.Minute),
|
||||
invocation.WithoutInvokedAt())
|
||||
invocation.WithoutInvokedAt(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, cid1, err := tkn1.ToSealed(privKey)
|
||||
@@ -41,10 +34,3 @@ func TestSealUnsealRoundtrip(t *testing.T) {
|
||||
assert.Equal(t, cid1, cid2)
|
||||
assert.Equal(t, tkn1, tkn2)
|
||||
}
|
||||
|
||||
func setupNew(t *testing.T) (privKey crypto.PrivKey, iss, sub did.DID, cmd command.Command, args map[string]datamodel.Node, prf []cid.Cid, meta map[string]datamodel.Node) {
|
||||
privKey, iss, sub, cmd, args, prf, meta, err := setupExampleNew()
|
||||
require.NoError(t, err)
|
||||
|
||||
return // WARNING: named return values
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ import (
|
||||
|
||||
"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 invocation Token.
|
||||
// creation of an invocation Token.
|
||||
type Option func(*Token) error
|
||||
|
||||
// WithArgument adds a key/value pair to the Token's Arguments field.
|
||||
|
||||
@@ -8,9 +8,10 @@ import (
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/v3/golden"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/token/internal/envelope"
|
||||
"github.com/ucan-wg/go-ucan/token/invocation"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -78,6 +79,7 @@ func TestSchemaRoundTrip(t *testing.T) {
|
||||
assert.JSONEq(t, string(invocationJson), readJson.String())
|
||||
})
|
||||
}
|
||||
|
||||
func privKey(t require.TestingT, privKeyCfg string) crypto.PrivKey {
|
||||
privKeyMar, err := crypto.ConfigDecodeKey(privKeyCfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
Reference in New Issue
Block a user