client: improve/modularize
This commit is contained in:
committed by
Michael Muré
parent
6c1602507b
commit
547416e60d
@@ -1,7 +1,8 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"iter"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ipfs/go-cid"
|
"github.com/ipfs/go-cid"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Pool struct {
|
type Pool struct {
|
||||||
|
mu sync.RWMutex
|
||||||
dlgs map[cid.Cid]*delegation.Bundle
|
dlgs map[cid.Cid]*delegation.Bundle
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,106 +21,45 @@ func NewPool() *Pool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pool) AddBundle(bundle *delegation.Bundle) {
|
func (p *Pool) AddBundle(bundle *delegation.Bundle) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
p.dlgs[bundle.Cid] = bundle
|
p.dlgs[bundle.Cid] = bundle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Pool) AddBundles(bundles iter.Seq[*delegation.Bundle]) {
|
||||||
|
for bundle := range bundles {
|
||||||
|
p.AddBundle(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.
|
||||||
// - issuer: the DID of the client, also the issuer of the invocation token
|
|
||||||
// - audience: the DID of the resource to operate on, also the audience (or subject if defined) of the invocation token
|
|
||||||
// - cmd: the command to execute
|
// - cmd: the command to execute
|
||||||
// - args: the args to execute
|
// - 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
|
||||||
|
// 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(iss did.DID, aud did.DID, cmd command.Command) []cid.Cid {
|
func (p *Pool) FindProof(cmd command.Command, iss did.DID, aud did.DID) []cid.Cid {
|
||||||
p.trim()
|
p.trim()
|
||||||
|
|
||||||
// Find the possible leaf delegations, directly matching the invocation parameters
|
p.mu.RLock()
|
||||||
var candidateLeaf []*delegation.Bundle
|
defer p.mu.RUnlock()
|
||||||
|
|
||||||
for _, bundle := range p.dlgs {
|
return FindProof(func() iter.Seq[*delegation.Bundle] {
|
||||||
dlg := bundle.Decoded
|
return func(yield func(*delegation.Bundle) bool) {
|
||||||
|
for _, bundle := range p.dlgs {
|
||||||
// The Subject of each delegation must equal the invocation's Audience field. - 4f
|
if !yield(bundle) {
|
||||||
if dlg.Subject() != aud {
|
return
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 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
|
|
||||||
if dlg.Audience() != iss {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// The command of each delegation must "allow" the one before it. - 4g
|
|
||||||
if !dlg.Command().Covers(cmd) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Time bound - 3b, 3c
|
|
||||||
if !dlg.IsValidNow() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// graph.WriteString("[*] --> " + bundle.Cid.String() + "\n")
|
|
||||||
candidateLeaf = append(candidateLeaf, bundle)
|
|
||||||
}
|
|
||||||
|
|
||||||
type state struct {
|
|
||||||
bundle *delegation.Bundle
|
|
||||||
path []cid.Cid
|
|
||||||
size int
|
|
||||||
}
|
|
||||||
|
|
||||||
var bestSize = math.MaxInt
|
|
||||||
var bestProof []cid.Cid
|
|
||||||
|
|
||||||
// Perform a depth-first search on the DAG of connected delegations, for each of our candidates
|
|
||||||
for _, leaf := range candidateLeaf {
|
|
||||||
var stack = []state{{bundle: leaf, path: []cid.Cid{leaf.Cid}, size: len(leaf.Sealed)}}
|
|
||||||
|
|
||||||
for len(stack) > 0 {
|
|
||||||
// dequeue a delegation
|
|
||||||
cur := stack[len(stack)-1]
|
|
||||||
stack = stack[:len(stack)-1]
|
|
||||||
at := cur.bundle
|
|
||||||
|
|
||||||
// if it's a root delegation, we found a valid proof
|
|
||||||
if at.Decoded.Issuer() == at.Decoded.Subject() {
|
|
||||||
if len(bestProof) == 0 || len(cur.path) < len(bestProof) || len(cur.path) == len(bestProof) && cur.size < bestSize {
|
|
||||||
bestProof = append([]cid.Cid{}, cur.path...) // make a copy
|
|
||||||
bestSize = cur.size
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// find parent delegation for our current delegation
|
|
||||||
for _, candidate := range p.dlgs {
|
|
||||||
// The Subject of each delegation must equal the invocation's Audience field. - 4f
|
|
||||||
if candidate.Decoded.Subject() != aud {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 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
|
|
||||||
if candidate.Decoded.Audience() != at.Decoded.Issuer() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// The command of each delegation must "allow" the one before it. - 4g
|
|
||||||
if !candidate.Decoded.Command().Covers(at.Decoded.Command()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Time bound - 3b, 3c
|
|
||||||
if !candidate.Decoded.IsValidNow() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
newPath := append([]cid.Cid{}, cur.path...) // make copy
|
|
||||||
newPath = append(newPath, candidate.Cid)
|
|
||||||
stack = append(stack, state{bundle: candidate, path: newPath, size: cur.size + len(candidate.Sealed)})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}, cmd, iss, aud)
|
||||||
|
|
||||||
return bestProof
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// trim removes expired tokens
|
// trim removes expired tokens
|
||||||
func (p *Pool) trim() {
|
func (p *Pool) trim() {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
for c, bundle := range p.dlgs {
|
for c, bundle := range p.dlgs {
|
||||||
if bundle.Decoded.Expiration() != nil && bundle.Decoded.Expiration().Before(now) {
|
if bundle.Decoded.Expiration() != nil && bundle.Decoded.Expiration().Before(now) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/ucan-wg/go-ucan/did/didtest"
|
"github.com/ucan-wg/go-ucan/did/didtest"
|
||||||
|
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||||
"github.com/ucan-wg/go-ucan/token/delegation/delegationtest"
|
"github.com/ucan-wg/go-ucan/token/delegation/delegationtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,14 +17,16 @@ func TestFindProof(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
require.Equal(t, delegationtest.ProofAliceBob,
|
require.Equal(t, delegationtest.ProofAliceBob,
|
||||||
p.FindProof(didtest.PersonaBob.DID(), didtest.PersonaAlice.DID(), delegationtest.NominalCommand))
|
p.FindProof(delegationtest.NominalCommand, didtest.PersonaBob.DID(), didtest.PersonaAlice.DID()))
|
||||||
require.Equal(t, delegationtest.ProofAliceBobCarol,
|
require.Equal(t, delegationtest.ProofAliceBobCarol,
|
||||||
p.FindProof(didtest.PersonaCarol.DID(), didtest.PersonaAlice.DID(), delegationtest.NominalCommand))
|
p.FindProof(delegationtest.NominalCommand, didtest.PersonaCarol.DID(), didtest.PersonaAlice.DID()))
|
||||||
require.Equal(t, delegationtest.ProofAliceBobCarolDan,
|
require.Equal(t, delegationtest.ProofAliceBobCarolDan,
|
||||||
p.FindProof(didtest.PersonaDan.DID(), didtest.PersonaAlice.DID(), delegationtest.NominalCommand))
|
p.FindProof(delegationtest.NominalCommand, didtest.PersonaDan.DID(), didtest.PersonaAlice.DID()))
|
||||||
require.Equal(t, delegationtest.ProofAliceBobCarolDanErin,
|
require.Equal(t, delegationtest.ProofAliceBobCarolDanErin,
|
||||||
p.FindProof(didtest.PersonaErin.DID(), didtest.PersonaAlice.DID(), delegationtest.NominalCommand))
|
p.FindProof(delegationtest.NominalCommand, didtest.PersonaErin.DID(), didtest.PersonaAlice.DID()))
|
||||||
require.Equal(t, delegationtest.ProofAliceBobCarolDanErinFrank,
|
require.Equal(t, delegationtest.ProofAliceBobCarolDanErinFrank,
|
||||||
p.FindProof(didtest.PersonaFrank.DID(), didtest.PersonaAlice.DID(), delegationtest.NominalCommand))
|
p.FindProof(delegationtest.NominalCommand, didtest.PersonaFrank.DID(), didtest.PersonaAlice.DID()))
|
||||||
|
|
||||||
|
// wrong command
|
||||||
|
require.Empty(t, p.FindProof(command.New("foo"), didtest.PersonaBob.DID(), didtest.PersonaAlice.DID()))
|
||||||
}
|
}
|
||||||
|
|||||||
103
toolkit/client/proof.go
Normal file
103
toolkit/client/proof.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"iter"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
|
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||||
|
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// - audience: the DID of the resource to operate on, also the subject (or audience if defined) of the invocation token
|
||||||
|
// 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.
|
||||||
|
func FindProof(dlgs func() iter.Seq[*delegation.Bundle], cmd command.Command, iss did.DID, aud did.DID) []cid.Cid {
|
||||||
|
// Find the possible leaf delegations, directly matching the invocation parameters
|
||||||
|
var candidateLeaf []*delegation.Bundle
|
||||||
|
|
||||||
|
for bundle := range dlgs() {
|
||||||
|
dlg := bundle.Decoded
|
||||||
|
|
||||||
|
// The Subject of each delegation must equal the invocation's Audience field. - 4f
|
||||||
|
if dlg.Subject() != aud {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
if dlg.Audience() != iss {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The command of each delegation must "allow" the one before it. - 4g
|
||||||
|
if !dlg.Command().Covers(cmd) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Time bound - 3b, 3c
|
||||||
|
if !dlg.IsValidNow() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
candidateLeaf = append(candidateLeaf, bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
type state struct {
|
||||||
|
bundle *delegation.Bundle
|
||||||
|
path []cid.Cid
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
var bestSize = math.MaxInt
|
||||||
|
var bestProof []cid.Cid
|
||||||
|
|
||||||
|
// Perform a depth-first search on the DAG of connected delegations, for each of our candidates
|
||||||
|
for _, leaf := range candidateLeaf {
|
||||||
|
var stack = []state{{bundle: leaf, path: []cid.Cid{leaf.Cid}, size: len(leaf.Sealed)}}
|
||||||
|
|
||||||
|
for len(stack) > 0 {
|
||||||
|
// dequeue a delegation
|
||||||
|
cur := stack[len(stack)-1]
|
||||||
|
stack = stack[:len(stack)-1]
|
||||||
|
at := cur.bundle
|
||||||
|
|
||||||
|
// if it's a root delegation, we found a valid proof
|
||||||
|
if at.Decoded.Issuer() == at.Decoded.Subject() {
|
||||||
|
if len(bestProof) == 0 || len(cur.path) < len(bestProof) || len(cur.path) == len(bestProof) && cur.size < bestSize {
|
||||||
|
bestProof = append([]cid.Cid{}, cur.path...) // make a copy
|
||||||
|
bestSize = cur.size
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find parent delegation for our current delegation
|
||||||
|
for candidate := range dlgs() {
|
||||||
|
// The Subject of each delegation must equal the invocation's Audience field. - 4f
|
||||||
|
if candidate.Decoded.Subject() != aud {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
if candidate.Decoded.Audience() != at.Decoded.Issuer() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The command of each delegation must "allow" the one before it. - 4g
|
||||||
|
if !candidate.Decoded.Command().Covers(at.Decoded.Command()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Time bound - 3b, 3c
|
||||||
|
if !candidate.Decoded.IsValidNow() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
newPath := append([]cid.Cid{}, cur.path...) // make a copy
|
||||||
|
newPath = append(newPath, candidate.Cid)
|
||||||
|
stack = append(stack, state{bundle: candidate, path: newPath, size: cur.size + len(candidate.Sealed)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestProof
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user