From a975f38f361d1f36e6d2cad64e65a6dc86a5372a Mon Sep 17 00:00:00 2001 From: reinkrul Date: Mon, 8 Apr 2024 11:05:06 +0200 Subject: [PATCH] IAM: Remove OpenID4VP PoC code (#3022) --- auth/api/iam/api.go | 30 +-- auth/api/iam/assets/authz_en.html | 14 -- auth/api/iam/assets/authz_wallet_en.html | 60 ----- auth/api/iam/assets/openid4vp_demo.html | 23 -- .../iam/assets/openid4vp_demo_completed.html | 30 --- auth/api/iam/openid4vp.go | 228 +----------------- auth/api/iam/openid4vp_demo.go | 69 ------ auth/api/iam/openid4vp_test.go | 100 -------- auth/api/iam/rendering.go | 92 ------- auth/api/iam/rendering_test.go | 73 ------ 10 files changed, 2 insertions(+), 717 deletions(-) delete mode 100644 auth/api/iam/assets/authz_en.html delete mode 100644 auth/api/iam/assets/authz_wallet_en.html delete mode 100644 auth/api/iam/assets/openid4vp_demo.html delete mode 100644 auth/api/iam/assets/openid4vp_demo_completed.html delete mode 100644 auth/api/iam/openid4vp_demo.go delete mode 100644 auth/api/iam/rendering.go delete mode 100644 auth/api/iam/rendering_test.go diff --git a/auth/api/iam/api.go b/auth/api/iam/api.go index ae82ad9548..360af8b99c 100644 --- a/auth/api/iam/api.go +++ b/auth/api/iam/api.go @@ -21,7 +21,6 @@ package iam import ( "context" crypto2 "crypto" - "embed" "encoding/json" "errors" "fmt" @@ -43,7 +42,6 @@ import ( "github.com/nuts-foundation/nuts-node/vdr" "github.com/nuts-foundation/nuts-node/vdr/didweb" "github.com/nuts-foundation/nuts-node/vdr/resolver" - "html/template" "net/http" "net/url" "strings" @@ -61,32 +59,22 @@ const httpRequestContextKey = "http-request" // TODO: Might want to make this configurable at some point const accessTokenValidity = 15 * time.Minute -//go:embed assets -var assets embed.FS - // Wrapper handles OAuth2 flows. type Wrapper struct { vcr vcr.VCR vdr vdr.VDR auth auth.AuthenticationServices policyBackend policy.PDPBackend - templates *template.Template storageEngine storage.Engine } func New(authInstance auth.AuthenticationServices, vcrInstance vcr.VCR, vdrInstance vdr.VDR, storageEngine storage.Engine, policyBackend policy.PDPBackend) *Wrapper { - templates := template.New("oauth2 templates") - _, err := templates.ParseFS(assets, "assets/*.html") - if err != nil { - panic(err) - } return &Wrapper{ storageEngine: storageEngine, auth: authInstance, policyBackend: policyBackend, vcr: vcrInstance, vdr: vdrInstance, - templates: templates, } } @@ -101,20 +89,8 @@ func (r Wrapper) Routes(router core.EchoRouter) { return audit.StrictMiddleware(f, apiModuleName, operationID) }, })) - auditMiddleware := audit.Middleware(apiModuleName) - // The following handler is of the OpenID4VCI wallet which is called by the holder (wallet owner) - // when accepting an OpenID4VP authorization request. - router.POST("/iam/:did/openid4vp_authz_accept", r.handlePresentationRequestAccept, auditMiddleware) - // The following handler is of the OpenID4VP verifier where the browser will be redirected to by the wallet, - // after completing a presentation exchange. - router.GET("/iam/:did/openid4vp_completed", r.handlePresentationRequestCompleted, auditMiddleware) - // The following 2 handlers are used to test/demo the OpenID4VP flow. - // - GET renders an HTML page with a form to start the flow. - // - POST handles the form submission, initiating the flow. - router.GET("/iam/:did/openid4vp_demo", r.handleOpenID4VPDemoLanding, auditMiddleware) - router.POST("/iam/:did/openid4vp_demo", r.handleOpenID4VPDemoSendRequest, auditMiddleware) // The following handlers are used for the user facing OAuth2 flows. - router.GET("/oauth2/:did/user", r.handleUserLanding, auditMiddleware) + router.GET("/oauth2/:did/user", r.handleUserLanding, audit.Middleware(apiModuleName)) } func (r Wrapper) middleware(ctx echo.Context, request interface{}, operationID string, f StrictHandlerFunc) (interface{}, error) { @@ -351,10 +327,6 @@ func (r Wrapper) HandleAuthorizeRequest(ctx context.Context, request HandleAutho // Options: // - OpenID4VP flow, vp_token is sent in Authorization Response return r.handleAuthorizeRequestFromVerifier(ctx, *ownDID, params) - case responseTypeVPIDToken: - // Options: - // - OpenID4VP+SIOP flow, vp_token is sent in Authorization Response - return r.handlePresentationRequest(ctx, params, session) default: // TODO: This should be a redirect? redirectURI, _ := url.Parse(session.RedirectURI) diff --git a/auth/api/iam/assets/authz_en.html b/auth/api/iam/assets/authz_en.html deleted file mode 100644 index c65c7a7d4e..0000000000 --- a/auth/api/iam/assets/authz_en.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - Authorization required - - -

Authorization required

-

You need to authorize this application to access your account.

-
- - -
- - \ No newline at end of file diff --git a/auth/api/iam/assets/authz_wallet_en.html b/auth/api/iam/assets/authz_wallet_en.html deleted file mode 100644 index 1923ab6e3d..0000000000 --- a/auth/api/iam/assets/authz_wallet_en.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - Credential required - - - -

Credential required (Wallet)

-

{{ .VerifierName }} requests identification using one or more credentials in your wallet:

- -
-
- {{ range $credential := .Credentials }} -
-
-
- {{ range $i, $type := $credential.Type }} - {{if $i}}, {{end}}{{ $type }} - {{ end }} -
-
    - {{ range $attr := $credential.Attributes }} -
  • - {{ $attr.Name }}: - {{ $attr.Value }} -
  • - {{ end }} -
-
-
- {{ end }} - {{ if .RequiresUserIdentity }} -
-
-
- ZorgverlenerCredential -
-
    -
  • - Naam: - M. Visser (zorgverlener) -
  • -
  • - Rol: - Verpleegkundige niveau 4 -
  • -
-
-
- {{ end }} -
- - -
- - -
-
- - \ No newline at end of file diff --git a/auth/api/iam/assets/openid4vp_demo.html b/auth/api/iam/assets/openid4vp_demo.html deleted file mode 100644 index 70b171136a..0000000000 --- a/auth/api/iam/assets/openid4vp_demo.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - OpenID4VP Demo - - - -

OpenID4VP Demo (Verifier)

-

This is a demo to have a verifier request a Verifiable Presentation from a wallet.

-
-
- Scope: - -
- -
Wallet Identifier:
-
-
- - \ No newline at end of file diff --git a/auth/api/iam/assets/openid4vp_demo_completed.html b/auth/api/iam/assets/openid4vp_demo_completed.html deleted file mode 100644 index 969f8afa2d..0000000000 --- a/auth/api/iam/assets/openid4vp_demo_completed.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - OpenID4VP Demo - - - -

OpenID4VP Demo (Verifier)

-

Presented credentials:

-{{ range $credential := .Credentials }} -
-
-
- {{ range $i, $type := $credential.Type }} - {{if $i}}, {{end}}{{ $type }} - {{ end }} -
- {{ range $attr := $credential.Attributes }} -
    -
  • - {{ $attr.Name }}: - {{ $attr.Value }} -
  • -
- {{ end }} -
-
-{{ end }} - - \ No newline at end of file diff --git a/auth/api/iam/openid4vp.go b/auth/api/iam/openid4vp.go index 13ff659e74..b798df8b06 100644 --- a/auth/api/iam/openid4vp.go +++ b/auth/api/iam/openid4vp.go @@ -19,21 +19,14 @@ package iam import ( - "bytes" "context" - "encoding/json" "errors" "fmt" "github.com/lestrrat-go/jwx/v2/jwt" - "net/http" "net/url" "slices" - "strings" "time" - "github.com/google/uuid" - "github.com/labstack/echo/v4" - ssi "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/go-did/vc" "github.com/nuts-foundation/nuts-node/auth/oauth" @@ -544,7 +537,7 @@ func (r Wrapper) handleAccessTokenRequest(ctx context.Context, verifier did.DID, return nil, withCallbackURI(oauthError(oauth.InvalidRequest, fmt.Sprintf("client_id does not match: %s vs %s", oauthSession.ClientID, *clientId)), callbackURI) } - state := oauthSession.ServerState + state := oauthSession.ServerState mapping, err := r.policyBackend.PresentationDefinitions(ctx, verifier, oauthSession.Scope) if err != nil { return nil, withCallbackURI(oauthError(oauth.ServerError, fmt.Sprintf("failed to fetch presentation definition: %s", err.Error())), callbackURI) @@ -635,229 +628,10 @@ func (r Wrapper) handleCallback(ctx context.Context, request CallbackRequestObje }, nil } -// createPresentationRequest creates a new Authorization Request as specified by OpenID4VP: https://openid.net/specs/openid-4-verifiable-presentations-1_0.html. -// It is sent by a verifier to a wallet, to request one or more verifiable credentials as verifiable presentation from the wallet. -func (r Wrapper) sendPresentationRequest(_ context.Context, response http.ResponseWriter, scope string, - redirectURL url.URL, verifierIdentifier url.URL, walletIdentifier url.URL) error { - // TODO: Lookup wallet metadata for correct authorization endpoint. But for Nuts nodes, we derive it from the walletIdentifier - authzEndpoint := walletIdentifier.JoinPath("/authorize") - params := make(map[string]string) - params[oauth.ScopeParam] = scope - params[oauth.RedirectURIParam] = redirectURL.String() - // TODO: Check this - params[clientMetadataURIParam] = verifierIdentifier.JoinPath("/.well-known/openid-wallet-metadata/metadata.xml").String() - params[responseModeParam] = responseModeDirectPost - params[oauth.ResponseTypeParam] = responseTypeVPIDToken - // TODO: Depending on parameter size, we either use redirect with query parameters or a form post. - // For simplicity, we now just query parameters. - result := httpNuts.AddQueryParams(*authzEndpoint, params) - response.Header().Add("Location", result.String()) - response.WriteHeader(http.StatusFound) - return nil -} - -// handlePresentationRequest handles an Authorization Request as specified by OpenID4VP: https://openid.net/specs/openid-4-verifiable-presentations-1_0.html. -// It is handled by a wallet, called by a verifier who wants the wallet to present one or more verifiable credentials. -func (r Wrapper) handlePresentationRequest(ctx context.Context, params oauthParameters, session *OAuthSession) (HandleAuthorizeRequestResponseObject, error) { - // Todo: for compatibility, we probably need to support presentation_definition and/or presentation_definition_uri. - if err := assertParamNotPresent(params, presentationDefUriParam); err != nil { - return nil, err - } - if err := assertParamPresent(params, presentationDefParam); err != nil { - return nil, err - } - if err := assertParamPresent(params, oauth.ScopeParam); err != nil { - return nil, err - } - if err := assertParamPresent(params, oauth.ResponseTypeParam); err != nil { - return nil, err - } - // Not supported: client_id_schema, client_metadata - if err := assertParamNotPresent(params, clientIDSchemeParam, clientMetadataParam); err != nil { - return nil, err - } - // Required: client_metadata_uri - if err := assertParamPresent(params, clientMetadataURIParam); err != nil { - return nil, err - } - // Response mode is always direct_post for now - if params.get(responseModeParam) != responseModeDirectPost { - return nil, oauth.OAuth2Error{ - Code: oauth.InvalidRequest, - Description: "response_mode must be direct_post", - RedirectURI: session.redirectURI(), - } - } - - presentationDefinition, err := pe.ParsePresentationDefinition([]byte(params.get(presentationDefParam))) - if err != nil { - return nil, oauth.OAuth2Error{ - Code: oauth.InvalidRequest, - Description: fmt.Sprintf("unsupported scope for presentation exchange: %s", params.get(oauth.ScopeParam)), - RedirectURI: session.redirectURI(), - } - } - session.PresentationDefinition = *presentationDefinition - - // Render HTML - templateParams := struct { - SessionID string - RequiresUserIdentity bool - VerifierName string - Credentials []CredentialInfo - }{ - // TODO: Maybe this should the verifier name be read from registered client metadata? - VerifierName: ssi.MustParseURI(session.RedirectURI).Host, - RequiresUserIdentity: strings.Contains(session.ResponseType, "id_token"), - } - - credentials, err := r.vcr.Wallet().List(ctx, *session.OwnDID) - if err != nil { - return nil, err - } - var ownCredentials []vc.VerifiableCredential - for _, cred := range credentials { - var subject []credential.NutsOrganizationCredentialSubject - if err = cred.UnmarshalCredentialSubject(&subject); err != nil { - return nil, fmt.Errorf("unable to unmarshal credential: %w", err) - } - if len(subject) != 1 { - continue - } - isOwner, _ := r.vdr.IsOwner(ctx, did.MustParseDID(subject[0].ID)) - if isOwner { - ownCredentials = append(ownCredentials, cred) - } - } - - submissionBuilder := presentationDefinition.PresentationSubmissionBuilder() - submissionBuilder.AddWallet(*session.OwnDID, ownCredentials) - _, signInstructions, err := submissionBuilder.Build("ldp_vp") - if err != nil { - return nil, fmt.Errorf("unable to match presentation definition: %w", err) - } - var credentialIDs []string - for _, signInstruction := range signInstructions { - for _, matchingCredential := range signInstruction.VerifiableCredentials { - templateParams.Credentials = append(templateParams.Credentials, makeCredentialInfo(matchingCredential)) - credentialIDs = append(credentialIDs, matchingCredential.ID.String()) - } - } - - sessionID := uuid.NewString() - err = r.storageEngine.GetSessionDatabase().GetStore(sessionExpiry, session.OwnDID.String(), "session").Put(sessionID, *session) - if err != nil { - return nil, err - } - templateParams.SessionID = sessionID - - // TODO: Support multiple languages - buf := new(bytes.Buffer) - err = r.templates.ExecuteTemplate(buf, "authz_wallet_en.html", templateParams) - if err != nil { - return nil, fmt.Errorf("unable to render authz page: %w", err) - } - return HandleAuthorizeRequest200TexthtmlResponse{ - Body: buf, - ContentLength: int64(buf.Len()), - }, nil -} - -// handleAuthConsent handles the authorization consent form submission. -func (r Wrapper) handlePresentationRequestAccept(c echo.Context) error { - // TODO: Needs authentication? - sessionID := c.FormValue("sessionID") - if sessionID == "" { - return errors.New("missing sessionID parameter") - } - - var session OAuthSession - sessionStore := r.storageEngine.GetSessionDatabase().GetStore(sessionExpiry, "openid", session.OwnDID.String(), "session") - err := sessionStore.Get(sessionID, &session) - if err != nil { - return fmt.Errorf("invalid session: %w", err) - } - - credentials, err := r.vcr.Wallet().List(c.Request().Context(), *session.OwnDID) - if err != nil { - return err - } - presentationDefinition := session.PresentationDefinition - // TODO: Options (including format) - resultParams := map[string]string{} - submissionBuilder := presentationDefinition.PresentationSubmissionBuilder() - submissionBuilder.AddWallet(*session.OwnDID, credentials) - submission, signInstructions, err := submissionBuilder.Build("ldp_vp") - if err != nil { - return err - } - presentationSubmissionJSON, _ := json.Marshal(submission) - resultParams[presentationSubmissionParam] = string(presentationSubmissionJSON) - if len(signInstructions) != 1 { - // todo support multiple wallets (org + user) - return errors.New("expected to create exactly one presentation") - } - verifiablePresentation, err := r.vcr.Wallet().BuildPresentation(c.Request().Context(), signInstructions[0].VerifiableCredentials, holder.PresentationOptions{}, &signInstructions[0].Holder, false) - if err != nil { - return err - } - verifiablePresentationJSON, _ := verifiablePresentation.MarshalJSON() - resultParams[vpTokenParam] = string(verifiablePresentationJSON) - - // TODO: check response mode, and submit accordingly (direct_post) - return c.Redirect(http.StatusFound, session.CreateRedirectURI(resultParams)) -} - -func (r Wrapper) handlePresentationRequestCompleted(ctx echo.Context) error { - // TODO: response direct_post mode - vpToken := ctx.QueryParams()[vpTokenParam] - if len(vpToken) == 0 { - // TODO: User-agent is a browser, need to render an HTML page - return errors.New("missing parameter " + vpTokenParam) - } - vp := vc.VerifiablePresentation{} - if err := vp.UnmarshalJSON([]byte(vpToken[0])); err != nil { - // TODO: User-agent is a browser, need to render an HTML page - return err - } - // TODO: verify signature and credentials of VP - var credentials []CredentialInfo - for _, cred := range vp.VerifiableCredential { - credentials = append(credentials, makeCredentialInfo(cred)) - } - buf := new(bytes.Buffer) - if err := r.templates.ExecuteTemplate(buf, "openid4vp_demo_completed.html", struct { - Credentials []CredentialInfo - }{ - Credentials: credentials, - }); err != nil { - return err - } - return ctx.HTML(http.StatusOK, buf.String()) -} - func (r Wrapper) oauthNonceStore() storage.SessionStore { return r.storageEngine.GetSessionDatabase().GetStore(oAuthFlowTimeout, oauthNonceKey...) } -func assertParamPresent(params oauthParameters, param ...string) error { - for _, curr := range param { - if params.get(curr) == "" { - return fmt.Errorf("%s parameter must be present", curr) - } - } - return nil -} - -func assertParamNotPresent(params oauthParameters, param ...string) error { - for _, curr := range param { - if params.get(curr) != "" { - return fmt.Errorf("%s parameter must not be present", curr) - } - } - return nil -} - func oauthError(code oauth.ErrorCode, description string) oauth.OAuth2Error { return oauth.OAuth2Error{ Code: code, diff --git a/auth/api/iam/openid4vp_demo.go b/auth/api/iam/openid4vp_demo.go deleted file mode 100644 index 8ee7290964..0000000000 --- a/auth/api/iam/openid4vp_demo.go +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2023 Nuts community - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package iam - -import ( - "bytes" - "errors" - "github.com/labstack/echo/v4" - "net/http" - "net/url" - "strings" -) - -func (r Wrapper) handleOpenID4VPDemoLanding(echoCtx echo.Context) error { - requestURL := *echoCtx.Request().URL - requestURL.Host = echoCtx.Request().Host - requestURL.Scheme = "http" - verifierID := requestURL.String() - verifierID, _ = strings.CutSuffix(verifierID, "/openid4vp_demo") - - buf := new(bytes.Buffer) - if err := r.templates.ExecuteTemplate(buf, "openid4vp_demo.html", struct { - VerifierID string - WalletID string - }{ - VerifierID: verifierID, - WalletID: verifierID, - }); err != nil { - return err - } - return echoCtx.HTML(http.StatusOK, buf.String()) -} - -func (r Wrapper) handleOpenID4VPDemoSendRequest(echoCtx echo.Context) error { - verifierID := echoCtx.FormValue("verifier_id") - if verifierID == "" { - return errors.New("missing verifier_id") - } - walletID := echoCtx.FormValue("wallet_id") - if walletID == "" { - return errors.New("missing wallet_id") - } - scope := echoCtx.FormValue("scope") - if scope == "" { - return errors.New("missing scope") - } - walletURL, _ := url.Parse(walletID) - verifierURL, _ := url.Parse(verifierID) - return r.sendPresentationRequest( - echoCtx.Request().Context(), echoCtx.Response(), scope, - *walletURL.JoinPath("openid4vp_completed"), *verifierURL, *walletURL, - ) -} diff --git a/auth/api/iam/openid4vp_test.go b/auth/api/iam/openid4vp_test.go index d6157ef97d..4b7e23f792 100644 --- a/auth/api/iam/openid4vp_test.go +++ b/auth/api/iam/openid4vp_test.go @@ -24,23 +24,16 @@ import ( "encoding/json" "github.com/lestrrat-go/jwx/v2/jwt" "net/http" - "net/url" "strings" "testing" - ssi "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/go-did/vc" - "github.com/nuts-foundation/nuts-node/auth" "github.com/nuts-foundation/nuts-node/auth/oauth" - "github.com/nuts-foundation/nuts-node/policy" "github.com/nuts-foundation/nuts-node/storage" "github.com/nuts-foundation/nuts-node/test" - "github.com/nuts-foundation/nuts-node/vcr" - "github.com/nuts-foundation/nuts-node/vcr/credential" "github.com/nuts-foundation/nuts-node/vcr/holder" "github.com/nuts-foundation/nuts-node/vcr/pe" - "github.com/nuts-foundation/nuts-node/vdr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -697,99 +690,6 @@ func TestWrapper_sendAndHandleDirectPostError(t *testing.T) { }) } -func TestWrapper_sendPresentationRequest(t *testing.T) { - instance := New(nil, nil, nil, nil, nil) - - redirectURI, _ := url.Parse("https://example.com/redirect") - verifierID, _ := url.Parse("https://example.com/verifier") - walletID, _ := url.Parse("https://example.com/wallet") - - httpResponse := &stubResponseWriter{} - - err := instance.sendPresentationRequest(context.Background(), httpResponse, "test-scope", *redirectURI, *verifierID, *walletID) - - require.NoError(t, err) - require.Equal(t, http.StatusFound, httpResponse.statusCode) - location := httpResponse.headers.Get("Location") - require.NotEmpty(t, location) - locationURL, err := url.Parse(location) - require.NoError(t, err) - assert.Equal(t, "https", locationURL.Scheme) - assert.Equal(t, "example.com", locationURL.Host) - assert.Equal(t, "/wallet/authorize", locationURL.Path) - assert.Equal(t, "test-scope", locationURL.Query().Get("scope")) - assert.Equal(t, "vp_token id_token", locationURL.Query().Get("response_type")) - assert.Equal(t, "direct_post", locationURL.Query().Get("response_mode")) - assert.Equal(t, "https://example.com/verifier/.well-known/openid-wallet-metadata/metadata.xml", locationURL.Query().Get("client_metadata_uri")) -} - -func TestWrapper_handlePresentationRequest(t *testing.T) { - credentialID, _ := ssi.ParseURI("did:web:example.com:issuer#6AF53584-3337-4766-8C8D-0BFD54F6E527") - walletCredentials := []vc.VerifiableCredential{ - { - Context: []ssi.URI{ - vc.VCContextV1URI(), - credential.NutsV1ContextURI, - }, - ID: credentialID, - Issuer: issuerDID.URI(), - Type: []ssi.URI{vc.VerifiableCredentialTypeV1URI(), *credential.NutsOrganizationCredentialTypeURI}, - CredentialSubject: []interface{}{ - map[string]interface{}{ - "id": holderDID.URI(), - "organization": map[string]interface{}{ - "name": "Test Organization", - "city": "Test City", - }, - }, - }, - }, - } - t.Run("with scope", func(t *testing.T) { - ctrl := gomock.NewController(t) - mockVDR := vdr.NewMockVDR(ctrl) - mockVCR := vcr.NewMockVCR(ctrl) - mockWallet := holder.NewMockWallet(ctrl) - mockPolicy := policy.NewMockPDPBackend(ctrl) - mockVCR.EXPECT().Wallet().Return(mockWallet) - mockAuth := auth.NewMockAuthenticationServices(ctrl) - mockWallet.EXPECT().List(gomock.Any(), holderDID).Return(walletCredentials, nil) - mockVDR.EXPECT().IsOwner(gomock.Any(), holderDID).Return(true, nil) - instance := New(mockAuth, mockVCR, mockVDR, storage.NewTestStorageEngine(t), mockPolicy) - - params := map[string]interface{}{ - "scope": "example-scope", - "response_type": "code", - "response_mode": "direct_post", - "client_metadata_uri": "https://example.com/client_metadata.xml", - "presentation_definition": `{"id":"1","input_descriptors":[]}`, - } - - response, err := instance.handlePresentationRequest(context.Background(), params, createSession(params, holderDID)) - - require.NoError(t, err) - httpResponse := &stubResponseWriter{} - _ = response.VisitHandleAuthorizeRequestResponse(httpResponse) - require.Equal(t, http.StatusOK, httpResponse.statusCode) - assert.Contains(t, httpResponse.body.String(), "") - }) - t.Run("invalid response_mode", func(t *testing.T) { - instance := New(nil, nil, nil, nil, nil) - params := map[string]interface{}{ - "scope": "example-scope", - "response_type": "code", - "response_mode": "invalid", - "client_metadata_uri": "https://example.com/client_metadata.xml", - "presentation_definition": "{}", - } - - response, err := instance.handlePresentationRequest(context.Background(), params, createSession(params, holderDID)) - - requireOAuthError(t, err, oauth.InvalidRequest, "response_mode must be direct_post") - assert.Nil(t, response) - }) -} - func Test_extractChallenge(t *testing.T) { t.Run("JSON-LD", func(t *testing.T) { vpStr := diff --git a/auth/api/iam/rendering.go b/auth/api/iam/rendering.go deleted file mode 100644 index 84c8d8b344..0000000000 --- a/auth/api/iam/rendering.go +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2023 Nuts community - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package iam - -import ( - "fmt" - "github.com/nuts-foundation/go-did/vc" - "sort" - "strconv" -) - -type CredentialInfoAttribute struct { - Name string - Value string -} - -type CredentialInfo struct { - ID string - Type []string - Attributes []CredentialInfoAttribute -} - -func makeCredentialInfo(cred vc.VerifiableCredential) CredentialInfo { - result := CredentialInfo{ - ID: cred.ID.String(), - } - - for _, curr := range cred.Type { - if curr.String() != vc.VerifiableCredentialType { - result.Type = append(result.Type, curr.String()) - } - } - - // Collect all properties from the credential subject - // This assumes it's a compacted JSON-LD document, with arrays compacted - propsMap := map[string]interface{}{} - for _, curr := range cred.CredentialSubject { - asMap, ok := curr.(map[string]interface{}) - if ok { - flatMap("", " ", asMap, propsMap) - } - } - - for key, value := range propsMap { - if key == "id" { - // omit ID attribute - continue - } - result.Attributes = append(result.Attributes, CredentialInfoAttribute{ - Name: key, - Value: fmt.Sprintf("%s", value), - }) - } - sort.SliceStable(result.Attributes, func(i, j int) bool { - return result.Attributes[i].Name < result.Attributes[j].Name - }) - return result -} - -func flatMap(path string, separator string, src map[string]interface{}, dest map[string]interface{}) { - if len(path) > 0 { - path += separator - } - for key, value := range src { - switch next := value.(type) { - case map[string]interface{}: - flatMap(path+key, separator, next, dest) - case []interface{}: - for i := 0; i < len(next); i++ { - dest[path+key+"."+strconv.Itoa(i)] = next[i] - } - default: - dest[path+key] = value - } - } -} diff --git a/auth/api/iam/rendering_test.go b/auth/api/iam/rendering_test.go deleted file mode 100644 index 5a2ffc5e2f..0000000000 --- a/auth/api/iam/rendering_test.go +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2023 Nuts community - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package iam - -import ( - "github.com/nuts-foundation/go-did/vc" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "testing" -) - -func Test_makeCredentialInfo(t *testing.T) { - var cred vc.VerifiableCredential - err := cred.UnmarshalJSON([]byte(nutsOrgCredentialJSON)) - require.NoError(t, err) - - info := makeCredentialInfo(cred) - - assert.Equal(t, cred.ID.String(), info.ID) - assert.Equal(t, []string{"NutsOrganizationCredential"}, info.Type) - assert.Len(t, info.Attributes, 2) - assert.Equal(t, "organization city", info.Attributes[0].Name) - assert.Equal(t, "IJbergen", info.Attributes[0].Value) - assert.Equal(t, "organization name", info.Attributes[1].Name) - assert.Equal(t, "Because we care B.V.", info.Attributes[1].Value) -} - -const nutsOrgCredentialJSON = ` -{ - "@context": [ - "https://nuts.nl/credentials/v1", - "https://www.w3.org/2018/credentials/v1", - "https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json" - ], - "credentialSubject": { - "id": "did:nuts:CuE3qeFGGLhEAS3gKzhMCeqd1dGa9at5JCbmCfyMU2Ey", - "organization": { - "city": "IJbergen", - "name": "Because we care B.V." - } - }, - "id": "did:nuts:CuE3qeFGGLhEAS3gKzhMCeqd1dGa9at5JCbmCfyMU2Ey#ec8af8cf-67d4-4b54-9bd6-8a861e729e11", - "issuanceDate": "2022-06-01T15:34:40.65319+02:00", - "issuer": "did:nuts:CuE3qeFGGLhEAS3gKzhMCeqd1dGa9at5JCbmCfyMU2Ey", - "proof": { - "created": "2022-06-01T12:00:00Z", - "jws": "eyJhbGciOiJFUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..Za6h29jt9fJMUDs9wkkbZAtB3-PTHfGBhFzPGz_DkWXariFkPQdd75BZU9-tQraiA7X8wMSSKQuYnQsNXMxvmw", - "proofPurpose": "assertionMethod", - "type": "JsonWebSignature2020", - "verificationMethod": "did:nuts:CuE3qeFGGLhEAS3gKzhMCeqd1dGa9at5JCbmCfyMU2Ey#sNGDQ3NlOe6Icv0E7_ufviOLG6Y25bSEyS5EbXBgp8Y" - }, - "type": [ - "NutsOrganizationCredential", - "VerifiableCredential" - ] -} -`