github.com/storacha/go-ucanto@v0.7.2/validator/capability.go (about)

     1  package validator
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/storacha/go-ucanto/core/delegation"
     8  	"github.com/storacha/go-ucanto/core/invocation"
     9  	"github.com/storacha/go-ucanto/core/result/failure"
    10  	"github.com/storacha/go-ucanto/core/schema"
    11  	"github.com/storacha/go-ucanto/ucan"
    12  )
    13  
    14  type Source interface {
    15  	Capability() ucan.Capability[any]
    16  	Delegation() delegation.Delegation
    17  }
    18  
    19  type source struct {
    20  	capability ucan.Capability[any]
    21  	delegation delegation.Delegation
    22  }
    23  
    24  func (s source) Capability() ucan.Capability[any] {
    25  	return s.capability
    26  }
    27  
    28  func (s source) Delegation() delegation.Delegation {
    29  	return s.delegation
    30  }
    31  
    32  func NewSource(capability ucan.Capability[any], delegation delegation.Delegation) Source {
    33  	return source{capability, delegation}
    34  }
    35  
    36  type Matcher[Caveats any] interface {
    37  	Match(source Source) (Match[Caveats], InvalidCapability)
    38  }
    39  
    40  type Selector[Caveats any] interface {
    41  	Select(sources []Source) ([]Match[Caveats], []DelegationError, []ucan.Capability[any])
    42  }
    43  
    44  type Match[Caveats any] interface {
    45  	Selector[Caveats]
    46  	Source() []Source
    47  	Value() ucan.Capability[Caveats]
    48  	Proofs() []delegation.Delegation
    49  	Prune(context CanIssuer[Caveats]) Match[Caveats]
    50  }
    51  
    52  type match[Caveats any] struct {
    53  	sources    []Source
    54  	value      ucan.Capability[Caveats]
    55  	descriptor Descriptor[Caveats]
    56  }
    57  
    58  func (m match[Caveats]) Proofs() []delegation.Delegation {
    59  	return []delegation.Delegation{m.sources[0].Delegation()}
    60  }
    61  
    62  func (m match[Caveats]) Prune(context CanIssuer[Caveats]) Match[Caveats] {
    63  	if context.CanIssue(m.value, m.sources[0].Delegation().Issuer().DID()) {
    64  		return nil
    65  	}
    66  	return m
    67  }
    68  
    69  func (m match[Caveats]) Source() []Source {
    70  	return m.sources
    71  }
    72  
    73  func (m match[Caveats]) Value() ucan.Capability[Caveats] {
    74  	return m.value
    75  }
    76  
    77  func (m match[Caveats]) Select(sources []Source) (matches []Match[Caveats], errors []DelegationError, unknowns []ucan.Capability[any]) {
    78  	for _, source := range sources {
    79  		cap, err := ResolveCapability(m.descriptor, m.value, source)
    80  		if err != nil {
    81  			if uerr, ok := err.(UnknownCapability); ok {
    82  				unknowns = append(unknowns, uerr.Capability())
    83  			} else if merr, ok := err.(MalformedCapability); ok {
    84  				errors = append(errors, NewDelegationError([]DelegationSubError{merr}, m))
    85  			} else {
    86  				panic(fmt.Errorf("unexpected error type in resolved capability result: %w", err))
    87  			}
    88  			continue
    89  		}
    90  
    91  		derr := m.descriptor.Derives(m.value, cap)
    92  		if derr != nil {
    93  			errors = append(errors, NewDelegationError([]DelegationSubError{NewEscalatedCapabilityError(m.value, cap, derr)}, m))
    94  			continue
    95  		}
    96  
    97  		matches = append(matches, NewMatch(source, cap, m.descriptor))
    98  	}
    99  	return
   100  }
   101  
   102  func (m match[Caveats]) String() string {
   103  	s, _ := m.value.MarshalJSON()
   104  	return string(s)
   105  }
   106  
   107  func NewMatch[Caveats any](source Source, capability ucan.Capability[Caveats], descriptor Descriptor[Caveats]) Match[Caveats] {
   108  	return match[Caveats]{[]Source{source}, capability, descriptor}
   109  }
   110  
   111  type CapabilityParser[Caveats any] interface {
   112  	Matcher[Caveats]
   113  	Selector[Caveats]
   114  	Can() ucan.Ability
   115  	// New creates a new capability from the passed options.
   116  	New(with ucan.Resource, nb Caveats) ucan.Capability[Caveats]
   117  	// Delegate creates a new signed token for this capability. If expiration is
   118  	// not set it defaults to 30 seconds from now.
   119  	Delegate(issuer ucan.Signer, audience ucan.Principal, with ucan.Resource, nb Caveats, options ...delegation.Option) (delegation.Delegation, error)
   120  	// Invoke creates an invocation of this capability.
   121  	Invoke(issuer ucan.Signer, audience ucan.Principal, with ucan.Resource, nb Caveats, options ...delegation.Option) (invocation.IssuedInvocation, error)
   122  }
   123  
   124  type Derivable[Caveats any] interface {
   125  	// Derives determines if a capability is derivable from another. Return `nil`
   126  	// to indicate the delegated capability can be derived from the claimed
   127  	// capability.
   128  	Derives(claimed, delegated ucan.Capability[Caveats]) failure.Failure
   129  }
   130  
   131  // DerivesFunc determines if a capability is derivable from another. Return
   132  // `nil` to indicate the delegated capability can be derived from the claimed
   133  // capability.
   134  type DerivesFunc[Caveats any] func(claimed, delegated ucan.Capability[Caveats]) failure.Failure
   135  
   136  type Descriptor[Caveats any] interface {
   137  	Derivable[Caveats]
   138  	Can() ucan.Ability
   139  	With() schema.Reader[string, ucan.Resource]
   140  	Nb() schema.Reader[any, Caveats]
   141  }
   142  
   143  type descriptor[Caveats any] struct {
   144  	can     ucan.Ability
   145  	with    schema.Reader[string, ucan.Resource]
   146  	nb      schema.Reader[any, Caveats]
   147  	derives DerivesFunc[Caveats]
   148  }
   149  
   150  func (d descriptor[C]) Can() ucan.Ability {
   151  	return d.can
   152  }
   153  
   154  func (d descriptor[C]) With() schema.Reader[string, ucan.Resource] {
   155  	return d.with
   156  }
   157  
   158  func (d descriptor[C]) Nb() schema.Reader[any, C] {
   159  	return d.nb
   160  }
   161  
   162  func (d descriptor[C]) Derives(parent, child ucan.Capability[C]) failure.Failure {
   163  	return d.derives(parent, child)
   164  }
   165  
   166  type capability[Caveats any] struct {
   167  	descriptor Descriptor[Caveats]
   168  }
   169  
   170  func (c capability[Caveats]) Can() ucan.Ability {
   171  	return c.descriptor.Can()
   172  }
   173  
   174  func (c capability[Caveats]) Select(capabilities []Source) ([]Match[Caveats], []DelegationError, []ucan.Capability[any]) {
   175  	return Select(c, capabilities)
   176  }
   177  
   178  func (c capability[Caveats]) Match(source Source) (Match[Caveats], InvalidCapability) {
   179  	cap, err := ParseCapability(c.descriptor, source)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	return NewMatch(source, cap, c.descriptor), nil
   184  }
   185  
   186  func (c capability[Caveats]) String() string {
   187  	return fmt.Sprintf(`{can:"%s"}`, c.Can())
   188  }
   189  
   190  func (c capability[Caveats]) New(with ucan.Resource, nb Caveats) ucan.Capability[Caveats] {
   191  	return ucan.NewCapability(c.descriptor.Can(), with, nb)
   192  }
   193  
   194  func (c capability[Caveats]) Delegate(issuer ucan.Signer, audience ucan.Principal, with ucan.Resource, nb Caveats, options ...delegation.Option) (delegation.Delegation, error) {
   195  	if bc, ok := any(nb).(ucan.CaveatBuilder); ok {
   196  		caps := []ucan.Capability[ucan.CaveatBuilder]{ucan.NewCapability(c.Can(), with, bc)}
   197  		return delegation.Delegate(issuer, audience, caps, options...)
   198  	}
   199  	return nil, fmt.Errorf("not an IPLD builder: %v", nb)
   200  }
   201  
   202  func (c capability[Caveats]) Invoke(issuer ucan.Signer, audience ucan.Principal, with ucan.Resource, nb Caveats, options ...delegation.Option) (invocation.IssuedInvocation, error) {
   203  	if bc, ok := any(nb).(ucan.CaveatBuilder); ok {
   204  		cap := ucan.NewCapability(c.Can(), with, bc)
   205  		return invocation.Invoke(issuer, audience, cap, options...)
   206  	}
   207  	return nil, fmt.Errorf("not an IPLD builder: %v", nb)
   208  }
   209  
   210  func NewCapability[Caveats any](
   211  	can ucan.Ability,
   212  	with schema.Reader[string, ucan.Resource],
   213  	nb schema.Reader[any, Caveats],
   214  	derives DerivesFunc[Caveats],
   215  ) CapabilityParser[Caveats] {
   216  	if derives == nil {
   217  		derives = DefaultDerives
   218  	}
   219  	d := descriptor[Caveats]{can, with, nb, derives}
   220  	return &capability[Caveats]{descriptor: d}
   221  }
   222  
   223  func ParseCapability[O any](descriptor Descriptor[O], source Source) (ucan.Capability[O], InvalidCapability) {
   224  	cap := source.Capability()
   225  
   226  	if descriptor.Can() != cap.Can() {
   227  		return nil, NewUnknownCapabilityError(cap)
   228  	}
   229  
   230  	uri, err := descriptor.With().Read(cap.With())
   231  	if err != nil {
   232  		return nil, NewMalformedCapabilityError(cap, err)
   233  	}
   234  
   235  	nb, err := descriptor.Nb().Read(cap.Nb())
   236  	if err != nil {
   237  		return nil, NewMalformedCapabilityError(cap, err)
   238  	}
   239  
   240  	return ucan.NewCapability(cap.Can(), uri, nb), nil
   241  }
   242  
   243  func Select[Caveats any](matcher Matcher[Caveats], capabilities []Source) (matches []Match[Caveats], errors []DelegationError, unknowns []ucan.Capability[any]) {
   244  	for _, capability := range capabilities {
   245  		match, err := matcher.Match(capability)
   246  		if err != nil {
   247  			if ux, ok := err.(UnknownCapability); ok {
   248  				unknowns = append(unknowns, ux.Capability())
   249  			} else if sx, ok := err.(DelegationSubError); ok {
   250  				errors = append(errors, NewDelegationError([]DelegationSubError{sx}, capability.Capability()))
   251  			} else {
   252  				panic(fmt.Errorf("unexpected error type in match result: %w", err))
   253  			}
   254  			continue
   255  		}
   256  		matches = append(matches, match)
   257  	}
   258  	return
   259  }
   260  
   261  // ResolveCapability resolves delegated capability `source` from the `claimed`
   262  // capability using provided capability `parser`. It is similar to
   263  // [ParseCapability] except `source` here is treated as capability pattern which
   264  // is matched against the `claimed` capability. This means we resolve `can` and
   265  // `with` fields from the `claimed` capability and...
   266  // TODO: inherit all missing `nb` fields from the claimed capability.
   267  func ResolveCapability[Caveats any](descriptor Descriptor[Caveats], claimed ucan.Capability[Caveats], source Source) (ucan.Capability[Caveats], InvalidCapability) {
   268  	can := ResolveAbility(source.Capability().Can(), claimed.Can())
   269  	if can == "" {
   270  		return nil, NewUnknownCapabilityError(source.Capability())
   271  	}
   272  
   273  	resource := ResolveResource(source.Capability().With(), claimed.With())
   274  	if resource == "" {
   275  		resource = source.Capability().With()
   276  	}
   277  
   278  	uri, err := descriptor.With().Read(resource)
   279  	if err != nil {
   280  		return nil, NewMalformedCapabilityError(source.Capability(), err)
   281  	}
   282  
   283  	// TODO: inherit missing fields
   284  	nb, err := descriptor.Nb().Read(claimed.Nb())
   285  	if err != nil {
   286  		return nil, NewMalformedCapabilityError(source.Capability(), err)
   287  	}
   288  
   289  	return ucan.NewCapability(can, uri, nb), nil
   290  }
   291  
   292  // ResolveAbility resolves ability `pattern` of the delegated capability from
   293  // the ability of the claimed capability. If pattern matches returns claimed
   294  // ability otherwise returns "".
   295  //
   296  //   - pattern "*"       can "store/add" → "store/add"
   297  //   - pattern "store/*" can "store/add" → "store/add"
   298  //   - pattern "*"       can "store/add" → "store/add"
   299  //   - pattern "*"       can "store/add" → ""
   300  //   - pattern "*"       can "store/add" → ""
   301  //   - pattern "*"       can "store/add" → ""
   302  func ResolveAbility(pattern string, can ucan.Ability) ucan.Ability {
   303  	if pattern == can || pattern == "*" {
   304  		return can
   305  	}
   306  	if strings.HasSuffix(pattern, "/*") && strings.HasPrefix(can, pattern[0:len(pattern)-1]) {
   307  		return can
   308  	}
   309  	return ""
   310  }
   311  
   312  // ResolveResource resolves `source` resource of the delegated capability from
   313  // the resource `uri` of the claimed capability. If `source` is `"ucan:*""` or
   314  // matches `uri` then it returns `uri` back otherwise it returns "".
   315  //
   316  //   - source "ucan:*"         uri "did:key:zAlice"      → "did:key:zAlice"
   317  //   - source "ucan:*"         uri "https://example.com" → "https://example.com"
   318  //   - source "did:*"          uri "did:key:zAlice"      → ""
   319  //   - source "did:key:zAlice" uri "did:key:zAlice"      → "did:key:zAlice"
   320  func ResolveResource(source string, uri ucan.Resource) ucan.Resource {
   321  	if source == uri || source == "ucan:*" {
   322  		return uri
   323  	}
   324  	return ""
   325  }
   326  
   327  func DefaultDerives[Caveats any](claimed, delegated ucan.Capability[Caveats]) failure.Failure {
   328  	dres := delegated.With()
   329  	cres := claimed.With()
   330  
   331  	if strings.HasSuffix(dres, "*") {
   332  		if !strings.HasPrefix(cres, dres[0:len(dres)-1]) {
   333  			return schema.NewSchemaError(fmt.Sprintf("Resource %s does not match delegated %s", cres, dres))
   334  		}
   335  	} else if dres != cres {
   336  		return schema.NewSchemaError(fmt.Sprintf("Resource %s is not contained by %s", cres, dres))
   337  	}
   338  
   339  	// TODO: is it possible to ensure claimed caveats match delegated caveats?
   340  	return nil
   341  }