feat(delegation): update to provide encoding/decoding straight from/to View
This commit is contained in:
1
delegation/delegation.go
Normal file
1
delegation/delegation.go
Normal file
@@ -0,0 +1 @@
|
||||
package delegation
|
||||
142
delegation/delegation_test.go
Normal file
142
delegation/delegation_test.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package delegation_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
)
|
||||
|
||||
const (
|
||||
nonce = "6roDhGi0kiNriQAz7J3d+bOeoI/tj8ENikmQNbtjnD0"
|
||||
|
||||
AudiencePrivKeyCfg = "CAESQL1hvbXpiuk2pWr/XFbfHJcZNpJ7S90iTA3wSCTc/BPRneCwPnCZb6c0vlD6ytDWqaOt0HEOPYnqEpnzoBDprSM="
|
||||
AudienceDID = "did:key:z6Mkq5YmbJcTrPExNDi26imrTCpKhepjBFBSHqrBDN2ArPkv"
|
||||
|
||||
issuerPrivKeyCfg = "CAESQLSql38oDmQXIihFFaYIjb73mwbPsc7MIqn4o8PN4kRNnKfHkw5gRP1IV9b6d0estqkZayGZ2vqMAbhRixjgkDU="
|
||||
issuerDID = "did:key:z6Mkpzn2n3ZGT2VaqMGSQC3tzmzV4TS9S71iFsDXE1WnoNH2"
|
||||
|
||||
subjectPrivKeyCfg = "CAESQL9RtjZ4dQBeXtvDe53UyvslSd64kSGevjdNiA1IP+hey5i/3PfRXSuDr71UeJUo1fLzZ7mGldZCOZL3gsIQz5c="
|
||||
subjectDID = "did:key:z6MktA1uBdCpq4uJBqE9jjMiLyxZBg9a6xgPPKJjMqss6Zc2"
|
||||
subJectCmd = "/foo/bar"
|
||||
subjectPol = `
|
||||
[
|
||||
[
|
||||
"==",
|
||||
".status",
|
||||
"draft"
|
||||
],
|
||||
[
|
||||
"all",
|
||||
".reviewer",
|
||||
[
|
||||
"like",
|
||||
".email",
|
||||
"*@example.com"
|
||||
]
|
||||
],
|
||||
[
|
||||
"any",
|
||||
".tags",
|
||||
[
|
||||
"or",
|
||||
[
|
||||
[
|
||||
"==",
|
||||
".",
|
||||
"news"
|
||||
],
|
||||
[
|
||||
"==",
|
||||
".",
|
||||
"press"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
`
|
||||
)
|
||||
|
||||
// func TestConstructors(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// privKey := privKey(t, issuerPrivKeyCfg)
|
||||
|
||||
// aud, err := did.Parse(AudienceDID)
|
||||
|
||||
// sub, err := did.Parse(subjectDID)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// cmd, err := command.Parse(subJectCmd)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// pol, err := policy.FromDagJson(subjectPol)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// exp := time.Time{}
|
||||
|
||||
// meta := map[string]datamodel.Node{
|
||||
// "foo": basicnode.NewString("fooo"),
|
||||
// "bar": basicnode.NewString("barr"),
|
||||
// }
|
||||
|
||||
// t.Run("New", func(t *testing.T) {
|
||||
// dlg, err := delegation.New(privKey, aud, &sub, cmd, pol, []byte(nonce), delegation.WithExpiration(&exp), delegation.WithMeta(meta))
|
||||
// require.NoError(t, err)
|
||||
|
||||
// data, err := dlg.ToDagJson()
|
||||
// require.NoError(t, err)
|
||||
|
||||
// t.Log(string(data))
|
||||
|
||||
// golden.Assert(t, string(data), "new.dagjson")
|
||||
// })
|
||||
|
||||
// t.Run("Root", func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// dlg, err := delegation.Root(privKey, aud, cmd, pol, []byte(nonce), delegation.WithExpiration(&exp), delegation.WithMeta(meta))
|
||||
// require.NoError(t, err)
|
||||
|
||||
// data, err := dlg.ToDagJson()
|
||||
// require.NoError(t, err)
|
||||
|
||||
// t.Log(string(data))
|
||||
|
||||
// golden.Assert(t, string(data), "root.dagjson")
|
||||
// })
|
||||
// }
|
||||
|
||||
func privKey(t *testing.T, privKeyCfg string) crypto.PrivKey {
|
||||
t.Helper()
|
||||
|
||||
privKeyMar, err := crypto.ConfigDecodeKey(privKeyCfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
privKey, err := crypto.UnmarshalPrivateKey(privKeyMar)
|
||||
require.NoError(t, err)
|
||||
|
||||
return privKey
|
||||
}
|
||||
|
||||
func TestKey(t *testing.T) {
|
||||
t.Skip()
|
||||
|
||||
priv, _, err := crypto.GenerateEd25519Key(rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
privMar, err := crypto.MarshalPrivateKey(priv)
|
||||
require.NoError(t, err)
|
||||
|
||||
privCfg := crypto.ConfigEncodeKey(privMar)
|
||||
t.Log(privCfg)
|
||||
|
||||
id, err := did.FromPubKey(priv.GetPublic())
|
||||
require.NoError(t, err)
|
||||
t.Log(id)
|
||||
|
||||
t.Fail()
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package delegation
|
||||
|
||||
import (
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||
)
|
||||
|
||||
func (p *PayloadModel) EncodeDagCbor() ([]byte, error) {
|
||||
return ipld.Marshal(dagcbor.Encode, p, PayloadType())
|
||||
}
|
||||
|
||||
func (p *PayloadModel) EncodeDagJson() ([]byte, error) {
|
||||
return ipld.Marshal(dagjson.Encode, p, PayloadType())
|
||||
}
|
||||
|
||||
func DecodeDagCbor(data []byte) (*PayloadModel, error) {
|
||||
var p PayloadModel
|
||||
_, err := ipld.Unmarshal(data, dagcbor.Decode, &p, PayloadType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func DecodeDagJson(data []byte) (*PayloadModel, error) {
|
||||
var p PayloadModel
|
||||
_, err := ipld.Unmarshal(data, dagjson.Decode, &p, PayloadType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
168
delegation/ipld.go
Normal file
168
delegation/ipld.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package delegation
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"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/libp2p/go-libp2p/core/crypto"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
"github.com/ucan-wg/go-ucan/internal/envelope"
|
||||
)
|
||||
|
||||
// Encode marshals a View to the format specified by the provided
|
||||
// codec.Encoder.
|
||||
func (d *View) Encode(privKey crypto.PrivKey, encFn codec.Encoder) ([]byte, error) {
|
||||
node, err := d.ToIPLD(privKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ipld.Encode(node, encFn)
|
||||
}
|
||||
|
||||
// EncodeWriter is the same as Encode but accepts an io.Writer.
|
||||
func (d *View) EncodeWriter(w io.Writer, privKey crypto.PrivKey, encFn codec.Encoder) error {
|
||||
node, err := d.ToIPLD(privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ipld.EncodeStreaming(w, node, encFn)
|
||||
}
|
||||
|
||||
// ToDagCbor marshals the View to the DAG-CBOR format.
|
||||
func (d *View) ToDagCbor(privKey crypto.PrivKey) ([]byte, error) {
|
||||
return d.Encode(privKey, dagcbor.Encode)
|
||||
}
|
||||
|
||||
// ToDagCborWriter is the same as ToDagCbor but it accepts an io.Writer.
|
||||
func (d *View) ToDagCborWriter(w io.Writer, privKey crypto.PrivKey) error {
|
||||
return d.EncodeWriter(w, privKey, dagcbor.Encode)
|
||||
}
|
||||
|
||||
// ToDagJson marshals the View to the DAG-JSON format.
|
||||
func (d *View) ToDagJson(privKey crypto.PrivKey) ([]byte, error) {
|
||||
return d.Encode(privKey, dagjson.Encode)
|
||||
}
|
||||
|
||||
// ToDagJsonWriter is the same as ToDagJson but it accepts an io.Writer.
|
||||
func (d *View) ToDagJsonWriter(w io.Writer, privKey crypto.PrivKey) error {
|
||||
return d.EncodeWriter(w, privKey, dagjson.Encode)
|
||||
}
|
||||
|
||||
// ToIPLD wraps the View in an IPLD datamodel.Node.
|
||||
func (d *View) ToIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
|
||||
var sub *string
|
||||
if d.Subject != did.Undef {
|
||||
s := d.Subject.String()
|
||||
sub = &s
|
||||
}
|
||||
|
||||
pol, err := d.Policy.ToIPLD()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metaKeys := make([]string, len(d.Meta))
|
||||
i := 0
|
||||
|
||||
for k := range d.Meta {
|
||||
metaKeys[i] = k
|
||||
i++
|
||||
}
|
||||
|
||||
var nbf *int64
|
||||
if d.NotBefore != nil {
|
||||
u := d.NotBefore.Unix()
|
||||
nbf = &u
|
||||
}
|
||||
|
||||
var exp *int64
|
||||
if d.Expiration != nil {
|
||||
u := d.Expiration.Unix()
|
||||
exp = &u
|
||||
}
|
||||
|
||||
model := &PayloadModel{
|
||||
Iss: d.Issuer.String(),
|
||||
Aud: d.Audience.String(),
|
||||
Sub: sub,
|
||||
Cmd: d.Command.String(),
|
||||
Pol: pol,
|
||||
Nonce: d.Nonce,
|
||||
Meta: MetaModel{
|
||||
Keys: metaKeys,
|
||||
Values: d.Meta,
|
||||
},
|
||||
Nbf: nbf,
|
||||
Exp: exp,
|
||||
}
|
||||
|
||||
return envelope.ToIPLD(privKey, model)
|
||||
}
|
||||
|
||||
// Decode unmarshals the input data using the format specified by the
|
||||
// provided codec.Decoder into a View.
|
||||
//
|
||||
// An error is returned if the conversion fails, or if the resulting
|
||||
// View is invalid.
|
||||
func Decode(b []byte, decFn codec.Decoder) (*View, error) {
|
||||
node, err := ipld.Decode(b, decFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FromIPLD(node)
|
||||
}
|
||||
|
||||
// DecodeReader is the same as Decode, but accept an io.Reader.
|
||||
func DecodeReader(r io.Reader, decFn codec.Decoder) (*View, error) {
|
||||
node, err := ipld.DecodeStreaming(r, decFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FromIPLD(node)
|
||||
}
|
||||
|
||||
// FromDagCbor unmarshals the input data into a View.
|
||||
//
|
||||
// An error is returned if the conversion fails, or if the resulting
|
||||
// View is invalid.
|
||||
func FromDagCbor(data []byte) (*View, error) {
|
||||
return Decode(data, dagcbor.Decode)
|
||||
}
|
||||
|
||||
// FromDagCborReader is the same as FromDagCbor, but accept an io.Reader.
|
||||
func FromDagCborReader(r io.Reader) (*View, error) {
|
||||
return DecodeReader(r, dagcbor.Decode)
|
||||
}
|
||||
|
||||
// FromDagJson unmarshals the input data into a View.
|
||||
//
|
||||
// An error is returned if the conversion fails, or if the resulting
|
||||
// View is invalid.
|
||||
func FromDagJson(data []byte) (*View, error) {
|
||||
return Decode(data, dagjson.Decode)
|
||||
}
|
||||
|
||||
// FromDagJsonReader is the same as FromDagJson, but accept an io.Reader.
|
||||
func FromDagJsonReader(r io.Reader) (*View, error) {
|
||||
return DecodeReader(r, dagjson.Decode)
|
||||
}
|
||||
|
||||
// FromIPLD unwraps a View from the provided IPLD datamodel.Node
|
||||
//
|
||||
// An error is returned if the conversion fails, or if the resulting
|
||||
// View is invalid.
|
||||
func FromIPLD(node datamodel.Node) (*View, error) {
|
||||
tkn, _, err := envelope.FromIPLD[*PayloadModel](node) // TODO add CID to view
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ViewFromModel(*tkn)
|
||||
}
|
||||
@@ -7,9 +7,13 @@ import (
|
||||
|
||||
"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/schema"
|
||||
"github.com/ucan-wg/go-ucan/internal/envelope"
|
||||
)
|
||||
|
||||
const Tag = "ucan/dlg@1.0.0-rc.1"
|
||||
|
||||
//go:embed delegation.ipldsch
|
||||
var schemaBytes []byte
|
||||
|
||||
@@ -33,6 +37,8 @@ func PayloadType() schema.Type {
|
||||
return mustLoadSchema().TypeByName("Payload")
|
||||
}
|
||||
|
||||
var _ envelope.Tokener = (*PayloadModel)(nil)
|
||||
|
||||
type PayloadModel struct {
|
||||
// Issuer DID (sender)
|
||||
Iss string
|
||||
@@ -63,6 +69,14 @@ type PayloadModel struct {
|
||||
Exp *int64
|
||||
}
|
||||
|
||||
func (e *PayloadModel) Prototype() schema.TypedPrototype {
|
||||
return bindnode.Prototype((*PayloadModel)(nil), PayloadType())
|
||||
}
|
||||
|
||||
func (*PayloadModel) Tag() string {
|
||||
return Tag
|
||||
}
|
||||
|
||||
type MetaModel struct {
|
||||
Keys []string
|
||||
Values map[string]datamodel.Node
|
||||
|
||||
@@ -1,66 +1,76 @@
|
||||
package delegation
|
||||
package delegation_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/delegation"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
//go:embed delegation.ipldsch
|
||||
var schemaBytes []byte
|
||||
|
||||
func TestSchemaRoundTrip(t *testing.T) {
|
||||
const delegationJson = `
|
||||
{
|
||||
"aud":"did:key:def456",
|
||||
"cmd":"/foo/bar",
|
||||
"exp":123456,
|
||||
"iss":"did:key:abc123",
|
||||
"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":""
|
||||
}
|
||||
`
|
||||
// const delegationJson = `
|
||||
// {
|
||||
// "aud":"did:key:def456",
|
||||
// "cmd":"/foo/bar",
|
||||
// "exp":123456,
|
||||
// "iss":"did:key:abc123",
|
||||
// "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":""
|
||||
// }
|
||||
// `
|
||||
|
||||
delegationJson := golden.Get(t, "new.dagjson")
|
||||
privKey := privKey(t, issuerPrivKeyCfg)
|
||||
|
||||
// format: dagJson --> PayloadModel --> dagCbor --> PayloadModel --> dagJson
|
||||
// function: DecodeDagJson() EncodeDagCbor() DecodeDagCbor() EncodeDagJson()
|
||||
|
||||
p1, err := DecodeDagJson([]byte(delegationJson))
|
||||
p1, err := delegation.FromDagJson([]byte(delegationJson))
|
||||
require.NoError(t, err)
|
||||
|
||||
cborBytes, err := p1.EncodeDagCbor()
|
||||
cborBytes, err := p1.ToDagCbor(privKey)
|
||||
require.NoError(t, err)
|
||||
fmt.Println("cborBytes length", len(cborBytes))
|
||||
fmt.Println("cbor", string(cborBytes))
|
||||
|
||||
p2, err := DecodeDagCbor(cborBytes)
|
||||
p2, err := delegation.FromDagCbor(cborBytes)
|
||||
require.NoError(t, err)
|
||||
fmt.Println("read Cbor", p2)
|
||||
|
||||
readJson, err := p2.EncodeDagJson()
|
||||
readJson, err := p2.ToDagJson(privKey)
|
||||
require.NoError(t, err)
|
||||
fmt.Println("readJson length", len(readJson))
|
||||
fmt.Println("json: ", string(readJson))
|
||||
|
||||
require.JSONEq(t, delegationJson, string(readJson))
|
||||
require.JSONEq(t, string(delegationJson), string(readJson))
|
||||
}
|
||||
|
||||
func BenchmarkSchemaLoad(b *testing.B) {
|
||||
|
||||
1
delegation/testdata/new.dagjson
vendored
Normal file
1
delegation/testdata/new.dagjson
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[{"/":{"bytes":"P2lPLfdMuZuc4NPZ0mbozU+/bn5xoWlJsu+Fvaxi4ICYXVJb9/wiTTht3WJEFqjxXLxfTl4BMZF3J1CNvMPqBg"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/dlg@1.0.0-rc.1":{"aud":"did:key:z6Mkq5YmbJcTrPExNDi26imrTCpKhepjBFBSHqrBDN2ArPkv","cmd":"/foo/bar","exp":-62135596800,"iss":"did:key:z6Mkpzn2n3ZGT2VaqMGSQC3tzmzV4TS9S71iFsDXE1WnoNH2","meta":{"bar":"barr","foo":"fooo"},"nonce":{"/":{"bytes":"NnJvRGhHaTBraU5yaVFBejdKM2QrYk9lb0kvdGo4RU5pa21RTmJ0am5EMA"}},"pol":[["==",".status","draft"],["all",".reviewer",["like",".email","*@example.com"]],["any",".tags",["or",[["==",".","news"],["==",".","press"]]]]],"sub":"did:key:z6MktA1uBdCpq4uJBqE9jjMiLyxZBg9a6xgPPKJjMqss6Zc2"}}]
|
||||
@@ -27,9 +27,9 @@ type View struct {
|
||||
// Arbitrary Metadata
|
||||
Meta map[string]datamodel.Node
|
||||
// "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
|
||||
NotBefore time.Time
|
||||
NotBefore *time.Time
|
||||
// The timestamp at which the Invocation becomes invalid
|
||||
Expiration time.Time
|
||||
Expiration *time.Time
|
||||
}
|
||||
|
||||
// ViewFromModel build a decoded view of the raw IPLD data.
|
||||
@@ -76,11 +76,13 @@ func ViewFromModel(m PayloadModel) (*View, error) {
|
||||
view.Meta = m.Meta.Values
|
||||
|
||||
if m.Nbf != nil {
|
||||
view.NotBefore = time.Unix(*m.Nbf, 0)
|
||||
t := time.Unix(*m.Nbf, 0)
|
||||
view.NotBefore = &t
|
||||
}
|
||||
|
||||
if m.Exp != nil {
|
||||
view.Expiration = time.Unix(*m.Exp, 0)
|
||||
t := time.Unix(*m.Exp, 0)
|
||||
view.Expiration = &t
|
||||
}
|
||||
|
||||
return &view, nil
|
||||
|
||||
Reference in New Issue
Block a user