Skip to content
Draft
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
18 changes: 16 additions & 2 deletions auth/api/iam/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@
"encoding/json"
"errors"
"fmt"
"github.com/nuts-foundation/nuts-node/core/to"
"html/template"
"net/http"
"net/url"
"slices"
"strings"
"time"

"github.com/nuts-foundation/nuts-node/core/to"

"github.com/labstack/echo/v4"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/lestrrat-go/jwx/v2/jwt"
Expand Down Expand Up @@ -234,6 +235,15 @@
Code: oauth.UnsupportedGrantType,
Description: "not implemented yet",
}
case oauth.JWTBearerGrantType:
// NL Generic Functions Authentication flow
if request.Body.Assertion == nil || request.Body.Scope == nil ||
request.Body.ClientId == nil || request.Body.ClientAssertion == nil {
return nil, oauth.OAuth2Error{
Code: oauth.InvalidRequest,
Description: "missing required parameters",
}
}
case oauth.VpTokenGrantType:
// Nuts RFC021 vp_token bearer flow
if request.Body.PresentationSubmission == nil || request.Body.Scope == nil || request.Body.Assertion == nil || request.Body.ClientId == nil {
Expand All @@ -249,7 +259,7 @@
Description: fmt.Sprintf("grant_type '%s' is not supported", request.Body.GrantType),
}
}
}

Check failure on line 262 in auth/api/iam/api.go

View workflow job for this annotation

GitHub Actions / Run govulncheck

missing return

