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 }