2025-02-06 14:19:30 +01:00
|
|
|
package client
|
2024-12-10 14:52:39 +01:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2025-02-03 16:17:30 +01:00
|
|
|
"fmt"
|
2024-12-10 14:52:39 +01:00
|
|
|
"iter"
|
|
|
|
|
|
2026-01-08 15:45:49 -05:00
|
|
|
"code.sonr.org/go/did-it"
|
|
|
|
|
"code.sonr.org/go/did-it/crypto"
|
2024-12-10 14:52:39 +01:00
|
|
|
"github.com/ipfs/go-cid"
|
2025-08-05 12:11:20 +02:00
|
|
|
|
2026-01-08 15:45:49 -05:00
|
|
|
"code.sonr.org/go/ucan/pkg/command"
|
|
|
|
|
"code.sonr.org/go/ucan/token/delegation"
|
2024-12-10 14:52:39 +01:00
|
|
|
)
|
|
|
|
|
|
2025-02-03 16:17:30 +01:00
|
|
|
// DlgIssuingLogic is a function that decides what powers are given to a client.
|
|
|
|
|
// - issuer: the DID of our issuer
|
|
|
|
|
// - audience: the DID of the client, also the issuer 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 "(audience) wants to do (cmd) on (subject)".
|
|
|
|
|
// Note: you can decide to match the input parameters exactly or issue a broader power, as long as it allows the
|
|
|
|
|
// 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)
|
|
|
|
|
|
2025-02-06 14:19:30 +01:00
|
|
|
var _ DelegationRequester = &WithIssuer{}
|
2024-12-10 14:52:39 +01:00
|
|
|
|
2025-02-06 14:19:30 +01:00
|
|
|
type WithIssuer struct {
|
|
|
|
|
*Client
|
|
|
|
|
logic DlgIssuingLogic
|
2024-12-10 14:52:39 +01:00
|
|
|
}
|
|
|
|
|
|
2025-08-05 12:11:20 +02:00
|
|
|
func NewWithIssuer(privKey crypto.PrivateKeySigningBytes, d did.DID, requester DelegationRequester, logic DlgIssuingLogic) (*WithIssuer, error) {
|
|
|
|
|
client, err := NewClient(privKey, d, requester)
|
2024-12-10 14:52:39 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2025-02-06 14:19:30 +01:00
|
|
|
return &WithIssuer{Client: client, logic: logic}, nil
|
2024-12-10 14:52:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RequestDelegation retrieve chain of delegation for the given parameters.
|
|
|
|
|
// - audience: the DID of the client, also the issuer 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 "(audience) does (cmd) on (subject)".
|
|
|
|
|
// Note: the returned delegation(s) don't have to match exactly the parameters, as long as they allow them.
|
2025-02-06 14:19:30 +01:00
|
|
|
func (c *WithIssuer) RequestDelegation(ctx context.Context, audience did.DID, cmd command.Command, subject did.DID) (iter.Seq2[*delegation.Bundle, error], error) {
|
2024-12-10 14:52:39 +01:00
|
|
|
var proof []cid.Cid
|
|
|
|
|
|
|
|
|
|
// is there already a valid proof chain?
|
2025-02-06 14:19:30 +01:00
|
|
|
if proof = c.pool.FindProof(audience, cmd, subject); len(proof) > 0 {
|
|
|
|
|
return c.pool.GetBundles(proof), nil
|
2024-12-10 14:52:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// do we have the power to delegate this?
|
2025-02-06 14:19:30 +01:00
|
|
|
if proof = c.pool.FindProof(c.did, cmd, subject); len(proof) == 0 {
|
2024-12-10 14:52:39 +01:00
|
|
|
// we need to request a new proof
|
2025-02-06 14:19:30 +01:00
|
|
|
proofBundles, err := c.requester.RequestDelegation(ctx, c.did, cmd, subject)
|
2024-12-10 14:52:39 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// cache the new proofs
|
|
|
|
|
for bundle, err := range proofBundles {
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
proof = append(proof, bundle.Cid)
|
2025-02-06 14:19:30 +01:00
|
|
|
c.pool.AddBundle(bundle)
|
2024-12-10 14:52:39 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// run the custom logic to get what we actually issue
|
2025-02-06 14:19:30 +01:00
|
|
|
dlg, err := c.logic(c.did, audience, cmd, subject)
|
2024-12-10 14:52:39 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2025-02-03 16:17:30 +01:00
|
|
|
if dlg.IsRoot() {
|
|
|
|
|
return nil, fmt.Errorf("issuing logic should return a non-root delegation")
|
|
|
|
|
}
|
2024-12-10 14:52:39 +01:00
|
|
|
|
|
|
|
|
// sign and cache the new token
|
2025-02-06 14:19:30 +01:00
|
|
|
dlgBytes, dlgCid, err := dlg.ToSealed(c.privKey)
|
2024-12-10 14:52:39 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
bundle := &delegation.Bundle{
|
|
|
|
|
Cid: dlgCid,
|
|
|
|
|
Decoded: dlg,
|
|
|
|
|
Sealed: dlgBytes,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// output the relevant delegations
|
|
|
|
|
return func(yield func(*delegation.Bundle, error) bool) {
|
|
|
|
|
if !yield(bundle, nil) {
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-02-06 14:19:30 +01:00
|
|
|
for b, err := range c.pool.GetBundles(proof) {
|
2024-12-10 14:52:39 +01:00
|
|
|
if !yield(b, err) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, nil
|
|
|
|
|
}
|