github.com/storacha/go-ucanto@v0.7.2/validator/lib_test.go (about) 1 package validator 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/ipfs/go-cid" 11 "github.com/ipld/go-ipld-prime" 12 cidlink "github.com/ipld/go-ipld-prime/linking/cid" 13 "github.com/ipld/go-ipld-prime/node/basicnode" 14 "github.com/storacha/go-ucanto/core/dag/blockstore" 15 "github.com/storacha/go-ucanto/core/delegation" 16 "github.com/storacha/go-ucanto/core/invocation" 17 "github.com/storacha/go-ucanto/core/ipld/block" 18 "github.com/storacha/go-ucanto/core/ipld/codec/cbor" 19 "github.com/storacha/go-ucanto/core/ipld/hash/sha256" 20 "github.com/storacha/go-ucanto/core/result/failure" 21 "github.com/storacha/go-ucanto/core/schema" 22 "github.com/storacha/go-ucanto/did" 23 "github.com/storacha/go-ucanto/principal" 24 "github.com/storacha/go-ucanto/principal/ed25519/verifier" 25 "github.com/storacha/go-ucanto/principal/signer" 26 "github.com/storacha/go-ucanto/testing/fixtures" 27 "github.com/storacha/go-ucanto/testing/helpers" 28 "github.com/storacha/go-ucanto/ucan" 29 udm "github.com/storacha/go-ucanto/ucan/datamodel/ucan" 30 "github.com/stretchr/testify/require" 31 ) 32 33 type storeAddCaveats struct { 34 Link ipld.Link 35 Origin ipld.Link 36 } 37 38 func (c storeAddCaveats) ToIPLD() (ipld.Node, error) { 39 np := basicnode.Prototype.Any 40 nb := np.NewBuilder() 41 ma, _ := nb.BeginMap(2) 42 if c != (storeAddCaveats{}) { 43 ma.AssembleKey().AssignString("link") 44 ma.AssembleValue().AssignLink(c.Link) 45 if c.Origin != nil { 46 ma.AssembleKey().AssignString("origin") 47 ma.AssembleValue().AssignLink(c.Origin) 48 } 49 } 50 ma.Finish() 51 return nb.Build(), nil 52 } 53 54 var storeAddTyp = helpers.Must(ipld.LoadSchemaBytes([]byte(` 55 type StoreAddCaveats struct { 56 link Link 57 origin optional Link 58 } 59 `))) 60 61 var storeAdd = NewCapability( 62 "store/add", 63 schema.DIDString(), 64 schema.Struct[storeAddCaveats](storeAddTyp.TypeByName("StoreAddCaveats"), nil), 65 func(claimed, delegated ucan.Capability[storeAddCaveats]) failure.Failure { 66 if claimed.With() != delegated.With() { 67 err := fmt.Errorf("Expected 'with: \"%s\"' instead got '%s'", delegated.With(), claimed.With()) 68 return failure.FromError(err) 69 } 70 if delegated.Nb().Link != nil && delegated.Nb().Link != claimed.Nb().Link { 71 var err error 72 if claimed.Nb().Link == nil { 73 err = fmt.Errorf("Link violates imposed %s constraint", delegated.Nb().Link) 74 } else { 75 err = fmt.Errorf("Link %s violates imposed %s constraint", claimed.Nb().Link, delegated.Nb().Link) 76 } 77 return failure.FromError(err) 78 } 79 return nil 80 }, 81 ) 82 83 var testLink = cidlink.Link{Cid: cid.MustParse("bafkqaaa")} 84 var validateAuthOk = func(ctx context.Context, auth Authorization[any]) Revoked { return nil } 85 var parseEdPrincipal = func(str string) (principal.Verifier, error) { 86 return verifier.Parse(str) 87 } 88 89 func TestAccess(t *testing.T) { 90 t.Run("authorized", func(t *testing.T) { 91 t.Run("self-issued invocation", func(t *testing.T) { 92 inv, err := storeAdd.Invoke( 93 fixtures.Alice, 94 fixtures.Bob, 95 fixtures.Alice.DID().String(), 96 storeAddCaveats{Link: testLink}, 97 ) 98 require.NoError(t, err) 99 100 vctx := NewValidationContext( 101 fixtures.Service.Verifier(), 102 storeAdd, 103 IsSelfIssued, 104 validateAuthOk, 105 ProofUnavailable, 106 parseEdPrincipal, 107 FailDIDKeyResolution, 108 NotExpiredNotTooEarly, 109 ) 110 111 a, x := Access(t.Context(), inv, vctx) 112 require.NoError(t, x) 113 require.Equal(t, storeAdd.Can(), a.Capability().Can()) 114 require.Equal(t, fixtures.Alice.DID().String(), a.Capability().With()) 115 require.Equal(t, fixtures.Alice.DID(), a.Issuer().DID()) 116 require.Equal(t, fixtures.Bob.DID(), a.Audience().DID()) 117 }) 118 119 t.Run("delegated invocation", func(t *testing.T) { 120 dlg, err := storeAdd.Delegate( 121 fixtures.Alice, 122 fixtures.Bob, 123 fixtures.Alice.DID().String(), 124 storeAddCaveats{}, 125 ) 126 require.NoError(t, err) 127 128 inv, err := storeAdd.Invoke( 129 fixtures.Bob, 130 fixtures.Service, 131 fixtures.Alice.DID().String(), 132 storeAddCaveats{Link: testLink}, 133 delegation.WithProof(delegation.FromDelegation(dlg)), 134 ) 135 require.NoError(t, err) 136 137 vctx := NewValidationContext( 138 fixtures.Service.Verifier(), 139 storeAdd, 140 IsSelfIssued, 141 validateAuthOk, 142 ProofUnavailable, 143 parseEdPrincipal, 144 FailDIDKeyResolution, 145 NotExpiredNotTooEarly, 146 ) 147 148 a, x := Access(t.Context(), inv, vctx) 149 require.NoError(t, x) 150 require.Equal(t, storeAdd.Can(), a.Capability().Can()) 151 require.Equal(t, fixtures.Alice.DID().String(), a.Capability().With()) 152 require.Equal(t, fixtures.Bob.DID(), a.Issuer().DID()) 153 require.Equal(t, fixtures.Service.DID(), a.Audience().DID()) 154 }) 155 156 t.Run("delegation chain", func(t *testing.T) { 157 alice2bob, err := storeAdd.Delegate( 158 fixtures.Alice, 159 fixtures.Bob, 160 fixtures.Alice.DID().String(), 161 storeAddCaveats{}, 162 ) 163 require.NoError(t, err) 164 165 bob2mallory, err := storeAdd.Delegate( 166 fixtures.Bob, 167 fixtures.Mallory, 168 fixtures.Alice.DID().String(), 169 storeAddCaveats{}, 170 delegation.WithProof(delegation.FromDelegation(alice2bob)), 171 ) 172 require.NoError(t, err) 173 174 inv, err := storeAdd.Invoke( 175 fixtures.Mallory, 176 fixtures.Service, 177 fixtures.Alice.DID().String(), 178 storeAddCaveats{Link: testLink}, 179 delegation.WithProof(delegation.FromDelegation(bob2mallory)), 180 ) 181 require.NoError(t, err) 182 183 vctx := NewValidationContext( 184 fixtures.Service.Verifier(), 185 storeAdd, 186 IsSelfIssued, 187 validateAuthOk, 188 ProofUnavailable, 189 parseEdPrincipal, 190 FailDIDKeyResolution, 191 NotExpiredNotTooEarly, 192 ) 193 194 a, x := Access(t.Context(), inv, vctx) 195 require.NoError(t, x) 196 require.Equal(t, storeAdd.Can(), a.Capability().Can()) 197 require.Equal(t, fixtures.Alice.DID().String(), a.Capability().With()) 198 require.Equal(t, fixtures.Mallory.DID(), a.Issuer().DID()) 199 require.Equal(t, fixtures.Service.DID(), a.Audience().DID()) 200 201 require.Equal(t, storeAdd.Can(), a.Proofs()[0].Capability().Can()) 202 require.Equal(t, fixtures.Alice.DID().String(), a.Proofs()[0].Capability().With()) 203 require.Equal(t, fixtures.Bob.DID(), a.Proofs()[0].Issuer().DID()) 204 require.Equal(t, fixtures.Mallory.DID(), a.Proofs()[0].Audience().DID()) 205 206 require.Equal(t, storeAdd.Can(), a.Proofs()[0].Proofs()[0].Capability().Can()) 207 require.Equal(t, fixtures.Alice.DID().String(), a.Proofs()[0].Proofs()[0].Capability().With()) 208 require.Equal(t, fixtures.Alice.DID(), a.Proofs()[0].Proofs()[0].Issuer().DID()) 209 require.Equal(t, fixtures.Bob.DID(), a.Proofs()[0].Proofs()[0].Audience().DID()) 210 }) 211 212 t.Run("resolve external proof", func(t *testing.T) { 213 dlg, err := storeAdd.Delegate( 214 fixtures.Alice, 215 fixtures.Bob, 216 fixtures.Alice.DID().String(), 217 storeAddCaveats{}, 218 ) 219 require.NoError(t, err) 220 221 inv, err := storeAdd.Invoke( 222 fixtures.Bob, 223 fixtures.Service, 224 fixtures.Alice.DID().String(), 225 storeAddCaveats{Link: testLink}, 226 delegation.WithProof(delegation.FromDelegation(dlg)), 227 ) 228 require.NoError(t, err) 229 230 vctx := NewValidationContext( 231 fixtures.Service.Verifier(), 232 storeAdd, 233 IsSelfIssued, 234 validateAuthOk, 235 func(ctx context.Context, p ucan.Link) (delegation.Delegation, UnavailableProof) { 236 if p == dlg.Link() { 237 return dlg, nil 238 } 239 return nil, NewUnavailableProofError(p, fmt.Errorf("no proof resolver configured")) 240 }, 241 parseEdPrincipal, 242 FailDIDKeyResolution, 243 NotExpiredNotTooEarly, 244 ) 245 246 a, x := Access(t.Context(), inv, vctx) 247 require.NoError(t, x) 248 require.Equal(t, storeAdd.Can(), a.Capability().Can()) 249 require.Equal(t, fixtures.Alice.DID().String(), a.Capability().With()) 250 require.Equal(t, fixtures.Bob.DID(), a.Issuer().DID()) 251 require.Equal(t, fixtures.Service.DID(), a.Audience().DID()) 252 253 require.Equal(t, storeAdd.Can(), a.Proofs()[0].Capability().Can()) 254 require.Equal(t, fixtures.Alice.DID().String(), a.Proofs()[0].Capability().With()) 255 require.Equal(t, fixtures.Alice.DID(), a.Proofs()[0].Issuer().DID()) 256 require.Equal(t, fixtures.Bob.DID(), a.Proofs()[0].Audience().DID()) 257 }) 258 259 t.Run("expiration and not valid before can be ignored", func(t *testing.T) { 260 exp := ucan.Now() - 5 261 nbf := ucan.Now() + 500 262 inv, err := storeAdd.Invoke( 263 fixtures.Alice, 264 fixtures.Service, 265 fixtures.Alice.DID().String(), 266 storeAddCaveats{Link: testLink}, 267 delegation.WithExpiration(exp), 268 delegation.WithNotBefore(nbf), 269 ) 270 require.NoError(t, err) 271 272 vctx := NewValidationContext( 273 fixtures.Service.Verifier(), 274 storeAdd, 275 IsSelfIssued, 276 validateAuthOk, 277 ProofUnavailable, 278 parseEdPrincipal, 279 FailDIDKeyResolution, 280 func(dlg delegation.Delegation) InvalidProof { 281 return nil 282 }, 283 ) 284 285 a, x := Access(t.Context(), inv, vctx) 286 require.NoError(t, x) 287 require.Equal(t, storeAdd.Can(), a.Capability().Can()) 288 require.Equal(t, fixtures.Alice.DID().String(), a.Capability().With()) 289 require.Equal(t, fixtures.Alice.DID(), a.Issuer().DID()) 290 require.Equal(t, fixtures.Service.DID(), a.Audience().DID()) 291 }) 292 }) 293 294 t.Run("unauthorized", func(t *testing.T) { 295 t.Run("expired invocation", func(t *testing.T) { 296 exp := ucan.Now() - 5 297 inv, err := storeAdd.Invoke( 298 fixtures.Alice, 299 fixtures.Service, 300 fixtures.Alice.DID().String(), 301 storeAddCaveats{Link: testLink}, 302 delegation.WithExpiration(exp), 303 ) 304 require.NoError(t, err) 305 306 vctx := NewValidationContext( 307 fixtures.Service.Verifier(), 308 storeAdd, 309 IsSelfIssued, 310 validateAuthOk, 311 ProofUnavailable, 312 parseEdPrincipal, 313 FailDIDKeyResolution, 314 NotExpiredNotTooEarly, 315 ) 316 317 a, x := Access(t.Context(), inv, vctx) 318 require.Nil(t, a) 319 require.Error(t, x) 320 require.Equal(t, x.Name(), "Unauthorized") 321 msg := strings.Join([]string{ 322 fmt.Sprintf("Claim %s is not authorized", storeAdd), 323 fmt.Sprintf(" - Proof %s has expired on %s", inv.Link(), time.Unix(int64(exp), 0).Format(time.RFC3339)), 324 }, "\n") 325 require.Equal(t, msg, x.Error()) 326 }) 327 328 t.Run("not valid before", func(t *testing.T) { 329 nbf := ucan.Now() + 500 330 inv, err := storeAdd.Invoke( 331 fixtures.Alice, 332 fixtures.Service, 333 fixtures.Alice.DID().String(), 334 storeAddCaveats{Link: testLink}, 335 delegation.WithNotBefore(nbf), 336 ) 337 require.NoError(t, err) 338 339 vctx := NewValidationContext( 340 fixtures.Service.Verifier(), 341 storeAdd, 342 IsSelfIssued, 343 validateAuthOk, 344 ProofUnavailable, 345 parseEdPrincipal, 346 FailDIDKeyResolution, 347 NotExpiredNotTooEarly, 348 ) 349 350 a, x := Access(t.Context(), inv, vctx) 351 require.Nil(t, a) 352 require.Error(t, x) 353 require.Equal(t, x.Name(), "Unauthorized") 354 msg := strings.Join([]string{ 355 fmt.Sprintf("Claim %s is not authorized", storeAdd), 356 fmt.Sprintf(" - Proof %s is not valid before %s", inv.Link(), time.Unix(int64(nbf), 0).Format(time.RFC3339)), 357 }, "\n") 358 require.Equal(t, msg, x.Error()) 359 }) 360 361 t.Run("invalid signature", func(t *testing.T) { 362 inv, err := storeAdd.Invoke( 363 fixtures.Alice, 364 fixtures.Service, 365 fixtures.Alice.DID().String(), 366 storeAddCaveats{Link: testLink}, 367 ) 368 require.NoError(t, err) 369 370 inv.Data().Model().S = fixtures.Bob.Sign(inv.Root().Bytes()).Bytes() 371 372 vctx := NewValidationContext( 373 fixtures.Service.Verifier(), 374 storeAdd, 375 IsSelfIssued, 376 validateAuthOk, 377 ProofUnavailable, 378 parseEdPrincipal, 379 FailDIDKeyResolution, 380 NotExpiredNotTooEarly, 381 ) 382 383 a, x := Access(t.Context(), inv, vctx) 384 require.Nil(t, a) 385 require.Error(t, x) 386 require.Equal(t, x.Name(), "Unauthorized") 387 msg := strings.Join([]string{ 388 fmt.Sprintf("Claim %s is not authorized", storeAdd), 389 fmt.Sprintf(" - Proof %s does not have a valid signature from %s", inv.Link(), fixtures.Alice.DID()), 390 }, "\n") 391 require.Equal(t, msg, x.Error()) 392 }) 393 394 t.Run("unknown capability", func(t *testing.T) { 395 inv, err := invocation.Invoke( 396 fixtures.Alice, 397 fixtures.Service, 398 ucan.NewCapability( 399 "store/write", 400 fixtures.Alice.DID().String(), 401 ucan.NoCaveats{}, 402 ), 403 ) 404 require.NoError(t, err) 405 406 vctx := NewValidationContext( 407 fixtures.Service.Verifier(), 408 storeAdd, 409 IsSelfIssued, 410 validateAuthOk, 411 ProofUnavailable, 412 parseEdPrincipal, 413 FailDIDKeyResolution, 414 NotExpiredNotTooEarly, 415 ) 416 417 a, x := Access(t.Context(), inv, vctx) 418 require.Nil(t, a) 419 require.Error(t, x) 420 require.Equal(t, x.Name(), "Unauthorized") 421 msg := strings.Join([]string{ 422 fmt.Sprintf("Claim %s is not authorized", storeAdd), 423 " - No matching delegated capability found", 424 " - Encountered unknown capabilities", 425 fmt.Sprintf(" - {\"can\":\"store/write\",\"with\":\"%s\",\"nb\":{}}", fixtures.Alice.DID()), 426 }, "\n") 427 require.Equal(t, msg, x.Error()) 428 }) 429 }) 430 431 t.Run("invalid claim", func(t *testing.T) { 432 t.Run("no proofs", func(t *testing.T) { 433 inv, err := storeAdd.Invoke( 434 fixtures.Alice, 435 fixtures.Bob, 436 fixtures.Bob.DID().String(), 437 storeAddCaveats{Link: testLink}, 438 ) 439 require.NoError(t, err) 440 441 vctx := NewValidationContext( 442 fixtures.Service.Verifier(), 443 storeAdd, 444 IsSelfIssued, 445 validateAuthOk, 446 ProofUnavailable, 447 parseEdPrincipal, 448 FailDIDKeyResolution, 449 NotExpiredNotTooEarly, 450 ) 451 452 a, x := Access(t.Context(), inv, vctx) 453 require.Nil(t, a) 454 require.Error(t, x) 455 require.Equal(t, x.Name(), "Unauthorized") 456 msg := strings.Join([]string{ 457 fmt.Sprintf("Claim %s is not authorized", storeAdd), 458 fmt.Sprintf(` - Capability {"can":"%s","with":"%s","nb":{"Link":{"/":"%s"},"Origin":null}} is not authorized because:`, storeAdd.Can(), fixtures.Bob.DID(), testLink), 459 fmt.Sprintf(" - Capability can not be (self) issued by '%s'", fixtures.Alice.DID()), 460 " - Delegated capability not found", 461 }, "\n") 462 require.Equal(t, msg, x.Error()) 463 }) 464 465 t.Run("expired", func(t *testing.T) { 466 exp := ucan.Now() - 5 467 dlg, err := storeAdd.Delegate( 468 fixtures.Alice, 469 fixtures.Bob, 470 fixtures.Alice.DID().String(), 471 storeAddCaveats{}, 472 delegation.WithExpiration(exp), 473 ) 474 require.NoError(t, err) 475 476 inv, err := storeAdd.Invoke( 477 fixtures.Bob, 478 fixtures.Service, 479 fixtures.Alice.DID().String(), 480 storeAddCaveats{Link: testLink}, 481 delegation.WithProof(delegation.FromDelegation(dlg)), 482 ) 483 require.NoError(t, err) 484 485 vctx := NewValidationContext( 486 fixtures.Service.Verifier(), 487 storeAdd, 488 IsSelfIssued, 489 validateAuthOk, 490 ProofUnavailable, 491 parseEdPrincipal, 492 FailDIDKeyResolution, 493 NotExpiredNotTooEarly, 494 ) 495 496 a, x := Access(t.Context(), inv, vctx) 497 require.Nil(t, a) 498 require.Error(t, x) 499 require.Equal(t, x.Name(), "Unauthorized") 500 msg := strings.Join([]string{ 501 fmt.Sprintf("Claim %s is not authorized", storeAdd), 502 fmt.Sprintf(` - Capability {"can":"%s","with":"%s","nb":{"Link":{"/":"%s"},"Origin":null}} is not authorized because:`, storeAdd.Can(), fixtures.Alice.DID(), testLink), 503 fmt.Sprintf(" - Capability can not be (self) issued by '%s'", fixtures.Bob.DID()), 504 fmt.Sprintf(" - Capability can not be derived from prf: %s because:", dlg.Link()), 505 fmt.Sprintf(" - Proof %s has expired on %s", dlg.Link(), time.Unix(int64(exp), 0).Format(time.RFC3339)), 506 }, "\n") 507 require.Equal(t, msg, x.Error()) 508 }) 509 510 t.Run("not valid before", func(t *testing.T) { 511 nbf := ucan.Now() + 60*60 512 dlg, err := storeAdd.Delegate( 513 fixtures.Alice, 514 fixtures.Bob, 515 fixtures.Alice.DID().String(), 516 storeAddCaveats{}, 517 delegation.WithNotBefore(nbf), 518 ) 519 require.NoError(t, err) 520 521 inv, err := storeAdd.Invoke( 522 fixtures.Bob, 523 fixtures.Service, 524 fixtures.Alice.DID().String(), 525 storeAddCaveats{Link: testLink}, 526 delegation.WithProof(delegation.FromDelegation(dlg)), 527 ) 528 require.NoError(t, err) 529 530 vctx := NewValidationContext( 531 fixtures.Service.Verifier(), 532 storeAdd, 533 IsSelfIssued, 534 validateAuthOk, 535 ProofUnavailable, 536 parseEdPrincipal, 537 FailDIDKeyResolution, 538 NotExpiredNotTooEarly, 539 ) 540 541 a, x := Access(t.Context(), inv, vctx) 542 require.Nil(t, a) 543 require.Error(t, x) 544 require.Equal(t, x.Name(), "Unauthorized") 545 msg := strings.Join([]string{ 546 fmt.Sprintf("Claim %s is not authorized", storeAdd), 547 fmt.Sprintf(` - Capability {"can":"%s","with":"%s","nb":{"Link":{"/":"%s"},"Origin":null}} is not authorized because:`, storeAdd.Can(), fixtures.Alice.DID(), testLink), 548 fmt.Sprintf(" - Capability can not be (self) issued by '%s'", fixtures.Bob.DID()), 549 fmt.Sprintf(" - Capability can not be derived from prf: %s because:", dlg.Link()), 550 fmt.Sprintf(" - Proof %s is not valid before %s", dlg.Link(), time.Unix(int64(nbf), 0).Format(time.RFC3339)), 551 }, "\n") 552 require.Equal(t, msg, x.Error()) 553 }) 554 555 t.Run("invalid signature", func(t *testing.T) { 556 // In order to mess up the signature we need to reach deep in UCAN library 557 // to create a UCAN model, manually setting the signature to something bad 558 // and then encode it as the root block of the delegation. 559 nb, _ := storeAddCaveats{Link: testLink}.ToIPLD() 560 exp := ucan.Now() + 30 561 model := udm.UCANModel{ 562 V: "0.9.1", 563 S: fixtures.Alice.Sign([]byte{}).Bytes(), 564 Iss: fixtures.Alice.DID().Bytes(), 565 Aud: fixtures.Bob.DID().Bytes(), 566 Att: []udm.CapabilityModel{ 567 { 568 Can: storeAdd.Can(), 569 With: fixtures.Alice.DID().String(), 570 Nb: nb, 571 }, 572 }, 573 Exp: &exp, 574 } 575 576 rt, err := block.Encode(&model, udm.Type(), cbor.Codec, sha256.Hasher) 577 require.NoError(t, err) 578 579 bs, err := blockstore.NewBlockStore(blockstore.WithBlocks([]block.Block{rt})) 580 require.NoError(t, err) 581 582 dlg, err := delegation.NewDelegation(rt, bs) 583 require.NoError(t, err) 584 585 inv, err := storeAdd.Invoke( 586 fixtures.Bob, 587 fixtures.Service, 588 fixtures.Alice.DID().String(), 589 storeAddCaveats{Link: testLink}, 590 delegation.WithProof(delegation.FromDelegation(dlg)), 591 ) 592 require.NoError(t, err) 593 594 vctx := NewValidationContext( 595 fixtures.Service.Verifier(), 596 storeAdd, 597 IsSelfIssued, 598 validateAuthOk, 599 ProofUnavailable, 600 parseEdPrincipal, 601 FailDIDKeyResolution, 602 NotExpiredNotTooEarly, 603 ) 604 605 a, x := Access(t.Context(), inv, vctx) 606 require.Nil(t, a) 607 require.Error(t, x) 608 require.Equal(t, x.Name(), "Unauthorized") 609 msg := strings.Join([]string{ 610 fmt.Sprintf("Claim %s is not authorized", storeAdd), 611 fmt.Sprintf(` - Capability {"can":"%s","with":"%s","nb":{"Link":{"/":"%s"},"Origin":null}} is not authorized because:`, storeAdd.Can(), fixtures.Alice.DID(), testLink), 612 fmt.Sprintf(" - Capability can not be (self) issued by '%s'", fixtures.Bob.DID()), 613 fmt.Sprintf(" - Capability can not be derived from prf: %s because:", dlg.Link()), 614 fmt.Sprintf(" - Proof %s does not have a valid signature from %s", dlg.Link(), fixtures.Alice.DID()), 615 }, "\n") 616 require.Equal(t, msg, x.Error()) 617 }) 618 619 t.Run("unknown capability", func(t *testing.T) { 620 dlg, err := delegation.Delegate( 621 fixtures.Alice, 622 fixtures.Bob, 623 []ucan.Capability[ucan.NoCaveats]{ 624 ucan.NewCapability("store/pin", fixtures.Alice.DID().String(), ucan.NoCaveats{}), 625 }, 626 ) 627 require.NoError(t, err) 628 629 inv, err := storeAdd.Invoke( 630 fixtures.Bob, 631 fixtures.Service, 632 fixtures.Alice.DID().String(), 633 storeAddCaveats{Link: testLink}, 634 delegation.WithProof(delegation.FromDelegation(dlg)), 635 ) 636 require.NoError(t, err) 637 638 vctx := NewValidationContext( 639 fixtures.Service.Verifier(), 640 storeAdd, 641 IsSelfIssued, 642 validateAuthOk, 643 ProofUnavailable, 644 parseEdPrincipal, 645 FailDIDKeyResolution, 646 NotExpiredNotTooEarly, 647 ) 648 649 a, x := Access(t.Context(), inv, vctx) 650 require.Nil(t, a) 651 require.Error(t, x) 652 require.Equal(t, x.Name(), "Unauthorized") 653 msg := strings.Join([]string{ 654 fmt.Sprintf("Claim %s is not authorized", storeAdd), 655 fmt.Sprintf(` - Capability {"can":"%s","with":"%s","nb":{"Link":{"/":"%s"},"Origin":null}} is not authorized because:`, storeAdd.Can(), fixtures.Alice.DID(), testLink), 656 fmt.Sprintf(" - Capability can not be (self) issued by '%s'", fixtures.Bob.DID()), 657 " - Delegated capability not found", 658 " - Encountered unknown capabilities", 659 fmt.Sprintf(` - {"can":"store/pin","with":"%s","nb":{}}`, fixtures.Alice.DID()), 660 }, "\n") 661 require.Equal(t, msg, x.Error()) 662 }) 663 664 t.Run("malformed capability", func(t *testing.T) { 665 badDID := fmt.Sprintf("bib:%s", fixtures.Alice.DID().String()[4:]) 666 dlg, err := storeAdd.Delegate( 667 fixtures.Alice, 668 fixtures.Bob, 669 badDID, 670 storeAddCaveats{}, 671 ) 672 require.NoError(t, err) 673 674 inv, err := storeAdd.Invoke( 675 fixtures.Bob, 676 fixtures.Service, 677 fixtures.Alice.DID().String(), 678 storeAddCaveats{Link: testLink}, 679 delegation.WithProof(delegation.FromDelegation(dlg)), 680 ) 681 require.NoError(t, err) 682 683 vctx := NewValidationContext( 684 fixtures.Service.Verifier(), 685 storeAdd, 686 IsSelfIssued, 687 validateAuthOk, 688 ProofUnavailable, 689 parseEdPrincipal, 690 FailDIDKeyResolution, 691 NotExpiredNotTooEarly, 692 ) 693 694 a, x := Access(t.Context(), inv, vctx) 695 require.Nil(t, a) 696 require.Error(t, x) 697 require.Equal(t, x.Name(), "Unauthorized") 698 msg := strings.Join([]string{ 699 fmt.Sprintf("Claim %s is not authorized", storeAdd), 700 fmt.Sprintf(` - Capability {"can":"%s","with":"%s","nb":{"Link":{"/":"%s"},"Origin":null}} is not authorized because:`, storeAdd.Can(), fixtures.Alice.DID(), testLink), 701 fmt.Sprintf(" - Capability can not be (self) issued by '%s'", fixtures.Bob.DID()), 702 fmt.Sprintf(` - Cannot derive {"can":"%s","with":"%s","nb":{"Link":{"/":"%s"},"Origin":null}} from delegated capabilities:`, storeAdd.Can(), fixtures.Alice.DID(), testLink), 703 fmt.Sprintf(` - Encountered malformed '%s' capability: {"can":"%s","with":"%s","nb":{}}`, storeAdd.Can(), storeAdd.Can(), badDID), 704 fmt.Sprintf(` - Expected a "did:" but got "%s" instead`, badDID), 705 }, "\n") 706 require.Equal(t, msg, x.Error()) 707 }) 708 709 t.Run("unavailable proof", func(t *testing.T) { 710 dlg, err := storeAdd.Delegate( 711 fixtures.Alice, 712 fixtures.Bob, 713 fixtures.Alice.DID().String(), 714 storeAddCaveats{}, 715 ) 716 require.NoError(t, err) 717 718 inv, err := storeAdd.Invoke( 719 fixtures.Bob, 720 fixtures.Service, 721 fixtures.Alice.DID().String(), 722 storeAddCaveats{Link: testLink}, 723 delegation.WithProof(delegation.FromLink(dlg.Link())), 724 ) 725 require.NoError(t, err) 726 727 vctx := NewValidationContext( 728 fixtures.Service.Verifier(), 729 storeAdd, 730 IsSelfIssued, 731 validateAuthOk, 732 ProofUnavailable, 733 parseEdPrincipal, 734 FailDIDKeyResolution, 735 NotExpiredNotTooEarly, 736 ) 737 738 a, x := Access(t.Context(), inv, vctx) 739 require.Nil(t, a) 740 require.Error(t, x) 741 require.Equal(t, x.Name(), "Unauthorized") 742 msg := strings.Join([]string{ 743 fmt.Sprintf("Claim %s is not authorized", storeAdd), 744 fmt.Sprintf(` - Capability {"can":"%s","with":"%s","nb":{"Link":{"/":"%s"},"Origin":null}} is not authorized because:`, storeAdd.Can(), fixtures.Alice.DID(), testLink), 745 fmt.Sprintf(" - Capability can not be (self) issued by '%s'", fixtures.Bob.DID()), 746 fmt.Sprintf(` - Capability can not be derived from prf: %s because:`, dlg.Link()), 747 fmt.Sprintf(` - Linked proof "%s" is not included and could not be resolved`, dlg.Link()), 748 ` - Proof resolution failed with: no proof resolver configured`, 749 }, "\n") 750 require.Equal(t, msg, x.Error()) 751 }) 752 753 t.Run("invalid audience", func(t *testing.T) { 754 dlg, err := storeAdd.Delegate( 755 fixtures.Alice, 756 fixtures.Bob, 757 fixtures.Alice.DID().String(), 758 storeAddCaveats{}, 759 ) 760 require.NoError(t, err) 761 762 inv, err := storeAdd.Invoke( 763 fixtures.Mallory, 764 fixtures.Service, 765 fixtures.Alice.DID().String(), 766 storeAddCaveats{Link: testLink}, 767 delegation.WithProof(delegation.FromDelegation(dlg)), 768 ) 769 require.NoError(t, err) 770 771 vctx := NewValidationContext( 772 fixtures.Service.Verifier(), 773 storeAdd, 774 IsSelfIssued, 775 validateAuthOk, 776 ProofUnavailable, 777 parseEdPrincipal, 778 FailDIDKeyResolution, 779 NotExpiredNotTooEarly, 780 ) 781 782 a, x := Access(t.Context(), inv, vctx) 783 require.Nil(t, a) 784 require.Error(t, x) 785 require.Equal(t, x.Name(), "Unauthorized") 786 msg := strings.Join([]string{ 787 fmt.Sprintf("Claim %s is not authorized", storeAdd), 788 fmt.Sprintf(` - Capability {"can":"%s","with":"%s","nb":{"Link":{"/":"%s"},"Origin":null}} is not authorized because:`, storeAdd.Can(), fixtures.Alice.DID(), testLink), 789 fmt.Sprintf(" - Capability can not be (self) issued by '%s'", fixtures.Mallory.DID()), 790 fmt.Sprintf(` - Capability can not be derived from prf: %s because:`, dlg.Link()), 791 fmt.Sprintf(` - Delegation audience is '%s' instead of '%s'`, fixtures.Bob.DID(), fixtures.Mallory.DID()), 792 }, "\n") 793 require.Equal(t, msg, x.Error()) 794 }) 795 796 t.Run("invalid claim", func(t *testing.T) { 797 dlg, err := storeAdd.Delegate( 798 fixtures.Alice, 799 fixtures.Bob, 800 fixtures.Mallory.DID().String(), 801 storeAddCaveats{}, 802 ) 803 require.NoError(t, err) 804 805 nb := storeAddCaveats{Link: testLink} 806 inv, err := storeAdd.Invoke( 807 fixtures.Bob, 808 fixtures.Service, 809 fixtures.Alice.DID().String(), 810 nb, 811 delegation.WithProof(delegation.FromDelegation(dlg)), 812 ) 813 require.NoError(t, err) 814 815 vctx := NewValidationContext( 816 fixtures.Service.Verifier(), 817 storeAdd, 818 IsSelfIssued, 819 validateAuthOk, 820 ProofUnavailable, 821 parseEdPrincipal, 822 FailDIDKeyResolution, 823 NotExpiredNotTooEarly, 824 ) 825 826 a, x := Access(t.Context(), inv, vctx) 827 require.Nil(t, a) 828 require.Error(t, x) 829 require.Equal(t, x.Name(), "Unauthorized") 830 msg := strings.Join([]string{ 831 fmt.Sprintf("Claim %s is not authorized", storeAdd), 832 fmt.Sprintf(` - Capability {"can":"%s","with":"%s","nb":{"Link":{"/":"%s"},"Origin":null}} is not authorized because:`, storeAdd.Can(), fixtures.Alice.DID(), testLink), 833 fmt.Sprintf(" - Capability can not be (self) issued by '%s'", fixtures.Bob.DID()), 834 fmt.Sprintf(` - Cannot derive {"can":"%s","with":"%s","nb":{"Link":{"/":"%s"},"Origin":null}} from delegated capabilities:`, storeAdd.Can(), fixtures.Alice.DID(), testLink), 835 fmt.Sprintf(` - Constraint violation: Expected 'with: "%s"' instead got '%s'`, fixtures.Mallory.DID(), fixtures.Alice.DID()), 836 }, "\n") 837 require.Equal(t, msg, x.Error()) 838 }) 839 840 t.Run("invalid sub delegation", func(t *testing.T) { 841 prf, err := storeAdd.Delegate( 842 fixtures.Alice, 843 fixtures.Bob, 844 fixtures.Service.DID().String(), 845 storeAddCaveats{}, 846 ) 847 require.NoError(t, err) 848 849 dlg, err := storeAdd.Delegate( 850 fixtures.Bob, 851 fixtures.Mallory, 852 fixtures.Service.DID().String(), 853 storeAddCaveats{}, 854 delegation.WithProof(delegation.FromDelegation(prf)), 855 ) 856 require.NoError(t, err) 857 858 nb := storeAddCaveats{Link: testLink} 859 inv, err := storeAdd.Invoke( 860 fixtures.Mallory, 861 fixtures.Service, 862 fixtures.Service.DID().String(), 863 nb, 864 delegation.WithProof(delegation.FromDelegation(dlg)), 865 ) 866 require.NoError(t, err) 867 868 vctx := NewValidationContext( 869 fixtures.Service.Verifier(), 870 storeAdd, 871 IsSelfIssued, 872 validateAuthOk, 873 ProofUnavailable, 874 parseEdPrincipal, 875 FailDIDKeyResolution, 876 NotExpiredNotTooEarly, 877 ) 878 879 cstr := fmt.Sprintf(`{"can":"%s","with":"%s","nb":{"Link":{"/":"%s"},"Origin":null}}`, storeAdd.Can(), fixtures.Service.DID(), testLink) 880 a, x := Access(t.Context(), inv, vctx) 881 require.Nil(t, a) 882 require.Error(t, x) 883 require.Equal(t, x.Name(), "Unauthorized") 884 msg := strings.Join([]string{ 885 fmt.Sprintf("Claim %s is not authorized", storeAdd), 886 fmt.Sprintf(` - Capability %s is not authorized because:`, cstr), 887 fmt.Sprintf(" - Capability can not be (self) issued by '%s'", fixtures.Mallory.DID()), 888 fmt.Sprintf(` - Capability %s is not authorized because:`, cstr), 889 fmt.Sprintf(` - Capability can not be (self) issued by '%s'`, fixtures.Bob.DID()), 890 fmt.Sprintf(` - Capability %s is not authorized because:`, cstr), 891 fmt.Sprintf(` - Capability can not be (self) issued by '%s'`, fixtures.Alice.DID()), 892 " - Delegated capability not found", 893 }, "\n") 894 require.Equal(t, msg, x.Error()) 895 }) 896 897 t.Run("principal alignment", func(t *testing.T) { 898 prf, err := storeAdd.Delegate( 899 fixtures.Alice, 900 fixtures.Bob, 901 fixtures.Alice.DID().String(), 902 storeAddCaveats{}, 903 ) 904 require.NoError(t, err) 905 906 nb := storeAddCaveats{Link: testLink} 907 inv, err := storeAdd.Invoke( 908 fixtures.Mallory, 909 fixtures.Service, 910 fixtures.Alice.DID().String(), 911 nb, 912 delegation.WithProof(delegation.FromDelegation(prf)), 913 ) 914 require.NoError(t, err) 915 916 vctx := NewValidationContext( 917 fixtures.Service.Verifier(), 918 storeAdd, 919 IsSelfIssued, 920 validateAuthOk, 921 ProofUnavailable, 922 parseEdPrincipal, 923 FailDIDKeyResolution, 924 NotExpiredNotTooEarly, 925 ) 926 927 cstr := fmt.Sprintf(`{"can":"%s","with":"%s","nb":{"Link":{"/":"%s"},"Origin":null}}`, storeAdd.Can(), fixtures.Alice.DID(), testLink) 928 a, x := Access(t.Context(), inv, vctx) 929 require.Nil(t, a) 930 require.Error(t, x) 931 require.Equal(t, x.Name(), "Unauthorized") 932 msg := strings.Join([]string{ 933 fmt.Sprintf("Claim %s is not authorized", storeAdd), 934 fmt.Sprintf(` - Capability %s is not authorized because:`, cstr), 935 fmt.Sprintf(" - Capability can not be (self) issued by '%s'", fixtures.Mallory.DID()), 936 fmt.Sprintf(` - Capability can not be derived from prf: %s because:`, prf.Link()), 937 fmt.Sprintf(` - Delegation audience is '%s' instead of '%s'`, fixtures.Bob.DID(), fixtures.Mallory.DID()), 938 }, "\n") 939 require.Equal(t, msg, x.Error()) 940 }) 941 942 t.Run("invalid delegation chain", func(t *testing.T) { 943 space := fixtures.Alice 944 945 prf, err := storeAdd.Delegate( 946 space, 947 fixtures.Service, 948 space.DID().String(), 949 storeAddCaveats{}, 950 ) 951 require.NoError(t, err) 952 953 nb := storeAddCaveats{Link: testLink} 954 inv, err := storeAdd.Invoke( 955 fixtures.Bob, 956 fixtures.Service, 957 space.DID().String(), 958 nb, 959 delegation.WithProof(delegation.FromDelegation(prf)), 960 ) 961 require.NoError(t, err) 962 963 vctx := NewValidationContext( 964 fixtures.Service.Verifier(), 965 storeAdd, 966 IsSelfIssued, 967 validateAuthOk, 968 ProofUnavailable, 969 parseEdPrincipal, 970 FailDIDKeyResolution, 971 NotExpiredNotTooEarly, 972 ) 973 974 cstr := fmt.Sprintf(`{"can":"%s","with":"%s","nb":{"Link":{"/":"%s"},"Origin":null}}`, storeAdd.Can(), space.DID(), testLink) 975 a, x := Access(t.Context(), inv, vctx) 976 require.Nil(t, a) 977 require.Error(t, x) 978 require.Equal(t, x.Name(), "Unauthorized") 979 msg := strings.Join([]string{ 980 fmt.Sprintf("Claim %s is not authorized", storeAdd), 981 fmt.Sprintf(` - Capability %s is not authorized because:`, cstr), 982 fmt.Sprintf(" - Capability can not be (self) issued by '%s'", fixtures.Bob.DID()), 983 fmt.Sprintf(` - Capability can not be derived from prf: %s because:`, prf.Link()), 984 fmt.Sprintf(` - Delegation audience is '%s' instead of '%s'`, fixtures.Service.DID(), fixtures.Bob.DID()), 985 }, "\n") 986 require.Equal(t, msg, x.Error()) 987 }) 988 }) 989 } 990 991 func TestClaim(t *testing.T) { 992 t.Run("without a proof", func(t *testing.T) { 993 dlg, err := storeAdd.Delegate( 994 fixtures.Alice, 995 fixtures.Service, 996 fixtures.Alice.DID().String(), 997 storeAddCaveats{}, 998 ) 999 require.NoError(t, err) 1000 1001 vctx := NewValidationContext( 1002 fixtures.Service.Verifier(), 1003 storeAdd, 1004 IsSelfIssued, 1005 validateAuthOk, 1006 ProofUnavailable, 1007 parseEdPrincipal, 1008 FailDIDKeyResolution, 1009 NotExpiredNotTooEarly, 1010 ) 1011 1012 a, x := Claim(t.Context(), storeAdd, []delegation.Proof{delegation.FromLink(dlg.Link())}, vctx) 1013 require.Nil(t, a) 1014 require.Error(t, x) 1015 require.Equal(t, x.Name(), "Unauthorized") 1016 msg := strings.Join([]string{ 1017 fmt.Sprintf("Claim %s is not authorized", storeAdd), 1018 fmt.Sprintf(` - Linked proof "%s" is not included and could not be resolved`, dlg.Link()), 1019 ` - Proof resolution failed with: no proof resolver configured`, 1020 }, "\n") 1021 require.Equal(t, msg, x.Error()) 1022 }) 1023 1024 t.Run("mismatched signature", func(t *testing.T) { 1025 svcdid, err := did.Parse("did:web:w3.storage") 1026 require.NoError(t, err) 1027 1028 old, err := signer.Wrap(fixtures.Alice, svcdid) 1029 require.NoError(t, err) 1030 1031 new, err := signer.Wrap(fixtures.Bob, svcdid) 1032 require.NoError(t, err) 1033 1034 dlg, err := storeAdd.Delegate( 1035 old, 1036 old, 1037 old.DID().String(), 1038 storeAddCaveats{Link: testLink}, 1039 ) 1040 require.NoError(t, err) 1041 1042 vctx := NewValidationContext( 1043 new.Verifier(), 1044 storeAdd, 1045 IsSelfIssued, 1046 validateAuthOk, 1047 ProofUnavailable, 1048 parseEdPrincipal, 1049 FailDIDKeyResolution, 1050 NotExpiredNotTooEarly, 1051 ) 1052 1053 a, x := Claim(t.Context(), storeAdd, []delegation.Proof{delegation.FromDelegation(dlg)}, vctx) 1054 require.Nil(t, a) 1055 require.Error(t, x) 1056 require.Equal(t, x.Name(), "Unauthorized") 1057 msg := strings.Join([]string{ 1058 fmt.Sprintf("Claim %s is not authorized", storeAdd), 1059 fmt.Sprintf(` - Proof %s issued by %s does not have a valid signature from %s`, dlg.Link(), new.DID(), new.DID()), 1060 ` ℹ️ Issuer probably signed with a different key, which got rotated, invalidating delegations that were issued with prior keys`, 1061 }, "\n") 1062 require.Equal(t, msg, x.Error()) 1063 }) 1064 } 1065 1066 func TestIsSelfIssued(t *testing.T) { 1067 cap := ucan.NewCapability("upload/add", fixtures.Alice.DID().String(), struct{}{}) 1068 1069 canIssue := IsSelfIssued(cap, fixtures.Alice.DID()) 1070 if canIssue == false { 1071 t.Fatal("capability self issued by alice") 1072 } 1073 1074 canIssue = IsSelfIssued(cap, fixtures.Bob.DID()) 1075 if canIssue == true { 1076 t.Fatal("capability not self issued by bob") 1077 } 1078 }