Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix schema validation logic #87

Merged
merged 7 commits into from
Apr 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
actions: read
contents: read
security-events: write
env:
GOFLAGS: "-tags=jwx_es256k"

strategy:
fail-fast: false
Expand All @@ -50,10 +52,8 @@ jobs:
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main


- run: |
mage clean
mage build
- name: Autobuild
uses: github/codeql-action/autobuild@v2

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
5 changes: 5 additions & 0 deletions credential/schema/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ package schema

import "fmt"

const (
// VCJSONSchemaType https://w3c-ccg.github.io/vc-json-schemas/v2/index.html#credential_schema_definition_metadata
VCJSONSchemaType string = "https://w3c-ccg.github.io/vc-json-schemas/schema/2.0/schema.json"
)

type JSONSchema map[string]interface{}

// VCJSONSchema is the model representing the
Expand Down
29 changes: 20 additions & 9 deletions credential/schema/vcjsonschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package schema
import (
"github.com/TBD54566975/ssi-sdk/schema"
"github.com/goccy/go-json"
"github.com/pkg/errors"

"github.com/TBD54566975/ssi-sdk/credential"
"github.com/gobuffalo/packr/v2"
Expand All @@ -19,29 +20,39 @@ var (

// StringToVCJSONCredentialSchema marshals a string into a credential json credential schema
func StringToVCJSONCredentialSchema(maybeVCJSONCredentialSchema string) (*VCJSONSchema, error) {
if err := schema.IsValidJSONSchema(maybeVCJSONCredentialSchema); err != nil {
return nil, err
}
var vcs VCJSONSchema
if err := json.Unmarshal([]byte(maybeVCJSONCredentialSchema), &vcs); err != nil {
return nil, err
}

schemaBytes, err := json.Marshal(vcs.Schema)
if err != nil {
return nil, errors.Wrap(err, "could not marshal vc json schema's schema property")
}
maybeSchema := string(schemaBytes)
if err := schema.IsValidJSONSchema(maybeSchema); err != nil {
return nil, errors.Wrap(err, "VC JSON Schema did not contain a valid JSON Schema")
}
return &vcs, nil
}

// IsValidCredentialSchema determines if a given credential schema is compliant with the specification's
// JSON Schema https://w3c-ccg.github.io/vc-json-schemas/v2/index.html#credential_schema_definition
func IsValidCredentialSchema(maybeCredentialSchema string) error {
if err := schema.IsValidJSONSchema(maybeCredentialSchema); err != nil {
return err
}

vcJSONSchemaSchema, err := getKnownSchema(verifiableCredentialJSONSchemaSchema)
if err != nil {
return err
return errors.Wrap(err, "could not get known schema for VC JSON Schema")
}

if err := schema.IsJSONValidAgainstSchema(maybeCredentialSchema, vcJSONSchemaSchema); err != nil {
return errors.Wrap(err, "credential schema did not validate")
}

if _, err := StringToVCJSONCredentialSchema(maybeCredentialSchema); err != nil {
return errors.Wrap(err, "credential schema not valid")
}

return schema.IsJSONValidAgainstSchema(maybeCredentialSchema, vcJSONSchemaSchema)
return nil
}

func IsCredentialValidForVCJSONSchema(credential credential.VerifiableCredential, vcJSONSchema VCJSONSchema) error {
Expand Down
1 change: 1 addition & 0 deletions credential/schema/vcjsonschema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func TestIsCredentialValidForSchema(t *testing.T) {

vcJSONSchema, err := StringToVCJSONCredentialSchema(vcJSONSchemaString)
assert.NoError(t, err)
assert.NotEmpty(t, vcJSONSchema)

// Validate credential against vcJSONSchema
err = IsCredentialValidForVCJSONSchema(cred, *vcJSONSchema)
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ require (
github.com/lestrrat-go/blackmagic v1.0.0 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/iter v1.0.1 // indirect
github.com/lestrrat-go/jwx v1.2.22
github.com/lestrrat-go/jwx v1.2.23
github.com/lestrrat-go/option v1.0.0 // indirect
github.com/magefile/mage v1.13.0
github.com/markbates/errx v1.1.0 // indirect
Expand All @@ -45,9 +45,9 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
Expand Down
15 changes: 8 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,8 @@ github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZ
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A=
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
github.com/lestrrat-go/jwx v1.2.22 h1:bCxMokwHNuJHVxgANP4OBddXGtQ9Oy+6cqp4O2rW7DU=
github.com/lestrrat-go/jwx v1.2.22/go.mod h1:sAXjRwzSvCN6soO4RLoWWm1bVPpb8iOuv0IYfH8OWd8=
github.com/lestrrat-go/jwx v1.2.23 h1:8oP5fY1yzCRraUNNyfAVdOkLCqY7xMZz11lVcvHqC1Y=
github.com/lestrrat-go/jwx v1.2.23/go.mod h1:sAXjRwzSvCN6soO4RLoWWm1bVPpb8iOuv0IYfH8OWd8=
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/magefile/mage v1.13.0 h1:XtLJl8bcCM7EFoO8FyH8XK3t7G5hQAeK+i4tq+veT9M=
Expand Down Expand Up @@ -368,8 +368,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 h1:iU7T1X1J6yxDr0rda54sWGkHgOp5XJrqm79gcNlC2VM=
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
Expand Down Expand Up @@ -516,11 +516,12 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f h1:8w7RhxzTVgUzw/AH/9mUV5q0vMgy40SQRursCcfmkCw=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
8 changes: 6 additions & 2 deletions schema/jsonschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ func IsValidJSONSchema(maybeSchema string) error {
if !IsValidJSON(maybeSchema) {
return errors.New("input is not valid json")
}
loader := gojsonschema.NewStringLoader(maybeSchema)
return gojsonschema.NewSchemaLoader().AddSchemas(loader)
stringLoader := gojsonschema.NewStringLoader(maybeSchema)
schemaLoader := gojsonschema.NewSchemaLoader()
schemaLoader.Validate = true
schemaLoader.Draft = gojsonschema.Draft7
_, err := schemaLoader.Compile(stringLoader)
return err
}

// IsValidJSON checks if a string is valid json https://stackoverflow.com/a/36922225
Expand Down
71 changes: 49 additions & 22 deletions schema/jsonschema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,36 @@ func TestJSONSchemaVectors(t *testing.T) {
}

func TestJSONSchemaValidation(t *testing.T) {
t.Run("Test Valid Address JSON Schema", func(t *testing.T) {
t.Run("Test Invalid JSON Schema", func(tt *testing.T) {
err := IsValidJSONSchema("bad")
assert.Error(tt, err)
assert.Contains(tt, err.Error(), "input is not valid json")

badSchema := `{
"$id": "https://example.com/person.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Person",
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "The person's first name."
},
"lastName": {
"type": "string",
"description": "The person's last name."
},
"required": ["middleName"]
},
"additionalProperties": false
}`
err = IsValidJSONSchema(badSchema)
assert.Error(tt, err)
})

t.Run("Test Valid Address JSON Schema", func(tt *testing.T) {
addressJSONSchema, err := getTestVector(JSONSchemaTestVector1)
assert.NoError(t, err)
assert.NoError(tt, err)

addressData := map[string]interface{}{
"street-address": "1455 Market St.",
Expand All @@ -43,17 +70,17 @@ func TestJSONSchemaValidation(t *testing.T) {
}

addressDataBytes, err := json.Marshal(addressData)
assert.NoError(t, err)
assert.NoError(tt, err)

addressDataJSON := string(addressDataBytes)
assert.True(t, IsValidJSON(addressDataJSON))
assert.True(tt, IsValidJSON(addressDataJSON))

assert.NoError(t, IsJSONValidAgainstSchema(addressDataJSON, addressJSONSchema))
assert.NoError(tt, IsJSONValidAgainstSchema(addressDataJSON, addressJSONSchema))
})

t.Run("Test Invalid Address JSON Schema", func(t *testing.T) {
t.Run("Test Invalid Address JSON Schema", func(tt *testing.T) {
addressJSONSchema, err := getTestVector(JSONSchemaTestVector1)
assert.NoError(t, err)
assert.NoError(tt, err)

// Missing required field
addressData := map[string]interface{}{
Expand All @@ -63,19 +90,19 @@ func TestJSONSchemaValidation(t *testing.T) {
}

addressDataBytes, err := json.Marshal(addressData)
assert.NoError(t, err)
assert.NoError(tt, err)

addressDataJSON := string(addressDataBytes)
assert.True(t, IsValidJSON(addressDataJSON))
assert.True(tt, IsValidJSON(addressDataJSON))

err = IsJSONValidAgainstSchema(addressDataJSON, addressJSONSchema)
assert.Error(t, err)
assert.Contains(t, err.Error(), "postal-code is required")
assert.Error(tt, err)
assert.Contains(tt, err.Error(), "postal-code is required")
})

t.Run("Test Valid Person JSON Schema", func(t *testing.T) {
t.Run("Test Valid Person JSON Schema", func(tt *testing.T) {
personJSONSchema, err := getTestVector(JSONSchemaTestVector2)
assert.NoError(t, err)
assert.NoError(tt, err)

// Additional field
personData := map[string]interface{}{
Expand All @@ -84,17 +111,17 @@ func TestJSONSchemaValidation(t *testing.T) {
}

personDataBytes, err := json.Marshal(personData)
assert.NoError(t, err)
assert.NoError(tt, err)

personDataJSON := string(personDataBytes)
assert.True(t, IsValidJSON(personDataJSON))
assert.True(tt, IsValidJSON(personDataJSON))

assert.NoError(t, IsJSONValidAgainstSchema(personDataJSON, personJSONSchema))
assert.NoError(tt, IsJSONValidAgainstSchema(personDataJSON, personJSONSchema))
})

t.Run("Test Invalid Person JSON Schema", func(t *testing.T) {
t.Run("Test Invalid Person JSON Schema", func(tt *testing.T) {
personJSONSchema, err := getTestVector(JSONSchemaTestVector2)
assert.NoError(t, err)
assert.NoError(tt, err)

// Additional field
personData := map[string]interface{}{
Expand All @@ -105,14 +132,14 @@ func TestJSONSchemaValidation(t *testing.T) {
}

personDataBytes, err := json.Marshal(personData)
assert.NoError(t, err)
assert.NoError(tt, err)

personDataJSON := string(personDataBytes)
assert.True(t, IsValidJSON(personDataJSON))
assert.True(tt, IsValidJSON(personDataJSON))

err = IsJSONValidAgainstSchema(personDataJSON, personJSONSchema)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Additional property middleName is not allowed")
assert.Error(tt, err)
assert.Contains(tt, err.Error(), "Additional property middleName is not allowed")
})
}

Expand Down