github.com/hyperledger/aries-framework-go@v0.3.2/pkg/wallet/query.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package wallet
     8  
     9  import (
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"strings"
    14  
    15  	"github.com/piprate/json-gold/ld"
    16  
    17  	"github.com/hyperledger/aries-framework-go/pkg/doc/presexch"
    18  	"github.com/hyperledger/aries-framework-go/pkg/doc/verifiable"
    19  )
    20  
    21  // Query errors.
    22  var (
    23  	// ErrQueryNoResultFound error when no records found from query.
    24  	ErrQueryNoResultFound = errors.New("no result found")
    25  )
    26  
    27  // QueryType is type of query supported by wallet implementation
    28  // More details can be found here : https://w3c-ccg.github.io/universal-wallet-interop-spec/#query
    29  type QueryType int
    30  
    31  const (
    32  	// QueryByExample https://w3c-ccg.github.io/vp-request-spec/#query-by-example
    33  	QueryByExample QueryType = iota + 1
    34  	// QueryByFrame https://github.com/w3c-ccg/vp-request-spec/issues/8
    35  	QueryByFrame
    36  	// PresentationExchange https://identity.foundation/presentation-exchange/
    37  	PresentationExchange
    38  	// DIDAuth https://w3c-ccg.github.io/vp-request-spec/#did-authentication-request
    39  	DIDAuth
    40  )
    41  
    42  // Name returns name of the query.
    43  func (q QueryType) Name() string {
    44  	return []string{"", "QueryByExample", "QueryByFrame", "PresentationExchange"}[q]
    45  }
    46  
    47  // GetQueryType returns QueryType instance for given string query type.
    48  func GetQueryType(name string) (QueryType, error) {
    49  	switch strings.ToLower(name) {
    50  	case "querybyexample":
    51  		return QueryByExample, nil
    52  	case "querybyframe":
    53  		return QueryByFrame, nil
    54  	case "presentationexchange":
    55  		return PresentationExchange, nil
    56  	case "didauth":
    57  		return DIDAuth, nil
    58  	default:
    59  		return 0, fmt.Errorf("unsupported query type, supported types - (%s, %s, %s)",
    60  			QueryByExample.Name(), QueryByFrame.Name(), PresentationExchange.Name())
    61  	}
    62  }
    63  
    64  // Query performs wallet credential queries, currently supporting all the QueryTypes defined in QueryType.
    65  type Query struct {
    66  	publicKeyFetcher verifiable.PublicKeyFetcher
    67  	documentLoader   ld.DocumentLoader
    68  	params           []*QueryParams
    69  }
    70  
    71  // NewQuery returns new wallet query instance.
    72  func NewQuery(pkFetcher verifiable.PublicKeyFetcher, loader ld.DocumentLoader, queries ...*QueryParams) *Query {
    73  	return &Query{publicKeyFetcher: pkFetcher, documentLoader: loader, params: queries}
    74  }
    75  
    76  // PerformQuery performs credential query on given credentials.
    77  // nolint:gocyclo
    78  func (q *Query) PerformQuery(credentials map[string]json.RawMessage) ([]*verifiable.Presentation, error) {
    79  	if len(credentials) == 0 {
    80  		return nil, ErrQueryNoResultFound
    81  	}
    82  
    83  	vcs, err := q.parseCredentialContents(credentials)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	// using map to remove duplicates from results
    89  	credResults := make(map[*verifiable.Credential]struct{}, len(credentials))
    90  
    91  	var results []*verifiable.Presentation
    92  
    93  	for _, param := range q.params {
    94  		qType, err := GetQueryType(param.Type)
    95  		if err != nil {
    96  			return nil, err
    97  		}
    98  
    99  		credentials, err := q.getCredentials(qType, vcs, param.Query...)
   100  		if err != nil {
   101  			return nil, err
   102  		}
   103  
   104  		for _, cred := range credentials {
   105  			credResults[cred] = struct{}{}
   106  		}
   107  
   108  		presentations, err := q.getPresentation(qType, vcs, param.Query...)
   109  		if err != nil {
   110  			return nil, err
   111  		}
   112  
   113  		results = append(results, presentations...)
   114  	}
   115  
   116  	if len(credResults) > 0 {
   117  		presentation, err := preparePresentation(credResults)
   118  		if err != nil {
   119  			return nil, err
   120  		}
   121  
   122  		results = append(results, presentation)
   123  	}
   124  
   125  	if len(results) == 0 {
   126  		return nil, ErrQueryNoResultFound
   127  	}
   128  
   129  	return results, nil
   130  }
   131  
   132  // getCredentials runs given query and returns query result as credentials.
   133  func (q *Query) getCredentials(qType QueryType, vcs []*verifiable.Credential, query ...json.RawMessage) ([]*verifiable.Credential, error) { // nolint: lll
   134  	switch qType {
   135  	case QueryByExample:
   136  		return queryByExample(vcs, query...)
   137  	case QueryByFrame:
   138  		return queryByFrame(vcs, q.publicKeyFetcher, q.documentLoader, query...)
   139  	default:
   140  		return []*verifiable.Credential{}, nil
   141  	}
   142  }
   143  
   144  // getPresentation runs given query and returns query result as presentation.
   145  func (q *Query) getPresentation(qType QueryType, vcs []*verifiable.Credential, query ...json.RawMessage) ([]*verifiable.Presentation, error) { // nolint: lll
   146  	switch qType {
   147  	case PresentationExchange:
   148  		return q.queryByPresentationExchange(vcs, query...)
   149  	case DIDAuth:
   150  		return didAuth()
   151  	default:
   152  		return []*verifiable.Presentation{}, nil
   153  	}
   154  }
   155  
   156  type credentialMatcher struct {
   157  	example *ExampleDefinition
   158  	frame   *QueryByFrameDefinition
   159  }
   160  
   161  func (cm *credentialMatcher) MatchExample(credential *verifiable.Credential) bool { // nolint: funlen,gocognit,gocyclo
   162  	// Match context
   163  	if !contains(credential.Context, cm.example.Context) {
   164  		return false
   165  	}
   166  
   167  	// Match type
   168  	if !contains(credential.Types, cm.example.Type) {
   169  		return false
   170  	}
   171  
   172  	// Issuer match
   173  	issuerMatched := len(cm.example.TrustedIssuer) == 0
   174  
   175  	for _, ti := range cm.example.TrustedIssuer {
   176  		matched := strings.EqualFold(credential.Issuer.ID, ti.Issuer)
   177  
   178  		// if not matched & this trusted issuer required then return false
   179  		if !matched && ti.Required {
   180  			return false
   181  		}
   182  
   183  		issuerMatched = issuerMatched || matched
   184  	}
   185  
   186  	// if none matched then return false
   187  	if !issuerMatched {
   188  		return false
   189  	}
   190  
   191  	// Match Credential Schema ID
   192  	if schemaID, ok := cm.example.CredentialSchema["id"]; ok {
   193  		schemaIDMatched := false
   194  
   195  		for _, schema := range credential.Schemas {
   196  			if schemaID == schema.ID {
   197  				schemaIDMatched = true
   198  			}
   199  		}
   200  
   201  		if !schemaIDMatched {
   202  			return false
   203  		}
   204  	}
   205  
   206  	// Match Credential Schema Type
   207  	if schemaType, ok := cm.example.CredentialSchema["type"]; ok {
   208  		schemaTypeMatched := false
   209  
   210  		for _, schema := range credential.Schemas {
   211  			if strings.EqualFold(schemaType, schema.Type) {
   212  				schemaTypeMatched = true
   213  			}
   214  		}
   215  
   216  		if !schemaTypeMatched {
   217  			return false
   218  		}
   219  	}
   220  
   221  	// Match credential subject
   222  	if cm.example.CredentialSubject != nil {
   223  		credSubjID, err := verifiable.SubjectID(credential.Subject)
   224  		if err != nil {
   225  			return false
   226  		}
   227  
   228  		if querySubjectID, ok := cm.example.CredentialSubject["id"]; ok && credSubjID != querySubjectID {
   229  			return false
   230  		}
   231  	}
   232  
   233  	return true
   234  }
   235  
   236  func (cm *credentialMatcher) MatchFrame(credential *verifiable.Credential) bool {
   237  	// Issuer match
   238  	issuerMatched := len(cm.frame.TrustedIssuer) == 0
   239  
   240  	for _, ti := range cm.frame.TrustedIssuer {
   241  		matched := strings.EqualFold(credential.Issuer.ID, ti.Issuer)
   242  
   243  		// if not matched & this trusted issuer required then return false
   244  		if !matched && ti.Required {
   245  			return false
   246  		}
   247  
   248  		issuerMatched = issuerMatched || matched
   249  	}
   250  
   251  	// also check if VC has bbs signature
   252  	for _, proof := range credential.Proofs {
   253  		if proof["type"] == BbsBlsSignature2020 {
   254  			return true
   255  		}
   256  	}
   257  
   258  	return false
   259  }
   260  
   261  func preparePresentation(credentials map[*verifiable.Credential]struct{}) (*verifiable.Presentation, error) {
   262  	var opts []verifiable.CreatePresentationOpt
   263  
   264  	for cred := range credentials {
   265  		opts = append(opts, verifiable.WithCredentials(cred))
   266  	}
   267  
   268  	return verifiable.NewPresentation(opts...)
   269  }
   270  
   271  // proof check is disabled while resolving credentials from raw bytes. A wallet implementation may or may not choose to
   272  // show credentials as verified. If a wallet implementation chooses to show credentials as 'verified' it
   273  // may to call 'wallet.Verify()' for each credential being presented.
   274  // (More details can be found in issue #2677).
   275  func (q *Query) parseCredentialContents(raws map[string]json.RawMessage) ([]*verifiable.Credential, error) {
   276  	var result []*verifiable.Credential
   277  
   278  	for _, raw := range raws {
   279  		vc, err := verifiable.ParseCredential(raw, verifiable.WithDisabledProofCheck(),
   280  			verifiable.WithJSONLDDocumentLoader(q.documentLoader))
   281  		if err != nil {
   282  			return nil, err
   283  		}
   284  
   285  		result = append(result, vc)
   286  	}
   287  
   288  	return result, nil
   289  }
   290  
   291  func parseQueryByExample(defs ...json.RawMessage) ([]*QueryByExampleDefinition, error) {
   292  	definitions := make([]*QueryByExampleDefinition, len(defs))
   293  
   294  	for i, def := range defs {
   295  		var query QueryByExampleDefinition
   296  
   297  		err := json.Unmarshal(def, &query)
   298  		if err != nil {
   299  			return nil, err
   300  		}
   301  
   302  		if query.Example == nil {
   303  			return nil, errors.New("invalid QueryByExample, 'example' is required")
   304  		}
   305  
   306  		if query.Example.Context == nil {
   307  			return nil, errors.New("invalid QueryByExample, 'example.context' is required")
   308  		}
   309  
   310  		if isEmpty(query.Example.Type) {
   311  			return nil, errors.New("invalid QueryByExample, 'example.type' is required")
   312  		}
   313  
   314  		definitions[i] = &query
   315  	}
   316  
   317  	return definitions, nil
   318  }
   319  
   320  func parseQueryByFrame(defs ...json.RawMessage) ([]*QueryByFrameDefinition, error) {
   321  	definitions := make([]*QueryByFrameDefinition, len(defs))
   322  
   323  	for i, def := range defs {
   324  		var query QueryByFrameDefinition
   325  
   326  		err := json.Unmarshal(def, &query)
   327  		if err != nil {
   328  			return nil, err
   329  		}
   330  
   331  		if len(query.Frame) == 0 {
   332  			return nil, errors.New("invalid QueryByFrame, 'frame' is required")
   333  		}
   334  
   335  		definitions[i] = &query
   336  	}
   337  
   338  	return definitions, nil
   339  }
   340  
   341  func queryByExample(vcs []*verifiable.Credential, defs ...json.RawMessage) ([]*verifiable.Credential, error) {
   342  	definitions, err := parseQueryByExample(defs...)
   343  	if err != nil {
   344  		return nil, fmt.Errorf("failed to parse QueryByExample query: %w", err)
   345  	}
   346  
   347  	var result []*verifiable.Credential
   348  
   349  	for _, vc := range vcs {
   350  		for _, definition := range definitions {
   351  			matcher := &credentialMatcher{example: definition.Example}
   352  
   353  			if matcher.MatchExample(vc) {
   354  				result = append(result, vc)
   355  			}
   356  		}
   357  	}
   358  
   359  	return result, nil
   360  }
   361  
   362  func queryByFrame(vcs []*verifiable.Credential, publicKeyFetcher verifiable.PublicKeyFetcher, loader ld.DocumentLoader,
   363  	defs ...json.RawMessage) ([]*verifiable.Credential, error) {
   364  	definitions, err := parseQueryByFrame(defs...)
   365  	if err != nil {
   366  		return nil, fmt.Errorf("failed to parse QueryByFrame query: %w", err)
   367  	}
   368  
   369  	var result []*verifiable.Credential
   370  
   371  	for _, vc := range vcs {
   372  		for _, definition := range definitions {
   373  			matcher := &credentialMatcher{frame: definition}
   374  
   375  			// match trusted issuer
   376  			if !matcher.MatchFrame(vc) {
   377  				continue
   378  			}
   379  
   380  			// match frame
   381  			bbsVC, err := vc.GenerateBBSSelectiveDisclosure(definition.Frame, nil,
   382  				verifiable.WithPublicKeyFetcher(publicKeyFetcher),
   383  				verifiable.WithJSONLDDocumentLoader(loader))
   384  			if err != nil {
   385  				continue
   386  			}
   387  
   388  			result = append(result, bbsVC)
   389  		}
   390  	}
   391  
   392  	return result, nil
   393  }
   394  
   395  // queryByPresentationExchange generates presentation submission result based on given query.
   396  func (q *Query) queryByPresentationExchange(vcs []*verifiable.Credential, defs ...json.RawMessage) ([]*verifiable.Presentation, error) { // nolint:lll
   397  	var results []*verifiable.Presentation
   398  
   399  	for _, def := range defs {
   400  		var presDefinition presexch.PresentationDefinition
   401  
   402  		err := json.Unmarshal(def, &presDefinition)
   403  		if err != nil {
   404  			return nil, err
   405  		}
   406  
   407  		result, err := presDefinition.CreateVP(vcs, q.documentLoader, verifiable.WithDisabledProofCheck(),
   408  			verifiable.WithJSONLDDocumentLoader(q.documentLoader))
   409  
   410  		if errors.Is(err, presexch.ErrNoCredentials) {
   411  			continue
   412  		}
   413  
   414  		if err != nil {
   415  			return nil, err
   416  		}
   417  
   418  		results = append(results, result)
   419  	}
   420  
   421  	return results, nil
   422  }
   423  
   424  // didAuth prepares presentation for DID authorization.
   425  func didAuth() ([]*verifiable.Presentation, error) {
   426  	presentation, err := verifiable.NewPresentation()
   427  	if err != nil {
   428  		return nil, err
   429  	}
   430  
   431  	return []*verifiable.Presentation{presentation}, nil
   432  }
   433  
   434  func contains(slice []string, item interface{}) bool {
   435  	set := make(map[string]struct{}, len(slice))
   436  	for _, s := range slice {
   437  		set[s] = struct{}{}
   438  	}
   439  
   440  	switch itemVal := item.(type) {
   441  	case string:
   442  		_, ok := set[itemVal]
   443  
   444  		return ok
   445  	case []interface{}:
   446  		for _, val := range itemVal {
   447  			_, ok := set[val.(string)]
   448  			if !ok {
   449  				return false
   450  			}
   451  		}
   452  
   453  		return true
   454  	case []string:
   455  		for _, val := range itemVal {
   456  			_, ok := set[val]
   457  			if !ok {
   458  				return false
   459  			}
   460  		}
   461  
   462  		return true
   463  	default:
   464  		return false
   465  	}
   466  }
   467  
   468  func isEmpty(item interface{}) bool {
   469  	switch itemVal := item.(type) {
   470  	case string:
   471  		return itemVal == ""
   472  	case []interface{}:
   473  		return len(itemVal) == 0
   474  	case []string:
   475  		return len(itemVal) == 0
   476  	default:
   477  		return itemVal == nil
   478  	}
   479  }