Merge pull request #16 from ucan-wg/policy-serial

policy: IPLD encode/decode
This commit is contained in:
Michael Muré
2024-09-02 02:34:55 +02:00
committed by GitHub
10 changed files with 489 additions and 225 deletions

262
capability/policy/ipld.go Normal file
View File

@@ -0,0 +1,262 @@
package policy
import (
"fmt"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagjson"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/must"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/ucan-wg/go-ucan/v1/capability/policy/selector"
)
func FromIPLD(node datamodel.Node) (Policy, error) {
return statementsFromIPLD("/", node)
}
func FromDagJson(json string) (Policy, error) {
nodes, err := ipld.Decode([]byte(json), dagjson.Decode)
if err != nil {
return nil, err
}
return FromIPLD(nodes)
}
func statementFromIPLD(path string, node datamodel.Node) (Statement, error) {
// sanity checks
if node.Kind() != datamodel.Kind_List {
return nil, ErrNotATuple(path)
}
if node.Length() != 2 && node.Length() != 3 {
return nil, ErrUnrecognizedShape(path)
}
// extract operator
opNode, _ := node.LookupByIndex(0)
if opNode.Kind() != datamodel.Kind_String {
return nil, ErrNotAString(path)
}
op := must.String(opNode)
arg2AsSelector := func() (selector.Selector, error) {
nd, _ := node.LookupByIndex(1)
if nd.Kind() != datamodel.Kind_String {
return nil, ErrNotAString(path + "1/")
}
sel, err := selector.Parse(must.String(nd))
if err != nil {
return nil, ErrInvalidSelector(path+"1/", err)
}
return sel, nil
}
switch node.Length() {
case 2:
switch op {
case KindNot:
arg2, _ := node.LookupByIndex(1)
statement, err := statementFromIPLD(path+"1/", arg2)
if err != nil {
return nil, err
}
return Not(statement), nil
case KindAnd, KindOr:
arg2, _ := node.LookupByIndex(1)
statement, err := statementsFromIPLD(path+"1/", arg2)
if err != nil {
return nil, err
}
return connective{kind: op, statements: statement}, nil
default:
return nil, ErrUnrecognizedOperator(path, op)
}
case 3:
switch op {
case KindEqual, KindLessThan, KindLessThanOrEqual, KindGreaterThan, KindGreaterThanOrEqual:
sel, err := arg2AsSelector()
if err != nil {
return nil, err
}
arg3, _ := node.LookupByIndex(2)
return equality{kind: op, selector: sel, value: arg3}, nil
case KindLike:
sel, err := arg2AsSelector()
if err != nil {
return nil, err
}
pattern, _ := node.LookupByIndex(2)
if pattern.Kind() != datamodel.Kind_String {
return nil, ErrNotAString(path + "2/")
}
res, err := Like(sel, must.String(pattern))
if err != nil {
return nil, ErrInvalidPattern(path+"2/", err)
}
return res, nil
case KindAll, KindAny:
sel, err := arg2AsSelector()
if err != nil {
return nil, err
}
statementsNodes, _ := node.LookupByIndex(2)
statements, err := statementsFromIPLD(path+"1/", statementsNodes)
return quantifier{kind: op, selector: sel, statements: statements}, nil
default:
return nil, ErrUnrecognizedOperator(path, op)
}
default:
return nil, ErrUnrecognizedShape(path)
}
}
func statementsFromIPLD(path string, node datamodel.Node) ([]Statement, error) {
// sanity checks
if node.Kind() != datamodel.Kind_List {
return nil, ErrNotATuple(path)
}
if node.Length() == 0 {
return nil, ErrEmptyList(path)
}
res := make([]Statement, node.Length())
for i := int64(0); i < node.Length(); i++ {
nd, _ := node.LookupByIndex(i)
statement, err := statementFromIPLD(fmt.Sprintf("%s%d/", path, i), nd)
if err != nil {
return nil, err
}
res[i] = statement
}
return res, nil
}
func (p Policy) ToIPLD() (datamodel.Node, error) {
return statementsToIPLD(p)
}
func statementsToIPLD(statements []Statement) (datamodel.Node, error) {
list := basicnode.Prototype.List.NewBuilder()
// can't error, we have the right builder.
listBuilder, _ := list.BeginList(int64(len(statements)))
for _, argStatement := range statements {
node, err := statementToIPLD(argStatement)
if err != nil {
return nil, err
}
err = listBuilder.AssembleValue().AssignNode(node)
if err != nil {
return nil, err
}
}
err := listBuilder.Finish()
if err != nil {
return nil, err
}
return list.Build(), nil
}
func statementToIPLD(statement Statement) (datamodel.Node, error) {
list := basicnode.Prototype.List.NewBuilder()
length := int64(3)
switch statement.(type) {
case negation, connective:
length = 2
}
// can't error, we have the right builder.
listBuilder, _ := list.BeginList(length)
switch statement := statement.(type) {
case equality:
err := listBuilder.AssembleValue().AssignString(statement.kind)
if err != nil {
return nil, err
}
err = listBuilder.AssembleValue().AssignString(statement.selector.String())
if err != nil {
return nil, err
}
err = listBuilder.AssembleValue().AssignNode(statement.value)
if err != nil {
return nil, err
}
case negation:
err := listBuilder.AssembleValue().AssignString(statement.Kind())
if err != nil {
return nil, err
}
node, err := statementToIPLD(statement.statement)
if err != nil {
return nil, err
}
err = listBuilder.AssembleValue().AssignNode(node)
if err != nil {
return nil, err
}
case connective:
err := listBuilder.AssembleValue().AssignString(statement.kind)
if err != nil {
return nil, err
}
args, err := statementsToIPLD(statement.statements)
if err != nil {
return nil, err
}
err = listBuilder.AssembleValue().AssignNode(args)
if err != nil {
return nil, err
}
case wildcard:
err := listBuilder.AssembleValue().AssignString(statement.Kind())
if err != nil {
return nil, err
}
err = listBuilder.AssembleValue().AssignString(statement.selector.String())
if err != nil {
return nil, err
}
err = listBuilder.AssembleValue().AssignString(statement.pattern)
if err != nil {
return nil, err
}
case quantifier:
err := listBuilder.AssembleValue().AssignString(statement.kind)
if err != nil {
return nil, err
}
err = listBuilder.AssembleValue().AssignString(statement.selector.String())
if err != nil {
return nil, err
}
args, err := statementsToIPLD(statement.statements)
if err != nil {
return nil, err
}
err = listBuilder.AssembleValue().AssignNode(args)
if err != nil {
return nil, err
}
}
err := listBuilder.Finish()
if err != nil {
return nil, err
}
return list.Build(), nil
}

