Merge pull request #93 from ucan-wg/proof-rephrase
invocation: rephrase slightly the proof rules to be less confusing down the line
This commit is contained in:
@@ -102,6 +102,9 @@ func (d DID) PubKey() (crypto.PubKey, error) {
|
|||||||
|
|
||||||
// String formats the decentralized identity document (DID) as a string.
|
// String formats the decentralized identity document (DID) as a string.
|
||||||
func (d DID) String() string {
|
func (d DID) String() string {
|
||||||
|
if d == Undef {
|
||||||
|
return "(undefined)"
|
||||||
|
}
|
||||||
key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.bytes))
|
key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.bytes))
|
||||||
return "did:key:" + key
|
return "did:key:" + key
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,13 +160,12 @@ func randToken() (*delegation.Token, cid.Cid, []byte) {
|
|||||||
|
|
||||||
opts := []delegation.Option{
|
opts := []delegation.Option{
|
||||||
delegation.WithExpiration(time.Now().Add(time.Hour)),
|
delegation.WithExpiration(time.Now().Add(time.Hour)),
|
||||||
delegation.WithSubject(iss),
|
|
||||||
}
|
}
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
opts = append(opts, delegation.WithMeta(randomString(8), randomString(10)))
|
opts = append(opts, delegation.WithMeta(randomString(8), randomString(10)))
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := delegation.New(iss, aud, cmd, pol, opts...)
|
t, err := delegation.Root(iss, aud, cmd, pol, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ package delegation
|
|||||||
// TODO: change the "delegation" link above when the specification is merged
|
// TODO: change the "delegation" link above when the specification is merged
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
@@ -44,16 +46,15 @@ type Token struct {
|
|||||||
expiration *time.Time
|
expiration *time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a validated Token from the provided parameters and options.
|
// New creates a validated delegation Token from the provided parameters and options.
|
||||||
|
// This is typically used to delegate a given power to another agent.
|
||||||
//
|
//
|
||||||
// When creating a delegated token, the Issuer's (iss) DID is assembled
|
// You can read it as "(issuer) allows (audience) to perform (cmd+pol) on (subject)".
|
||||||
// using the public key associated with the private key sent as the first
|
func New(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, sub did.DID, opts ...Option) (*Token, error) {
|
||||||
// parameter.
|
|
||||||
func New(iss, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
|
|
||||||
tkn := &Token{
|
tkn := &Token{
|
||||||
issuer: iss,
|
issuer: iss,
|
||||||
audience: aud,
|
audience: aud,
|
||||||
subject: did.Undef,
|
subject: sub,
|
||||||
command: cmd,
|
command: cmd,
|
||||||
policy: pol,
|
policy: pol,
|
||||||
meta: meta.NewMeta(),
|
meta: meta.NewMeta(),
|
||||||
@@ -81,16 +82,27 @@ func New(iss, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Optio
|
|||||||
return tkn, nil
|
return tkn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root creates a validated UCAN delegation Token from the provided
|
// Root creates a validated UCAN delegation Token from the provided parameters and options.
|
||||||
// parameters and options.
|
// This is typically used to create and give a power to an agent.
|
||||||
//
|
//
|
||||||
// When creating a root token, both the Issuer's (iss) and Subject's
|
// You can read it as "(issuer) allows (audience) to perform (cmd+pol) on itself".
|
||||||
// (sub) DIDs are assembled from the public key associated with the
|
func Root(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
|
||||||
// private key passed as the first argument.
|
return New(iss, aud, cmd, pol, iss, opts...)
|
||||||
func Root(iss, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
|
}
|
||||||
opts = append(opts, WithSubject(iss))
|
|
||||||
|
|
||||||
return New(iss, aud, cmd, pol, opts...)
|
// Powerline creates a validated UCAN delegation Token from the provided parameters and options.
|
||||||
|
//
|
||||||
|
// Powerline is a pattern for automatically delegating all future delegations to another agent regardless of Subject.
|
||||||
|
// This is a very powerful pattern, use it only if you understand it.
|
||||||
|
// Powerline delegations MUST NOT be used as the root delegation to a resource
|
||||||
|
//
|
||||||
|
// A very common use case for Powerline is providing a stable DID across multiple agents (e.g. representing a user with
|
||||||
|
// multiple devices). This enables the automatic sharing of authority across their devices without needing to share keys
|
||||||
|
// or set up a threshold scheme. It is also flexible, since a Powerline delegation MAY be revoked.
|
||||||
|
//
|
||||||
|
// You can read it as "(issuer) allows (audience) to perform (cmd+pol) on anything".
|
||||||
|
func Powerline(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
|
||||||
|
return New(iss, aud, cmd, pol, did.Undef, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issuer returns the did.DID representing the Token's issuer.
|
// Issuer returns the did.DID representing the Token's issuer.
|
||||||
@@ -160,6 +172,32 @@ func (t *Token) IsValidAt(ti time.Time) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Token) String() string {
|
||||||
|
var res strings.Builder
|
||||||
|
|
||||||
|
var kind string
|
||||||
|
switch {
|
||||||
|
case t.issuer == t.subject:
|
||||||
|
kind = " (root delegation)"
|
||||||
|
case t.subject == did.Undef:
|
||||||
|
kind = " (powerline delegation)"
|
||||||
|
default:
|
||||||
|
kind = " (normal delegation)"
|
||||||
|
}
|
||||||
|
|
||||||
|
res.WriteString(fmt.Sprintf("Issuer: %s\n", t.Issuer()))
|
||||||
|
res.WriteString(fmt.Sprintf("Audience: %s\n", t.Audience()))
|
||||||
|
res.WriteString(fmt.Sprintf("Subject: %s%s\n", t.Subject(), kind))
|
||||||
|
res.WriteString(fmt.Sprintf("Command: %s\n", t.Command()))
|
||||||
|
res.WriteString(fmt.Sprintf("Policy: %s\n", t.Policy()))
|
||||||
|
res.WriteString(fmt.Sprintf("Nonce: %s\n", base64.StdEncoding.EncodeToString(t.Nonce())))
|
||||||
|
res.WriteString(fmt.Sprintf("Meta: %s\n", t.Meta()))
|
||||||
|
res.WriteString(fmt.Sprintf("NotBefore: %v\n", t.NotBefore()))
|
||||||
|
res.WriteString(fmt.Sprintf("Expiration: %v", t.Expiration()))
|
||||||
|
|
||||||
|
return res.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Token) validate() error {
|
func (t *Token) validate() error {
|
||||||
var errs error
|
var errs error
|
||||||
|
|
||||||
|
|||||||
@@ -56,9 +56,6 @@ const (
|
|||||||
]
|
]
|
||||||
`
|
`
|
||||||
|
|
||||||
newCID = "zdpuAwa4qv3ncMDPeDoqVxjZy3JoyWsbqUzm94rdA1AvRFkkw"
|
|
||||||
rootCID = "zdpuAkgGmUp5JrXvehGuuw9JA8DLQKDaxtK3R8brDQQVC2i5X"
|
|
||||||
|
|
||||||
aesKey = "xQklMmNTnVrmaPBq/0pwV5fEwuv/iClF5HWak9MsgI8="
|
aesKey = "xQklMmNTnVrmaPBq/0pwV5fEwuv/iClF5HWak9MsgI8="
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -75,9 +72,8 @@ func TestConstructors(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Run("New", func(t *testing.T) {
|
t.Run("New", func(t *testing.T) {
|
||||||
tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol,
|
tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, didtest.PersonaCarol.DID(),
|
||||||
delegation.WithNonce([]byte(nonce)),
|
delegation.WithNonce([]byte(nonce)),
|
||||||
delegation.WithSubject(didtest.PersonaAlice.DID()),
|
|
||||||
delegation.WithExpiration(exp),
|
delegation.WithExpiration(exp),
|
||||||
delegation.WithMeta("foo", "fooo"),
|
delegation.WithMeta("foo", "fooo"),
|
||||||
delegation.WithMeta("bar", "barr"),
|
delegation.WithMeta("bar", "barr"),
|
||||||
@@ -106,6 +102,23 @@ func TestConstructors(t *testing.T) {
|
|||||||
|
|
||||||
golden.Assert(t, string(data), "root.dagjson")
|
golden.Assert(t, string(data), "root.dagjson")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Powerline", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tkn, err := delegation.Powerline(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol,
|
||||||
|
delegation.WithNonce([]byte(nonce)),
|
||||||
|
delegation.WithExpiration(exp),
|
||||||
|
delegation.WithMeta("foo", "fooo"),
|
||||||
|
delegation.WithMeta("bar", "barr"),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
data, err := tkn.ToDagJson(didtest.PersonaAlice.PrivKey())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
golden.Assert(t, string(data), "powerline.dagjson")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncryptedMeta(t *testing.T) {
|
func TestEncryptedMeta(t *testing.T) {
|
||||||
@@ -153,7 +166,7 @@ func TestEncryptedMeta(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol,
|
tkn, err := delegation.Root(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol,
|
||||||
delegation.WithEncryptedMetaString(tt.key, tt.value, encryptionKey),
|
delegation.WithEncryptedMetaString(tt.key, tt.value, encryptionKey),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -191,7 +204,7 @@ func TestEncryptedMeta(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create token with multiple encrypted values
|
// Create token with multiple encrypted values
|
||||||
tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, opts...)
|
tkn, err := delegation.Root(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, opts...)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
data, err := tkn.ToDagCbor(didtest.PersonaAlice.PrivKey())
|
data, err := tkn.ToDagCbor(didtest.PersonaAlice.PrivKey())
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -30,10 +30,11 @@ const (
|
|||||||
var constantNonce = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b}
|
var constantNonce = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b}
|
||||||
|
|
||||||
type newDelegationParams struct {
|
type newDelegationParams struct {
|
||||||
privKey crypto.PrivKey
|
privKey crypto.PrivKey // iss
|
||||||
aud did.DID
|
aud did.DID
|
||||||
cmd command.Command
|
cmd command.Command
|
||||||
pol policy.Policy
|
pol policy.Policy
|
||||||
|
sub did.DID
|
||||||
opts []delegation.Option
|
opts []delegation.Option
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,8 +90,8 @@ func (g *generator) chainPersonas(personas []didtest.Persona, acc acc, vari vari
|
|||||||
aud: personas[1].DID(),
|
aud: personas[1].DID(),
|
||||||
cmd: delegationtest.NominalCommand,
|
cmd: delegationtest.NominalCommand,
|
||||||
pol: policytest.EmptyPolicy,
|
pol: policytest.EmptyPolicy,
|
||||||
|
sub: didtest.PersonaAlice.DID(),
|
||||||
opts: []delegation.Option{
|
opts: []delegation.Option{
|
||||||
delegation.WithSubject(didtest.PersonaAlice.DID()),
|
|
||||||
delegation.WithNonce(constantNonce),
|
delegation.WithNonce(constantNonce),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -117,7 +118,7 @@ func (g *generator) chainPersonas(personas []didtest.Persona, acc acc, vari vari
|
|||||||
p.cmd = delegationtest.AttenuatedCommand
|
p.cmd = delegationtest.AttenuatedCommand
|
||||||
}},
|
}},
|
||||||
{name: "InvalidSubject", variant: func(p *newDelegationParams) {
|
{name: "InvalidSubject", variant: func(p *newDelegationParams) {
|
||||||
p.opts = append(p.opts, delegation.WithSubject(didtest.PersonaBob.DID()))
|
p.sub = didtest.PersonaBob.DID()
|
||||||
}},
|
}},
|
||||||
{name: "InvalidExpired", variant: func(p *newDelegationParams) {
|
{name: "InvalidExpired", variant: func(p *newDelegationParams) {
|
||||||
// Note: this makes the generator not deterministic
|
// Note: this makes the generator not deterministic
|
||||||
@@ -161,7 +162,7 @@ func (g *generator) createDelegation(params newDelegationParams, name string, va
|
|||||||
return cid.Undef, err
|
return cid.Undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tkn, err := delegation.New(issDID, params.aud, params.cmd, params.pol, params.opts...)
|
tkn, err := delegation.New(issDID, params.aud, params.cmd, params.pol, params.sub, params.opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cid.Undef, err
|
return cid.Undef, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,21 +107,21 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
TokenCarolDan_InvalidExpiredCID = cid.MustParse("bafyreigvdcarp6426e5aenal3q3c4uyhncwfbmt3fsmb4qzspkpjy57gti")
|
TokenCarolDan_InvalidExpiredCID = cid.MustParse("bafyreifyzm5jkx2sfu5awyndg3dn5zlg7sq5hssgfatafk62kiiilapnqe")
|
||||||
TokenCarolDan_InvalidExpiredSealed = mustGetBundle(TokenCarolDan_InvalidExpiredCID).Sealed
|
TokenCarolDan_InvalidExpiredSealed = mustGetBundle(TokenCarolDan_InvalidExpiredCID).Sealed
|
||||||
TokenCarolDan_InvalidExpiredBundle = mustGetBundle(TokenCarolDan_InvalidExpiredCID)
|
TokenCarolDan_InvalidExpiredBundle = mustGetBundle(TokenCarolDan_InvalidExpiredCID)
|
||||||
TokenCarolDan_InvalidExpired = mustGetBundle(TokenCarolDan_InvalidExpiredCID).Decoded
|
TokenCarolDan_InvalidExpired = mustGetBundle(TokenCarolDan_InvalidExpiredCID).Decoded
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
TokenDanErin_InvalidExpiredCID = cid.MustParse("bafyreifa265mung4nfpjjuiir5nytzgbrhfl66ye2dqmbq4p6dm7bzp4he")
|
TokenDanErin_InvalidExpiredCID = cid.MustParse("bafyreihhnisabmkofuk3qaw37leijxqjaz5or6v2cufjxwzdkvuvv2dzbq")
|
||||||
TokenDanErin_InvalidExpiredSealed = mustGetBundle(TokenDanErin_InvalidExpiredCID).Sealed
|
TokenDanErin_InvalidExpiredSealed = mustGetBundle(TokenDanErin_InvalidExpiredCID).Sealed
|
||||||
TokenDanErin_InvalidExpiredBundle = mustGetBundle(TokenDanErin_InvalidExpiredCID)
|
TokenDanErin_InvalidExpiredBundle = mustGetBundle(TokenDanErin_InvalidExpiredCID)
|
||||||
TokenDanErin_InvalidExpired = mustGetBundle(TokenDanErin_InvalidExpiredCID).Decoded
|
TokenDanErin_InvalidExpired = mustGetBundle(TokenDanErin_InvalidExpiredCID).Decoded
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
TokenErinFrank_InvalidExpiredCID = cid.MustParse("bafyreicde2per7ynjruvstuae67dxzdg2iepid3nonrmgr6tvodzpbqomu")
|
TokenErinFrank_InvalidExpiredCID = cid.MustParse("bafyreigeokaziviwm5kzmkpwesj3gta5k7zrd62x4a746fnrnkhvatwbna")
|
||||||
TokenErinFrank_InvalidExpiredSealed = mustGetBundle(TokenErinFrank_InvalidExpiredCID).Sealed
|
TokenErinFrank_InvalidExpiredSealed = mustGetBundle(TokenErinFrank_InvalidExpiredCID).Sealed
|
||||||
TokenErinFrank_InvalidExpiredBundle = mustGetBundle(TokenErinFrank_InvalidExpiredCID)
|
TokenErinFrank_InvalidExpiredBundle = mustGetBundle(TokenErinFrank_InvalidExpiredCID)
|
||||||
TokenErinFrank_InvalidExpired = mustGetBundle(TokenErinFrank_InvalidExpiredCID).Decoded
|
TokenErinFrank_InvalidExpired = mustGetBundle(TokenErinFrank_InvalidExpiredCID).Decoded
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ func ExampleNew() {
|
|||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
tkn, err := delegation.New(didtest.PersonaBob.DID(), didtest.PersonaCarol.DID(), cmd, pol,
|
tkn, err := delegation.New(didtest.PersonaBob.DID(), didtest.PersonaCarol.DID(), cmd, pol, didtest.PersonaAlice.DID(),
|
||||||
delegation.WithSubject(didtest.PersonaAlice.DID()),
|
|
||||||
delegation.WithExpirationIn(time.Hour),
|
delegation.WithExpirationIn(time.Hour),
|
||||||
delegation.WithNotBeforeIn(time.Minute),
|
delegation.WithNotBeforeIn(time.Minute),
|
||||||
delegation.WithMeta("foo", "bar"),
|
delegation.WithMeta("foo", "bar"),
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package delegation
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Option is a type that allows optional fields to be set during the
|
// Option is a type that allows optional fields to be set during the
|
||||||
@@ -85,20 +83,6 @@ func WithNotBeforeIn(nbf time.Duration) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSubject sets the Tokens's optional "subject" field to the value of
|
|
||||||
// provided did.DID.
|
|
||||||
//
|
|
||||||
// This Option should only be used with the New constructor - since
|
|
||||||
// Subject is a required parameter when creating a Token via the Root
|
|
||||||
// constructor, any value provided via this Option will be silently
|
|
||||||
// overwritten.
|
|
||||||
func WithSubject(sub did.DID) Option {
|
|
||||||
return func(t *Token) error {
|
|
||||||
t.subject = sub
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithNonce sets the Token's nonce with the given value.
|
// 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 required field.
|
// If this option is not used, a random 12-byte nonce is generated for this required field.
|
||||||
func WithNonce(nonce []byte) Option {
|
func WithNonce(nonce []byte) Option {
|
||||||
|
|||||||
@@ -33,9 +33,12 @@ func TestSchemaRoundTrip(t *testing.T) {
|
|||||||
p1, err := delegation.FromDagJson(delegationJson)
|
p1, err := delegation.FromDagJson(delegationJson)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, newCID, err := p1.ToSealed(privKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
cborBytes, id, err := p1.ToSealed(privKey)
|
cborBytes, id, err := p1.ToSealed(privKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, newCID, envelope.CIDToBase58BTC(id))
|
assert.Equal(t, envelope.CIDToBase58BTC(newCID), envelope.CIDToBase58BTC(id))
|
||||||
|
|
||||||
p2, c2, err := delegation.FromSealed(cborBytes)
|
p2, c2, err := delegation.FromSealed(cborBytes)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -58,10 +61,13 @@ func TestSchemaRoundTrip(t *testing.T) {
|
|||||||
p1, err := delegation.FromDagJsonReader(buf)
|
p1, err := delegation.FromDagJsonReader(buf)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, newCID, err := p1.ToSealed(privKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
cborBytes := &bytes.Buffer{}
|
cborBytes := &bytes.Buffer{}
|
||||||
id, err := p1.ToSealedWriter(cborBytes, privKey)
|
id, err := p1.ToSealedWriter(cborBytes, privKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, newCID, envelope.CIDToBase58BTC(id))
|
assert.Equal(t, envelope.CIDToBase58BTC(newCID), envelope.CIDToBase58BTC(id))
|
||||||
|
|
||||||
// buf = bytes.NewBuffer(cborBytes.Bytes())
|
// buf = bytes.NewBuffer(cborBytes.Bytes())
|
||||||
p2, c2, err := delegation.FromSealedReader(cborBytes)
|
p2, c2, err := delegation.FromSealedReader(cborBytes)
|
||||||
|
|||||||
2
token/delegation/testdata/new.dagjson
vendored
2
token/delegation/testdata/new.dagjson
vendored
@@ -1 +1 @@
|
|||||||
[{"/":{"bytes":"BBabgnWqd+cjwG1td0w9BudNocmUwoR89RMZTqZHk3osCXEI/bOkko0zTvlusaE4EMBBeSzZDKzjvunLBfdiBg"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/dlg@1.0.0-rc.1":{"aud":"did:key:z6MkvJPmEZZYbgiw1ouT1oouTsTFBHJSts9ophVsNgcRmYxU","cmd":"/foo/bar","exp":7258118400,"iss":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p","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:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p"}}]
|
[{"/":{"bytes":"YJsl8EMLnXSFE/nKKjMxz9bHHo+Y7QeLEzukEzW1TB+m53TTiY1aOt+qUO8JaTcOKsOHt/a4Vn+YiOd5CkLdAQ"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/dlg@1.0.0-rc.1":{"aud":"did:key:z6MkvJPmEZZYbgiw1ouT1oouTsTFBHJSts9ophVsNgcRmYxU","cmd":"/foo/bar","exp":7258118400,"iss":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p","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:z6Mkgupchh5HwuHahS7YsyE8bLua1Mr8p2iKNRhyvSvRAs9n"}}]
|
||||||
1
token/delegation/testdata/powerline.dagjson
vendored
Normal file
1
token/delegation/testdata/powerline.dagjson
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[{"/":{"bytes":"i3YkPDvNSU4V8XYEluZhLH0b+NDcW/6+PtPSUHC17cmXXqgelG0K4EzWQQkS9UsYCHfkZSCn9NjGSXYMMFhaAQ"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/dlg@1.0.0-rc.1":{"aud":"did:key:z6MkvJPmEZZYbgiw1ouT1oouTsTFBHJSts9ophVsNgcRmYxU","cmd":"/foo/bar","exp":7258118400,"iss":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p","meta":{"bar":"barr","foo":"fooo"},"nonce":{"/":{"bytes":"NnJvRGhHaTBraU5yaVFBejdKM2QrYk9lb0kvdGo4RU5pa21RTmJ0am5EMA"}},"pol":[["==",".status","draft"],["all",".reviewer",["like",".email","*@example.com"]],["any",".tags",["or",[["==",".","news"],["==",".","press"]]]]]}}]
|
||||||
@@ -27,7 +27,7 @@ func ExampleNew() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
inv, err := invocation.New(iss, sub, cmd, prf,
|
inv, err := invocation.New(iss, cmd, sub, prf,
|
||||||
invocation.WithArgument("uri", args["uri"]),
|
invocation.WithArgument("uri", args["uri"]),
|
||||||
invocation.WithArgument("headers", args["headers"]),
|
invocation.WithArgument("headers", args["headers"]),
|
||||||
invocation.WithArgument("payload", args["payload"]),
|
invocation.WithArgument("payload", args["payload"]),
|
||||||
|
|||||||
@@ -8,8 +8,10 @@
|
|||||||
package invocation
|
package invocation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ipfs/go-cid"
|
"github.com/ipfs/go-cid"
|
||||||
@@ -67,7 +69,9 @@ type Token struct {
|
|||||||
//
|
//
|
||||||
// With the exception of the WithMeta option, all others will overwrite
|
// With the exception of the WithMeta option, all others will overwrite
|
||||||
// the previous contents of their target field.
|
// the previous contents of their target field.
|
||||||
func New(iss, sub did.DID, cmd command.Command, prf []cid.Cid, opts ...Option) (*Token, error) {
|
//
|
||||||
|
// You can read it as "(Issuer - I) executes (command) on (subject)".
|
||||||
|
func New(iss did.DID, cmd command.Command, sub did.DID, prf []cid.Cid, opts ...Option) (*Token, error) {
|
||||||
iat := time.Now()
|
iat := time.Now()
|
||||||
|
|
||||||
tkn := Token{
|
tkn := Token{
|
||||||
@@ -212,6 +216,24 @@ func (t *Token) IsValidAt(ti time.Time) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Token) String() string {
|
||||||
|
var res strings.Builder
|
||||||
|
|
||||||
|
res.WriteString(fmt.Sprintf("Issuer: %s\n", t.Issuer()))
|
||||||
|
res.WriteString(fmt.Sprintf("Audience: %s\n", t.Audience()))
|
||||||
|
res.WriteString(fmt.Sprintf("Subject: %v\n", t.Subject()))
|
||||||
|
res.WriteString(fmt.Sprintf("Command: %s\n", t.Command()))
|
||||||
|
res.WriteString(fmt.Sprintf("Args: %s\n", t.Arguments()))
|
||||||
|
res.WriteString(fmt.Sprintf("Proof: %v\n", t.Proof()))
|
||||||
|
res.WriteString(fmt.Sprintf("Nonce: %s\n", base64.StdEncoding.EncodeToString(t.Nonce())))
|
||||||
|
res.WriteString(fmt.Sprintf("Meta: %s\n", t.Meta()))
|
||||||
|
res.WriteString(fmt.Sprintf("Expiration: %v\n", t.Expiration()))
|
||||||
|
res.WriteString(fmt.Sprintf("Invoked At: %v\n", t.InvokedAt()))
|
||||||
|
res.WriteString(fmt.Sprintf("Cause: %v", t.Cause()))
|
||||||
|
|
||||||
|
return res.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Token) validate() error {
|
func (t *Token) validate() error {
|
||||||
var errs error
|
var errs error
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ func TestToken_ExecutionAllowed(t *testing.T) {
|
|||||||
testFails(t, invocation.ErrWrongSub, didtest.PersonaFrank, delegationtest.ExpandedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_InvalidSubject)
|
testFails(t, invocation.ErrWrongSub, didtest.PersonaFrank, delegationtest.ExpandedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_InvalidSubject)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("passes - arguments satify example policy", func(t *testing.T) {
|
t.Run("passes - arguments satisfy example policy", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
testFails(t, invocation.ErrPolicyNotSatisfied, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.SpecInvalidArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy)
|
testFails(t, invocation.ErrPolicyNotSatisfied, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.SpecInvalidArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy)
|
||||||
@@ -142,7 +142,7 @@ func test(t *testing.T, persona didtest.Persona, cmd command.Command, args *args
|
|||||||
|
|
||||||
opts = append(opts, invocation.WithArguments(args))
|
opts = append(opts, invocation.WithArguments(args))
|
||||||
|
|
||||||
tkn, err := invocation.New(persona.DID(), didtest.PersonaAlice.DID(), cmd, prf, opts...)
|
tkn, err := invocation.New(persona.DID(), cmd, didtest.PersonaAlice.DID(), prf, opts...)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return tkn.ExecutionAllowed(delegationtest.GetDelegationLoader())
|
return tkn.ExecutionAllowed(delegationtest.GetDelegationLoader())
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func TestSealUnsealRoundtrip(t *testing.T) {
|
|||||||
privKey, iss, sub, cmd, args, prf, meta, err := setupExampleNew()
|
privKey, iss, sub, cmd, args, prf, meta, err := setupExampleNew()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tkn1, err := invocation.New(iss, sub, cmd, prf,
|
tkn1, err := invocation.New(iss, cmd, sub, prf,
|
||||||
invocation.WithArgument("uri", args["uri"]),
|
invocation.WithArgument("uri", args["uri"]),
|
||||||
invocation.WithArgument("headers", args["headers"]),
|
invocation.WithArgument("headers", args["headers"]),
|
||||||
invocation.WithArgument("payload", args["payload"]),
|
invocation.WithArgument("payload", args["payload"]),
|
||||||
|
|||||||
@@ -38,11 +38,15 @@ func WithArguments(args *args.Args) Option {
|
|||||||
|
|
||||||
// WithAudience sets the Token's audience to the provided did.DID.
|
// WithAudience sets the Token's audience to the provided did.DID.
|
||||||
//
|
//
|
||||||
|
// This can be used if the resource on which the token operates on is different
|
||||||
|
// from the subject. In that situation, the subject is akin to the "service" and
|
||||||
|
// the audience is akin to the resource.
|
||||||
|
//
|
||||||
// If the provided did.DID is the same as the Token's subject, the
|
// If the provided did.DID is the same as the Token's subject, the
|
||||||
// audience is not set.
|
// audience is not set.
|
||||||
func WithAudience(aud did.DID) Option {
|
func WithAudience(aud did.DID) Option {
|
||||||
return func(t *Token) error {
|
return func(t *Token) error {
|
||||||
if t.subject.String() != aud.String() {
|
if t.subject != aud {
|
||||||
t.audience = aud
|
t.audience = aud
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import (
|
|||||||
// c. The first proof must be issued to the Invoker (audience DID).
|
// c. The first proof must be issued to the Invoker (audience DID).
|
||||||
// d. The Issuer of each delegation must be the Audience in the next one.
|
// d. The Issuer of each delegation must be the Audience in the next one.
|
||||||
// e. The last token must be a root delegation.
|
// e. The last token must be a root delegation.
|
||||||
// f. The Subject of each delegation must equal the invocation's Audience field.
|
// f. The Subject of each delegation must equal the invocation's Subject (or Audience if defined)
|
||||||
// g. The command of each delegation must "allow" the one before it.
|
// g. The command of each delegation must "allow" the one before it.
|
||||||
//
|
//
|
||||||
// 5. If steps 1-4 pass:
|
// 5. If steps 1-4 pass:
|
||||||
@@ -58,18 +58,18 @@ func (t *Token) verifyProofs(delegations []*delegation.Token) error {
|
|||||||
|
|
||||||
cmd := t.command
|
cmd := t.command
|
||||||
iss := t.issuer
|
iss := t.issuer
|
||||||
aud := t.audience
|
sub := t.subject
|
||||||
if !aud.Defined() {
|
if t.audience.Defined() {
|
||||||
aud = t.subject
|
sub = t.audience
|
||||||
}
|
}
|
||||||
|
|
||||||
// control from the invocation to the root
|
// control from the invocation to the root
|
||||||
for i, dlgCid := range t.proof {
|
for i, dlgCid := range t.proof {
|
||||||
dlg := delegations[i]
|
dlg := delegations[i]
|
||||||
|
|
||||||
// The Subject of each delegation must equal the invocation's Audience field. - 4f
|
// The Subject of each delegation must equal the invocation's Subject (or Audience if defined). - 4f
|
||||||
if dlg.Subject() != aud {
|
if dlg.Subject() != sub {
|
||||||
return fmt.Errorf("%w: delegation %s, expected %s, got %s", ErrWrongSub, dlgCid, aud, dlg.Subject())
|
return fmt.Errorf("%w: delegation %s, expected %s, got %s", ErrWrongSub, dlgCid, sub, dlg.Subject())
|
||||||
}
|
}
|
||||||
|
|
||||||
// The first proof must be issued to the Invoker (audience DID). - 4c
|
// The first proof must be issued to the Invoker (audience DID). - 4c
|
||||||
|
|||||||
Reference in New Issue
Block a user