Skip to content

Commit

Permalink
additional tests
Browse files Browse the repository at this point in the history
  • Loading branch information
woutslakhorst committed Nov 24, 2023
1 parent 64ebe23 commit 20e189a
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 40 deletions.
2 changes: 1 addition & 1 deletion auth/api/iam/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"net/http/httptest"
"net/url"
"testing"
"time"

"github.com/labstack/echo/v4"
ssi "github.com/nuts-foundation/go-did"
Expand All @@ -44,7 +45,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"time"
)

var nutsDID = did.MustParseDID("did:nuts:123")
Expand Down
3 changes: 2 additions & 1 deletion auth/api/iam/openid4vp.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/go-did/vc"
"github.com/nuts-foundation/nuts-node/auth/oauth"
http2 "github.com/nuts-foundation/nuts-node/http"
"github.com/nuts-foundation/nuts-node/vcr/credential"
"github.com/nuts-foundation/nuts-node/vcr/holder"
"net/http"
Expand All @@ -55,7 +56,7 @@ func (r *Wrapper) sendPresentationRequest(ctx context.Context, response http.Res
params[responseTypeParam] = responseTypeVPIDToken
// TODO: Depending on parameter size, we either use redirect with query parameters or a form post.
// For simplicity, we now just query parameters.
result := AddQueryParams(*authzEndpoint, params)
result := http2.AddQueryParams(*authzEndpoint, params)
response.Header().Add("Location", result.String())
response.WriteHeader(http.StatusFound)
return nil
Expand Down
12 changes: 2 additions & 10 deletions auth/api/iam/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package iam

import (
"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/nuts-node/http"
"net/url"
)

Expand All @@ -40,17 +41,8 @@ type UserSession struct {
OwnDID did.DID
}

func AddQueryParams(u url.URL, params map[string]string) url.URL {
values := u.Query()
for key, value := range params {
values.Add(key, value)
}
u.RawQuery = values.Encode()
return u
}

func (s OAuthSession) CreateRedirectURI(params map[string]string) string {
redirectURI, _ := url.Parse(s.RedirectURI)
r := AddQueryParams(*redirectURI, params)
r := http.AddQueryParams(*redirectURI, params)
return r.String()
}
3 changes: 2 additions & 1 deletion auth/api/iam/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"context"
"github.com/labstack/echo/v4"
"github.com/nuts-foundation/nuts-node/auth/log"
http2 "github.com/nuts-foundation/nuts-node/http"
"net/http"
"time"

Expand All @@ -45,7 +46,7 @@ func (r *Wrapper) requestUserAccessToken(_ context.Context, requestHolder did.DI
return nil, err
}
// redirect to generic user page, context of token will render correct page
redirectURL := AddQueryParams(*webURL.JoinPath("user"), map[string]string{
redirectURL := http2.AddQueryParams(*webURL.JoinPath("user"), map[string]string{
"token": token,
})
return RequestAccessToken302Response{
Expand Down
21 changes: 13 additions & 8 deletions auth/services/oauth/relying_party.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"crypto/tls"
"errors"
"fmt"
"github.com/nuts-foundation/nuts-node/openid4vc"
"net/http"
"net/url"
"strings"
Expand All @@ -38,6 +37,8 @@ import (
"github.com/nuts-foundation/nuts-node/core"
nutsCrypto "github.com/nuts-foundation/nuts-node/crypto"
"github.com/nuts-foundation/nuts-node/didman"
http2 "github.com/nuts-foundation/nuts-node/http"
"github.com/nuts-foundation/nuts-node/openid4vc"
"github.com/nuts-foundation/nuts-node/vcr/credential"
"github.com/nuts-foundation/nuts-node/vcr/holder"
"github.com/nuts-foundation/nuts-node/vcr/signature/proof"
Expand Down Expand Up @@ -111,24 +112,28 @@ func (s *relyingParty) CreateJwtGrant(ctx context.Context, request services.Crea
}

func (s *relyingParty) AuthorizationRequest(ctx context.Context, requestHolder did.DID, verifier did.DID, scopes string, clientState string) (*url.URL, error) {
// todo: return type
// we want to make a call according to §4.1.1 of RFC6749, https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.1
// The URL should be listed in the verifier metadata under the "authorization_endpoint" key
iamClient := iam.NewHTTPClient(s.strictMode, s.httpClientTimeout, s.httpClientTLS)
metadata, err := iamClient.OAuthAuthorizationServerMetadata(ctx, verifier)
if err != nil {
return nil, fmt.Errorf("failed to retrieve remote OAuth Authorization Server metadata: %w", err)
}
request, err := url.Parse(metadata.AuthorizationEndpoint)
if len(metadata.AuthorizationEndpoint) == 0 {
return nil, fmt.Errorf("no authorization endpoint found in metadata for %s", verifier)
}
endpoint, err := url.Parse(metadata.AuthorizationEndpoint)
if err != nil {
return nil, fmt.Errorf("failed to parse authorization endpoint URL: %w", err)
}
request.Query().Add("client_id", requestHolder.String())
request.Query().Add("response_type", "code")
request.Query().Add("scope", scopes)
request.Query().Add("state", clientState)
// todo: redirect_uri
return request, nil
redirectURL := http2.AddQueryParams(*endpoint, map[string]string{
"client_id": requestHolder.String(),
"response_type": "code",
"scope": scopes,
"state": clientState,
})
return &redirectURL, nil
}

func (s *relyingParty) RequestRFC003AccessToken(ctx context.Context, jwtGrantToken string, authorizationServerEndpoint url.URL) (*oauth.TokenResponse, error) {
Expand Down
90 changes: 71 additions & 19 deletions auth/services/oauth/relying_party_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,56 @@ func TestRelyingParty_RequestRFC021AccessToken(t *testing.T) {
})
}

func TestRelyingParty_AuthorizationRequest(t *testing.T) {
walletDID := did.MustParseDID("did:test:123")
scopes := "first second"
clientState := crypto.GenerateNonce()

t.Run("ok", func(t *testing.T) {
ctx := createOAuthRPContext(t)

redirectURL, err := ctx.relyingParty.AuthorizationRequest(context.Background(), walletDID, ctx.verifierDID, scopes, clientState)

assert.NoError(t, err)
require.NotNil(t, redirectURL)
assert.Equal(t, walletDID.String(), redirectURL.Query().Get("client_id"))
assert.Equal(t, "code", redirectURL.Query().Get("response_type"))
assert.Equal(t, "first second", redirectURL.Query().Get("scope"))
assert.NotEmpty(t, redirectURL.Query().Get("state"))
})
t.Run("error - failed to get authorization server metadata", func(t *testing.T) {
ctx := createOAuthRPContext(t)
ctx.metadata = nil

_, err := ctx.relyingParty.AuthorizationRequest(context.Background(), walletDID, ctx.verifierDID, scopes, clientState)

assert.Error(t, err)
assert.EqualError(t, err, "failed to retrieve remote OAuth Authorization Server metadata: server returned HTTP 404 (expected: 200)")
})
t.Run("error - faulty authorization server metadata", func(t *testing.T) {
ctx := createOAuthRPContext(t)
ctx.metadata = func(writer http.ResponseWriter) {
writer.Header().Add("Content-Type", "application/json")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte("{"))
}

_, err := ctx.relyingParty.AuthorizationRequest(context.Background(), walletDID, ctx.verifierDID, scopes, clientState)

assert.Error(t, err)
assert.EqualError(t, err, "failed to retrieve remote OAuth Authorization Server metadata: unable to unmarshal response: unexpected end of JSON input, {")
})
t.Run("error - missing authorization endpoint", func(t *testing.T) {
ctx := createOAuthRPContext(t)
ctx.authzServerMetadata.AuthorizationEndpoint = ""

_, err := ctx.relyingParty.AuthorizationRequest(context.Background(), walletDID, ctx.verifierDID, scopes, clientState)

assert.Error(t, err)
assert.ErrorContains(t, err, "no authorization endpoint found in metadata for")
})
}

func TestService_CreateJwtBearerToken(t *testing.T) {
usi := vc.VerifiablePresentation{}

Expand Down Expand Up @@ -420,26 +470,27 @@ func createOAuthRPContext(t *testing.T) *rpOAuthTestContext {
authzServerMetadata := oauth.AuthorizationServerMetadata{VPFormats: formats}
ctx := &rpOAuthTestContext{
rpTestContext: createRPContext(t, nil),
metadata: func(writer http.ResponseWriter) {
writer.Header().Add("Content-Type", "application/json")
writer.WriteHeader(http.StatusOK)
bytes, _ := json.Marshal(authzServerMetadata)
_, _ = writer.Write(bytes)
return
},
presentationDefinition: func(writer http.ResponseWriter) {
writer.Header().Add("Content-Type", "application/json")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte(presentationDefinition))
return
},
token: func(writer http.ResponseWriter) {
writer.Header().Add("Content-Type", "application/json")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte(`{"access_token": "token", "token_type": "bearer"}`))
return
},
}
ctx.metadata = func(writer http.ResponseWriter) {
writer.Header().Add("Content-Type", "application/json")
writer.WriteHeader(http.StatusOK)
bytes, _ := json.Marshal(ctx.authzServerMetadata)
_, _ = writer.Write(bytes)
return
}
ctx.presentationDefinition = func(writer http.ResponseWriter) {
writer.Header().Add("Content-Type", "application/json")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte(presentationDefinition))
return
}
ctx.token = func(writer http.ResponseWriter) {
writer.Header().Add("Content-Type", "application/json")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte(`{"access_token": "token", "token_type": "bearer"}`))
return
}

ctx.handler = func(writer http.ResponseWriter, request *http.Request) {
switch request.URL.Path {
case "/.well-known/oauth-authorization-server":
Expand All @@ -464,6 +515,7 @@ func createOAuthRPContext(t *testing.T) *rpOAuthTestContext {
ctx.verifierDID = didweb.ServerURLToDIDWeb(t, ctx.tlsServer.URL)
authzServerMetadata.TokenEndpoint = ctx.tlsServer.URL + "/token"
authzServerMetadata.PresentationDefinitionEndpoint = ctx.tlsServer.URL + "/presentation_definition"
authzServerMetadata.AuthorizationEndpoint = ctx.tlsServer.URL + "/authorize"
ctx.authzServerMetadata = authzServerMetadata

return ctx
Expand Down
31 changes: 31 additions & 0 deletions http/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2023 Nuts community
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/

package http

import "net/url"

// AddQueryParams adds the given params to the given url as query params
func AddQueryParams(u url.URL, params map[string]string) url.URL {
values := u.Query()
for key, value := range params {
values.Add(key, value)
}
u.RawQuery = values.Encode()
return u
}
44 changes: 44 additions & 0 deletions http/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2023 Nuts community
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/

package http

import (
"net/url"
"testing"

"github.com/stretchr/testify/assert"
)

func TestAddQueryParams(t *testing.T) {
t.Run("new key", func(t *testing.T) {
u, _ := url.Parse("https://test.test?test=1")

result := AddQueryParams(*u, map[string]string{"test2": "2"})

assert.Equal(t, "https://test.test?test=1&test2=2", result.String())
})

t.Run("multiple params with same key", func(t *testing.T) {
u, _ := url.Parse("https://test.test?test=1")

result := AddQueryParams(*u, map[string]string{"test1": "2"})

assert.Equal(t, "https://test.test?test=1&test1=2", result.String())
})
}

0 comments on commit 20e189a

Please sign in to comment.