feat(att): first tests proving attenuation, cleanup
This commit is contained in:
@@ -2,20 +2,33 @@ package ucan
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const CapKey = "cap"
|
||||
|
||||
// Attenuations is a list of attenuations
|
||||
type Attenuations []Attenuation
|
||||
|
||||
func (att Attenuations) String() string {
|
||||
str := ""
|
||||
for _, a := range att {
|
||||
str += fmt.Sprintf("%s\n", a)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// Contains is true if all attenuations in b are contained
|
||||
func (att Attenuations) Contains(b Attenuations) bool {
|
||||
// fmt.Printf("%scontains\n%s?\n\n", att, b)
|
||||
LOOP:
|
||||
for _, x := range b {
|
||||
for _, y := range att {
|
||||
if y.Contains(x) {
|
||||
for _, bb := range b {
|
||||
for _, aa := range att {
|
||||
if aa.Contains(bb) {
|
||||
// fmt.Printf("%s contains %s\n", aa, bb)
|
||||
continue LOOP
|
||||
} else if aa.Rsc.Contains(bb.Rsc) {
|
||||
// fmt.Printf("%s < %s\n", aa, bb)
|
||||
// fmt.Printf("rscEq:%t rscContains: %t capContains:%t\n", aa.Rsc.Type() == bb.Rsc.Type(), aa.Rsc.Contains(bb.Rsc), aa.Cap.Contains(bb.Cap))
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -23,24 +36,28 @@ LOOP:
|
||||
return true
|
||||
}
|
||||
|
||||
// AttenuationConstructor is a function that creates an attenuation from a map
|
||||
// Users of this package provide an Attenuation Constructor to the parser to
|
||||
// bind attenuation logic to a UCAN
|
||||
type AttenuationConstructor func(v map[string]interface{}) (Attenuation, error)
|
||||
|
||||
// Attenuation is a capability on a resource
|
||||
type Attenuation struct {
|
||||
Cap Capability
|
||||
Rsc Resource
|
||||
}
|
||||
|
||||
// String formats an attenuation as a string
|
||||
func (a Attenuation) String() string {
|
||||
return fmt.Sprintf("cap:%s %s:%s", a.Cap, a.Rsc.Type(), a.Rsc.Value())
|
||||
}
|
||||
|
||||
// Contains returns true if both
|
||||
func (a Attenuation) Contains(b Attenuation) bool {
|
||||
return a.Rsc.Type() == b.Rsc.Type() && a.Rsc.Contains(b.Rsc) && a.Cap.Contains(b.Cap)
|
||||
}
|
||||
|
||||
func NewAttenuation(cap Capability, rsc Resource) Attenuation {
|
||||
return Attenuation{
|
||||
Rsc: rsc,
|
||||
Cap: cap,
|
||||
}
|
||||
return a.Rsc.Contains(b.Rsc) && a.Cap.Contains(b.Cap)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaller interface
|
||||
func (a Attenuation) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]interface{}{
|
||||
a.Rsc.Type(): a.Rsc.Value(),
|
||||
@@ -48,22 +65,21 @@ func (a Attenuation) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// ResourcePool is a pool of type strings to
|
||||
var ResourcePool map[string]ResourceConstructor
|
||||
|
||||
// Resource is a unique identifier for a thing, usually stored state. Resources
|
||||
// are organized by string types
|
||||
type Resource interface {
|
||||
Type() string
|
||||
Value() string
|
||||
Contains(b Resource) bool
|
||||
}
|
||||
|
||||
type ResourceConstructor func(typ, val string) Resource
|
||||
|
||||
type stringLengthRsc struct {
|
||||
t string
|
||||
v string
|
||||
}
|
||||
|
||||
// NewStringLengthResource is a silly implementation of resource to use while
|
||||
// I figure out what an OR filter on strings is. Don't use this.
|
||||
func NewStringLengthResource(typ, val string) Resource {
|
||||
return stringLengthRsc{
|
||||
t: typ,
|
||||
@@ -80,17 +96,20 @@ func (r stringLengthRsc) Value() string {
|
||||
}
|
||||
|
||||
func (r stringLengthRsc) Contains(b Resource) bool {
|
||||
return len(r.Value()) < len(b.Value())
|
||||
return r.Type() == b.Type() && len(r.Value()) <= len(b.Value())
|
||||
}
|
||||
|
||||
// Capability is the interface for an action users can perform
|
||||
// Capability is an action users can perform
|
||||
type Capability interface {
|
||||
// A Capability must be expressable as a string
|
||||
String() string
|
||||
// Capabilities must be comparable to other same-type capabilities
|
||||
Contains(b Capability) bool
|
||||
}
|
||||
|
||||
// NestedCapabilities is a basic implementation of the Capabilities interface
|
||||
// based on a hierarchal list of strings
|
||||
// based on a hierarchal list of strings ordered from most to least capable
|
||||
// It is both a capability and a capability factory with the .Cap method
|
||||
type NestedCapabilities struct {
|
||||
cap string
|
||||
idx int
|
||||
@@ -100,7 +119,7 @@ type NestedCapabilities struct {
|
||||
// assert at compile-time NestedCapabilities implements Capability
|
||||
var _ Capability = (*NestedCapabilities)(nil)
|
||||
|
||||
// NewNestedCapabilities
|
||||
// NewNestedCapabilities creates a set of NestedCapabilities
|
||||
func NewNestedCapabilities(strs ...string) NestedCapabilities {
|
||||
return NestedCapabilities{
|
||||
cap: strs[0],
|
||||
@@ -109,13 +128,18 @@ func NewNestedCapabilities(strs ...string) NestedCapabilities {
|
||||
}
|
||||
}
|
||||
|
||||
// Cap creates a new capability from the hierarchy
|
||||
func (nc NestedCapabilities) Cap(str string) Capability {
|
||||
idx := -1
|
||||
for i, c := range *nc.hierarchy {
|
||||
if c == str {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx == -1 {
|
||||
panic(fmt.Sprintf("%s is not a nested capability. must be one of: %v", str, *nc.hierarchy))
|
||||
}
|
||||
|
||||
return NestedCapabilities{
|
||||
cap: str,
|
||||
@@ -124,18 +148,20 @@ func (nc NestedCapabilities) Cap(str string) Capability {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the Capability value as a string
|
||||
func (nc NestedCapabilities) String() string {
|
||||
return nc.cap
|
||||
}
|
||||
|
||||
// Contains returns true if cap is equal or less than the NestedCapability value
|
||||
func (nc NestedCapabilities) Contains(cap Capability) bool {
|
||||
str := cap.String()
|
||||
for i, c := range *nc.hierarchy {
|
||||
if c == str {
|
||||
if i > nc.idx {
|
||||
return false
|
||||
if i >= nc.idx {
|
||||
return true
|
||||
}
|
||||
return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -1,9 +1,96 @@
|
||||
package ucan
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAttenuationsContains(t *testing.T) {
|
||||
aContains := [][2]string{
|
||||
{
|
||||
`[
|
||||
{ "cap": "SUPER_USER", "dataset": "b5/world_bank_population"},
|
||||
{ "cap": "OVERWRITE", "api": "https://api.qri.cloud" }
|
||||
]`,
|
||||
`[
|
||||
{"cap": "SOFT_DELETE", "dataset": "b5/world_bank_population" }
|
||||
]`,
|
||||
},
|
||||
{
|
||||
`[
|
||||
{ "cap": "SUPER_USER", "dataset": "b5/world_bank_population"},
|
||||
{ "cap": "OVERWRITE", "api": "https://api.qri.cloud" }
|
||||
]`,
|
||||
`[
|
||||
{"cap": "SUPER_USER", "dataset": "b5/world_bank_population" }
|
||||
]`,
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range aContains {
|
||||
t.Run(fmt.Sprintf("contains_%d", i), func(t *testing.T) {
|
||||
a := testAttenuations(c[0])
|
||||
b := testAttenuations(c[1])
|
||||
if !a.Contains(b) {
|
||||
t.Errorf("expected a attenuations to contain b attenuations")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
aNotContains := [][2]string{
|
||||
{
|
||||
`[
|
||||
{ "cap": "SUPER_USER", "dataset": "b5/world_bank_population"},
|
||||
{ "cap": "OVERWRITE", "api": "https://api.qri.cloud" }
|
||||
]`,
|
||||
`[
|
||||
{ "cap": "CREATE", "dataset": "b5" }
|
||||
]`,
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range aNotContains {
|
||||
t.Run(fmt.Sprintf("not_contains_%d", i), func(t *testing.T) {
|
||||
a := testAttenuations(c[0])
|
||||
b := testAttenuations(c[1])
|
||||
if a.Contains(b) {
|
||||
t.Errorf("expected a attenuations to NOT contain b attenuations")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustJSON(data string, v interface{}) {
|
||||
if err := json.Unmarshal([]byte(data), v); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testAttenuations(data string) Attenuations {
|
||||
caps := NewNestedCapabilities("SUPER_USER", "OVERWRITE", "SOFT_DELETE", "REVISE", "CREATE")
|
||||
|
||||
v := []map[string]string{}
|
||||
mustJSON(data, &v)
|
||||
|
||||
var att Attenuations
|
||||
for _, x := range v {
|
||||
var cap Capability
|
||||
var rsc Resource
|
||||
for key, val := range x {
|
||||
switch key {
|
||||
case CapKey:
|
||||
cap = caps.Cap(val)
|
||||
default:
|
||||
rsc = NewStringLengthResource(key, val)
|
||||
}
|
||||
}
|
||||
att = append(att, Attenuation{cap, rsc})
|
||||
}
|
||||
|
||||
return att
|
||||
}
|
||||
|
||||
func TestNestedCapabilities(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ func CtxWithUCAN(ctx context.Context, t UCAN) context.Context {
|
||||
return context.WithValue(ctx, UCANCtxKey, t)
|
||||
}
|
||||
|
||||
// UCANFromCtx extracts a Dataset reference from a given
|
||||
// FromCtx extracts a Dataset reference from a given
|
||||
// context if one is set, returning nil otherwise
|
||||
func UCANFromCtx(ctx context.Context) *UCAN {
|
||||
func FromCtx(ctx context.Context) *UCAN {
|
||||
iface := ctx.Value(UCANCtxKey)
|
||||
if ref, ok := iface.(*UCAN); ok {
|
||||
return ref
|
||||
|
||||
@@ -23,10 +23,10 @@ func ExampleWalkthrough() {
|
||||
zero := time.Time{}
|
||||
|
||||
// create a root UCAN
|
||||
rootToken, err := source.NewRootUCAN(subjectDID, att, nil, zero, zero)
|
||||
origin, err := source.NewOriginUCAN(subjectDID, att, nil, zero, zero)
|
||||
panicIfError(err)
|
||||
|
||||
id, err := rootToken.CID()
|
||||
id, err := origin.CID()
|
||||
panicIfError(err)
|
||||
|
||||
fmt.Printf("cid of root UCAN: %s\n", id.String())
|
||||
@@ -35,17 +35,30 @@ func ExampleWalkthrough() {
|
||||
{caps.Cap("SUPER_USER"), ucan.NewStringLengthResource("dataset", "third:resource")},
|
||||
}
|
||||
|
||||
if _, err = source.NewAttenuatedUCAN(rootToken, subjectDID, att, nil, zero, zero); err != nil {
|
||||
if _, err = source.NewAttenuatedUCAN(origin, subjectDID, att, nil, zero, zero); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
att = ucan.Attenuations{
|
||||
{caps.Cap("OVERWRITE"), ucan.NewStringLengthResource("dataset", "b5:world_bank_population:*")},
|
||||
}
|
||||
|
||||
derivedToken, err := source.NewAttenuatedUCAN(origin, subjectDID, att, nil, zero, zero)
|
||||
panicIfError(err)
|
||||
|
||||
id, err = derivedToken.CID()
|
||||
panicIfError(err)
|
||||
|
||||
fmt.Printf("cid of derived UCAN: %s\n", id.String())
|
||||
|
||||
p := exampleParser()
|
||||
_, err = p.ParseAndVerify(context.Background(), rootToken.Raw)
|
||||
_, err = p.ParseAndVerify(context.Background(), origin.Raw)
|
||||
panicIfError(err)
|
||||
|
||||
// Output:
|
||||
// cid of root UCAN: bafkreidhsvhlctwylgeibl2eeapdvbl3qm3mbqcqhxhvy4grmr25ji77hu
|
||||
// scope of ucan attenuations must be less than it's parent
|
||||
// cid of derived UCAN: bafkreifglbwtr27fbzmv3uardlygvggr722fckusfvfyfsonwkroca7efu
|
||||
}
|
||||
|
||||
func panicIfError(err error) {
|
||||
|
||||
17
proof.go
Normal file
17
proof.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package ucan
|
||||
|
||||
import (
|
||||
"github.com/ipfs/go-cid"
|
||||
)
|
||||
|
||||
// Proof is a string representing a fact. Expected to be either a raw UCAN token
|
||||
// or the CID of a raw UCAN token
|
||||
type Proof string
|
||||
|
||||
// IsCID returns true if the Proof string is a CID
|
||||
func (prf Proof) IsCID() bool {
|
||||
if _, err := cid.Decode(string(prf)); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
84
token.go
84
token.go
@@ -1,3 +1,11 @@
|
||||
// Package ucan implements User-Controlled Authorization Network tokens by
|
||||
// fission:
|
||||
// https://whitepaper.fission.codes/access-control/ucan/ucan-tokens
|
||||
//
|
||||
// From the paper:
|
||||
// The UCAN format is designed as an authenticated digraph in some larger
|
||||
// authorization space. The other way to view this is as a function from a set
|
||||
// of authorizations (“UCAN proofs“) to a subset output (“UCAN capabilities”).
|
||||
package ucan
|
||||
|
||||
import (
|
||||
@@ -24,12 +32,20 @@ const (
|
||||
UCANVersion = "0.4.0"
|
||||
// UCANVersionKey is the key used in version headers for the UCAN spec
|
||||
UCANVersionKey = "ucv"
|
||||
// PrfKey denotes "Proofs" in a UCAN. Stored in JWT Claims
|
||||
PrfKey = "prf"
|
||||
// FctKey denotes "Facts" in a UCAN. Stored in JWT Claims
|
||||
FctKey = "fct"
|
||||
// AttKey denotes "Attenuations" in a UCAN. Stored in JWT Claims
|
||||
AttKey = "att"
|
||||
// CapKey indicates a resource Capability. Used in an attenuation
|
||||
CapKey = "cap"
|
||||
)
|
||||
|
||||
// UCAN is a JSON Web Token (JWT) that contains special keys
|
||||
type UCAN struct {
|
||||
// Entire UCAN as a signed JWT string
|
||||
Raw string
|
||||
|
||||
// the "inputs" to this token, a chain UCAN tokens with broader scopes &
|
||||
// deadlines than this token
|
||||
Proofs []Proof `json:"prf,omitempty"`
|
||||
@@ -57,15 +73,22 @@ func (t *UCAN) PrefixCID(pref cid.Prefix) (cid.Cid, error) {
|
||||
return pref.Sum([]byte(t.Raw))
|
||||
}
|
||||
|
||||
type Proof string
|
||||
|
||||
func (prf Proof) IsCID() bool {
|
||||
if _, err := cid.Decode(string(prf)); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
// Claims is the claims component of a UCAN token. UCAN claims are expressed
|
||||
// as a standard JWT claims object with additional special fields
|
||||
type Claims struct {
|
||||
*jwt.StandardClaims
|
||||
// the "inputs" to this token, a chain UCAN tokens with broader scopes &
|
||||
// deadlines than this token
|
||||
// Proofs are UCAN chains, leading back to a self-evident origin token
|
||||
Proofs []Proof `json:"prf,omitempty"`
|
||||
// the "outputs" of this token, an array of heterogenous resources &
|
||||
// capabilities
|
||||
Attenuations Attenuations `json:"att,omitempty"`
|
||||
// Facts are facts, jack.
|
||||
Facts []Fact `json:"fct,omitempty"`
|
||||
}
|
||||
|
||||
// Fact is self-evident statement
|
||||
type Fact struct {
|
||||
cidString string
|
||||
value map[string]interface{}
|
||||
@@ -80,6 +103,9 @@ type Fact struct {
|
||||
// }
|
||||
// }
|
||||
|
||||
// CIDBytesResolver is a small interface for turning a CID into the bytes
|
||||
// they reference. In practice this may be backed by a network connection that
|
||||
// can fetch CIDs, eg: IPFS.
|
||||
type CIDBytesResolver interface {
|
||||
ResolveCIDBytes(ctx context.Context, id cid.Cid) ([]byte, error)
|
||||
}
|
||||
@@ -90,22 +116,10 @@ type CIDBytesResolver interface {
|
||||
// implementations of UCANSource must conform to the assertion test defined
|
||||
// in the spec subpackage
|
||||
type UCANSource interface {
|
||||
NewRootUCAN(subjectDID string, att Attenuations, fct []Fact, notBefore, expires time.Time) (*UCAN, error)
|
||||
NewOriginUCAN(subjectDID string, att Attenuations, fct []Fact, notBefore, expires time.Time) (*UCAN, error)
|
||||
NewAttenuatedUCAN(parent *UCAN, subjectDID string, att Attenuations, fct []Fact, notBefore, expires time.Time) (*UCAN, error)
|
||||
}
|
||||
|
||||
type UCANClaims struct {
|
||||
*jwt.StandardClaims
|
||||
// the "inputs" to this token, a chain UCAN tokens with broader scopes &
|
||||
// deadlines than this token
|
||||
Proofs []Proof `json:"prf,omitempty"`
|
||||
// the "outputs" of this token, an array of heterogenous resources &
|
||||
// capabilities
|
||||
Attenuations Attenuations `json:"att,omitempty"`
|
||||
// Facts are facts, jack.
|
||||
Facts []Fact `json:"fct,omitempty"`
|
||||
}
|
||||
|
||||
type pkUCANSource struct {
|
||||
pk crypto.PrivKey
|
||||
issuerDID string
|
||||
@@ -171,7 +185,7 @@ func NewPrivKeyUCANSource(privKey crypto.PrivKey) (UCANSource, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *pkUCANSource) NewRootUCAN(subjectDID string, att Attenuations, fct []Fact, nbf, exp time.Time) (*UCAN, error) {
|
||||
func (a *pkUCANSource) NewOriginUCAN(subjectDID string, att Attenuations, fct []Fact, nbf, exp time.Time) (*UCAN, error) {
|
||||
return a.newUCAN(subjectDID, nil, att, fct, nbf, exp)
|
||||
}
|
||||
|
||||
@@ -206,7 +220,7 @@ func (a *pkUCANSource) newUCAN(subjectDID string, prf []Proof, att Attenuations,
|
||||
}
|
||||
|
||||
// set our claims
|
||||
t.Claims = &UCANClaims{
|
||||
t.Claims = &Claims{
|
||||
StandardClaims: &jwt.StandardClaims{
|
||||
Issuer: a.issuerDID,
|
||||
Subject: subjectDID,
|
||||
@@ -277,6 +291,10 @@ func NewUCANParser(ap AttenuationConstructor, didr DIDPubKeyResolver, cidr CIDBy
|
||||
|
||||
// ParseAndVerify will parse, validate and return a token
|
||||
func (p *UCANParser) ParseAndVerify(ctx context.Context, raw string) (*UCAN, error) {
|
||||
return p.parseAndVerify(ctx, raw, nil)
|
||||
}
|
||||
|
||||
func (p *UCANParser) parseAndVerify(ctx context.Context, raw string, child *UCAN) (*UCAN, error) {
|
||||
tok, err := jwt.Parse(raw, p.matchVerifyKeyFunc(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -288,7 +306,7 @@ func (p *UCANParser) ParseAndVerify(ctx context.Context, raw string) (*UCAN, err
|
||||
}
|
||||
|
||||
var att Attenuations
|
||||
if acci, ok := mc["att"].([]interface{}); ok {
|
||||
if acci, ok := mc[AttKey].([]interface{}); ok {
|
||||
for i, a := range acci {
|
||||
if mapv, ok := a.(map[string]interface{}); ok {
|
||||
a, err := p.ap(mapv)
|
||||
@@ -297,16 +315,30 @@ func (p *UCANParser) ParseAndVerify(ctx context.Context, raw string) (*UCAN, err
|
||||
}
|
||||
att = append(att, a)
|
||||
} else {
|
||||
return nil, fmt.Errorf(`"acc[%d]" is not an object`, i)
|
||||
return nil, fmt.Errorf(`"att[%d]" is not an object`, i)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf(`"acc" key is not an array`)
|
||||
return nil, fmt.Errorf(`"att" key is not an array`)
|
||||
}
|
||||
|
||||
var prf []Proof
|
||||
if prfi, ok := mc[PrfKey].([]interface{}); ok {
|
||||
for i, a := range prfi {
|
||||
if pStr, ok := a.(string); ok {
|
||||
prf = append(prf, Proof(pStr))
|
||||
} else {
|
||||
return nil, fmt.Errorf(`"prf[%d]" is not a string`, i)
|
||||
}
|
||||
}
|
||||
} else if mc[PrfKey] != nil {
|
||||
return nil, fmt.Errorf(`"prf" key is not an array`)
|
||||
}
|
||||
|
||||
return &UCAN{
|
||||
Raw: raw,
|
||||
Attenuations: att,
|
||||
Proofs: prf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrivKeyTokens(t *testing.T) {
|
||||
tokens, err := ucan.NewPrivKeyUCANSource(keyOne)
|
||||
func TestPrivKeySource(t *testing.T) {
|
||||
source, err := ucan.NewPrivKeyUCANSource(keyOne)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -57,14 +57,30 @@ func TestPrivKeyTokens(t *testing.T) {
|
||||
}
|
||||
zero := time.Time{}
|
||||
|
||||
token, err := tokens.NewRootUCAN(didStr, att, nil, zero, zero)
|
||||
root, err := source.NewOriginUCAN(didStr, att, nil, zero, zero)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expect := `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVjdiI6IjAuNC4wIn0.eyJpc3MiOiJkaWQ6a2V5Ok1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBb2FkUjRtY1U3QzBBbWg1bHRfM0hObVEyYVlDOEotYU5mNTJUNEtrLTBzbHh6LVc1LXhrREJ0NUR4RUZuSmVKNGJTMV9ZWkt3UkxKQjYzU0phcWZjMXhUTUFYMnJmcW44d3NwUmd2MEFReGU4RV9icGkzZTUyNnU2UU1VRjdYbDRKN2JkbVlZT0lCUDVCSk83eU1pX2pfU3FWaVdmOG82Y3BJTEF3dXpUNTY2X0ttUWFOclM5QmVNUHQ5NTJZUk1lejZlMFoycXR0aVRQS3hmalJ3b0VwRklldDVhZTFZY0p2VDBLQnJiZEYwNXhDc2F6RUoxSm52eUlSamNiUE9FYVljUjNPZnAxdW8ySTRKdVczQ2FKeHNqMU8yNnZyLWRUSzlqcGVFVTl5X1dUU1lNOUVsazBwZ0xZZ1M4ZHE4aTYwNDVnejByemU4QzV2YkZoSFZwa1ZRSURBUUFCIiwic3ViIjoiZGlkOmtleTpNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQW9hZFI0bWNVN0MwQW1oNWx0XzNITm1RMmFZQzhKLWFOZjUyVDRLay0wc2x4ei1XNS14a0RCdDVEeEVGbkplSjRiUzFfWVpLd1JMSkI2M1NKYXFmYzF4VE1BWDJyZnFuOHdzcFJndjBBUXhlOEVfYnBpM2U1MjZ1NlFNVUY3WGw0SjdiZG1ZWU9JQlA1QkpPN3lNaV9qX1NxVmlXZjhvNmNwSUxBd3V6VDU2Nl9LbVFhTnJTOUJlTVB0OTUyWVJNZXo2ZTBaMnF0dGlUUEt4ZmpSd29FcEZJZXQ1YWUxWWNKdlQwS0JyYmRGMDV4Q3NhekVKMUpudnlJUmpjYlBPRWFZY1IzT2ZwMXVvMkk0SnVXM0NhSnhzajFPMjZ2ci1kVEs5anBlRVU5eV9XVFNZTTlFbGswcGdMWWdTOGRxOGk2MDQ1Z3owcnplOEM1dmJGaEhWcGtWUUlEQVFBQiIsImF0dCI6W3siYXBpIjoiKiIsImNhcCI6IlNVUEVSX1VTRVIifSx7ImNhcCI6IlNVUEVSX1VTRVIiLCJkYXRhc2V0IjoiYjU6d29ybGRfYmFua19wb3B1bGF0aW9uOioifV19.Z32-i-pGAtPRsG0JW4ZS8-c17x3mX3kFrmZ0BYhyWk2JH4QMwXFRtkUl8xVQtrC3JigeQeaDiz-WTUSFqJIs5dunL1Xf_SXqq8SZ7NCh6u6OEo2L1BnQkwdO8kDsFoiF42byWDBwzHRog0N-pRXgMhlo8si6Pek4KAZokQ5F-8FuLb3MXXxc9-FnhGRsKgGt_bNWS322h5gXCaXJAzbdAHwGSlORCCJI4CrbWUHs03i4viun2Ht01JO-p4ySlut6YyQ_vW4NGNSAAXGeR-ggkB0B6TGgt695CxX1zgQKV7X6JZx-NF_J-OXCIWngCfr6VdRv1_ADce9s1ODEm2N7eA`
|
||||
if expect != token.Raw {
|
||||
t.Errorf("token mismatch. expected: %q.\ngot: %q", expect, token.Raw)
|
||||
if expect != root.Raw {
|
||||
t.Errorf("token mismatch. expected: %q.\ngot: %q", expect, root.Raw)
|
||||
}
|
||||
|
||||
att = ucan.Attenuations{
|
||||
{caps.Cap("OVERWRITE"), ucan.NewStringLengthResource("dataset", "b5:world_bank_population:*")},
|
||||
}
|
||||
|
||||
derivedToken, err := source.NewAttenuatedUCAN(root, didStr, att, nil, zero, zero)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cidStr := mustCidString(t, derivedToken)
|
||||
expectCID := "bafkreifglbwtr27fbzmv3uardlygvggr722fckusfvfyfsonwkroca7efu"
|
||||
|
||||
if expectCID != cidStr {
|
||||
t.Errorf("derived token CID mismatch. expected: %q.\ngot: %q", expectCID, cidStr)
|
||||
}
|
||||
|
||||
// tokenWithExpiryString, err := tokens.CreateToken(pro, time.Hour)
|
||||
@@ -120,3 +136,12 @@ func TestTokenParse(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func mustCidString(t *testing.T, tok *ucan.UCAN) string {
|
||||
t.Helper()
|
||||
id, err := tok.CID()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return id.String()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user