View File

@@ -0,0 +1,47 @@
package policy
import "fmt"
type errWithPath struct {
path string
msg string
}
func (e errWithPath) Error() string {
return fmt.Sprintf("IPLD path '%s': %s", e.path, e.msg)
}
func ErrInvalidSelector(path string, err error) error {
return errWithPath{path: path, msg: fmt.Sprintf("invalid selector: %s", err)}
}
func ErrInvalidPattern(path string, err error) error {
return errWithPath{path: path, msg: fmt.Sprintf("invalid pattern: %s", err)}
}
func ErrNotAString(path string) error {
return errWithPath{path: path, msg: ""}
}
func ErrUnrecognizedOperator(path string, op string) error {
return errWithPath{path: path, msg: fmt.Sprintf("unrecognized operator '%s'", safeStr(op))}
}
func ErrUnrecognizedShape(path string) error {
return errWithPath{path: path, msg: "unrecognized shape"}
}
func ErrNotATuple(path string) error {
return errWithPath{path: path, msg: "not a tuple"}
}
func ErrEmptyList(path string) error {
return errWithPath{path: path, msg: "empty list"}
}
func safeStr(str string) string {
if len(str) > 10 {
return str[:10]
}
return str
}

View File

@@ -0,0 +1,45 @@
package policy
import (
"testing"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagjson"
"github.com/stretchr/testify/require"
)
func TestIpldRoundTrip(t *testing.T) {
const illustrativeExample = `
[
["==", ".status", "draft"],
["all", ".reviewer", [
["like", ".email", "*@example.com"]]
],
["any", ".tags", [
["or", [
["==", ".", "news"],
["==", ".", "press"]]
]]
]
]`
for _, tc := range []struct {
name, dagJsonStr string
}{
{"illustrativeExample", illustrativeExample},
} {
nodes, err := ipld.Decode([]byte(tc.dagJsonStr), dagjson.Decode)
require.NoError(t, err)
pol, err := FromIPLD(nodes)
require.NoError(t, err)
wroteIpld, err := pol.ToIPLD()
require.NoError(t, err)
wroteAsDagJson, err := ipld.Encode(wroteIpld, dagjson.Encode)
require.NoError(t, err)
require.JSONEq(t, tc.dagJsonStr, string(wroteAsDagJson))
}
}

