Skip to content

Commit

Permalink
Merge branch 'master' into usecaselist-maintainer
Browse files Browse the repository at this point in the history
  • Loading branch information
reinkrul committed Nov 12, 2023
2 parents 31324ad + 478a8ee commit 51537f8
Show file tree
Hide file tree
Showing 115 changed files with 1,478 additions and 870 deletions.
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# golang alpine
FROM golang:1.21.3-alpine as builder
FROM golang:1.21.4-alpine as builder

ARG TARGETARCH
ARG TARGETOS
Expand All @@ -11,6 +11,9 @@ ARG GIT_VERSION=undefined
LABEL maintainer="[email protected]"

RUN apk update \
&& apk add --no-cache \
gcc \
musl-dev \
&& update-ca-certificates

ENV GO111MODULE on
Expand All @@ -22,7 +25,7 @@ COPY go.sum .
RUN go mod download && go mod verify

COPY . .
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags="-w -s -X 'github.com/nuts-foundation/nuts-node/core.GitCommit=${GIT_COMMIT}' -X 'github.com/nuts-foundation/nuts-node/core.GitBranch=${GIT_BRANCH}' -X 'github.com/nuts-foundation/nuts-node/core.GitVersion=${GIT_VERSION}'" -o /opt/nuts/nuts
RUN CGO_ENABLED=1 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags="-w -s -X 'github.com/nuts-foundation/nuts-node/core.GitCommit=${GIT_COMMIT}' -X 'github.com/nuts-foundation/nuts-node/core.GitBranch=${GIT_BRANCH}' -X 'github.com/nuts-foundation/nuts-node/core.GitVersion=${GIT_VERSION}'" -o /opt/nuts/nuts

# alpine
FROM alpine:3.18.4
Expand Down
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ The following options can be configured on the server:
storage.redis.sentinel.password Password for authenticating to Redis Sentinels.
storage.redis.sentinel.username Username for authenticating to Redis Sentinels.
storage.redis.tls.truststorefile PEM file containing the trusted CA certificate(s) for authenticating remote Redis servers. Can only be used when connecting over TLS (use 'rediss://' as scheme in address).
storage.sql.connection Connection string for the SQL database. If not set, it defaults to a SQLite database stored inside the configured data directory
**VCR**
vcr.openid4vci.definitionsdir Directory with the additional credential definitions the node could issue (experimental, may change without notice).
vcr.openid4vci.enabled true Enable issuing and receiving credentials over OpenID4VCI.
Expand Down
2 changes: 1 addition & 1 deletion api/ssi_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func createDidDocument() did.Document {
KeyAgreement: did.VerificationRelationships{verificationRelationship},
VerificationMethod: did.VerificationMethods{verificationMethod},
Controller: []did.DID{did.MustParseDID("did:example:controller")},
ID: verificationMethod.ID,
ID: verificationMethod.ID.DID,
Service: []did.Service{
{
ID: ssi.MustParseURI("example"),
Expand Down
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 51537f8

Please sign in to comment.