github.com/hyperledger/aries-framework-go@v0.3.2/pkg/wallet/query_test.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package wallet 8 9 import ( 10 "encoding/json" 11 "fmt" 12 "strconv" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/google/uuid" 18 "github.com/stretchr/testify/require" 19 20 "github.com/hyperledger/aries-framework-go/internal/testdata" 21 "github.com/hyperledger/aries-framework-go/pkg/doc/did" 22 "github.com/hyperledger/aries-framework-go/pkg/doc/presexch" 23 "github.com/hyperledger/aries-framework-go/pkg/doc/util" 24 "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" 25 vdrapi "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr" 26 "github.com/hyperledger/aries-framework-go/pkg/internal/ldtestutil" 27 mockvdr "github.com/hyperledger/aries-framework-go/pkg/mock/vdr" 28 "github.com/hyperledger/aries-framework-go/pkg/vdr/key" 29 ) 30 31 // nolint: lll 32 const ( 33 sampleVCFmt = `{ 34 "@context": [ 35 "https://www.w3.org/2018/credentials/v1", 36 "https://www.w3.org/2018/credentials/examples/v1", 37 "https://w3id.org/security/bbs/v1" 38 ], 39 "credentialSchema": [{"id": "%s", "type": "JsonSchemaValidator2018"}], 40 "credentialSubject": { 41 "degree": { 42 "type": "BachelorDegree", 43 "university": "MIT" 44 }, 45 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 46 "name": "Jayden Doe", 47 "spouse": "did:example:c276e12ec21ebfeb1f712ebc6f1" 48 }, 49 "expirationDate": "2020-01-01T19:23:24Z", 50 "id": "http://example.edu/credentials/1872", 51 "issuanceDate": "2010-01-01T19:23:24Z", 52 "issuer": { 53 "id": "did:example:76e12ec712ebc6f1c221ebfeb1f", 54 "name": "Example University" 55 }, 56 "referenceNumber": 83294847, 57 "type": [ 58 "VerifiableCredential", 59 "UniversityDegreeCredential" 60 ] 61 }` 62 samplePRCVC = `{ 63 "@context": [ 64 "https://www.w3.org/2018/credentials/v1", 65 "https://w3id.org/citizenship/v1", 66 "https://w3id.org/security/bbs/v1" 67 ], 68 "id": "https://issuer.oidp.uscis.gov/credentials/83627465", 69 "type": [ 70 "VerifiableCredential", 71 "PermanentResidentCard" 72 ], 73 "issuer": "did:example:489398593", 74 "identifier": "83627465", 75 "name": "Permanent Resident Card", 76 "description": "Government of Example Permanent Resident Card.", 77 "issuanceDate": "2019-12-03T12:19:52Z", 78 "expirationDate": "2029-12-03T12:19:52Z", 79 "credentialSchema": [], 80 "credentialSubject": { 81 "id": "did:example:b34ca6cd37bbf23", 82 "type": [ 83 "PermanentResident", 84 "Person" 85 ], 86 "givenName": "JOHN", 87 "familyName": "SMITH", 88 "gender": "Male", 89 "image": "data:image/png;base64,iVBORw0KGgokJggg==", 90 "residentSince": "2015-01-01", 91 "lprCategory": "C09", 92 "lprNumber": "999-999-999", 93 "commuterClassification": "C1", 94 "birthCountry": "Bahamas", 95 "birthDate": "1958-07-17" 96 } 97 }` 98 sampleBBSVC = `{ 99 "@context": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1", "https://w3id.org/security/bbs/v1"], 100 "credentialSubject": { 101 "degree": {"type": "BachelorDegree", "university": "MIT"}, 102 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 103 "name": "Jayden Doe", 104 "spouse": "did:example:c276e12ec21ebfeb1f712ebc6f1" 105 }, 106 "expirationDate": "2020-01-01T19:23:24Z", 107 "id": "http://example.edu/credentials/1872", 108 "issuanceDate": "2010-01-01T19:23:24Z", 109 "issuer": {"id": "did:example:76e12ec712ebc6f1c221ebfeb1f", "name": "Example University"}, 110 "proof": { 111 "created": "2021-03-29T13:27:36.483097-04:00", 112 "proofPurpose": "assertionMethod", 113 "proofValue": "rw7FeV6K1wimnYogF9qd-N0zmq5QlaIoszg64HciTca-mK_WU4E1jIusKTT6EnN2GZz04NVPBIw4yhc0kTwIZ07etMvfWUlHt_KMoy2CfTw8FBhrf66q4h7Qcqxh_Kxp6yCHyB4A-MmURlKKb8o-4w", 114 "type": "BbsBlsSignature2020", 115 "verificationMethod": "did:key:zUC72c7u4BYVmfYinDceXkNAwzPEyuEE23kUmJDjLy8495KH3pjLwFhae1Fww9qxxRdLnS2VNNwni6W3KbYZKsicDtiNNEp76fYWR6HCD8jAz6ihwmLRjcHH6kB294Xfg1SL1qQ#zUC72c7u4BYVmfYinDceXkNAwzPEyuEE23kUmJDjLy8495KH3pjLwFhae1Fww9qxxRdLnS2VNNwni6W3KbYZKsicDtiNNEp76fYWR6HCD8jAz6ihwmLRjcHH6kB294Xfg1SL1qQ" 116 }, 117 "referenceNumber": 83294847, 118 "type": ["VerifiableCredential", "UniversityDegreeCredential"] 119 }` 120 sampleQueryByExFmt = `{ 121 "reason": "Please present your identity document.", 122 "example": { 123 "@context": [ 124 "https://www.w3.org/2018/credentials/v1", 125 "https://www.w3.org/2018/credentials/examples/v1", 126 "https://w3id.org/security/bbs/v1" 127 ], 128 "type": ["UniversityDegreeCredential"], 129 "trustedIssuer": [ 130 { 131 "issuer": "urn:some:required:issuer" 132 }, 133 { 134 "required": true, 135 "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f" 136 } 137 ], 138 "credentialSubject": { 139 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21" 140 }, 141 "credentialSchema": { 142 "id": "%s", 143 "type": "JsonSchemaValidator2018" 144 } 145 } 146 }` 147 sampleQueryByFrame = `{ 148 "reason": "Please provide your Passport details.", 149 "frame": { 150 "@context": [ 151 "https://www.w3.org/2018/credentials/v1", 152 "https://w3id.org/citizenship/v1", 153 "https://w3id.org/security/bbs/v1" 154 ], 155 "type": ["VerifiableCredential", "PermanentResidentCard"], 156 "@explicit": true, 157 "identifier": {}, 158 "issuer": {}, 159 "issuanceDate": {}, 160 "credentialSubject": { 161 "@explicit": true, 162 "name": {}, 163 "spouse": {} 164 } 165 }, 166 "trustedIssuer": [ 167 { 168 "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f", 169 "required": true 170 } 171 ], 172 "required": true 173 }` 174 ) 175 176 func TestGetQueryType(t *testing.T) { 177 t.Run("test get query type by string", func(t *testing.T) { 178 tests := []struct { 179 name string 180 typeStr []string 181 expected QueryType 182 expectedName string 183 error string 184 }{ 185 { 186 name: "test for QueryByExample", 187 typeStr: []string{"QueryByExample", "QuerybyExample", "querybyexample"}, 188 expected: QueryByExample, 189 expectedName: "QueryByExample", 190 }, 191 { 192 name: "test for QueryByFrame", 193 typeStr: []string{"QueryByFrame", "Querybyframe", "querybyframe"}, 194 expected: QueryByFrame, 195 expectedName: "QueryByFrame", 196 }, 197 { 198 name: "test for PresentationExchange", 199 typeStr: []string{"PresentationExchange", "Presentationexchange", "presentationExchange"}, 200 expected: PresentationExchange, 201 expectedName: "PresentationExchange", 202 }, 203 { 204 name: "test for DIDAuth", 205 typeStr: []string{"didAuth", "didauth", "DIDAuth", "DIDauth"}, 206 expected: DIDAuth, 207 expectedName: "DIDAuth", 208 }, 209 { 210 name: "test for invalid types", 211 typeStr: []string{"", "QueryByFram", "QueryByExamples", "DIDAuthorization", "invalid"}, 212 error: "unsupported query type", 213 expectedName: "", 214 }, 215 } 216 217 t.Parallel() 218 219 for _, test := range tests { 220 tc := test 221 t.Run(tc.name, func(t *testing.T) { 222 for _, str := range tc.typeStr { 223 qType, err := GetQueryType(str) 224 require.Equal(t, qType, tc.expected) 225 if tc.error != "" { 226 require.Error(t, err) 227 require.Contains(t, err.Error(), tc.error) 228 } else { 229 require.NoError(t, err) 230 } 231 } 232 }) 233 } 234 }) 235 } 236 237 func TestQuery_PerformQuery(t *testing.T) { 238 vc1, err := (&verifiable.Credential{ 239 Context: []string{verifiable.ContextURI}, 240 Types: []string{verifiable.VCType}, 241 ID: "http://example.edu/credentials/9999", 242 CustomFields: map[string]interface{}{ 243 "first_name": "Jesse", 244 }, 245 Issued: &util.TimeWrapper{ 246 Time: time.Now(), 247 }, 248 Issuer: verifiable.Issuer{ 249 ID: "did:example:76e12ec712ebc6f1c221ebfeb1f", 250 }, 251 Subject: uuid.New().String(), 252 }).MarshalJSON() 253 require.NoError(t, err) 254 255 // presentation exchange query 256 pd := &presexch.PresentationDefinition{ 257 ID: uuid.New().String(), 258 InputDescriptors: []*presexch.InputDescriptor{{ 259 ID: uuid.New().String(), 260 Schema: []*presexch.Schema{{ 261 URI: fmt.Sprintf("%s#%s", verifiable.ContextID, verifiable.VCType), 262 }}, 263 Constraints: &presexch.Constraints{ 264 Fields: []*presexch.Field{{ 265 Path: []string{"$.first_name"}, 266 }}, 267 }, 268 }}, 269 } 270 271 // query by example 272 queryByExample := []byte(fmt.Sprintf(sampleQueryByExFmt, verifiable.ContextURI)) 273 // query by frame 274 queryByFrame := []byte(sampleQueryByFrame) 275 276 pdJSON, err := json.Marshal(pd) 277 require.NoError(t, err) 278 require.NotEmpty(t, pdJSON) 279 280 udcVC := testdata.SampleUDCVC 281 vcForQuery := []byte(fmt.Sprintf(sampleVCFmt, verifiable.ContextURI)) 282 vcForDerive := []byte(sampleBBSVC) 283 284 customVDR := &mockvdr.MockVDRegistry{ 285 ResolveFunc: func(didID string, opts ...vdrapi.DIDMethodOption) (*did.DocResolution, error) { 286 if strings.HasPrefix(didID, "did:key:") { 287 k := key.New() 288 289 d, e := k.Read(didID) 290 if e != nil { 291 return nil, e 292 } 293 294 return d, nil 295 } 296 297 return nil, fmt.Errorf("did not found") 298 }, 299 } 300 pubKeyFetcher := verifiable.NewVDRKeyResolver(customVDR).PublicKeyFetcher() 301 302 loader, err := ldtestutil.DocumentLoader() 303 require.NoError(t, err) 304 305 t.Run("test wallet queries", func(t *testing.T) { 306 tests := []struct { 307 name string 308 query []*QueryParams 309 credentials []json.RawMessage 310 resultCount int 311 vcCount map[int]int 312 error string 313 }{ 314 // Presentation Exchange tests 315 { 316 name: "query by presentation exchange - success", 317 query: []*QueryParams{ 318 {Type: "PresentationExchange", Query: []json.RawMessage{pdJSON}}, 319 }, 320 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 321 resultCount: 1, 322 vcCount: map[int]int{0: 1}, 323 }, 324 { 325 name: "query by presentation exchange - multi query frame - success", 326 query: []*QueryParams{ 327 {Type: "PresentationExchange", Query: []json.RawMessage{pdJSON, pdJSON}}, 328 }, 329 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 330 resultCount: 2, 331 vcCount: map[int]int{0: 1, 1: 1}, 332 }, 333 { 334 name: "query by presentation exchange - multiple - success", 335 query: []*QueryParams{ 336 {Type: "PresentationExchange", Query: []json.RawMessage{pdJSON, pdJSON}}, 337 {Type: "PresentationExchange", Query: []json.RawMessage{pdJSON}}, 338 }, 339 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 340 resultCount: 3, 341 vcCount: map[int]int{0: 1, 1: 1, 2: 1}, 342 }, 343 { 344 name: "query by presentation exchange - no results", 345 query: []*QueryParams{ 346 {Type: "PresentationExchange", Query: []json.RawMessage{pdJSON}}, 347 }, 348 credentials: []json.RawMessage{udcVC, vcForQuery, vcForDerive}, 349 resultCount: 0, 350 error: ErrQueryNoResultFound.Error(), 351 }, 352 { 353 name: "query by presentation exchange - invalid definition", 354 query: []*QueryParams{ 355 {Type: "PresentationExchange", Query: []json.RawMessage{[]byte(sampleInvalidDIDContent)}}, 356 }, 357 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 358 resultCount: 0, 359 error: "input_descriptors is required", 360 }, 361 { 362 name: "query by presentation exchange - invalid query frame", 363 query: []*QueryParams{ 364 {Type: "PresentationExchange", Query: []json.RawMessage{[]byte("---")}}, 365 }, 366 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 367 resultCount: 0, 368 error: "invalid character", 369 }, 370 // QueryByExample tests 371 { 372 name: "query by example - success", 373 query: []*QueryParams{ 374 {Type: "QueryByExample", Query: []json.RawMessage{queryByExample}}, 375 }, 376 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 377 resultCount: 1, 378 vcCount: map[int]int{0: 1}, 379 }, 380 { 381 name: "query by example - success & normalize results", 382 query: []*QueryParams{ 383 {Type: "QueryByExample", Query: []json.RawMessage{queryByExample, queryByExample}}, 384 {Type: "QueryByExample", Query: []json.RawMessage{queryByExample}}, 385 }, 386 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 387 resultCount: 1, 388 vcCount: map[int]int{0: 1}, 389 }, 390 { 391 name: "query by example - invalid query", 392 query: []*QueryParams{ 393 {Type: "QueryByExample", Query: []json.RawMessage{[]byte(sampleInvalidDIDContent)}}, 394 }, 395 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 396 resultCount: 0, 397 error: "failed to parse QueryByExample query", 398 }, 399 // QueryByFrame tests 400 { 401 name: "query by frame - success", 402 query: []*QueryParams{ 403 {Type: "QueryByFrame", Query: []json.RawMessage{queryByFrame}}, 404 }, 405 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 406 resultCount: 1, 407 vcCount: map[int]int{0: 1}, 408 }, 409 { 410 name: "query by frame - multiple results", 411 query: []*QueryParams{ 412 {Type: "QueryByFrame", Query: []json.RawMessage{queryByFrame, queryByFrame}}, 413 {Type: "QueryByFrame", Query: []json.RawMessage{queryByFrame}}, 414 }, 415 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 416 resultCount: 1, 417 vcCount: map[int]int{0: 3}, 418 }, 419 { 420 name: "query by frame - invalid query", 421 query: []*QueryParams{ 422 {Type: "QueryByFrame", Query: []json.RawMessage{[]byte(sampleInvalidDIDContent)}}, 423 }, 424 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 425 resultCount: 0, 426 error: "failed to parse QueryByFrame query", 427 }, 428 // DIDAuth tests 429 { 430 name: "didAuth - success", 431 query: []*QueryParams{ 432 {Type: "DIDAuth"}, 433 }, 434 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 435 resultCount: 1, 436 vcCount: map[int]int{0: 0}, 437 }, 438 439 // Mixed Query Types 440 { 441 name: "query by PresentationExchange,QueryByExample,QueryByFrame - success", 442 query: []*QueryParams{ 443 {Type: "PresentationExchange", Query: []json.RawMessage{pdJSON}}, 444 {Type: "QueryByExample", Query: []json.RawMessage{queryByExample}}, 445 {Type: "QueryByFrame", Query: []json.RawMessage{queryByFrame}}, 446 {Type: "DIDAuth"}, 447 }, 448 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 449 resultCount: 3, 450 vcCount: map[int]int{0: 1, 1: 0, 2: 2}, 451 }, 452 { 453 name: "query by PresentationExchange,QueryByExample,QueryByFrame - normalized result - success", 454 query: []*QueryParams{ 455 {Type: "PresentationExchange", Query: []json.RawMessage{pdJSON}}, 456 {Type: "QueryByExample", Query: []json.RawMessage{queryByExample}}, 457 {Type: "QueryByExample", Query: []json.RawMessage{queryByExample}}, 458 {Type: "QueryByExample", Query: []json.RawMessage{queryByExample}}, 459 {Type: "QueryByFrame", Query: []json.RawMessage{queryByFrame}}, 460 }, 461 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 462 resultCount: 2, 463 vcCount: map[int]int{0: 1, 1: 2}, 464 }, 465 466 // Validations 467 { 468 name: "unsupported query type", 469 query: []*QueryParams{ 470 {Type: "QueryByInvalid", Query: []json.RawMessage{queryByExample}}, 471 }, 472 credentials: []json.RawMessage{vc1, udcVC, vcForQuery, vcForDerive}, 473 resultCount: 0, 474 error: "unsupported query type", 475 }, 476 { 477 name: "empty credentials", 478 query: []*QueryParams{ 479 {Type: "QueryByExample", Query: []json.RawMessage{queryByExample}}, 480 }, 481 credentials: []json.RawMessage{}, 482 resultCount: 0, 483 error: ErrQueryNoResultFound.Error(), 484 }, 485 { 486 name: "credential parsing error", 487 query: []*QueryParams{ 488 {Type: "QueryByExample", Query: []json.RawMessage{queryByExample}}, 489 }, 490 credentials: []json.RawMessage{[]byte("----")}, 491 resultCount: 0, 492 error: "unmarshal new credential", 493 }, 494 } 495 496 t.Parallel() 497 498 for _, test := range tests { 499 tc := test 500 t.Run(tc.name, func(t *testing.T) { 501 credentials := make(map[string]json.RawMessage) 502 for i, v := range tc.credentials { 503 credentials[strconv.Itoa(i)] = v 504 } 505 506 results, err := NewQuery(pubKeyFetcher, loader, tc.query...).PerformQuery(credentials) 507 508 if tc.error != "" { 509 require.Empty(t, results) 510 require.Error(t, err) 511 require.Contains(t, err.Error(), tc.error) 512 require.Len(t, results, tc.resultCount) 513 514 return 515 } 516 517 require.NoError(t, err) 518 require.Len(t, results, tc.resultCount) 519 520 for i, result := range results { 521 require.Len(t, result.Credentials(), tc.vcCount[i]) 522 } 523 }) 524 } 525 }) 526 } 527 528 func TestQueryByExample(t *testing.T) { 529 loader, err := ldtestutil.DocumentLoader() 530 require.NoError(t, err) 531 532 vc1, err := verifiable.ParseCredential([]byte(fmt.Sprintf(sampleVCFmt, verifiable.ContextURI)), 533 verifiable.WithDisabledProofCheck(), 534 verifiable.WithJSONLDDocumentLoader(loader)) 535 require.NoError(t, err) 536 537 vc2, err := verifiable.ParseCredential([]byte(samplePRCVC), verifiable.WithDisabledProofCheck(), 538 verifiable.WithJSONLDDocumentLoader(loader)) 539 require.NoError(t, err) 540 541 // sample queries 542 queryByExampleAll := []byte(fmt.Sprintf(sampleQueryByExFmt, verifiable.ContextURI)) 543 544 queryByExampleContext1 := `{ 545 "reason": "Please present your identity document.", 546 "example": { 547 "@context": [ 548 "https://www.w3.org/2018/credentials/v1" 549 ], 550 "type": "VerifiableCredential", 551 "trustedIssuer": [], 552 "credentialSubject": {}, 553 "credentialSchema": {} 554 } 555 }` 556 557 queryByExampleContext2 := `{ 558 "reason": "Please present your identity document.", 559 "example": { 560 "@context": [ 561 "https://www.w3.org/2018/credentials/v1", 562 "https://w3id.org/citizenship/v1" 563 ], 564 "type": "VerifiableCredential" 565 } 566 }` 567 568 queryByExampleContextType1 := `{ 569 "reason": "Please present your identity document.", 570 "example": { 571 "@context": [ 572 "https://www.w3.org/2018/credentials/v1", 573 "https://w3id.org/citizenship/v1" 574 ], 575 "type": "PermanentResidentCard", 576 "trustedIssuer": [], 577 "credentialSubject": {}, 578 "credentialSchema": {} 579 } 580 }` 581 582 queryByExampleContextType2 := `{ 583 "reason": "Please present your identity document.", 584 "example": { 585 "@context": [ 586 "https://www.w3.org/2018/credentials/v1", 587 "https://www.w3.org/2018/credentials/examples/v1" 588 ], 589 "type": ["UniversityDegreeCredential"], 590 "trustedIssuer": [], 591 "credentialSubject": {}, 592 "credentialSchema": {} 593 } 594 }` 595 596 queryByExampleContextTypeIssuer1 := `{ 597 "reason": "Please present your identity document.", 598 "example": { 599 "@context": [ 600 "https://www.w3.org/2018/credentials/v1", 601 "https://www.w3.org/2018/credentials/examples/v1" 602 ], 603 "type": ["UniversityDegreeCredential"], 604 "trustedIssuer": [ 605 { 606 "issuer": "urn:some:required:issuer" 607 }, 608 { 609 "required": true, 610 "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f" 611 } 612 ] 613 } 614 }` 615 616 queryByExampleContextTypeIssuer2 := `{ 617 "reason": "Please present your identity document.", 618 "example": { 619 "@context": [ 620 "https://www.w3.org/2018/credentials/v1" 621 ], 622 "type": ["PermanentResidentCard"], 623 "trustedIssuer": [ 624 { 625 "required": true, 626 "issuer": "did:example:489398593" 627 } 628 ] 629 } 630 }` 631 632 queryByExampleContextTypeIssuer3 := `{ 633 "reason": "Please present your identity document.", 634 "example": { 635 "@context": [ 636 "https://www.w3.org/2018/credentials/v1" 637 ], 638 "type": ["PermanentResidentCard"], 639 "trustedIssuer": [ 640 { 641 "issuer": "did:example:489398593" 642 }, 643 { 644 "required": true, 645 "issuer": "did:example:1234" 646 } 647 ] 648 } 649 }` 650 651 queryByExampleContextTypeIssuer4 := `{ 652 "reason": "Please present your identity document.", 653 "example": { 654 "@context": [ 655 "https://www.w3.org/2018/credentials/v1" 656 ], 657 "type": ["PermanentResidentCard"], 658 "trustedIssuer": [ 659 { 660 "issuer": "did:example:7777" 661 }, 662 { 663 "issuer": "did:example:1234" 664 } 665 ] 666 } 667 }` 668 669 queryByExampleContextTypeCredSubject1 := `{ 670 "reason": "Please present your identity document.", 671 "example": { 672 "@context": [ 673 "https://www.w3.org/2018/credentials/v1" 674 ], 675 "type": ["PermanentResidentCard"], 676 "trustedIssuer": [ 677 { 678 "required": true, 679 "issuer": "did:example:489398593" 680 } 681 ], 682 "credentialSubject": { 683 "id": "did:example:b34ca6cd37bbf23" 684 }, 685 "credentialSchema": {} 686 } 687 }` 688 689 queryByExampleContextTypeCredSubject2 := `{ 690 "reason": "Please present your identity document.", 691 "example": { 692 "@context": [ 693 "https://www.w3.org/2018/credentials/v1" 694 ], 695 "type": ["UniversityDegreeCredential"], 696 "trustedIssuer": [ 697 { 698 "required": true, 699 "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f" 700 } 701 ], 702 "credentialSubject": { 703 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21" 704 }, 705 "credentialSchema": {} 706 } 707 }` 708 709 queryByExampleContextTypeCredSubject3 := `{ 710 "reason": "Please present your identity document.", 711 "example": { 712 "@context": [ 713 "https://www.w3.org/2018/credentials/v1" 714 ], 715 "type": ["UniversityDegreeCredential"], 716 "trustedIssuer": [ 717 { 718 "required": true, 719 "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f" 720 } 721 ], 722 "credentialSubject": { 723 "id": "did:example:ebfeb1f712ebc6f1c276e12ec22" 724 }, 725 "credentialSchema": {} 726 } 727 }` 728 729 queryByExampleContextTypeCredSchema1 := fmt.Sprintf(`{ 730 "reason": "Please present your identity document.", 731 "example": { 732 "@context": [ 733 "https://www.w3.org/2018/credentials/v1" 734 ], 735 "type": "UniversityDegreeCredential", 736 "credentialSubject": { 737 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21" 738 }, 739 "credentialSchema": { 740 "id": "%s", 741 "type": "JsonSchemaValidator2018" 742 } 743 } 744 }`, verifiable.ContextURI) 745 746 queryByExampleContextTypeCredSchema2 := fmt.Sprintf(`{ 747 "reason": "Please present your identity document.", 748 "example": { 749 "@context": [ 750 "https://www.w3.org/2018/credentials/v1" 751 ], 752 "type": "UniversityDegreeCredential", 753 "credentialSubject": { 754 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21" 755 }, 756 "credentialSchema": { 757 "id": "%s", 758 "type": "JsonSchemaValidator2020" 759 } 760 } 761 }`, verifiable.ContextURI) 762 763 queryByExampleInvalid1 := `{ 764 "reason": "Please present your identity document.", 765 "example": { 766 "trustedIssuer": [ 767 { 768 "required": true, 769 "issuer": "did:example:489398593" 770 } 771 ], 772 "credentialSubject": { 773 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21" 774 }, 775 "credentialSchema": {} 776 } 777 }` 778 779 queryByExampleInvalid2 := `{ 780 "reason": "Please present your identity document.", 781 "example": { 782 "@context": [ 783 "https://www.w3.org/2018/credentials/v1" 784 ], 785 "type": [] 786 } 787 }` 788 789 queryByExampleInvalid3 := `{ 790 "reason": "Please present your identity document.", 791 "example": { 792 "credentialSubject": "invalid", 793 "credentialSchema": true 794 } 795 }` 796 797 vc3 := &verifiable.Credential{ 798 Context: []string{verifiable.ContextURI}, 799 Types: []string{verifiable.VCType}, 800 ID: "http://example.edu/credentials/9999", 801 CustomFields: map[string]interface{}{ 802 "first_name": "Jesse", 803 }, 804 Issued: &util.TimeWrapper{ 805 Time: time.Now(), 806 }, 807 Issuer: verifiable.Issuer{ 808 ID: "did:example:76e12ec712ebc6f1c221ebfeb1f", 809 }, 810 Subject: uuid.New().String(), 811 } 812 813 t.Run("test query", func(t *testing.T) { 814 tests := []struct { 815 name string 816 credentials []*verifiable.Credential 817 example []json.RawMessage 818 resultCount int 819 error string 820 skip bool 821 }{ 822 { 823 name: "QueryByExample all criteria matched - success", 824 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 825 example: []json.RawMessage{queryByExampleAll}, 826 resultCount: 1, 827 }, 828 { 829 name: "QueryByExample multiple query frame - success", 830 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 831 example: []json.RawMessage{queryByExampleAll, queryByExampleAll}, 832 resultCount: 2, 833 }, 834 { 835 name: "QueryByExample context matching #1 - success", 836 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 837 example: []json.RawMessage{[]byte(queryByExampleContext1)}, 838 resultCount: 3, 839 }, 840 { 841 name: "QueryByExample context matching #2 - success", 842 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 843 example: []json.RawMessage{[]byte(queryByExampleContext2)}, 844 resultCount: 1, 845 }, 846 { 847 name: "QueryByExample context & type matching #1 - success", 848 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 849 example: []json.RawMessage{[]byte(queryByExampleContextType1)}, 850 resultCount: 1, 851 }, 852 { 853 name: "QueryByExample context & type matching #2 - success", 854 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 855 example: []json.RawMessage{[]byte(queryByExampleContextType2)}, 856 resultCount: 1, 857 }, 858 { 859 name: "QueryByExample context, type & issuer matching #1 - success", 860 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 861 example: []json.RawMessage{[]byte(queryByExampleContextTypeIssuer1)}, 862 resultCount: 1, 863 }, 864 { 865 name: "QueryByExample context, type & issuer matching #2 - success", 866 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 867 example: []json.RawMessage{[]byte(queryByExampleContextTypeIssuer2)}, 868 resultCount: 1, 869 }, 870 { 871 name: "QueryByExample context, type & issuer matching #3 - success", 872 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 873 example: []json.RawMessage{[]byte(queryByExampleContextTypeIssuer3)}, 874 resultCount: 0, 875 }, 876 { 877 name: "QueryByExample context, type & issuer matching #4 - success", 878 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 879 example: []json.RawMessage{[]byte(queryByExampleContextTypeIssuer4)}, 880 resultCount: 0, 881 }, 882 { 883 name: "QueryByExample context, type, issuer & subject matching #1 - success", 884 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 885 example: []json.RawMessage{[]byte(queryByExampleContextTypeCredSubject1)}, 886 resultCount: 1, 887 }, 888 { 889 name: "QueryByExample context, type, issuer & subject matching #2 - success", 890 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 891 example: []json.RawMessage{[]byte(queryByExampleContextTypeCredSubject2)}, 892 resultCount: 1, 893 }, 894 { 895 name: "QueryByExample context, type, issuer & subject matching #3 - success", 896 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 897 example: []json.RawMessage{[]byte(queryByExampleContextTypeCredSubject3)}, 898 resultCount: 0, 899 }, 900 { 901 name: "QueryByExample context, type, issuer, schema matching #1 - success", 902 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 903 example: []json.RawMessage{[]byte(queryByExampleContextTypeCredSchema1)}, 904 resultCount: 1, 905 }, 906 { 907 name: "QueryByExample context, type, issuer, schema matching #2- success", 908 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 909 example: []json.RawMessage{[]byte(queryByExampleContextTypeCredSchema2)}, 910 resultCount: 0, 911 }, 912 { 913 name: "QueryByExample invalid query #1", 914 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 915 example: []json.RawMessage{[]byte(queryByExampleInvalid1)}, 916 resultCount: 0, 917 error: "failed to parse QueryByExample query", 918 }, 919 { 920 name: "QueryByExample invalid query #2", 921 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 922 example: []json.RawMessage{[]byte(queryByExampleInvalid2)}, 923 resultCount: 0, 924 error: "failed to parse QueryByExample query", 925 }, 926 { 927 name: "QueryByExample invalid query #3", 928 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 929 example: []json.RawMessage{[]byte(queryByExampleInvalid3)}, 930 resultCount: 0, 931 error: "failed to parse QueryByExample query", 932 }, 933 } 934 935 t.Parallel() 936 937 for _, test := range tests { 938 tc := test 939 t.Run(tc.name, func(t *testing.T) { 940 if tc.skip { 941 return 942 } 943 944 results, err := queryByExample(tc.credentials, tc.example...) 945 946 if tc.error != "" { 947 require.Empty(t, results) 948 require.Error(t, err) 949 require.Contains(t, err.Error(), tc.error) 950 require.Len(t, results, tc.resultCount) 951 952 return 953 } 954 955 require.NoError(t, err) 956 require.Len(t, results, tc.resultCount) 957 }) 958 } 959 }) 960 } 961 962 func TestQueryByFrame(t *testing.T) { 963 loader, err := ldtestutil.DocumentLoader() 964 require.NoError(t, err) 965 966 vc1, err := verifiable.ParseCredential([]byte(fmt.Sprintf(sampleVCFmt, verifiable.ContextURI)), 967 verifiable.WithDisabledProofCheck(), 968 verifiable.WithJSONLDDocumentLoader(loader)) 969 require.NoError(t, err) 970 971 vc2, err := verifiable.ParseCredential([]byte(samplePRCVC), verifiable.WithDisabledProofCheck(), 972 verifiable.WithJSONLDDocumentLoader(loader)) 973 require.NoError(t, err) 974 975 vc3, err := verifiable.ParseCredential([]byte(sampleBBSVC), verifiable.WithDisabledProofCheck(), 976 verifiable.WithJSONLDDocumentLoader(loader)) 977 require.NoError(t, err) 978 979 tampered := strings.ReplaceAll(sampleBBSVC, `rw7FeV6K1wimnYogF9qd-N0zmq5QlaIoszg64HciTca`, ``) 980 vc4, err := verifiable.ParseCredential([]byte(tampered), verifiable.WithDisabledProofCheck(), 981 verifiable.WithJSONLDDocumentLoader(loader)) 982 require.NoError(t, err) 983 984 queryByFrameExampleNoIssuer := `{ 985 "reason": "Please provide your Passport details.", 986 "frame": { 987 "@context": [ 988 "https://www.w3.org/2018/credentials/v1", 989 "https://w3id.org/citizenship/v1", 990 "https://w3id.org/security/bbs/v1" 991 ], 992 "type": ["VerifiableCredential", "PermanentResidentCard"], 993 "@explicit": true, 994 "identifier": {}, 995 "issuer": {}, 996 "issuanceDate": {}, 997 "credentialSubject": { 998 "@explicit": true, 999 "name": {}, 1000 "spouse": {} 1001 } 1002 } 1003 }` 1004 1005 queryByFrameExampleIssuerNotMatched := strings.ReplaceAll(sampleQueryByFrame, 1006 "did:example:76e12ec712ebc6f1c221ebfeb1f", "1234") 1007 1008 customVDR := &mockvdr.MockVDRegistry{ 1009 ResolveFunc: func(didID string, opts ...vdrapi.DIDMethodOption) (*did.DocResolution, error) { 1010 if strings.HasPrefix(didID, "did:key:") { 1011 k := key.New() 1012 1013 d, e := k.Read(didID) 1014 if e != nil { 1015 return nil, e 1016 } 1017 1018 return d, nil 1019 } 1020 1021 return nil, fmt.Errorf("did not found") 1022 }, 1023 } 1024 pubKeyFetcher := verifiable.NewVDRKeyResolver(customVDR).PublicKeyFetcher() 1025 1026 t.Run("test query", func(t *testing.T) { 1027 tests := []struct { 1028 name string 1029 credentials []*verifiable.Credential 1030 example []json.RawMessage 1031 resultCount int 1032 error string 1033 skip bool 1034 }{ 1035 { 1036 name: "QueryByFrame all criteria matched - success", 1037 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 1038 example: []json.RawMessage{[]byte(sampleQueryByFrame)}, 1039 resultCount: 1, 1040 }, 1041 { 1042 name: "QueryByFrame multiple query - success", 1043 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 1044 example: []json.RawMessage{ 1045 []byte(sampleQueryByFrame), []byte(sampleQueryByFrame), 1046 []byte(sampleQueryByFrame), 1047 }, 1048 resultCount: 3, 1049 }, 1050 { 1051 name: "QueryByFrame without issuer criteria - success", 1052 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 1053 example: []json.RawMessage{[]byte(queryByFrameExampleNoIssuer)}, 1054 resultCount: 1, 1055 }, 1056 { 1057 name: "QueryByFrame issuer not matched - success", 1058 credentials: []*verifiable.Credential{vc1, vc2, vc3}, 1059 example: []json.RawMessage{[]byte(queryByFrameExampleIssuerNotMatched)}, 1060 resultCount: 0, 1061 }, 1062 { 1063 name: "QueryByFrame NO BBS signature - success", 1064 credentials: []*verifiable.Credential{vc1, vc2}, 1065 example: []json.RawMessage{[]byte(queryByFrameExampleIssuerNotMatched)}, 1066 resultCount: 0, 1067 }, 1068 { 1069 name: "QueryByFrame invalid BBS signature - success", 1070 credentials: []*verifiable.Credential{vc1, vc2, vc4}, 1071 example: []json.RawMessage{[]byte(sampleQueryByFrame)}, 1072 resultCount: 0, 1073 }, 1074 { 1075 name: "QueryByFrame invalid query - success", 1076 credentials: []*verifiable.Credential{vc1, vc3}, 1077 example: []json.RawMessage{[]byte(sampleInvalidDIDContent)}, 1078 error: "failed to parse QueryByFrame query", 1079 }, 1080 { 1081 name: "QueryByFrame parse query error - success", 1082 credentials: []*verifiable.Credential{vc1, vc3}, 1083 example: []json.RawMessage{[]byte("---")}, 1084 error: "failed to parse QueryByFrame query", 1085 }, 1086 } 1087 1088 t.Parallel() 1089 1090 for _, test := range tests { 1091 tc := test 1092 t.Run(tc.name, func(t *testing.T) { 1093 if tc.skip { 1094 return 1095 } 1096 1097 results, err := queryByFrame(tc.credentials, pubKeyFetcher, loader, tc.example...) 1098 1099 if tc.error != "" { 1100 require.Empty(t, results) 1101 require.Error(t, err) 1102 require.Contains(t, err.Error(), tc.error) 1103 require.Len(t, results, tc.resultCount) 1104 1105 return 1106 } 1107 1108 require.NoError(t, err) 1109 require.Len(t, results, tc.resultCount) 1110 }) 1111 } 1112 }) 1113 } 1114 1115 func TestUtilFunctions(t *testing.T) { 1116 require.True(t, isEmpty("")) 1117 require.True(t, isEmpty([]string{})) 1118 require.True(t, isEmpty([]interface{}{})) 1119 require.True(t, isEmpty(nil)) 1120 1121 require.False(t, isEmpty("x")) 1122 require.False(t, isEmpty([]string{""})) 1123 require.False(t, isEmpty([]interface{}{&QueryParams{}})) 1124 require.False(t, isEmpty(&QueryParams{})) 1125 1126 require.False(t, contains([]string{"a", "b"}, nil)) 1127 }