fix(delegation): simplify package API and restore convenient encoding
This commit is contained in:
@@ -1,19 +1,14 @@
|
|||||||
package delegation
|
package delegation
|
||||||
|
|
||||||
// Code generated by github.com/launchdarkly/go-options. DO NOT EDIT.
|
// Code generated by github.com/selesy/go-options. DO NOT EDIT.
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ApplyOptionFunc func(c *config) error
|
type Option func(c *config) error
|
||||||
|
|
||||||
func (f ApplyOptionFunc) apply(c *config) error {
|
|
||||||
return f(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConfig(options ...Option) (config, error) {
|
func newConfig(options ...Option) (config, error) {
|
||||||
var c config
|
var c config
|
||||||
@@ -23,152 +18,52 @@ func newConfig(options ...Option) (config, error) {
|
|||||||
|
|
||||||
func applyConfigOptions(c *config, options ...Option) error {
|
func applyConfigOptions(c *config, options ...Option) error {
|
||||||
for _, o := range options {
|
for _, o := range options {
|
||||||
if err := o.apply(c); err != nil {
|
if err := o(c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Option interface {
|
|
||||||
apply(*config) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type withExpirationImpl struct {
|
|
||||||
o *time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o withExpirationImpl) apply(c *config) error {
|
|
||||||
c.Expiration = o.o
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o withExpirationImpl) String() string {
|
|
||||||
name := "WithExpiration"
|
|
||||||
|
|
||||||
// hack to avoid go vet error about passing a function to Sprintf
|
|
||||||
var value interface{} = o.o
|
|
||||||
return fmt.Sprintf("%s: %+v", name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithExpiration(o *time.Time) Option {
|
func WithExpiration(o *time.Time) Option {
|
||||||
return withExpirationImpl{
|
return func(c *config) error {
|
||||||
o: o,
|
c.Expiration = o
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type withMetaImpl struct {
|
func WithMeta(o map[string]datamodel.Node) Option {
|
||||||
o map[string]any
|
return func(c *config) error {
|
||||||
}
|
c.Meta = o
|
||||||
|
return nil
|
||||||
func (o withMetaImpl) apply(c *config) error {
|
|
||||||
c.Meta = o.o
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o withMetaImpl) String() string {
|
|
||||||
name := "WithMeta"
|
|
||||||
|
|
||||||
// hack to avoid go vet error about passing a function to Sprintf
|
|
||||||
var value interface{} = o.o
|
|
||||||
return fmt.Sprintf("%s: %+v", name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithMeta(o map[string]any) Option {
|
|
||||||
return withMetaImpl{
|
|
||||||
o: o,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type withNoExpirationImpl struct {
|
|
||||||
o bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o withNoExpirationImpl) apply(c *config) error {
|
|
||||||
c.NoExpiration = o.o
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o withNoExpirationImpl) String() string {
|
|
||||||
name := "WithNoExpiration"
|
|
||||||
|
|
||||||
// hack to avoid go vet error about passing a function to Sprintf
|
|
||||||
var value interface{} = o.o
|
|
||||||
return fmt.Sprintf("%s: %+v", name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithNoExpiration(o bool) Option {
|
func WithNoExpiration(o bool) Option {
|
||||||
return withNoExpirationImpl{
|
return func(c *config) error {
|
||||||
o: o,
|
c.NoExpiration = o
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type withNotBeforeImpl struct {
|
|
||||||
o *time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o withNotBeforeImpl) apply(c *config) error {
|
|
||||||
c.NotBefore = o.o
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o withNotBeforeImpl) String() string {
|
|
||||||
name := "WithNotBefore"
|
|
||||||
|
|
||||||
// hack to avoid go vet error about passing a function to Sprintf
|
|
||||||
var value interface{} = o.o
|
|
||||||
return fmt.Sprintf("%s: %+v", name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithNotBefore(o *time.Time) Option {
|
func WithNotBefore(o *time.Time) Option {
|
||||||
return withNotBeforeImpl{
|
return func(c *config) error {
|
||||||
o: o,
|
c.NotBefore = o
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type withSubjectImpl struct {
|
|
||||||
o *did.DID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o withSubjectImpl) apply(c *config) error {
|
|
||||||
c.Subject = o.o
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o withSubjectImpl) String() string {
|
|
||||||
name := "WithSubject"
|
|
||||||
|
|
||||||
// hack to avoid go vet error about passing a function to Sprintf
|
|
||||||
var value interface{} = o.o
|
|
||||||
return fmt.Sprintf("%s: %+v", name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSubject is a did.DID representing the Subject.
|
// WithSubject is a did.DID representing the Subject.
|
||||||
func WithSubject(o *did.DID) Option {
|
func WithSubject(o *did.DID) Option {
|
||||||
return withSubjectImpl{
|
return func(c *config) error {
|
||||||
o: o,
|
c.Subject = o
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type withPowerlineImpl struct {
|
|
||||||
o bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o withPowerlineImpl) apply(c *config) error {
|
|
||||||
c.Powerline = o.o
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o withPowerlineImpl) String() string {
|
|
||||||
name := "WithPowerline"
|
|
||||||
|
|
||||||
// hack to avoid go vet error about passing a function to Sprintf
|
|
||||||
var value interface{} = o.o
|
|
||||||
return fmt.Sprintf("%s: %+v", name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithPowerline(o bool) Option {
|
func WithPowerline(o bool) Option {
|
||||||
return withPowerlineImpl{
|
return func(c *config) error {
|
||||||
o: o,
|
c.Powerline = o
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package delegation
|
package delegation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
"github.com/ipld/go-ipld-prime/node/bindnode"
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||||||
"github.com/ipld/go-ipld-prime/schema"
|
|
||||||
"github.com/ucan-wg/go-ucan/capability/command"
|
"github.com/ucan-wg/go-ucan/capability/command"
|
||||||
"github.com/ucan-wg/go-ucan/capability/policy"
|
"github.com/ucan-wg/go-ucan/capability/policy"
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
@@ -14,15 +15,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Tag = "ucan/dlg@"
|
Tag = "ucan/dlg@1.0.0-rc.1"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate -command options go run github.com/launchdarkly/go-options
|
type Delegation struct {
|
||||||
//go:generate options -type=config -prefix=With -output=delegatiom_options.go -cmp=false -imports=time,github.com/ucan-wg/go-ucan/did
|
envel *envelope.Envelope
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate -command options go run github.com/selesy/go-options
|
||||||
|
//go:generate options -type=config -prefix=With -output=delegatiom_options.go -cmp=false -stringer=false -imports=time,github.com/ucan-wg/go-ucan/did,github.com/ipld/go-ipld-prime/datamodel
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
Expiration *time.Time
|
Expiration *time.Time
|
||||||
Meta map[string]any
|
Meta map[string]datamodel.Node
|
||||||
NoExpiration bool
|
NoExpiration bool
|
||||||
NotBefore *time.Time
|
NotBefore *time.Time
|
||||||
// is a did.DID representing the Subject.
|
// is a did.DID representing the Subject.
|
||||||
@@ -30,81 +35,105 @@ type config struct {
|
|||||||
Powerline bool
|
Powerline bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Meta struct {
|
// Required fields for delegation
|
||||||
Keys []string
|
|
||||||
Values map[string]datamodel.Node
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMeta(meta map[string]any) Meta {
|
// Requirements for root
|
||||||
keys := make([]string, len(meta))
|
|
||||||
values := make(map[string]datamodel.Node, len(meta))
|
|
||||||
i := 0
|
|
||||||
|
|
||||||
for k, v := range meta {
|
func New(privKey crypto.PrivKey, iss did.DID, aud did.DID, cmd *command.Command, pol *policy.Policy, exp *time.Time, nonce []byte, opts ...Option) (*Delegation, error) {
|
||||||
keys[i] = k
|
|
||||||
values[k] = bindnode.Wrap(&v, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Meta{
|
|
||||||
Keys: keys,
|
|
||||||
Values: values,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ envelope.Tokener = (*Token)(nil)
|
|
||||||
|
|
||||||
type Token struct {
|
|
||||||
// Issuer DID (sender)
|
|
||||||
Issuer did.DID
|
|
||||||
// Audience DID (receiver)
|
|
||||||
Audience did.DID
|
|
||||||
// Principal that the chain is about (the Subject)
|
|
||||||
Subject *did.DID
|
|
||||||
// The Command to eventually invoke
|
|
||||||
Command *command.Command
|
|
||||||
// The delegation policy
|
|
||||||
Policy *policy.Policy
|
|
||||||
// A unique, random nonce
|
|
||||||
Nonce []byte
|
|
||||||
// Arbitrary Metadata
|
|
||||||
Meta Meta
|
|
||||||
// "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
|
|
||||||
NotBefore *time.Time
|
|
||||||
// The timestamp at which the Invocation becomes invalid
|
|
||||||
Expiration *time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(iss did.DID, aud did.DID, prf []Token, cmd *command.Command, pol *policy.Policy, exp *time.Time, nonce []byte, opts ...Option) (*Token, error) {
|
|
||||||
cfg, err := newConfig(opts...)
|
cfg, err := newConfig(opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tkn := &Token{
|
if !iss.Defined() {
|
||||||
Issuer: iss,
|
return nil, fmt.Errorf("%w: %s", token.ErrMissingRequiredDID, "iss")
|
||||||
Audience: aud,
|
|
||||||
Subject: cfg.Subject,
|
|
||||||
Command: cmd,
|
|
||||||
Policy: pol,
|
|
||||||
Nonce: nonce,
|
|
||||||
NotBefore: cfg.NotBefore,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !aud.Defined() {
|
||||||
|
return nil, fmt.Errorf("%w: %s", token.ErrMissingRequiredDID, "aud")
|
||||||
|
}
|
||||||
|
audience := aud.String()
|
||||||
|
|
||||||
|
var subject *string
|
||||||
|
if cfg.Subject != nil && cfg.Subject.Defined() {
|
||||||
|
s := cfg.Subject.String()
|
||||||
|
subject = &s
|
||||||
|
}
|
||||||
|
|
||||||
|
policy, err := pol.ToIPLD()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce = []uint8(nonce)
|
||||||
|
|
||||||
|
var notBefore *int
|
||||||
|
if cfg.NotBefore != nil {
|
||||||
|
n := int(cfg.NotBefore.Unix())
|
||||||
|
notBefore = &n
|
||||||
|
}
|
||||||
|
|
||||||
|
var meta *token.Map__String__Any
|
||||||
if len(cfg.Meta) > 0 {
|
if len(cfg.Meta) > 0 {
|
||||||
tkn.Meta = NewMeta(cfg.Meta)
|
m := token.ToIPLDMapStringAny(cfg.Meta)
|
||||||
|
meta = &m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var expiration *int
|
||||||
if exp != nil && !cfg.NoExpiration {
|
if exp != nil && !cfg.NoExpiration {
|
||||||
tkn.Expiration = exp
|
e := int(cfg.NotBefore.Unix())
|
||||||
|
expiration = &e
|
||||||
}
|
}
|
||||||
|
|
||||||
return tkn, nil
|
tkn := &token.Token{
|
||||||
|
Issuer: iss.String(),
|
||||||
|
Audience: &audience,
|
||||||
|
Subject: subject,
|
||||||
|
Command: cmd.String(),
|
||||||
|
Policy: &policy,
|
||||||
|
Nonce: &nonce,
|
||||||
|
Meta: meta,
|
||||||
|
NotBefore: notBefore,
|
||||||
|
Expiration: expiration,
|
||||||
|
}
|
||||||
|
|
||||||
|
envel, err := envelope.New(privKey, tkn, Tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dlg := &Delegation{envel: envel}
|
||||||
|
|
||||||
|
if err := dlg.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dlg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Token) Tag() string {
|
type validateFunc func() error
|
||||||
return Tag
|
|
||||||
|
func (d *Delegation) Validate() error {
|
||||||
|
return errors.Join(
|
||||||
|
d.validateDID("iss", &d.envel.TokenPayload().Issuer, false),
|
||||||
|
d.validateDID("aud", d.envel.TokenPayload().Audience, false),
|
||||||
|
d.validateDID("sub", d.envel.TokenPayload().Subject, true),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Token) Prototype() schema.TypedPrototype {
|
func (d *Delegation) validateDID(fieldName string, identity *string, nullableOrOptional bool) error {
|
||||||
return bindnode.Prototype((*Token)(nil), mustLoadSchema().TypeByName("Delegation"), token.BindnodeOptions()...)
|
if identity == nil && !nullableOrOptional {
|
||||||
|
return fmt.Errorf("a required DID is missing: %s", fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := did.Parse(*identity)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if !id.Defined() && !id.Key() {
|
||||||
|
return fmt.Errorf("a required DID is missing: %s", fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
type DID string
|
|
||||||
|
|
||||||
# The Delegation payload MUST describe the authorization claims, who is involved, and its validity period.
|
|
||||||
type Payload struct {
|
|
||||||
# Issuer DID (sender)
|
|
||||||
iss DID
|
|
||||||
# Audience DID (receiver)
|
|
||||||
aud DID
|
|
||||||
# Principal that the chain is about (the Subject)
|
|
||||||
sub optional DID
|
|
||||||
|
|
||||||
# The Command to eventually invoke
|
|
||||||
cmd String
|
|
||||||
|
|
||||||
# The delegation policy
|
|
||||||
# It doesn't seem possible to represent it with a schema.
|
|
||||||
pol Any
|
|
||||||
|
|
||||||
# A unique, random nonce
|
|
||||||
nonce Bytes
|
|
||||||
|
|
||||||
# Arbitrary Metadata
|
|
||||||
meta {String : Any}
|
|
||||||
|
|
||||||
# "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
|
|
||||||
nbf optional Int
|
|
||||||
# The timestamp at which the Invocation becomes invalid
|
|
||||||
exp nullable Int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Delegation struct {
|
|
||||||
# Issuer DID (sender)
|
|
||||||
issuer DID (rename "iss")
|
|
||||||
# Audience DID (receiver)
|
|
||||||
audience DID (rename "aud")
|
|
||||||
# Principal that the chain is about (the Subject)
|
|
||||||
subject optional DID (rename "sub")
|
|
||||||
|
|
||||||
# The Command to eventually invoke
|
|
||||||
command String (rename "cmd")
|
|
||||||
|
|
||||||
# The delegation policy
|
|
||||||
# It doesn't seem possible to represent it with a schema.
|
|
||||||
policy Any (rename "pol")
|
|
||||||
|
|
||||||
# A unique, random nonce
|
|
||||||
nonce Bytes
|
|
||||||
|
|
||||||
# Arbitrary Metadata
|
|
||||||
meta {String : Any}
|
|
||||||
|
|
||||||
# "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
|
|
||||||
notBefore optional Int (rename "nbf")
|
|
||||||
# The timestamp at which the Invocation becomes invalid
|
|
||||||
expiration nullable Int (rename "exp")
|
|
||||||
}
|
|
||||||
|
|
||||||
type Save struct {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package delegation_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
|
||||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
|
||||||
"github.com/ipld/go-ipld-prime/node/bindnode"
|
|
||||||
"github.com/ipld/go-ipld-prime/schema"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/ucan-wg/go-ucan/delegation"
|
|
||||||
"github.com/ucan-wg/go-ucan/internal/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestToken_Proto(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const delegationJson = `
|
|
||||||
{
|
|
||||||
"aud":"did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
|
|
||||||
"cmd":"/foo/bar",
|
|
||||||
"exp":123456,
|
|
||||||
"iss":"did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
|
|
||||||
"meta":{
|
|
||||||
"bar":"baaar",
|
|
||||||
"foo":"fooo"
|
|
||||||
},
|
|
||||||
"nbf":123456,
|
|
||||||
"nonce":{
|
|
||||||
"/":{
|
|
||||||
"bytes":"c3VwZXItcmFuZG9t"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pol":[
|
|
||||||
["==", ".status", "draft"],
|
|
||||||
["all", ".reviewer", [
|
|
||||||
["like", ".email", "*@example.com"]]
|
|
||||||
],
|
|
||||||
["any", ".tags", [
|
|
||||||
["or", [
|
|
||||||
["==", ".", "news"],
|
|
||||||
["==", ".", "press"]]
|
|
||||||
]]
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"sub":"did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
proto := (*delegation.Token)(nil).Prototype()
|
|
||||||
|
|
||||||
node, err := ipld.DecodeUsingPrototype([]byte(delegationJson), dagjson.Decode, proto)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tkn, ok := bindnode.Unwrap(node).(*delegation.Token)
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
t.Log("Token:")
|
|
||||||
t.Log(" Audience:", tkn.Audience)
|
|
||||||
t.Log(" Command: ", tkn.Command)
|
|
||||||
// t.Log(" Expiration: ", token.Expiration)
|
|
||||||
t.Log(" Issuer:", tkn.Issuer)
|
|
||||||
// t.Log(" Meta:", token.Meta)
|
|
||||||
// t.Log(" NotBefore", token.NotBefore)
|
|
||||||
// t.Log(" Nonce:", token.Nonce)
|
|
||||||
// t.Log(" Policy:", token.Policy)
|
|
||||||
t.Log(" Subject:", tkn.Subject)
|
|
||||||
|
|
||||||
// token.Command = nil
|
|
||||||
// token.Meta = nil
|
|
||||||
// token.Policy = nil
|
|
||||||
// token.Expiration = nil
|
|
||||||
// token.NotBefore = nil
|
|
||||||
|
|
||||||
_ = bindnode.Wrap(tkn, proto.Type(), token.BindnodeOptions()...)
|
|
||||||
|
|
||||||
typed, ok := node.(schema.TypedNode)
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
json, err := ipld.Encode(typed.Representation(), dagjson.Encode)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.JSONEq(t, delegationJson, string(json))
|
|
||||||
|
|
||||||
t.Log(string(json))
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
93
delegation/encoding.go
Normal file
93
delegation/encoding.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package delegation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/ipld/go-ipld-prime/codec"
|
||||||
|
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||||
|
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
"github.com/ucan-wg/go-ucan/internal/envelope"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encode marshals a Delegation to the the format specified by the provided
|
||||||
|
// codec.Encoder.
|
||||||
|
func (d *Delegation) Encode(encFn codec.Encoder) ([]byte, error) {
|
||||||
|
node, err := d.ToIPLD()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipld.Encode(node, encFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToDagCbor marshals the Delegation to the DAG-CBOR format.
|
||||||
|
func (d *Delegation) ToDagCbor() ([]byte, error) {
|
||||||
|
return d.Encode(dagcbor.Encode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToDagJson marshals the Delegation to the DAG-JSON format.
|
||||||
|
func (d *Delegation) ToDagJson() ([]byte, error) {
|
||||||
|
return d.Encode(dagjson.Encode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToIPLD wraps the Delegation in an IPLD datamodel.Node.
|
||||||
|
func (d *Delegation) ToIPLD() (datamodel.Node, error) {
|
||||||
|
return d.envel.Wrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode unmarshals the input data using the format specified by the
|
||||||
|
// provided codec.Decoder into a Delegation.
|
||||||
|
//
|
||||||
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
|
// Delegation is invalid.
|
||||||
|
func Decode(b []byte, decFn codec.Decoder) (*Delegation, error) {
|
||||||
|
node, err := ipld.Decode(b, decFn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return FromIPLD(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromDagCbor unmarshals the input data into a Delegation.
|
||||||
|
//
|
||||||
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
|
// Delegation is invalid.
|
||||||
|
func FromDagCbor(data []byte) (*Delegation, error) {
|
||||||
|
return Decode(data, dagcbor.Decode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromDagsjon unmarshals the input data into a Delegation.
|
||||||
|
//
|
||||||
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
|
// Delegation is invalid.
|
||||||
|
func FromDagJson(data []byte) (*Delegation, error) {
|
||||||
|
return Decode(data, dagjson.Decode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromIPLD unwraps a Delegation from the provided IPLD datamodel.Node
|
||||||
|
//
|
||||||
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
|
// Delegation is invalid.
|
||||||
|
func FromIPLD(node datamodel.Node) (*Delegation, error) {
|
||||||
|
envel, err := envelope.Unwrap(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if envel.Tag() != Tag {
|
||||||
|
return nil, fmt.Errorf("wrong tag for TokenPayload: received %s but expected %s", envel.Tag(), Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
dlg := &Delegation{
|
||||||
|
envel: envel,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dlg.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dlg, nil
|
||||||
|
}
|
||||||
101
delegation/encoding_test.go
Normal file
101
delegation/encoding_test.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package delegation_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/ucan-wg/go-ucan/delegation"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncodingRoundTrip(t *testing.T) {
|
||||||
|
const delegationJson = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"/": {
|
||||||
|
"bytes": "QWr0Pk+sSWE1nszuBMQzggbHX4ofJb8QRdwrLJK/AGCx2p4s/xaCRieomfstDjsV4ezBzX1HARvcoNgdwDQ8Aw"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": {
|
||||||
|
"/": {
|
||||||
|
"bytes": "NO0BcQ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ucan/dlg@1.0.0-rc.1": {
|
||||||
|
"aud": "did:key:z6Mkpzn2n3ZGT2VaqMGSQC3tzmzV4TS9S71iFsDXE1WnoNH2",
|
||||||
|
"cmd": "/foo/bar",
|
||||||
|
"exp": -62135596800,
|
||||||
|
"iss": "did:key:z6Mkpzn2n3ZGT2VaqMGSQC3tzmzV4TS9S71iFsDXE1WnoNH2",
|
||||||
|
"meta": {
|
||||||
|
"bar": "barr",
|
||||||
|
"foo": "fooo"
|
||||||
|
},
|
||||||
|
"nbf": -62135596800,
|
||||||
|
"nonce": {
|
||||||
|
"/": {
|
||||||
|
"bytes": "X93ORvN1QIXrKPyEP5m5XoVK9VLX9nX8VV/+HlWrp9c"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pol": [
|
||||||
|
[
|
||||||
|
"==",
|
||||||
|
".status",
|
||||||
|
"draft"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"all",
|
||||||
|
".reviewer",
|
||||||
|
[
|
||||||
|
"like",
|
||||||
|
".email",
|
||||||
|
"*@example.com"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"any",
|
||||||
|
".tags",
|
||||||
|
[
|
||||||
|
"or",
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"==",
|
||||||
|
".",
|
||||||
|
"news"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"==",
|
||||||
|
".",
|
||||||
|
"press"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"sub": "did:key:z6MktA1uBdCpq4uJBqE9jjMiLyxZBg9a6xgPPKJjMqss6Zc2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`
|
||||||
|
// format: dagJson --> Delegation --> dagCbor --> Delegation --> dagJson
|
||||||
|
// function: FromDagJson() ToDagCbor() FromDagCbor() ToDagJson()
|
||||||
|
|
||||||
|
p1, err := delegation.FromDagJson([]byte(delegationJson))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cborBytes, err := p1.ToDagCbor()
|
||||||
|
require.NoError(t, err)
|
||||||
|
fmt.Println("cborBytes length", len(cborBytes))
|
||||||
|
fmt.Println("cbor", string(cborBytes))
|
||||||
|
|
||||||
|
p2, err := delegation.FromDagCbor(cborBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
fmt.Println("read Cbor", p2)
|
||||||
|
|
||||||
|
readJson, err := p2.ToDagJson()
|
||||||
|
require.NoError(t, err)
|
||||||
|
fmt.Println("readJson length", len(readJson))
|
||||||
|
fmt.Println("json: ", string(readJson))
|
||||||
|
|
||||||
|
require.JSONEq(t, delegationJson, string(readJson))
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package delegation
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
|
||||||
"github.com/ipld/go-ipld-prime/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed delegation.ipldsch
|
|
||||||
var schemaBytes []byte
|
|
||||||
|
|
||||||
var (
|
|
||||||
once sync.Once
|
|
||||||
ts *schema.TypeSystem
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
func mustLoadSchema() *schema.TypeSystem {
|
|
||||||
once.Do(func() {
|
|
||||||
ts, err = ipld.LoadSchemaBytes(schemaBytes)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("failed to load IPLD schema: %s", err))
|
|
||||||
}
|
|
||||||
return ts
|
|
||||||
}
|
|
||||||
|
|
||||||
func PayloadType() schema.Type {
|
|
||||||
return mustLoadSchema().TypeByName("Payload")
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package delegation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
|
||||||
)
|
|
||||||
|
|
||||||
func BenchmarkSchemaLoad(b *testing.B) {
|
|
||||||
b.ReportAllocs()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, _ = ipld.LoadSchemaBytes(schemaBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -98,10 +98,16 @@ func Unwrap(node datamodel.Node) (*Envelope, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Envelope{
|
envel := &Envelope{
|
||||||
signature: signature,
|
signature: signature,
|
||||||
sigPayload: sigPayload,
|
sigPayload: sigPayload,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
if ok, err := envel.Verify(); !ok || err != nil {
|
||||||
|
return nil, fmt.Errorf("envelope was not signed by issuer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return envel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signature returns the cryptographic signature of the Envelope's
|
// Signature returns the cryptographic signature of the Envelope's
|
||||||
|
|||||||
21
internal/envelope/testdata/example.dagjson
vendored
21
internal/envelope/testdata/example.dagjson
vendored
@@ -1 +1,20 @@
|
|||||||
[{"/":{"bytes":"PZV6A2aI7n+MlyADqcqmWhkuyNrgUCDz+qSLSnI9bpasOwOhKUTx95m5Nu5CO/INa1LqzHGioD9+PVf6qdtTBg"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/example@v1.0.0-rc.1":{"cmd":"","exp":null,"iss":"did:key:z6MkpuK2Amsu1RqcLGgmHHQHhvmeXCCBVsM4XFSg2cCyg4Nh","sub":null}}]
|
[
|
||||||
|
{
|
||||||
|
"/": {
|
||||||
|
"bytes": "PZV6A2aI7n+MlyADqcqmWhkuyNrgUCDz+qSLSnI9bpasOwOhKUTx95m5Nu5CO/INa1LqzHGioD9+PVf6qdtTBg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": {
|
||||||
|
"/": {
|
||||||
|
"bytes": "NO0BcQ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ucan/example@v1.0.0-rc.1": {
|
||||||
|
"cmd": "",
|
||||||
|
"exp": null,
|
||||||
|
"iss": "did:key:z6MkpuK2Amsu1RqcLGgmHHQHhvmeXCCBVsM4XFSg2cCyg4Nh",
|
||||||
|
"sub": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user