Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IAM: Implement correct OAuth2 error handling #2515

Merged
merged 7 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading