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

Add DID static validation without tests #291

Merged
merged 1 commit into from
Mar 3, 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
23 changes: 12 additions & 11 deletions x/cheqd/types/did.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package types

import (
"github.com/cheqd/cheqd-node/x/cheqd/utils"
"github.com/multiformats/go-multibase"
)

var _ StateValueData = &Did{}
Expand All @@ -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 {
Expand All @@ -48,4 +47,6 @@ func FindVerificationMethod(vms []VerificationMethod, id string) (VerificationMe
return VerificationMethod{}, false
}

func FilterVerificationMethods(vms []VerificationMethod, func())
//func FilterVerificationMethods(vms []VerificationMethod, func()) {
//
//}
220 changes: 220 additions & 0 deletions x/cheqd/types/did_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package types

import (
"github.com/cheqd/cheqd-node/x/cheqd/utils"
"regexp"
)

var SplitDIDRegexp, _ = regexp.Compile(`did:([^:]+?)(:([^:]+?))?:([^:]+)$`)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var SplitDIDRegexp, _ = regexp.Compile(`did:([^:]+?)(:([^:]+?))?:([^:]+)$`)
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
askolesov marked this conversation as resolved.
Show resolved Hide resolved
// 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???)
askolesov marked this conversation as resolved.
Show resolved Hide resolved
// 6 - [^#]+[\$]? - fragment only (key1???)
// {0,1} - means that number of fragments can be only 0 or 1
// Amount of query is not limited.
askolesov marked this conversation as resolved.
Show resolved Hide resolved
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) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move regexp matches to the split method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So validate will only compare namespace / method with passed parameters

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it be cool to throw the exception while splitting the DID? From my point of view it's a phase of validation.

return ErrStaticDIDNamespaceNotAllowed.Wrap(namespace)
}
// check unique-id
err := ValidateUniqueId(unique_id)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one too


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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't panic :) Return an error

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]
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's throw an error otherwise


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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's check for matches count

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we check for matches count?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It matches only once for Golang regexps... I checked it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, The last changes in regexp doesn't match in case of fragment being #key123???#####

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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And let's move those checks to the split method as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need to mix validation and splitting?

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
}
36 changes: 36 additions & 0 deletions x/cheqd/types/did_validation_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
8 changes: 8 additions & 0 deletions x/cheqd/types/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
)
2 changes: 1 addition & 1 deletion x/cheqd/types/stateValue.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 5 additions & 6 deletions x/cheqd/types/validate.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,44 @@
package types

import (
"github.com/cheqd/cheqd-node/x/cheqd/utils"
"github.com/go-playground/validator/v10"
)

func BuildValidator(DIDMethod string, allowedDIDNamespaces []string) (*validator.Validate, error) {
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 {
return nil, err
}

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 {
return nil, err
}

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 {
Expand Down
Loading