Skip to content

Commit

Permalink
IAM: createAccessToken() func for handling s2s access token requests …
Browse files Browse the repository at this point in the history
…and token introspection (#2558)
  • Loading branch information
reinkrul authored Oct 25, 2023
1 parent 5521f17 commit c5afa09
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 6 deletions.
8 changes: 6 additions & 2 deletions auth/api/iam/api_test.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/core"
"github.com/nuts-foundation/nuts-node/storage"
"github.com/nuts-foundation/nuts-node/vdr"
"github.com/nuts-foundation/nuts-node/vdr/resolver"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -241,18 +242,21 @@ func newTestClient(t testing.TB) *testCtx {
publicURL, err := url.Parse("https://example.com")
require.NoError(t, err)
ctrl := gomock.NewController(t)
storageEngine := storage.NewTestStorageEngine(t)
authnServices := auth.NewMockAuthenticationServices(ctrl)
authnServices.EXPECT().PublicURL().Return(publicURL).AnyTimes()
resolver := resolver.NewMockDIDResolver(ctrl)
vdr := vdr.NewMockVDR(ctrl)
vdr.EXPECT().Resolver().Return(resolver).AnyTimes()

return &testCtx{
authnServices: authnServices,
resolver: resolver,
vdr: vdr,
client: &Wrapper{
auth: authnServices,
vdr: vdr,
auth: authnServices,
vdr: vdr,
storageEngine: storageEngine,
},
}
}
Expand Down
53 changes: 53 additions & 0 deletions auth/api/iam/s2s_vptoken.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,27 @@ package iam

import (
"context"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"github.com/labstack/echo/v4"
"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/go-did/vc"
"github.com/nuts-foundation/nuts-node/core"
"github.com/nuts-foundation/nuts-node/storage"
"github.com/nuts-foundation/nuts-node/vdr/resolver"
"net/http"
"time"
)

// secretSizeBits is the size of the generated random secrets (access tokens, pre-authorized codes) in bits.
const secretSizeBits = 128

// accessTokenValidity defines how long access tokens are valid.
// TODO: Might want to make this configurable at some point
const accessTokenValidity = 15 * time.Minute

// serviceToService adds support for service-to-service OAuth2 flows,
// which uses a custom vp_token grant to authenticate calls to the token endpoint.
// Clients first call the presentation definition endpoint to get a presentation definition for the desired scope,
Expand Down Expand Up @@ -99,3 +112,43 @@ func (r Wrapper) RequestAccessToken(ctx context.Context, request RequestAccessTo

return RequestAccessToken200JSONResponse{}, nil
}

func (r Wrapper) createAccessToken(issuer did.DID, issueTime time.Time, presentation vc.VerifiablePresentation, scope string) (*TokenResponse, error) {
accessToken := AccessToken{
Token: generateCode(),
Issuer: issuer.String(),
Expiration: issueTime.Add(accessTokenValidity),
Presentation: presentation,
}
err := r.accessTokenStore(issuer).Put(accessToken.Token, accessToken)
if err != nil {
return nil, fmt.Errorf("unable to store access token: %w", err)
}
expiresIn := int(accessTokenValidity.Seconds())
return &TokenResponse{
AccessToken: accessToken.Token,
ExpiresIn: &expiresIn,
Scope: &scope,
TokenType: "bearer",
}, nil
}

func (r Wrapper) accessTokenStore(issuer did.DID) storage.SessionStore {
return r.storageEngine.GetSessionDatabase().GetStore(accessTokenValidity, "s2s", issuer.String(), "accesstoken")
}

func generateCode() string {
buf := make([]byte, secretSizeBits/8)
_, err := rand.Read(buf)
if err != nil {
panic(err)
}
return base64.URLEncoding.EncodeToString(buf)
}

type AccessToken struct {
Token string
Issuer string
Expiration time.Time
Presentation vc.VerifiablePresentation
}
37 changes: 34 additions & 3 deletions auth/api/iam/s2s_vptoken_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
package iam

import (
"github.com/nuts-foundation/nuts-node/vdr/resolver"
"testing"

"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/go-did/vc"
"github.com/nuts-foundation/nuts-node/jsonld"
"github.com/nuts-foundation/nuts-node/vdr/resolver"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
"time"
)

func TestWrapper_RequestAccessToken(t *testing.T) {
Expand Down Expand Up @@ -86,3 +88,32 @@ func TestWrapper_RequestAccessToken(t *testing.T) {
assert.EqualError(t, err, "verifier not found: unable to find the DID document")
})
}

func TestWrapper_createAccessToken(t *testing.T) {
credential, err := vc.ParseVerifiableCredential(jsonld.TestOrganizationCredential)
require.NoError(t, err)
presentation := vc.VerifiablePresentation{
VerifiableCredential: []vc.VerifiableCredential{*credential},
}
t.Run("ok", func(t *testing.T) {
ctx := newTestClient(t)

accessToken, err := ctx.client.createAccessToken(issuerDID, time.Now(), presentation, "everything")

require.NoError(t, err)
assert.NotEmpty(t, accessToken.AccessToken)
assert.Equal(t, "bearer", accessToken.TokenType)
assert.Equal(t, 900, *accessToken.ExpiresIn)
assert.Equal(t, "everything", *accessToken.Scope)

var storedToken AccessToken
err = ctx.client.accessTokenStore(issuerDID).Get(accessToken.AccessToken, &storedToken)
require.NoError(t, err)
assert.Equal(t, accessToken.AccessToken, storedToken.Token)
expectedVPJSON, _ := presentation.MarshalJSON()
actualVPJSON, _ := storedToken.Presentation.MarshalJSON()
assert.JSONEq(t, string(expectedVPJSON), string(actualVPJSON))
assert.Equal(t, issuerDID.String(), storedToken.Issuer)
assert.NotEmpty(t, storedToken.Expiration)
})
}
2 changes: 1 addition & 1 deletion storage/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func NewTestStorageEngineInDir(dir string) Engine {
return result
}

func NewTestStorageEngine(t *testing.T) Engine {
func NewTestStorageEngine(t testing.TB) Engine {
oldOpts := append(DefaultBBoltOptions[:])
t.Cleanup(func() {
DefaultBBoltOptions = oldOpts
Expand Down

0 comments on commit c5afa09

Please sign in to comment.