From 7ee98aef3a6b3a45adffb7721733619a45aec7f5 Mon Sep 17 00:00:00 2001 From: Andrew Nikitin Date: Wed, 2 Mar 2022 20:43:00 +0300 Subject: [PATCH] Add DID static validation without tests Signed-off-by: Andrew Nikitin --- x/cheqd/types/did.go | 23 +-- x/cheqd/types/did_validation.go | 220 +++++++++++++++++++++++++++ x/cheqd/types/did_validation_test.go | 36 +++++ x/cheqd/types/error.go | 8 + x/cheqd/types/stateValue.go | 2 +- x/cheqd/types/validate.go | 11 +- x/cheqd/utils/did.go | 116 -------------- x/cheqd/utils/did_test.go | 31 ---- 8 files changed, 282 insertions(+), 165 deletions(-) create mode 100644 x/cheqd/types/did_validation.go create mode 100644 x/cheqd/types/did_validation_test.go delete mode 100644 x/cheqd/utils/did.go delete mode 100644 x/cheqd/utils/did_test.go diff --git a/x/cheqd/types/did.go b/x/cheqd/types/did.go index 9eaff7877..8662cf2f2 100644 --- a/x/cheqd/types/did.go +++ b/x/cheqd/types/did.go @@ -2,7 +2,6 @@ package types import ( "github.com/cheqd/cheqd-node/x/cheqd/utils" - "github.com/multiformats/go-multibase" ) var _ StateValueData = &Did{} @@ -28,15 +27,15 @@ func (did *Did) AggregateControllerDids() []string { return utils.Unique(result) } -func (did *Did) FindVerificationMethod(id string) (VerificationMethod, bool) { - for _, vm := range vms { - if vm.Id == id { - return vm - } - } - - return nil -} +//func (did *Did) FindVerificationMethod(id string) (VerificationMethod, bool) { +// for _, vm := range vms { +// if vm.Id == id { +// return vm +// } +// } +// +// return nil, true +//} func FindVerificationMethod(vms []VerificationMethod, id string) (VerificationMethod, bool) { for _, vm := range vms { @@ -48,4 +47,6 @@ func FindVerificationMethod(vms []VerificationMethod, id string) (VerificationMe return VerificationMethod{}, false } -func FilterVerificationMethods(vms []VerificationMethod, func()) \ No newline at end of file +//func FilterVerificationMethods(vms []VerificationMethod, func()) { +// +//} \ No newline at end of file diff --git a/x/cheqd/types/did_validation.go b/x/cheqd/types/did_validation.go new file mode 100644 index 000000000..cec234f32 --- /dev/null +++ b/x/cheqd/types/did_validation.go @@ -0,0 +1,220 @@ +package types + +import ( + "github.com/cheqd/cheqd-node/x/cheqd/utils" + "regexp" +) + +var SplitDIDRegexp, _ = regexp.Compile(`did:([^:]+?)(:([^:]+?))?:([^:]+)$`) +var DidNamespaceRegexp, _ = regexp.Compile(`^[a-zA-Z0-9]$`) +// Base58 only allowed (without OolI and 0) +var UniqueIDRegexp, _ = regexp.Compile(`^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]*$`) +// That for groups: +// Example: did:cheqd:testnet:fafdsffq11213343/path-to-s/ome-external-resource?query#key1???#123 +// 1 - [^/?#]* - all the symbols except / and ? and # . This is the DID part (did:cheqd:testnet:fafdsffq11213343) +// 2 - [^?#]* - all the symbols except ? and #. it means te section started from /, path-abempty (/path-to-s/ome-external-resource) +// 3 - \?([^#]*) - group for `query` part but with ? symbol (?query) +// 4 - [^#]* - group inside query string, match only exact query (query) +// 5 - #([^#]+[\$]?) - group for fragment, starts with #, includes # (#key1???) +// 6 - [^#]+[\$]? - fragment only (key1???) +// {0,1} - means that number of fragments can be only 0 or 1 +// Amount of query is not limited. +var SplitDIDURL, _ = regexp.Compile(`([^/?#]*)?([^?#]*)(\?([^#]*)){0,1}(#([^#]+$)){0,1}$`) +var DIDPathAbemptyRegexp, _ = regexp.Compile(`^[/a-zA-Z0-9\-\.\_\~\%\!\$\&\'\(\)\*\+\,\;\=\:\@]+$`) +var DIDQueryRegexp, _ = regexp.Compile(`^[/a-zA-Z0-9\-\.\_\~\%\!\$\&\'\(\)\*\+\,\;\=\:\@\/\?]*$`) +var DIDFragmentRegexp, _ = regexp.Compile(`^[/a-zA-Z0-9\-\.\_\~\%\!\$\&\'\(\)\*\+\,\;\=\:\@\/\?]*$`) + +//[^/]+(/[^\?#]*)?(\?[^#]+)?(#.+)? +//Old implementation +//var DidForbiddenSymbolsRegexp, _ = regexp.Compile(`^[^#?&/\\]+$`) +// +//func SplitDidUrlIntoDidAndFragment(didUrl string) (string, string) { +// fragments := strings.Split(didUrl, "#") +// return fragments[0], fragments[1] +//} +// +//func IsDidFragment(prefix string, didUrl string) bool { +// if !strings.Contains(didUrl, "#") { +// return false +// } +// +// if didUrl[0] == '#' { +// return true +// } +// +// did, _ := SplitDidUrlIntoDidAndFragment(didUrl) +// return IsValidDid(prefix, did) +//} +// +//func IsFullDidFragment(prefix string, didUrl string) bool { +// if !strings.Contains(didUrl, "#") { +// return false +// } +// +// did, _ := SplitDidUrlIntoDidAndFragment(didUrl) +// return IsValidDid(prefix, did) +//} +// +//func ResolveId(did string, methodId string) string { +// result := methodId +// +// methodDid, methodFragment := SplitDidUrlIntoDidAndFragment(methodId) +// if len(methodDid) == 0 { +// result = did + "#" + methodFragment +// } +// +// return result +//} +// +//func IsNotValidDIDArray(prefix string, array []string) (bool, int) { +// for i, did := range array { +// if !IsValidDid(prefix, did) { +// return true, i +// } +// } +// +// return false, 0 +//} +// +//func IsNotValidDIDArrayFragment(prefix string, array []string) (bool, int) { +// for i, did := range array { +// if !IsDidFragment(prefix, did) { +// return true, i +// } +// } +// +// return false, 0 +//} +// +//func IsValidDid(prefix string, did string) bool { +// if len(did) == 0 { +// return false +// } +// +// if !DidForbiddenSymbolsRegexp.MatchString(did) { +// return false +// } +// +// // FIXME: Empty namespace must be allowed even if namespace is set in state +// // https://github.com/cheqd/cheqd-node/blob/main/architecture/adr-list/adr-002-cheqd-did-method.md#method-specific-identifier +// return strings.HasPrefix(did, prefix) +//} + + +// DID + +func ValidateDID(did string, method string, allowedNamespaces []string) error { + method, namespace, unique_id := SplitDID(did) + + // check method + if method != method { + return ErrStaticDIDBadMethod.Wrap(method) + } + // check namespaces + if !DidNamespaceRegexp.MatchString(namespace) || !utils.Contains(allowedNamespaces, namespace) { + return ErrStaticDIDNamespaceNotAllowed.Wrap(namespace) + } + // check unique-id + err := ValidateUniqueId(unique_id) + + return err +} + +func ValidateUniqueId(unique_id string) error { + // Length should be 16 or 32 symbols + if len(unique_id) != 16 && len(unique_id) != 32 { + return ErrStaticDIDBadUniqueIDLen.Wrap(unique_id) + } + // Base58 check + if !UniqueIDRegexp.MatchString(unique_id) { + return ErrStaticDIDNotBase58ID.Wrap(unique_id) + } + + return nil +} + +func IsValidDID(did string, method string, allowedNamespaces []string) bool { + err := ValidateDID(did, method, allowedNamespaces) + return err == nil +} + +// SplitDID panics if did is not valid +func SplitDID(did string) (method string, namespace string, id string) { + // Example: did:cheqd:testnet:base58str1ng1111 + // match [0] - the whole string + // match [1] - cheqd - method + // match [2] - :testnet + // match [3] - testnet - namespace + // match [4] - base58str1ng1111 - id + match := SplitDIDRegexp.FindStringSubmatch(did) + if len(match) > 0 { + return match[1], match[3], match[4] + } + + return "", "", "" +} + +// SplitDIDUrl panics if did cannot be splitted properly +func SplitDIDUrl(didUrl string) (did string, path string , query string, fragment string) { + match := SplitDIDURL.FindStringSubmatch(didUrl) + return match[1], match[2], match[4], match[6] +} + + +// DIDUrl: did:namespace:id[/path][?query][#fragment] +// TODO: Can path, query, fragment be set at the same time? +// TODO: Is service -> id URI or DIDUrl? What should we support? +// https://www.w3.org/TR/did-core/#did-url-syntax + +func ValidateDIDUrl(didUrl string, method string, allowedNamespaces []string) error { + did, path, query, fragment := SplitDIDUrl(didUrl) + // Validate DID + err := ValidateDID(did, method, allowedNamespaces) + if err != nil { + return err + } + // Validate path + err = ValidatePath(path) + if err != nil { + return err + } + // Validate query + err = ValidateQuery(query) + if err != nil { + return err + } + // Validate fragment + err = ValidateFragment(fragment) + if err != nil { + return err + } + + return nil +} + +func ValidateFragment(fragment string) error { + if !DIDFragmentRegexp.MatchString(fragment) { + return ErrStaticDIDURLFragmentNotValid.Wrap(fragment) + } + return nil +} + +func ValidateQuery(query string) error { + if !DIDQueryRegexp.MatchString(query) { + return ErrStaticDIDURLQueryNotValid.Wrap(query) + } + return nil +} + +func ValidatePath(path string) error { + if !DIDPathAbemptyRegexp.MatchString(path) { + return ErrStaticDIDURLPathAbemptyNotValid.Wrap(path) + } + return nil +} + +func IsValidDIDUrl(didUrl string, method string, allowedNamespaces []string) bool { + err := ValidateDIDUrl(didUrl, method, allowedNamespaces) + + return nil == err +} \ No newline at end of file diff --git a/x/cheqd/types/did_validation_test.go b/x/cheqd/types/did_validation_test.go new file mode 100644 index 000000000..d277f3751 --- /dev/null +++ b/x/cheqd/types/did_validation_test.go @@ -0,0 +1,36 @@ +package types + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestIsDid(t *testing.T) { + cases := []struct { + valid bool + did string + }{ + {true, "did:cheqd:test:wyywywywyw"}, + {true, "did:cheqd:test:wyywywywyw:sdadasda"}, + {false, "did1:cheqd:test:wyywywywyw:sdadasda"}, + {false, "did:cheqd2:test:wyywywywyw:sdadasda"}, + {false, "did:cheqd:test4:wyywywywyw:sdadasda"}, + {false, ""}, + {false, "did:cheqd"}, + {false, "did:cheqd:test"}, + {false, "did:cheqd:test:dsdasdad#weqweqwew"}, + {false, "did:cheqd:test:sdasdasdasd/qeweqweqwee"}, + {false, "did:cheqd:test:sdasdasdasd?=qeweqweqwee"}, + {false, "did:cheqd:test:sdasdasdasd&qeweqweqwee"}, + } + + for _, tc := range cases { + isDid := IsValidDID(tc.did, "cheqd", []string{"testnet"}) + + if tc.valid { + require.True(t, isDid) + } else { + require.False(t, isDid) + } + } +} diff --git a/x/cheqd/types/error.go b/x/cheqd/types/error.go index 8f6d9d658..9a33e46bb 100644 --- a/x/cheqd/types/error.go +++ b/x/cheqd/types/error.go @@ -27,4 +27,12 @@ var ( ErrInternal = sdkerrors.Register(ModuleName, 1500, "internal error") ErrNotImplemented = sdkerrors.Register(ModuleName, 1501, "not implemented") ErrValidatorInitialisation = sdkerrors.Register(ModuleName, 1502, "can't init validator") + // Static validation errors + ErrStaticDIDBadMethod = sdkerrors.Register(ModuleName, 1600, "DID method is not cheqd") + ErrStaticDIDNamespaceNotAllowed = sdkerrors.Register(ModuleName, 1601, "Namespace is not allow for this network") + ErrStaticDIDBadUniqueIDLen = sdkerrors.Register(ModuleName, 1602, "Length of unique ID should be 16 or 32 symbols") + ErrStaticDIDNotBase58ID = sdkerrors.Register(ModuleName, 1603, "Not base58 symbols for unique ID string") + ErrStaticDIDURLPathAbemptyNotValid = sdkerrors.Register(ModuleName, 1604, "There are not allowed symbols in Path.") + ErrStaticDIDURLQueryNotValid = sdkerrors.Register(ModuleName, 1605, "Query part in DIDUrl is not valid.") + ErrStaticDIDURLFragmentNotValid = sdkerrors.Register(ModuleName, 1606, "Fragment part in DIDUrl is not valid.") ) diff --git a/x/cheqd/types/stateValue.go b/x/cheqd/types/stateValue.go index e4a0c043d..ae227d8ef 100644 --- a/x/cheqd/types/stateValue.go +++ b/x/cheqd/types/stateValue.go @@ -40,7 +40,7 @@ func NewMetadataFromContext(ctx sdk.Context) Metadata { func (m StateValue) UnpackData() (StateValueData, error) { value, isOk := m.Data.GetCachedValue().(StateValueData) if !isOk { - return nil, ErrUnpackStateValue.Wrapf("invalid type url: ", m.Data.TypeUrl) + return nil, ErrUnpackStateValue.Wrapf("invalid type url: %s", m.Data.TypeUrl) } return value, nil diff --git a/x/cheqd/types/validate.go b/x/cheqd/types/validate.go index ee37901a7..7eeb847db 100644 --- a/x/cheqd/types/validate.go +++ b/x/cheqd/types/validate.go @@ -1,7 +1,6 @@ package types import ( - "github.com/cheqd/cheqd-node/x/cheqd/utils" "github.com/go-playground/validator/v10" ) @@ -9,21 +8,21 @@ func BuildValidator(DIDMethod string, allowedDIDNamespaces []string) (*validator validate := validator.New() err := validate.RegisterValidation("did", func(fl validator.FieldLevel) bool { - return utils.IsValidDID(fl.Field().String(), DIDMethod, allowedDIDNamespaces) + return IsValidDID(fl.Field().String(), DIDMethod, allowedDIDNamespaces) }) if err != nil { return nil, err } err = validate.RegisterValidation("did-url", func(fl validator.FieldLevel) bool { - return utils.IsValidDIDUrl(fl.Field().String(), DIDMethod, allowedDIDNamespaces) + return IsValidDIDUrl(fl.Field().String(), DIDMethod, allowedDIDNamespaces) }) if err != nil { return nil, err } err = validate.RegisterValidation("did-url-no-path", func(fl validator.FieldLevel) bool { - _, _, _, path, _, _ := utils.SplitDIDUrl(fl.Field().String()) + _, path, _, _ := SplitDIDUrl(fl.Field().String()) return path == "" }) if err != nil { @@ -31,7 +30,7 @@ func BuildValidator(DIDMethod string, allowedDIDNamespaces []string) (*validator } err = validate.RegisterValidation("did-url-no-query", func(fl validator.FieldLevel) bool { - _, _, _, _, query, _ := utils.SplitDIDUrl(fl.Field().String()) + _, _, query, _ := SplitDIDUrl(fl.Field().String()) return query == "" }) if err != nil { @@ -39,7 +38,7 @@ func BuildValidator(DIDMethod string, allowedDIDNamespaces []string) (*validator } err = validate.RegisterValidation("did-url-with-fragment", func(fl validator.FieldLevel) bool { - _, _, _, _, _, fragment := utils.SplitDIDUrl(fl.Field().String()) + _, _, _, fragment := SplitDIDUrl(fl.Field().String()) return fragment != "" }) if err != nil { diff --git a/x/cheqd/utils/did.go b/x/cheqd/utils/did.go deleted file mode 100644 index 97ad9b8fd..000000000 --- a/x/cheqd/utils/did.go +++ /dev/null @@ -1,116 +0,0 @@ -package utils - -//Old implementation -//var DidForbiddenSymbolsRegexp, _ = regexp.Compile(`^[^#?&/\\]+$`) -// -//func SplitDidUrlIntoDidAndFragment(didUrl string) (string, string) { -// fragments := strings.Split(didUrl, "#") -// return fragments[0], fragments[1] -//} -// -//func IsDidFragment(prefix string, didUrl string) bool { -// if !strings.Contains(didUrl, "#") { -// return false -// } -// -// if didUrl[0] == '#' { -// return true -// } -// -// did, _ := SplitDidUrlIntoDidAndFragment(didUrl) -// return IsValidDid(prefix, did) -//} -// -//func IsFullDidFragment(prefix string, didUrl string) bool { -// if !strings.Contains(didUrl, "#") { -// return false -// } -// -// did, _ := SplitDidUrlIntoDidAndFragment(didUrl) -// return IsValidDid(prefix, did) -//} -// -//func ResolveId(did string, methodId string) string { -// result := methodId -// -// methodDid, methodFragment := SplitDidUrlIntoDidAndFragment(methodId) -// if len(methodDid) == 0 { -// result = did + "#" + methodFragment -// } -// -// return result -//} -// -//func IsNotValidDIDArray(prefix string, array []string) (bool, int) { -// for i, did := range array { -// if !IsValidDid(prefix, did) { -// return true, i -// } -// } -// -// return false, 0 -//} -// -//func IsNotValidDIDArrayFragment(prefix string, array []string) (bool, int) { -// for i, did := range array { -// if !IsDidFragment(prefix, did) { -// return true, i -// } -// } -// -// return false, 0 -//} -// -//func IsValidDid(prefix string, did string) bool { -// if len(did) == 0 { -// return false -// } -// -// if !DidForbiddenSymbolsRegexp.MatchString(did) { -// return false -// } -// -// // FIXME: Empty namespace must be allowed even if namespace is set in state -// // https://github.com/cheqd/cheqd-node/blob/main/architecture/adr-list/adr-002-cheqd-did-method.md#method-specific-identifier -// return strings.HasPrefix(did, prefix) -//} - - -// DID - -func ValidateDID(did string, method string, allowedNamespaces []string) error { - // TODO: Implement - return nil -} - -func IsValidDID(did string, method string, allowedNamespaces []string) bool { - err := ValidateDID(did, method, allowedNamespaces) - return err == nil -} - -// SplitDID panics if did is not valid -func SplitDID(did string) (method string, namespace string, id string) { - // TODO: Implement - return "", "", "" -} - -// DIDUrl: did:namespace:id[/path][?query][#fragment] -// TODO: Can path, query, fragment be set at the same time? -// TODO: Is service -> id URI or DIDUrl? What should we support? -// https://www.w3.org/TR/did-core/#did-url-syntax - -func ValidateDIDUrl(didUrl string, method string, allowedNamespaces []string) error { - // TODO: Implement - return nil -} - -func IsValidDIDUrl(didUrl string, method string, allowedNamespaces []string) bool { - err := ValidateDIDUrl(didUrl, method, allowedNamespaces) - return err == nil -} - -// SplitDIDUrl panics if did is not valid -func SplitDIDUrl(didUrl string) (method string, namespace string, id string, path string, query string, fragment string) { - // TODO: Implement - return "", "", "", "", "", "" -} diff --git a/x/cheqd/utils/did_test.go b/x/cheqd/utils/did_test.go deleted file mode 100644 index be8f24f04..000000000 --- a/x/cheqd/utils/did_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package utils - -//func TestIsDid(t *testing.T) { -// cases := []struct { -// valid bool -// did string -// }{ -// {true, "did:cheqd:test:wyywywywyw"}, -// {true, "did:cheqd:test:wyywywywyw:sdadasda"}, -// {false, "did1:cheqd:test:wyywywywyw:sdadasda"}, -// {false, "did:cheqd2:test:wyywywywyw:sdadasda"}, -// {false, "did:cheqd:test4:wyywywywyw:sdadasda"}, -// {false, ""}, -// {false, "did:cheqd"}, -// {false, "did:cheqd:test"}, -// {false, "did:cheqd:test:dsdasdad#weqweqwew"}, -// {false, "did:cheqd:test:sdasdasdasd/qeweqweqwee"}, -// {false, "did:cheqd:test:sdasdasdasd?=qeweqweqwee"}, -// {false, "did:cheqd:test:sdasdasdasd&qeweqweqwee"}, -// } -// -// for _, tc := range cases { -// isDid := IsValidDid("did:cheqd:test:", tc.did) -// -// if tc.valid { -// require.True(t, isDid) -// } else { -// require.False(t, isDid) -// } -// } -//}