github.com/hyperledger/aries-framework-go@v0.3.2/pkg/doc/cm/resolve.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package cm
     8  
     9  import (
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"strings"
    14  
    15  	"github.com/PaesslerAG/gval"
    16  	"github.com/PaesslerAG/jsonpath"
    17  
    18  	"github.com/hyperledger/aries-framework-go/pkg/doc/verifiable"
    19  )
    20  
    21  // ResolvedProperty contains resolved result for each resolved property.
    22  type ResolvedProperty struct {
    23  	Schema Schema      `json:"schema"`
    24  	Label  string      `json:"label"`
    25  	Value  interface{} `json:"value"`
    26  }
    27  
    28  // ResolvedDescriptor typically represents results of resolving manifests by credential response.
    29  // Typically represents a DataDisplayDescriptor that's had its various "template" fields resolved
    30  // into concrete values based on a Verifiable Credential.
    31  type ResolvedDescriptor struct {
    32  	DescriptorID string              `json:"descriptor_id"`
    33  	Title        string              `json:"title,omitempty"`
    34  	Subtitle     string              `json:"subtitle,omitempty"`
    35  	Description  string              `json:"description,omitempty"`
    36  	Styles       *Styles             `json:"styles,omitempty"`
    37  	Properties   []*ResolvedProperty `json:"properties,omitempty"`
    38  }
    39  
    40  // resolveCredOpts contains options to provide credential to resolve manifest.
    41  type resolveCredOpts struct {
    42  	credential    *verifiable.Credential
    43  	rawCredential json.RawMessage
    44  }
    45  
    46  // CredentialToResolveOption is an option to provide credential to resolve manifest.
    47  type CredentialToResolveOption func(opts *resolveCredOpts)
    48  
    49  // CredentialToResolve is an option to provide verifiable credential instance to resolve.
    50  func CredentialToResolve(credential *verifiable.Credential) CredentialToResolveOption {
    51  	return func(opts *resolveCredOpts) {
    52  		opts.credential = credential
    53  	}
    54  }
    55  
    56  // RawCredentialToResolve is an option to provide raw JSON bytes of verifiable credential to resolve.
    57  func RawCredentialToResolve(raw json.RawMessage) CredentialToResolveOption {
    58  	return func(opts *resolveCredOpts) {
    59  		opts.rawCredential = raw
    60  	}
    61  }
    62  
    63  // ResolveResponse resolves given credential response and returns results.
    64  // Currently supports only 'ldp_vc' format of response credentials.
    65  func (cm *CredentialManifest) ResolveResponse(response *verifiable.Presentation) ([]*ResolvedDescriptor, error) { //nolint:funlen,gocyclo,lll
    66  	var results []*ResolvedDescriptor
    67  
    68  	credentialResponseMap, ok := lookUpMap(response.CustomFields, "credential_response")
    69  	if !ok {
    70  		return nil, errors.New("invalid credential response")
    71  	}
    72  
    73  	if manifestID, k := credentialResponseMap["manifest_id"]; !k || cm.ID != manifestID {
    74  		return nil, errors.New("credential response not matching")
    75  	}
    76  
    77  	descriptorMaps, ok := lookUpArray(credentialResponseMap, "descriptor_map")
    78  	if !ok {
    79  		return nil, errors.New("invalid descriptor map")
    80  	}
    81  
    82  	if len(descriptorMaps) == 0 {
    83  		return results, nil
    84  	}
    85  
    86  	outputDescriptors := mapDescriptors(cm)
    87  
    88  	builder := gval.Full(jsonpath.PlaceholderExtension())
    89  
    90  	vpBits, err := response.MarshalJSON()
    91  	if err != nil {
    92  		return nil, fmt.Errorf("failed to marshal vp: %w", err)
    93  	}
    94  
    95  	typelessVP := interface{}(nil)
    96  
    97  	err = json.Unmarshal(vpBits, &typelessVP)
    98  	if err != nil {
    99  		return nil, fmt.Errorf("failed to unmarshal vp: %w", err)
   100  	}
   101  
   102  	for _, descriptorMap := range descriptorMaps {
   103  		descriptor, ok := descriptorMap.(map[string]interface{})
   104  		if !ok {
   105  			return nil, errors.New("invalid descriptor format")
   106  		}
   107  
   108  		id, ok := lookUpString(descriptor, "id")
   109  		if !ok {
   110  			return nil, errors.New("invalid descriptor ID")
   111  		}
   112  
   113  		outputDescriptor, ok := outputDescriptors[id]
   114  		if !ok {
   115  			return nil, errors.New("unable to find matching output descriptor from manifest")
   116  		}
   117  
   118  		if format, k := lookUpString(descriptor, "format"); !k || format != "ldp_vc" {
   119  			// currently, only ldp_vc format is supported
   120  			continue
   121  		}
   122  
   123  		path, ok := lookUpString(descriptor, "path")
   124  		if !ok {
   125  			return nil, fmt.Errorf("invalid credential path in descriptor '%s'", id)
   126  		}
   127  
   128  		credential, err := selectVCByPath(builder, typelessVP, path)
   129  		if err != nil {
   130  			return nil, fmt.Errorf("failed to select vc from descriptor: %w", err)
   131  		}
   132  
   133  		resolved, err := resolveOutputDescriptor(outputDescriptor, credential)
   134  		if err != nil {
   135  			return nil, fmt.Errorf("failed to resolve credential by descriptor: %w", err)
   136  		}
   137  
   138  		results = append(results, resolved)
   139  	}
   140  
   141  	return results, nil
   142  }
   143  
   144  // ResolveCredential resolves given credential and returns results.
   145  func (cm *CredentialManifest) ResolveCredential(descriptorID string, credential CredentialToResolveOption) (*ResolvedDescriptor, error) { //nolint:lll
   146  	opts := &resolveCredOpts{}
   147  
   148  	if credential != nil {
   149  		credential(opts)
   150  	}
   151  
   152  	var err error
   153  
   154  	var vcmap map[string]interface{}
   155  
   156  	switch {
   157  	case opts.credential != nil:
   158  		opts.rawCredential, err = opts.credential.MarshalJSON()
   159  		if err != nil {
   160  			return nil, err
   161  		}
   162  
   163  		fallthrough
   164  	case len(opts.rawCredential) > 0:
   165  		if opts.rawCredential[0] != '{' {
   166  			// try to parse as jwt vc
   167  			var jwtCred []byte
   168  
   169  			jwtCred, err = verifiable.JWTVCToJSON(opts.rawCredential)
   170  			if err == nil {
   171  				opts.rawCredential = jwtCred
   172  			}
   173  		}
   174  
   175  		err = json.Unmarshal(opts.rawCredential, &vcmap)
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  	default:
   180  		return nil, errors.New("credential to resolve is not provided")
   181  	}
   182  
   183  	// find matching descriptor and resolve.
   184  	for _, descriptor := range cm.OutputDescriptors {
   185  		if descriptor.ID == descriptorID {
   186  			return resolveOutputDescriptor(descriptor, vcmap)
   187  		}
   188  	}
   189  
   190  	return nil, errors.New("unable to find matching descriptor")
   191  }
   192  
   193  func resolveOutputDescriptor(outputDescriptor *OutputDescriptor,
   194  	vc map[string]interface{}) (*ResolvedDescriptor, error) {
   195  	var resolved ResolvedDescriptor
   196  
   197  	staticDisplayMappings, err := resolveStaticDisplayMappingObjects(outputDescriptor, vc)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	resolved.DescriptorID = outputDescriptor.ID
   203  	resolved.Title = staticDisplayMappings.title
   204  	resolved.Subtitle = staticDisplayMappings.subtitle
   205  	resolved.Description = staticDisplayMappings.description
   206  	resolved.Styles = outputDescriptor.Styles
   207  
   208  	resolved.Properties, err =
   209  		resolveDescriptorProperties(outputDescriptor.Display.Properties, vc)
   210  	if err != nil {
   211  		return nil, fmt.Errorf("failed to resolve properties: %w", err)
   212  	}
   213  
   214  	return &resolved, nil
   215  }
   216  
   217  func resolveStaticDisplayMappingObjects(outputDescriptor *OutputDescriptor,
   218  	vc map[string]interface{}) (staticDisplayMappingObjects, error) {
   219  	title, err := resolveDisplayMappingObject(outputDescriptor.Display.Title, vc)
   220  	if err != nil {
   221  		return staticDisplayMappingObjects{}, fmt.Errorf("failed to resolve title display mapping object: %w", err)
   222  	}
   223  
   224  	subtitle, err := resolveDisplayMappingObject(outputDescriptor.Display.Subtitle, vc)
   225  	if err != nil {
   226  		return staticDisplayMappingObjects{}, fmt.Errorf("failed to resolve subtitle display mapping object: %w", err)
   227  	}
   228  
   229  	description, err := resolveDisplayMappingObject(outputDescriptor.Display.Description, vc)
   230  	if err != nil {
   231  		return staticDisplayMappingObjects{}, fmt.Errorf("failed to resolve description display mapping object: %w", err)
   232  	}
   233  
   234  	return staticDisplayMappingObjects{
   235  		title:       fmt.Sprintf("%v", title),
   236  		subtitle:    fmt.Sprintf("%v", subtitle),
   237  		description: fmt.Sprintf("%v", description),
   238  	}, nil
   239  }
   240  
   241  func resolveDescriptorProperties(properties []*LabeledDisplayMappingObject,
   242  	vc map[string]interface{}) ([]*ResolvedProperty, error) {
   243  	var resolvedProperties []*ResolvedProperty
   244  
   245  	for i := range properties {
   246  		var err error
   247  
   248  		value, err := resolveDisplayMappingObject(&properties[i].DisplayMappingObject, vc)
   249  		if err != nil {
   250  			return nil, fmt.Errorf("failed to resolve the display mapping object for the property with label '%s': %w", properties[i].Label, err) // nolint:lll
   251  		}
   252  
   253  		resolvedProperties = append(resolvedProperties, &ResolvedProperty{
   254  			Label:  properties[i].Label,
   255  			Schema: properties[i].Schema,
   256  			Value:  value,
   257  		})
   258  	}
   259  
   260  	return resolvedProperties, nil
   261  }
   262  
   263  func resolveDisplayMappingObject(displayMappingObject *DisplayMappingObject,
   264  	vc map[string]interface{}) (interface{}, error) {
   265  	if len(displayMappingObject.Paths) > 0 {
   266  		resolvedValue, err := resolveJSONPathsUsingVC(displayMappingObject.Paths, displayMappingObject.Fallback, vc)
   267  		return resolvedValue, err
   268  	}
   269  
   270  	return displayMappingObject.Text, nil
   271  }
   272  
   273  func resolveJSONPathsUsingVC(paths []string, fallback string, vc map[string]interface{}) (interface{}, error) {
   274  	for _, path := range paths {
   275  		resolvedValue, err := jsonpath.Get(path, vc)
   276  		if err != nil {
   277  			if strings.HasPrefix(err.Error(), "unknown key") {
   278  				continue
   279  			}
   280  
   281  			return nil, err
   282  		}
   283  
   284  		return resolvedValue, nil
   285  	}
   286  
   287  	return fallback, nil
   288  }