View File

@@ -25,52 +25,52 @@ func Match(policy Policy, node ipld.Node) bool {
func matchStatement(statement Statement, node ipld.Node) bool {
switch statement.Kind() {
case KindEqual:
if s, ok := statement.(EqualityStatement); ok {
one, _, err := selector.Select(s.Selector(), node)
if s, ok := statement.(equality); ok {
one, _, err := selector.Select(s.selector, node)
if err != nil || one == nil {
return false
}
return datamodel.DeepEqual(s.Value(), one)
return datamodel.DeepEqual(s.value, one)
}
case KindGreaterThan:
if s, ok := statement.(InequalityStatement); ok {
one, _, err := selector.Select(s.Selector(), node)
if s, ok := statement.(equality); ok {
one, _, err := selector.Select(s.selector, node)
if err != nil || one == nil {
return false
}
return isOrdered(s.Value(), one, gt)
return isOrdered(s.value, one, gt)
}
case KindGreaterThanOrEqual:
if s, ok := statement.(InequalityStatement); ok {
one, _, err := selector.Select(s.Selector(), node)
if s, ok := statement.(equality); ok {
one, _, err := selector.Select(s.selector, node)
if err != nil || one == nil {
return false
}
return isOrdered(s.Value(), one, gte)
return isOrdered(s.value, one, gte)
}
case KindLessThan:
if s, ok := statement.(InequalityStatement); ok {
one, _, err := selector.Select(s.Selector(), node)
if s, ok := statement.(equality); ok {
one, _, err := selector.Select(s.selector, node)
if err != nil || one == nil {
return false
}
return isOrdered(s.Value(), one, lt)
return isOrdered(s.value, one, lt)
}
case KindLessThanOrEqual:
if s, ok := statement.(InequalityStatement); ok {
one, _, err := selector.Select(s.Selector(), node)
if s, ok := statement.(equality); ok {
one, _, err := selector.Select(s.selector, node)
if err != nil || one == nil {
return false
}
return isOrdered(s.Value(), one, lte)
return isOrdered(s.value, one, lte)
}
case KindNot:
if s, ok := statement.(NegationStatement); ok {
return !matchStatement(s.Value(), node)
if s, ok := statement.(negation); ok {
return !matchStatement(s.statement, node)
}
case KindAnd:
if s, ok := statement.(ConjunctionStatement); ok {
for _, cs := range s.Value() {
if s, ok := statement.(connective); ok {
for _, cs := range s.statements {
r := matchStatement(cs, node)
if !r {
return false
@@ -79,11 +79,11 @@ func matchStatement(statement Statement, node ipld.Node) bool {
return true
}
case KindOr:
if s, ok := statement.(DisjunctionStatement); ok {
if len(s.Value()) == 0 {
if s, ok := statement.(connective); ok {
if len(s.statements) == 0 {
return true
}
for _, cs := range s.Value() {
for _, cs := range s.statements {
r := matchStatement(cs, node)
if r {
return true
@@ -92,8 +92,8 @@ func matchStatement(statement Statement, node ipld.Node) bool {
return false
}
case KindLike:
if s, ok := statement.(WildcardStatement); ok {
one, _, err := selector.Select(s.Selector(), node)
if s, ok := statement.(wildcard); ok {
one, _, err := selector.Select(s.selector, node)
if err != nil || one == nil {
return false
}
@@ -101,16 +101,16 @@ func matchStatement(statement Statement, node ipld.Node) bool {
if err != nil {
return false
}
return s.Value().Match(v)
return s.glob.Match(v)
}
case KindAll:
if s, ok := statement.(QuantifierStatement); ok {
_, many, err := selector.Select(s.Selector(), node)
if s, ok := statement.(quantifier); ok {
_, many, err := selector.Select(s.selector, node)
if err != nil || many == nil {
return false
}
for _, n := range many {
ok := Match(s.Value(), n)
ok := Match(s.statements, n)
if !ok {
return false
}
@@ -118,13 +118,13 @@ func matchStatement(statement Statement, node ipld.Node) bool {
return true
}
case KindAny:
if s, ok := statement.(QuantifierStatement); ok {
_, many, err := selector.Select(s.Selector(), node)
if s, ok := statement.(quantifier); ok {
_, many, err := selector.Select(s.selector, node)
if err != nil || many == nil {
return false
}
for _, n := range many {
ok := Match(s.Value(), n)
ok := Match(s.statements, n)
if ok {
return true
}

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"testing"
"github.com/gobwas/glob"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
@@ -294,8 +293,7 @@ func TestMatch(t *testing.T) {
})
t.Run("wildcard", func(t *testing.T) {
glb, err := glob.Compile(`Alice\*, Bob*, Carol.`)
require.NoError(t, err)
pattern := `Alice\*, Bob*, Carol.`
for _, s := range []string{
"Alice*, Bob, Carol.",
@@ -310,7 +308,10 @@ func TestMatch(t *testing.T) {
nb.AssignString(s)
nd := nb.Build()
pol := Policy{Like(selector.MustParse("."), glb)}
statement, err := Like(selector.MustParse("."), pattern)
require.NoError(t, err)
pol := Policy{statement}
ok := Match(pol, nd)
require.True(t, ok)
})
@@ -331,7 +332,10 @@ func TestMatch(t *testing.T) {
nb.AssignString(s)
nd := nb.Build()
pol := Policy{Like(selector.MustParse("."), glb)}
statement, err := Like(selector.MustParse("."), pattern)
require.NoError(t, err)
pol := Policy{statement}
ok := Match(pol, nd)
require.False(t, ok)
})

View File

@@ -10,68 +10,25 @@ import (
)
const (
KindEqual = "=="
KindGreaterThan = ">"
KindGreaterThanOrEqual = ">="
KindLessThan = "<"
KindLessThanOrEqual = "<="
KindNot = "not"
KindAnd = "and"
KindOr = "or"
KindLike = "like"
KindAll = "all"
KindAny = "any"
KindEqual = "==" // implemented by equality
KindGreaterThan = ">" // implemented by equality
KindGreaterThanOrEqual = ">=" // implemented by equality
KindLessThan = "<" // implemented by equality
KindLessThanOrEqual = "<=" // implemented by equality
KindNot = "not" // implemented by negation
KindAnd = "and" // implemented by connective
KindOr = "or" // implemented by connective
KindLike = "like" // implemented by wildcard
KindAll = "all" // implemented by quantifier
KindAny = "any" // implemented by quantifier
)
type Policy = []Statement
type Policy []Statement
type Statement interface {
Kind() string
}
type EqualityStatement interface {
Statement
Selector() selector.Selector
Value() ipld.Node
}
type InequalityStatement interface {
Statement
Selector() selector.Selector
Value() ipld.Node
}
type WildcardStatement interface {
Statement
Selector() selector.Selector
Value() glob.Glob
}
type ConnectiveStatement interface {
Statement
}
type NegationStatement interface {
ConnectiveStatement
Value() Statement
}
type ConjunctionStatement interface {
ConnectiveStatement
Value() []Statement
}
type DisjunctionStatement interface {
ConnectiveStatement
Value() []Statement
}
type QuantifierStatement interface {
Statement
Selector() selector.Selector
Value() Policy
}
type equality struct {
kind string
selector selector.Selector
@@ -82,31 +39,23 @@ func (e equality) Kind() string {
return e.kind
}
func (e equality) Value() ipld.Node {
return e.value
}
func (e equality) Selector() selector.Selector {
return e.selector
}
func Equal(selector selector.Selector, value ipld.Node) EqualityStatement {
func Equal(selector selector.Selector, value ipld.Node) Statement {
return equality{KindEqual, selector, value}
}
func GreaterThan(selector selector.Selector, value ipld.Node) InequalityStatement {
func GreaterThan(selector selector.Selector, value ipld.Node) Statement {
return equality{KindGreaterThan, selector, value}
}
func GreaterThanOrEqual(selector selector.Selector, value ipld.Node) InequalityStatement {
func GreaterThanOrEqual(selector selector.Selector, value ipld.Node) Statement {
return equality{KindGreaterThanOrEqual, selector, value}
}
func LessThan(selector selector.Selector, value ipld.Node) InequalityStatement {
func LessThan(selector selector.Selector, value ipld.Node) Statement {
return equality{KindLessThan, selector, value}
}
func LessThanOrEqual(selector selector.Selector, value ipld.Node) InequalityStatement {
func LessThanOrEqual(selector selector.Selector, value ipld.Node) Statement {
return equality{KindLessThanOrEqual, selector, value}
}
@@ -118,89 +67,59 @@ func (n negation) Kind() string {
return KindNot
}
func (n negation) Value() Statement {
return n.statement
}
func Not(stmt Statement) NegationStatement {
func Not(stmt Statement) Statement {
return negation{stmt}
}
type conjunction struct {
type connective struct {
kind string
statements []Statement
}
func (n conjunction) Kind() string {
return KindAnd
func (c connective) Kind() string {
return c.kind
}
func (n conjunction) Value() []Statement {
return n.statements
func And(stmts ...Statement) Statement {
return connective{KindAnd, stmts}
}
func And(stmts ...Statement) ConjunctionStatement {
return conjunction{stmts}
}
type disjunction struct {
statements []Statement
}
func (n disjunction) Kind() string {
return KindOr
}
func (n disjunction) Value() []Statement {
return n.statements
}
func Or(stmts ...Statement) DisjunctionStatement {
return disjunction{stmts}
func Or(stmts ...Statement) Statement {
return connective{KindOr, stmts}
}
type wildcard struct {
selector selector.Selector
glob glob.Glob
pattern string
glob glob.Glob // not serialized
}
func (n wildcard) Kind() string {
return KindLike
}
func (n wildcard) Selector() selector.Selector {
return n.selector
}
func (n wildcard) Value() glob.Glob {
return n.glob
}
func Like(selector selector.Selector, glob glob.Glob) WildcardStatement {
return wildcard{selector, glob}
func Like(selector selector.Selector, pattern string) (Statement, error) {
g, err := glob.Compile(pattern)
if err != nil {
return nil, err
}
return wildcard{selector, pattern, g}, nil
}
type quantifier struct {
kind string
selector selector.Selector
policy Policy
statements []Statement
}
func (n quantifier) Kind() string {
return n.kind
}
func (n quantifier) Selector() selector.Selector {
return n.selector
}
func (n quantifier) Value() Policy {
return n.policy
}
func All(selector selector.Selector, policy ...Statement) QuantifierStatement {
func All(selector selector.Selector, policy ...Statement) Statement {
return quantifier{KindAll, selector, policy}
}
func Any(selector selector.Selector, policy ...Statement) QuantifierStatement {
func Any(selector selector.Selector, policy ...Statement) Statement {
return quantifier{KindAny, selector, policy}
}

View File

@@ -13,7 +13,8 @@ type Payload struct {
cmd String
# The delegation policy
pol Policy
# It doesn't seem possible to represent it with a schema.
pol Any
# A unique, random nonce
nonce Bytes
@@ -26,7 +27,3 @@ type Payload struct {
# The timestamp at which the Invocation becomes invalid
exp nullable Int
}
type Policy struct {
}

View File

@@ -46,7 +46,7 @@ type PayloadModel struct {
Cmd string
// The delegation policy
Pol PolicyModel
Pol datamodel.Node
// A unique, random nonce
Nonce []byte
@@ -67,10 +67,3 @@ type MetaModel struct {
Keys []string
Values map[string]datamodel.Node
}
type PolicyModel struct {
}
func PointerTo[T any](v T) *T {
return &v
}

View File

@@ -5,51 +5,62 @@ import (
"testing"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/bindnode"
"github.com/stretchr/testify/require"
)
func TestSchemaRoundTrip(t *testing.T) {
p := &PayloadModel{
Iss: "did:key:abc123",
Aud: "did:key:def456",
Sub: PointerTo(""),
Cmd: "/foo/bar",
Pol: PolicyModel{}, // TODO: have something here
Nonce: []byte("super-random"),
Meta: MetaModel{
Keys: []string{"foo", "bar"},
Values: map[string]datamodel.Node{
"foo": bindnode.Wrap(PointerTo("fooo"), nil),
"bar": bindnode.Wrap(PointerTo("baaar"), nil),
const delegationJson = `
{
"aud":"did:key:def456",
"cmd":"/foo/bar",
"exp":123456,
"iss":"did:key:abc123",
"meta":{
"bar":"baaar",
"foo":"fooo"
},
},
Nbf: PointerTo(int64(123456)),
Exp: PointerTo(int64(123456)),
"nbf":123456,
"nonce":{
"/":{
"bytes":"c3VwZXItcmFuZG9t"
}
},
"pol":[
["==", ".status", "draft"],
["all", ".reviewer", [
["like", ".email", "*@example.com"]]
],
["any", ".tags", [
["or", [
["==", ".", "news"],
["==", ".", "press"]]
]]
]
],
"sub":""
}
`
// format: dagJson --> PayloadModel --> dagCbor --> PayloadModel --> dagJson
// function: DecodeDagJson() EncodeDagCbor() DecodeDagCbor() EncodeDagJson()
cborBytes, err := p.EncodeDagCbor()
p1, err := DecodeDagJson([]byte(delegationJson))
require.NoError(t, err)
cborBytes, err := p1.EncodeDagCbor()
require.NoError(t, err)
fmt.Println("cborBytes length", len(cborBytes))
fmt.Println("cbor", string(cborBytes))
jsonBytes, err := p.EncodeDagJson()
p2, err := DecodeDagCbor(cborBytes)
require.NoError(t, err)
fmt.Println("jsonBytes length", len(jsonBytes))
fmt.Println("json: ", string(jsonBytes))
fmt.Println("read Cbor", p2)
fmt.Println()
readCbor, err := DecodeDagCbor(cborBytes)
readJson, err := p2.EncodeDagJson()
require.NoError(t, err)
fmt.Println("readCbor", readCbor)
require.Equal(t, p, readCbor)
fmt.Println("readJson length", len(readJson))
fmt.Println("json: ", string(readJson))
readJson, err := DecodeDagJson(jsonBytes)
require.NoError(t, err)
fmt.Println("readJson", readJson)
require.Equal(t, p, readJson)
require.JSONEq(t, delegationJson, string(readJson))
}
func BenchmarkSchemaLoad(b *testing.B) {

View File

@@ -6,6 +6,7 @@ import (
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ucan-wg/go-ucan/v1/capability/command"
"github.com/ucan-wg/go-ucan/v1/capability/policy"
"github.com/ucan-wg/go-ucan/v1/did"
)
@@ -18,7 +19,7 @@ type View struct {
// Principal that the chain is about (the Subject)
Subject did.DID
// The Command to eventually invoke
Command string
Command *command.Command
// The delegation policy
Policy policy.Policy
// A unique, random nonce
@@ -56,11 +57,15 @@ func ViewFromModel(m PayloadModel) (*View, error) {
view.Subject = did.Undef
}
// TODO: make that a Command object, and validate it
view.Command = m.Cmd
view.Command, err = command.Parse(m.Cmd)
if err != nil {
return nil, fmt.Errorf("parse command: %w", err)
}
// TODO: parsing + validation
view.Policy = policy.Policy{}
view.Policy, err = policy.FromIPLD(m.Pol)
if err != nil {
return nil, fmt.Errorf("parse policy: %w", err)
}
if len(m.Nonce) == 0 {
return nil, fmt.Errorf("nonce is required")
@@ -80,22 +85,3 @@ func ViewFromModel(m PayloadModel) (*View, error) {
return &view, nil
}
func (view *View) Capability() *Capability {
return &Capability{
Subject: view.Subject,
Command: view.Command,
Policy: view.Policy,
}
}
// Capability is a subset of a delegation formed by the triple (subject, command, policy).
// TODO: useful?
type Capability struct {
// Principal that the chain is about (the Subject)
Subject did.DID
// The Command to eventually invoke
Command string
// The delegation policy
Policy policy.Policy
}