Skip to content

Commit

Permalink
Merge pull request #291 from cheqd/DEV-705-did-validation
Browse files Browse the repository at this point in the history
Add DID static validation without tests
  • Loading branch information
askolesov authored Mar 3, 2022
2 parents 661b60d + 7ee98ae commit 00e222a
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 165 deletions.
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:([^:]+?)(:([^:]+?))?:([^:]+)$`)
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
}
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

0 comments on commit 00e222a

Please sign in to comment.