diff --git a/cmd/vc-rest/startcmd/params.go b/cmd/vc-rest/startcmd/params.go index 664cc8873..da1df87d2 100644 --- a/cmd/vc-rest/startcmd/params.go +++ b/cmd/vc-rest/startcmd/params.go @@ -392,6 +392,7 @@ const ( defaultOIDC4CITransactionDataTTL = 15 * time.Minute defaultOIDC4CIAckDataTTL = 24 * time.Hour defaultOIDC4CIAuthStateTTL = 15 * time.Minute + defaultDynamicWellKnownTTL = 1 * time.Hour defaultDataEncryptionKeyLength = 256 ) diff --git a/cmd/vc-rest/startcmd/start.go b/cmd/vc-rest/startcmd/start.go index 2154ff975..7394a2ab4 100644 --- a/cmd/vc-rest/startcmd/start.go +++ b/cmd/vc-rest/startcmd/start.go @@ -115,6 +115,7 @@ import ( "github.com/trustbloc/vcs/pkg/storage/redis" redisclient "github.com/trustbloc/vcs/pkg/storage/redis" "github.com/trustbloc/vcs/pkg/storage/redis/ackstore" + "github.com/trustbloc/vcs/pkg/storage/redis/dynamicwellknown" oidc4ciclaimdatastoreredis "github.com/trustbloc/vcs/pkg/storage/redis/oidc4ciclaimdatastore" oidc4cinoncestoreredis "github.com/trustbloc/vcs/pkg/storage/redis/oidc4cinoncestore" oidc4cistatestoreredis "github.com/trustbloc/vcs/pkg/storage/redis/oidc4cistatestore" @@ -553,10 +554,16 @@ func buildEchoHandler( return newHTTPClient(tlsConfig, conf.StartupParameters, metrics, id) } + dynamicWellKnownStore, err := getDynamicWellKnownStore(redisClient) + if err != nil { + return nil, fmt.Errorf("failed to get dynamic well-known store: %w", err) + } + openidCredentialIssuerConfigProviderSvc := wellknownprovider.NewService(&wellknownprovider.Config{ - ExternalHostURL: conf.StartupParameters.apiGatewayURL, - KMSRegistry: kmsRegistry, - CryptoJWTSigner: vcCrypto, + ExternalHostURL: conf.StartupParameters.apiGatewayURL, + KMSRegistry: kmsRegistry, + CryptoJWTSigner: vcCrypto, + DynamicWellKnownStore: dynamicWellKnownStore, }) // Issuer Profile Management API @@ -735,6 +742,7 @@ func buildEchoHandler( AckService: ackService, DocumentLoader: documentLoader, PrepareCredential: prepareCredentialSvc, + WellKnownProvider: openidCredentialIssuerConfigProviderSvc, }) if err != nil { return nil, fmt.Errorf("failed to instantiate new oidc4ci service: %w", err) @@ -1213,6 +1221,12 @@ func getOIDC4CITransactionStore( return store, nil } +func getDynamicWellKnownStore( + redisClient *redis.Client, +) (wellknownprovider.DynamicWellKnownStore, error) { + return dynamicwellknown.New(redisClient, defaultDynamicWellKnownTTL), nil +} + func getAckStore( redisClient *redis.Client, oidc4ciAckDataTTL int32, diff --git a/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go b/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go index aa88633e2..40622db8f 100644 --- a/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go +++ b/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go @@ -13,7 +13,6 @@ import ( "encoding/json" "errors" "fmt" - "go.uber.org/zap" "io" "log/slog" "net" @@ -24,6 +23,8 @@ import ( "strings" "time" + "go.uber.org/zap" + "github.com/cli/browser" "github.com/google/uuid" "github.com/piprate/json-gold/ld" diff --git a/pkg/profile/api.go b/pkg/profile/api.go index e40ef96da..57a8eff35 100644 --- a/pkg/profile/api.go +++ b/pkg/profile/api.go @@ -179,6 +179,7 @@ type OIDCConfig struct { CredentialResponseAlgValuesSupported []string `json:"credential_response_alg_values_supported"` CredentialResponseEncValuesSupported []string `json:"credential_response_enc_values_supported"` CredentialResponseEncryptionRequired bool `json:"credential_response_encryption_required"` + DynamicWellKnownSupported bool `json:"dynamic_well_known_supported"` ClaimsEndpoint string `json:"claims_endpoint"` } diff --git a/pkg/restapi/v1/common/common.go b/pkg/restapi/v1/common/common.go index 872033cb8..bf64c5fe5 100644 --- a/pkg/restapi/v1/common/common.go +++ b/pkg/restapi/v1/common/common.go @@ -53,6 +53,19 @@ func ValidateVPFormat(format VPFormat) (vcsverifiable.Format, error) { return "", fmt.Errorf("unsupported vp format %s, use one of next [%s, %s]", format, JwtVcJsonLd, LdpVc) } +func MapToVCFormat(format vcsverifiable.Format) (vcsverifiable.OIDCFormat, error) { + switch format { + case vcsverifiable.Jwt: + return vcsverifiable.JwtVCJson, nil + case vcsverifiable.Ldp: + return vcsverifiable.JwtVCJsonLD, nil + case vcsverifiable.Cwt: + return vcsverifiable.CwtVcLD, nil + } + + return "", fmt.Errorf("vc format missmatch %s, rest api supports only [%s, %s]", format, JwtVcJsonLd, LdpVc) +} + func MapToVPFormat(format vcsverifiable.Format) (VPFormat, error) { switch format { case vcsverifiable.Jwt: diff --git a/pkg/restapi/v1/common/common_test.go b/pkg/restapi/v1/common/common_test.go index c8969d09c..c534db86b 100644 --- a/pkg/restapi/v1/common/common_test.go +++ b/pkg/restapi/v1/common/common_test.go @@ -221,6 +221,23 @@ func TestValidateVPFormat(t *testing.T) { require.Error(t, err) } +func TestMapVcFormat(t *testing.T) { + got, err := MapToVCFormat(vcsverifiable.Jwt) + require.NoError(t, err) + require.EqualValues(t, JwtVcJson, got) + + got, err = MapToVCFormat(vcsverifiable.Ldp) + require.NoError(t, err) + require.EqualValues(t, JwtVcJsonLd, got) + + got, err = MapToVCFormat(vcsverifiable.Cwt) + require.NoError(t, err) + require.EqualValues(t, CwtVcLd, got) + + _, err = MapToVCFormat("invalid") + require.Error(t, err) +} + func TestValidateDIDMethod(t *testing.T) { got, err := ValidateDIDMethod(DIDMethodKey) require.NoError(t, err) diff --git a/pkg/service/oidc4ci/interfaces.go b/pkg/service/oidc4ci/interfaces.go index 480fe7c99..b25722543 100644 --- a/pkg/service/oidc4ci/interfaces.go +++ b/pkg/service/oidc4ci/interfaces.go @@ -5,6 +5,7 @@ import ( "github.com/trustbloc/vc-go/verifiable" + profileapi "github.com/trustbloc/vcs/pkg/profile" "github.com/trustbloc/vcs/pkg/service/issuecredential" ) @@ -24,3 +25,12 @@ type composer interface { // nolint:unused req *issuecredential.PrepareCredentialsRequest, ) (*verifiable.Credential, error) } + +type wellKnownProvider interface { + AddDynamicConfiguration( + ctx context.Context, + profileID string, + id string, + credSupported *profileapi.CredentialsConfigurationSupported, + ) error +} diff --git a/pkg/service/oidc4ci/oidc4ci_service.go b/pkg/service/oidc4ci/oidc4ci_service.go index 4973f2b40..6834271b8 100644 --- a/pkg/service/oidc4ci/oidc4ci_service.go +++ b/pkg/service/oidc4ci/oidc4ci_service.go @@ -175,6 +175,7 @@ type Config struct { AckService ackService DocumentLoader documentLoader PrepareCredential credentialIssuer + WellKnownProvider wellKnownProvider } // Service implements VCS credential interaction API for OIDC credential issuance. @@ -198,6 +199,7 @@ type Service struct { ackService ackService documentLoader documentLoader credentialIssuer credentialIssuer + wellKnownProvider wellKnownProvider } // NewService returns a new Service instance. @@ -222,6 +224,7 @@ func NewService(config *Config) (*Service, error) { ackService: config.AckService, documentLoader: config.DocumentLoader, credentialIssuer: config.PrepareCredential, + wellKnownProvider: config.WellKnownProvider, }, nil } diff --git a/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go b/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go index 81412f2c5..0ddde1ae9 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go +++ b/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go @@ -26,6 +26,7 @@ import ( "github.com/trustbloc/vcs/pkg/doc/verifiable" profileapi "github.com/trustbloc/vcs/pkg/profile" "github.com/trustbloc/vcs/pkg/restapi/resterr" + "github.com/trustbloc/vcs/pkg/restapi/v1/common" "github.com/trustbloc/vcs/pkg/service/issuecredential" ) @@ -208,16 +209,17 @@ func (s *Service) newTxCredentialConf( } } - credentialConfigurationID, _, err := findCredentialConfigurationID( - targetCredentialTemplate.ID, targetCredentialTemplate.Type, profile) + credentialConfigurationID, metaCredentialConfiguration, err := s.findCredentialConfigurationID( + ctx, + targetCredentialTemplate.ID, + targetCredentialTemplate.Type, + profile, + ) + if err != nil { return nil, err } - profileMeta := profile.CredentialMetaData - - metaCredentialConfiguration := profileMeta.CredentialsConfigurationSupported[credentialConfigurationID] - txCredentialConfiguration := &issuecredential.TxCredentialConfiguration{ ID: uuid.NewString(), CredentialTemplate: targetCredentialTemplate, @@ -489,7 +491,8 @@ func findCredentialTemplate( return profile.CredentialTemplates[0], nil } -func findCredentialConfigurationID( +func (s *Service) findCredentialConfigurationID( + ctx context.Context, requestedTemplateID string, credentialType string, profile *profileapi.Issuer, @@ -500,6 +503,28 @@ func findCredentialConfigurationID( } } + if profile.OIDCConfig.DynamicWellKnownSupported { + id := uuid.NewString() + + vcFormat, err := common.MapToVCFormat(profile.VCConfig.Format) + if err != nil { + return "", nil, err + } + + cfg := &profileapi.CredentialsConfigurationSupported{ + Format: vcFormat, + CredentialDefinition: &profileapi.CredentialDefinition{ + Type: []string{credentialType}, + }, + } + + if err = s.wellKnownProvider.AddDynamicConfiguration(ctx, profile.ID, id, cfg); err != nil { + return "", nil, err + } + + return id, cfg, nil + } + return "", nil, resterr.NewValidationError(resterr.InvalidValue, "credential_template_id", fmt.Errorf("credential configuration not found for requested template id %s", requestedTemplateID)) } diff --git a/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance_test.go b/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance_test.go index a91b267bd..169698867 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance_test.go +++ b/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance_test.go @@ -56,6 +56,7 @@ type mocks struct { ackService *MockAckService documentLoader *jsonld.DefaultDocumentLoader composer *Mockcomposer + wellKnown *MockwellKnownProvider } func TestService_InitiateIssuance(t *testing.T) { @@ -1335,6 +1336,45 @@ func TestService_InitiateIssuance(t *testing.T) { require.Nil(t, resp) }, }, + { + name: "Success with Dynamic WellKnown", + setup: func(mocks *mocks) { + initialOpState := "" + claimData := degreeClaims + + b, err := json.Marshal(testProfile) //nolint + assert.NoError(t, err) + + assert.NoError(t, json.Unmarshal(b, &profile)) + delete(profile.CredentialMetaData.CredentialsConfigurationSupported, "UniversityDegreeCredentialIdentifier") + profile.OIDCConfig.DynamicWellKnownSupported = true + + mocks.wellKnown.EXPECT(). + AddDynamicConfiguration(gomock.Any(), profile.ID, gomock.Any(), gomock.Any()). + Return(nil) + + mocks.jsonSchemaValidator.EXPECT().Validate(gomock.Any(), gomock.Any(), gomock.Any()). + Return(errors.New("schema validation err")) + + issuanceReq = &oidc4ci.InitiateIssuanceRequest{ + ClientWellKnownURL: walletWellKnownURL, + OpState: initialOpState, + UserPinRequired: false, + GrantType: oidc4ci.GrantTypePreAuthorizedCode, + Scope: []string{"openid", "profile"}, + CredentialConfiguration: []oidc4ci.InitiateIssuanceCredentialConfiguration{ + { + CredentialTemplateID: "templateID2", + ClaimData: claimData, + }, + }, + } + }, + check: func(t *testing.T, resp *oidc4ci.InitiateIssuanceResponse, err error) { + require.ErrorContains(t, err, "schema validation err") + require.Nil(t, resp) + }, + }, { name: "Error because of event publishing", setup: func(mocks *mocks) { @@ -1533,6 +1573,7 @@ func TestService_InitiateIssuance(t *testing.T) { } profile = &testProfile + profile.OIDCConfig.DynamicWellKnownSupported = false }, check: func(t *testing.T, resp *oidc4ci.InitiateIssuanceResponse, err error) { require.Nil(t, resp) @@ -1866,6 +1907,7 @@ func TestService_InitiateIssuance(t *testing.T) { crypto: NewMockDataProtector(gomock.NewController(t)), jsonSchemaValidator: NewMockJSONSchemaValidator(gomock.NewController(t)), documentLoader: jsonld.NewDefaultDocumentLoader(http.DefaultClient), + wellKnown: NewMockwellKnownProvider(gomock.NewController(t)), } tt.setup(m) @@ -1881,6 +1923,7 @@ func TestService_InitiateIssuance(t *testing.T) { DataProtector: m.crypto, JSONSchemaValidator: m.jsonSchemaValidator, DocumentLoader: m.documentLoader, + WellKnownProvider: m.wellKnown, }) require.NoError(t, err) diff --git a/pkg/service/verifycredential/verifycredential_service.go b/pkg/service/verifycredential/verifycredential_service.go index cb918bb0a..7f2d3fd40 100644 --- a/pkg/service/verifycredential/verifycredential_service.go +++ b/pkg/service/verifycredential/verifycredential_service.go @@ -131,6 +131,7 @@ func (s *Service) verifyVC( return fmt.Errorf("get data integrity verifier: %w", err) } + logger.Info(fmt.Sprintf("verifying VC with challenge[%s] and domain[%s]", challenge, domain)) opts := []verifiable.CredentialOpt{ verifiable.WithProofChecker( defaults.NewDefaultProofChecker(vermethod.NewVDRResolver(s.vdr)), diff --git a/pkg/service/wellknown/provider/api.go b/pkg/service/wellknown/provider/api.go new file mode 100644 index 000000000..fc3a5c4d6 --- /dev/null +++ b/pkg/service/wellknown/provider/api.go @@ -0,0 +1,9 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package provider + +type DynamicWellKnownStore dynamicWellKnownStore diff --git a/pkg/service/wellknown/provider/interfaces.go b/pkg/service/wellknown/provider/interfaces.go new file mode 100644 index 000000000..291b7cbaa --- /dev/null +++ b/pkg/service/wellknown/provider/interfaces.go @@ -0,0 +1,24 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package provider + +import ( + "context" + + profileapi "github.com/trustbloc/vcs/pkg/profile" +) + +//go:generate mockgen -destination interfaces_mocks_test.go -package provider -source=interfaces.go + +type dynamicWellKnownStore interface { + Upsert( + ctx context.Context, + profileID string, + item map[string]*profileapi.CredentialsConfigurationSupported, + ) error + Get(ctx context.Context, profileID string) (map[string]*profileapi.CredentialsConfigurationSupported, error) +} diff --git a/pkg/service/wellknown/provider/wellknown_service.go b/pkg/service/wellknown/provider/wellknown_service.go index ce80d4838..f38f6bb98 100644 --- a/pkg/service/wellknown/provider/wellknown_service.go +++ b/pkg/service/wellknown/provider/wellknown_service.go @@ -9,6 +9,8 @@ SPDX-License-Identifier: Apache-2.0 package provider import ( + "context" + "errors" "fmt" "net/url" "strings" @@ -41,25 +43,39 @@ type JWTWellKnownOpenIDIssuerConfigurationClaims struct { } type Config struct { - ExternalHostURL string - KMSRegistry kmsRegistry - CryptoJWTSigner cryptoJWTSigner + ExternalHostURL string + KMSRegistry kmsRegistry + CryptoJWTSigner cryptoJWTSigner + DynamicWellKnownStore dynamicWellKnownStore } type Service struct { - externalHostURL string - kmsRegistry kmsRegistry - cryptoJWTSigner cryptoJWTSigner + externalHostURL string + kmsRegistry kmsRegistry + cryptoJWTSigner cryptoJWTSigner + dynamicWellKnownStore dynamicWellKnownStore } func NewService(config *Config) *Service { return &Service{ - externalHostURL: config.ExternalHostURL, - kmsRegistry: config.KMSRegistry, - cryptoJWTSigner: config.CryptoJWTSigner, + externalHostURL: config.ExternalHostURL, + kmsRegistry: config.KMSRegistry, + cryptoJWTSigner: config.CryptoJWTSigner, + dynamicWellKnownStore: config.DynamicWellKnownStore, } } +func (s *Service) AddDynamicConfiguration( + ctx context.Context, + profileID string, + id string, + credSupported *profileapi.CredentialsConfigurationSupported, +) error { + return s.dynamicWellKnownStore.Upsert(ctx, profileID, map[string]*profileapi.CredentialsConfigurationSupported{ + id: credSupported, + }) +} + // GetOpenIDCredentialIssuerConfig returns issuer.WellKnownOpenIDIssuerConfiguration object, and // it's JWT signed representation, if this feature is enabled for specific profile. // @@ -79,7 +95,10 @@ func (s *Service) GetOpenIDCredentialIssuerConfig( err error ) - issuerMetadata := s.getOpenIDIssuerConfig(issuerProfile) + issuerMetadata, err := s.getOpenIDIssuerConfig(issuerProfile) + if err != nil { + return nil, "", err + } if issuerProfile.OIDCConfig != nil && issuerProfile.OIDCConfig.SignedIssuerMetadataSupported { jwtSignedIssuerMetadata, err = s.signIssuerMetadata(issuerProfile, issuerMetadata) @@ -91,7 +110,9 @@ func (s *Service) GetOpenIDCredentialIssuerConfig( return issuerMetadata, jwtSignedIssuerMetadata, nil } -func (s *Service) getOpenIDIssuerConfig(issuerProfile *profileapi.Issuer) *issuer.WellKnownOpenIDIssuerConfiguration { +func (s *Service) getOpenIDIssuerConfig( + issuerProfile *profileapi.Issuer, +) (*issuer.WellKnownOpenIDIssuerConfiguration, error) { // TODO: add support of internationalization and Accept-Language Header for this function. // Spec: https://openid.github.io/OpenID4VCI/openid-4-verifiable-credential-issuance-wg-draft.html#section-11.2.2 // For now, the following option from the spec supported: @@ -101,6 +122,27 @@ func (s *Service) getOpenIDIssuerConfig(issuerProfile *profileapi.Issuer) *issue host += "/" } + if issuerProfile.OIDCConfig != nil && issuerProfile.OIDCConfig.DynamicWellKnownSupported { + if issuerProfile.CredentialMetaData == nil { + issuerProfile.CredentialMetaData = &profileapi.CredentialMetaData{} + } + + dynamic, err := s.dynamicWellKnownStore.Get(context.Background(), issuerProfile.ID) + if err != nil { + return nil, errors.Join(err, errors.New("can not get dynamic well-known")) + } + + if issuerProfile.CredentialMetaData.CredentialsConfigurationSupported == nil { + issuerProfile.CredentialMetaData.CredentialsConfigurationSupported = make( + map[string]*profileapi.CredentialsConfigurationSupported, + ) + } + + for k, v := range dynamic { + issuerProfile.CredentialMetaData.CredentialsConfigurationSupported[k] = v + } + } + credentialsConfigurationSupported := s.buildCredentialConfigurationsSupported(issuerProfile) issuerURL, _ := url.JoinPath(s.externalHostURL, "issuer", issuerProfile.ID, issuerProfile.Version) @@ -142,7 +184,7 @@ func (s *Service) getOpenIDIssuerConfig(issuerProfile *profileapi.Issuer) *issue lo.ToPtr(issuerProfile.OIDCConfig.PreAuthorizedGrantAnonymousAccessSupported) } - return final + return final, nil } func (s *Service) signIssuerMetadata( diff --git a/pkg/service/wellknown/provider/wellknown_service_test.go b/pkg/service/wellknown/provider/wellknown_service_test.go index d01b8cf53..44552a074 100644 --- a/pkg/service/wellknown/provider/wellknown_service_test.go +++ b/pkg/service/wellknown/provider/wellknown_service_test.go @@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0 package provider import ( + "context" _ "embed" "encoding/json" "errors" @@ -286,6 +287,93 @@ func checkWellKnownOpenIDIssuerConfiguration( } } +func TestBuildWithDynamic(t *testing.T) { + t.Run("dynamic err", func(t *testing.T) { + store := NewMockdynamicWellKnownStore(gomock.NewController(t)) + + srv := NewService(&Config{ + DynamicWellKnownStore: store, + }) + + store.EXPECT().Get(gomock.Any(), "12345"). + Return(nil, errors.New("unexpected err")) + + resp, err := srv.getOpenIDIssuerConfig(&profileapi.Issuer{ + ID: "12345", + CredentialMetaData: &profileapi.CredentialMetaData{}, + OIDCConfig: &profileapi.OIDCConfig{ + DynamicWellKnownSupported: true, + }, + VCConfig: &profileapi.VCConfig{ + KeyType: "someKey", + }, + }) + + assert.ErrorContains(t, err, "unexpected err") + assert.Nil(t, resp) + }) + t.Run("success", func(t *testing.T) { + store := NewMockdynamicWellKnownStore(gomock.NewController(t)) + + srv := NewService(&Config{ + DynamicWellKnownStore: store, + }) + + store.EXPECT().Get(gomock.Any(), "12345"). + Return(map[string]*profileapi.CredentialsConfigurationSupported{ + "a": { + CredentialDefinition: &profileapi.CredentialDefinition{ + Type: []string{"SomeType"}, + }, + }, + }, nil) + + resp, err := srv.getOpenIDIssuerConfig(&profileapi.Issuer{ + ID: "12345", + CredentialMetaData: &profileapi.CredentialMetaData{}, + OIDCConfig: &profileapi.OIDCConfig{ + DynamicWellKnownSupported: true, + }, + VCConfig: &profileapi.VCConfig{ + KeyType: "someKey", + }, + }) + + assert.NoError(t, err) + assert.NotNil(t, resp) + + assert.Len(t, resp.CredentialConfigurationsSupported.AdditionalProperties, 1) + assert.EqualValues( + t, + []string{"SomeType"}, + resp.CredentialConfigurationsSupported.AdditionalProperties["a"].CredentialDefinition.Type, + ) + }) +} + +func TestUpsert(t *testing.T) { + store := NewMockdynamicWellKnownStore(gomock.NewController(t)) + + srv := NewService(&Config{ + DynamicWellKnownStore: store, + }) + + val := &profileapi.CredentialsConfigurationSupported{} + + store.EXPECT().Upsert(gomock.Any(), "profileID", gomock.Any()). + DoAndReturn(func( + ctx context.Context, + s string, + m map[string]*profileapi.CredentialsConfigurationSupported, + ) error { + assert.Len(t, m, 1) + assert.Equal(t, val, m["key1"]) + return nil + }) + assert.NoError(t, srv.AddDynamicConfiguration(context.TODO(), "profileID", "key1", + val)) +} + func checkWellKnownOpenIDIssuerConfigurationDisplayPropertyExist(t *testing.T, display []issuer.CredentialDisplay) { t.Helper() diff --git a/pkg/storage/redis/dynamicwellknown/dynamicwellknown.go b/pkg/storage/redis/dynamicwellknown/dynamicwellknown.go new file mode 100644 index 000000000..40fc7b3a5 --- /dev/null +++ b/pkg/storage/redis/dynamicwellknown/dynamicwellknown.go @@ -0,0 +1,89 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package dynamicwellknown + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/redis/go-redis/v9" + + profileapi "github.com/trustbloc/vcs/pkg/profile" +) + +const ( + keyPrefix = "dynamic_well_known" +) + +// Store stores claim data with expiration. +type Store struct { + redisClient redisClient + defaultTTL time.Duration +} + +// New creates presentation claims store. +func New(redisClient redisClient, ttl time.Duration) *Store { + return &Store{ + redisClient: redisClient, + defaultTTL: ttl, + } +} + +func (s *Store) Upsert( + ctx context.Context, + profileID string, + item map[string]*profileapi.CredentialsConfigurationSupported, +) error { + currentValue, err := s.Get(ctx, profileID) + if err != nil { + return err + } + + if currentValue == nil { + currentValue = make(map[string]*profileapi.CredentialsConfigurationSupported) + } + + for k, v := range item { + currentValue[k] = v + } + + b, err := json.Marshal(currentValue) + if err != nil { + return err + } + + return s.redisClient.API().Set(ctx, s.resolveRedisKey(profileID), string(b), s.defaultTTL).Err() +} + +func (s *Store) Get(ctx context.Context, id string) (map[string]*profileapi.CredentialsConfigurationSupported, error) { + b, err := s.redisClient.API().Get(ctx, s.resolveRedisKey(id)).Bytes() + if err != nil { + if errors.Is(err, redis.Nil) { + return map[string]*profileapi.CredentialsConfigurationSupported{}, nil + } + + return nil, err + } + + if len(b) == 0 { + return map[string]*profileapi.CredentialsConfigurationSupported{}, nil + } + + var result map[string]*profileapi.CredentialsConfigurationSupported + if err = json.Unmarshal(b, &result); err != nil { + return nil, err + } + + return result, nil +} + +func (s *Store) resolveRedisKey(id string) string { + return fmt.Sprintf("%s:%s", keyPrefix, id) +} diff --git a/pkg/storage/redis/dynamicwellknown/dynamicwellknown_test.go b/pkg/storage/redis/dynamicwellknown/dynamicwellknown_test.go new file mode 100644 index 000000000..7fbb017b3 --- /dev/null +++ b/pkg/storage/redis/dynamicwellknown/dynamicwellknown_test.go @@ -0,0 +1,85 @@ +package dynamicwellknown_test + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/golang/mock/gomock" + redisapi "github.com/redis/go-redis/v9" + "github.com/stretchr/testify/assert" + + profileapi "github.com/trustbloc/vcs/pkg/profile" + "github.com/trustbloc/vcs/pkg/storage/redis/dynamicwellknown" +) + +func TestUpsert(t *testing.T) { + t.Run("no existing", func(t *testing.T) { + cl := NewMockredisClient(gomock.NewController(t)) + api := NewMockredisApi(gomock.NewController(t)) + + cl.EXPECT().API().Return(api).AnyTimes() + + store := dynamicwellknown.New(cl, time.Hour) + + api.EXPECT().Get(context.TODO(), "dynamic_well_known:1234").Return( + redisapi.NewStringResult("", nil)) + + api.EXPECT().Set(gomock.Any(), "dynamic_well_known:1234", gomock.Any(), time.Hour). + Return(redisapi.NewStatusResult("", nil)) + + assert.NoError(t, store.Upsert(context.TODO(), "1234", + map[string]*profileapi.CredentialsConfigurationSupported{ + "key1": {}, + }), + ) + }) + + t.Run("err get", func(t *testing.T) { + cl := NewMockredisClient(gomock.NewController(t)) + api := NewMockredisApi(gomock.NewController(t)) + + cl.EXPECT().API().Return(api).AnyTimes() + + store := dynamicwellknown.New(cl, time.Hour) + + api.EXPECT().Get(context.TODO(), "dynamic_well_known:1234").Return( + redisapi.NewStringResult("", errors.New("unexpected err"))) + + assert.ErrorContains(t, store.Upsert(context.TODO(), "1234", + map[string]*profileapi.CredentialsConfigurationSupported{ + "key1": {}, + }), "unexpected err") + }) + + t.Run("not found err", func(t *testing.T) { + cl := NewMockredisClient(gomock.NewController(t)) + api := NewMockredisApi(gomock.NewController(t)) + + cl.EXPECT().API().Return(api).AnyTimes() + + store := dynamicwellknown.New(cl, time.Hour) + api.EXPECT().Get(context.TODO(), "dynamic_well_known:1234").Return( + redisapi.NewStringResult("", redisapi.Nil)) + + resp, err := store.Get(context.TODO(), "1234") + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + t.Run("err", func(t *testing.T) { + cl := NewMockredisClient(gomock.NewController(t)) + api := NewMockredisApi(gomock.NewController(t)) + + cl.EXPECT().API().Return(api).AnyTimes() + + store := dynamicwellknown.New(cl, time.Hour) + api.EXPECT().Get(context.TODO(), "dynamic_well_known:1234").Return( + redisapi.NewStringResult("", errors.New("unexpected err"))) + + resp, err := store.Get(context.TODO(), "1234") + assert.ErrorContains(t, err, "unexpected err") + assert.Nil(t, resp) + }) +} diff --git a/pkg/storage/redis/dynamicwellknown/interfaces.go b/pkg/storage/redis/dynamicwellknown/interfaces.go new file mode 100644 index 000000000..72065e77e --- /dev/null +++ b/pkg/storage/redis/dynamicwellknown/interfaces.go @@ -0,0 +1,20 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package dynamicwellknown + +import redisapi "github.com/redis/go-redis/v9" + +//go:generate mockgen -destination interfaces_mocks_test.go -package dynamicwellknown_test -source=interfaces.go + +type redisClient interface { + API() redisapi.UniversalClient +} + +// nolint +type redisApi interface { + redisapi.UniversalClient +} diff --git a/test/bdd/features/oidc4vc_api.feature b/test/bdd/features/oidc4vc_api.feature index 97a7fded8..74b6cb43a 100644 --- a/test/bdd/features/oidc4vc_api.feature +++ b/test/bdd/features/oidc4vc_api.feature @@ -296,6 +296,7 @@ Feature: OIDC4VC REST API | issuerProfile | credentialType | credentialTemplate | verifierProfile | presentationDefinitionID | fields | proofType | credentialEncoded | | acme_issuer/v1.0 | UniversityDegreeCredential | | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id | jwt | ewogICJAY29udGV4dCI6IFsKICAgICJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsCiAgICAiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiLAogICAgImh0dHBzOi8vdzNpZC5vcmcvdmMvc3RhdHVzLWxpc3QvMjAyMS92MSIKICBdLAogICJjcmVkZW50aWFsU3RhdHVzIjogewogICAgImlkIjogInVybjp1dWlkOjViYjIwNTU4LTI1Y2UtNDBiNS04MGZjLThhZjY5MWVlZGNjZSIsCiAgICAic3RhdHVzTGlzdENyZWRlbnRpYWwiOiAiaHR0cDovL3ZjLXJlc3QtZWNoby50cnVzdGJsb2MubG9jYWw6ODA3NS9pc3N1ZXIvZ3JvdXBzL2dyb3VwX2FjbWVfaXNzdWVyL2NyZWRlbnRpYWxzL3N0YXR1cy8wNmIxM2U5Mi0yN2E2LTRiNzYtOTk3Ny02ZWNlMjA3NzgzZGQiLAogICAgInN0YXR1c0xpc3RJbmRleCI6ICI2NTQ4IiwKICAgICJzdGF0dXNQdXJwb3NlIjogInJldm9jYXRpb24iLAogICAgInR5cGUiOiAiU3RhdHVzTGlzdDIwMjFFbnRyeSIKICB9LAogICJjcmVkZW50aWFsU3ViamVjdCI6IHsKICAgICJkZWdyZWUiOiB7CiAgICAgICJkZWdyZWUiOiAiTUlUIiwKICAgICAgInR5cGUiOiAiQmFjaGVsb3JEZWdyZWUiCiAgICB9LAogICAgImlkIjogImRpZDppb246RWlCeVFBeFhtT0FFTTdUbEVVRzl1NlJyMnJzNVFDTHh1T2ZWbE5ONXpUM1JtUTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKaFpHUXRjSFZpYkdsakxXdGxlWE1pTENKd2RXSnNhV05MWlhseklqcGJleUpwWkNJNklsOXVWVGx5YmtGcGNIZzNNRmhYU0RSS2RVcDNUMVZwV2paNFNVWk5ZVEZuUm1oS1VWVXdWSFoyTTFFaUxDSndkV0pzYVdOTFpYbEtkMnNpT25zaVkzSjJJam9pVUMwek9EUWlMQ0pyYVdRaU9pSmZibFU1Y201QmFYQjROekJZVjBnMFNuVktkMDlWYVZvMmVFbEdUV0V4WjBab1NsRlZNRlIyZGpOUklpd2lhM1I1SWpvaVJVTWlMQ0o0SWpvaU5sYzNkSEZhTVhabFRUZFpTRTU1ZEROME9YaE1PVGRNVDBoelp6a3libTV3WDJSc2FXTlFjVlJsY2pSdFNYaFplVFZ4TTA1MmVVa3djekkxV2tsRk9DSXNJbmtpT2lKRGExUlhaaTFZVlZwNkxWZDJOekl0TlhCdk9XZHBjblo2U25kNE1uRlRRMGt5Wm1GNWJIZFhjbXR0YW5wWE16RkVjV0ZIY2paRldrUXRkVmxEVTJkT0luMHNJbkIxY25CdmMyVnpJanBiSW1GMWRHaGxiblJwWTJGMGFXOXVJaXdpWVhOelpYSjBhVzl1VFdWMGFHOWtJbDBzSW5SNWNHVWlPaUpLYzI5dVYyVmlTMlY1TWpBeU1DSjlYWDFkTENKMWNHUmhkR1ZEYjIxdGFYUnRaVzUwSWpvaVJXbERNbWh0YzJSRk5IRk9kMjFUUTFCbU1qRjFNRXg2UWtodmMzQldVRkZzT1ZKdFRHczNORloxWnpWcmR5SjlMQ0p6ZFdabWFYaEVZWFJoSWpwN0ltUmxiSFJoU0dGemFDSTZJa1ZwUVMxWk9YVjBUVTVoYVVoVlIxcHROVEJtUVZKamNWTmFjVWRCTXpkNlFsWlVOVVk0UzBsdFh6TlRka0VpTENKeVpXTnZkbVZ5ZVVOdmJXMXBkRzFsYm5RaU9pSkZhVUpSYlVOaWRsOVZRelZSUmtOMk9VTndiMUpQYVZNNWJIWkNkbmR1WTJsWkxWOTRjbEU0U1RKdlIwSm5JbjBzSW5SNWNHVWlPaUpqY21WaGRHVWlmUSIsCiAgICAibmFtZSI6ICJKYXlkZW4gRG9lIiwKICAgICJzcG91c2UiOiAiZGlkOmV4YW1wbGU6YzI3NmUxMmVjMjFlYmZlYjFmNzEyZWJjNmYxIgogIH0sCiAgImV4cGlyYXRpb25EYXRlIjogIjIwMjUtMDMtMThUMjI6MDY6MzEuMzQyNTAxMDZaIiwKICAiaWQiOiAidXJuOnV1aWQ6N2NiYjliYTItMzgwMS00Nzg2LWI3YzEtYWQyZDQyOTI0M2E4IiwKICAiaXNzdWFuY2VEYXRlIjogIjIwMjQtMDMtMThUMjI6MDY6MzEuMzc1NDM0MTMxWiIsCiAgImlzc3VlciI6IHsKICAgICJpZCI6ICJkaWQ6aW9uOkVpQW9hSEpZRjJRNms5eml5aDRQRHJ1c0ladE5VS20xLWFGckF2clBEQ0VNU1E6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSmhaR1F0Y0hWaWJHbGpMV3RsZVhNaUxDSndkV0pzYVdOTFpYbHpJanBiZXlKcFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpsSWpvaVFWRkJRaUlzSW10cFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW10MGVTSTZJbEpUUVNJc0ltNGlPaUp1V1RCaU5tRkdURTVWYW1RM2VFRnBiVnA0ZFVnd1prNVNUVWN6WldOSFdHOVpTM1JSV0RSMVMxRmpjMWxaVTI1R2FFRkRlRGhtYW5Cbk5qSkplVnBDZFZSUVlUWnVaM04yWDA1UlFraFVXVWxNUmpKUldWVlFhVFJRVkVzdGVVRTNUbVJQU2xad05VRkZWelZ5YzNsc1pUZ3dTeTFQVUdsVVVXMUtSMng1VWpoSlVrWklOazl0TTNNMllYTnRXVmw1WmtrdExXdFJaVGhJYTBkMGJYYzNUR2xpVldWWlduZDNSemxJU1d4NlNEUlFObWhJVDNOVVpsaGhSRmwyYkRVM1dXcDFibWd5Y1ZVeVlXbFhSRms1UVdsVVVrSnBiVXhDYUVsdVpuWTBOVTVZZVRsMWQzSkdPWE5aYkZodWJHZDViVlZUTVZGV2NUYzVWUzFsYmt0bVJYTXpSMnMyUlVSaVYzSkhiWHBIY0hSUFpHcHpkRWt3T1dWNGFWUklPRXRyZG5oT1JXaFFla3hrZW5rd1VGTmxSV1o0WDJsVFdXeE1aRmxKUmtWVU4yb3hNVTV2TFRGeU1rVjZRa05wVVRWdWRGSXhNMUVpZlN3aWNIVnljRzl6WlhNaU9sc2lZWFYwYUdWdWRHbGpZWFJwYjI0aUxDSmhjM05sY25ScGIyNU5aWFJvYjJRaVhTd2lkSGx3WlNJNklrcHpiMjVYWldKTFpYa3lNREl3SW4xZGZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVORVJsRlFabmhTUm5Oc2NrRkliVm80YVhOUWJFNDRRelpLVW1SemVuUTBabkkwYURsSU5HUm1NVlpuSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEJTRE14TkZkcVJUTkJhVFJoTFhNeVNtOUhjWFpqUzFWcFRFUmZNVEpWWjNWeFkwWTFiVWR6Um5aMFp5SXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFTMXBka1pxVFRaTGRteDJZVXRVT0dkQ2EzQkphelpMWmt4dk9VbDVNR28wWVdobWFpMXJUMGwyV2xFaWZTd2lkSGx3WlNJNkltTnlaV0YwWlNKOSIsCiAgICAibmFtZSI6ICJBY21lIElzc3VlciIKICB9LAogICJ0eXBlIjogWwogICAgIlZlcmlmaWFibGVDcmVkZW50aWFsIiwKICAgICJVbml2ZXJzaXR5RGVncmVlQ3JlZGVudGlhbCIKICBdCn0= | | acme_issuer_no_template/v1.0 | UniversityDegreeCredential | | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id | jwt | ewogICJAY29udGV4dCI6IFsKICAgICJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsCiAgICAiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiLAogICAgImh0dHBzOi8vdzNpZC5vcmcvdmMvc3RhdHVzLWxpc3QvMjAyMS92MSIKICBdLAogICJjcmVkZW50aWFsU3RhdHVzIjogewogICAgImlkIjogInVybjp1dWlkOjViYjIwNTU4LTI1Y2UtNDBiNS04MGZjLThhZjY5MWVlZGNjZSIsCiAgICAic3RhdHVzTGlzdENyZWRlbnRpYWwiOiAiaHR0cDovL3ZjLXJlc3QtZWNoby50cnVzdGJsb2MubG9jYWw6ODA3NS9pc3N1ZXIvZ3JvdXBzL2dyb3VwX2FjbWVfaXNzdWVyL2NyZWRlbnRpYWxzL3N0YXR1cy8wNmIxM2U5Mi0yN2E2LTRiNzYtOTk3Ny02ZWNlMjA3NzgzZGQiLAogICAgInN0YXR1c0xpc3RJbmRleCI6ICI2NTQ4IiwKICAgICJzdGF0dXNQdXJwb3NlIjogInJldm9jYXRpb24iLAogICAgInR5cGUiOiAiU3RhdHVzTGlzdDIwMjFFbnRyeSIKICB9LAogICJjcmVkZW50aWFsU3ViamVjdCI6IHsKICAgICJkZWdyZWUiOiB7CiAgICAgICJkZWdyZWUiOiAiTUlUIiwKICAgICAgInR5cGUiOiAiQmFjaGVsb3JEZWdyZWUiCiAgICB9LAogICAgImlkIjogImRpZDppb246RWlCeVFBeFhtT0FFTTdUbEVVRzl1NlJyMnJzNVFDTHh1T2ZWbE5ONXpUM1JtUTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKaFpHUXRjSFZpYkdsakxXdGxlWE1pTENKd2RXSnNhV05MWlhseklqcGJleUpwWkNJNklsOXVWVGx5YmtGcGNIZzNNRmhYU0RSS2RVcDNUMVZwV2paNFNVWk5ZVEZuUm1oS1VWVXdWSFoyTTFFaUxDSndkV0pzYVdOTFpYbEtkMnNpT25zaVkzSjJJam9pVUMwek9EUWlMQ0pyYVdRaU9pSmZibFU1Y201QmFYQjROekJZVjBnMFNuVktkMDlWYVZvMmVFbEdUV0V4WjBab1NsRlZNRlIyZGpOUklpd2lhM1I1SWpvaVJVTWlMQ0o0SWpvaU5sYzNkSEZhTVhabFRUZFpTRTU1ZEROME9YaE1PVGRNVDBoelp6a3libTV3WDJSc2FXTlFjVlJsY2pSdFNYaFplVFZ4TTA1MmVVa3djekkxV2tsRk9DSXNJbmtpT2lKRGExUlhaaTFZVlZwNkxWZDJOekl0TlhCdk9XZHBjblo2U25kNE1uRlRRMGt5Wm1GNWJIZFhjbXR0YW5wWE16RkVjV0ZIY2paRldrUXRkVmxEVTJkT0luMHNJbkIxY25CdmMyVnpJanBiSW1GMWRHaGxiblJwWTJGMGFXOXVJaXdpWVhOelpYSjBhVzl1VFdWMGFHOWtJbDBzSW5SNWNHVWlPaUpLYzI5dVYyVmlTMlY1TWpBeU1DSjlYWDFkTENKMWNHUmhkR1ZEYjIxdGFYUnRaVzUwSWpvaVJXbERNbWh0YzJSRk5IRk9kMjFUUTFCbU1qRjFNRXg2UWtodmMzQldVRkZzT1ZKdFRHczNORloxWnpWcmR5SjlMQ0p6ZFdabWFYaEVZWFJoSWpwN0ltUmxiSFJoU0dGemFDSTZJa1ZwUVMxWk9YVjBUVTVoYVVoVlIxcHROVEJtUVZKamNWTmFjVWRCTXpkNlFsWlVOVVk0UzBsdFh6TlRka0VpTENKeVpXTnZkbVZ5ZVVOdmJXMXBkRzFsYm5RaU9pSkZhVUpSYlVOaWRsOVZRelZSUmtOMk9VTndiMUpQYVZNNWJIWkNkbmR1WTJsWkxWOTRjbEU0U1RKdlIwSm5JbjBzSW5SNWNHVWlPaUpqY21WaGRHVWlmUSIsCiAgICAibmFtZSI6ICJKYXlkZW4gRG9lIiwKICAgICJzcG91c2UiOiAiZGlkOmV4YW1wbGU6YzI3NmUxMmVjMjFlYmZlYjFmNzEyZWJjNmYxIgogIH0sCiAgImV4cGlyYXRpb25EYXRlIjogIjIwMjUtMDMtMThUMjI6MDY6MzEuMzQyNTAxMDZaIiwKICAiaWQiOiAidXJuOnV1aWQ6N2NiYjliYTItMzgwMS00Nzg2LWI3YzEtYWQyZDQyOTI0M2E4IiwKICAiaXNzdWFuY2VEYXRlIjogIjIwMjQtMDMtMThUMjI6MDY6MzEuMzc1NDM0MTMxWiIsCiAgImlzc3VlciI6IHsKICAgICJpZCI6ICJkaWQ6aW9uOkVpQW9hSEpZRjJRNms5eml5aDRQRHJ1c0ladE5VS20xLWFGckF2clBEQ0VNU1E6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSmhaR1F0Y0hWaWJHbGpMV3RsZVhNaUxDSndkV0pzYVdOTFpYbHpJanBiZXlKcFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpsSWpvaVFWRkJRaUlzSW10cFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW10MGVTSTZJbEpUUVNJc0ltNGlPaUp1V1RCaU5tRkdURTVWYW1RM2VFRnBiVnA0ZFVnd1prNVNUVWN6WldOSFdHOVpTM1JSV0RSMVMxRmpjMWxaVTI1R2FFRkRlRGhtYW5Cbk5qSkplVnBDZFZSUVlUWnVaM04yWDA1UlFraFVXVWxNUmpKUldWVlFhVFJRVkVzdGVVRTNUbVJQU2xad05VRkZWelZ5YzNsc1pUZ3dTeTFQVUdsVVVXMUtSMng1VWpoSlVrWklOazl0TTNNMllYTnRXVmw1WmtrdExXdFJaVGhJYTBkMGJYYzNUR2xpVldWWlduZDNSemxJU1d4NlNEUlFObWhJVDNOVVpsaGhSRmwyYkRVM1dXcDFibWd5Y1ZVeVlXbFhSRms1UVdsVVVrSnBiVXhDYUVsdVpuWTBOVTVZZVRsMWQzSkdPWE5aYkZodWJHZDViVlZUTVZGV2NUYzVWUzFsYmt0bVJYTXpSMnMyUlVSaVYzSkhiWHBIY0hSUFpHcHpkRWt3T1dWNGFWUklPRXRyZG5oT1JXaFFla3hrZW5rd1VGTmxSV1o0WDJsVFdXeE1aRmxKUmtWVU4yb3hNVTV2TFRGeU1rVjZRa05wVVRWdWRGSXhNMUVpZlN3aWNIVnljRzl6WlhNaU9sc2lZWFYwYUdWdWRHbGpZWFJwYjI0aUxDSmhjM05sY25ScGIyNU5aWFJvYjJRaVhTd2lkSGx3WlNJNklrcHpiMjVYWldKTFpYa3lNREl3SW4xZGZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVORVJsRlFabmhTUm5Oc2NrRkliVm80YVhOUWJFNDRRelpLVW1SemVuUTBabkkwYURsSU5HUm1NVlpuSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEJTRE14TkZkcVJUTkJhVFJoTFhNeVNtOUhjWFpqUzFWcFRFUmZNVEpWWjNWeFkwWTFiVWR6Um5aMFp5SXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFTMXBka1pxVFRaTGRteDJZVXRVT0dkQ2EzQkphelpMWmt4dk9VbDVNR28wWVdobWFpMXJUMGwyV2xFaWZTd2lkSGx3WlNJNkltTnlaV0YwWlNKOSIsCiAgICAibmFtZSI6ICJBY21lIElzc3VlciIKICB9LAogICJ0eXBlIjogWwogICAgIlZlcmlmaWFibGVDcmVkZW50aWFsIiwKICAgICJVbml2ZXJzaXR5RGVncmVlQ3JlZGVudGlhbCIKICBdCn0= | + | playground_issuer/v1.0 | UniversityDegreeCredential | | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id | jwt | ewogICJAY29udGV4dCI6IFsKICAgICJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsCiAgICAiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiLAogICAgImh0dHBzOi8vdzNpZC5vcmcvdmMvc3RhdHVzLWxpc3QvMjAyMS92MSIKICBdLAogICJjcmVkZW50aWFsU3RhdHVzIjogewogICAgImlkIjogInVybjp1dWlkOjViYjIwNTU4LTI1Y2UtNDBiNS04MGZjLThhZjY5MWVlZGNjZSIsCiAgICAic3RhdHVzTGlzdENyZWRlbnRpYWwiOiAiaHR0cDovL3ZjLXJlc3QtZWNoby50cnVzdGJsb2MubG9jYWw6ODA3NS9pc3N1ZXIvZ3JvdXBzL2dyb3VwX2FjbWVfaXNzdWVyL2NyZWRlbnRpYWxzL3N0YXR1cy8wNmIxM2U5Mi0yN2E2LTRiNzYtOTk3Ny02ZWNlMjA3NzgzZGQiLAogICAgInN0YXR1c0xpc3RJbmRleCI6ICI2NTQ4IiwKICAgICJzdGF0dXNQdXJwb3NlIjogInJldm9jYXRpb24iLAogICAgInR5cGUiOiAiU3RhdHVzTGlzdDIwMjFFbnRyeSIKICB9LAogICJjcmVkZW50aWFsU3ViamVjdCI6IHsKICAgICJkZWdyZWUiOiB7CiAgICAgICJkZWdyZWUiOiAiTUlUIiwKICAgICAgInR5cGUiOiAiQmFjaGVsb3JEZWdyZWUiCiAgICB9LAogICAgImlkIjogImRpZDppb246RWlCeVFBeFhtT0FFTTdUbEVVRzl1NlJyMnJzNVFDTHh1T2ZWbE5ONXpUM1JtUTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKaFpHUXRjSFZpYkdsakxXdGxlWE1pTENKd2RXSnNhV05MWlhseklqcGJleUpwWkNJNklsOXVWVGx5YmtGcGNIZzNNRmhYU0RSS2RVcDNUMVZwV2paNFNVWk5ZVEZuUm1oS1VWVXdWSFoyTTFFaUxDSndkV0pzYVdOTFpYbEtkMnNpT25zaVkzSjJJam9pVUMwek9EUWlMQ0pyYVdRaU9pSmZibFU1Y201QmFYQjROekJZVjBnMFNuVktkMDlWYVZvMmVFbEdUV0V4WjBab1NsRlZNRlIyZGpOUklpd2lhM1I1SWpvaVJVTWlMQ0o0SWpvaU5sYzNkSEZhTVhabFRUZFpTRTU1ZEROME9YaE1PVGRNVDBoelp6a3libTV3WDJSc2FXTlFjVlJsY2pSdFNYaFplVFZ4TTA1MmVVa3djekkxV2tsRk9DSXNJbmtpT2lKRGExUlhaaTFZVlZwNkxWZDJOekl0TlhCdk9XZHBjblo2U25kNE1uRlRRMGt5Wm1GNWJIZFhjbXR0YW5wWE16RkVjV0ZIY2paRldrUXRkVmxEVTJkT0luMHNJbkIxY25CdmMyVnpJanBiSW1GMWRHaGxiblJwWTJGMGFXOXVJaXdpWVhOelpYSjBhVzl1VFdWMGFHOWtJbDBzSW5SNWNHVWlPaUpLYzI5dVYyVmlTMlY1TWpBeU1DSjlYWDFkTENKMWNHUmhkR1ZEYjIxdGFYUnRaVzUwSWpvaVJXbERNbWh0YzJSRk5IRk9kMjFUUTFCbU1qRjFNRXg2UWtodmMzQldVRkZzT1ZKdFRHczNORloxWnpWcmR5SjlMQ0p6ZFdabWFYaEVZWFJoSWpwN0ltUmxiSFJoU0dGemFDSTZJa1ZwUVMxWk9YVjBUVTVoYVVoVlIxcHROVEJtUVZKamNWTmFjVWRCTXpkNlFsWlVOVVk0UzBsdFh6TlRka0VpTENKeVpXTnZkbVZ5ZVVOdmJXMXBkRzFsYm5RaU9pSkZhVUpSYlVOaWRsOVZRelZSUmtOMk9VTndiMUpQYVZNNWJIWkNkbmR1WTJsWkxWOTRjbEU0U1RKdlIwSm5JbjBzSW5SNWNHVWlPaUpqY21WaGRHVWlmUSIsCiAgICAibmFtZSI6ICJKYXlkZW4gRG9lIiwKICAgICJzcG91c2UiOiAiZGlkOmV4YW1wbGU6YzI3NmUxMmVjMjFlYmZlYjFmNzEyZWJjNmYxIgogIH0sCiAgImV4cGlyYXRpb25EYXRlIjogIjIwMjUtMDMtMThUMjI6MDY6MzEuMzQyNTAxMDZaIiwKICAiaWQiOiAidXJuOnV1aWQ6N2NiYjliYTItMzgwMS00Nzg2LWI3YzEtYWQyZDQyOTI0M2E4IiwKICAiaXNzdWFuY2VEYXRlIjogIjIwMjQtMDMtMThUMjI6MDY6MzEuMzc1NDM0MTMxWiIsCiAgImlzc3VlciI6IHsKICAgICJpZCI6ICJkaWQ6aW9uOkVpQW9hSEpZRjJRNms5eml5aDRQRHJ1c0ladE5VS20xLWFGckF2clBEQ0VNU1E6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSmhaR1F0Y0hWaWJHbGpMV3RsZVhNaUxDSndkV0pzYVdOTFpYbHpJanBiZXlKcFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpsSWpvaVFWRkJRaUlzSW10cFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW10MGVTSTZJbEpUUVNJc0ltNGlPaUp1V1RCaU5tRkdURTVWYW1RM2VFRnBiVnA0ZFVnd1prNVNUVWN6WldOSFdHOVpTM1JSV0RSMVMxRmpjMWxaVTI1R2FFRkRlRGhtYW5Cbk5qSkplVnBDZFZSUVlUWnVaM04yWDA1UlFraFVXVWxNUmpKUldWVlFhVFJRVkVzdGVVRTNUbVJQU2xad05VRkZWelZ5YzNsc1pUZ3dTeTFQVUdsVVVXMUtSMng1VWpoSlVrWklOazl0TTNNMllYTnRXVmw1WmtrdExXdFJaVGhJYTBkMGJYYzNUR2xpVldWWlduZDNSemxJU1d4NlNEUlFObWhJVDNOVVpsaGhSRmwyYkRVM1dXcDFibWd5Y1ZVeVlXbFhSRms1UVdsVVVrSnBiVXhDYUVsdVpuWTBOVTVZZVRsMWQzSkdPWE5aYkZodWJHZDViVlZUTVZGV2NUYzVWUzFsYmt0bVJYTXpSMnMyUlVSaVYzSkhiWHBIY0hSUFpHcHpkRWt3T1dWNGFWUklPRXRyZG5oT1JXaFFla3hrZW5rd1VGTmxSV1o0WDJsVFdXeE1aRmxKUmtWVU4yb3hNVTV2TFRGeU1rVjZRa05wVVRWdWRGSXhNMUVpZlN3aWNIVnljRzl6WlhNaU9sc2lZWFYwYUdWdWRHbGpZWFJwYjI0aUxDSmhjM05sY25ScGIyNU5aWFJvYjJRaVhTd2lkSGx3WlNJNklrcHpiMjVYWldKTFpYa3lNREl3SW4xZGZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVORVJsRlFabmhTUm5Oc2NrRkliVm80YVhOUWJFNDRRelpLVW1SemVuUTBabkkwYURsSU5HUm1NVlpuSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEJTRE14TkZkcVJUTkJhVFJoTFhNeVNtOUhjWFpqUzFWcFRFUmZNVEpWWjNWeFkwWTFiVWR6Um5aMFp5SXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFTMXBka1pxVFRaTGRteDJZVXRVT0dkQ2EzQkphelpMWmt4dk9VbDVNR28wWVdobWFpMXJUMGwyV2xFaWZTd2lkSGx3WlNJNkltTnlaV0YwWlNKOSIsCiAgICAibmFtZSI6ICJBY21lIElzc3VlciIKICB9LAogICJ0eXBlIjogWwogICAgIlZlcmlmaWFibGVDcmVkZW50aWFsIiwKICAgICJVbml2ZXJzaXR5RGVncmVlQ3JlZGVudGlhbCIKICBdCn0= | @oidc4vc_rest_pre_auth_flow_compose_with_attachment Scenario Outline: OIDC credential issuance and verification Pre Auth flow (attachment evidence) diff --git a/test/bdd/fixtures/profile/profiles.json b/test/bdd/fixtures/profile/profiles.json index 560159d9f..c1db11fdf 100644 --- a/test/bdd/fixtures/profile/profiles.json +++ b/test/bdd/fixtures/profile/profiles.json @@ -134,6 +134,66 @@ } }, "createDID": true + },{ + "issuer": { + "id": "playground_issuer", + "version": "v1.0", + "groupID": "playground_issuer", + "name": "Acme Issuer", + "organizationID": "00000000-0000-0000-0000-000000000001", + "url": "http://vc-rest-echo.trustbloc.local:8075", + "webHook": "http://vcs.webhook.example.com:8180", + "active": true, + "vcConfig": { + "refreshServiceEnabled": false, + "signingAlgorithm": "Ed25519Signature2020", + "signatureRepresentation": 0, + "keyType": "ED25519", + "format": "ldp", + "didMethod": "ion", + "status": { + "type": "StatusList2021Entry" + }, + "sdjwt": { + "enable": false + }, + "dataIntegrityProof": { + "enable": true, + "suiteType": "eddsa-rdfc-2022" + } + }, + "oidcConfig": { + "client_id": "7d4u50e7w6nfq8tfayhzplgjf", + "client_secret_handle": "282ks4fkuqfosus5k0x30abnv", + "issuer_well_known": "http://cognito-mock.trustbloc.local:9229/local_5a9GzRvB/.well-known/openid-configuration", + "scopes_supported": [ + "openid", + "profile" + ], + "grant_types_supported": [ + "authorization_code", + "urn:ietf:params:oauth:grant-type:pre-authorized_code" + ], + "response_types_supported": [ + "code" + ], + "token_endpoint_auth_methods_supported": [ + "none" + ], + "enable_dynamic_client_registration": true, + "enable_discoverable_client_id_scheme": true, + "pre-authorized_grant_anonymous_access_supported": true, + "wallet_initiated_auth_flow_supported": true, + "dynamic_well_known_supported" : true, + "claims_endpoint": "https://mock-login-consent.example.com:8099/claim-data?credentialType=UniversityDegreeCredential" + }, + "kmsConfig" : { + "kmsType" : "local" + }, + "credentialTemplates": [], + "credentialMetadata": {} + }, + "createDID": true }, { "issuer": { diff --git a/test/bdd/pkg/v1/oidc4vc/oidc4vci.go b/test/bdd/pkg/v1/oidc4vc/oidc4vci.go index 723e2b09f..cc05fdece 100644 --- a/test/bdd/pkg/v1/oidc4vc/oidc4vci.go +++ b/test/bdd/pkg/v1/oidc4vc/oidc4vci.go @@ -147,7 +147,7 @@ func (s *Steps) runOIDC4VCIPreAuth(initiateOIDC4CIResponseData initiateOIDC4VCIR opts = append(opts, options...) - if !s.useCredentialOfferCredConfigIDForCredentialRequest { + if !s.useCredentialOfferCredConfigIDForCredentialRequest && !s.issuerProfile.OIDCConfig.DynamicWellKnownSupported { credentialTypes := strings.Split(s.issuedCredentialType, ",") // Set option filters