Skip to content

Commit

Permalink
feat(sdk): credential display API v2
Browse files Browse the repository at this point in the history
Signed-off-by: Andrii Holovko <[email protected]>
  • Loading branch information
aholovko committed Nov 7, 2024
1 parent 18186b9 commit d90128a
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 95 deletions.
9 changes: 9 additions & 0 deletions cmd/wallet-sdk-gomobile/display/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Opts struct {
maskingString *string
didResolver api.DIDResolver
skipNonClaimData bool
credentialConfigurationIDs []string
}

// NewOpts returns a new Opts object.
Expand Down Expand Up @@ -116,3 +117,11 @@ func (o *Opts) SkipNonClaimData() *Opts {

return o
}

// SetCredentialConfigurationIDs associates the credentials from CredentialsArray with IDs from issuer's
// CredentialConfigurationSupported metadata.
func (o *Opts) SetCredentialConfigurationIDs(ids []string) *Opts {
o.credentialConfigurationIDs = ids

return o
}
34 changes: 28 additions & 6 deletions cmd/wallet-sdk-gomobile/display/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,16 @@ package display

import (
"errors"
"fmt"

"github.com/trustbloc/vc-go/proof/defaults"
afgoverifiable "github.com/trustbloc/vc-go/verifiable"

"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/api"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/openid4ci"
"github.com/trustbloc/wallet-sdk/pkg/common"

afgoverifiable "github.com/trustbloc/vc-go/verifiable"

"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/verifiable"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/wrapper"
"github.com/trustbloc/wallet-sdk/pkg/common"
goapicredentialschema "github.com/trustbloc/wallet-sdk/pkg/credentialschema"
)

Expand All @@ -44,7 +43,30 @@ func Resolve(vcs *verifiable.CredentialsArray, issuerURI string, opts *Opts) (*D
return &Data{resolvedDisplayData: resolvedDisplayData}, nil
}

