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

Commit

Permalink
fix: credential manifest validate improvements
Browse files Browse the repository at this point in the history
Signed-off-by: heidihan0000 <daeun.han@avast.com>
  • Loading branch information
HeidiHan0000 committed Aug 15, 2022
1 parent e5f18f2 commit 4a716b1
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 149 deletions.
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

0 comments on commit 4a716b1

Please sign in to comment.