Skip to content

Commit

Permalink
Merge pull request #381 from hypersign-protocol/credential-schema-val…
Browse files Browse the repository at this point in the history
…idation

feat: `name` and `properties` validation for Credential Schema
  • Loading branch information
arnabghose997 authored Nov 25, 2023
2 parents a6d2c8c + a87f940 commit 700703b
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 7 deletions.
93 changes: 91 additions & 2 deletions tests/e2e/ssi_tests/e2e_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import sys
sys.path.insert(1, os.getcwd())
import time
import copy

from utils import run_blockchain_command, generate_key_pair, secp256k1_pubkey_to_address, add_keyAgreeemnt_pubKeyMultibase
from generate_doc import generate_did_document, generate_schema_document, generate_cred_status_document
Expand Down Expand Up @@ -805,8 +806,96 @@ def schema_test():
schema_doc_id = schema_doc["id"]
run_blockchain_command(create_schema_cmd, f"Registering Schema with Id: {schema_doc_id}")

print("3. PASS: Bob updates a schema using one of her VMs\n")
updated_schema_doc = schema_doc
print("3. FAIL: Bob creates a Schema where the name field is not in Pascal Case \n")

invalid_schema_doc_1 = copy.deepcopy(schema_doc)
invalid_schema_doc_1["name"] = "Day Pass Credential"
schema_id_list = list(invalid_schema_doc_1["id"])
schema_id_list[-3] = '4'
invalid_schema_doc_1["id"] = "".join(schema_id_list)
invalid_schema_doc_1_id = invalid_schema_doc_1["id"]

_, schema_proof = generate_schema_document(
kp_bob,
did_doc_bob,
did_doc_bob_vm["id"],
updated_schema=invalid_schema_doc_1
)
update_schema_cmd = form_update_schema_tx(
invalid_schema_doc_1,
schema_proof,
DEFAULT_BLOCKCHAIN_ACCOUNT_NAME
)
run_blockchain_command(update_schema_cmd, f"Updating Schema with Id: {invalid_schema_doc_1_id}", True)

print("4. FAIL: Bob creates a Schema where the property field is some random string\n")

invalid_schema_doc_2 = copy.deepcopy(schema_doc)
invalid_schema_doc_2["schema"]["properties"] = "somestring"
schema_id_list = list(invalid_schema_doc_2["id"])
schema_id_list[-3] = '4'
invalid_schema_doc_2["id"] = "".join(schema_id_list)
invalid_schema_doc_2_id = invalid_schema_doc_2["id"]

_, schema_proof = generate_schema_document(
kp_bob,
did_doc_bob,
did_doc_bob_vm["id"],
updated_schema=invalid_schema_doc_2
)
update_schema_cmd = form_update_schema_tx(
invalid_schema_doc_2,
schema_proof,
DEFAULT_BLOCKCHAIN_ACCOUNT_NAME
)
run_blockchain_command(update_schema_cmd, f"Updating Schema with Id: {invalid_schema_doc_2_id}", True)

print("5. FAIL: Bob creates a Schema where the property field is a valid JSON, but one of the attributes has an invalid sub-attribute\n")

invalid_schema_doc_3 = copy.deepcopy(schema_doc)
invalid_schema_doc_3["schema"]["properties"] = "{\"fullName\":{\"type\":\"string\",\"sda\":\"string\"},\"companyName\":{\"type\":\"string\"},\"center\":{\"type\":\"string\"},\"invoiceNumber\":{\"type\":\"string\"}}"
schema_id_list = list(invalid_schema_doc_3["id"])
schema_id_list[-3] = '4'
invalid_schema_doc_3["id"] = "".join(schema_id_list)
invalid_schema_doc_3_id = invalid_schema_doc_3["id"]

_, schema_proof = generate_schema_document(
kp_bob,
did_doc_bob,
did_doc_bob_vm["id"],
updated_schema=invalid_schema_doc_3
)
update_schema_cmd = form_update_schema_tx(
invalid_schema_doc_3,
schema_proof,
DEFAULT_BLOCKCHAIN_ACCOUNT_NAME
)
run_blockchain_command(update_schema_cmd, f"Updating Schema with Id: {invalid_schema_doc_3_id}", True)

print("6. FAIL: Bob creates a Schema where the property field is a valid JSON, but `type` sub-attribute is missing\n")

invalid_schema_doc_4 = copy.deepcopy(schema_doc)
invalid_schema_doc_4["schema"]["properties"] = "{\"fullName\":{\"format\":\"string\"},\"companyName\":{\"type\":\"string\"},\"center\":{\"type\":\"string\"},\"invoiceNumber\":{\"type\":\"string\"}}"
schema_id_list = list(invalid_schema_doc_4["id"])
schema_id_list[-3] = '4'
invalid_schema_doc_4["id"] = "".join(schema_id_list)
invalid_schema_doc_4_id = invalid_schema_doc_4["id"]

_, schema_proof = generate_schema_document(
kp_bob,
did_doc_bob,
did_doc_bob_vm["id"],
updated_schema=invalid_schema_doc_4
)
update_schema_cmd = form_update_schema_tx(
invalid_schema_doc_4,
schema_proof,
DEFAULT_BLOCKCHAIN_ACCOUNT_NAME
)
run_blockchain_command(update_schema_cmd, f"Updating Schema with Id: {invalid_schema_doc_4_id}", True)