func ResolveCredential(vcs *verifiable.CredentialsArray, issuerURI string, opts *Opts) (*Resolved, error) {
func ResolveCredential(credentialsArray interface{}, issuerURI string, opts *Opts) (*Resolved, error) {
var vcs *verifiable.CredentialsArray
var configIDs []string

switch a := credentialsArray.(type) {
case *verifiable.CredentialsArray:
vcs = a
case *verifiable.CredentialsArrayV2:
vcs = &verifiable.CredentialsArray{}
configIDs = make([]string, a.Length())

for i := 0; i < a.Length(); i++ {
vc, configID := a.AtIndex(i)
vcs.Add(vc)
configIDs[i] = configID
}

if len(configIDs) > 0 {
opts.SetCredentialConfigurationIDs(configIDs)
}
default:
return nil, fmt.Errorf("unsupported credentials array type: %T", credentialsArray)
}

goAPIOpts, err := generateGoAPIOpts(vcs, issuerURI, opts)
if err != nil {
return nil, err
Expand Down Expand Up @@ -89,7 +111,7 @@ func generateGoAPIOpts(vcs *verifiable.CredentialsArray, issuerURI string,
httpClient := wrapper.NewHTTPClient(opts.httpTimeout, opts.additionalHeaders, opts.disableHTTPClientTLSVerification)

goAPIOpts := []goapicredentialschema.ResolveOpt{
goapicredentialschema.WithCredentials(mobileVCsArrayToGoAPIVCsArray(vcs)),
goapicredentialschema.WithCredentials(mobileVCsArrayToGoAPIVCsArray(vcs), opts.credentialConfigurationIDs...),
goapicredentialschema.WithIssuerURI(issuerURI),
goapicredentialschema.WithPreferredLocale(opts.preferredLocale),
goapicredentialschema.WithHTTPClient(httpClient),
Expand Down
77 changes: 54 additions & 23 deletions cmd/wallet-sdk-gomobile/display/resolve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@ import (
"strconv"
"testing"

"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/did"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/openid4ci"
"github.com/trustbloc/wallet-sdk/pkg/models/issuer"

"github.com/stretchr/testify/require"

"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/api"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/did"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/display"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/openid4ci"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/verifiable"
"github.com/trustbloc/wallet-sdk/pkg/models/issuer"
)

const (
Expand Down Expand Up @@ -215,33 +214,65 @@ func TestResolveCredential(t *testing.T) {
parseVCOptionalArgs := verifiable.NewOpts()
parseVCOptionalArgs.DisableProofCheck()

vc, err := verifiable.ParseCredential(credentialUniversityDegree, parseVCOptionalArgs)
require.NoError(t, err)
t.Run("Success: CredentialArray v1", func(t *testing.T) {
vc, err := verifiable.ParseCredential(credentialUniversityDegree, parseVCOptionalArgs)
require.NoError(t, err)

vcs := verifiable.NewCredentialsArray()
vcs.Add(vc)
vcs := verifiable.NewCredentialsArray()
vcs.Add(vc)

opts := display.NewOpts().SetMaskingString("*")
opts := display.NewOpts().SetMaskingString("*")

resolvedDisplayData, err := display.ResolveCredential(vcs, server.URL, opts)
require.NoError(t, err)
resolvedDisplayData, err := display.ResolveCredential(vcs, server.URL, opts)
require.NoError(t, err)

require.Equal(t, resolvedDisplayData.LocalizedIssuersLength(), 2)
require.Equal(t, resolvedDisplayData.CredentialsLength(), 1)
require.Equal(t, resolvedDisplayData.CredentialAtIndex(0).LocalizedOverviewsLength(), 1)
require.Equal(t, resolvedDisplayData.CredentialAtIndex(0).SubjectsLength(), 6)
require.Equal(t, resolvedDisplayData.LocalizedIssuersLength(), 2)
require.Equal(t, resolvedDisplayData.CredentialsLength(), 1)
require.Equal(t, resolvedDisplayData.CredentialAtIndex(0).LocalizedOverviewsLength(), 1)
require.Equal(t, resolvedDisplayData.CredentialAtIndex(0).SubjectsLength(), 6)

credentialDisplay := resolvedDisplayData.CredentialAtIndex(0)
credentialDisplay := resolvedDisplayData.CredentialAtIndex(0)

for i := 0; i < credentialDisplay.SubjectsLength(); i++ {
claim := credentialDisplay.SubjectAtIndex(i)
for i := 0; i < credentialDisplay.SubjectsLength(); i++ {
claim := credentialDisplay.SubjectAtIndex(i)

if claim.LocalizedLabelAtIndex(0).Name() == sensitiveIDLabel {
require.Equal(t, "*****6789", claim.Value())
} else if claim.LocalizedLabelAtIndex(0).Name() == reallySensitiveIDLabel {
require.Equal(t, "*******", claim.Value())
if claim.LocalizedLabelAtIndex(0).Name() == sensitiveIDLabel {
require.Equal(t, "*****6789", claim.Value())
} else if claim.LocalizedLabelAtIndex(0).Name() == reallySensitiveIDLabel {
require.Equal(t, "*******", claim.Value())
}
}
}
})

t.Run("Success: CredentialArray v2", func(t *testing.T) {
vc, err := verifiable.ParseCredential(credentialUniversityDegree, parseVCOptionalArgs)
require.NoError(t, err)

vcs := verifiable.NewCredentialsArrayV2()
vcs.Add(vc, "UniversityDegreeCredential_jwt_vc_json_v1")

opts := display.NewOpts().SetMaskingString("*")

resolvedDisplayData, err := display.ResolveCredential(vcs, server.URL, opts)
require.NoError(t, err)

require.Equal(t, resolvedDisplayData.LocalizedIssuersLength(), 2)
require.Equal(t, resolvedDisplayData.CredentialsLength(), 1)
require.Equal(t, resolvedDisplayData.CredentialAtIndex(0).LocalizedOverviewsLength(), 1)
require.Equal(t, resolvedDisplayData.CredentialAtIndex(0).SubjectsLength(), 6)

credentialDisplay := resolvedDisplayData.CredentialAtIndex(0)

for i := 0; i < credentialDisplay.SubjectsLength(); i++ {
claim := credentialDisplay.SubjectAtIndex(i)

if claim.LocalizedLabelAtIndex(0).Name() == sensitiveIDLabel {
require.Equal(t, "*****6789", claim.Value())
} else if claim.LocalizedLabelAtIndex(0).Name() == reallySensitiveIDLabel {
require.Equal(t, "*******", claim.Value())
}
}
})
}

func TestResolveCredentialOffer(t *testing.T) {
Expand Down
34 changes: 34 additions & 0 deletions cmd/wallet-sdk-gomobile/verifiable/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,37 @@ func (a *CredentialsArray) AtIndex(index int) *Credential {

return a.credentials[index]
}

// CredentialsArrayV2 represents an array of Credentials with associated issuer configuration IDs.
type CredentialsArrayV2 struct {
credentials []map[*Credential]string // credential -> issuerConfigID
}

// NewCredentialsArrayV2 creates a new CredentialsArrayV2.
func NewCredentialsArrayV2() *CredentialsArrayV2 {
return &CredentialsArrayV2{}
}

// Add adds a new credential with associated issuer configuration ID.
func (a *CredentialsArrayV2) Add(credential *Credential, issuerConfigID string) {
a.credentials = append(a.credentials, map[*Credential]string{credential: issuerConfigID})
}

// Length returns the number of Credentials contained within this CredentialsArrayV2.
func (a *CredentialsArrayV2) Length() int {
return len(a.credentials)
}

// AtIndex returns the Credential at the given index with associated issue configuration ID.
// If the index passed in is out of bounds, then nil is returned.
func (a *CredentialsArrayV2) AtIndex(index int) (*Credential, string) {
if index < 0 || index >= len(a.credentials) {
return nil, ""
}

for credential, configurationID := range a.credentials[index] {
return credential, configurationID
}

return nil, ""
}
73 changes: 33 additions & 40 deletions pkg/credentialschema/credentialdisplay.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,18 @@ var (
const defaultLocale = "en-US"

func buildCredentialDisplays(
vcs []*verifiable.Credential,
credentialConfigurationsSupported map[issuer.CredentialConfigurationID]*issuer.CredentialConfigurationSupported,
credentialConfigMappings []*credentialConfigMapping,
preferredLocale, maskingString string,
) ([]CredentialDisplay, error) {
var credentialDisplays []CredentialDisplay

for _, vc := range vcs {
for _, m := range credentialConfigMappings {
vc := m.credential

if vc == nil {
return nil, errors.New("no credential specified")
}

// The call below creates a copy of the VC with the selective disclosures merged into the credential subject.
displayVC, err := vc.CreateDisplayCredential(verifiable.DisplayAllDisclosures())
if err != nil {
Expand All @@ -45,34 +50,21 @@ func buildCredentialDisplays(
return nil, err
}

var foundMatchingType bool
var credentialDisplay *CredentialDisplay

for _, credentialConfigurationSupported := range credentialConfigurationsSupported {
if !haveMatchingTypes(credentialConfigurationSupported, displayVC.Contents().Types) {
continue
}

credentialDisplay, err := buildCredentialDisplay(credentialConfigurationSupported, vc, subject, preferredLocale,
maskingString)
if m.config != nil {
credentialDisplay, err = buildCredentialDisplay(m.config, vc, subject, preferredLocale, maskingString)
if err != nil {
return nil, err
}

credentialDisplays = append(credentialDisplays, *credentialDisplay)

foundMatchingType = true

break
}

if !foundMatchingType {
} else {
// In case the issuer's metadata doesn't contain display info for this type of credential for some
// reason, we build up a default/generic type of credential display based only on information in the VC.
// It'll be functional, but won't be pretty.
credentialDisplay := buildDefaultCredentialDisplay(vc.Contents().ID, subject)

credentialDisplays = append(credentialDisplays, *credentialDisplay)
credentialDisplay = buildDefaultCredentialDisplay(vc.Contents().ID, subject)
}

credentialDisplays = append(credentialDisplays, *credentialDisplay)
}

return credentialDisplays, nil
Expand Down Expand Up @@ -413,14 +405,24 @@ func convertLogo(logo *issuer.Logo) *Logo {
}

func buildCredentialDisplaysAllLocale(
vcs []*verifiable.Credential,
credentialConfigurationsSupported map[issuer.CredentialConfigurationID]*issuer.CredentialConfigurationSupported,
credentialConfigMappings []*credentialConfigMapping,
maskingString string,
skipNonClaimData bool,
) ([]Credential, error) {
var credentialDisplays []Credential

for _, vc := range vcs {
for _, m := range credentialConfigMappings {
vc := m.credential
config := m.config

if vc == nil {
return nil, errors.New("no credential specified")
}

if config == nil {
return nil, errors.New("no credential configuration specified")
}

// The call below creates a copy of the VC with the selective disclosures merged into the credential subject.
displayVC, err := vc.CreateDisplayCredential(verifiable.DisplayAllDisclosures())
if err != nil {
Expand All @@ -432,21 +434,12 @@ func buildCredentialDisplaysAllLocale(
return nil, err
}

for _, credentialConfigurationSupported := range credentialConfigurationsSupported {
if !haveMatchingTypes(credentialConfigurationSupported, displayVC.Contents().Types) {
continue
}

credentialDisplay, err := buildCredentialDisplayAllLocale(credentialConfigurationSupported, vc, subject,
maskingString, skipNonClaimData)
if err != nil {
return nil, err
}

credentialDisplays = append(credentialDisplays, *credentialDisplay)

break
credentialDisplay, err := buildCredentialDisplayAllLocale(config, vc, subject, maskingString, skipNonClaimData)
if err != nil {
return nil, err
}

credentialDisplays = append(credentialDisplays, *credentialDisplay)
}

return credentialDisplays, nil
Expand Down
Loading

0 comments on commit d90128a

Please sign in to comment.