client: follow go-ucan changes, improvements, example
This commit is contained in:
committed by
Michael Muré
parent
1187674a24
commit
2eeaaccc6d
@@ -38,9 +38,9 @@ func (c *Client) PrepareInvoke(ctx context.Context, cmd command.Command, subject
|
|||||||
var proof []cid.Cid
|
var proof []cid.Cid
|
||||||
|
|
||||||
// do we already have a valid proof?
|
// do we already have a valid proof?
|
||||||
if proof = c.pool.FindProof(cmd, c.did, subject); len(proof) == 0 {
|
if proof = c.pool.FindProof(c.did, cmd, subject); len(proof) == 0 {
|
||||||
// we need to request a new proof
|
// we need to request a new proof
|
||||||
proofBundles, err := c.requester.RequestDelegation(ctx, cmd, c.did, subject)
|
proofBundles, err := c.requester.RequestDelegation(ctx, c.did, cmd, subject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("requesting delegation: %w", err)
|
return nil, fmt.Errorf("requesting delegation: %w", err)
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ func (c *Client) PrepareInvoke(ctx context.Context, cmd command.Command, subject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inv, err := invocation.New(c.did, subject, cmd, proof, opts...)
|
inv, err := invocation.New(c.did, cmd, subject, proof, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
76
toolkit/client/client_test.go
Normal file
76
toolkit/client/client_test.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"iter"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
|
"github.com/ucan-wg/go-ucan/did/didtest"
|
||||||
|
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||||
|
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||||
|
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||||
|
"github.com/ucan-wg/go-ucan/token/invocation"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleNewClient() {
|
||||||
|
servicePersona := didtest.PersonaAlice
|
||||||
|
clientPersona := didtest.PersonaBob
|
||||||
|
|
||||||
|
// requester is an adaptor for a real world issuer, we use a mock in that example
|
||||||
|
requester := &requesterMock{persona: servicePersona}
|
||||||
|
|
||||||
|
client, err := NewClient(clientPersona.PrivKey(), requester)
|
||||||
|
handleError(err)
|
||||||
|
|
||||||
|
cont, err := client.PrepareInvoke(
|
||||||
|
context.Background(),
|
||||||
|
command.New("crud", "add"),
|
||||||
|
servicePersona.DID(),
|
||||||
|
// extra invocation parameters:
|
||||||
|
invocation.WithExpirationIn(10*time.Minute),
|
||||||
|
invocation.WithArgument("foo", "bar"),
|
||||||
|
invocation.WithMeta("baz", 1234),
|
||||||
|
)
|
||||||
|
handleError(err)
|
||||||
|
|
||||||
|
// this container holds the invocation and all the delegation proofs
|
||||||
|
b64, err := cont.ToCborBase64()
|
||||||
|
handleError(err)
|
||||||
|
|
||||||
|
fmt.Println(string(b64))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleError(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type requesterMock struct {
|
||||||
|
persona didtest.Persona
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r requesterMock) RequestDelegation(_ context.Context, audience did.DID, cmd command.Command, _ did.DID) (iter.Seq2[*delegation.Bundle, error], error) {
|
||||||
|
// the mock issue whatever the client asks:
|
||||||
|
dlg, err := delegation.Root(r.persona.DID(), audience, cmd, policy.Policy{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dlgBytes, dlgCid, err := dlg.ToSealed(r.persona.PrivKey())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bundle := &delegation.Bundle{
|
||||||
|
Cid: dlgCid,
|
||||||
|
Decoded: dlg,
|
||||||
|
Sealed: dlgBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(yield func(*delegation.Bundle, error) bool) {
|
||||||
|
yield(bundle, nil)
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -34,12 +34,13 @@ func (p *Pool) AddBundles(bundles iter.Seq[*delegation.Bundle]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FindProof find in the pool the best (shortest, smallest in bytes) chain of delegation(s) matching the given invocation parameters.
|
// FindProof find in the pool the best (shortest, smallest in bytes) chain of delegation(s) matching the given invocation parameters.
|
||||||
// - cmd: the command to execute
|
|
||||||
// - issuer: the DID of the client, also the issuer of the invocation token
|
// - issuer: the DID of the client, also the issuer of the invocation token
|
||||||
// - audience: the DID of the resource to operate on, also the subject (or audience if defined) of the invocation token
|
// - cmd: the command to execute
|
||||||
|
// - subject: the DID of the resource to operate on, also the subject (or audience if defined) of the invocation token
|
||||||
|
// Note: you can read it as "(issuer) wants to do (cmd) on (subject)".
|
||||||
// Note: the returned delegation(s) don't have to match exactly the parameters, as long as they allow them.
|
// Note: the returned delegation(s) don't have to match exactly the parameters, as long as they allow them.
|
||||||
// Note: the implemented algorithm won't perform well with a large number of delegations.
|
// Note: the implemented algorithm won't perform well with a large number of delegations.
|
||||||
func (p *Pool) FindProof(cmd command.Command, iss did.DID, aud did.DID) []cid.Cid {
|
func (p *Pool) FindProof(issuer did.DID, cmd command.Command, subject did.DID) []cid.Cid {
|
||||||
// TODO: move to some kind of background trim job?
|
// TODO: move to some kind of background trim job?
|
||||||
p.trim()
|
p.trim()
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ func (p *Pool) FindProof(cmd command.Command, iss did.DID, aud did.DID) []cid.Ci
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, cmd, iss, aud)
|
}, issuer, cmd, subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pool) GetBundles(cids []cid.Cid) iter.Seq2[*delegation.Bundle, error] {
|
func (p *Pool) GetBundles(cids []cid.Cid) iter.Seq2[*delegation.Bundle, error] {
|
||||||
|
|||||||
@@ -11,21 +11,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// FindProof find in the pool the best (shortest, smallest in bytes) chain of delegation(s) matching the given invocation parameters.
|
// FindProof find in the pool the best (shortest, smallest in bytes) chain of delegation(s) matching the given invocation parameters.
|
||||||
// - cmd: the command to execute
|
|
||||||
// - issuer: the DID of the client, also the issuer of the invocation token
|
// - issuer: the DID of the client, also the issuer of the invocation token
|
||||||
// - audience: the DID of the resource to operate on, also the subject (or audience if defined) of the invocation token
|
// - cmd: the command to execute
|
||||||
|
// - subject: the DID of the resource to operate on, also the subject (or audience if defined) of the invocation token
|
||||||
|
// Note: you can read it as "(issuer) wants to do (cmd) on (subject)".
|
||||||
// Note: the returned delegation(s) don't have to match exactly the parameters, as long as they allow them.
|
// Note: the returned delegation(s) don't have to match exactly the parameters, as long as they allow them.
|
||||||
// Note: the implemented algorithm won't perform well with a large number of delegations.
|
// Note: the implemented algorithm won't perform well with a large number of delegations.
|
||||||
func FindProof(dlgs func() iter.Seq[*delegation.Bundle], cmd command.Command, iss did.DID, aud did.DID) []cid.Cid {
|
func FindProof(dlgs func() iter.Seq[*delegation.Bundle], issuer did.DID, cmd command.Command, subject did.DID) []cid.Cid {
|
||||||
// TODO: maybe that should be part of delegation.Token directly?
|
// TODO: maybe that should be part of delegation.Token directly?
|
||||||
dlgMatch := func(dlg *delegation.Token, cmd command.Command, aud, iss did.DID) bool {
|
dlgMatch := func(dlg *delegation.Token, issuer did.DID, cmd command.Command, subject did.DID) bool {
|
||||||
// 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() != subject {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// The first proof must be issued to the Invoker (audience DID). - 4c
|
// The first proof must be issued to the Invoker (audience DID). - 4c
|
||||||
// The Issuer of each delegation must be the Audience in the next one. - 4d
|
// The Issuer of each delegation must be the Audience in the next one. - 4d
|
||||||
if dlg.Audience() != iss {
|
if dlg.Audience() != issuer {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// The command of each delegation must "allow" the one before it. - 4g
|
// The command of each delegation must "allow" the one before it. - 4g
|
||||||
@@ -43,7 +44,7 @@ func FindProof(dlgs func() iter.Seq[*delegation.Bundle], cmd command.Command, is
|
|||||||
var candidateLeaf []*delegation.Bundle
|
var candidateLeaf []*delegation.Bundle
|
||||||
|
|
||||||
for bundle := range dlgs() {
|
for bundle := range dlgs() {
|
||||||
if dlgMatch(bundle.Decoded, cmd, iss, aud) {
|
if dlgMatch(bundle.Decoded, issuer, cmd, subject) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
candidateLeaf = append(candidateLeaf, bundle)
|
candidateLeaf = append(candidateLeaf, bundle)
|
||||||
@@ -79,7 +80,7 @@ func FindProof(dlgs func() iter.Seq[*delegation.Bundle], cmd command.Command, is
|
|||||||
|
|
||||||
// find parent delegation for our current delegation
|
// find parent delegation for our current delegation
|
||||||
for candidate := range dlgs() {
|
for candidate := range dlgs() {
|
||||||
if !dlgMatch(candidate.Decoded, at.Decoded.Command(), aud, at.Decoded.Issuer()) {
|
if !dlgMatch(candidate.Decoded, at.Decoded.Issuer(), at.Decoded.Command(), subject) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,16 +18,16 @@ func TestFindProof(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
require.Equal(t, delegationtest.ProofAliceBob,
|
require.Equal(t, delegationtest.ProofAliceBob,
|
||||||
FindProof(dlgs, delegationtest.NominalCommand, didtest.PersonaBob.DID(), didtest.PersonaAlice.DID()))
|
FindProof(dlgs, didtest.PersonaBob.DID(), delegationtest.NominalCommand, didtest.PersonaAlice.DID()))
|
||||||
require.Equal(t, delegationtest.ProofAliceBobCarol,
|
require.Equal(t, delegationtest.ProofAliceBobCarol,
|
||||||
FindProof(dlgs, delegationtest.NominalCommand, didtest.PersonaCarol.DID(), didtest.PersonaAlice.DID()))
|
FindProof(dlgs, didtest.PersonaCarol.DID(), delegationtest.NominalCommand, didtest.PersonaAlice.DID()))
|
||||||
require.Equal(t, delegationtest.ProofAliceBobCarolDan,
|
require.Equal(t, delegationtest.ProofAliceBobCarolDan,
|
||||||
FindProof(dlgs, delegationtest.NominalCommand, didtest.PersonaDan.DID(), didtest.PersonaAlice.DID()))
|
FindProof(dlgs, didtest.PersonaDan.DID(), delegationtest.NominalCommand, didtest.PersonaAlice.DID()))
|
||||||
require.Equal(t, delegationtest.ProofAliceBobCarolDanErin,
|
require.Equal(t, delegationtest.ProofAliceBobCarolDanErin,
|
||||||
FindProof(dlgs, delegationtest.NominalCommand, didtest.PersonaErin.DID(), didtest.PersonaAlice.DID()))
|
FindProof(dlgs, didtest.PersonaErin.DID(), delegationtest.NominalCommand, didtest.PersonaAlice.DID()))
|
||||||
require.Equal(t, delegationtest.ProofAliceBobCarolDanErinFrank,
|
require.Equal(t, delegationtest.ProofAliceBobCarolDanErinFrank,
|
||||||
FindProof(dlgs, delegationtest.NominalCommand, didtest.PersonaFrank.DID(), didtest.PersonaAlice.DID()))
|
FindProof(dlgs, didtest.PersonaFrank.DID(), delegationtest.NominalCommand, didtest.PersonaAlice.DID()))
|
||||||
|
|
||||||
// wrong command
|
// wrong command
|
||||||
require.Empty(t, FindProof(dlgs, command.New("foo"), didtest.PersonaBob.DID(), didtest.PersonaAlice.DID()))
|
require.Empty(t, FindProof(dlgs, didtest.PersonaBob.DID(), command.New("foo"), didtest.PersonaAlice.DID()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package client
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"iter"
|
"iter"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/avast/retry-go/v4"
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||||
"github.com/ucan-wg/go-ucan/token/delegation"
|
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||||
@@ -12,8 +14,37 @@ import (
|
|||||||
type DelegationRequester interface {
|
type DelegationRequester interface {
|
||||||
// RequestDelegation retrieve a delegation or chain of delegation for the given parameters.
|
// RequestDelegation retrieve a delegation or chain of delegation for the given parameters.
|
||||||
// - cmd: the command to execute
|
// - cmd: the command to execute
|
||||||
// - issuer: the DID of the client, also the issuer of the invocation token
|
// - audience: the DID of the client, also the issuer of the invocation token
|
||||||
// - audience: the DID of the resource to operate on, also the subject (or audience if defined) of the invocation token
|
// - subject: the DID of the resource to operate on, also the subject (or audience if defined) of the invocation token
|
||||||
|
// Note: you can read it as "(audience) wants to do (cmd) on (subject)".
|
||||||
// Note: the returned delegation(s) don't have to match exactly the parameters, as long as they allow them.
|
// Note: the returned delegation(s) don't have to match exactly the parameters, as long as they allow them.
|
||||||
RequestDelegation(ctx context.Context, cmd command.Command, audience did.DID, subject did.DID) (iter.Seq2[*delegation.Bundle, error], error)
|
RequestDelegation(ctx context.Context, audience did.DID, cmd command.Command, subject did.DID) (iter.Seq2[*delegation.Bundle, error], error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ DelegationRequester = &withRetry{}
|
||||||
|
|
||||||
|
type withRetry struct {
|
||||||
|
requester DelegationRequester
|
||||||
|
initialDelay time.Duration
|
||||||
|
maxAttempts uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequesterWithRetry wraps a DelegationRequester to perform exponential backoff,
|
||||||
|
// with an initial delay and a maximum attempt count.
|
||||||
|
func RequesterWithRetry(requester DelegationRequester, initialDelay time.Duration, maxAttempt uint) DelegationRequester {
|
||||||
|
return &withRetry{
|
||||||
|
requester: requester,
|
||||||
|
initialDelay: initialDelay,
|
||||||
|
maxAttempts: maxAttempt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w withRetry) RequestDelegation(ctx context.Context, audience did.DID, cmd command.Command, subject did.DID) (iter.Seq2[*delegation.Bundle, error], error) {
|
||||||
|
return retry.DoWithData(func() (iter.Seq2[*delegation.Bundle, error], error) {
|
||||||
|
return w.requester.RequestDelegation(ctx, audience, cmd, subject)
|
||||||
|
},
|
||||||
|
retry.Context(ctx),
|
||||||
|
retry.Delay(w.initialDelay),
|
||||||
|
retry.Attempts(w.maxAttempts),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user