From 2276e28e1474bb36f488c7c26618cb2c4acb6e3b Mon Sep 17 00:00:00 2001 From: Mykhailo Sizov Date: Tue, 12 Mar 2024 22:13:07 +0200 Subject: [PATCH] feat: butch preauth Signed-off-by: Mykhailo Sizov --- .../wallet-cli/pkg/oidc4vci/oidc4vci_flow.go | 136 ++++++++++-------- pkg/service/oidc4ci/api.go | 2 +- .../oidc4ci/oidc4ci_acknowledgement.go | 13 +- pkg/service/oidc4ci/oidc4ci_service.go | 2 +- pkg/storage/redis/ackstore/ackstore.go | 2 +- test/bdd/features/oidc4vc_api.feature | 19 +++ .../cognito-config/db/local_5a9GzRvB.json | 15 +- test/bdd/fixtures/docker-compose.yml | 2 +- test/bdd/pkg/v1/oidc4vc/oidc4vci.go | 73 ++++++++-- test/bdd/pkg/v1/oidc4vc/steps.go | 1 + 10 files changed, 184 insertions(+), 81 deletions(-) diff --git a/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go b/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go index b0d961036..2aa87ecb3 100644 --- a/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go +++ b/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go @@ -779,15 +779,9 @@ func (f *Flow) receiveVC( }) } - credentials := make([]*verifiable.Credential, 0, len(parseCredentialResponseDataList)) - - for _, credentialRsp := range parseCredentialResponseDataList { - vc, err := f.parseCredentialsResponse(credentialRsp, token, wellKnown) - if err != nil { - return nil, fmt.Errorf("parseCredentialsResponse: %w", err) - } - - credentials = append(credentials, vc) + credentials, err := f.parseCredentialsResponse(parseCredentialResponseDataList, token, wellKnown) + if err != nil { + return nil, fmt.Errorf("parseCredentialsResponse: %w", err) } return credentials, nil @@ -864,61 +858,69 @@ func (f *Flow) batchCredentialRequest( } func (f *Flow) parseCredentialsResponse( - parseCredentialResponseData *parseCredentialResponseData, + parseCredentialResponseDataList []*parseCredentialResponseData, token *oauth2.Token, wellKnown *issuerv1.WellKnownOpenIDIssuerConfiguration, -) (*verifiable.Credential, error) { - vcBytes, err := json.Marshal(parseCredentialResponseData.credential) - if err != nil { - return nil, fmt.Errorf("marshal credential response: %w", err) - } +) ([]*verifiable.Credential, error) { + notificationIDs := make([]string, 0, len(parseCredentialResponseDataList)) + credentials := make([]*verifiable.Credential, 0, len(parseCredentialResponseDataList)) - parsedVC, err := verifiable.ParseCredential(vcBytes, - verifiable.WithJSONLDDocumentLoader(f.documentLoader), - verifiable.WithDisabledProofCheck(), - ) - if err != nil { - return nil, fmt.Errorf("parse credential: %w", err) - } + for _, parseCredentialData := range parseCredentialResponseDataList { + vcBytes, err := json.Marshal(parseCredentialData.credential) + if err != nil { + return nil, fmt.Errorf("marshal credential response: %w", err) + } - if err = f.wallet.Add(vcBytes); err != nil { - return nil, fmt.Errorf("add credential to wallet: %w", err) - } + parsedVC, err := verifiable.ParseCredential(vcBytes, + verifiable.WithJSONLDDocumentLoader(f.documentLoader), + verifiable.WithDisabledProofCheck(), + ) + if err != nil { + return nil, fmt.Errorf("parse credential: %w", err) + } + + if err = f.wallet.Add(vcBytes); err != nil { + return nil, fmt.Errorf("add credential to wallet: %w", err) + } - var cslURL, statusListIndex, statusListType string + var cslURL, statusListIndex, statusListType string - if vcc := parsedVC.Contents(); vcc.Status != nil && vcc.Status.CustomFields != nil { - statusListType = vcc.Status.Type + if vcc := parsedVC.Contents(); vcc.Status != nil && vcc.Status.CustomFields != nil { + statusListType = vcc.Status.Type - u, ok := vcc.Status.CustomFields["statusListCredential"].(string) - if ok { - cslURL = u + u, ok := vcc.Status.CustomFields["statusListCredential"].(string) + if ok { + cslURL = u + } + + i, ok := vcc.Status.CustomFields["statusListIndex"].(string) + if ok { + statusListIndex = i + } } - i, ok := vcc.Status.CustomFields["statusListIndex"].(string) - if ok { - statusListIndex = i + predicate := func(item string, i int) bool { + return !strings.EqualFold(item, "VerifiableCredential") } - } - predicate := func(item string, i int) bool { - return !strings.EqualFold(item, "VerifiableCredential") - } + slog.Info("credential added to wallet", + "credential_id", parsedVC.Contents().ID, + "credential_type", strings.Join(lo.Filter(parsedVC.Contents().Types, predicate), ","), + "issuer_id", parsedVC.Contents().Issuer.ID, + "csl_url", cslURL, + "status_list_index", statusListIndex, + "status_list_type", statusListType, + ) - slog.Info("credential added to wallet", - "credential_id", parsedVC.Contents().ID, - "credential_type", strings.Join(lo.Filter(parsedVC.Contents().Types, predicate), ","), - "issuer_id", parsedVC.Contents().Issuer.ID, - "csl_url", cslURL, - "status_list_index", statusListIndex, - "status_list_type", statusListType, - ) + credentials = append(credentials, parsedVC) + notificationIDs = append(notificationIDs, lo.FromPtr(parseCredentialData.notificationID)) + } - if err = f.handleIssuanceAck(wellKnown, parseCredentialResponseData.notificationID, token); err != nil { - return nil, err + if err := f.handleIssuanceAck(wellKnown, notificationIDs, token); err != nil { + return nil, fmt.Errorf("handleIssuanceAck: %w", err) } - return parsedVC, nil + return credentials, nil } func (f *Flow) buildProof( @@ -1075,15 +1077,15 @@ func (f *Flow) getCredentialRequestOIDCCredentialFilters( func (f *Flow) handleIssuanceAck( wellKnown *issuerv1.WellKnownOpenIDIssuerConfiguration, - notificationID *string, + notificationIDs []string, token *oauth2.Token, ) error { - if wellKnown == nil || notificationID == nil { + if wellKnown == nil || len(notificationIDs) == 0 { return nil } notificationEndpoint := lo.FromPtr(wellKnown.NotificationEndpoint) - if notificationEndpoint == "" || lo.FromPtr(notificationID) == "" { + if notificationEndpoint == "" { return nil } @@ -1093,20 +1095,28 @@ func (f *Flow) handleIssuanceAck( }() slog.Info("Sending wallet notification", - "notification_id", notificationID, + "notification_ids", notificationIDs, "endpoint", notificationEndpoint, ) - b, err := json.Marshal(oidc4civ1.AckRequest{ - Credentials: []oidc4civ1.AcpRequestItem{ - { - NotificationId: *notificationID, - EventDescription: nil, - Event: "credential_accepted", - IssuerIdentifier: wellKnown.CredentialIssuer, - }, - }, - }) + ackRequest := oidc4civ1.AckRequest{ + Credentials: []oidc4civ1.AcpRequestItem{}, + } + + for _, notificationID := range notificationIDs { + if notificationID == "" { + continue + } + + ackRequest.Credentials = append(ackRequest.Credentials, oidc4civ1.AcpRequestItem{ + Event: "credential_accepted", + EventDescription: nil, + IssuerIdentifier: wellKnown.CredentialIssuer, + NotificationId: notificationID, + }) + } + + b, err := json.Marshal(ackRequest) if err != nil { return err } diff --git a/pkg/service/oidc4ci/api.go b/pkg/service/oidc4ci/api.go index 980ce2f8a..584cfdf8c 100644 --- a/pkg/service/oidc4ci/api.go +++ b/pkg/service/oidc4ci/api.go @@ -345,7 +345,7 @@ type Ack struct { HashedToken string `json:"hashed_token"` ProfileID string `json:"profile_id"` ProfileVersion string `json:"profile_version"` - TxID TxID `json:"tx_id"` + TxID string `json:"tx_id"` // [tx ID]-[short uuid] WebHookURL string `json:"webhook_url"` OrgID string `json:"org_id"` } diff --git a/pkg/service/oidc4ci/oidc4ci_acknowledgement.go b/pkg/service/oidc4ci/oidc4ci_acknowledgement.go index 40a10d4bd..c4b029485 100644 --- a/pkg/service/oidc4ci/oidc4ci_acknowledgement.go +++ b/pkg/service/oidc4ci/oidc4ci_acknowledgement.go @@ -10,6 +10,7 @@ import ( "context" "errors" "fmt" + "github.com/google/uuid" "strings" "github.com/trustbloc/vcs/pkg/event/spi" @@ -86,7 +87,7 @@ func (s *AckService) HandleAckNotFound( eventPayload.Error = req.EventDescription } - err = s.sendEvent(ctx, spi.IssuerOIDCInteractionAckExpired, TxID(req.ID), eventPayload) + err = s.sendEvent(ctx, spi.IssuerOIDCInteractionAckExpired, extractTransactionID(req.ID), eventPayload) if err != nil { return err } @@ -132,7 +133,7 @@ func (s *AckService) Ack( return err } - err = s.sendEvent(ctx, targetEvent, ack.TxID, eventPayload) + err = s.sendEvent(ctx, targetEvent, extractTransactionID(ack.TxID), eventPayload) if err != nil { return err } @@ -170,3 +171,11 @@ func (s *AckService) AckEventMap(status string) (spi.EventType, error) { return spi.IssuerOIDCInteractionAckFailed, fmt.Errorf("invalid status: %s", status) } + +func extractTransactionID(ackTxID string) TxID { + return TxID(strings.Split(ackTxID, "_")[0]) +} + +func generateAckTxID(transactionID TxID) string { + return fmt.Sprintf("%s_%s", transactionID, strings.Split(uuid.NewString(), "-")[0]) +} diff --git a/pkg/service/oidc4ci/oidc4ci_service.go b/pkg/service/oidc4ci/oidc4ci_service.go index fe46c8e48..e75d3c859 100644 --- a/pkg/service/oidc4ci/oidc4ci_service.go +++ b/pkg/service/oidc4ci/oidc4ci_service.go @@ -879,7 +879,7 @@ func (s *Service) prepareCredential( //nolint:funlen HashedToken: prepareCredentialRequest.HashedToken, ProfileID: tx.ProfileID, ProfileVersion: tx.ProfileVersion, - TxID: tx.ID, + TxID: generateAckTxID(tx.ID), WebHookURL: tx.WebHookURL, OrgID: tx.OrgID, }) diff --git a/pkg/storage/redis/ackstore/ackstore.go b/pkg/storage/redis/ackstore/ackstore.go index 328172728..a99a62572 100644 --- a/pkg/storage/redis/ackstore/ackstore.go +++ b/pkg/storage/redis/ackstore/ackstore.go @@ -40,7 +40,7 @@ func (s *Store) Create( ctx context.Context, ack *oidc4ci.Ack, ) (string, error) { - id := string(ack.TxID) // for now, it should much with TxID before new spec. + id := ack.TxID b, err := json.Marshal(ack) if err != nil { diff --git a/test/bdd/features/oidc4vc_api.feature b/test/bdd/features/oidc4vc_api.feature index 116127e1c..7a9334abb 100644 --- a/test/bdd/features/oidc4vc_api.feature +++ b/test/bdd/features/oidc4vc_api.feature @@ -89,6 +89,25 @@ Feature: OIDC4VC REST API | bank_issuer/v1.0 | UniversityDegreeCredential_001,CrudeProductCredential_001,VerifiedEmployeeCredential_001 | 3 | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id | | bank_issuer/v1.0 | UniversityDegreeCredential_001,CrudeProductCredential_001 | 2 | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id | + @oidc4vc_rest_preauth_flow_bunch_credential_filters + Scenario Outline: OIDC Batch credential issuance and verification Pre Auth flow (request all credentials by credential type) + Given Profile "" issuer has been authorized with username "profile-user-issuer-1" and password "profile-user-issuer-1-pwd" + And User holds credential "" with templateID "nil" + And Profile "" verifier has been authorized with username "profile-user-verifier-1" and password "profile-user-verifier-1-pwd" + + When User interacts with Wallet to initiate bunch credential issuance using pre authorization code flow + Then "" credentials are issued + Then User interacts with Verifier and initiate OIDC4VP interaction under "" profile with presentation definition ID "" and fields "" + And Verifier with profile "" retrieves interactions claims + Then we wait 2 seconds + And Verifier with profile "" requests deleted interactions claims + + Examples: + | issuerProfile | credentialType | issuedCredentialsAmount | verifierProfile | presentationDefinitionID | fields | +# SDJWT issuer, JWT verifier, no limit disclosure in PD query. + | bank_issuer/v1.0 | UniversityDegreeCredential,CrudeProductCredential,VerifiedEmployee | 3 | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id | + | bank_issuer/v1.0 | UniversityDegreeCredential,CrudeProductCredential | 2 | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id | + @oidc4vc_rest_auth_flow_credential_conf_id Scenario Outline: OIDC credential issuance and verification Auth flow using credential configuration ID to request specific credential type Given Profile "" issuer has been authorized with username "profile-user-issuer-1" and password "profile-user-issuer-1-pwd" diff --git a/test/bdd/fixtures/cognito-config/db/local_5a9GzRvB.json b/test/bdd/fixtures/cognito-config/db/local_5a9GzRvB.json index 41fd8242d..c55ba614c 100644 --- a/test/bdd/fixtures/cognito-config/db/local_5a9GzRvB.json +++ b/test/bdd/fixtures/cognito-config/db/local_5a9GzRvB.json @@ -25,7 +25,14 @@ "UserStatus": "CONFIRMED", "UserCreateDate": "2023-09-06T09:10:29.107Z", "UserLastModifiedDate": "2023-09-06T09:10:29.107Z", - "RefreshTokens": [] + "RefreshTokens": [ + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2duaXRvOnVzZXJuYW1lIjoicHJvZmlsZS11c2VyLWlzc3Vlci0xIiwiZW1haWwiOiJwcm9maWxlLXVzZXItaXNzdWVyLTFAZXhhbXBsZS5jb20iLCJpYXQiOjE3MTAyNzM4MDcsImp0aSI6ImZlYmI1Yjg1LTk3NWQtNDRjOS1iYTdjLWQyM2VkMTFjN2Q4YyIsImV4cCI6MTcxMDg3ODYwNywiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MjI5L2xvY2FsXzVhOUd6UnZCIn0.kE2iy9i2JsMuPWyP3QLt2r6A6NJiZQbDr1lnAsTqYK7LZFmavDHuQDbhk3Nt40JCPo9NO4aE74aaFLsXWlCm5STFY_7rOwbWCt_58xA0pRZf0LgSgpznqjDjOC3H4Y20K2SDdoKNLhhwZtyDPjdaWw6Uj7I-9ZwN08N-QeRxTh_0QA6MpEZCoiQDwAwOrul2rtGpsIXEG0afl0uCOWPfZngHrNUvn9iIRY8LonqE2-vwO3mqP6lTpaF9v5DdhyEwIc_ExzGd_l8v9tpv9nWFHPF1KmYllGj8bm6S5w7jdYwbYR_IExNbCQBrZ7_K8esG-yyOSFRDHDMlhPAGbaye5w", + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2duaXRvOnVzZXJuYW1lIjoicHJvZmlsZS11c2VyLWlzc3Vlci0xIiwiZW1haWwiOiJwcm9maWxlLXVzZXItaXNzdWVyLTFAZXhhbXBsZS5jb20iLCJpYXQiOjE3MTAyNzM5ODEsImp0aSI6IjU1MjdiNjBhLWFiNDYtNDg3MS05ZGY2LWM1M2QzNTM4NDVmNiIsImV4cCI6MTcxMDg3ODc4MSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MjI5L2xvY2FsXzVhOUd6UnZCIn0.BC5QZxai-2hjHeX1t7wQ9vfKXYJDqkvlDMlxB1Bb9Sk9UGOn6oL0j6zzshXap-jQFOxMF7uNvGOsg83JKkpygR-SJryql31tjbWvBxUEF8m9_j82A2nwwSuT7WxGCgEPAu3spC3UnNI0Js-y0G7hxSuyZxmLVvlsL9LCbWBagLgIgGy7_zfG8E0qvS_x-dKAoSKcimWkLbLmXuiyMC6x7N_h29CgZ0KxGNYDJsuejIVrbzYZ6T0-M7UFNQahvR4UTqBEXGZD7Zr_1-MChqD82dTKbRPEiZE_q-TyCvgHi5RMDnecpghD1Pk6nYWsUKiFYSlxIU8ZUZTZQqwYAmEELg", + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2duaXRvOnVzZXJuYW1lIjoicHJvZmlsZS11c2VyLWlzc3Vlci0xIiwiZW1haWwiOiJwcm9maWxlLXVzZXItaXNzdWVyLTFAZXhhbXBsZS5jb20iLCJpYXQiOjE3MTAyNzQxNjEsImp0aSI6IjZmZTZlYTYxLTFjNWItNGU3Zi1iNzZkLTdjYmYxNDMxNmViMSIsImV4cCI6MTcxMDg3ODk2MSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MjI5L2xvY2FsXzVhOUd6UnZCIn0.gN3RaQTo3FMMd9hs6VBYCko_D5TfKKsKldwR_fdboksBur_cMVM9dJhlFK9-jLtsAepbAGa08A9df9FTApaKzAZ-t0X1gKWbOlD7SRWzHJC1I917UhYb37Y2eZOZfknIg75Ucav-LhVvGhUQCMC9mR7Wfy2oQRMyQf30R7sDm7Mg969oEmGZKhmvN1GlnJhvPnunQjRLdHhOd00tDAh3dYjU4-Q-25GROzQVQ-HTI0a0kGD_dLreOH7lcxXUrOEZH6YGUIPxFB8crmDvJijgExGE87MltBBB-9owAImnu7iqsvmbQVGRHpDz0EJ0APaWBsTNXdiuFojvJrbk6Q74PQ", + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2duaXRvOnVzZXJuYW1lIjoicHJvZmlsZS11c2VyLWlzc3Vlci0xIiwiZW1haWwiOiJwcm9maWxlLXVzZXItaXNzdWVyLTFAZXhhbXBsZS5jb20iLCJpYXQiOjE3MTAyNzQxOTIsImp0aSI6IjNjOGZiNWM4LTY1NDYtNDhhMi1iZjExLWFiM2M2YjFjYmI2YiIsImV4cCI6MTcxMDg3ODk5MiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MjI5L2xvY2FsXzVhOUd6UnZCIn0.WdZNIYoTy6RvFLQHRkSFxAPeDRbn19ypO_-HvV8iXTvFEYMQNQErX7a21WWYK90LcFVjdrn3z6WDjZlDQDRrIQaFJwUna5BQKt1Qg4r9O0WJAD2gpATfSLxcBfFOHkxvh51fVHM91RP9gbU3b3n4RGQJTLeoLSTg-r5JFrT2wPH4VmLU1ZWopFV3uQCwPbd7TyAsu3AfttOu1DjDiZn3k06f6VJcJajgQ3D4l0cHglV-7EPkm-YP1U-upvwbIIId7yBXknpqHU-EEJ5FlZLyllBa6gzF6PrndjSV7v9Xb07TvcJ4hDg0w9M8nuHBuju40oy_BKQYzOoLd7RsDlQpDQ", + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2duaXRvOnVzZXJuYW1lIjoicHJvZmlsZS11c2VyLWlzc3Vlci0xIiwiZW1haWwiOiJwcm9maWxlLXVzZXItaXNzdWVyLTFAZXhhbXBsZS5jb20iLCJpYXQiOjE3MTAyNzQyNDgsImp0aSI6ImY2NzkxMDI5LTE4NjAtNGU1MC04ZGZlLWY4MDUzYjE0MjZjNiIsImV4cCI6MTcxMDg3OTA0OCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MjI5L2xvY2FsXzVhOUd6UnZCIn0.z7tVhJJE-7Y42wH_IxBF1yA5Ru48mLGM1nAhvgceZpLm9MGZWr6iTYC5E02owZFnELzcH3JkXrzYRz9qO1mw6ksjbV9N2mQohkjZzRERYUJ87lEACA1D9VFnYjPvAUeCsJWf5PCWPbH63F-PjENCE2qLsVrxjEAEYgVN4BbWrMoVek00QrCUXJLxhkRm9naRcLwEHRxrvLAS6oqzYji8ZFipJPS4fm-tHkK-IdHBDw7Y9UCmxDXvYvagBLLZNFE94wYeoXKV7W9pQEJF1SSipW0dWMGjcfDc_r954AkDFr2VNznk6XZshX-AJkuoEJmhbRWVeWSaS1PSRAa0sqCEdg", + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2duaXRvOnVzZXJuYW1lIjoicHJvZmlsZS11c2VyLWlzc3Vlci0xIiwiZW1haWwiOiJwcm9maWxlLXVzZXItaXNzdWVyLTFAZXhhbXBsZS5jb20iLCJpYXQiOjE3MTAyNzQyOTIsImp0aSI6IjQ1MjdkOTU1LTY1ZmYtNDUwZi04ZWUxLTY3MGYwYTk4NjRiMiIsImV4cCI6MTcxMDg3OTA5MiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MjI5L2xvY2FsXzVhOUd6UnZCIn0.DBxZ1xMmbvQ5ApW_HJTQxE8qbZgIUxRAMl986DiV_FyNXJ_7x0a2mD65dXWj7mXsSLarHc7xfCI2o_vcORAAE_NLCaBUKfkJ5NLX56D0Dg1PzxfHvEb780rIuvSlgK-O1W_Q-l4kUGYLnSD5HytSP1ljqLLpj3tOV8kbqEV0K0uLN7kRRCmsaqxhzgQ8izIZxwC_45W4c2UfJH8S8f8L4_4-W-2WSbPwiMf3-BouS_CtF1juuwR2og3dRxCRtnv-tKOM_Z2lEejNH9goT1MeQ_4UdIJevGdnKQ1L3V9Gpg5Hpizh8iibuyFqeqWnH7fvSg1lCpSsFevjzNYWB5xSWw" + ] }, "profile-user-verifier-1": { "Username": "profile-user-verifier-1", @@ -52,7 +59,11 @@ "UserStatus": "CONFIRMED", "UserCreateDate": "2023-09-06T09:10:29.107Z", "UserLastModifiedDate": "2023-09-06T09:10:29.107Z", - "RefreshTokens": [] + "RefreshTokens": [ + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2duaXRvOnVzZXJuYW1lIjoicHJvZmlsZS11c2VyLXZlcmlmaWVyLTEiLCJlbWFpbCI6InByb2ZpbGUtdXNlci12ZXJpZmllci0xQGV4YW1wbGUuY29tIiwiaWF0IjoxNzEwMjczODE0LCJqdGkiOiI5YWVkMTBlNy1jMmI0LTQ3OGUtOTkwZi0yMzQ4Nzg5MTNmMGEiLCJleHAiOjE3MTA4Nzg2MTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6OTIyOS9sb2NhbF81YTlHelJ2QiJ9.DzDoen1x3dvPzAA3o12L-LIOrFLm4vXz2I1Bkpca_y4YosoLMV3OiSM8RU0toVomEhNratd4TCM_48H-hSfav6PGc6SzWDqeHFY8H8tD-HevwCV6WFBHBIRw2PINXR1Pt3DssPKOZ5B6V6HsznVRkyRv5z6lvSl-9DqX_rLihzDZ5qndaRvGSE71A4qxQXhNAW7dXYCBDrc1Q5j0IY86qIbtxahi_60FOKipuC9S_hH5vbIVcCceDne6PzDuukgAVWKuGzdyTqrD9ifG9s2pGyjUi9CdXfZoMiti5G8xJ8t5TBAKkBYIQgRVRRNFS6pSGMMdsfHhxzxBqACs0kg9cg", + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2duaXRvOnVzZXJuYW1lIjoicHJvZmlsZS11c2VyLXZlcmlmaWVyLTEiLCJlbWFpbCI6InByb2ZpbGUtdXNlci12ZXJpZmllci0xQGV4YW1wbGUuY29tIiwiaWF0IjoxNzEwMjc0MTY2LCJqdGkiOiI2M2RiZjE5Zi1jMzYxLTRhMmUtYjAxMi00NDZjNTViOTMwODciLCJleHAiOjE3MTA4Nzg5NjYsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6OTIyOS9sb2NhbF81YTlHelJ2QiJ9.KYlQxTDB5OyjAY9L3IupSpytgTsx4l5B9C4q5qns_uwu1UPHSoM-cq6btYKqtHoPgpingh9jm_NTC5khUv6v74Vqq1RPt5OldB_lr_TjJmkDRzDO0LWMJooEkqjftk-b1hMBJV6KwMImbIyam2sod5n_RtYSfFWDOek2kLVnueHRphw-zCziJ1O-JZRm5kiabzA_vaFoN6-CZ3yptpwdOnoiITg88gtVblwO3BZopYvHJVnWKV-JZWk1_i9pS5UaR_ez6p0cdhf-dvHgFbrN5cC3s9klFZ4IqSwD1u1Z5CjVibjz5A_gY_WdqHTBwSRnoPGUcpLPEDPVTpaCUJScrw", + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2duaXRvOnVzZXJuYW1lIjoicHJvZmlsZS11c2VyLXZlcmlmaWVyLTEiLCJlbWFpbCI6InByb2ZpbGUtdXNlci12ZXJpZmllci0xQGV4YW1wbGUuY29tIiwiaWF0IjoxNzEwMjc0MjUzLCJqdGkiOiI5MWY0NDBiYS0xNTI2LTQ4YzgtODEzZi05NDk4MmMyZjIzODciLCJleHAiOjE3MTA4NzkwNTMsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6OTIyOS9sb2NhbF81YTlHelJ2QiJ9.IqjrKDeVj8HwuSg9hmQ6MV7wS_inpE7F-TWsMjG7t-UOe-xpl6ImthnI0KFGuTNo17qVmRQ9d9pRVc2wDvoTVqBuyO9MuNlH9jyTNdSISFFFICkfYgxArLC4Fve7iCViKxwCY2qUpyQ-SKPumRr6lPj1qGxc9GTg3nKqMV9tXcx-_QG0yOBlCxT9bcMrP1Mq0-LOjAYMhq9XvYiTn_rRHnTRhJMx1rD1WP-m_UOwSbNo1dyC1VTKG6YEr-x6iB1Va20Mtzds-2ckdiNW7rXf7uh8BHP4cS8JbWEnvPWztOsKknEPClPJ2ZIAZRvgZNelfNENSdGA8_bCKK9c4d2HFw" + ] }, "profile-user-non-issuer-1": { "Username": "profile-user-non-issuer-1", diff --git a/test/bdd/fixtures/docker-compose.yml b/test/bdd/fixtures/docker-compose.yml index 65109645f..ae15daa2f 100644 --- a/test/bdd/fixtures/docker-compose.yml +++ b/test/bdd/fixtures/docker-compose.yml @@ -43,7 +43,7 @@ services: - VC_SYSTEM_VERSION=v1.0.0 - VC_REST_DATA_ENCRYPTION_KEY_ID=bc436485-5092-42b8-92a3-0aa8b93536dc - VC_REST_ENABLE_PROFILER=true - - VC_TRANSIENT_DATA_STORE_TYPE=mongo + - VC_TRANSIENT_DATA_STORE_TYPE=redis - VC_REDIS_URL=redis.example.com:6379 - VC_REDIS_DISABLE_TLS=true ports: diff --git a/test/bdd/pkg/v1/oidc4vc/oidc4vci.go b/test/bdd/pkg/v1/oidc4vc/oidc4vci.go index aef218b6b..e55e83427 100644 --- a/test/bdd/pkg/v1/oidc4vc/oidc4vci.go +++ b/test/bdd/pkg/v1/oidc4vc/oidc4vci.go @@ -128,7 +128,7 @@ func (s *Steps) checkInitiateIssuanceURL(initiateIssuanceURL string) error { return nil } -func (s *Steps) runOIDC4VCIPreAuth(initiateOIDC4CIRequest initiateOIDC4VCIRequest) error { +func (s *Steps) runOIDC4VCIPreAuth(initiateOIDC4CIRequest initiateOIDC4VCIRequest, options ...oidc4vci.Opt) error { initiateOIDC4CIResponseData, err := s.initiateCredentialIssuance(initiateOIDC4CIRequest) if err != nil { return fmt.Errorf("init credential issuance: %w", err) @@ -137,10 +137,19 @@ func (s *Steps) runOIDC4VCIPreAuth(initiateOIDC4CIRequest initiateOIDC4VCIReques opts := []oidc4vci.Opt{ oidc4vci.WithFlowType(oidc4vci.FlowTypePreAuthorizedCode), oidc4vci.WithCredentialOffer(initiateOIDC4CIResponseData.OfferCredentialURL), - oidc4vci.WithCredentialFilter(s.issuedCredentialType, s.getIssuerOIDCCredentialFormat(s.issuedCredentialType)), oidc4vci.WithPin(*initiateOIDC4CIResponseData.UserPin), } + opts = append(opts, options...) + + credentialTypes := strings.Split(s.issuedCredentialType, ",") + + // Set option filters + for _, credentialType := range credentialTypes { + format := s.getIssuerOIDCCredentialFormat(credentialType) + opts = append(opts, oidc4vci.WithCredentialFilter(credentialType, format)) + } + opts = s.addProofBuilder(opts) flow, err := oidc4vci.NewFlow(s.oidc4vciProvider, @@ -586,15 +595,9 @@ func (s *Steps) runOIDC4VCIAuthBunch() error { } // Set option filters - profileCredentialConf := s.issuerProfile.CredentialMetaData.CredentialsConfigurationSupported for _, credentialType := range credentialTypes { - for _, credentialConf := range profileCredentialConf { - if !lo.Contains(credentialConf.CredentialDefinition.Type, credentialType) { - continue - } - - opts = append(opts, oidc4vci.WithCredentialFilter(credentialType, credentialConf.Format)) - } + format := s.getIssuerOIDCCredentialFormat(credentialType) + opts = append(opts, oidc4vci.WithCredentialFilter(credentialType, format)) } opts = s.addProofBuilder(opts) @@ -653,6 +656,15 @@ func (s *Steps) runOIDC4VCIAuthBunchWithScopes(scopes string) error { return nil } +func (s *Steps) runOIDC4VCIPreAuthBunch() error { + initiateRequest, err := s.getInitiatePreAuthIssuanceRequestOfAllSupportedCredentials() + if err != nil { + return fmt.Errorf("getInitiatePreAuthIssuanceRequestOfAllSupportedCredentials: %w", err) + } + + return s.runOIDC4VCIPreAuth(*initiateRequest, oidc4vci.WithBatchCredentialIssuance()) +} + // getInitiateAuthIssuanceRequestOfAllSupportedCredentials returns Initiate issuance request body // for all supported credential types by given Issuer. // Returned structure contains CredentialConfiguration field, that is aimed for batch credentials issuance. @@ -690,6 +702,47 @@ func (s *Steps) getInitiateAuthIssuanceRequestOfAllSupportedCredentials() (*init return initiateRequest, nil } +// getInitiatePreAuthIssuanceRequestOfAllSupportedCredentials returns Pre Auth Initiate issuance request body +// for all supported credential types by given Issuer. +// Returned structure contains CredentialConfiguration field, that is aimed for batch credentials issuance. +func (s *Steps) getInitiatePreAuthIssuanceRequestOfAllSupportedCredentials() (*initiateOIDC4VCIRequest, error) { + initiateRequest := &initiateOIDC4VCIRequest{ + GrantType: preAuthorizedCodeGrantType, + OpState: uuid.New().String(), + ResponseType: "code", + Scope: []string{"openid", "profile"}, + UserPinRequired: true, + CredentialConfiguration: map[string]InitiateIssuanceCredentialConfiguration{}, + } + + profileCredentialConf := s.issuerProfile.CredentialMetaData.CredentialsConfigurationSupported + for credentialConfigurationID, credentialConf := range profileCredentialConf { + credentialType := credentialConf.CredentialDefinition.Type[1] + + credentialTemplate, ok := lo.Find(s.issuerProfile.CredentialTemplates, func(item *profileapi.CredentialTemplate) bool { + return item.Type == credentialType + }) + + if !ok { + return nil, fmt.Errorf("unable to find credential template with type %s", credentialTemplate) + } + + claims, err := s.fetchClaimData(credentialType) + if err != nil { + return nil, fmt.Errorf("fetchClaimData: %w", err) + } + + initiateRequest.CredentialConfiguration[credentialConfigurationID] = InitiateIssuanceCredentialConfiguration{ + ClaimData: claims, + CredentialTemplateId: credentialTemplate.ID, + } + + initiateRequest.Scope = append(initiateRequest.Scope, credentialConf.Scope) + } + + return initiateRequest, nil +} + func (s *Steps) runOIDC4VCIAuthWithCredentialConfigurationID(credentialConfigurationIDRaw string) error { resp, err := s.initiateCredentialIssuance(s.getInitiateIssuanceRequestAuthFlow()) if err != nil { diff --git a/test/bdd/pkg/v1/oidc4vc/steps.go b/test/bdd/pkg/v1/oidc4vc/steps.go index f0d4671ad..4034b46d1 100644 --- a/test/bdd/pkg/v1/oidc4vc/steps.go +++ b/test/bdd/pkg/v1/oidc4vc/steps.go @@ -99,6 +99,7 @@ func (s *Steps) RegisterSteps(sc *godog.ScenarioContext) { sc.Step(`^User interacts with Wallet to initiate credential issuance using authorization code flow with client registration method "([^"]*)"$`, s.runOIDC4CIAuthWithClientRegistrationMethod) sc.Step(`^User interacts with Wallet to initiate credential issuance using authorization code flow with wallet-initiated$`, s.runOIDC4VCIAuthWalletInitiatedFlow) sc.Step(`^User interacts with Wallet to initiate credential issuance using pre authorization code flow$`, s.runOIDC4CIPreAuthWithValidClaims) + sc.Step(`^User interacts with Wallet to initiate bunch credential issuance using pre authorization code flow$`, s.runOIDC4VCIPreAuthBunch) sc.Step(`^User interacts with Wallet to initiate credential issuance using authorization code flow with invalid claims schema$`, s.runOIDC4VCIAuthWithInvalidClaims) sc.Step(`^User interacts with Wallet to initiate credential issuance using pre authorization code flow with client attestation enabled$`, s.runOIDC4CIPreAuthWithClientAttestation) sc.Step(`^proofType is "([^"]*)"$`, s.setProofType)