Skip to content

Commit

Permalink
added presentation_definition endpoint to the metadata
Browse files Browse the repository at this point in the history
added implementation of the presentation definition endpoint

test fix

added API client for presentation definition

implemented all steps in RequestAccessToken

refactor to prevent cycle

test setup for relying_party.RequestRFC021AccessToken

rebase fixes
  • Loading branch information
woutslakhorst committed Oct 27, 2023
1 parent 6d4bef3 commit 1da9d1c
Show file tree
Hide file tree
Showing 47 changed files with 725 additions and 435 deletions.
10 changes: 5 additions & 5 deletions auth/api/auth/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"encoding/json"
"fmt"
"github.com/nuts-foundation/nuts-node/audit"
"github.com/nuts-foundation/nuts-node/auth/oauth"
"net/http"
"net/url"
"regexp"
Expand Down Expand Up @@ -295,7 +296,7 @@ func (w Wrapper) RequestAccessToken(ctx context.Context, request RequestAccessTo
return nil, core.InvalidInputError("invalid authorization server endpoint: %s", jwtGrant.AuthorizationServerEndpoint)
}

accessTokenResult, err := w.Auth.RelyingParty().RequestAccessToken(ctx, jwtGrant.BearerToken, *authServerEndpoint)
accessTokenResult, err := w.Auth.RelyingParty().RequestRFC003AccessToken(ctx, jwtGrant.BearerToken, *authServerEndpoint)
if err != nil {
return nil, core.Error(http.StatusServiceUnavailable, err.Error())
}
Expand All @@ -310,22 +311,21 @@ func (w Wrapper) CreateAccessToken(ctx context.Context, request CreateAccessToke

if request.Body.GrantType != client.JwtBearerGrantType {
errDesc := fmt.Sprintf("grant_type must be: '%s'", client.JwtBearerGrantType)
errorResponse := AccessTokenRequestFailedResponse{Error: errOauthUnsupportedGrant, ErrorDescription: errDesc}
errorResponse := oauth.ErrorResponse{Error: errOauthUnsupportedGrant, Description: &errDesc}
return CreateAccessToken400JSONResponse(errorResponse), nil
}

const jwtPattern = `^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$`
if matched, err := regexp.Match(jwtPattern, []byte(request.Body.Assertion)); !matched || err != nil {
errDesc := "Assertion must be a valid encoded jwt"
errorResponse := AccessTokenRequestFailedResponse{Error: errOauthInvalidGrant, ErrorDescription: errDesc}
errorResponse := AccessTokenRequestFailedResponse{Error: errOauthInvalidGrant, Description: &errDesc}
return CreateAccessToken400JSONResponse(errorResponse), nil
}

catRequest := services.CreateAccessTokenRequest{RawJwtBearerToken: request.Body.Assertion}
acResponse, oauthError := w.Auth.AuthzServer().CreateAccessToken(ctx, catRequest)
if oauthError != nil {
errorResponse := AccessTokenRequestFailedResponse{Error: AccessTokenRequestFailedResponseError(oauthError.Code), ErrorDescription: oauthError.Error()}
return CreateAccessToken400JSONResponse(errorResponse), nil
return CreateAccessToken400JSONResponse(*oauthError), nil
}
response := AccessTokenResponse{
AccessToken: acResponse.AccessToken,
Expand Down
25 changes: 14 additions & 11 deletions auth/api/auth/v1/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/nuts-foundation/nuts-node/audit"
pkg2 "github.com/nuts-foundation/nuts-node/auth"
"github.com/nuts-foundation/nuts-node/auth/contract"
oauth2 "github.com/nuts-foundation/nuts-node/auth/oauth"
"github.com/nuts-foundation/nuts-node/auth/services"
"github.com/nuts-foundation/nuts-node/auth/services/dummy"
"github.com/nuts-foundation/nuts-node/auth/services/oauth"
Expand Down Expand Up @@ -475,7 +476,7 @@ func TestWrapper_RequestAccessToken(t *testing.T) {
BearerToken: bearerToken,
AuthorizationServerEndpoint: authEndpointURL.String(),
}, nil)
ctx.relyingPartyMock.EXPECT().RequestAccessToken(gomock.Any(), bearerToken, *authEndpointURL).Return(nil, errors.New("random error"))
ctx.relyingPartyMock.EXPECT().RequestRFC003AccessToken(gomock.Any(), bearerToken, *authEndpointURL).Return(nil, errors.New("random error"))

response, err := ctx.wrapper.RequestAccessToken(ctx.audit, RequestAccessTokenRequestObject{Body: &fakeRequest})

Expand Down Expand Up @@ -513,9 +514,10 @@ func TestWrapper_RequestAccessToken(t *testing.T) {
request := fakeRequest
request.Credentials = credentials

in10 := 10
expectedResponse := AccessTokenResponse{
TokenType: "token-type",
ExpiresIn: 10,
ExpiresIn: &in10,
AccessToken: "actual-token",
}

Expand All @@ -532,7 +534,7 @@ func TestWrapper_RequestAccessToken(t *testing.T) {
AuthorizationServerEndpoint: authEndpointURL.String(),
}, nil)
ctx.relyingPartyMock.EXPECT().
RequestAccessToken(gomock.Any(), bearerToken, *authEndpointURL).
RequestRFC003AccessToken(gomock.Any(), bearerToken, *authEndpointURL).
Return(&expectedResponse, nil)

response, err := ctx.wrapper.RequestAccessToken(ctx.audit, RequestAccessTokenRequestObject{Body: &request})
Expand All @@ -551,7 +553,7 @@ func TestWrapper_CreateAccessToken(t *testing.T) {
params := CreateAccessTokenRequest{GrantType: "unknown type"}

errorDescription := "grant_type must be: 'urn:ietf:params:oauth:grant-type:jwt-bearer'"
expectedResponse := CreateAccessToken400JSONResponse{ErrorDescription: errorDescription, Error: errOauthUnsupportedGrant}
expectedResponse := CreateAccessToken400JSONResponse{Description: &errorDescription, Error: errOauthUnsupportedGrant}

response, err := ctx.wrapper.CreateAccessToken(ctx.audit, CreateAccessTokenRequestObject{Body: &params})

Expand All @@ -565,7 +567,7 @@ func TestWrapper_CreateAccessToken(t *testing.T) {
params := CreateAccessTokenRequest{GrantType: "urn:ietf:params:oauth:grant-type:jwt-bearer", Assertion: "invalid jwt"}

errorDescription := "Assertion must be a valid encoded jwt"
expectedResponse := CreateAccessToken400JSONResponse{ErrorDescription: errorDescription, Error: errOauthInvalidGrant}
expectedResponse := CreateAccessToken400JSONResponse{Description: &errorDescription, Error: errOauthInvalidGrant}

response, err := ctx.wrapper.CreateAccessToken(ctx.audit, CreateAccessTokenRequestObject{Body: &params})

Expand All @@ -579,11 +581,11 @@ func TestWrapper_CreateAccessToken(t *testing.T) {
params := CreateAccessTokenRequest{GrantType: "urn:ietf:params:oauth:grant-type:jwt-bearer", Assertion: validJwt}

errorDescription := "oh boy"
expectedResponse := CreateAccessToken400JSONResponse{ErrorDescription: errorDescription, Error: errOauthInvalidRequest}
expectedResponse := CreateAccessToken400JSONResponse{Description: &errorDescription, Error: errOauthInvalidRequest}

ctx.authzServerMock.EXPECT().CreateAccessToken(ctx.audit, services.CreateAccessTokenRequest{RawJwtBearerToken: validJwt}).Return(nil, &oauth.ErrorResponse{
Description: errors.New(errorDescription),
Code: errOauthInvalidRequest,
ctx.authzServerMock.EXPECT().CreateAccessToken(ctx.audit, services.CreateAccessTokenRequest{RawJwtBearerToken: validJwt}).Return(nil, &oauth2.ErrorResponse{
Description: &errorDescription,
Error: errOauthInvalidRequest,
})

response, err := ctx.wrapper.CreateAccessToken(ctx.audit, CreateAccessTokenRequestObject{Body: &params})
Expand All @@ -597,12 +599,13 @@ func TestWrapper_CreateAccessToken(t *testing.T) {

params := CreateAccessTokenRequest{GrantType: "urn:ietf:params:oauth:grant-type:jwt-bearer", Assertion: validJwt}

pkgResponse := &services.AccessTokenResult{AccessToken: "foo", ExpiresIn: 800000}
in800000 := 800000
pkgResponse := &oauth2.TokenResponse{AccessToken: "foo", ExpiresIn: &in800000}
ctx.authzServerMock.EXPECT().CreateAccessToken(gomock.Any(), services.CreateAccessTokenRequest{RawJwtBearerToken: validJwt}).Return(pkgResponse, nil)

expectedResponse := CreateAccessToken200JSONResponse{
AccessToken: pkgResponse.AccessToken,
ExpiresIn: 800000,
ExpiresIn: &in800000,
TokenType: "bearer",
}

Expand Down
18 changes: 0 additions & 18 deletions auth/api/auth/v1/client/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions auth/api/auth/v1/client/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package client

import (
"github.com/nuts-foundation/go-did/vc"
"github.com/nuts-foundation/nuts-node/auth/services"
"github.com/nuts-foundation/nuts-node/auth/oauth"
)

// JwtBearerGrantType defines the grant-type to use in the access token request
Expand All @@ -33,4 +33,7 @@ type VerifiableCredential = vc.VerifiableCredential
type VerifiablePresentation = vc.VerifiablePresentation

// AccessTokenResponse is an alias to use from within the API
type AccessTokenResponse = services.AccessTokenResult
type AccessTokenResponse = oauth.TokenResponse

// AccessTokenRequestFailedResponse is an alias to use from within the API
type AccessTokenRequestFailedResponse = oauth.ErrorResponse
18 changes: 0 additions & 18 deletions auth/api/auth/v1/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions auth/api/auth/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package v1

import (
"github.com/nuts-foundation/go-did/vc"
"github.com/nuts-foundation/nuts-node/auth/services"
"github.com/nuts-foundation/nuts-node/auth/oauth"
)

// VerifiableCredential is an alias to use from within the API
Expand All @@ -30,4 +30,6 @@ type VerifiableCredential = vc.VerifiableCredential
type VerifiablePresentation = vc.VerifiablePresentation

// AccessTokenResponse is an alias to use from within the API
type AccessTokenResponse = services.AccessTokenResult
type AccessTokenResponse = oauth.TokenResponse

type AccessTokenRequestFailedResponse = oauth.ErrorResponse
33 changes: 17 additions & 16 deletions auth/api/iam/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/nuts-foundation/nuts-node/audit"
"github.com/nuts-foundation/nuts-node/auth"
"github.com/nuts-foundation/nuts-node/auth/log"
"github.com/nuts-foundation/nuts-node/auth/oauth"
"github.com/nuts-foundation/nuts-node/core"
"github.com/nuts-foundation/nuts-node/storage"
"github.com/nuts-foundation/nuts-node/vcr"
Expand Down Expand Up @@ -105,7 +106,7 @@ func (r Wrapper) middleware(ctx echo.Context, request interface{}, operationID s
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{})
ctx.Set(core.ErrorWriterContextKey, &oauth.Oauth2ErrorWriter{})
}
audit.StrictMiddleware(f, apiModuleName, operationID)
return f(ctx, request)
Expand All @@ -118,27 +119,27 @@ func (r Wrapper) HandleTokenRequest(ctx context.Context, request HandleTokenRequ
// Options:
// - OpenID4VCI
// - OpenID4VP, vp_token is sent in Token Response
return nil, OAuth2Error{
Code: UnsupportedGrantType,
return nil, oauth.OAuth2Error{
Code: oauth.UnsupportedGrantType,
Description: "not implemented yet",
}
case "vp_token":
// Options:
// - service-to-service vp_token flow
return nil, OAuth2Error{
Code: UnsupportedGrantType,
return nil, oauth.OAuth2Error{
Code: oauth.UnsupportedGrantType,
Description: "not implemented yet",
}
case "urn:ietf:params:oauth:grant-type:pre-authorized_code":
// Options:
// - OpenID4VCI
return nil, OAuth2Error{
Code: UnsupportedGrantType,
return nil, oauth.OAuth2Error{
Code: oauth.UnsupportedGrantType,
Description: "not implemented yet",
}
default:
return nil, OAuth2Error{
Code: UnsupportedGrantType,
return nil, oauth.OAuth2Error{
Code: oauth.UnsupportedGrantType,
}
}
}
Expand All @@ -160,8 +161,8 @@ 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, OAuth2Error{
Code: InvalidRequest,
return nil, oauth.OAuth2Error{
Code: oauth.InvalidRequest,
Description: "redirect_uri is required",
}
}
Expand All @@ -186,8 +187,8 @@ func (r Wrapper) HandleAuthorizeRequest(ctx context.Context, request HandleAutho
return r.handlePresentationRequest(params, session)
default:
// TODO: This should be a redirect?
return nil, OAuth2Error{
Code: UnsupportedResponseType,
return nil, oauth.OAuth2Error{
Code: oauth.UnsupportedResponseType,
RedirectURI: session.RedirectURI,
}
}
Expand Down Expand Up @@ -254,9 +255,9 @@ func (r Wrapper) PresentationDefinition(_ context.Context, request PresentationD
scopes := strings.Split(request.Params.Scope, " ")
presentationDefinition := r.auth.PresentationDefinitions().ByScope(scopes[0])
if presentationDefinition == nil {
return PresentationDefinition400JSONResponse{
Code: "invalid_scope",
}, nil
return nil, oauth.OAuth2Error{
Code: oauth.InvalidScope,
}
}

return PresentationDefinition200JSONResponse(*presentationDefinition), nil
Expand Down
Loading

0 comments on commit 1da9d1c

Please sign in to comment.