Skip to content

Commit

Permalink
feat(sdk): api for offering metadata (trustbloc#721)
Browse files Browse the repository at this point in the history
feat(sdk): simplified api for offering display data.

Signed-off-by: Volodymyr Kubiv <[email protected]>
  • Loading branch information
vkubiv authored Feb 13, 2024
1 parent 849c996 commit d8a04c3
Show file tree
Hide file tree
Showing 15 changed files with 242 additions and 15 deletions.
53 changes: 53 additions & 0 deletions cmd/wallet-sdk-gomobile/api/stringarray.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,56 @@ func (s *StringArray) AtIndex(index int) string {

return s.Strings[index]
}

// StringArrayArray represents an array of StringArray.
type StringArrayArray struct {
stringArrays []*StringArray
}

// NewStringArrayArray creates new StringArrayArray.
func NewStringArrayArray() *StringArrayArray {
return &StringArrayArray{}
}

// Add adds new item to underlying array.
func (a *StringArrayArray) Add(cred *StringArray) *StringArrayArray {
a.stringArrays = append(a.stringArrays, cred)

return a
}

// Length returns the number of StringArrays contained within this StringArrayArray.
func (a *StringArrayArray) Length() int {
return len(a.stringArrays)
}

// AtIndex returns the StringArray at the given index.
// If the index passed in is out of bounds, then nil is returned.
func (a *StringArrayArray) AtIndex(index int) *StringArray {
maxIndex := len(a.stringArrays) - 1
if index > maxIndex || index < 0 {
return nil
}

return a.stringArrays[index]
}

// StringArrayArrayToGoArray converts StringArrayArray to [][]string.
func StringArrayArrayToGoArray(arrayArray *StringArrayArray) [][]string {
var result [][]string
for _, arr := range arrayArray.stringArrays {
result = append(result, arr.Strings)
}

return result
}

// StringArrayArrayFromGoArray converts [][]string to StringArrayArray.
func StringArrayArrayFromGoArray(arrayArray [][]string) *StringArrayArray {
result := NewStringArrayArray()
for _, arr := range arrayArray {
result.Add(&StringArray{Strings: arr})
}

return result
}
25 changes: 25 additions & 0 deletions cmd/wallet-sdk-gomobile/api/stringarray_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,28 @@ func TestStringArray_Append(t *testing.T) {
require.Equal(t, "string1", stringArray.AtIndex(0))
require.Equal(t, "string2", stringArray.AtIndex(1))
}

func TestStringArrayArray_Append(t *testing.T) {
stringArray := api.NewStringArray()
stringArrayArray := api.NewStringArrayArray()

require.Equal(t, 0, stringArray.Length())
require.Equal(t, 0, stringArrayArray.Length())

stringArray.Append("string1")
stringArrayArray.Add(stringArray)

require.Equal(t, 1, stringArrayArray.Length())
require.Equal(t, "string1", stringArrayArray.AtIndex(0).AtIndex(0))

require.Nil(t, stringArrayArray.AtIndex(-1))
}

func TestStringArrayArrayToGoArray(t *testing.T) {
strings := [][]string{
{"str11", "str12"},
{"str21", "str22"},
}
result := api.StringArrayArrayToGoArray(api.StringArrayArrayFromGoArray(strings))
require.ElementsMatch(t, strings, result)
}
15 changes: 15 additions & 0 deletions cmd/wallet-sdk-gomobile/display/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (

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

"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"
Expand Down Expand Up @@ -40,6 +42,19 @@ func Resolve(vcs *verifiable.CredentialsArray, issuerURI string, opts *Opts) (*D
return &Data{resolvedDisplayData: resolvedDisplayData}, nil
}

// ResolveCredentialOffer resolves display information for some offered credentials based on an issuer's metadata.
// The CredentialDisplays in the returned ResolvedDisplayData object correspond to the offered credential types
// passed in and are in the same order.
func ResolveCredentialOffer(
issuerMetadata *openid4ci.IssuerMetadata, offeredTypes *api.StringArrayArray, preferredLocale string,
) *Data {
resolvedDisplayData := goapicredentialschema.ResolveCredentialOffer(openid4ci.IssuerMetadataToGoImpl(issuerMetadata),
api.StringArrayArrayToGoArray(offeredTypes),
preferredLocale)

return &Data{resolvedDisplayData: resolvedDisplayData}
}

func generateGoAPIOpts(vcs *verifiable.CredentialsArray, issuerURI string,
opts *Opts,
) ([]goapicredentialschema.ResolveOpt, error) {
Expand Down
34 changes: 30 additions & 4 deletions cmd/wallet-sdk-gomobile/display/resolve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ package display_test

import (
_ "embed"
"encoding/json"
"net/http"
"net/http/httptest"
"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"

Expand Down Expand Up @@ -200,6 +203,23 @@ func TestResolve(t *testing.T) {
})
}

func TestResolveCredentialOffer(t *testing.T) {
metadata := &issuer.Metadata{}

require.NoError(t, json.Unmarshal(sampleIssuerMetadata, metadata))

resolvedDisplayData := display.ResolveCredentialOffer(
openid4ci.IssuerMetadataFromGoImpl(metadata), api.NewStringArrayArray().Add(
api.NewStringArray().Append("UniversityDegreeCredential")), "")

checkIssuerDisplay(t, resolvedDisplayData.IssuerDisplay())

require.Equal(t, 1, resolvedDisplayData.CredentialDisplaysLength())

credentialDisplay := resolvedDisplayData.CredentialDisplayAtIndex(0)
checkCredentialOverview(t, credentialDisplay)
}

func checkResolvedDisplayData(t *testing.T, resolvedDisplayData *display.Data) {
t.Helper()

Expand All @@ -221,17 +241,23 @@ func checkIssuerDisplay(t *testing.T, issuerDisplay *display.IssuerDisplay) {
func checkCredentialDisplay(t *testing.T, credentialDisplay *display.CredentialDisplay) {
t.Helper()

checkCredentialOverview(t, credentialDisplay)

require.Equal(t, 6, credentialDisplay.ClaimsLength())

checkClaims(t, credentialDisplay)
}

func checkCredentialOverview(t *testing.T, credentialDisplay *display.CredentialDisplay) {
t.Helper()

credentialOverview := credentialDisplay.Overview()
require.Equal(t, "University Credential", credentialOverview.Name())
require.Equal(t, "en-US", credentialOverview.Locale())
require.Equal(t, "https://exampleuniversity.com/public/logo.png", credentialOverview.Logo().URL())
require.Equal(t, "a square logo of a university", credentialOverview.Logo().AltText())
require.Equal(t, "#12107c", credentialOverview.BackgroundColor())
require.Equal(t, "#FFFFFF", credentialOverview.TextColor())

require.Equal(t, 6, credentialDisplay.ClaimsLength())

checkClaims(t, credentialDisplay)
}

func checkClaims(t *testing.T, credentialDisplay *display.CredentialDisplay) { //nolint:gocyclo // Test file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ func (i *IssuerInitiatedInteraction) DynamicClientRegistrationEndpoint() (string
return endpoint, nil
}

// OfferedCredentialsTypes returns types of offered credentials.
func (i *IssuerInitiatedInteraction) OfferedCredentialsTypes() *api.StringArrayArray {
return api.StringArrayArrayFromGoArray(i.goAPIInteraction.OfferedCredentialsTypes())
}

// IssuerMetadata returns the issuer's metadata.
func (i *IssuerInitiatedInteraction) IssuerMetadata() (*IssuerMetadata, error) {
goAPIIssuerMetadata, err := i.goAPIInteraction.IssuerMetadata()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
goapi "github.com/trustbloc/wallet-sdk/pkg/api"
"github.com/trustbloc/wallet-sdk/pkg/did/creator"
"github.com/trustbloc/wallet-sdk/pkg/models"
"github.com/trustbloc/wallet-sdk/pkg/models/issuer"
goapiopenid4ci "github.com/trustbloc/wallet-sdk/pkg/openid4ci"
)

Expand Down Expand Up @@ -597,6 +598,12 @@ func TestIssuerInitiatedInteractionAlias(t *testing.T) {
require.NotNil(t, issuerMetadata)
}

func TestIssuerMetadataFromToGoImpl(t *testing.T) {
goImpl := &issuer.Metadata{}
restored := openid4ci.IssuerMetadataToGoImpl(openid4ci.IssuerMetadataFromGoImpl(goImpl))
require.Equal(t, goImpl, restored)
}

func doRequestCredentialTest(t *testing.T, additionalHeaders *api.Headers,
disableTLSVerification bool,
) {
Expand Down Expand Up @@ -635,6 +642,10 @@ func doRequestCredentialTestExt(t *testing.T, additionalHeaders *api.Headers,
additionalHeaders,
disableTLSVerification)

offeringTypes := api.StringArrayArrayToGoArray(interaction.OfferedCredentialsTypes())
require.Len(t, offeringTypes, 1)
require.Contains(t, offeringTypes[0], "VerifiedEmployee")

keyHandle, err := kms.Create(arieskms.ED25519)
require.NoError(t, err)

Expand Down
10 changes: 10 additions & 0 deletions cmd/wallet-sdk-gomobile/openid4ci/issuermetadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ func (i *IssuerMetadata) LocalizedIssuerDisplays() *LocalizedIssuerDisplays {
return &LocalizedIssuerDisplays{localizedIssuerDisplays: i.issuerMetadata.LocalizedIssuerDisplays}
}

// IssuerMetadataToGoImpl unwrap original issuer.Metadata from IssuerMetadata wrapper.
func IssuerMetadataToGoImpl(wrapped *IssuerMetadata) *issuer.Metadata {
return wrapped.issuerMetadata
}

// IssuerMetadataFromGoImpl wrap original issuer.Metadata into IssuerMetadata wrapper.
func IssuerMetadataFromGoImpl(goAPIIssuerMetadata *issuer.Metadata) *IssuerMetadata {
return &IssuerMetadata{issuerMetadata: goAPIIssuerMetadata}
}

// IssuerTrustInfo represent issuer trust information.
type IssuerTrustInfo struct {
DID string
Expand Down
28 changes: 25 additions & 3 deletions pkg/credentialschema/credentialdisplay.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func buildCredentialDisplays(vcs []*verifiable.Credential, credentialsSupported
var foundMatchingType bool

for i := range credentialsSupported {
if !haveMatchingTypes(&credentialsSupported[i], displayVC) {
if !haveMatchingTypes(&credentialsSupported[i], displayVC.Contents().Types) {
continue
}

Expand Down Expand Up @@ -72,10 +72,32 @@ func buildCredentialDisplays(vcs []*verifiable.Credential, credentialsSupported
return credentialDisplays, nil
}

func buildCredentialOfferingDisplays(offeringTypes [][]string, credentialsSupported []issuer.SupportedCredential,
preferredLocale string,
) []CredentialDisplay {
var credentialDisplays []CredentialDisplay

for _, vcTypes := range offeringTypes {
for i := range credentialsSupported {
if !haveMatchingTypes(&credentialsSupported[i], vcTypes) {
continue
}

credentialDisplay := &CredentialDisplay{Overview: getOverviewDisplay(&credentialsSupported[i], preferredLocale)}

credentialDisplays = append(credentialDisplays, *credentialDisplay)

break
}
}

return credentialDisplays
}

// The VC is considered to be a match for the supportedCredential if the VC has at least one type that's the same as
// the type specified by the supportCredential (excluding the "VerifiableCredential" type that all VCs have).
func haveMatchingTypes(supportedCredential *issuer.SupportedCredential, vc *verifiable.Credential) bool {
for _, typeFromVC := range vc.Contents().Types {
func haveMatchingTypes(supportedCredential *issuer.SupportedCredential, vcTypes []string) bool {
for _, typeFromVC := range vcTypes {
// We expect the types in the VC and SupportedCredential to always include VerifiableCredential,
// so we skip this case.
if strings.EqualFold(typeFromVC, "VerifiableCredential") {
Expand Down
17 changes: 17 additions & 0 deletions pkg/credentialschema/credentialschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ SPDX-License-Identifier: Apache-2.0
// Package credentialschema contains a function that can be used to resolve display values per the OpenID4CI spec.
package credentialschema

import "github.com/trustbloc/wallet-sdk/pkg/models/issuer"

// Resolve resolves display information for some issued credentials based on an issuer's metadata.
// The CredentialDisplays in the returned ResolvedDisplayData object correspond to the VCs passed in and are in the
// same order.
Expand Down Expand Up @@ -35,3 +37,18 @@ func Resolve(opts ...ResolveOpt) (*ResolvedDisplayData, error) {
CredentialDisplays: credentialDisplays,
}, nil
}

// ResolveCredentialOffer resolves display information for some offered credentials based on an issuer's metadata.
// The CredentialDisplays in the returned ResolvedDisplayData object correspond to the offered credential types
// passed in and are in the same order.
func ResolveCredentialOffer(
metadata *issuer.Metadata, offeredCredentialTypes [][]string, preferredLocale string,
) *ResolvedDisplayData {
issuerOverview := getIssuerDisplay(metadata.LocalizedIssuerDisplays, preferredLocale)

return &ResolvedDisplayData{
IssuerDisplay: issuerOverview,
CredentialDisplays: buildCredentialOfferingDisplays(offeredCredentialTypes,
metadata.CredentialsSupported, preferredLocale),
}
}
21 changes: 20 additions & 1 deletion pkg/credentialschema/credentialschema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,20 @@ func TestResolve(t *testing.T) { //nolint: gocognit // Test file
})
}

func checkSuccessCaseMatchedDisplayData(t *testing.T, resolvedDisplayData *credentialschema.ResolvedDisplayData) {
func TestResolveCredentialOffer(t *testing.T) {
var issuerMetadata issuer.Metadata
err := json.Unmarshal(sampleIssuerMetadata, &issuerMetadata)
require.NoError(t, err)

t.Run("Success", func(t *testing.T) {
resolvedDisplayData := credentialschema.ResolveCredentialOffer(
&issuerMetadata, [][]string{{"UniversityDegreeCredential"}}, "")

checkSuccessCaseMatchedOverviewData(t, resolvedDisplayData)
})
}

func checkSuccessCaseMatchedOverviewData(t *testing.T, resolvedDisplayData *credentialschema.ResolvedDisplayData) {
t.Helper()

require.Equal(t, "Example University", resolvedDisplayData.IssuerDisplay.Name)
Expand All @@ -460,6 +473,12 @@ func checkSuccessCaseMatchedDisplayData(t *testing.T, resolvedDisplayData *crede
resolvedDisplayData.CredentialDisplays[0].Overview.Logo.AltText)
require.Equal(t, "#12107c", resolvedDisplayData.CredentialDisplays[0].Overview.BackgroundColor)
require.Equal(t, "#FFFFFF", resolvedDisplayData.CredentialDisplays[0].Overview.TextColor)
}

func checkSuccessCaseMatchedDisplayData(t *testing.T, resolvedDisplayData *credentialschema.ResolvedDisplayData) {
t.Helper()

checkSuccessCaseMatchedOverviewData(t, resolvedDisplayData)

expectedIDOrder := 0
expectedGivenNameOrder := 1
Expand Down
5 changes: 5 additions & 0 deletions pkg/openid4ci/issuerinitiatedinteraction.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,11 @@ func (i *IssuerInitiatedInteraction) IssuerMetadata() (*issuer.Metadata, error)
return i.interaction.issuerMetadata, nil
}

// OfferedCredentialsTypes returns types of offered credentials.
func (i *IssuerInitiatedInteraction) OfferedCredentialsTypes() [][]string {
return i.credentialTypes
}

// VerifyIssuer verifies the issuer via its issuer metadata. If successful, then the service URL is returned.
// An error means that either the issuer failed the verification check, or something went wrong during the
// process (and so a verification status could not be determined).
Expand Down
4 changes: 4 additions & 0 deletions pkg/openid4ci/issuerinitiatedinteraction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,10 @@ func TestIssuerInitiatedInteraction_RequestCredential(t *testing.T) {

interaction := newIssuerInitiatedInteraction(t, createCredentialOfferIssuanceURI(t, server.URL, false, true))

offeredCredentialsTypes := interaction.OfferedCredentialsTypes()
require.Len(t, offeredCredentialsTypes, 1)
require.Contains(t, offeredCredentialsTypes[0], "VerifiedEmployee")

credentials, err := interaction.RequestCredentialWithPreAuth(&jwtSignerMock{
keyID: mockKeyID,
}, openid4ci.WithPIN("1234"))
Expand Down
12 changes: 11 additions & 1 deletion test/integration/openid4ci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,17 @@ func doPreAuthCodeFlowTest(t *testing.T) {

require.False(t, preAuthorizedCodeGrantParams.PINRequired())

issuerMetadata, err := interaction.IssuerMetadata()
require.NoError(t, err)

offeringDisplayData := display.ResolveCredentialOffer(
issuerMetadata,
interaction.OfferedCredentialsTypes(),
"",
)

helpers.CheckResolvedDisplayData(t, offeringDisplayData, tc.expectedDisplayData, false)

vm, err := testHelper.DIDDoc.AssertionMethod()
require.NoError(t, err)

Expand Down Expand Up @@ -306,7 +317,6 @@ func doPreAuthCodeFlowTest(t *testing.T) {

testHelper.CheckActivityLogAfterOpenID4CIFlow(t, vcsAPIDirectURL,
tc.issuerProfileID, subID)
testHelper.CheckMetricsLoggerAfterOpenID4CIFlow(t, tc.issuerProfileID)
}

require.Len(t, traceIDs, len(preAuthTests))
Expand Down
Loading

0 comments on commit d8a04c3

Please sign in to comment.