print("7. PASS: Bob updates a schema using one of her VMs\n")
updated_schema_doc = copy.deepcopy(schema_doc)
#Increment version in Schema id
schema_id_list = list(updated_schema_doc["id"])
schema_id_list[-3] = '4'
Expand Down
10 changes: 7 additions & 3 deletions tests/e2e/ssi_tests/generate_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,19 @@ def generate_schema_document(key_pair, schema_author, vm, signature=None, algo="
"type": "https://schema.org/Person",
"modelVersion": "v1.0",
"id": "",
"name": "Person",
"name": "SomeCredentialSchema",
"author": "",
"authored": "2022-08-16T10:22:12Z",
"schema": {
"schema":"https://json-schema.org/draft-07/schema#",
"description":"Person Schema",
"type":"object",
"properties":"{givenName:{type:string},gender:{type:string},email:{type:text},address:{type:text}}",
"required":["givenName","address"],
"properties":"{\"fullName\":{\"type\":\"string\"},\"companyName\":{\"type\":\"string\"},\"center\":{\"type\":\"string\"},\"invoiceNumber\":{\"type\":\"string\"}}",
"required": [
"fullName",
"center",
"invoiceNumber"
],
}
}

Expand Down
53 changes: 53 additions & 0 deletions x/ssi/keeper/msg_server_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package keeper

import (
"context"
"encoding/json"
"fmt"
"regexp"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
Expand Down Expand Up @@ -47,6 +49,16 @@ func storeCredentialSchema(
return sdkerrors.Wrap(types.ErrSchemaExists, fmt.Sprintf("Schema ID: %s", schemaID))
}

// Check if the `name` attribute of schema is in PascalCase
if !isStringInPascalCase(schemaDoc.Name) {
return sdkerrors.Wrapf(types.ErrInvalidCredentialSchema, "name must always be in PascalCase: %v", schemaDoc.Name)
}

// Check if `properties` field is a valid JSON document
if err := isValidJSONDocument(schemaDoc.Schema.Properties); err != nil {
return sdkerrors.Wrapf(types.ErrInvalidCredentialSchema, "invalid `property` provided: %v", err.Error())
}

// Signature check
if err := k.VerifyDocumentProof(ctx, schemaDoc, schemaProof); err != nil {
return sdkerrors.Wrap(types.ErrInvalidClientSpecType, err.Error())
Expand Down Expand Up @@ -82,3 +94,44 @@ func (k msgServer) UpdateCredentialSchema(goCtx context.Context, msg *types.MsgU

return &types.MsgUpdateCredentialSchemaResponse{}, nil
}

// isStringInPascalCase checks if an input string is in Pascal case or not
func isStringInPascalCase(s string) bool {
pascalCaseRegex := regexp.MustCompile(`^[A-Z][a-zA-Z0-9]*$`)

return pascalCaseRegex.MatchString(s)
}

// isValidJSONDocument checks if the schema property attribute is a valid JSON string
// The `type` sub-attribute is a must for every attributes in property.
// The `format` sub-attribute is acceptable, but optional
func isValidJSONDocument(schemaProperty string) error {
var schemaPropertyDocument map[string]map[string]interface{}
if err := json.Unmarshal([]byte(schemaProperty), &schemaPropertyDocument); err != nil {
return err
}

for attributeName, attributeObj := range schemaPropertyDocument {
isTypeSubAttributePresent := false

for subAttributeName := range attributeObj {
if subAttributeName == "type" {
isTypeSubAttributePresent = true
} else if subAttributeName == "format" {
continue
} else {
return fmt.Errorf(
"invalid sub-attribute %v of attribute %v. Only `type` and `format` sub-attributes are permitted",
subAttributeName,
attributeName,
)
}
}

if !isTypeSubAttributePresent {
return fmt.Errorf("%v attribute is missing the required sub-attribute `type`", attributeName)
}
}

return nil
}
3 changes: 1 addition & 2 deletions x/ssi/ld-context/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func NewJsonLdCredentialSchema(credSchema *types.CredentialSchemaDocument) *Json

// It is a similar to `Did` struct, with the exception that the `context` attribute is of type
// `contextObject` instead of `[]string`, which is meant for accomodating Context JSON body
// having arbritrary attributes. It should be used for performing Canonization.
// having arbritrary attributes. It should be used for performing Canonization.
type JsonLdDidDocumentWithoutVM struct {
Context []contextObject `json:"@context,omitempty"`
Id string `json:"id,omitempty"`
Expand Down Expand Up @@ -269,4 +269,3 @@ func newVerificationMethodWithoutController(vm *types.VerificationMethod) verifi
}
return vmWithoutController
}

1 change: 1 addition & 0 deletions x/ssi/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ var (
ErrCredentialStatusExists = sdkerrors.Register(ModuleName, 116, "credential status document already exists")
ErrInvalidCredentialStatusID = sdkerrors.Register(ModuleName, 117, "invalid credential status Id")
ErrInvalidProof = sdkerrors.Register(ModuleName, 118, "invalid document proof")
ErrInvalidCredentialSchema = sdkerrors.Register(ModuleName, 119, "invalid credential schema")
)

0 comments on commit 700703b

Please sign in to comment.