example: add sub-delegation

This commit is contained in:
Michael Muré
2025-02-06 14:19:30 +01:00
committed by Michael Muré
parent 55f38fef4a
commit c670433335
13 changed files with 478 additions and 228 deletions

View File

@@ -0,0 +1,9 @@
![scenario 1](scenario1.png)
![scenario 2](scenario2.png)
TODO
- differences with a real system
- issuer protocol + token exchange
- opinionated with HTTP
- toolkit is helpers, you can change or write your own thing

View File

@@ -0,0 +1,41 @@
package protocol
import (
"encoding/json"
"net/http"
"github.com/ucan-wg/go-ucan/did"
"github.com/ucan-wg/go-ucan/pkg/command"
"github.com/INFURA/go-ucan-toolkit/issuer"
)
func RequestResolver(r *http.Request) (*issuer.ResolvedRequest, error) {
// Let's make up a simple json protocol
req := struct {
Audience string `json:"aud"`
Cmd string `json:"cmd"`
Subject string `json:"sub"`
}{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
return nil, err
}
aud, err := did.Parse(req.Audience)
if err != nil {
return nil, err
}
cmd, err := command.Parse(req.Cmd)
if err != nil {
return nil, err
}
sub, err := did.Parse(req.Subject)
if err != nil {
return nil, err
}
return &issuer.ResolvedRequest{
Audience: aud,
Cmd: cmd,
Subject: sub,
}, nil
}

View File

@@ -0,0 +1,59 @@
package protocol
import (
"bytes"
"context"
"encoding/json"
"iter"
"log"
"net/http"
"github.com/ucan-wg/go-ucan/did"
"github.com/ucan-wg/go-ucan/pkg/command"
"github.com/ucan-wg/go-ucan/token/delegation"
"github.com/INFURA/go-ucan-toolkit/client"
"github.com/INFURA/go-ucan-toolkit/issuer"
)
var _ client.DelegationRequester = &Requester{}
type Requester struct {
issuerURL string
}
func NewRequester(issuerURL string) *Requester {
return &Requester{issuerURL: issuerURL}
}
func (r Requester) RequestDelegation(ctx context.Context, audience did.DID, cmd command.Command, subject did.DID) (iter.Seq2[*delegation.Bundle, error], error) {
log.Printf("requesting delegation for %s on %s", cmd, subject)
// we match the simple json protocol of the issuer
data := struct {
Audience string `json:"aud"`
Cmd string `json:"cmd"`
Subject string `json:"sub"`
}{
Audience: audience.String(),
Cmd: cmd.String(),
Subject: subject.String(),
}
buf := &bytes.Buffer{}
err := json.NewEncoder(buf).Encode(data)
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, "http://"+r.issuerURL, buf)
if err != nil {
return nil, err
}
res, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
return nil, err
}
return issuer.DecodeResponse(res)
}

View File

