From 6008f49e92aede522cdc638018bd8282d31efdb7 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Mon, 20 Nov 2023 15:31:29 +0100 Subject: [PATCH] added authorized client API and tests --- policy/api/v1/client/client.go | 48 +++++++++++++--- policy/api/v1/client/client_test.go | 85 +++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 8 deletions(-) diff --git a/policy/api/v1/client/client.go b/policy/api/v1/client/client.go index 1e8b21b1d8..20d735a6d6 100644 --- a/policy/api/v1/client/client.go +++ b/policy/api/v1/client/client.go @@ -26,7 +26,6 @@ import ( "github.com/nuts-foundation/go-did/did" "io" "net/http" - "net/url" "time" "github.com/nuts-foundation/nuts-node/core" @@ -48,20 +47,21 @@ func NewHTTPClient(strictMode bool, timeout time.Duration, tlsConfig *tls.Config } // PresentationDefinition retrieves the presentation definition from the presentation definition endpoint for the given scope and . -func (hb HTTPClient) PresentationDefinition(ctx context.Context, policyEndpoint string, authorizer did.DID, scopes string) (*pe.PresentationDefinition, error) { - presentationDefinitionURL, err := core.ParsePublicURL(policyEndpoint, hb.strictMode) +func (hb HTTPClient) PresentationDefinition(ctx context.Context, serverAddress string, authorizer did.DID, scopes string) (*pe.PresentationDefinition, error) { + _, err := core.ParsePublicURL(serverAddress, hb.strictMode) if err != nil { return nil, err } - presentationDefinitionURL.Path = fmt.Sprintf("%s/presentation_definition", presentationDefinitionURL.Path) - presentationDefinitionURL.RawQuery = url.Values{"scope": []string{scopes}, "authorizer": []string{authorizer.String()}}.Encode() - // create a GET request with query params - request, err := http.NewRequestWithContext(ctx, http.MethodGet, presentationDefinitionURL.String(), nil) + client, err := NewClient(serverAddress, WithHTTPClient(hb.httpClient)) if err != nil { return nil, err } - response, err := hb.httpClient.Do(request.WithContext(ctx)) + params := &PresentationDefinitionParams{ + Scope: scopes, + Authorizer: authorizer.String(), + } + response, err := client.PresentationDefinition(ctx, params) if err != nil { return nil, fmt.Errorf("failed to call endpoint: %w", err) } @@ -81,3 +81,35 @@ func (hb HTTPClient) PresentationDefinition(ctx context.Context, policyEndpoint return &presentationDefinition, nil } + +func (hb HTTPClient) Authorized(ctx context.Context, serverAddress string, request AuthorizedRequest) (bool, error) { + _, err := core.ParsePublicURL(serverAddress, hb.strictMode) + if err != nil { + return false, err + } + + client, err := NewClient(serverAddress, WithHTTPClient(hb.httpClient)) + if err != nil { + return false, err + } + + response, err := client.CheckAuthorized(ctx, request) + if err != nil { + return false, fmt.Errorf("failed to call endpoint: %w", err) + } + if httpErr := core.TestResponseCode(http.StatusOK, response); httpErr != nil { + return false, httpErr + } + + var authorized bool + var data []byte + + if data, err = io.ReadAll(response.Body); err != nil { + return false, fmt.Errorf("unable to read response: %w", err) + } + if err = json.Unmarshal(data, &authorized); err != nil { + return false, fmt.Errorf("unable to unmarshal response: %w", err) + } + + return authorized, nil +} diff --git a/policy/api/v1/client/client_test.go b/policy/api/v1/client/client_test.go index 94749da224..57ba5fa460 100644 --- a/policy/api/v1/client/client_test.go +++ b/policy/api/v1/client/client_test.go @@ -26,6 +26,7 @@ import ( "github.com/nuts-foundation/nuts-node/vcr/pe" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "io" "net/http" "net/http/httptest" "testing" @@ -98,6 +99,90 @@ func TestHTTPClient_PresentationDefinition(t *testing.T) { }) } +func TestHTTPClient_Authorized(t *testing.T) { + ctx := context.Background() + audience := did.MustParseDID("did:web:example.com:audience") + request := AuthorizedRequest{ + Audience: audience.String(), + ClientId: "did:web:example.com:client", + PresentationSubmission: PresentationSubmission{}, + RequestMethod: "GET", + RequestUrl: "/resource", + Scope: "test 1 2 3", + Vps: nil, + } + + t.Run("ok", func(t *testing.T) { + var capturedRequest *http.Request + var capturedRequestBody []byte + handler := func(writer http.ResponseWriter, request *http.Request) { + switch request.URL.Path { + case "/authorized": + capturedRequest = request + capturedRequestBody, _ = io.ReadAll(request.Body) + writer.WriteHeader(http.StatusOK) + writer.Write([]byte("true")) + } + writer.WriteHeader(http.StatusNotFound) + } + tlsServer, client := testServerAndClient(t, http.HandlerFunc(handler)) + + response, err := client.Authorized(ctx, tlsServer.URL, request) + + require.NoError(t, err) + assert.True(t, response) + require.NotNil(t, capturedRequest) + assert.Equal(t, "POST", capturedRequest.Method) + assert.Equal(t, "/authorized", capturedRequest.URL.Path) + // check body + require.NotNil(t, capturedRequest.Body) + var capturedRequestData AuthorizedRequest + err = json.Unmarshal(capturedRequestBody, &capturedRequestData) + require.NoError(t, err) + assert.Equal(t, request, capturedRequestData) + }) + t.Run("error - not found", func(t *testing.T) { + handler := http2.Handler{StatusCode: http.StatusNotFound} + tlsServer, client := testServerAndClient(t, &handler) + + response, err := client.Authorized(ctx, tlsServer.URL, request) + + require.Error(t, err) + assert.EqualError(t, err, "server returned HTTP 404 (expected: 200)") + assert.False(t, response) + }) + t.Run("error - invalid URL", func(t *testing.T) { + handler := http2.Handler{StatusCode: http.StatusNotFound} + _, client := testServerAndClient(t, &handler) + + response, err := client.Authorized(ctx, ":", request) + + require.Error(t, err) + assert.EqualError(t, err, "parse \":\": missing protocol scheme") + assert.False(t, response) + }) + t.Run("error - invalid response", func(t *testing.T) { + handler := http2.Handler{StatusCode: http.StatusOK, ResponseData: "}"} + tlsServer, client := testServerAndClient(t, &handler) + + response, err := client.Authorized(ctx, tlsServer.URL, request) + + require.Error(t, err) + assert.EqualError(t, err, "unable to unmarshal response: invalid character '}' looking for beginning of value") + assert.False(t, response) + }) + t.Run("error - invalid endpoint", func(t *testing.T) { + handler := http2.Handler{StatusCode: http.StatusOK} + _, client := testServerAndClient(t, &handler) + + response, err := client.Authorized(ctx, "http://::1:1", request) + + require.Error(t, err) + assert.EqualError(t, err, "failed to call endpoint: Post \"http://::1:1/authorized\": dial tcp [::1]:1: connect: connection refused") + assert.False(t, response) + }) +} + func testServerAndClient(t *testing.T, handler http.Handler) (*httptest.Server, *HTTPClient) { tlsServer := http2.TestTLSServer(t, handler) return tlsServer, &HTTPClient{