github.com/hyperledger/aries-framework-go@v0.3.2/pkg/doc/jsonld/validate_test.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  SPDX-License-Identifier: Apache-2.0
     4  */
     5  
     6  package jsonld
     7  
     8  import (
     9  	_ "embed"
    10  	"fmt"
    11  	"testing"
    12  
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/hyperledger/aries-framework-go/pkg/doc/ld"
    16  	"github.com/hyperledger/aries-framework-go/pkg/doc/ldcontext"
    17  	"github.com/hyperledger/aries-framework-go/pkg/internal/ldtestutil"
    18  )
    19  
    20  //nolint:gochecknoglobals
    21  var (
    22  	//go:embed testdata/context/context3.jsonld
    23  	context3 []byte
    24  	//go:embed testdata/context/context4.jsonld
    25  	context4 []byte
    26  	//go:embed testdata/context/context5.jsonld
    27  	context5 []byte
    28  	//go:embed testdata/context/context6.jsonld
    29  	context6 []byte
    30  
    31  	//go:embed testdata/context/wallet_v1.jsonld
    32  	walletV1Context []byte
    33  )
    34  
    35  func Test_ValidateJSONLD(t *testing.T) {
    36  	t.Run("Extended both basic VC and subject model", func(t *testing.T) {
    37  		contextURL := "http://127.0.0.1?context=3"
    38  
    39  		vcJSONTemplate := `{
    40    "@context": [
    41      "https://www.w3.org/2018/credentials/v1",
    42      "%s"
    43    ],
    44    "id": "http://example.com/credentials/4643",
    45    "type": [
    46      "VerifiableCredential",
    47      "CustomExt12"
    48    ],
    49    "issuer": "https://example.com/issuers/14",
    50    "issuanceDate": "2018-02-24T05:28:04Z",
    51    "referenceNumber": 83294847,
    52    "credentialSubject": [
    53      {
    54        "id": "did:example:abcdef1234567",
    55        "name": "Jane Doe",
    56        "favoriteFood": "Papaya"
    57      },
    58      {
    59        "id": "did:example:abcdef1234568",
    60        "name": "Alex"
    61      },
    62      {
    63        "id": "did:example:abcdef1234569",
    64        "name": "Justin"
    65      }
    66    ],
    67    "proof": [
    68      {
    69        "type": "Ed25519Signature2018",
    70        "created": "2020-04-10T21:35:35Z",
    71        "verificationMethod": "did:key:z6MkjRag",
    72        "proofPurpose": "assertionMethod",
    73        "jws": "eyJ..l9d0Y"
    74      },
    75      {
    76        "type": "Ed25519Signature2018",
    77        "created": "2020-04-11T21:35:35Z",
    78        "verificationMethod": "did:key:z6MkjRll",
    79        "proofPurpose": "assertionMethod",
    80        "jws": "eyJ..l9dZq"
    81      }
    82    ],
    83    "termsOfUse": [
    84      {
    85        "type": [
    86          "IssuerPolicy",
    87          "HolderPolicy"
    88        ],
    89        "id": "http://example.com/policies/credential/4"
    90      },
    91      {
    92        "type": [
    93          "IssuerPolicy",
    94          "HolderPolicy"
    95        ],
    96        "id": "http://example.com/policies/credential/5"
    97      }
    98    ]
    99  }
   100  `
   101  		vc := fmt.Sprintf(vcJSONTemplate, contextURL)
   102  
   103  		loader := createTestDocumentLoader(t, ldcontext.Document{
   104  			URL:     "http://127.0.0.1?context=3",
   105  			Content: context3,
   106  		})
   107  
   108  		err := ValidateJSONLD(vc, WithDocumentLoader(loader))
   109  		require.NoError(t, err)
   110  	})
   111  
   112  	t.Run("Extended basic VC model, credentialSubject is defined as string (ID only)", func(t *testing.T) {
   113  		// Use a different VC to verify the case when credentialSubject is a string (i.e. ID is defined only).
   114  
   115  		contextURL := "http://127.0.0.1?context=4"
   116  
   117  		vcJSONTemplate := `
   118  {
   119    "@context": [
   120      "https://www.w3.org/2018/credentials/v1",
   121      "%s"
   122    ],
   123    "id": "http://example.com/credentials/4643",
   124    "type": [
   125      "VerifiableCredential",
   126      "CustomExt12"
   127    ],
   128    "issuer": "https://example.com/issuers/14",
   129    "issuanceDate": "2018-02-24T05:28:04Z",
   130    "referenceNumber": 83294847,
   131    "credentialSubject": "did:example:abcdef1234567"
   132  }
   133  `
   134  		vcJSON := fmt.Sprintf(vcJSONTemplate, contextURL)
   135  
   136  		loader := createTestDocumentLoader(t, ldcontext.Document{
   137  			URL:     "http://127.0.0.1?context=4",
   138  			Content: context4,
   139  		})
   140  
   141  		err := ValidateJSONLD(vcJSON, WithDocumentLoader(loader))
   142  		require.NoError(t, err)
   143  	})
   144  }
   145  
   146  func Test_ValidateJSONLDWithExtraUndefinedFields(t *testing.T) {
   147  	contextURL := "http://127.0.0.1?context=5"
   148  
   149  	vcJSONTemplate := `
   150  {
   151    "@context": [
   152      "https://www.w3.org/2018/credentials/v1",
   153      "%s"
   154    ],
   155    "id": "http://example.com/credentials/4643",
   156    "type": ["VerifiableCredential", "CustomExt12"],
   157    "issuer": "https://example.com/issuers/14",
   158    "issuanceDate": "2018-02-24T05:28:04Z",
   159    "referenceNumber": 83294847,
   160    "credentialSubject": {
   161      "id": "did:example:abcdef1234567",
   162      "name": "Jane Doe",
   163      "favoriteFood": "Papaya"
   164    }
   165  }
   166  `
   167  	vc := fmt.Sprintf(vcJSONTemplate, contextURL)
   168  
   169  	loader := createTestDocumentLoader(t, ldcontext.Document{
   170  		URL:     "http://127.0.0.1?context=5",
   171  		Content: context5,
   172  	})
   173  
   174  	err := ValidateJSONLD(vc, WithDocumentLoader(loader))
   175  	require.Error(t, err)
   176  	require.EqualError(t, err, "JSON-LD doc has different structure after compaction")
   177  }
   178  
   179  func Test_ValidateJSONLDWithExtraUndefinedSubjectFields(t *testing.T) {
   180  	contextURL := "http://127.0.0.1?context=6"
   181  
   182  	loader := createTestDocumentLoader(t, ldcontext.Document{
   183  		URL:     contextURL,
   184  		Content: context6,
   185  	})
   186  
   187  	t.Run("Extended basic VC model, credentialSubject is defined as object - undefined fields present",
   188  		func(t *testing.T) {
   189  			// Use a different VC to verify the case when credentialSubject is an array.
   190  			vcJSONTemplate := `
   191  {
   192    "@context": [
   193      "https://www.w3.org/2018/credentials/v1",
   194      "%s"
   195    ],
   196    "id": "http://example.com/credentials/4643",
   197    "type": [
   198      "VerifiableCredential",
   199      "CustomExt12"
   200    ],
   201    "issuer": "https://example.com/issuers/14",
   202    "issuanceDate": "2018-02-24T05:28:04Z",
   203    "referenceNumber": 83294847,
   204    "credentialSubject": [
   205      {
   206        "id": "did:example:abcdef1234567",
   207        "name": "Jane Doe",
   208        "favoriteFood": "Papaya"
   209      }
   210    ]
   211  }
   212  `
   213  
   214  			vcJSON := fmt.Sprintf(vcJSONTemplate, contextURL)
   215  
   216  			err := ValidateJSONLD(vcJSON, WithDocumentLoader(loader))
   217  			require.Error(t, err)
   218  			require.EqualError(t, err, "JSON-LD doc has different structure after compaction")
   219  		})
   220  
   221  	t.Run("Extended basic VC model, credentialSubject is defined as array - undefined fields present", func(t *testing.T) {
   222  		// Use a different VC to verify the case when credentialSubject is an array.
   223  		vcJSONTemplate := `
   224  {
   225    "@context": [
   226      "https://www.w3.org/2018/credentials/v1",
   227      "%s"
   228    ],
   229    "id": "http://example.com/credentials/4643",
   230    "type": [
   231      "VerifiableCredential",
   232      "CustomExt12"
   233    ],
   234    "issuer": "https://example.com/issuers/14",
   235    "issuanceDate": "2018-02-24T05:28:04Z",
   236    "referenceNumber": 83294847,
   237    "credentialSubject": [
   238      {
   239        "id": "did:example:abcdef1234567",
   240        "name": "Jane Doe",
   241        "favoriteFood": "Papaya"
   242      }
   243    ]
   244  }
   245  `
   246  
   247  		vcJSON := fmt.Sprintf(vcJSONTemplate, contextURL)
   248  
   249  		err := ValidateJSONLD(vcJSON, WithDocumentLoader(loader))
   250  		require.Error(t, err)
   251  		require.EqualError(t, err, "JSON-LD doc has different structure after compaction")
   252  	})
   253  }
   254  
   255  func Test_ValidateJSONLD_WithExtraUndefinedFieldsInProof(t *testing.T) {
   256  	vcJSONWithValidProof := `
   257  {
   258    "@context": [
   259      "https://www.w3.org/2018/credentials/v1"
   260    ],
   261    "id": "http://example.com/credentials/4643",
   262    "type": [
   263      "VerifiableCredential"
   264    ],
   265    "issuer": "https://example.com/issuers/14",
   266    "issuanceDate": "2018-02-24T05:28:04Z",
   267    "credentialSubject": [
   268      {
   269        "id": "did:example:abcdef1234567"
   270      }
   271    ],
   272    "proof": {
   273      "type": "Ed25519Signature2018",
   274      "created": "2020-04-10T21:35:35Z",
   275      "verificationMethod": "did:key:z6MkjRag",
   276      "proofPurpose": "assertionMethod",
   277      "jws": "eyJ..l9d0Y"
   278    }
   279  }
   280  `
   281  
   282  	err := ValidateJSONLD(vcJSONWithValidProof, WithDocumentLoader(createTestDocumentLoader(t)))
   283  	require.NoError(t, err)
   284  
   285  	// "newProp" field is present in the proof
   286  	vcJSONWithInvalidProof := `{
   287    "@context": [
   288      "https://www.w3.org/2018/credentials/v1"
   289    ],
   290    "id": "http://example.com/credentials/4643",
   291    "type": [
   292      "VerifiableCredential"
   293    ],
   294    "issuer": "https://example.com/issuers/14",
   295    "issuanceDate": "2018-02-24T05:28:04Z",
   296    "credentialSubject": [
   297      {
   298        "id": "did:example:abcdef1234567"
   299      }
   300    ],
   301    "proof": {
   302      "type": "Ed25519Signature2018",
   303      "created": "2020-04-10T21:35:35Z",
   304      "verificationMethod": "did:key:z6MkjRag",
   305      "proofPurpose": "assertionMethod",
   306      "jws": "eyJ..l9d0Y",
   307      "newProp": "foo"
   308    }
   309  }`
   310  
   311  	err = ValidateJSONLD(vcJSONWithInvalidProof, WithDocumentLoader(createTestDocumentLoader(t)))
   312  
   313  	require.Error(t, err)
   314  	require.EqualError(t, err, "JSON-LD doc has different structure after compaction")
   315  }
   316  
   317  func Test_ValidateJSONLD_CornerErrorCases(t *testing.T) {
   318  	t.Run("Invalid JSON input", func(t *testing.T) {
   319  		err := ValidateJSONLD("not a json", WithDocumentLoader(createTestDocumentLoader(t)))
   320  		require.Error(t, err)
   321  		require.Contains(t, err.Error(), "convert JSON-LD doc to map")
   322  	})
   323  
   324  	t.Run("JSON-LD compact error", func(t *testing.T) {
   325  		vcJSONTemplate := `
   326  {
   327    "@context": 777,
   328    "id": "http://example.com/credentials/4643",
   329    "type": [
   330      "VerifiableCredential",
   331      "CustomExt12"
   332    ],
   333    "issuer": "https://example.com/issuers/14",
   334    "issuanceDate": "2018-02-24T05:28:04Z",
   335    "referenceNumber": 83294847,
   336    "credentialSubject": "did:example:abcdef1234567"
   337  }
   338  `
   339  
   340  		err := ValidateJSONLD(vcJSONTemplate, WithDocumentLoader(createTestDocumentLoader(t)))
   341  		require.Error(t, err)
   342  		require.Contains(t, err.Error(), "compact JSON-LD document")
   343  	})
   344  
   345  	t.Run("JSON-LD WithStrictContextURIPosition invalid context", func(t *testing.T) {
   346  		vcJSONTemplate := `
   347  {
   348    "@context": "https://www.w3.org/2018/credentials/v1",
   349    "id": "http://example.com/credentials/4643",
   350    "type": [
   351      "VerifiableCredential"
   352    ],
   353    "issuer": "https://example.com/issuers/14",
   354    "issuanceDate": "2018-02-24T05:28:04Z",
   355    "credentialSubject": "did:example:abcdef1234567"
   356  }
   357  `
   358  
   359  		err := ValidateJSONLD(vcJSONTemplate,
   360  			WithDocumentLoader(createTestDocumentLoader(t)),
   361  			WithStrictContextURIPosition("https://www.w3.org/2018/credentials/v1"),
   362  			WithStrictContextURIPosition("https://www.w3.org/2018/credentials/examples/v1"))
   363  		require.Error(t, err)
   364  		require.Contains(t, err.Error(), "doc context URIs amount mismatch")
   365  	})
   366  
   367  	t.Run("JSON-LD WithStrictContextURIPosition invalid context URI amount", func(t *testing.T) {
   368  		vcJSONTemplate := `
   369  {
   370    "@context": [
   371      "https://www.w3.org/2018/credentials/v1"
   372    ],
   373    "id": "http://example.com/credentials/4643",
   374    "type": [
   375      "VerifiableCredential"
   376    ],
   377    "issuer": "https://example.com/issuers/14",
   378    "issuanceDate": "2018-02-24T05:28:04Z",
   379    "credentialSubject": "did:example:abcdef1234567"
   380  }
   381  `
   382  
   383  		err := ValidateJSONLD(vcJSONTemplate,
   384  			WithDocumentLoader(createTestDocumentLoader(t)),
   385  			WithStrictContextURIPosition("https://www.w3.org/2018/credentials/v1"),
   386  			WithStrictContextURIPosition("https://www.w3.org/2018/credentials/examples/v1"),
   387  		)
   388  		require.Error(t, err)
   389  		require.Contains(t, err.Error(), "doc context URIs amount mismatch")
   390  	})
   391  
   392  	t.Run("JSON-LD WithStrictContextURIPosition validate context URI position", func(t *testing.T) {
   393  		vcJSONTemplate := `
   394  {
   395    "@context": [
   396      "https://www.w3.org/2018/credentials/v1",
   397  	"https://www.w3.org/2018/credentials/examples/v1"
   398    ],
   399    "id": "http://example.com/credentials/4643",
   400    "type": [
   401      "VerifiableCredential"
   402    ],
   403    "issuer": "https://example.com/issuers/14",
   404    "issuanceDate": "2018-02-24T05:28:04Z",
   405    "credentialSubject": "did:example:abcdef1234567"
   406  }
   407  `
   408  
   409  		err := ValidateJSONLD(vcJSONTemplate,
   410  			WithDocumentLoader(createTestDocumentLoader(t)),
   411  			WithStrictContextURIPosition("https://www.w3.org/2018/credentials/examples/v1"),
   412  		)
   413  		require.Error(t, err)
   414  		require.Contains(t, err.Error(), "invalid context URI on position")
   415  
   416  		err = ValidateJSONLD(vcJSONTemplate,
   417  			WithDocumentLoader(createTestDocumentLoader(t)),
   418  			WithStrictContextURIPosition("https://www.w3.org/2018/credentials/v1"),
   419  			WithStrictContextURIPosition("https://www.w3.org/2018/credentials/examples/v1"),
   420  		)
   421  		require.NoError(t, err)
   422  	})
   423  }
   424  
   425  // nolint:gochecknoglobals // needed to avoid Go compiler perf optimizations for benchmarks.
   426  var MajorSink string
   427  
   428  func Benchmark_ValidateJSONLD(b *testing.B) {
   429  	var sink string
   430  
   431  	b.Run("Extended both basic VC and subject model", func(b *testing.B) {
   432  		contextURL := "http://127.0.0.1?context=3"
   433  
   434  		vcJSONTemplate := `{
   435    "@context": [
   436      "https://www.w3.org/2018/credentials/v1",
   437      "%s"
   438    ],
   439    "id": "http://example.com/credentials/4643",
   440    "type": [
   441      "VerifiableCredential",
   442      "CustomExt12"
   443    ],
   444    "issuer": "https://example.com/issuers/14",
   445    "issuanceDate": "2018-02-24T05:28:04Z",
   446    "referenceNumber": 83294847,
   447    "credentialSubject": [
   448      {
   449        "id": "did:example:abcdef1234567",
   450        "name": "Jane Doe",
   451        "favoriteFood": "Papaya"
   452      },
   453      {
   454        "id": "did:example:abcdef1234568",
   455        "name": "Alex"
   456      },
   457      {
   458        "id": "did:example:abcdef1234569",
   459        "name": "Justin"
   460      }
   461    ],
   462    "proof": [
   463      {
   464        "type": "Ed25519Signature2018",
   465        "created": "2020-04-10T21:35:35Z",
   466        "verificationMethod": "did:key:z6MkjRag",
   467        "proofPurpose": "assertionMethod",
   468        "jws": "eyJ..l9d0Y"
   469      },
   470      {
   471        "type": "Ed25519Signature2018",
   472        "created": "2020-04-11T21:35:35Z",
   473        "verificationMethod": "did:key:z6MkjRll",
   474        "proofPurpose": "assertionMethod",
   475        "jws": "eyJ..l9dZq"
   476      }
   477    ],
   478    "termsOfUse": [
   479      {
   480        "type": [
   481          "IssuerPolicy",
   482          "HolderPolicy"
   483        ],
   484        "id": "http://example.com/policies/credential/4"
   485      },
   486      {
   487        "type": [
   488          "IssuerPolicy",
   489          "HolderPolicy"
   490        ],
   491        "id": "http://example.com/policies/credential/5"
   492      }
   493    ]
   494  }
   495  `
   496  
   497  		vc := fmt.Sprintf(vcJSONTemplate, contextURL)
   498  
   499  		b.RunParallel(func(pb *testing.PB) {
   500  			b.ResetTimer()
   501  
   502  			for pb.Next() {
   503  				loader, err := ldtestutil.DocumentLoader(ldcontext.Document{
   504  					URL:     "http://127.0.0.1?context=3",
   505  					Content: context3,
   506  				})
   507  				require.NoError(b, err)
   508  
   509  				err = ValidateJSONLD(vc, WithDocumentLoader(loader))
   510  				require.NoError(b, err)
   511  
   512  				sink = "basic_compact_test"
   513  			}
   514  
   515  			MajorSink = sink
   516  		})
   517  	})
   518  
   519  	b.Run("Extended basic VC model, credentialSubject is defined as string (ID only)", func(b *testing.B) {
   520  		// Use a different VC to verify the case when credentialSubject is a string (i.e. ID is defined only).
   521  
   522  		contextURL := "http://127.0.0.1?context=4"
   523  
   524  		vcJSONTemplate := `
   525  {
   526    "@context": [
   527      "https://www.w3.org/2018/credentials/v1",
   528      "%s"
   529    ],
   530    "id": "http://example.com/credentials/4643",
   531    "type": [
   532      "VerifiableCredential",
   533      "CustomExt12"
   534    ],
   535    "issuer": "https://example.com/issuers/14",
   536    "issuanceDate": "2018-02-24T05:28:04Z",
   537    "referenceNumber": 83294847,
   538    "credentialSubject": "did:example:abcdef1234567"
   539  }
   540  `
   541  		vcJSON := fmt.Sprintf(vcJSONTemplate, contextURL)
   542  
   543  		b.RunParallel(func(pb *testing.PB) {
   544  			b.ResetTimer()
   545  
   546  			for pb.Next() {
   547  				loader, err := ldtestutil.DocumentLoader(ldcontext.Document{
   548  					URL:     "http://127.0.0.1?context=4",
   549  					Content: context4,
   550  				})
   551  				require.NoError(b, err)
   552  
   553  				err = ValidateJSONLD(vcJSON, WithDocumentLoader(loader))
   554  				require.NoError(b, err)
   555  
   556  				sink = "extended_compact_test"
   557  			}
   558  
   559  			MajorSink = sink
   560  		})
   561  	})
   562  
   563  	b.Run("Extended both basic VC and subject model", func(b *testing.B) {
   564  		const testMetadata = `{
   565    			"@context": ["https://w3id.org/wallet/v1"],
   566    		  	"id": "test-id",
   567      		"type": "Person",
   568      		"name": "John Smith",
   569      		"image": "https://via.placeholder.com/150",
   570      		"description" : "Professional software developer for Acme Corp."
   571    		}`
   572  
   573  		b.RunParallel(func(pb *testing.PB) {
   574  			b.ResetTimer()
   575  
   576  			for pb.Next() {
   577  				loader, err := ldtestutil.DocumentLoader(ldcontext.Document{
   578  					URL:     "https://w3id.org/wallet/v1",
   579  					Content: walletV1Context,
   580  				})
   581  				require.NoError(b, err)
   582  
   583  				err = ValidateJSONLD(testMetadata, WithDocumentLoader(loader))
   584  				require.NoError(b, err)
   585  
   586  				sink = "basic_compact_test"
   587  			}
   588  
   589  			MajorSink = sink
   590  		})
   591  	})
   592  }
   593  
   594  func createTestDocumentLoader(t *testing.T, extraContexts ...ldcontext.Document) *ld.DocumentLoader {
   595  	t.Helper()
   596  
   597  	loader, err := ldtestutil.DocumentLoader(extraContexts...)
   598  	require.NoError(t, err)
   599  
   600  	return loader
   601  }