func (r Wrapper) Callback(ctx context.Context, request CallbackRequestObject) (CallbackResponseObject, error) {
if !r.auth.AuthorizationEndpointEnabled() {
Expand Down Expand Up @@ -774,7 +784,11 @@
useDPoP = false
}
clientID := r.subjectToBaseURL(request.SubjectID)
tokenResult, err := r.auth.IAMClient().RequestRFC021AccessToken(ctx, clientID.String(), request.SubjectID, request.Body.AuthorizationServer, request.Body.Scope, useDPoP, credentials)
var policyId string
if request.Body.PolicyId != nil {
policyId = *request.Body.PolicyId
}
tokenResult, err := r.auth.IAMClient().RequestRFC021AccessToken(ctx, clientID.String(), request.SubjectID, request.Body.AuthorizationServer, request.Body.Scope, policyId, useDPoP, credentials)
if err != nil {
// this can be an internal server error, a 400 oauth error or a 412 precondition failed if the wallet does not contain the required credentials
return nil, err
Expand Down
18 changes: 9 additions & 9 deletions auth/api/iam/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,7 @@ func TestWrapper_RequestServiceAccessToken(t *testing.T) {
request := RequestServiceAccessTokenRequestObject{SubjectID: holderSubjectID, Body: body}
request.Params.CacheControl = to.Ptr("no-cache")
// Initial call to populate cache
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", true, nil).Return(response, nil).Times(2)
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", "", true, nil).Return(response, nil).Times(2)
token, err := ctx.client.RequestServiceAccessToken(nil, request)

// Test call to check cache is bypassed
Expand All @@ -894,7 +894,7 @@ func TestWrapper_RequestServiceAccessToken(t *testing.T) {
TokenType: "Bearer",
ExpiresIn: to.Ptr(900),
}
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", true, nil).Return(response, nil)
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", "", true, nil).Return(response, nil)

token, err := ctx.client.RequestServiceAccessToken(nil, request)

Expand Down Expand Up @@ -933,7 +933,7 @@ func TestWrapper_RequestServiceAccessToken(t *testing.T) {
t.Run("cache expired", func(t *testing.T) {
cacheKey := accessTokenRequestCacheKey(request)
_ = ctx.client.accessTokenCache().Delete(cacheKey)
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", true, nil).Return(&oauth.TokenResponse{AccessToken: "other"}, nil)
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", "", true, nil).Return(&oauth.TokenResponse{AccessToken: "other"}, nil)

otherToken, err := ctx.client.RequestServiceAccessToken(nil, request)

Expand All @@ -950,7 +950,7 @@ func TestWrapper_RequestServiceAccessToken(t *testing.T) {
Scope: "first second",
TokenType: &tokenTypeBearer,
}
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", false, nil).Return(&oauth.TokenResponse{}, nil)
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", "", false, nil).Return(&oauth.TokenResponse{}, nil)

_, err := ctx.client.RequestServiceAccessToken(nil, RequestServiceAccessTokenRequestObject{SubjectID: holderSubjectID, Body: body})

Expand All @@ -959,7 +959,7 @@ func TestWrapper_RequestServiceAccessToken(t *testing.T) {
t.Run("ok with expired cache by ttl", func(t *testing.T) {
ctx := newTestClient(t)
request := RequestServiceAccessTokenRequestObject{SubjectID: holderSubjectID, Body: body}
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", true, nil).Return(&oauth.TokenResponse{ExpiresIn: to.Ptr(5)}, nil)
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", "", true, nil).Return(&oauth.TokenResponse{ExpiresIn: to.Ptr(5)}, nil)

_, err := ctx.client.RequestServiceAccessToken(nil, request)

Expand All @@ -968,7 +968,7 @@ func TestWrapper_RequestServiceAccessToken(t *testing.T) {
})
t.Run("error - no matching credentials", func(t *testing.T) {
ctx := newTestClient(t)
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", true, nil).Return(nil, pe.ErrNoCredentials)
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", "", true, nil).Return(nil, pe.ErrNoCredentials)

_, err := ctx.client.RequestServiceAccessToken(nil, RequestServiceAccessTokenRequestObject{SubjectID: holderSubjectID, Body: body})

Expand All @@ -984,8 +984,8 @@ func TestWrapper_RequestServiceAccessToken(t *testing.T) {
ctx.client.storageEngine = mockStorage

request := RequestServiceAccessTokenRequestObject{SubjectID: holderSubjectID, Body: body}
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", true, nil).Return(&oauth.TokenResponse{AccessToken: "first"}, nil)
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", true, nil).Return(&oauth.TokenResponse{AccessToken: "second"}, nil)
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", "", true, nil).Return(&oauth.TokenResponse{AccessToken: "first"}, nil)
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", "", true, nil).Return(&oauth.TokenResponse{AccessToken: "second"}, nil)

token1, err := ctx.client.RequestServiceAccessToken(nil, request)
require.NoError(t, err)
Expand All @@ -1010,7 +1010,7 @@ func TestWrapper_RequestServiceAccessToken(t *testing.T) {
{ID: to.Ptr(ssi.MustParseURI("not empty"))},
}
request := RequestServiceAccessTokenRequestObject{SubjectID: holderSubjectID, Body: body}
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", true, *body.Credentials).Return(response, nil)
ctx.iamClient.EXPECT().RequestRFC021AccessToken(nil, holderClientID, holderSubjectID, verifierURL.String(), "first second", "", true, *body.Credentials).Return(response, nil)

_, err := ctx.client.RequestServiceAccessToken(nil, request)

Expand Down
7 changes: 7 additions & 0 deletions auth/api/iam/generated.go

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

16 changes: 10 additions & 6 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ package auth
import (
"crypto/tls"
"errors"
"net/url"
"path"
"slices"
"time"

"github.com/nuts-foundation/nuts-node/auth/client/iam"
"github.com/nuts-foundation/nuts-node/policy"
"github.com/nuts-foundation/nuts-node/vdr"
"github.com/nuts-foundation/nuts-node/vdr/didjwk"
"github.com/nuts-foundation/nuts-node/vdr/didkey"
Expand All @@ -30,10 +36,6 @@ import (
"github.com/nuts-foundation/nuts-node/vdr/didweb"
"github.com/nuts-foundation/nuts-node/vdr/didx509"
"github.com/nuts-foundation/nuts-node/vdr/resolver"
"net/url"
"path"
"slices"
"time"

"github.com/nuts-foundation/nuts-node/auth/services"
"github.com/nuts-foundation/nuts-node/auth/services/notary"
Expand All @@ -58,6 +60,7 @@ type Auth struct {
relyingParty oauth.RelyingParty
contractNotary services.ContractNotary
serviceResolver didman.CompoundServiceResolver
policyBackend policy.PDPBackend
keyStore crypto.KeyStore
vcr vcr.VCR
pkiProvider pki.Provider
Expand Down Expand Up @@ -100,12 +103,13 @@ func (auth *Auth) ContractNotary() services.ContractNotary {

// NewAuthInstance accepts a Config with several Nuts Engines and returns an instance of Auth
func NewAuthInstance(config Config, vdrInstance vdr.VDR, subjectManager didsubject.Manager, vcr vcr.VCR, keyStore crypto.KeyStore,
serviceResolver didman.CompoundServiceResolver, jsonldManager jsonld.JSONLD, pkiProvider pki.Provider) *Auth {
serviceResolver didman.CompoundServiceResolver, jsonldManager jsonld.JSONLD, pkiProvider pki.Provider, policyBackend policy.PDPBackend) *Auth {
return &Auth{
config: config,
jsonldManager: jsonldManager,
vdrInstance: vdrInstance,
subjectManager: subjectManager,
policyBackend: policyBackend,
keyStore: keyStore,
vcr: vcr,
pkiProvider: pkiProvider,
Expand All @@ -126,7 +130,7 @@ func (auth *Auth) RelyingParty() oauth.RelyingParty {

func (auth *Auth) IAMClient() iam.Client {
keyResolver := resolver.DIDKeyResolver{Resolver: auth.vdrInstance.Resolver()}
return iam.NewClient(auth.vcr.Wallet(), keyResolver, auth.subjectManager, auth.keyStore, auth.jsonldManager.DocumentLoader(), auth.strictMode, auth.httpClientTimeout)
return iam.NewClient(auth.vcr.Wallet(), keyResolver, auth.subjectManager, auth.keyStore, auth.jsonldManager.DocumentLoader(), auth.policyBackend, auth.strictMode, auth.httpClientTimeout)
}

// Configure the Auth struct by creating a validator and create an Irma server
Expand Down
9 changes: 5 additions & 4 deletions auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
package auth

import (
"testing"

"github.com/nuts-foundation/nuts-node/core"
"github.com/nuts-foundation/nuts-node/crypto"
"github.com/nuts-foundation/nuts-node/jsonld"
Expand All @@ -28,7 +30,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"testing"
)

func TestAuth_Configure(t *testing.T) {
Expand All @@ -47,7 +48,7 @@ func TestAuth_Configure(t *testing.T) {
vdrInstance := vdr.NewMockVDR(ctrl)
vdrInstance.EXPECT().Resolver().AnyTimes()

i := NewAuthInstance(config, vdrInstance, nil, vcr.NewTestVCRInstance(t), crypto.NewMemoryCryptoInstance(t), nil, nil, pkiMock)
i := NewAuthInstance(config, vdrInstance, nil, vcr.NewTestVCRInstance(t), crypto.NewMemoryCryptoInstance(t), nil, nil, pkiMock, nil)

require.NoError(t, i.Configure(tlsServerConfig))
})
Expand All @@ -61,7 +62,7 @@ func TestAuth_Configure(t *testing.T) {
vdrInstance := vdr.NewMockVDR(ctrl)
vdrInstance.EXPECT().Resolver().AnyTimes()

i := NewAuthInstance(config, vdrInstance, nil, vcr.NewTestVCRInstance(t), crypto.NewMemoryCryptoInstance(t), nil, nil, pkiMock)
i := NewAuthInstance(config, vdrInstance, nil, vcr.NewTestVCRInstance(t), crypto.NewMemoryCryptoInstance(t), nil, nil, pkiMock, nil)

require.NoError(t, i.Configure(tlsServerConfig))
})
Expand Down Expand Up @@ -119,7 +120,7 @@ func TestAuth_IAMClient(t *testing.T) {
vdrInstance := vdr.NewMockVDR(ctrl)
vdrInstance.EXPECT().Resolver().AnyTimes()

i := NewAuthInstance(config, vdrInstance, nil, vcr.NewTestVCRInstance(t), crypto.NewMemoryCryptoInstance(t), nil, jsonld.NewTestJSONLDManager(t), pkiMock)
i := NewAuthInstance(config, vdrInstance, nil, vcr.NewTestVCRInstance(t), crypto.NewMemoryCryptoInstance(t), nil, jsonld.NewTestJSONLDManager(t), pkiMock, nil)

assert.NotNil(t, i.IAMClient())
})
Expand Down
2 changes: 1 addition & 1 deletion auth/client/iam/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type Client interface {
// PresentationDefinition returns the presentation definition from the given endpoint.
PresentationDefinition(ctx context.Context, endpoint string) (*pe.PresentationDefinition, error)
// RequestRFC021AccessToken is called by the local EHR node to request an access token from a remote OAuth2 Authorization Server using Nuts RFC021.
RequestRFC021AccessToken(ctx context.Context, clientID string, subjectDID string, authServerURL string, scopes string, useDPoP bool,
RequestRFC021AccessToken(ctx context.Context, clientID string, subjectDID string, authServerURL string, scopes string, policyId string, useDPoP bool,
credentials []vc.VerifiableCredential) (*oauth.TokenResponse, error)

// OpenIdCredentialIssuerMetadata returns the metadata of the remote credential issuer.
Expand Down
8 changes: 4 additions & 4 deletions auth/client/iam/mock.go

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

Loading
Loading