From b56311cc59566c69a384154a5a6d1695acbe6719 Mon Sep 17 00:00:00 2001 From: Mykhailo Sizov Date: Thu, 1 Feb 2024 15:51:09 +0200 Subject: [PATCH] feat: 7.2. Credential Request udpates Signed-off-by: Mykhailo Sizov --- api/spec/openapi.gen.go | 352 +++++++++--------- docs/v1/common.yaml | 5 + docs/v1/openapi.yaml | 14 + .../wrappers/oidc4ci/oidc4ci_wrapper.go | 2 +- pkg/restapi/v1/common/openapi.gen.go | 54 +-- pkg/restapi/v1/issuer/controller.go | 29 +- pkg/restapi/v1/issuer/controller_test.go | 189 +++++++++- pkg/restapi/v1/issuer/openapi.gen.go | 6 +- pkg/restapi/v1/oidc4ci/controller.go | 13 + pkg/restapi/v1/oidc4ci/controller_test.go | 125 ++++++- pkg/restapi/v1/oidc4ci/models.go | 14 - pkg/restapi/v1/oidc4ci/openapi.cfg.yaml | 2 + pkg/restapi/v1/oidc4ci/openapi.gen.go | 4 + pkg/service/oidc4ci/api.go | 29 +- .../oidc4ci/oidc4ci_service_exchange_code.go | 21 +- .../oidc4ci_service_exchange_code_test.go | 86 +++++ 16 files changed, 695 insertions(+), 250 deletions(-) diff --git a/api/spec/openapi.gen.go b/api/spec/openapi.gen.go index 96db26ffe..f18fb1e05 100644 --- a/api/spec/openapi.gen.go +++ b/api/spec/openapi.gen.go @@ -19,181 +19,183 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "//8XmN3CyfoAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/docs/v1/common.yaml b/docs/v1/common.yaml index 2dac53f2d..9e9e613a1 100644 --- a/docs/v1/common.yaml +++ b/docs/v1/common.yaml @@ -115,6 +115,11 @@ components: type: array items: type: string + credential_identifiers: + description: For Token response only. Array of strings, each uniquely identifying a Credential that can be issued using the Access Token returned in this response. Each of these Credentials corresponds to the same entry in the credential_configurations_supported Credential Issuer metadata but can contain different claim values or a different subset of claims within the claims set identified by that Credential type. + type: array + items: + type: string required: - type CredentialDefinition: diff --git a/docs/v1/openapi.yaml b/docs/v1/openapi.yaml index 2a5178a23..b68b319e0 100644 --- a/docs/v1/openapi.yaml +++ b/docs/v1/openapi.yaml @@ -1346,6 +1346,10 @@ components: properties: tx_id: type: string + authorization_details: + type: array + items: + $ref: './common.yaml#/components/schemas/AuthorizationDetails' required: - tx_id StoreAuthorizationCodeRequest: @@ -1416,6 +1420,11 @@ components: type: array items: type: string + authorization_details: + description: REQUIRED when authorization_details parameter is used to request issuance of a certain Credential type as defined in Section 5.1.1. It MUST NOT be used otherwise. It is an array of objects, as defined in Section 7 of [RFC9396]. + type: array + items: + $ref: './common.yaml#/components/schemas/AuthorizationDetails' required: - op_state - scopes @@ -1604,6 +1613,11 @@ components: c_nonce_expires_in: description: Integer denoting the lifetime in seconds of the c_nonce. type: integer + authorization_details: + description: REQUIRED when authorization_details parameter is used to request issuance of a certain Credential type as defined in Section 5.1.1. It MUST NOT be used otherwise. It is an array of objects, as defined in Section 7 of [RFC9396]. + type: array + items: + $ref: './common.yaml#/components/schemas/AuthorizationDetails' required: - access_token - token_type diff --git a/pkg/observability/tracing/wrappers/oidc4ci/oidc4ci_wrapper.go b/pkg/observability/tracing/wrappers/oidc4ci/oidc4ci_wrapper.go index 7628955d1..b933f45e5 100644 --- a/pkg/observability/tracing/wrappers/oidc4ci/oidc4ci_wrapper.go +++ b/pkg/observability/tracing/wrappers/oidc4ci/oidc4ci_wrapper.go @@ -70,7 +70,7 @@ func (w *Wrapper) StoreAuthorizationCode(ctx context.Context, opState string, co return w.svc.StoreAuthorizationCode(ctx, opState, code, flowData) } -func (w *Wrapper) ExchangeAuthorizationCode(ctx context.Context, opState, clientID, clientAttestationType, clientAttestation string) (oidc4ci.TxID, error) { +func (w *Wrapper) ExchangeAuthorizationCode(ctx context.Context, opState, clientID, clientAttestationType, clientAttestation string) (*oidc4ci.ExchangeAuthorizationCodeResult, error) { return w.svc.ExchangeAuthorizationCode(ctx, opState, clientID, clientAttestationType, clientAttestation) } diff --git a/pkg/restapi/v1/common/openapi.gen.go b/pkg/restapi/v1/common/openapi.gen.go index bb0bede38..76402f961 100644 --- a/pkg/restapi/v1/common/openapi.gen.go +++ b/pkg/restapi/v1/common/openapi.gen.go @@ -50,6 +50,9 @@ type AuthorizationDetails struct { // Object containing the detailed description of the credential type. CredentialDefinition *CredentialDefinition `json:"credential_definition,omitempty"` + // For Token response only. Array of strings, each uniquely identifying a Credential that can be issued using the Access Token returned in this response. Each of these Credentials corresponds to the same entry in the credential_configurations_supported Credential Issuer metadata but can contain different claim values or a different subset of claims within the claims set identified by that Credential type. + CredentialIdentifiers *[]string `json:"credential_identifiers,omitempty"` + // REQUIRED when CredentialConfigurationId parameter is not present. String identifying the format of the Credential the Wallet needs. This Credential format identifier determines further claims in the authorization details object needed to identify the Credential type in the requested format. It MUST NOT be present if credential_configuration_id parameter is present. Format *string `json:"format,omitempty"` @@ -118,30 +121,33 @@ type WalletInitiatedFlowData struct { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "///tjedAUhIAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/restapi/v1/issuer/controller.go b/pkg/restapi/v1/issuer/controller.go index c47bd1c64..6fc62a409 100644 --- a/pkg/restapi/v1/issuer/controller.go +++ b/pkg/restapi/v1/issuer/controller.go @@ -664,7 +664,7 @@ func (c *Controller) ExchangeAuthorizationCodeRequest(ctx echo.Context) error { return err } - txID, err := c.oidc4ciService.ExchangeAuthorizationCode(ctx.Request().Context(), + exchangeAuthorizationCodeResult, err := c.oidc4ciService.ExchangeAuthorizationCode(ctx.Request().Context(), body.OpState, lo.FromPtr(body.ClientId), lo.FromPtr(body.ClientAssertionType), @@ -674,7 +674,17 @@ func (c *Controller) ExchangeAuthorizationCodeRequest(ctx echo.Context) error { return util.WriteOutput(ctx)(nil, err) } - return util.WriteOutput(ctx)(ExchangeAuthorizationCodeResponse{TxId: string(txID)}, nil) + var authorizationDetailsDTOList []common.AuthorizationDetails + if exchangeAuthorizationCodeResult.AuthorizationDetails != nil { + authorizationDetailsDTO := exchangeAuthorizationCodeResult.AuthorizationDetails.ToDTO() + authorizationDetailsDTOList = []common.AuthorizationDetails{authorizationDetailsDTO} + } + + return util.WriteOutput(ctx)( + ExchangeAuthorizationCodeResponse{ + AuthorizationDetails: lo.ToPtr(authorizationDetailsDTOList), + TxId: string(exchangeAuthorizationCodeResult.TxID), + }, nil) } // ValidatePreAuthorizedCodeRequest Validates authorization code and pin. @@ -686,7 +696,7 @@ func (c *Controller) ValidatePreAuthorizedCodeRequest(ctx echo.Context) error { return err } - result, err := c.oidc4ciService.ValidatePreAuthorizedCodeRequest(ctx.Request().Context(), + transaction, err := c.oidc4ciService.ValidatePreAuthorizedCodeRequest(ctx.Request().Context(), body.PreAuthorizedCode, lo.FromPtr(body.UserPin), lo.FromPtr(body.ClientId), @@ -697,10 +707,17 @@ func (c *Controller) ValidatePreAuthorizedCodeRequest(ctx echo.Context) error { return err } + var authorizationDetailsDTOList []common.AuthorizationDetails + if transaction.AuthorizationDetails != nil { + authorizationDetailsDTO := transaction.AuthorizationDetails.ToDTO() + authorizationDetailsDTOList = []common.AuthorizationDetails{authorizationDetailsDTO} + } + return util.WriteOutput(ctx)(ValidatePreAuthorizedCodeResponse{ - TxId: string(result.ID), - OpState: result.OpState, - Scopes: result.Scope, + AuthorizationDetails: lo.ToPtr(authorizationDetailsDTOList), + TxId: string(transaction.ID), + OpState: transaction.OpState, + Scopes: transaction.Scope, }, nil) } diff --git a/pkg/restapi/v1/issuer/controller_test.go b/pkg/restapi/v1/issuer/controller_test.go index 790ddf8fe..0b6b60490 100644 --- a/pkg/restapi/v1/issuer/controller_test.go +++ b/pkg/restapi/v1/issuer/controller_test.go @@ -1315,26 +1315,107 @@ func TestController_StoreAuthZCode(t *testing.T) { } func TestController_ExchangeAuthorizationCode(t *testing.T) { - t.Run("success", func(t *testing.T) { + t.Run("success with CredentialDefinition", func(t *testing.T) { opState := uuid.NewString() mockOIDC4CIService := NewMockOIDC4CIService(gomock.NewController(t)) mockOIDC4CIService.EXPECT().ExchangeAuthorizationCode(gomock.Any(), opState, "", "", ""). - Return(oidc4ci.TxID("1234"), nil) + Return(&oidc4ci.ExchangeAuthorizationCodeResult{ + TxID: "TxID", + AuthorizationDetails: getTestAuthorizationDetails(t, true), + }, nil) c := &Controller{ oidc4ciService: mockOIDC4CIService, } - req := fmt.Sprintf(`{"op_state":"%s"}`, opState) //nolint:lll - ctx := echoContext(withRequestBody([]byte(req))) + recorder := httptest.NewRecorder() + + req := fmt.Sprintf(`{"op_state":"%s"}`, opState) + ctx := echoContext(withRecorder(recorder), withRequestBody([]byte(req))) + assert.NoError(t, c.ExchangeAuthorizationCodeRequest(ctx)) + + assert.Equal(t, "application/json; charset=UTF-8", recorder.Header().Get("Content-Type")) + + var exchangeResult ExchangeAuthorizationCodeResponse + + err := json.NewDecoder(recorder.Body).Decode(&exchangeResult) + assert.NoError(t, err) + + assert.Equal(t, "TxID", exchangeResult.TxId) + + assert.NotNil(t, exchangeResult.AuthorizationDetails) + + checkTestAuthorizationDetailsDTO(t, exchangeResult.AuthorizationDetails, true) + }) + t.Run("success without CredentialDefinition", func(t *testing.T) { + opState := uuid.NewString() + mockOIDC4CIService := NewMockOIDC4CIService(gomock.NewController(t)) + mockOIDC4CIService.EXPECT().ExchangeAuthorizationCode(gomock.Any(), opState, "", "", ""). + Return(&oidc4ci.ExchangeAuthorizationCodeResult{ + TxID: "TxID", + AuthorizationDetails: getTestAuthorizationDetails(t, false), + }, nil) + + c := &Controller{ + oidc4ciService: mockOIDC4CIService, + } + + recorder := httptest.NewRecorder() + + req := fmt.Sprintf(`{"op_state":"%s"}`, opState) + ctx := echoContext(withRecorder(recorder), withRequestBody([]byte(req))) + assert.NoError(t, c.ExchangeAuthorizationCodeRequest(ctx)) + + assert.Equal(t, "application/json; charset=UTF-8", recorder.Header().Get("Content-Type")) + + var exchangeResult ExchangeAuthorizationCodeResponse + + err := json.NewDecoder(recorder.Body).Decode(&exchangeResult) + assert.NoError(t, err) + + assert.Equal(t, "TxID", exchangeResult.TxId) + + assert.NotNil(t, exchangeResult.AuthorizationDetails) + + checkTestAuthorizationDetailsDTO(t, exchangeResult.AuthorizationDetails, false) + }) + + t.Run("success without AuthorizationDetails", func(t *testing.T) { + opState := uuid.NewString() + mockOIDC4CIService := NewMockOIDC4CIService(gomock.NewController(t)) + mockOIDC4CIService.EXPECT().ExchangeAuthorizationCode(gomock.Any(), opState, "", "", ""). + Return(&oidc4ci.ExchangeAuthorizationCodeResult{ + TxID: "TxID", + AuthorizationDetails: nil, + }, nil) + + c := &Controller{ + oidc4ciService: mockOIDC4CIService, + } + + recorder := httptest.NewRecorder() + + req := fmt.Sprintf(`{"op_state":"%s"}`, opState) + ctx := echoContext(withRecorder(recorder), withRequestBody([]byte(req))) assert.NoError(t, c.ExchangeAuthorizationCodeRequest(ctx)) + + assert.Equal(t, "application/json; charset=UTF-8", recorder.Header().Get("Content-Type")) + + var exchangeResult ExchangeAuthorizationCodeResponse + + err := json.NewDecoder(recorder.Body).Decode(&exchangeResult) + assert.NoError(t, err) + + assert.Equal(t, "TxID", exchangeResult.TxId) + + assert.Nil(t, exchangeResult.AuthorizationDetails) }) t.Run("error from service", func(t *testing.T) { opState := uuid.NewString() mockOIDC4CIService := NewMockOIDC4CIService(gomock.NewController(t)) mockOIDC4CIService.EXPECT().ExchangeAuthorizationCode(gomock.Any(), opState, "", "", ""). - Return(oidc4ci.TxID(""), errors.New("unexpected error")) + Return(nil, errors.New("unexpected error")) c := &Controller{ oidc4ciService: mockOIDC4CIService, @@ -1355,13 +1436,15 @@ func TestController_ExchangeAuthorizationCode(t *testing.T) { } func TestController_ValidatePreAuthorizedCodeRequest(t *testing.T) { - t.Run("success with pin", func(t *testing.T) { + t.Run("success with pin and authorizationDetails", func(t *testing.T) { mockOIDC4CIService := NewMockOIDC4CIService(gomock.NewController(t)) mockOIDC4CIService.EXPECT().ValidatePreAuthorizedCodeRequest(gomock.Any(), "1234", "5432", "123", "", ""). Return(&oidc4ci.Transaction{ + ID: "txID", TransactionData: oidc4ci.TransactionData{ - OpState: "random_op_state", - Scope: []string{"a", "b"}, + OpState: "random_op_state", + Scope: []string{"a", "b"}, + AuthorizationDetails: getTestAuthorizationDetails(t, true), }, }, nil) @@ -1369,18 +1452,34 @@ func TestController_ValidatePreAuthorizedCodeRequest(t *testing.T) { oidc4ciService: mockOIDC4CIService, } + recorder := httptest.NewRecorder() + req := `{"pre-authorized_code":"1234", "user_pin" : "5432", "client_id": "123" }` //nolint:lll - ctx := echoContext(withRequestBody([]byte(req))) - assert.NoError(t, c.ValidatePreAuthorizedCodeRequest(ctx)) + ctx := echoContext(withRecorder(recorder), withRequestBody([]byte(req))) + + err := c.ValidatePreAuthorizedCodeRequest(ctx) + assert.NoError(t, err) + + var response ValidatePreAuthorizedCodeResponse + err = json.NewDecoder(recorder.Body).Decode(&response) + assert.NoError(t, err) + + assert.Equal(t, "txID", response.TxId) + assert.Equal(t, "random_op_state", response.OpState) + assert.Equal(t, []string{"a", "b"}, response.Scopes) + + checkTestAuthorizationDetailsDTO(t, response.AuthorizationDetails, true) }) - t.Run("success without pin", func(t *testing.T) { + t.Run("success without pin and without authorizationDetails", func(t *testing.T) { mockOIDC4CIService := NewMockOIDC4CIService(gomock.NewController(t)) mockOIDC4CIService.EXPECT().ValidatePreAuthorizedCodeRequest(gomock.Any(), "1234", "", "123", "", ""). Return(&oidc4ci.Transaction{ + ID: "txID", TransactionData: oidc4ci.TransactionData{ - OpState: "random_op_state", - Scope: []string{"a", "b"}, + OpState: "random_op_state", + Scope: []string{"a", "b"}, + AuthorizationDetails: nil, }, }, nil) @@ -1388,9 +1487,20 @@ func TestController_ValidatePreAuthorizedCodeRequest(t *testing.T) { oidc4ciService: mockOIDC4CIService, } + recorder := httptest.NewRecorder() + req := `{"pre-authorized_code":"1234", "client_id": "123" }` //nolint:lll - ctx := echoContext(withRequestBody([]byte(req))) + ctx := echoContext(withRecorder(recorder), withRequestBody([]byte(req))) assert.NoError(t, c.ValidatePreAuthorizedCodeRequest(ctx)) + + var response ValidatePreAuthorizedCodeResponse + err := json.NewDecoder(recorder.Body).Decode(&response) + assert.NoError(t, err) + + assert.Equal(t, "txID", response.TxId) + assert.Equal(t, "random_op_state", response.OpState) + assert.Equal(t, []string{"a", "b"}, response.Scopes) + assert.Nil(t, response.AuthorizationDetails) }) t.Run("fail with pin", func(t *testing.T) { @@ -2258,3 +2368,54 @@ func requireCustomError(t *testing.T, expectedCode resterr.ErrorCode, actual err require.Equal(t, expectedCode, actualErr.Code) require.Error(t, actualErr.Err) } + +func getTestAuthorizationDetails(t *testing.T, includeCredentialDefinition bool) *oidc4ci.AuthorizationDetails { + t.Helper() + + res := &oidc4ci.AuthorizationDetails{ + CredentialConfigurationID: "CredentialConfigurationID", + Locations: []string{"https://example.com/rs1", "https://example.com/rs2"}, + Type: "openid_credential", + CredentialDefinition: nil, + Format: "jwt", + CredentialIdentifiers: []string{"CredentialIdentifiers1", "CredentialIdentifiers2"}, + } + + if includeCredentialDefinition { + res.CredentialDefinition = &oidc4ci.CredentialDefinition{ + Context: []string{"https://example.com/context/1", "https://example.com/context/2"}, + CredentialSubject: map[string]interface{}{"key": "value"}, + Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, + } + } + + return res +} + +func checkTestAuthorizationDetailsDTO( + t *testing.T, + authorizationDetailsDTOList *[]common.AuthorizationDetails, + includeCredentialDefinition bool, +) { + authorizationDetailsReceived := *authorizationDetailsDTOList + + assert.Len(t, authorizationDetailsReceived, 1) + assert.Equal(t, "CredentialConfigurationID", *authorizationDetailsReceived[0].CredentialConfigurationId) + assert.Equal(t, []string{"CredentialIdentifiers1", "CredentialIdentifiers2"}, + *authorizationDetailsReceived[0].CredentialIdentifiers) + assert.Equal(t, "jwt", lo.FromPtr(authorizationDetailsReceived[0].Format)) + assert.Equal(t, []string{"https://example.com/rs1", "https://example.com/rs2"}, + *authorizationDetailsReceived[0].Locations) + assert.Equal(t, "openid_credential", authorizationDetailsReceived[0].Type) + + ad := authorizationDetailsReceived[0].CredentialDefinition + + if includeCredentialDefinition { + assert.NotNil(t, ad) + assert.Equal(t, []string{"https://example.com/context/1", "https://example.com/context/2"}, *ad.Context) + assert.Equal(t, map[string]interface{}{"key": "value"}, *ad.CredentialSubject) + assert.Equal(t, []string{"VerifiableCredential", "UniversityDegreeCredential"}, ad.Type) + } else { + assert.Nil(t, ad) + } +} diff --git a/pkg/restapi/v1/issuer/openapi.gen.go b/pkg/restapi/v1/issuer/openapi.gen.go index eda8b19d2..4eeaaa32c 100644 --- a/pkg/restapi/v1/issuer/openapi.gen.go +++ b/pkg/restapi/v1/issuer/openapi.gen.go @@ -125,7 +125,8 @@ type ExchangeAuthorizationCodeRequest struct { // Response model for exchanging auth code from issuer oauth type ExchangeAuthorizationCodeResponse struct { - TxId string `json:"tx_id"` + AuthorizationDetails *[]externalRef0.AuthorizationDetails `json:"authorization_details,omitempty"` + TxId string `json:"tx_id"` } // Model for Initiate OIDC Credential Issuance Request. @@ -366,6 +367,9 @@ type ValidatePreAuthorizedCodeRequest struct { // Model for validating pre-authorized code and pin. type ValidatePreAuthorizedCodeResponse struct { + // REQUIRED when authorization_details parameter is used to request issuance of a certain Credential type as defined in Section 5.1.1. It MUST NOT be used otherwise. It is an array of objects, as defined in Section 7 of [RFC9396]. + AuthorizationDetails *[]externalRef0.AuthorizationDetails `json:"authorization_details,omitempty"` + // Op state. OpState string `json:"op_state"` diff --git a/pkg/restapi/v1/oidc4ci/controller.go b/pkg/restapi/v1/oidc4ci/controller.go index 65fa9087b..5db901db9 100644 --- a/pkg/restapi/v1/oidc4ci/controller.go +++ b/pkg/restapi/v1/oidc4ci/controller.go @@ -490,6 +490,7 @@ func (c *Controller) OidcToken(e echo.Context) error { nonce := mustGenerateNonce() var txID string + var authorisationDetails *[]common.AuthorizationDetails isPreAuthFlow := strings.EqualFold(e.FormValue("grant_type"), preAuthorizedCodeGrantType) if isPreAuthFlow { //nolint:nestif @@ -507,6 +508,7 @@ func (c *Controller) OidcToken(e echo.Context) error { } txID = resp.TxId + authorisationDetails = resp.AuthorizationDetails } else { exchangeResp, errExchange := c.issuerInteractionClient.ExchangeAuthorizationCodeRequest( ctx, @@ -536,6 +538,7 @@ func (c *Controller) OidcToken(e echo.Context) error { return fmt.Errorf("read exchange auth code response: %w", err) } txID = exchangeResult.TxId + authorisationDetails = exchangeResult.AuthorizationDetails } c.setCNonceSession(session, nonce, txID, isPreAuthFlow) @@ -546,6 +549,9 @@ func (c *Controller) OidcToken(e echo.Context) error { } c.setCNonce(responder, nonce) + if authorisationDetails != nil { + c.setAuthorizationDetails(responder, authorisationDetails) + } c.oauth2Provider.WriteAccessResponse(ctx, e.Response().Writer, ar, responder) return nil @@ -559,6 +565,13 @@ func (c *Controller) setCNonce( responder.SetExtra("c_nonce_expires_in", cNonceTTL.Seconds()) } +func (c *Controller) setAuthorizationDetails( + responder fosite.AccessResponder, + authorisationDetails *[]common.AuthorizationDetails, +) { + responder.SetExtra("authorization_details", authorisationDetails) +} + func (c *Controller) setCNonceSession( session *fosite.DefaultSession, nonce string, diff --git a/pkg/restapi/v1/oidc4ci/controller_test.go b/pkg/restapi/v1/oidc4ci/controller_test.go index 0cbd73739..e3cde43aa 100644 --- a/pkg/restapi/v1/oidc4ci/controller_test.go +++ b/pkg/restapi/v1/oidc4ci/controller_test.go @@ -971,7 +971,7 @@ func TestController_OidcRedirect(t *testing.T) { } } -func TestController_OidcToken(t *testing.T) { +func TestController_OidcToken_Authorize(t *testing.T) { var ( mockOAuthProvider = NewMockOAuth2Provider(gomock.NewController(t)) mockInteractionClient = NewMockIssuerInteractionClient(gomock.NewController(t)) @@ -984,6 +984,70 @@ func TestController_OidcToken(t *testing.T) { }{ { name: "success", + setup: func() { + ad := getTestOIDCTokenAuthorizationDetailsPayload(t) + payload := fmt.Sprintf(`{"tx_id":"txID", "authorization_details": %s}`, ad) + + opState := uuid.NewString() + mockOAuthProvider.EXPECT().NewAccessRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return( + &fosite.AccessRequest{ + Request: fosite.Request{ + Session: &fosite.DefaultSession{ + Extra: map[string]interface{}{ + "opState": opState, + }, + }, + Client: &fosite.DefaultClient{ + ID: clientID, + }, + }, + }, nil) + + mockInteractionClient.EXPECT().ExchangeAuthorizationCodeRequest(gomock.Any(), + issuer.ExchangeAuthorizationCodeRequestJSONRequestBody{ + OpState: opState, + ClientId: lo.ToPtr(clientID), + ClientAssertionType: lo.ToPtr(""), + ClientAssertion: lo.ToPtr(""), + }). + Return( + &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(payload)), + }, nil) + + mockOAuthProvider.EXPECT().NewAccessResponse(gomock.Any(), gomock.Any()).Return( + fosite.NewAccessResponse(), nil) + + mockOAuthProvider.EXPECT().WriteAccessResponse(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Do(func(ctx context.Context, rw http.ResponseWriter, requester fosite.AccessRequester, responder fosite.AccessResponder) { + js, err := json.Marshal(responder.ToMap()) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + rw.Header().Set("Content-Type", "application/json;charset=UTF-8") + + rw.WriteHeader(http.StatusOK) + _, _ = rw.Write(js) + }) + }, + check: func(t *testing.T, rec *httptest.ResponseRecorder, err error) { + require.NoError(t, err) + require.Equal(t, http.StatusOK, rec.Code) + + var resp oidc4ci.AccessTokenResponse + + assert.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + + ad := *resp.AuthorizationDetails + assert.Len(t, ad, 1) + assert.Equal(t, "openid_credential", ad[0].Type) + }, + }, + { + name: "success no authorization detail in response", setup: func() { opState := uuid.NewString() mockOAuthProvider.EXPECT().NewAccessRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return( @@ -1016,11 +1080,29 @@ func TestController_OidcToken(t *testing.T) { mockOAuthProvider.EXPECT().NewAccessResponse(gomock.Any(), gomock.Any()).Return( fosite.NewAccessResponse(), nil) - mockOAuthProvider.EXPECT().WriteAccessResponse(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) + mockOAuthProvider.EXPECT().WriteAccessResponse(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Do(func(ctx context.Context, rw http.ResponseWriter, requester fosite.AccessRequester, responder fosite.AccessResponder) { + js, err := json.Marshal(responder.ToMap()) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + rw.Header().Set("Content-Type", "application/json;charset=UTF-8") + + rw.WriteHeader(http.StatusOK) + _, _ = rw.Write(js) + }) }, check: func(t *testing.T, rec *httptest.ResponseRecorder, err error) { require.NoError(t, err) require.Equal(t, http.StatusOK, rec.Code) + + var resp oidc4ci.AccessTokenResponse + + assert.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + + assert.Nil(t, resp.AuthorizationDetails) }, }, { @@ -2346,7 +2428,7 @@ func TestController_OidcCredential(t *testing.T) { } } -func TestController_OidcPreAuthorize(t *testing.T) { +func TestController_OidcToken_PreAuthorize(t *testing.T) { var ( mockOAuthProvider = NewMockOAuth2Provider(gomock.NewController(t)) mockInteractionClient = NewMockIssuerInteractionClient(gomock.NewController(t)) @@ -2366,10 +2448,13 @@ func TestController_OidcPreAuthorize(t *testing.T) { "user_pin": {"5678"}, }.Encode()), setup: func() { + ad := getTestOIDCTokenAuthorizationDetailsPayload(t) + payload := fmt.Sprintf(`{"scopes" : ["a","b"], "op_state" : "opp123", "authorization_details": %s}`, ad) + mockInteractionClient.EXPECT().ValidatePreAuthorizedCodeRequest(gomock.Any(), gomock.Any()). Return(&http.Response{ StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(`{"scopes" : ["a","b"], "op_state" : "opp123"}`)), + Body: io.NopCloser(strings.NewReader(payload)), }, nil) accessRq := &fosite.AccessRequest{ @@ -2404,12 +2489,18 @@ func TestController_OidcPreAuthorize(t *testing.T) { }) }, check: func(t *testing.T, rec *httptest.ResponseRecorder, err error) { - assert.NoError(t, err) + require.NoError(t, err) + require.Equal(t, http.StatusOK, rec.Code) + var resp oidc4ci.AccessTokenResponse assert.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) assert.Equal(t, "123456", resp.AccessToken) assert.NotEmpty(t, *resp.ExpiresIn) + + ad := *resp.AuthorizationDetails + assert.Len(t, ad, 1) + assert.Equal(t, "openid_credential", ad[0].Type) }, }, { @@ -2954,3 +3045,27 @@ func (m *mockJWEEncrypter) EncryptWithAuthData([]byte, []byte) (*gojose.JSONWebE func (m *mockJWEEncrypter) Options() gojose.EncrypterOptions { return gojose.EncrypterOptions{} } + +func getTestOIDCTokenAuthorizationDetailsPayload(t *testing.T) string { + t.Helper() + + res := &oidc4cisrv.AuthorizationDetails{ + CredentialConfigurationID: "CredentialConfigurationID", + Locations: []string{"https://example.com/rs1", "https://example.com/rs2"}, + Type: "openid_credential", + CredentialDefinition: &oidc4cisrv.CredentialDefinition{ + Context: []string{"https://example.com/context/1", "https://example.com/context/2"}, + CredentialSubject: map[string]interface{}{"key": "value"}, + Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, + }, + Format: "jwt", + CredentialIdentifiers: []string{"CredentialIdentifiers1", "CredentialIdentifiers2"}, + } + + payload := []common.AuthorizationDetails{res.ToDTO()} + + b, err := json.Marshal(payload) + assert.NoError(t, err) + + return string(b) +} diff --git a/pkg/restapi/v1/oidc4ci/models.go b/pkg/restapi/v1/oidc4ci/models.go index 2f967da88..460e0a446 100644 --- a/pkg/restapi/v1/oidc4ci/models.go +++ b/pkg/restapi/v1/oidc4ci/models.go @@ -12,20 +12,6 @@ type PushedAuthorizationRequest struct { OpState string `form:"op_state"` } -// AuthorizationDetails parameter is used to convey the details about VC the Wallet wants to obtain. -// Refer to https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-7.1.1 for more details. -type AuthorizationDetails struct { - // Type determines the authorization details type. MUST be set to "openid_credential". - Type string - // CredentialType denotes the type of the requested Credential. - CredentialType string - // Format represents a format in which the Credential is requested to be issued. - Format *string - // Locations param is an array of strings that allows a client to specify the location of the resource server(s) for - // the AS to mint audience restricted access tokens. - Locations *[]string -} - type JWTProofClaims struct { Issuer string `json:"iss,omitempty"` Audience string `json:"aud,omitempty"` diff --git a/pkg/restapi/v1/oidc4ci/openapi.cfg.yaml b/pkg/restapi/v1/oidc4ci/openapi.cfg.yaml index 522526a1d..d17896976 100644 --- a/pkg/restapi/v1/oidc4ci/openapi.cfg.yaml +++ b/pkg/restapi/v1/oidc4ci/openapi.cfg.yaml @@ -10,6 +10,8 @@ generate: models: true echo-server: true embedded-spec: false +import-mapping: + ./common.yaml: github.com/trustbloc/vcs/pkg/restapi/v1/common output-options: include-tags: - oidc4ci diff --git a/pkg/restapi/v1/oidc4ci/openapi.gen.go b/pkg/restapi/v1/oidc4ci/openapi.gen.go index 791c91161..81970e470 100644 --- a/pkg/restapi/v1/oidc4ci/openapi.gen.go +++ b/pkg/restapi/v1/oidc4ci/openapi.gen.go @@ -9,6 +9,7 @@ import ( "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/labstack/echo/v4" + externalRef0 "github.com/trustbloc/vcs/pkg/restapi/v1/common" ) // Model for Access Token Response. @@ -16,6 +17,9 @@ type AccessTokenResponse struct { // The access token issued by the authorization server. AccessToken string `json:"access_token"` + // REQUIRED when authorization_details parameter is used to request issuance of a certain Credential type as defined in Section 5.1.1. It MUST NOT be used otherwise. It is an array of objects, as defined in Section 7 of [RFC9396]. + AuthorizationDetails *[]externalRef0.AuthorizationDetails `json:"authorization_details,omitempty"` + // String containing a nonce to be used to create a proof of possession of key material when requesting a credential. CNonce *string `json:"c_nonce,omitempty"` diff --git a/pkg/service/oidc4ci/api.go b/pkg/service/oidc4ci/api.go index 388c514fa..03cb55706 100644 --- a/pkg/service/oidc4ci/api.go +++ b/pkg/service/oidc4ci/api.go @@ -13,6 +13,7 @@ import ( "time" "github.com/labstack/echo/v4" + "github.com/samber/lo" "github.com/trustbloc/vc-go/verifiable" "github.com/trustbloc/vcs/pkg/dataprotect" @@ -103,6 +104,27 @@ type AuthorizationDetails struct { Locations []string CredentialConfigurationID string CredentialDefinition *CredentialDefinition + CredentialIdentifiers []string +} + +func (ad *AuthorizationDetails) ToDTO() common.AuthorizationDetails { + var credentialDefinition *common.CredentialDefinition + if cd := ad.CredentialDefinition; cd != nil { + credentialDefinition = &common.CredentialDefinition{ + Context: &cd.Context, + CredentialSubject: &cd.CredentialSubject, + Type: cd.Type, + } + } + + return common.AuthorizationDetails{ + CredentialConfigurationId: &ad.CredentialConfigurationID, + CredentialDefinition: credentialDefinition, + CredentialIdentifiers: lo.ToPtr(ad.CredentialIdentifiers), + Format: lo.ToPtr(string(ad.Format)), + Locations: &ad.Locations, + Type: ad.Type, + } } // CredentialDefinition contains the detailed description of the credential type. @@ -269,7 +291,7 @@ type ServiceInterface interface { clientID, clientAssertionType, clientAssertion string, - ) (TxID, error) + ) (*ExchangeAuthorizationCodeResult, error) ValidatePreAuthorizedCodeRequest( ctx context.Context, preAuthorizedCode, @@ -298,4 +320,9 @@ type AckRemote struct { IssuerIdentifier string `json:"issuer_identifier"` } +type ExchangeAuthorizationCodeResult struct { + TxID TxID + AuthorizationDetails *AuthorizationDetails +} + var ErrDataNotFound = errors.New("data not found") diff --git a/pkg/service/oidc4ci/oidc4ci_service_exchange_code.go b/pkg/service/oidc4ci/oidc4ci_service_exchange_code.go index fc4936090..7ee9104b7 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_exchange_code.go +++ b/pkg/service/oidc4ci/oidc4ci_service_exchange_code.go @@ -23,16 +23,16 @@ func (s *Service) ExchangeAuthorizationCode( clientID, // nolint:revive clientAssertionType, clientAssertion string, -) (TxID, error) { +) (*ExchangeAuthorizationCodeResult, error) { tx, err := s.store.FindByOpState(ctx, opState) if err != nil { - return "", fmt.Errorf("get transaction by opstate: %w", err) + return nil, fmt.Errorf("get transaction by opstate: %w", err) } newState := TransactionStateIssuerOIDCAuthorizationDone if err = s.validateStateTransition(tx.State, newState); err != nil { s.sendFailedTransactionEvent(ctx, tx, err) - return "", err + return nil, err } tx.State = newState @@ -48,12 +48,12 @@ func (s *Service) ExchangeAuthorizationCode( s.sendFailedTransactionEvent(ctx, tx, e) - return "", e + return nil, e } if err = s.AuthenticateClient(ctx, profile, clientAssertionType, clientAssertion); err != nil { s.sendFailedTransactionEvent(ctx, tx, err) - return "", resterr.NewCustomError(resterr.OIDCClientAuthenticationFailed, err) + return nil, resterr.NewCustomError(resterr.OIDCClientAuthenticationFailed, err) } oauth2Client := oauth2.Config{ @@ -73,19 +73,22 @@ func (s *Service) ExchangeAuthorizationCode( resp, err := oauth2Client.Exchange(ctx, tx.IssuerAuthCode) if err != nil { s.sendFailedTransactionEvent(ctx, tx, err) - return "", err + return nil, err } tx.IssuerToken = resp.AccessToken if err = s.store.Update(ctx, tx); err != nil { s.sendFailedTransactionEvent(ctx, tx, err) - return "", err + return nil, err } if err = s.sendTransactionEvent(ctx, tx, spi.IssuerOIDCInteractionAuthorizationCodeExchanged); err != nil { - return "", err + return nil, err } - return tx.ID, nil + return &ExchangeAuthorizationCodeResult{ + TxID: tx.ID, + AuthorizationDetails: tx.AuthorizationDetails, //TODO: add tx.AuthorizationDetails.CredentialIdentifiers + }, nil } diff --git a/pkg/service/oidc4ci/oidc4ci_service_exchange_code_test.go b/pkg/service/oidc4ci/oidc4ci_service_exchange_code_test.go index abb26cb2e..53896d11f 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_exchange_code_test.go +++ b/pkg/service/oidc4ci/oidc4ci_service_exchange_code_test.go @@ -397,6 +397,92 @@ func TestExchangeCodePublishError(t *testing.T) { assert.Empty(t, resp) } +func TestExchangeCode_Success(t *testing.T) { + store := NewMockTransactionStore(gomock.NewController(t)) + eventMock := NewMockEventService(gomock.NewController(t)) + profileService := NewMockProfileService(gomock.NewController(t)) + + httpClient := &http.Client{ + Transport: &mockTransport{ + func(req *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"access_token":"SlAV32hkKG"}`)), + }, nil + }, + }, + } + + svc, err := oidc4ci.NewService(&oidc4ci.Config{ + TransactionStore: store, + ProfileService: profileService, + HTTPClient: httpClient, + EventService: eventMock, + EventTopic: spi.IssuerEventTopic, + }) + assert.NoError(t, err) + + opState := uuid.NewString() + authCode := uuid.NewString() + + eventMock.EXPECT().Publish(gomock.Any(), spi.IssuerEventTopic, gomock.Any()). + DoAndReturn(func(ctx context.Context, topic string, messages ...*spi.Event) error { + assert.Len(t, messages, 1) + assert.Equal(t, messages[0].Type, spi.IssuerOIDCInteractionAuthorizationCodeExchanged) + + return nil + }) + + authorizationDetails := &oidc4ci.AuthorizationDetails{ + CredentialConfigurationID: "", + Locations: []string{"https://example.com/rs1", "https://example.com/rs2"}, + Type: "openid_credential", + CredentialDefinition: &oidc4ci.CredentialDefinition{ + Context: []string{"https://example.com/context/1", "https://example.com/context/2"}, + CredentialSubject: map[string]interface{}{ + "key": "value", + }, + Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, + }, + Format: "", + } + + baseTx := &oidc4ci.Transaction{ + ID: oidc4ci.TxID("id"), + TransactionData: oidc4ci.TransactionData{ + TokenEndpoint: "https://localhost/token", + IssuerAuthCode: authCode, + State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + AuthorizationDetails: authorizationDetails, + }, + } + + store.EXPECT().FindByOpState(gomock.Any(), opState).Return(baseTx, nil) + store.EXPECT().Update(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { + assert.Equal(t, baseTx, tx) + assert.Equal(t, "SlAV32hkKG", tx.IssuerToken) + assert.Equal(t, oidc4ci.TransactionStateIssuerOIDCAuthorizationDone, tx.State) + + return nil + }) + + profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()). + Return(&profile.Issuer{ + OIDCConfig: &profile.OIDCConfig{ + ClientID: "clientID", + ClientSecretHandle: "clientSecret", + }, + }, nil) + + resp, err := svc.ExchangeAuthorizationCode(context.TODO(), opState, "", "", "") + assert.NoError(t, err) + assert.Equal(t, &oidc4ci.ExchangeAuthorizationCodeResult{ + TxID: "id", + AuthorizationDetails: authorizationDetails, + }, resp) +} + type mockTransport struct { roundTripFunc func(req *http.Request) (*http.Response, error) }