Skip to content

Commit

Permalink
IAM: Implement correct OAuth2 error handling (#2515)
Browse files Browse the repository at this point in the history
  • Loading branch information
reinkrul authored Oct 2, 2023
1 parent 3b49fa8 commit 7ab8356
Show file tree
Hide file tree
Showing 11 changed files with 500 additions and 95 deletions.
95 changes: 44 additions & 51 deletions auth/api/iam/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,19 @@ import (
"github.com/nuts-foundation/nuts-node/auth/log"
"github.com/nuts-foundation/nuts-node/core"
"github.com/nuts-foundation/nuts-node/vcr"
"github.com/nuts-foundation/nuts-node/vcr/openid4vci"
"github.com/nuts-foundation/nuts-node/vdr"
"github.com/nuts-foundation/nuts-node/vdr/resolver"
"html/template"
"net/http"
"strings"
"sync"
)

var _ core.Routable = &Wrapper{}
var _ StrictServerInterface = &Wrapper{}

const apiPath = "iam"
const apiModuleName = auth.ModuleName + "/" + apiPath
const httpRequestContextKey = "http-request"

//go:embed assets
Expand Down Expand Up @@ -71,31 +72,12 @@ func New(authInstance auth.AuthenticationServices, vcrInstance vcr.VCR, vdrInsta
}

func (r Wrapper) Routes(router core.EchoRouter) {
const apiModuleName = auth.ModuleName + "/" + apiPath
RegisterHandlers(router, NewStrictHandler(r, []StrictMiddlewareFunc{
func(f StrictHandlerFunc, operationID string) StrictHandlerFunc {
return func(ctx echo.Context, request interface{}) (response interface{}, err error) {
ctx.Set(core.OperationIDContextKey, operationID)
ctx.Set(core.ModuleNameContextKey, apiModuleName)
// Add http.Request to context, to allow reading URL query parameters
requestCtx := context.WithValue(ctx.Request().Context(), httpRequestContextKey, ctx.Request())
ctx.SetRequest(ctx.Request().WithContext(requestCtx))
// TODO: Do we need a generic error handler?
// ctx.Set(core.ErrorWriterContextKey, &protocolErrorWriter{})
return f(ctx, request)
return r.middleware(ctx, request, operationID, f)
}
},
func(f StrictHandlerFunc, operationID string) StrictHandlerFunc {
return func(ctx echo.Context, args interface{}) (interface{}, error) {
if !r.auth.V2APIEnabled() {
return nil, core.Error(http.StatusForbidden, "Access denied")
}
return f(ctx, args)
}
},
func(f StrictHandlerFunc, operationID string) StrictHandlerFunc {
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)
Expand All @@ -111,37 +93,54 @@ func (r Wrapper) Routes(router core.EchoRouter) {
router.POST("/iam/:did/openid4vp_demo", r.handleOpenID4VPDemoSendRequest, auditMiddleware)
}

func (r Wrapper) middleware(ctx echo.Context, request interface{}, operationID string, f StrictHandlerFunc) (interface{}, error) {
ctx.Set(core.OperationIDContextKey, operationID)
ctx.Set(core.ModuleNameContextKey, apiModuleName)

if !r.auth.V2APIEnabled() {
return nil, core.Error(http.StatusForbidden, "Access denied")
}

// Add http.Request to context, to allow reading URL query parameters
requestCtx := context.WithValue(ctx.Request().Context(), httpRequestContextKey, ctx.Request())
ctx.SetRequest(ctx.Request().WithContext(requestCtx))
if strings.HasPrefix(ctx.Request().URL.Path, "/iam/") {
ctx.Set(core.ErrorWriterContextKey, &oauth2ErrorWriter{})
}
audit.StrictMiddleware(f, apiModuleName, operationID)
return f(ctx, request)
}

// HandleTokenRequest handles calls to the token endpoint for exchanging a grant (e.g authorization code or pre-authorized code) for an access token.
func (r Wrapper) HandleTokenRequest(ctx context.Context, request HandleTokenRequestRequestObject) (HandleTokenRequestResponseObject, error) {
switch request.Body.GrantType {
case "authorization_code":
// Options:
// - OpenID4VCI
// - OpenID4VP, vp_token is sent in Token Response
return nil, OAuth2Error{
Code: UnsupportedGrantType,
Description: "not implemented yet",
}
case "vp_token":
// Options:
// - service-to-service vp_token flow
return nil, OAuth2Error{
Code: UnsupportedGrantType,
Description: "not implemented yet",
}
case "urn:ietf:params:oauth:grant-type:pre-authorized_code":
// Options:
// - OpenID4VCI
return nil, OAuth2Error{
Code: UnsupportedGrantType,
Description: "not implemented yet",
}
default:
// TODO: Don't use openid4vci package for errors
return nil, openid4vci.Error{
Code: openid4vci.InvalidRequest,
StatusCode: http.StatusBadRequest,
//Description: "invalid grant type",
return nil, OAuth2Error{
Code: UnsupportedGrantType,
}
}

// TODO: Handle?
//scope, err := handler(request.Body.AdditionalProperties)
//if err != nil {
// return nil, err
//}
// TODO: Generate access token with scope
return HandleTokenRequest200JSONResponse(TokenResponse{
AccessToken: "",
}), nil
}

// HandleAuthorizeRequest handles calls to the authorization endpoint for starting an authorization code flow.
Expand All @@ -161,7 +160,10 @@ func (r Wrapper) HandleAuthorizeRequest(ctx context.Context, request HandleAutho
// TODO: Spec says that the redirect URI is optional, but it's not clear what to do if it's not provided.
// Threat models say it's unsafe to omit redirect_uri.
// See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1
return nil, errors.New("missing redirect URI")
return nil, OAuth2Error{
Code: InvalidRequest,
Description: "redirect_uri is required",
}
}

switch session.ResponseType {
Expand All @@ -171,33 +173,24 @@ func (r Wrapper) HandleAuthorizeRequest(ctx context.Context, request HandleAutho
// - OpenID4VCI; authorization code flow for credential issuance to (end-user) wallet
// - OpenID4VP, vp_token is sent in Token Response; authorization code flow for presentation exchange (not required a.t.m.)
// TODO: Switch on parameters to right flow
panic("not implemented")
case responseTypeVPToken:
// Options:
// - OpenID4VP flow, vp_token is sent in Authorization Response
// TODO: Check parameters for right flow
// TODO: Do we actually need this? (probably not)
panic("not implemented")
case responseTypeVPIDToken:
// Options:
// - OpenID4VP+SIOP flow, vp_token is sent in Authorization Response
return r.handlePresentationRequest(params, session)
default:
// TODO: This should be a redirect?
// TODO: Don't use openid4vci package for errors
return nil, openid4vci.Error{
Code: openid4vci.InvalidRequest,
StatusCode: http.StatusBadRequest,
//Description: "invalid/unsupported response_type",
return nil, OAuth2Error{
Code: UnsupportedResponseType,
RedirectURI: session.RedirectURI,
}
}

// No handler could handle the request
// TODO: This should be a redirect?
// TODO: Don't use openid4vci package for errors
return nil, openid4vci.Error{
Code: openid4vci.InvalidRequest,
StatusCode: http.StatusBadRequest,
//Description: "missing or invalid parameters",
}
}

// OAuthAuthorizationServerMetadata returns the Authorization Server's metadata
Expand Down
Loading

0 comments on commit 7ab8356

Please sign in to comment.