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

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  SPDX-License-Identifier: Apache-2.0
     4  */
     5  
     6  package verifiable
     7  
     8  import (
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"testing"
    13  
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  // UniversityDegree university degree.
    18  type UniversityDegree struct {
    19  	Type       string `json:"type,omitempty"`
    20  	Name       string `json:"name,omitempty"`
    21  	College    string `json:"college,omitempty"`
    22  	University string `json:"university,omitempty"`
    23  }
    24  
    25  // UniversityDegreeSubject subject of university degree.
    26  type UniversityDegreeSubject struct {
    27  	ID     string `json:"id,omitempty"`
    28  	Name   string `json:"name,omitempty"`
    29  	Spouse string `json:"spouse,omitempty"`
    30  
    31  	Degree UniversityDegree `json:"degree,omitempty"`
    32  }
    33  
    34  func mapUniversityDegreeSubject(subjects []Subject) *UniversityDegreeSubject {
    35  	if len(subjects) != 1 {
    36  		return nil
    37  	}
    38  
    39  	subject := &subjects[0]
    40  	customFields := subject.CustomFields
    41  	udSubject := &UniversityDegreeSubject{
    42  		ID:     subject.ID,
    43  		Name:   safeStringValue(customFields["name"]),
    44  		Spouse: safeStringValue(customFields["spouse"]),
    45  	}
    46  
    47  	degreeValue, ok := customFields["degree"]
    48  	if !ok {
    49  		return udSubject
    50  	}
    51  
    52  	degreeMap, ok := degreeValue.(map[string]interface{})
    53  	if !ok {
    54  		return udSubject
    55  	}
    56  
    57  	udDegree := &udSubject.Degree
    58  	udDegree.Type = safeStringValue(degreeMap["type"])
    59  	udDegree.Name = safeStringValue(degreeMap["name"])
    60  	udDegree.College = safeStringValue(degreeMap["college"])
    61  	udDegree.University = safeStringValue(degreeMap["university"])
    62  
    63  	return udSubject
    64  }
    65  
    66  // UniversityDegreeCredential University Degree credential, from examples of https://w3c.github.io/vc-data-model
    67  type UniversityDegreeCredential struct {
    68  	Base Credential `json:"-"`
    69  
    70  	Subject *UniversityDegreeSubject `json:"credentialSubject,omitempty"`
    71  }
    72  
    73  func NewUniversityDegreeCredential(t *testing.T, vcData []byte,
    74  	opts ...CredentialOpt) (*UniversityDegreeCredential, error) {
    75  	cred, err := parseTestCredential(t, vcData, opts...)
    76  	if err != nil {
    77  		return nil, fmt.Errorf("new university degree credential: %w", err)
    78  	}
    79  
    80  	// One way to build custom credential subject is to convert []Subject to the custom credential.
    81  	udc := UniversityDegreeCredential{
    82  		Base:    *cred,
    83  		Subject: mapUniversityDegreeSubject(credSubjects(cred)),
    84  	}
    85  
    86  	// The other way is to marshal credential subject and unmarshal back to custom subject structure.
    87  	subjects, ok := cred.Subject.([]Subject)
    88  	if !ok {
    89  		return nil, errors.New("expected subject of []Subject type")
    90  	}
    91  
    92  	if len(subjects) != 1 {
    93  		return nil, errors.New("expected a single subject")
    94  	}
    95  
    96  	subjectBytes, err := json.Marshal(subjects[0])
    97  	if err != nil {
    98  		return nil, fmt.Errorf("new university degree credential subject: %w", err)
    99  	}
   100  
   101  	err = json.Unmarshal(subjectBytes, &udc.Subject)
   102  	if err != nil {
   103  		return nil, fmt.Errorf("new university degree credential subject: %w", err)
   104  	}
   105  
   106  	return &udc, nil
   107  }
   108  
   109  func credSubjects(vc *Credential) []Subject {
   110  	if vc.Subject == nil {
   111  		return nil
   112  	}
   113  
   114  	if subjects, ok := vc.Subject.([]Subject); ok {
   115  		return subjects
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  func TestCredentialExtensibility(t *testing.T) {
   122  	udCredential := `
   123  
   124  {
   125    "@context": [
   126      "https://www.w3.org/2018/credentials/v1",
   127      "https://www.w3.org/2018/credentials/examples/v1"
   128    ],
   129    "id": "http://example.edu/credentials/1872",
   130    "type": [
   131      "VerifiableCredential",
   132      "UniversityDegreeCredential"
   133    ],
   134    "credentialSubject": {
   135      "id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
   136      "degree": {
   137        "type": "BachelorDegree"
   138      },
   139      "name": "Jayden Doe",
   140      "spouse": "did:example:c276e12ec21ebfeb1f712ebc6f1"
   141    },
   142  
   143    "issuer": {
   144      "id": "did:example:76e12ec712ebc6f1c221ebfeb1f",
   145      "name": "Example University"
   146    },
   147  
   148    "issuanceDate": "2010-01-01T19:23:24Z",
   149  
   150    "expirationDate": "2020-01-01T19:23:24Z",
   151  
   152    "credentialStatus": {
   153      "id": "https://example.edu/status/24",
   154      "type": "CredentialStatusList2017"
   155    },
   156  
   157    "evidence": [{
   158      "id": "https://example.edu/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d4231",
   159      "type": ["DocumentVerification"],
   160      "verifier": "https://example.edu/issuers/14",
   161      "evidenceDocument": "DriversLicense",
   162      "subjectPresence": "Physical",
   163      "documentPresence": "Physical"
   164    },{
   165      "id": "https://example.edu/evidence/f2aeec97-fc0d-42bf-8ca7-0548192dxyzab",
   166      "type": ["SupportingActivity"],
   167      "verifier": "https://example.edu/issuers/14",
   168      "evidenceDocument": "Fluid Dynamics Focus",
   169      "subjectPresence": "Digital",
   170      "documentPresence": "Digital"
   171    }],
   172  
   173    "termsOfUse": [
   174      {
   175        "type": "IssuerPolicy",
   176        "id": "http://example.com/policies/credential/4",
   177        "profile": "http://example.com/profiles/credential",
   178        "prohibition": [
   179          {
   180            "assigner": "https://example.edu/issuers/14",
   181            "assignee": "AllVerifiers",
   182            "target": "http://example.edu/credentials/3732",
   183            "action": [
   184              "Archival"
   185            ]
   186          }
   187        ]
   188      }
   189    ],
   190  
   191    "refreshService": {
   192      "id": "https://example.edu/refresh/3732",
   193      "type": "ManualRefreshService2018"
   194    }
   195  }
   196  `
   197  
   198  	cred, err := parseTestCredential(t, []byte(udCredential))
   199  	require.NoError(t, err)
   200  	require.NotNil(t, cred)
   201  
   202  	udc, err := NewUniversityDegreeCredential(t, []byte(udCredential))
   203  	require.NoError(t, err)
   204  
   205  	// base Credential part is the same
   206  	require.Equal(t, *cred, udc.Base)
   207  
   208  	// default issuer credential decoder is applied (i.e. not re-written by new custom decoder)
   209  	require.NotNil(t, cred.Issuer)
   210  	require.Equal(t, cred.Issuer.ID, "did:example:76e12ec712ebc6f1c221ebfeb1f")
   211  	require.Equal(t, cred.Issuer.CustomFields["name"], "Example University")
   212  
   213  	// new mapping is applied
   214  	subj := udc.Subject
   215  	require.Equal(t, "did:example:ebfeb1f712ebc6f1c276e12ec21", subj.ID)
   216  	require.Equal(t, "BachelorDegree", subj.Degree.Type)
   217  	require.Equal(t, "Jayden Doe", subj.Name)
   218  	require.Equal(t, "did:example:c276e12ec21ebfeb1f712ebc6f1", subj.Spouse)
   219  }