Skip to content

Commit

Permalink
Check authz request URI and non-URI params (#3084)
Browse files Browse the repository at this point in the history
* check authz request URI and non-URI params

* pr feedback

* moved codeblocks to private methods

* fix merge issues

* typo
  • Loading branch information
gerardsn authored May 7, 2024
1 parent b8f5a3b commit a9ce5be
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 12 deletions.
58 changes: 50 additions & 8 deletions auth/api/iam/openid4vp.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package iam

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
Expand Down Expand Up @@ -293,15 +294,15 @@ func (r Wrapper) handleAuthorizeRequestFromVerifier(ctx context.Context, tenantD
}

// get verifier metadata
metadata, err := r.auth.IAMClient().ClientMetadata(ctx, params.get(clientMetadataURIParam))
if err != nil {
return r.sendAndHandleDirectPostError(ctx, oauth.OAuth2Error{Code: oauth.ServerError, Description: "failed to get client metadata (verifier)"}, responseURI, state)
metadata, oauth2Err := r.getClientMetadataFromRequest(ctx, params)
if oauth2Err != nil {
return r.sendAndHandleDirectPostError(ctx, *oauth2Err, responseURI, state)
}
// get presentation_definition from presentation_definition_uri
presentationDefinitionURI := params.get(presentationDefUriParam)
presentationDefinition, err := r.auth.IAMClient().PresentationDefinition(ctx, presentationDefinitionURI)
if err != nil {
return r.sendAndHandleDirectPostError(ctx, oauth.OAuth2Error{Code: oauth.InvalidPresentationDefinitionURI, Description: fmt.Sprintf("failed to retrieve presentation definition on %s", presentationDefinitionURI)}, responseURI, state)

// get presentation_definition
presentationDefinition, oauth2Err := r.getPresentationDefinitionFromRequest(ctx, params)
if oauth2Err != nil {
return r.sendAndHandleDirectPostError(ctx, *oauth2Err, responseURI, state)
}

// at this point in the flow it would be possible to ask the user to confirm the credentials to use
Expand Down Expand Up @@ -340,6 +341,47 @@ func (r Wrapper) handleAuthorizeRequestFromVerifier(ctx context.Context, tenantD
return r.sendAndHandleDirectPost(ctx, tenantDID, *vp, *submission, responseURI, state)
}

func (r Wrapper) getClientMetadataFromRequest(ctx context.Context, params oauthParameters) (*oauth.OAuthClientMetadata, *oauth.OAuth2Error) {
var metadata *oauth.OAuthClientMetadata
var err error
if metadataString := params.get(clientMetadataParam); metadataString != "" {
if params.get(clientMetadataURIParam) != "" {
return nil, &oauth.OAuth2Error{Code: oauth.InvalidRequest, Description: "client_metadata and client_metadata_uri are mutually exclusive", InternalError: err}
}
err = json.Unmarshal([]byte(metadataString), &metadata)
if err != nil {
return nil, &oauth.OAuth2Error{Code: oauth.InvalidRequest, Description: "invalid client_metadata", InternalError: err}
}
} else {
metadata, err = r.auth.IAMClient().ClientMetadata(ctx, params.get(clientMetadataURIParam))
if err != nil {
return nil, &oauth.OAuth2Error{Code: oauth.ServerError, Description: "failed to get client metadata (verifier)", InternalError: err}
}
}
return metadata, nil
}

func (r Wrapper) getPresentationDefinitionFromRequest(ctx context.Context, params oauthParameters) (*pe.PresentationDefinition, *oauth.OAuth2Error) {
var presentationDefinition *pe.PresentationDefinition
var err error
if pdString := params.get(presentationDefParam); pdString != "" {
if params.get(presentationDefUriParam) != "" {
return nil, &oauth.OAuth2Error{Code: oauth.InvalidRequest, Description: "presentation_definition and presentation_definition_uri are mutually exclusive"}
}
err = json.Unmarshal([]byte(pdString), &presentationDefinition)
if err != nil {
return nil, &oauth.OAuth2Error{Code: oauth.InvalidRequest, Description: "invalid presentation_definition", InternalError: err}
}
} else {
presentationDefinitionURI := params.get(presentationDefUriParam)
presentationDefinition, err = r.auth.IAMClient().PresentationDefinition(ctx, presentationDefinitionURI)
if err != nil {
return nil, &oauth.OAuth2Error{Code: oauth.InvalidPresentationDefinitionURI, Description: fmt.Sprintf("failed to retrieve presentation definition on %s", presentationDefinitionURI), InternalError: err}
}
}
return presentationDefinition, nil
}

// sendAndHandleDirectPost sends OpenID4VP direct_post to the verifier. The verifier responds with a redirect to the client (including error fields if needed).
// If the direct post fails, the user-agent will be redirected back to the client with an error. (Original redirect_uri).
func (r Wrapper) sendAndHandleDirectPost(ctx context.Context, walletDID did.DID, vp vc.VerifiablePresentation, presentationSubmission pe.PresentationSubmission, verifierResponseURI string, state string) (HandleAuthorizeRequestResponseObject, error) {
Expand Down
57 changes: 53 additions & 4 deletions auth/api/iam/openid4vp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ package iam
import (
"context"
"encoding/json"
"github.com/lestrrat-go/jwx/v2/jwt"
"net/http"
"net/url"
"strings"
"testing"

"github.com/lestrrat-go/jwx/v2/jwt"

"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/go-did/vc"
"github.com/nuts-foundation/nuts-node/auth/oauth"
Expand Down Expand Up @@ -228,6 +229,29 @@ func TestWrapper_handleAuthorizeRequestFromVerifier(t *testing.T) {

require.NoError(t, err)
})
t.Run("client_metadata and client_metadata_uri are mutually exclusive", func(t *testing.T) {
ctx := newTestClient(t)
params := defaultParams()
params[clientMetadataParam] = "not empty"
_ = ctx.client.userSessionStore().Put(userSessionID, userSession)
expectPostError(t, ctx, oauth.InvalidRequest, "client_metadata and client_metadata_uri are mutually exclusive", responseURI, "state")

_, err := ctx.client.handleAuthorizeRequestFromVerifier(httpRequestCtx, holderDID, params, pe.WalletOwnerOrganization)

require.NoError(t, err)
})
t.Run("invalid client_metadata", func(t *testing.T) {
ctx := newTestClient(t)
params := defaultParams()
delete(params, clientMetadataURIParam)
params[clientMetadataParam] = "{invalid"
_ = ctx.client.userSessionStore().Put(userSessionID, userSession)
expectPostError(t, ctx, oauth.InvalidRequest, "invalid client_metadata", responseURI, "state")

_, err := ctx.client.handleAuthorizeRequestFromVerifier(httpRequestCtx, holderDID, params, pe.WalletOwnerOrganization)

require.NoError(t, err)
})
t.Run("fetching client metadata failed", func(t *testing.T) {
ctx := newTestClient(t)
params := defaultParams()
Expand Down Expand Up @@ -257,14 +281,39 @@ func TestWrapper_handleAuthorizeRequestFromVerifier(t *testing.T) {

assert.EqualError(t, err, "invalid_request - missing response_uri parameter")
})
t.Run("missing state and missing response_uri", func(t *testing.T) {
t.Run("missing state", func(t *testing.T) {
ctx := newTestClient(t)
params := defaultParams()
delete(params, responseURIParam)
delete(params, oauth.StateParam)

_, err := ctx.client.handleAuthorizeRequestFromVerifier(httpRequestCtx, holderDID, params, pe.WalletOwnerOrganization)

require.Error(t, err)
assert.EqualError(t, err, "invalid_request - missing state parameter")
})
t.Run("presentation_definition and presentation_definition_uri are mutually exclusive", func(t *testing.T) {
ctx := newTestClient(t)
ctx.iamClient.EXPECT().ClientMetadata(gomock.Any(), "https://example.com/.well-known/authorization-server/iam/verifier").Return(&clientMetadata, nil)
params := defaultParams()
params[presentationDefParam] = "not empty"
_ = ctx.client.userSessionStore().Put(userSessionID, userSession)
expectPostError(t, ctx, oauth.InvalidRequest, "presentation_definition and presentation_definition_uri are mutually exclusive", responseURI, "state")

_, err := ctx.client.handleAuthorizeRequestFromVerifier(httpRequestCtx, holderDID, params, pe.WalletOwnerOrganization)

require.NoError(t, err)
})
t.Run("invalid presentation_definition", func(t *testing.T) {
ctx := newTestClient(t)
params := defaultParams()
delete(params, presentationDefUriParam)
params[presentationDefParam] = "{invalid"
ctx.iamClient.EXPECT().ClientMetadata(gomock.Any(), "https://example.com/.well-known/authorization-server/iam/verifier").Return(&clientMetadata, nil)
_ = ctx.client.userSessionStore().Put(userSessionID, userSession)
expectPostError(t, ctx, oauth.InvalidRequest, "invalid presentation_definition", responseURI, "state")

_, err := ctx.client.handleAuthorizeRequestFromVerifier(httpRequestCtx, holderDID, params, pe.WalletOwnerOrganization)

require.NoError(t, err)
})
t.Run("invalid presentation_definition_uri", func(t *testing.T) {
ctx := newTestClient(t)
Expand Down

0 comments on commit a9ce5be

Please sign in to comment.