github.com/hyperledger/aries-framework-go@v0.3.2/pkg/doc/cm/credentialresponse.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  
    14  	"github.com/PaesslerAG/jsonpath"
    15  	"github.com/google/uuid"
    16  
    17  	"github.com/hyperledger/aries-framework-go/pkg/doc/presexch"
    18  	"github.com/hyperledger/aries-framework-go/pkg/doc/verifiable"
    19  )
    20  
    21  const (
    22  	// CredentialResponseAttachmentFormat defines the format type of Credential Response when used as an
    23  	// attachment in the WACI issuance flow.
    24  	// Refer to https://identity.foundation/waci-presentation-exchange/#issuance-2 for more info.
    25  	CredentialResponseAttachmentFormat = "dif/credential-manifest/response@v1.0"
    26  	// CredentialResponsePresentationContext defines the context type of Credential Response when used as part of
    27  	// a presentation attachment in the WACI issuance flow.
    28  	// Refer to https://identity.foundation/waci-presentation-exchange/#issuance-2 for more info.
    29  	CredentialResponsePresentationContext = "https://identity.foundation/credential-manifest/response/v1"
    30  )
    31  
    32  // CredentialResponse represents a Credential Response object as defined in
    33  // https://identity.foundation/credential-manifest/#credential-response.
    34  type CredentialResponse struct {
    35  	ID                             string                `json:"id,omitempty"`          // mandatory property
    36  	ManifestID                     string                `json:"manifest_id,omitempty"` // mandatory property
    37  	ApplicationID                  string                `json:"application_id,omitempty"`
    38  	OutputDescriptorMappingObjects []OutputDescriptorMap `json:"descriptor_map,omitempty"` // mandatory property
    39  }
    40  
    41  // OutputDescriptorMap represents an Output Descriptor Mapping Object as defined in
    42  // https://identity.foundation/credential-manifest/#credential-response.
    43  // It has the same format as the InputDescriptorMapping object from the presexch package, but has a different meaning
    44  // here.
    45  type OutputDescriptorMap presexch.InputDescriptorMapping
    46  
    47  // UnmarshalJSON is the custom unmarshal function gets called automatically when the standard json.Unmarshal is called.
    48  // It also ensures that the given data is a valid CredentialResponse object per the specification.
    49  func (cf *CredentialResponse) UnmarshalJSON(data []byte) error {
    50  	err := cf.standardUnmarshal(data)
    51  	if err != nil {
    52  		return err
    53  	}
    54  
    55  	err = cf.validate()
    56  	if err != nil {
    57  		return fmt.Errorf("invalid Credential Response: %w", err)
    58  	}
    59  
    60  	return nil
    61  }
    62  
    63  // ResolveDescriptorMaps resolves Verifiable Credentials based on this Credential Response's descriptor maps.
    64  // This function looks at each OutputDescriptorMap's path property and checks for that path in the given JSON data,
    65  // which is expected to be from
    66  // the attachment of an Issue Credential message (i.e. issuecredential.IssueCredentialV3.Attachments[i].Data.JSON).
    67  // See the TestCredentialResponse_ResolveDescriptorMap method for examples.
    68  // If a VC is found at that path's location, then it is added to the array of VCs that will be returned by this method.
    69  // Once all OutputDescriptorMaps are done being scanned, the array of VCs will be returned.
    70  func (cf *CredentialResponse) ResolveDescriptorMaps(jsonDataFromAttachment interface{},
    71  	parseCredentialOpts ...verifiable.CredentialOpt) ([]verifiable.Credential, error) {
    72  	// The jsonpath library needs a map[string]interface{}.
    73  	// The issuecredential.IssueCredentialV3.Attachments[i].Data.JSON object (expected to be passed in here) is of type
    74  	// interface{}, but the Go unmarshaler should have set it to a map[string]interface{}.
    75  	jsonDataFromAttachmentAsMap, ok := jsonDataFromAttachment.(map[string]interface{})
    76  	if !ok {
    77  		return nil, errors.New("the given JSON data could not be asserted as a map[string]interface{}")
    78  	}
    79  
    80  	verifiableCredentials := make([]verifiable.Credential, len(cf.OutputDescriptorMappingObjects))
    81  
    82  	for i, descriptorMap := range cf.OutputDescriptorMappingObjects {
    83  		vc, err := resolveDescriptorMap(descriptorMap, jsonDataFromAttachmentAsMap, parseCredentialOpts)
    84  		if err != nil {
    85  			return nil, fmt.Errorf("failed to resolve descriptor map at index %d: %w", i, err)
    86  		}
    87  
    88  		verifiableCredentials[i] = *vc
    89  	}
    90  
    91  	return verifiableCredentials, nil
    92  }
    93  
    94  func (cf *CredentialResponse) standardUnmarshal(data []byte) error {
    95  	// The type alias below is used as to allow the standard json.Unmarshal to be called within a custom unmarshal
    96  	// function without causing infinite recursion. See https://stackoverflow.com/a/43178272 for more information.
    97  	type credentialResponseWithoutMethods *CredentialResponse
    98  
    99  	err := json.Unmarshal(data, credentialResponseWithoutMethods(cf))
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	return nil
   105  }
   106  
   107  func (cf *CredentialResponse) validate() error {
   108  	if cf.ID == "" {
   109  		return errors.New("missing ID")
   110  	}
   111  
   112  	if cf.ManifestID == "" {
   113  		return errors.New("missing manifest ID")
   114  	}
   115  
   116  	return nil
   117  }
   118  
   119  // presentCredentialResponseOpts holds options for the PresentCredentialResponse method.
   120  type presentCredentialResponseOpts struct {
   121  	existingPresentation    verifiable.Presentation
   122  	existingPresentationSet bool
   123  }
   124  
   125  // PresentCredentialResponseOpt is an option for the PresentCredentialResponse method.
   126  type PresentCredentialResponseOpt func(opts *presentCredentialResponseOpts)
   127  
   128  // WithExistingPresentationForPresentCredentialResponse is an option for the PresentCredentialResponse method
   129  // that allows Credential Response data to be added to an existing Presentation. The existing Presentation
   130  // should not already have Credential Response data.
   131  func WithExistingPresentationForPresentCredentialResponse(
   132  	presentation *verifiable.Presentation) PresentCredentialResponseOpt {
   133  	return func(opts *presentCredentialResponseOpts) {
   134  		opts.existingPresentation = *presentation
   135  		opts.existingPresentationSet = true
   136  	}
   137  }
   138  
   139  // PresentCredentialResponse creates a basic Presentation (without proofs) with Credential Response data based
   140  // on credentialManifest. The WithExistingPresentationForPresentCredentialResponse can be used to add the Credential
   141  // Response data to an existing Presentation object instead. Note that any existing proofs are not updated.
   142  // Note also the following assumptions/limitations of this method:
   143  // 1. The format of all credentials is assumed to be ldp_vc.
   144  // 2. The location of the Verifiable Credentials is assumed to be an array at the root under a field called
   145  //    "verifiableCredential".
   146  // 3. The Verifiable Credentials in the presentation is assumed to be in the same order as the Output Descriptors in
   147  //    the Credential Manifest.
   148  func PresentCredentialResponse(credentialManifest *CredentialManifest,
   149  	opts ...PresentCredentialResponseOpt) (*verifiable.Presentation, error) {
   150  	if credentialManifest == nil {
   151  		return nil, errors.New("credential manifest argument cannot be nil")
   152  	}
   153  
   154  	appliedOptions := getPresentCredentialResponseOpts(opts)
   155  
   156  	var presentation verifiable.Presentation
   157  
   158  	if appliedOptions.existingPresentationSet {
   159  		presentation = appliedOptions.existingPresentation
   160  	} else {
   161  		newPresentation, err := verifiable.NewPresentation()
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  
   166  		presentation = *newPresentation
   167  	}
   168  
   169  	presentation.Context = append(presentation.Context,
   170  		"https://identity.foundation/credential-manifest/response/v1")
   171  	presentation.Type = append(presentation.Type, "CredentialResponse")
   172  
   173  	outputDescriptorMappingObjects := make([]OutputDescriptorMap, len(credentialManifest.OutputDescriptors))
   174  
   175  	for i := range credentialManifest.OutputDescriptors {
   176  		outputDescriptorMappingObjects[i].ID = credentialManifest.OutputDescriptors[i].ID
   177  		outputDescriptorMappingObjects[i].Format = "ldp_vc"
   178  		outputDescriptorMappingObjects[i].Path = fmt.Sprintf("$.verifiableCredential[%d]", i)
   179  	}
   180  
   181  	response := CredentialResponse{
   182  		ID:                             uuid.New().String(),
   183  		ManifestID:                     credentialManifest.ID,
   184  		OutputDescriptorMappingObjects: outputDescriptorMappingObjects,
   185  	}
   186  
   187  	if presentation.CustomFields == nil {
   188  		presentation.CustomFields = make(map[string]interface{})
   189  	}
   190  
   191  	presentation.CustomFields["credential_response"] = response
   192  
   193  	return &presentation, nil
   194  }
   195  
   196  func getPresentCredentialResponseOpts(opts []PresentCredentialResponseOpt) *presentCredentialResponseOpts {
   197  	processedOptions := &presentCredentialResponseOpts{}
   198  
   199  	for _, opt := range opts {
   200  		opt(processedOptions)
   201  	}
   202  
   203  	return processedOptions
   204  }
   205  
   206  func resolveDescriptorMap(descriptorMap OutputDescriptorMap, jsonDataFromAttachmentAsMap map[string]interface{},
   207  	parseCredentialOpts []verifiable.CredentialOpt) (*verifiable.Credential, error) {
   208  	vcRaw, err := jsonpath.Get(descriptorMap.Path, jsonDataFromAttachmentAsMap)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  
   213  	vcBytes, err := json.Marshal(vcRaw)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  
   218  	vc, err := verifiable.ParseCredential(vcBytes, parseCredentialOpts...)
   219  	if err != nil {
   220  		return nil, fmt.Errorf("failed to parse credential: %w", err)
   221  	}
   222  
   223  	return vc, nil
   224  }