Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sdk): credential display API v2 #824

Merged
merged 1 commit into from
Nov 15, 2024
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
1 change: 1 addition & 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
credentialConfigIDs []string
}

// NewOpts returns a new Opts object.
Expand Down
35 changes: 27 additions & 8 deletions cmd/wallet-sdk-gomobile/display/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,14 @@ package display

import (
"errors"

"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,8 +41,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) {
goAPIOpts, err := generateGoAPIOpts(vcs, issuerURI, opts)
func ResolveCredential(credentialsArray *verifiable.CredentialsArray, issuerURI string, opts *Opts) (*Resolved, error) {
goAPIOpts, err := generateGoAPIOpts(credentialsArray, issuerURI, opts)
if err != nil {
return nil, err
}

resolvedDisplayData, err := goapicredentialschema.ResolveCredential(goAPIOpts...)
if err != nil {
return nil, err
}

return &Resolved{resolvedDisplayData: resolvedDisplayData}, nil
}

func ResolveCredentialV2(credentialsArray *verifiable.CredentialsArrayV2, issuerURI string, opts *Opts) (*Resolved, error) {
credentials := &verifiable.CredentialsArray{}
opts.credentialConfigIDs = make([]string, credentialsArray.Length())

for i := 0; i < credentialsArray.Length(); i++ {
credentials.Add(credentialsArray.AtIndex(i))
opts.credentialConfigIDs[i] = credentialsArray.ConfigIDAtIndex(i)
}

goAPIOpts, err := generateGoAPIOpts(credentials, issuerURI, opts)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -89,7 +108,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.credentialConfigIDs...),
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.ResolveCredentialV2(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
10 changes: 10 additions & 0 deletions cmd/wallet-sdk-gomobile/openid4ci/interaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,13 @@ func toGomobileCredentials(credentials []*afgoverifiable.Credential) *verifiable

return gomobileCredentials
}

func toGomobileCredentialsV2(credentials []*afgoverifiable.Credential, configIDs []string) *verifiable.CredentialsArrayV2 {
credentialArray := verifiable.NewCredentialsArrayV2()

for i := range credentials {
credentialArray.Add(verifiable.NewCredential(credentials[i]), configIDs[i])
}

return credentialArray
}
44 changes: 39 additions & 5 deletions cmd/wallet-sdk-gomobile/openid4ci/issuerinitiatedinteraction.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ package openid4ci

import (
"errors"
"fmt"

"github.com/trustbloc/wallet-sdk/pkg/walleterror"
verifiableapi "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/otel"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/verifiable"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/wrapper"
openid4cigoapi "github.com/trustbloc/wallet-sdk/pkg/openid4ci"
"github.com/trustbloc/wallet-sdk/pkg/walleterror"
)

// IssuerInitiatedInteraction represents a single issuer-instantiated OpenID4CI interaction between a wallet and an
Expand Down Expand Up @@ -102,32 +104,64 @@ func (i *IssuerInitiatedInteraction) CreateAuthorizationURL(clientID, redirectUR
func (i *IssuerInitiatedInteraction) RequestCredentialWithPreAuth(
vm *api.VerificationMethod, opts *RequestCredentialWithPreAuthOpts,
) (*verifiable.CredentialsArray, error) {
credentials, _, err := i.requestCredentialWithPreAuth(vm, opts)
if err != nil {
return nil, wrapper.ToMobileErrorWithTrace(err, i.oTel)
}

return toGomobileCredentials(credentials), nil
}

// RequestCredentialWithPreAuthV2 requests credentials using a pre-authorized code flow.
// Returns an array of credentials with config IDs, which map to CredentialConfigurationSupported in the
// issuer's metadata.
func (i *IssuerInitiatedInteraction) RequestCredentialWithPreAuthV2(
vm *api.VerificationMethod, opts *RequestCredentialWithPreAuthOpts,
) (*verifiable.CredentialsArrayV2, error) {
credentials, configIDs, err := i.requestCredentialWithPreAuth(vm, opts)
if err != nil {
return nil, wrapper.ToMobileErrorWithTrace(err, i.oTel)
}

return toGomobileCredentialsV2(credentials, configIDs), nil
}

func (i *IssuerInitiatedInteraction) requestCredentialWithPreAuth(
vm *api.VerificationMethod, opts *RequestCredentialWithPreAuthOpts,
) ([]*verifiableapi.Credential, []string, error) {
if opts == nil {
opts = NewRequestCredentialWithPreAuthOpts()
}

signer, err := createSigner(vm, i.crypto)
if err != nil {
return nil, wrapper.ToMobileErrorWithTrace(err, i.oTel)
return nil, nil, wrapper.ToMobileErrorWithTrace(err, i.oTel)
}

goOpts := []openid4cigoapi.RequestCredentialWithPreAuthOpt{openid4cigoapi.WithPIN(opts.pin)}

if opts.attestationVM != nil {
attestationSigner, attErr := createSigner(opts.attestationVM, i.crypto)
if attErr != nil {
return nil, wrapper.ToMobileErrorWithTrace(attErr, i.oTel)
return nil, nil, wrapper.ToMobileErrorWithTrace(attErr, i.oTel)
}

goOpts = append(goOpts, openid4cigoapi.WithAttestationVC(attestationSigner, opts.attestationVC))
}

credentials, err := i.goAPIInteraction.RequestCredentialWithPreAuth(signer, goOpts...)
if err != nil {
return nil, wrapper.ToMobileErrorWithTrace(err, i.oTel)
return nil, nil, wrapper.ToMobileErrorWithTrace(err, i.oTel)
}

return toGomobileCredentials(credentials), nil
configIDs := i.goAPIInteraction.CredentialConfigIDs()

if len(credentials) != len(configIDs) {
return nil, nil, fmt.Errorf("mismatch in the number of credentials and configuration IDs: "+
"expected %d but got %d", len(credentials), len(configIDs))
}

return credentials, configIDs, nil
}

// RequestCredentialWithAuth requests credential(s) from the issuer. This method can only be used for the
Expand Down
43 changes: 43 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,46 @@ func (a *CredentialsArray) AtIndex(index int) *Credential {

return a.credentials[index]
}

// CredentialsArrayV2 represents an array of Credentials with associated array of config IDs.
// Each config ID maps to CredentialConfigurationSupported in the issuer's metadata.
type CredentialsArrayV2 struct {
credentials []*Credential
configIDs []string
}

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

// Add adds a new Credential with associated credential config ID.
func (a *CredentialsArrayV2) Add(credential *Credential, configID string) {
a.credentials = append(a.credentials, credential)
a.configIDs = append(a.configIDs, configID)
}

// 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.
// If the index passed in is out of bounds, then nil is returned.
func (a *CredentialsArrayV2) AtIndex(index int) *Credential {
if index < 0 || index >= len(a.credentials) {
return nil
}

return a.credentials[index]
}

// ConfigIDAtIndex returns the config ID for the Credential at the given index.
// If the index is out of bounds, it returns an empty string.
func (a *CredentialsArrayV2) ConfigIDAtIndex(index int) string {
if index < 0 || index >= len(a.credentials) {
return ""
}

return a.configIDs[index]
}
Loading
Loading