@@ -0,0 +1,153 @@
package main
import (
"context"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"os/signal"
"syscall"
"time"
"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/pkg/container"
"github.com/ucan-wg/go-ucan/pkg/policy"
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
"github.com/ucan-wg/go-ucan/token/delegation"
example "github.com/INFURA/go-ucan-toolkit/_example"
protocol "github.com/INFURA/go-ucan-toolkit/_example/_protocol-issuer"
"github.com/INFURA/go-ucan-toolkit/client"
"github.com/INFURA/go-ucan-toolkit/issuer"
"github.com/INFURA/go-ucan-toolkit/server/bearer"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
// register as handler of the interrupt signal to trigger the teardown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
go func() {
<-quit
cancel()
}()
err := run(ctx,
example.AliceIssuerUrl, example.AlicePrivKey, example.AliceDid,
example.ServiceIssuerUrl, example.ServiceUrl, example.ServiceDid)
if err != nil {
log.Println(err)
os.Exit(1)
}
}
func run(ctx context.Context, ownIssuerUrl string, priv crypto.PrivKey, d did.DID,
serviceIssuerUrl string, serviceUrl string, serviceDid did.DID) error {
log.Printf("Alice DID is %s", d.String())
issuingLogic := func(iss did.DID, aud did.DID, cmd command.Command, subject did.DID) (*delegation.Token, error) {
log.Printf("issuing delegation to %v for %v to operate on %v", aud, cmd, subject)
// As another example, we'll force Bob to use a specific HTTP sub-path
policies, err := policy.Construct(
policy.Equal(".http.path", literal.String(fmt.Sprintf("/%s/%s", iss.String(), aud.String()))),
)
if err != nil {
return nil, err
}
return delegation.New(iss, aud, cmd, policies, subject)
}
cli, err := client.NewWithIssuer(priv, protocol.NewRequester(serviceIssuerUrl), issuingLogic)
if err != nil {
return err
}
go startIssuerHttp(ctx, ownIssuerUrl, cli)
for {
proofs, err := cli.PrepareInvoke(ctx, command.MustParse("/foo/bar"), serviceDid)
if err != nil {
return err
}
err = makeRequest(ctx, d, serviceUrl, proofs)
if err != nil {
log.Println(err)
}
select {
case <-ctx.Done():
return nil
case <-time.After(20 * time.Second):
}
}
}
func startIssuerHttp(ctx context.Context, issuerUrl string, cli *client.WithIssuer) {
handler := issuer.HttpWrapper(cli, protocol.RequestResolver)
srv := &http.Server{
Addr: issuerUrl,
Handler: handler,
}
go func() {
if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("listen: %s\n", err)
}
}()
log.Printf("issuer listening on %s\n", srv.Addr)
<-ctx.Done()
if err := srv.Shutdown(ctx); err != nil && !errors.Is(err, context.Canceled) {
log.Printf("issuer error: %v\n", err)
}
}
func makeRequest(ctx context.Context, clientDid did.DID, serviceUrl string, proofs container.Writer) error {
// we construct a URL that include the client DID as path, as requested by the UCAN policy we get issued
u, err := url.JoinPath("http://"+serviceUrl, clientDid.String())
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodGet, u, nil)
if err != nil {
return err
}
err = bearer.AddBearerContainerCompressed(req.Header, proofs)
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
body, err := io.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("unexpected status code: %d, error reading body: %w", res.StatusCode, err)
}
return fmt.Errorf("unexpected status code: %d, body: %v", res.StatusCode, string(body))
}
log.Printf("response status code: %d", res.StatusCode)
return nil
}

View File

@@ -0,0 +1,111 @@
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"os/signal"
"syscall"
"time"
"github.com/ucan-wg/go-ucan/did"
"github.com/ucan-wg/go-ucan/pkg/command"
"github.com/ucan-wg/go-ucan/pkg/container"
example "github.com/INFURA/go-ucan-toolkit/_example"
protocol "github.com/INFURA/go-ucan-toolkit/_example/_protocol-issuer"
"github.com/INFURA/go-ucan-toolkit/client"
"github.com/INFURA/go-ucan-toolkit/server/bearer"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
// register as handler of the interrupt signal to trigger the teardown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
go func() {
<-quit
cancel()
}()
err := run(ctx, example.AliceIssuerUrl, example.AliceDid, example.ServiceUrl, example.ServiceDid)
if err != nil {
log.Println(err)
os.Exit(1)
}
}
func run(ctx context.Context, aliceUrl string, aliceDid did.DID, serverUrl string, serviceDid did.DID) error {
// Let's generate a keypair for our client:
priv, d, err := did.GenerateEd25519()
if err != nil {
return err
}
log.Printf("Bob DID is %s", d.String())
cli, err := client.NewClient(priv, protocol.NewRequester(aliceUrl))
if err != nil {
return err
}
for {
proofs, err := cli.PrepareInvoke(ctx, command.MustParse("/foo/bar"), serviceDid)
if err != nil {
return err
}
err = makeRequest(ctx, d, serverUrl, aliceDid, proofs)
if err != nil {
log.Println(err)
}
select {
case <-ctx.Done():
return nil
case <-time.After(5 * time.Second):
}
}
}
func makeRequest(ctx context.Context, clientDid did.DID, serviceUrl string, aliceDid did.DID, proofs container.Writer) error {
// we construct a URL that include the our DID and Alice DID as path, as requested by the UCAN policy we get issued
u, err := url.JoinPath("http://"+serviceUrl, aliceDid.String(), clientDid.String())
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodGet, u, nil)
if err != nil {
return err
}
err = bearer.AddBearerContainerCompressed(req.Header, proofs)
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
body, err := io.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("unexpected status code: %d, error reading body: %w", res.StatusCode, err)
}
return fmt.Errorf("unexpected status code: %d, body: %v", res.StatusCode, string(body))
}
log.Printf("response status code: %d", res.StatusCode)
return nil
}

