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

add s2s access token request for relying party #2568

Merged
merged 6 commits into from
Nov 10, 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
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
Loading