Skip to content
This repository has been archived by the owner on Mar 27, 2024. It is now read-only.

fix: credential manifest omit optional properties #3336

Merged
merged 1 commit into from
Aug 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions pkg/doc/cm/credentialapplication.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ const (
// See https://github.com/decentralized-identity/credential-manifest/issues/73 for more information about this name
// overloading.
type CredentialApplication struct {
ID string `json:"id,omitempty"`
ID string `json:"id,omitempty"` // mandatory property
// The value of this property MUST be the ID of a valid Credential Manifest.
ManifestID string `json:"manifest_id,omitempty"`
ManifestID string `json:"manifest_id,omitempty"` // mandatory property
// Must be a subset of the format property of the CredentialManifest that this CredentialApplication is related to
Format presexch.Format `json:"format,omitempty"`
Format presexch.Format `json:"format,omitempty"` // mandatory property
}

// UnmarshalAndValidateAgainstCredentialManifest unmarshals the credentialApplicationBytes into a CredentialApplication
Expand Down Expand Up @@ -187,7 +187,7 @@ func (ca *CredentialApplication) validateFormatAgainstCredManifestFormat(cm *Cre
return errors.New("the Credential Manifest specifies a format but the Credential Application does not")
}

err := ca.ensureFormatIsSubsetOfCredManifestFormat(cm.Format)
err := ca.ensureFormatIsSubsetOfCredManifestFormat(*cm.Format)
if err != nil {
return fmt.Errorf("invalid format request: %w", err)
}
Expand Down Expand Up @@ -328,10 +328,15 @@ func setCredentialApplicationContext(presentation *verifiable.Presentation) {
}

func setCustomFields(presentation *verifiable.Presentation, credentialManifest *CredentialManifest) {
format := presexch.Format{}
if credentialManifest.Format != nil {
format = *credentialManifest.Format
}

application := CredentialApplication{
ID: uuid.New().String(),
ManifestID: credentialManifest.ID,
Format: credentialManifest.Format,
Format: format,
}

if presentation.CustomFields == nil {
Expand Down
6 changes: 3 additions & 3 deletions pkg/doc/cm/credentialfulfillment.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ const (
// CredentialFulfillment represents a Credential Fulfillment object as defined in
// https://identity.foundation/credential-manifest/#credential-fulfillment.
type CredentialFulfillment struct {
ID string `json:"id,omitempty"`
ManifestID string `json:"manifest_id,omitempty"`
ID string `json:"id,omitempty"` // mandatory property
ManifestID string `json:"manifest_id,omitempty"` // mandatory property
ApplicationID string `json:"application_id,omitempty"`
OutputDescriptorMappingObjects []OutputDescriptorMap `json:"descriptor_map,omitempty"`
OutputDescriptorMappingObjects []OutputDescriptorMap `json:"descriptor_map,omitempty"` // mandatory property
}

// OutputDescriptorMap represents an Output Descriptor Mapping Object as defined in
Expand Down
151 changes: 104 additions & 47 deletions pkg/doc/cm/credentialmanifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,27 @@ const CredentialManifestAttachmentFormat = "dif/credential-manifest/manifest@v1.
// CredentialManifest represents a Credential Manifest object as defined in
// https://identity.foundation/credential-manifest/#credential-manifest-2.
type CredentialManifest struct {
ID string `json:"id,omitempty"`
Version string `json:"version,omitempty"`
Issuer Issuer `json:"issuer,omitempty"`
OutputDescriptors []*OutputDescriptor `json:"output_descriptors,omitempty"`
Format presexch.Format `json:"format,omitempty"`
ID string `json:"id,omitempty"` // mandatory property
Issuer Issuer `json:"issuer,omitempty"` // mandatory property
OutputDescriptors []*OutputDescriptor `json:"output_descriptors,omitempty"` // mandatory property
Format *presexch.Format `json:"format,omitempty"`
PresentationDefinition *presexch.PresentationDefinition `json:"presentation_definition,omitempty"`
}

// Issuer represents the issuer object defined in https://identity.foundation/credential-manifest/#general-composition.
type Issuer struct {
ID string `json:"id,omitempty"` // Must be a valid URI
Name string `json:"name,omitempty"`
Styles Styles `json:"styles,omitempty"`
ID string `json:"id,omitempty"` // mandatory, must be a valid URI
Name string `json:"name,omitempty"`
Styles *Styles `json:"styles,omitempty"`
}

// Styles represents an Entity Styles object as defined in
// https://identity.foundation/credential-manifest/wallet-rendering/#entity-styles.
// https://identity.foundation/wallet-rendering/#entity-styles.
type Styles struct {
Thumbnail ImageURIWithAltText `json:"thumbnail,omitempty"`
Hero ImageURIWithAltText `json:"hero,omitempty"`
Background Color `json:"background,omitempty"`
Text Color `json:"text,omitempty"`
Thumbnail *ImageURIWithAltText `json:"thumbnail,omitempty"`
Hero *ImageURIWithAltText `json:"hero,omitempty"`
Background *Color `json:"background,omitempty"`
Text *Color `json:"text,omitempty"`
}

// Color represents a single color in RGB hex code format.
Expand All @@ -59,31 +58,31 @@ type Color struct {
// OutputDescriptor represents an Output Descriptor object as defined in
// https://identity.foundation/credential-manifest/#output-descriptor.
type OutputDescriptor struct {
ID string `json:"id,omitempty"`
Schema string `json:"schema,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Display DataDisplayDescriptor `json:"display,omitempty"`
Styles Styles `json:"styles,omitempty"`
ID string `json:"id,omitempty"` // mandatory property
Schema string `json:"schema,omitempty"` // mandatory property
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Display *DataDisplayDescriptor `json:"display,omitempty"`
Styles *Styles `json:"styles,omitempty"`
}

// ImageURIWithAltText represents a URI that points to an image along with the alt text for it.
type ImageURIWithAltText struct {
URI string `json:"uri,omitempty"`
URI string `json:"uri,omitempty"` // mandatory property
Alt string `json:"alt,omitempty"`
}

// DataDisplayDescriptor represents a Data Display Descriptor as defined in
// https://identity.foundation/credential-manifest/wallet-rendering/#data-display.
type DataDisplayDescriptor struct {
Title DisplayMappingObject `json:"title,omitempty"`
Subtitle DisplayMappingObject `json:"subtitle,omitempty"`
Description DisplayMappingObject `json:"description,omitempty"`
Properties []LabeledDisplayMappingObject `json:"properties,omitempty"`
Title *DisplayMappingObject `json:"title,omitempty"`
Subtitle *DisplayMappingObject `json:"subtitle,omitempty"`
Description *DisplayMappingObject `json:"description,omitempty"`
Properties []*LabeledDisplayMappingObject `json:"properties,omitempty"`
}

// DisplayMappingObject represents a Display Mapping Object as defined in
// https://identity.foundation/credential-manifest/wallet-rendering/#display-mapping-object
// https://identity.foundation/wallet-rendering/#display-mapping-object
// There are two possibilities here:
// 1. If the text field is used, schema is not required. The text field will contain display
// information about the target Claim.
Expand All @@ -100,13 +99,13 @@ type DisplayMappingObject struct {
// They are used for the dynamic Properties array in a DataDisplayDescriptor.
type LabeledDisplayMappingObject struct {
DisplayMappingObject
Label string `json:"label,omitempty"`
Label string `json:"label,omitempty"` // mandatory property
}

// Schema represents Type and (optional) Format information for a DisplayMappingObject that uses the Paths field,
// as defined in https://identity.foundation/credential-manifest/wallet-rendering/#using-path.
// as defined in https://identity.foundation/wallet-rendering/#using-path.
type Schema struct {
Type string `json:"type,omitempty"` // MUST be here
Type string `json:"type"` // MUST be here
Format string `json:"format,omitempty"` // MAY be here if the Type is "string".
ContentMediaType string `json:"contentMediaType,omitempty"` // MAY be here if the Type is "string".
ContentEncoding string `json:"contentEncoding,omitempty"` // MAY be here if the Type is "string".
Expand Down Expand Up @@ -137,27 +136,64 @@ func (cm *CredentialManifest) UnmarshalJSON(data []byte) error {
// Validate ensures that this CredentialManifest is valid as per the spec.
// Note that this function is automatically called when unmarshalling a []byte into a CredentialManifest.
func (cm *CredentialManifest) Validate() error {
if cm.Issuer.ID == "" {
return errors.New("issuer ID missing")
if cm.ID == "" {
return errors.New("ID missing")
}

err := validateIssuer(cm.Issuer)
if err != nil {
return err
}

if len(cm.OutputDescriptors) == 0 {
return errors.New("no output descriptors found")
}

err := ValidateOutputDescriptors(cm.OutputDescriptors)
err = ValidateOutputDescriptors(cm.OutputDescriptors)
if err != nil {
return err
}

return nil
}

func validateIssuer(issuer Issuer) error {
if issuer.ID == "" {
return errors.New("issuer ID missing")
}

if issuer.Styles != nil {
return validateStyles(*issuer.Styles)
}

return nil
}

func validateStyles(styles Styles) error {
if styles.Thumbnail != nil {
return validateImage(*styles.Thumbnail)
}

if styles.Hero != nil {
return validateImage(*styles.Hero)
}

return nil
}

func validateImage(image ImageURIWithAltText) error {
if image.URI == "" {
return errors.New("uri missing for image")
}

return nil
}

// ValidateOutputDescriptors checks the given slice of OutputDescriptors to ensure that they are valid (per the spec)
// when placed together within a single Credential Manifest.
// To pass validation, the following two conditions must be satisfied:
// 1. Each OutputDescriptor must have a unique ID.
// 2. Each OutputDescriptor must also have valid contents. See the validateOutputDescriptor function for details.
// 2. Each OutputDescriptor must also have valid contents. See the validateOutputDescriptorDisplay function for details.
func ValidateOutputDescriptors(descriptors []*OutputDescriptor) error {
allOutputDescriptorIDs := make(map[string]struct{})

Expand All @@ -177,17 +213,28 @@ func ValidateOutputDescriptors(descriptors []*OutputDescriptor) error {
return fmt.Errorf("missing schema for output descriptor at index %d", i)
}

err := validateOutputDescriptor(descriptors[i], i)
err := validateOutputDescriptorDisplay(descriptors[i], i)
if err != nil {
return err
}

if descriptors[i].Styles != nil {
err = validateStyles(*descriptors[i].Styles)
if err != nil {
return fmt.Errorf("%w at index %d", err, i)
}
}
}

return nil
}

func (cm *CredentialManifest) hasFormat() bool {
return hasAnyAlgorithmsOrProofTypes(cm.Format)
if cm.Format == nil {
return false
}

return hasAnyAlgorithmsOrProofTypes(*cm.Format)
}

func (cm *CredentialManifest) standardUnmarshal(data []byte) error {
Expand All @@ -203,23 +250,33 @@ func (cm *CredentialManifest) standardUnmarshal(data []byte) error {
return nil
}

func validateOutputDescriptor(outputDescriptor *OutputDescriptor, outputDescriptorIndex int) error {
err := validateDisplayMappingObject(&outputDescriptor.Display.Title)
if err != nil {
return fmt.Errorf("display title for output descriptor at index %d is invalid: %w",
outputDescriptorIndex, err)
func validateOutputDescriptorDisplay(outputDescriptor *OutputDescriptor, outputDescriptorIndex int) error {
if outputDescriptor.Display == nil {
return nil
}

err = validateDisplayMappingObject(&outputDescriptor.Display.Subtitle)
if err != nil {
return fmt.Errorf("display subtitle for output descriptor at index %d is invalid: %w",
outputDescriptorIndex, err)
if outputDescriptor.Display.Title != nil {
err := validateDisplayMappingObject(outputDescriptor.Display.Title)
if err != nil {
return fmt.Errorf("display title for output descriptor at index %d is invalid: %w",
outputDescriptorIndex, err)
}
}

err = validateDisplayMappingObject(&outputDescriptor.Display.Description)
if err != nil {
return fmt.Errorf("display description for output descriptor at index %d is invalid: %w",
outputDescriptorIndex, err)
if outputDescriptor.Display.Subtitle != nil {
err := validateDisplayMappingObject(outputDescriptor.Display.Subtitle)
if err != nil {
return fmt.Errorf("display subtitle for output descriptor at index %d is invalid: %w",
outputDescriptorIndex, err)
}
}

if outputDescriptor.Display.Description != nil {
err := validateDisplayMappingObject(outputDescriptor.Display.Description)
if err != nil {
return fmt.Errorf("display description for output descriptor at index %d is invalid: %w",
outputDescriptorIndex, err)
}
}

for i := range outputDescriptor.Display.Properties {
Expand Down
Loading