View File

@@ -1,151 +0,0 @@
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"iter"
"log"
"net/http"
"net/url"
"os"
"os/signal"
"syscall"
"time"
"github.com/ucan-wg/go-ucan/did"
"github.com/ucan-wg/go-ucan/pkg/command"
"github.com/ucan-wg/go-ucan/pkg/container"
"github.com/ucan-wg/go-ucan/token/delegation"
example "github.com/INFURA/go-ucan-toolkit/_example"
"github.com/INFURA/go-ucan-toolkit/client"
"github.com/INFURA/go-ucan-toolkit/issuer"
"github.com/INFURA/go-ucan-toolkit/server/bearer"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
// register as handler of the interrupt signal to trigger the teardown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
go func() {
<-quit
cancel()
}()
err := run(ctx, example.IssuerUrl, example.ServerUrl, example.ServiceDid)
if err != nil {
log.Println(err)
os.Exit(1)
}
}
var _ client.DelegationRequester = &requester{}
type requester struct {
issuerURL string
}
func (r requester) RequestDelegation(ctx context.Context, audience did.DID, cmd command.Command, subject did.DID) (iter.Seq2[*delegation.Bundle, error], error) {
log.Printf("requesting delegation for %s on %s", cmd, subject)
// we match the simple json protocol of the issuer
data := struct {
Audience string `json:"aud"`
Cmd string `json:"cmd"`
Subject string `json:"sub"`
}{
Audience: audience.String(),
Cmd: cmd.String(),
Subject: subject.String(),
}
buf := &bytes.Buffer{}
err := json.NewEncoder(buf).Encode(data)
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, "http://"+r.issuerURL, buf)
if err != nil {
return nil, err
}
res, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
return nil, err
}
return issuer.DecodeResponse(res)
}
func run(ctx context.Context, issuerUrl string, serverUrl string, serviceDid did.DID) error {
// Let's generate a keypair for our client:
priv, d, err := did.GenerateEd25519()
if err != nil {
return err
}
log.Printf("client DID is %s", d.String())
cli, err := client.NewClient(priv, requester{issuerURL: issuerUrl})
if err != nil {
return err
}
for {
select {
case <-ctx.Done():
case <-time.After(5 * time.Second):
proofs, err := cli.PrepareInvoke(ctx, command.MustParse("/foo/bar"), serviceDid)
if err != nil {
return err
}
err = makeRequest(ctx, d, serverUrl, proofs)
if err != nil {
log.Println(err)
}
}
}
}
func makeRequest(ctx context.Context, clientDid did.DID, serverUrl string, proofs container.Writer) error {
// we construct a URL that include the client DID as path, as requested by the UCAN policy we get issued
u, err := url.JoinPath("http://"+serverUrl, clientDid.String())
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodGet, u, nil)
if err != nil {
return err
}
err = bearer.AddBearerContainerCompressed(req.Header, proofs)
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
body, err := io.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("unexpected status code: %d, error reading body: %w", res.StatusCode, err)
}
return fmt.Errorf("unexpected status code: %d, body: %v", res.StatusCode, string(body))
}
log.Printf("response status code: %d", res.StatusCode)
return nil
}

View File

@@ -0,0 +1,57 @@
@startuml
left to right direction
rectangle Service as owner {
rectangle Issuer as issuer
rectangle Executor as exec
}
node resource as res
owner --> res : Controls
exec --> res : Allow access to
rectangle "Alice" as alice {
rectangle Client as aliceclient
}
aliceclient --> issuer : [1] request delegation
aliceclient <-- issuer : [2] issue delegation A
aliceclient --> exec : [3] make request with A
@enduml
@startuml
left to right direction
rectangle Service as owner {
rectangle Issuer as issuer
rectangle Executor as exec
}
node resource as res
owner --> res : Controls
exec --> res : Allow access to
rectangle "Alice" as alice {
rectangle Client as aliceclient
rectangle Issuer as aliceissuer
}
aliceclient --> issuer : [1] request delegation
aliceclient <-- issuer : [2] issue delegation A
aliceclient --> exec : [3] make request with A
rectangle "Bob" as bob {
rectangle Client as bobclient
}
bobclient --> aliceissuer : [4] request delegation
bobclient <-- aliceissuer : [5] issue delegation B\nalso returns A
bobclient -down-> exec : [6] make request with A+B
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -2,7 +2,6 @@ package main
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"log" "log"
@@ -20,6 +19,7 @@ import (
"github.com/ucan-wg/go-ucan/token/delegation" "github.com/ucan-wg/go-ucan/token/delegation"
example "github.com/INFURA/go-ucan-toolkit/_example" example "github.com/INFURA/go-ucan-toolkit/_example"
protocol "github.com/INFURA/go-ucan-toolkit/_example/_protocol-issuer"
"github.com/INFURA/go-ucan-toolkit/issuer" "github.com/INFURA/go-ucan-toolkit/issuer"
) )
@@ -35,7 +35,7 @@ func main() {
cancel() cancel()
}() }()
err := run(ctx, example.IssuerUrl, example.ServicePrivKey) err := run(ctx, example.ServiceIssuerUrl, example.ServicePrivKey)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
os.Exit(1) os.Exit(1)
@@ -50,7 +50,12 @@ func run(ctx context.Context, issuerUrl string, servicePrivKey crypto.PrivKey) e
// Here, we enforce that the caller uses its own DID endpoint (an arbitrary construct for this example). // Here, we enforce that the caller uses its own DID endpoint (an arbitrary construct for this example).
// You will notice that the server doesn't need to know about this logic to enforce it. // You will notice that the server doesn't need to know about this logic to enforce it.
policies, err := policy.Construct( policies, err := policy.Construct(
policy.Equal(".http.path", literal.String(fmt.Sprintf("/%s", aud.String()))), policy.Or(
// allow exact path
policy.Equal(".http.path", literal.String(fmt.Sprintf("/%s", aud.String()))),
// allow sub-path
policy.Like(".http.path", fmt.Sprintf("/%s/*", aud.String())),
),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -67,35 +72,7 @@ func run(ctx context.Context, issuerUrl string, servicePrivKey crypto.PrivKey) e
return err return err
} }
handler := issuer.HttpWrapper(rootIssuer, func(r *http.Request) (*issuer.ResolvedRequest, error) { handler := issuer.HttpWrapper(rootIssuer, protocol.RequestResolver)
// Let's make up a simple json protocol
req := struct {
Audience string `json:"aud"`
Cmd string `json:"cmd"`
Subject string `json:"sub"`
}{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
return nil, err
}
aud, err := did.Parse(req.Audience)
if err != nil {
return nil, err
}
cmd, err := command.Parse(req.Cmd)
if err != nil {
return nil, err
}
sub, err := did.Parse(req.Subject)
if err != nil {
return nil, err
}
return &issuer.ResolvedRequest{
Audience: aud,
Cmd: cmd,
Subject: sub,
}, nil
})
srv := &http.Server{ srv := &http.Server{
Addr: issuerUrl, Addr: issuerUrl,

View File

@@ -27,14 +27,16 @@ func main() {
cancel() cancel()
}() }()
err := run(ctx, example.ServerUrl, example.ServiceDid) err := run(ctx, example.ServiceUrl, example.ServiceDid)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
os.Exit(1) os.Exit(1)
} }
} }
func run(ctx context.Context, serverUrl string, serviceDID did.DID) error { func run(ctx context.Context, serviceUrl string, serviceDID did.DID) error {
log.Printf("service DID is %s\n", serviceDID.String())
// we'll make a simple handling pipeline: // we'll make a simple handling pipeline:
// - exectx.ExtractMW to extract and decode the UCAN context, verify the service DID // - exectx.ExtractMW to extract and decode the UCAN context, verify the service DID
// - exectx.HttpExtArgsVerify to verify the HTTP policies // - exectx.HttpExtArgsVerify to verify the HTTP policies
@@ -50,7 +52,8 @@ func run(ctx context.Context, serverUrl string, serviceDID did.DID) error {
switch ucanCtx.Command().String() { switch ucanCtx.Command().String() {
case "/foo/bar": case "/foo/bar":
log.Printf("handled command %v for %v", ucanCtx.Command(), ucanCtx.Invocation().Issuer()) log.Printf("handled command %v at %v for %v", ucanCtx.Command(), r.URL.Path, ucanCtx.Invocation().Issuer())
log.Printf("proof is %v", ucanCtx.Invocation().Proof())
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK")) _, _ = w.Write([]byte("OK"))
default: default:
@@ -64,7 +67,7 @@ func run(ctx context.Context, serverUrl string, serviceDID did.DID) error {
handler = exectx.ExtractMW(handler, serviceDID) handler = exectx.ExtractMW(handler, serviceDID)
srv := &http.Server{ srv := &http.Server{
Addr: serverUrl, Addr: serviceUrl,
Handler: handler, Handler: handler,
} }

View File

@@ -9,16 +9,27 @@ import (
// Endpoints // Endpoints
var ServerUrl = ":8080" var ServiceUrl = ":8080"
var IssuerUrl = ":8081" var ServiceIssuerUrl = ":8081"
var AliceIssuerUrl = ":8082"
// Service // Service
var ServicePrivKey crypto.PrivKey var ServicePrivKey crypto.PrivKey
var ServiceDid did.DID var ServiceDid did.DID
// Alice
var AlicePrivKey crypto.PrivKey
var AliceDid did.DID
func init() { func init() {
privRaw, _ := base64.StdEncoding.DecodeString("CAESQGs7hPBRBmxH1UmHrdcPrBkecuFUuCWHK0kMJvZYCBqIa35SGxUdXVGuigQDkMpf7xO4C2C2Acl8QTtSrYS7Cnc=") servPrivRaw, _ := base64.StdEncoding.DecodeString("CAESQGs7hPBRBmxH1UmHrdcPrBkecuFUuCWHK0kMJvZYCBqIa35SGxUdXVGuigQDkMpf7xO4C2C2Acl8QTtSrYS7Cnc=")
ServicePrivKey, _ = crypto.UnmarshalPrivateKey(privRaw) ServicePrivKey, _ = crypto.UnmarshalPrivateKey(servPrivRaw)
ServiceDid, _ = did.FromPrivKey(ServicePrivKey) ServiceDid, _ = did.FromPrivKey(ServicePrivKey)
alicePrivRaw, _ := base64.StdEncoding.DecodeString("CAESQFESA31nDYUhXXwbCNSFvg7M+TOFgyxy0tVX6o+TkJAKqAwDvtGxZeGyUjibGd/op+xOLvzE6BrTIOw62K3yLp8=")
AlicePrivKey, _ = crypto.UnmarshalPrivateKey(alicePrivRaw)
AliceDid, _ = did.FromPrivKey(AlicePrivKey)
} }

View File

@@ -1,18 +1,15 @@
package issuer package client
import ( import (
"context" "context"
"fmt" "fmt"
"iter" "iter"
"time"
"github.com/ipfs/go-cid" "github.com/ipfs/go-cid"
"github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/crypto"
"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"
"github.com/INFURA/go-ucan-toolkit/client"
) )
// DlgIssuingLogic is a function that decides what powers are given to a client. // DlgIssuingLogic is a function that decides what powers are given to a client.
@@ -25,32 +22,19 @@ import (
// expected action. If you don't want to give that power, return an error instead. // expected action. If you don't want to give that power, return an error instead.
type DlgIssuingLogic func(iss did.DID, aud did.DID, cmd command.Command, subject did.DID) (*delegation.Token, error) type DlgIssuingLogic func(iss did.DID, aud did.DID, cmd command.Command, subject did.DID) (*delegation.Token, error)
var _ client.DelegationRequester = &Issuer{} var _ DelegationRequester = &WithIssuer{}
// Issuer is an implementation of a re-delegating issuer. type WithIssuer struct {
// Note: Your actual needs for an issuer can easily be different (caching...) than the choices made here. *Client
// Feel free to replace this component with your own flavor. logic DlgIssuingLogic
type Issuer struct {
did did.DID
privKey crypto.PrivKey
pool *client.Pool
requester client.DelegationRequester
logic DlgIssuingLogic
} }
func NewIssuer(privKey crypto.PrivKey, requester client.DelegationRequester, logic DlgIssuingLogic) (*Issuer, error) { func NewWithIssuer(privKey crypto.PrivKey, requester DelegationRequester, logic DlgIssuingLogic) (*WithIssuer, error) {
d, err := did.FromPrivKey(privKey) client, err := NewClient(privKey, requester)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Issuer{ return &WithIssuer{Client: client, logic: logic}, nil
did: d,
privKey: privKey,
pool: client.NewPool(),
requester: client.RequesterWithRetry(requester, time.Second, 3),
logic: logic,
}, nil
} }
// RequestDelegation retrieve chain of delegation for the given parameters. // RequestDelegation retrieve chain of delegation for the given parameters.
@@ -59,22 +43,18 @@ func NewIssuer(privKey crypto.PrivKey, requester client.DelegationRequester, log
// - subject: 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) does (cmd) on (subject)". // Note: you can read it as "(audience) does (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.
func (i *Issuer) RequestDelegation(ctx context.Context, audience did.DID, cmd command.Command, subject did.DID) (iter.Seq2[*delegation.Bundle, error], error) { func (c *WithIssuer) RequestDelegation(ctx context.Context, audience did.DID, cmd command.Command, subject did.DID) (iter.Seq2[*delegation.Bundle, error], error) {
if subject != i.did {
return nil, fmt.Errorf("subject DID doesn't match the issuer DID")
}
var proof []cid.Cid var proof []cid.Cid
// is there already a valid proof chain? // is there already a valid proof chain?
if proof = i.pool.FindProof(audience, cmd, subject); len(proof) > 0 { if proof = c.pool.FindProof(audience, cmd, subject); len(proof) > 0 {
return i.pool.GetBundles(proof), nil return c.pool.GetBundles(proof), nil
} }
// do we have the power to delegate this? // do we have the power to delegate this?
if proof = i.pool.FindProof(i.did, cmd, 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 := i.requester.RequestDelegation(ctx, i.did, cmd, subject) proofBundles, err := c.requester.RequestDelegation(ctx, c.did, cmd, subject)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -85,12 +65,12 @@ func (i *Issuer) RequestDelegation(ctx context.Context, audience did.DID, cmd co
return nil, err return nil, err
} }
proof = append(proof, bundle.Cid) proof = append(proof, bundle.Cid)
i.pool.AddBundle(bundle) c.pool.AddBundle(bundle)
} }
} }
// run the custom logic to get what we actually issue // run the custom logic to get what we actually issue
dlg, err := i.logic(i.did, audience, cmd, subject) dlg, err := c.logic(c.did, audience, cmd, subject)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -99,7 +79,7 @@ func (i *Issuer) RequestDelegation(ctx context.Context, audience did.DID, cmd co
} }
// sign and cache the new token // sign and cache the new token
dlgBytes, dlgCid, err := dlg.ToSealed(i.privKey) dlgBytes, dlgCid, err := dlg.ToSealed(c.privKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -114,7 +94,7 @@ func (i *Issuer) RequestDelegation(ctx context.Context, audience did.DID, cmd co
if !yield(bundle, nil) { if !yield(bundle, nil) {
return return
} }
for b, err := range i.pool.GetBundles(proof) { for b, err := range c.pool.GetBundles(proof) {
if !yield(b, err) { if !yield(b, err) {
return return
} }