From 5448ebc194cb08bdde3819731000c96d9413d553 Mon Sep 17 00:00:00 2001 From: Davide Bianchi <10374360+davidebianchi@users.noreply.github.com> Date: Wed, 5 Jul 2023 11:33:59 +0200 Subject: [PATCH] fix: patch bulk during revoke bindings API invocation and refactor to use crud client lib (#217) * feat: use crud client lib * feat: remove mongoclient * update: remove unused function and move helpers to internal --- go.mod | 3 +- go.sum | 3 + helpers/headers_to_proxy.go | 55 --- helpers/headers_to_proxy_test.go | 137 ------- internal/crudclient/crudclient.go | 122 ------ internal/crudclient/crudclient_test.go | 368 ------------------ internal/crudclient/testing.go | 121 ------ .../helpers}/gracefulshutdown.go | 0 .../helpers}/gracefulshutdown_test.go | 0 internal/helpers/headers_to_proxy.go | 30 ++ internal/helpers/headers_to_proxy_test.go | 52 +++ main.go | 2 +- service/router.go | 3 - service/standalone_apis.go | 77 ++-- service/standalone_apis_test.go | 140 +++---- types/rbactypes.go | 4 - 16 files changed, 203 insertions(+), 914 deletions(-) delete mode 100644 helpers/headers_to_proxy.go delete mode 100644 helpers/headers_to_proxy_test.go delete mode 100644 internal/crudclient/crudclient.go delete mode 100644 internal/crudclient/crudclient_test.go delete mode 100644 internal/crudclient/testing.go rename {helpers => internal/helpers}/gracefulshutdown.go (100%) rename {helpers => internal/helpers}/gracefulshutdown_test.go (100%) create mode 100644 internal/helpers/headers_to_proxy.go create mode 100644 internal/helpers/headers_to_proxy_test.go diff --git a/go.mod b/go.mod index e4161f63..9b5e069b 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,13 @@ module github.com/rond-authz/rond go 1.18 require ( - github.com/davidebianchi/go-jsonclient v1.5.0 github.com/davidebianchi/gswagger v0.9.0 github.com/getkin/kin-openapi v0.118.0 github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 github.com/mia-platform/configlib v1.0.0 github.com/mia-platform/glogger/v2 v2.1.3 + github.com/mia-platform/go-crud-service-client v0.10.0 github.com/open-policy-agent/opa v0.54.0 github.com/prometheus/client_golang v1.16.0 github.com/prometheus/common v0.44.0 @@ -27,6 +27,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davidebianchi/go-jsonclient v1.5.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-ini/ini v1.67.0 // indirect diff --git a/go.sum b/go.sum index 16b28369..da69e2a2 100644 --- a/go.sum +++ b/go.sum @@ -245,6 +245,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= +github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ= @@ -347,6 +348,8 @@ github.com/mia-platform/configlib v1.0.0 h1:8sh40jZlCxrtGBq87nbjKa4zisgccuXxBrjK github.com/mia-platform/configlib v1.0.0/go.mod h1:oyELirRsp1AzPaSF7GzcMe/R3Rf7uRvw3FclA9Q4fFQ= github.com/mia-platform/glogger/v2 v2.1.3 h1:Qt/qHETYaFa+Oso9XZauysnHF9CE5816AQJmMo2Uh0Q= github.com/mia-platform/glogger/v2 v2.1.3/go.mod h1:dUYhwmsXVcZe5Eg8V4FIcdv5P4pfk9a/8JExTmi8Uig= +github.com/mia-platform/go-crud-service-client v0.10.0 h1:BUFKQVxfdcnI82cXNIU8Zp2mCI9d7JJS4eUrTUHfxHc= +github.com/mia-platform/go-crud-service-client v0.10.0/go.mod h1:Sj3BEGMu0RTdeye/fPBVwRoojOjKuhWl5j+psY3s40s= github.com/mia-platform/jsonschema v0.1.0 h1:tjQf7TaYROsAqk7SXTL+44TrfKk3bSEvhRGPS51IA5Y= github.com/mia-platform/jsonschema v0.1.0/go.mod h1:r2DJjPA/+6S+WPnXZt1xONMvO2b4hlhfXfUYV0po/Dk= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= diff --git a/helpers/headers_to_proxy.go b/helpers/headers_to_proxy.go deleted file mode 100644 index 46d87048..00000000 --- a/helpers/headers_to_proxy.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2021 Mia srl -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package helpers - -import ( - "context" - "net/http" - - "github.com/gorilla/mux" - "github.com/sirupsen/logrus" -) - -type requestHeadersToProxy struct{} - -func SetHeadersToProxy(ctx context.Context, headers http.Header) { - reqHeadersToProxy, ok := ctx.Value(requestHeadersToProxy{}).(http.Header) - if ok && len(reqHeadersToProxy) != 0 { - for name := range reqHeadersToProxy { - headers.Set(name, reqHeadersToProxy.Get(name)) - } - } -} - -func AddHeadersToProxyMiddleware(logger *logrus.Logger, headerNamesToAdd []string) mux.MiddlewareFunc { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - headersToProxy := http.Header{} - for _, headerNameToAdd := range headerNamesToAdd { - headerValue := r.Header.Get(headerNameToAdd) - if len(headerValue) > 0 { - headersToProxy.Set(headerNameToAdd, headerValue) - } - } - ctx := AddHeadersToProxyToContext(r.Context(), headersToProxy) - - next.ServeHTTP(w, r.WithContext(ctx)) - }) - } -} - -func AddHeadersToProxyToContext(ctx context.Context, value http.Header) context.Context { - return context.WithValue(ctx, requestHeadersToProxy{}, value) -} diff --git a/helpers/headers_to_proxy_test.go b/helpers/headers_to_proxy_test.go deleted file mode 100644 index 65a5a99d..00000000 --- a/helpers/headers_to_proxy_test.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2021 Mia srl -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package helpers - -import ( - "context" - "net/http" - "net/http/httptest" - "testing" - - "github.com/sirupsen/logrus/hooks/test" - "github.com/stretchr/testify/require" -) - -func TestAddHeadersToProxyToContext(t *testing.T) { - t.Run("correctly set nil headers in context", func(t *testing.T) { - ctx := context.Background() - ctx = AddHeadersToProxyToContext(ctx, nil) - - requestHeadersToProxy, ok := ctx.Value(requestHeadersToProxy{}).(http.Header) - require.True(t, ok) - require.Len(t, requestHeadersToProxy, 0) - }) - - t.Run("correctly set headers in context", func(t *testing.T) { - ctx := context.Background() - h := http.Header{} - h.Set("foo", "bar") - ctx = AddHeadersToProxyToContext(ctx, h) - - requestHeadersToProxy, ok := ctx.Value(requestHeadersToProxy{}).(http.Header) - require.True(t, ok) - require.Len(t, requestHeadersToProxy, 1) - require.Equal(t, requestHeadersToProxy.Get("foo"), "bar") - }) -} - -func TestAddHeadersToProxyMiddleware(t *testing.T) { - var called = false - t.Run("set empty headers if headers to proxy is nil", func(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - called = true - }) - testMockMiddlewareInvocation(handler, nil) - - require.True(t, called) - }) - - t.Run("set empty headers if headers to proxy is empty", func(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - called = true - }) - testMockMiddlewareInvocation(handler, []string{}) - - require.True(t, called) - }) - - t.Run("set headers proxy in context", func(t *testing.T) { - requestHeaders := http.Header{} - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - called = true - requestHeaders = r.Context().Value(requestHeadersToProxy{}).(http.Header) - }) - h := testMockMiddlewareInvocation(handler, []string{"foo", "x-request-id", "x-forwarded-for", "x-forwarded-host"}) - - require.True(t, called) - require.Equal(t, h, requestHeaders) - }) -} - -func TestSetHeadersToProxy(t *testing.T) { - t.Run("not panic if context not contains headersToProxy key", func(t *testing.T) { - ctx := context.Background() - headers := http.Header{} - SetHeadersToProxy(ctx, headers) - - require.Len(t, headers, 0) - }) - - t.Run("not set header if empty headers to proxy", func(t *testing.T) { - ctx := context.Background() - requestHeaders := http.Header{} - ctx = AddHeadersToProxyToContext(ctx, requestHeaders) - - headers := http.Header{} - SetHeadersToProxy(ctx, headers) - - require.Len(t, headers, 0) - }) - - t.Run("set headers correctly", func(t *testing.T) { - ctx := context.Background() - requestHeaders := http.Header{} - requestHeaders.Set("foo", "bar") - requestHeaders.Set("taz", "ok") - ctx = AddHeadersToProxyToContext(ctx, requestHeaders) - - headers := http.Header{} - SetHeadersToProxy(ctx, headers) - - require.Len(t, headers, 2) - require.Equal(t, headers.Get("foo"), "bar") - require.Equal(t, headers.Get("taz"), "ok") - }) -} - -func testMockMiddlewareInvocation(next http.HandlerFunc, headersToAdd []string) http.Header { - // create a request - req := httptest.NewRequest(http.MethodGet, "/", nil) - req.Header.Add("x-request-id", "123") - req.Header.Add("x-forwarded-for", "my-ip") - req.Header.Add("x-forwarded-host", "my-host") - - logger, _ := test.NewNullLogger() - - handler := AddHeadersToProxyMiddleware(logger, headersToAdd) - // invoke the handler - server := handler(next) - // Create a response writer - writer := httptest.NewRecorder() - // Serve HTTP server - server.ServeHTTP(writer, req) - - return req.Header -} diff --git a/internal/crudclient/crudclient.go b/internal/crudclient/crudclient.go deleted file mode 100644 index 01fc2582..00000000 --- a/internal/crudclient/crudclient.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2021 Mia srl -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package crudclient - -import ( - "context" - "fmt" - "net/http" - "strings" - - "github.com/davidebianchi/go-jsonclient" - "github.com/rond-authz/rond/helpers" -) - -// CRUD struct. -type CRUD struct { - httpClient *jsonclient.Client -} - -// New creates new CRUDClient. -func New(apiURL string) (*CRUD, error) { - apiURLWithoutFinalSlash := strings.TrimSuffix(apiURL, "/") - - opts := jsonclient.Options{ - BaseURL: fmt.Sprintf("%s/", apiURLWithoutFinalSlash), - } - httpClient, err := jsonclient.New(opts) - if err != nil { - return nil, err - } - - crudClient := &CRUD{ - httpClient, - } - return crudClient, nil -} - -// Get fetch item by id on CRUD. -func (crud CRUD) Get(ctx context.Context, queryParam string, responseBody interface{}) error { - req, err := crud.httpClient.NewRequestWithContext(ctx, http.MethodGet, "?"+queryParam, nil) - if err != nil { - return err - } - - helpers.SetHeadersToProxy(ctx, req.Header) - - if _, err := crud.httpClient.Do(req, responseBody); err != nil { - return err - } - return nil -} - -func (crud CRUD) Post(ctx context.Context, body interface{}, responseBody interface{}) error { - req, err := crud.httpClient.NewRequestWithContext(ctx, http.MethodPost, "", body) - if err != nil { - return err - } - - helpers.SetHeadersToProxy(ctx, req.Header) - - _, err = crud.httpClient.Do(req, responseBody) - if err != nil { - return err - } - - return nil -} - -func (crud CRUD) Delete(ctx context.Context, queryParam string, responseBody interface{}) error { - req, err := crud.httpClient.NewRequestWithContext(ctx, http.MethodDelete, "?"+queryParam, nil) - if err != nil { - return err - } - - helpers.SetHeadersToProxy(ctx, req.Header) - - if _, err := crud.httpClient.Do(req, responseBody); err != nil { - return err - } - return nil -} - -func (crud CRUD) PatchBulk(ctx context.Context, requestBody interface{}, responseBody interface{}) error { - req, err := crud.httpClient.NewRequestWithContext(ctx, http.MethodPatch, "", requestBody) - if err != nil { - return err - } - - helpers.SetHeadersToProxy(ctx, req.Header) - - if _, err := crud.httpClient.Do(req, responseBody); err != nil { - return err - } - return nil -} - -// IsHealthy checks if crud is healthy. -func (crud CRUD) IsHealthy(ctx context.Context) error { - req, err := crud.httpClient.NewRequest(http.MethodGet, "/-/healthz", nil) - if err != nil { - return err - } - - helpers.SetHeadersToProxy(ctx, req.Header) - - if _, err := crud.httpClient.Do(req, nil); err != nil { - return err - } - return nil -} diff --git a/internal/crudclient/crudclient_test.go b/internal/crudclient/crudclient_test.go deleted file mode 100644 index 05aa81c3..00000000 --- a/internal/crudclient/crudclient_test.go +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright 2021 Mia srl -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package crudclient - -import ( - "context" - "net/http" - "testing" - - "github.com/rond-authz/rond/helpers" - "github.com/stretchr/testify/require" -) - -func TestCRUDClient(t *testing.T) { - usersCrudBaseURL := "http://crud.example.org/my-coll-name" - - t.Run("New", func(t *testing.T) { - t.Run("should return a Client instance", func(t *testing.T) { - crudClient, err := New(usersCrudBaseURL) - require.NoError(t, err) - require.NotNil(t, crudClient) - }) - - t.Run("https is allowed", func(t *testing.T) { - crudClient, err := New("https://crud.example.org") - require.NoError(t, err) - require.NotNil(t, crudClient) - }) - - t.Run("should return error invalid url", func(t *testing.T) { - crudClient, err := New("in\t") - require.Error(t, err) - require.Nil(t, crudClient) - }) - - t.Run("should return error on unknown protocol", func(t *testing.T) { - crudClient, err := New("in://validURL") - require.Error(t, err) - require.Nil(t, crudClient) - }) - - t.Run("should return error if URL is not absolute - 1", func(t *testing.T) { - crudClient, err := New("validURL") - require.Error(t, err) - require.Nil(t, crudClient) - }) - - t.Run("should return error if URL is not absolute - 2", func(t *testing.T) { - crudClient, err := New("/validURL") - require.Error(t, err) - require.Nil(t, crudClient) - }) - }) -} - -func TestGet(t *testing.T) { - usersCrudBaseURL := "http://crud.example.org/my-coll-name/" - - crudClient, err := New(usersCrudBaseURL) - require.NoError(t, err) - require.NotNil(t, crudClient) - var ctx = context.Background() - - type ResponseBody struct { - Bar string `json:"bar"` - } - - t.Run("returns error creating request", func(t *testing.T) { - err := crudClient.Get(ctx, " ", nil) - require.Error(t, err) - }) - - t.Run("returns error if context timeout", func(t *testing.T) { - ctx, cancel := context.WithTimeout(ctx, 0) - defer cancel() - - err := crudClient.Get(ctx, "", nil) - require.EqualError(t, err, context.DeadlineExceeded.Error()) - }) - - t.Run("returns error if crud returns 404", func(t *testing.T) { - MockGet(t, usersCrudBaseURL, 404, nil, nil) - - err := crudClient.Get(ctx, "", nil) - require.EqualError(t, err, "GET http://crud.example.org/my-coll-name/?: 404 - null\n") - }) - - t.Run("correctly returns if crud returns 200", func(t *testing.T) { - expectedResponseBody := ResponseBody{ - Bar: "some", - } - - MockGet(t, usersCrudBaseURL, 200, expectedResponseBody, nil) - - var responseBody ResponseBody - err := crudClient.Get(ctx, "", &responseBody) - - require.NoError(t, err) - require.Equal(t, expectedResponseBody, responseBody) - }) - - t.Run("correctly returns if crud returns 200 with additional headers to proxy", func(t *testing.T) { - expectedResponseBody := ResponseBody{ - Bar: "some", - } - - headersToProxy := http.Header{} - headersToProxy.Set("request-id", "123") - headersToProxy.Set("taz", "ok") - - ctx = helpers.AddHeadersToProxyToContext(ctx, headersToProxy) - - MockGet(t, usersCrudBaseURL, 200, expectedResponseBody, headersToProxy) - - var responseBody ResponseBody - err := crudClient.Get(ctx, "", &responseBody) - - require.NoError(t, err) - require.Equal(t, expectedResponseBody, responseBody) - }) -} - -func TestPost(t *testing.T) { - usersCrudBaseURL := "http://crud.example.org/my-coll-name/" - - crudClient, err := New(usersCrudBaseURL) - require.NoError(t, err) - require.NotNil(t, crudClient) - var ctx = context.Background() - - type ResponseBody struct { - Bar string `json:"bar"` - } - - t.Run("returns error creating request", func(t *testing.T) { - err := crudClient.Post(ctx, nil, nil) - require.Error(t, err) - }) - - t.Run("returns error if context timeout", func(t *testing.T) { - ctx, cancel := context.WithTimeout(ctx, 0) - defer cancel() - - err := crudClient.Post(ctx, "", nil) - require.EqualError(t, err, context.DeadlineExceeded.Error()) - }) - - t.Run("returns error if crud returns 404", func(t *testing.T) { - MockPost(t, usersCrudBaseURL, 404, nil, nil) - - err := crudClient.Post(ctx, "", nil) - require.EqualError(t, err, "POST http://crud.example.org/my-coll-name/: 404 - null\n") - }) - - t.Run("correctly returns if crud returns 200", func(t *testing.T) { - expectedResponseBody := ResponseBody{ - Bar: "some", - } - - MockPost(t, usersCrudBaseURL, 200, expectedResponseBody, nil) - - var responseBody ResponseBody - err := crudClient.Get(ctx, "", &responseBody) - - require.NoError(t, err) - require.Equal(t, expectedResponseBody, responseBody) - }) - - t.Run("correctly returns if crud returns 200 with additional headers to proxy", func(t *testing.T) { - expectedResponseBody := ResponseBody{ - Bar: "some", - } - - headersToProxy := http.Header{} - headersToProxy.Set("request-id", "123") - headersToProxy.Set("taz", "ok") - - ctx = helpers.AddHeadersToProxyToContext(ctx, headersToProxy) - - MockPost(t, usersCrudBaseURL, 200, expectedResponseBody, headersToProxy) - - var responseBody ResponseBody - err := crudClient.Get(ctx, "", &responseBody) - - require.NoError(t, err) - require.Equal(t, expectedResponseBody, responseBody) - }) -} - -func TestDelete(t *testing.T) { - usersCrudBaseURL := "http://crud.example.org/my-coll-name/" - - crudClient, err := New(usersCrudBaseURL) - require.NoError(t, err) - require.NotNil(t, crudClient) - var ctx = context.Background() - - type ResponseBody struct { - Bar string `json:"bar"` - } - - t.Run("returns error creating request", func(t *testing.T) { - err := crudClient.Delete(ctx, "", nil) - require.Error(t, err) - }) - - t.Run("returns error if context timeout", func(t *testing.T) { - ctx, cancel := context.WithTimeout(ctx, 0) - defer cancel() - - err := crudClient.Delete(ctx, "", nil) - require.EqualError(t, err, context.DeadlineExceeded.Error()) - }) - - t.Run("returns error if crud returns 404", func(t *testing.T) { - MockDelete(t, usersCrudBaseURL, 404, nil, nil) - - err := crudClient.Delete(ctx, "", nil) - require.EqualError(t, err, "DELETE http://crud.example.org/my-coll-name/?: 404 - null\n") - }) - - t.Run("correctly returns if crud returns 200", func(t *testing.T) { - expectedResponseBody := ResponseBody{ - Bar: "some", - } - - MockDelete(t, usersCrudBaseURL, 200, expectedResponseBody, nil) - - var responseBody ResponseBody - err := crudClient.Delete(ctx, "", &responseBody) - - require.NoError(t, err) - require.Equal(t, expectedResponseBody, responseBody) - }) - - t.Run("correctly returns if crud returns 200 with additional headers to proxy", func(t *testing.T) { - expectedResponseBody := ResponseBody{ - Bar: "some", - } - - headersToProxy := http.Header{} - headersToProxy.Set("request-id", "123") - headersToProxy.Set("taz", "ok") - - ctx = helpers.AddHeadersToProxyToContext(ctx, headersToProxy) - - MockDelete(t, usersCrudBaseURL, 200, expectedResponseBody, headersToProxy) - - var responseBody ResponseBody - err := crudClient.Delete(ctx, "", &responseBody) - - require.NoError(t, err) - require.Equal(t, expectedResponseBody, responseBody) - }) -} - -func TestPatchBulk(t *testing.T) { - usersCrudBaseURL := "http://crud.example.org/my-coll-name/" - - crudClient, err := New(usersCrudBaseURL) - require.NoError(t, err) - require.NotNil(t, crudClient) - var ctx = context.Background() - - type ResponseBody struct { - Bar string `json:"bar"` - } - - t.Run("returns error creating request", func(t *testing.T) { - err := crudClient.PatchBulk(ctx, nil, nil) - require.Error(t, err) - }) - - t.Run("returns error if context timeout", func(t *testing.T) { - ctx, cancel := context.WithTimeout(ctx, 0) - defer cancel() - - err := crudClient.PatchBulk(ctx, "", nil) - require.EqualError(t, err, context.DeadlineExceeded.Error()) - }) - - t.Run("returns error if crud returns 404", func(t *testing.T) { - MockPatchBulk(t, usersCrudBaseURL, 404, nil, nil) - - err := crudClient.PatchBulk(ctx, "", nil) - require.EqualError(t, err, "PATCH http://crud.example.org/my-coll-name/: 404 - null\n") - }) - - t.Run("correctly returns if crud returns 200", func(t *testing.T) { - expectedResponseBody := ResponseBody{ - Bar: "some", - } - - MockPatchBulk(t, usersCrudBaseURL, 200, expectedResponseBody, nil) - - var responseBody ResponseBody - err := crudClient.PatchBulk(ctx, "", &responseBody) - - require.NoError(t, err) - require.Equal(t, expectedResponseBody, responseBody) - }) - - t.Run("correctly returns if crud returns 200 with additional headers to proxy", func(t *testing.T) { - expectedResponseBody := ResponseBody{ - Bar: "some", - } - - headersToProxy := http.Header{} - headersToProxy.Set("request-id", "123") - headersToProxy.Set("taz", "ok") - - ctx = helpers.AddHeadersToProxyToContext(ctx, headersToProxy) - - MockPatchBulk(t, usersCrudBaseURL, 200, expectedResponseBody, headersToProxy) - - var responseBody ResponseBody - err := crudClient.PatchBulk(ctx, "", &responseBody) - - require.NoError(t, err) - require.Equal(t, expectedResponseBody, responseBody) - }) -} - -func TestIsHealthy(t *testing.T) { - usersCrudBaseURL := "http://crud.example.org/my-coll-name/" - - crudClient, err := New(usersCrudBaseURL) - require.NoError(t, err) - require.NotNil(t, crudClient) - - t.Run("ok", func(t *testing.T) { - MockIsHealthy(t, usersCrudBaseURL, 200, nil) - - err := crudClient.IsHealthy(context.Background()) - require.NoError(t, err) - }) - - t.Run("ok - proxy headers", func(t *testing.T) { - h := http.Header{} - h.Set("foo", "bar") - MockIsHealthy(t, usersCrudBaseURL, 200, h) - - ctx := context.Background() - ctx = helpers.AddHeadersToProxyToContext(ctx, h) - - err := crudClient.IsHealthy(ctx) - require.NoError(t, err) - }) - - t.Run("ko", func(t *testing.T) { - MockIsHealthy(t, usersCrudBaseURL, 503, nil) - - err := crudClient.IsHealthy(context.Background()) - require.Error(t, err) - }) -} diff --git a/internal/crudclient/testing.go b/internal/crudclient/testing.go deleted file mode 100644 index b35ddc92..00000000 --- a/internal/crudclient/testing.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2021 Mia srl -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package crudclient - -import ( - "net/http" - "testing" - - "gopkg.in/h2non/gock.v1" -) - -// MockUpsertWithQueryParameters mocks upsert to a collection. -func getHeadersMap(headers http.Header) map[string]string { - requestHeadersMap := map[string]string{} - for name, values := range headers { - requestHeadersMap[name] = values[0] - } - return requestHeadersMap -} - -// MockGetByID mocks get in a collection. -func MockGet(t *testing.T, baseURL string, statusCode int, responseBody interface{}, headersToProxy http.Header) { - t.Helper() - t.Cleanup(func() { - gockCleanup(t) - }) - gock.DisableNetworking() - - gock.New(baseURL). - MatchHeaders(getHeadersMap(headersToProxy)). - Reply(statusCode). - JSON(responseBody) -} - -// MockPost mocks post in a collection. -func MockPost(t *testing.T, baseURL string, statusCode int, responseBody interface{}, headersToProxy http.Header) { - t.Helper() - t.Cleanup(func() { - gockCleanup(t) - }) - gock.DisableNetworking() - - gock.New(baseURL). - MatchHeaders(getHeadersMap(headersToProxy)). - Reply(statusCode). - JSON(responseBody) -} - -// MockDelete mocks post in a collection. -func MockDelete(t *testing.T, baseURL string, statusCode int, responseBody interface{}, headersToProxy http.Header) { - t.Helper() - t.Cleanup(func() { - gockCleanup(t) - }) - gock.DisableNetworking() - - gock.New(baseURL). - MatchHeaders(getHeadersMap(headersToProxy)). - Reply(statusCode). - JSON(responseBody) -} - -// MockPatchBulk mocks post in a collection. -func MockPatchBulk(t *testing.T, baseURL string, statusCode int, responseBody interface{}, headersToProxy http.Header) { - t.Helper() - t.Cleanup(func() { - gockCleanup(t) - }) - gock.DisableNetworking() - - gock.New(baseURL). - MatchHeaders(getHeadersMap(headersToProxy)). - Reply(statusCode). - JSON(responseBody) -} - -// MockIsHealthy mock the healthy function -func MockIsHealthy(t *testing.T, baseURL string, statusCode int, headersToProxy http.Header) { - t.Helper() - t.Cleanup(func() { - gockCleanup(t) - }) - gock.DisableNetworking() - - responseBody := map[string]interface{}{ - "status": "OK", - } - if statusCode >= 300 { - responseBody = map[string]interface{}{ - "status": "KO", - } - } - - gock.New(baseURL). - MatchHeaders(getHeadersMap(headersToProxy)). - Get("/-/healthz"). - Reply(statusCode). - JSON(responseBody) -} - -func gockCleanup(t *testing.T) { - t.Helper() - - if !gock.IsDone() { - gock.OffAll() - t.Fatal("fails to mock crud") - } - gock.Off() -} diff --git a/helpers/gracefulshutdown.go b/internal/helpers/gracefulshutdown.go similarity index 100% rename from helpers/gracefulshutdown.go rename to internal/helpers/gracefulshutdown.go diff --git a/helpers/gracefulshutdown_test.go b/internal/helpers/gracefulshutdown_test.go similarity index 100% rename from helpers/gracefulshutdown_test.go rename to internal/helpers/gracefulshutdown_test.go diff --git a/internal/helpers/headers_to_proxy.go b/internal/helpers/headers_to_proxy.go new file mode 100644 index 00000000..03ed3169 --- /dev/null +++ b/internal/helpers/headers_to_proxy.go @@ -0,0 +1,30 @@ +// Copyright 2021 Mia srl +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helpers + +import ( + "net/http" +) + +func GetHeadersToProxy(r *http.Request, headerNamesToAdd []string) http.Header { + headersToProxy := http.Header{} + for _, headerNameToAdd := range headerNamesToAdd { + headerValue := r.Header.Get(headerNameToAdd) + if len(headerValue) > 0 { + headersToProxy.Set(headerNameToAdd, headerValue) + } + } + return headersToProxy +} diff --git a/internal/helpers/headers_to_proxy_test.go b/internal/helpers/headers_to_proxy_test.go new file mode 100644 index 00000000..971f845b --- /dev/null +++ b/internal/helpers/headers_to_proxy_test.go @@ -0,0 +1,52 @@ +// Copyright 2021 Mia srl +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helpers + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetHeadersToProxy(t *testing.T) { + t.Run("not set header if empty headers to proxy", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/", nil) + actual := GetHeadersToProxy(req, nil) + + expected := http.Header{} + + require.Equal(t, actual, expected) + }) + + t.Run("get headers to proxy correctly", func(t *testing.T) { + requestHeaders := http.Header{} + requestHeaders.Set("foo", "bar") + requestHeaders.Set("taz", "ok") + requestHeaders.Set("proxy", "me") + + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header = requestHeaders + + actual := GetHeadersToProxy(req, []string{"foo", "proxy"}) + + expected := http.Header{} + expected.Set("foo", "bar") + expected.Set("proxy", "me") + + require.Equal(t, actual, expected) + }) +} diff --git a/main.go b/main.go index 22ace354..870908fe 100644 --- a/main.go +++ b/main.go @@ -25,8 +25,8 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/rond-authz/rond/core" - "github.com/rond-authz/rond/helpers" "github.com/rond-authz/rond/internal/config" + "github.com/rond-authz/rond/internal/helpers" "github.com/rond-authz/rond/internal/mongoclient" "github.com/rond-authz/rond/openapi" "github.com/rond-authz/rond/service" diff --git a/service/router.go b/service/router.go index 1318c7af..39e3b5d4 100644 --- a/service/router.go +++ b/service/router.go @@ -27,7 +27,6 @@ import ( "github.com/getkin/kin-openapi/openapi3" "github.com/prometheus/client_golang/prometheus" "github.com/rond-authz/rond/core" - "github.com/rond-authz/rond/helpers" "github.com/rond-authz/rond/internal/config" "github.com/rond-authz/rond/internal/metrics" "github.com/rond-authz/rond/internal/mongoclient" @@ -116,8 +115,6 @@ func SetupRouter( evalRouter := router.NewRoute().Subrouter() if env.Standalone { - router.Use(helpers.AddHeadersToProxyMiddleware(log, env.GetAdditionalHeadersToProxy())) - swaggerRouter, err := swagger.NewRouter(gorilla.NewRouter(router), swagger.Options{ Context: context.Background(), Openapi: &openapi3.T{ diff --git a/service/standalone_apis.go b/service/standalone_apis.go index 3e096e2e..73792b6c 100644 --- a/service/standalone_apis.go +++ b/service/standalone_apis.go @@ -20,13 +20,14 @@ import ( "net/http" "github.com/rond-authz/rond/internal/config" - "github.com/rond-authz/rond/internal/crudclient" + "github.com/rond-authz/rond/internal/helpers" "github.com/rond-authz/rond/internal/utils" "github.com/rond-authz/rond/types" "github.com/google/uuid" "github.com/gorilla/mux" "github.com/mia-platform/glogger/v2" + "github.com/mia-platform/go-crud-service-client" "github.com/sirupsen/logrus" ) @@ -67,23 +68,24 @@ func revokeHandler(w http.ResponseWriter, r *http.Request) { return } - bindings := make([]types.Binding, 0) - - client, err := crudclient.New(env.BindingsCrudServiceURL) + client, err := crud.NewClient[types.Binding](crud.ClientOptions{ + BaseURL: env.BindingsCrudServiceURL, + Headers: helpers.GetHeadersToProxy(r, env.GetAdditionalHeadersToProxy()), + }) if err != nil { logger.WithField("error", logrus.Fields{"message": err.Error()}).Error("failed crud setup") utils.FailResponseWithCode(w, http.StatusInternalServerError, err.Error(), utils.GENERIC_BUSINESS_ERROR_MESSAGE) return } - query, err := buildQuery(resourceType, reqBody.ResourceIDs, reqBody.Subjects, reqBody.Groups) + query := buildQuery(resourceType, reqBody.ResourceIDs, reqBody.Subjects, reqBody.Groups) + bindings, err := client.List(r.Context(), crud.Options{ + Filter: crud.Filter{ + MongoQuery: query, + Limit: BINDINGS_MAX_PAGE_SIZE, + }, + }) if err != nil { - logger.WithField("error", logrus.Fields{"message": err.Error()}).Error("failed find query crud setup") - utils.FailResponseWithCode(w, http.StatusInternalServerError, "failed find query crud setup", utils.GENERIC_BUSINESS_ERROR_MESSAGE) - return - } - - if err := client.Get(r.Context(), fmt.Sprintf("_q=%s&_l=%d", string(query), BINDINGS_MAX_PAGE_SIZE), &bindings); err != nil { logger.WithField("error", logrus.Fields{"message": err.Error()}).Error("failed crud request") utils.FailResponseWithCode(w, http.StatusInternalServerError, "failed crud request for finding bindings", utils.GENERIC_BUSINESS_ERROR_MESSAGE) return @@ -95,7 +97,7 @@ func revokeHandler(w http.ResponseWriter, r *http.Request) { var patchCrudResponse int if len(bindingsToDelete) > 0 { - query, err := buildQueryForBindingsToDelete(bindingsToDelete) + query := buildQueryForBindingsToDelete(bindingsToDelete) if err != nil { logger.WithField("error", logrus.Fields{"message": err.Error()}).Error("failed delete query crud setup") utils.FailResponseWithCode(w, http.StatusInternalServerError, "failed delete query crud setup", utils.GENERIC_BUSINESS_ERROR_MESSAGE) @@ -107,7 +109,8 @@ func revokeHandler(w http.ResponseWriter, r *http.Request) { "bindingsToDelete": len(bindingsToDelete), }).Debug("generated query for bindings to delete") - if err := client.Delete(r.Context(), fmt.Sprintf("_q=%s", string(query)), &deleteCrudResponse); err != nil { + deleteCrudResponse, err = client.DeleteMany(r.Context(), crud.Options{Filter: crud.Filter{MongoQuery: query}}) + if err != nil { logger.WithField("error", logrus.Fields{"message": err.Error()}).Error("failed crud request") utils.FailResponseWithCode(w, http.StatusInternalServerError, "failed crud request for deleting unused bindings", utils.GENERIC_BUSINESS_ERROR_MESSAGE) return @@ -118,7 +121,8 @@ func revokeHandler(w http.ResponseWriter, r *http.Request) { if len(bindingsToPatch) > 0 { body := buildRequestBodyForBindingsToPatch(bindingsToPatch) - if err := client.PatchBulk(r.Context(), body, &patchCrudResponse); err != nil { + patchCrudResponse, err = client.PatchBulk(r.Context(), body, crud.Options{}) + if err != nil { logger.WithField("error", logrus.Fields{"message": err.Error()}).Error("failed crud request") utils.FailResponseWithCode( w, @@ -186,7 +190,10 @@ func grantHandler(w http.ResponseWriter, r *http.Request) { return } - client, err := crudclient.New(env.BindingsCrudServiceURL) + client, err := crud.NewClient[types.Binding](crud.ClientOptions{ + BaseURL: env.BindingsCrudServiceURL, + Headers: helpers.GetHeadersToProxy(r, env.GetAdditionalHeadersToProxy()), + }) if err != nil { logger.WithField("error", logrus.Fields{"message": err.Error()}).Error("failed crud setup") utils.FailResponseWithCode(w, http.StatusInternalServerError, err.Error(), utils.GENERIC_BUSINESS_ERROR_MESSAGE) @@ -208,14 +215,14 @@ func grantHandler(w http.ResponseWriter, r *http.Request) { } } - var bindingIDCreated types.BindingCreateResponse - if err := client.Post(r.Context(), &bindingToCreate, &bindingIDCreated); err != nil { + bindingIDCreated, err := client.Create(r.Context(), bindingToCreate, crud.Options{}) + if err != nil { logger.WithField("error", logrus.Fields{"message": err.Error()}).Error("failed crud request") utils.FailResponseWithCode(w, http.StatusInternalServerError, "failed crud request for creating bindings", utils.GENERIC_BUSINESS_ERROR_MESSAGE) return } logger.WithFields(logrus.Fields{ - "createdBindingObjectId": utils.SanitizeString(bindingIDCreated.ObjectID), + "createdBindingObjectId": utils.SanitizeString(bindingIDCreated), "createdBindingId": utils.SanitizeString(bindingToCreate.BindingID), "resourceId": utils.SanitizeString(reqBody.ResourceID), "resourceType": utils.SanitizeString(resourceType), @@ -238,7 +245,7 @@ func grantHandler(w http.ResponseWriter, r *http.Request) { } } -func buildQuery(resourceType string, resourceIDs []string, subjects []string, groups []string) ([]byte, error) { +func buildQuery(resourceType string, resourceIDs []string, subjects []string, groups []string) map[string]interface{} { queryPartForSubjectOrGroups := map[string]interface{}{ "$or": []map[string]interface{}{}, } @@ -255,7 +262,7 @@ func buildQuery(resourceType string, resourceIDs []string, subjects []string, gr } if resourceType == "" { - return json.Marshal(queryPartForSubjectOrGroups) + return queryPartForSubjectOrGroups } query := map[string]interface{}{ @@ -268,10 +275,10 @@ func buildQuery(resourceType string, resourceIDs []string, subjects []string, gr }, } - return json.Marshal(query) + return query } -func buildQueryForBindingsToDelete(bindingsToDelete []types.Binding) ([]byte, error) { +func buildQueryForBindingsToDelete(bindingsToDelete []types.Binding) map[string]interface{} { bindingsIds := make([]string, len(bindingsToDelete)) for i := 0; i < len(bindingsToDelete); i++ { bindingsIds[i] = bindingsToDelete[i].BindingID @@ -282,25 +289,21 @@ func buildQueryForBindingsToDelete(bindingsToDelete []types.Binding) ([]byte, er "$in": bindingsIds, }, } - return json.Marshal(query) -} - -type UpdateCommand struct { - SetCommand types.BindingUpdate `json:"$set"` -} -type PatchItem struct { - Filter types.BindingFilter `json:"filter"` - Update UpdateCommand `json:"update"` + return query } -func buildRequestBodyForBindingsToPatch(bindingsToPatch []types.Binding) []PatchItem { - patches := make([]PatchItem, len(bindingsToPatch)) +func buildRequestBodyForBindingsToPatch(bindingsToPatch []types.Binding) crud.PatchBulkBody { + patches := make(crud.PatchBulkBody, len(bindingsToPatch)) for i := 0; i < len(bindingsToPatch); i++ { currentBinding := bindingsToPatch[i] - patches[i] = PatchItem{ - Filter: types.BindingFilter{BindingID: currentBinding.BindingID}, - Update: UpdateCommand{ - SetCommand: types.BindingUpdate{ + patches[i] = crud.PatchBulkItem{ + Filter: crud.PatchBulkFilter{ + Fields: map[string]string{ + "bindingId": currentBinding.BindingID, + }, + }, + Update: crud.PatchBody{ + Set: types.BindingUpdate{ Subjects: currentBinding.Subjects, Groups: currentBinding.Groups, }, diff --git a/service/standalone_apis_test.go b/service/standalone_apis_test.go index 041d3ff5..5956b5d7 100644 --- a/service/standalone_apis_test.go +++ b/service/standalone_apis_test.go @@ -18,6 +18,7 @@ import ( "bytes" "context" "encoding/json" + "fmt" "io" "net/http" "net/http/httptest" @@ -26,8 +27,10 @@ import ( "github.com/google/uuid" "github.com/gorilla/mux" + "github.com/mia-platform/go-crud-service-client" "github.com/rond-authz/rond/internal/config" "github.com/rond-authz/rond/types" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/h2non/gock.v1" ) @@ -82,8 +85,7 @@ func TestRevokeHandler(t *testing.T) { responseBody := map[string]interface{}{"answer": float64(42)} gock.DisableNetworking() - gock.New("http://crud-service"). - Get("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodGet, "/bindings/"). Reply(http.StatusBadRequest). JSON(responseBody) @@ -109,13 +111,11 @@ func TestRevokeHandler(t *testing.T) { }, } gock.DisableNetworking() - gock.New("http://crud-service"). - Get("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodGet, "/bindings/"). Reply(http.StatusOK). JSON(bindingsFromCrud) - gock.New("http://crud-service"). - Delete("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodDelete, "/bindings/"). Reply(http.StatusInternalServerError). JSON(map[string]interface{}{"statusCode": "500", "error": "InternalServerError", "message": "some message"}) @@ -136,7 +136,6 @@ func TestRevokeHandler(t *testing.T) { t.Run("does not invoke delete API if not necessary", func(t *testing.T) { defer gock.Flush() - bindingsFromCrud := []types.Binding{ { BindingID: "bindingToDelete", @@ -149,21 +148,18 @@ func TestRevokeHandler(t *testing.T) { } gock.DisableNetworking() - gock.New("http://crud-service"). - Get("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodGet, "/bindings/"). AddMatcher(func(req *http.Request, greq *gock.Request) (bool, error) { mongoQueryString := req.URL.Query().Get("_q") - match := mongoQueryString == `{"$and":[{"resource.resourceId":{"$in":["mike"]},"resource.resourceType":"project"},{"$or":[{"subjects":{"$in":["piero"]}}]}]}` - limit := req.URL.Query().Get("_l") - matchLimit := limit == "200" + matchLimit := assert.Equal(t, "200", limit) + match := assert.Equal(t, `{"$and":[{"resource.resourceId":{"$in":["mike"]},"resource.resourceType":"project"},{"$or":[{"subjects":{"$in":["piero"]}}]}]}`, mongoQueryString) return match && matchLimit, nil }). Reply(http.StatusOK). JSON(bindingsFromCrud) - gock.New("http://crud-service"). - Patch("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodPatch, "/bindings/bulk"). Reply(http.StatusOK) reqBody := setupRevokeRequestBody(t, RevokeRequestBody{ @@ -195,8 +191,7 @@ func TestRevokeHandler(t *testing.T) { }, } gock.DisableNetworking() - gock.New("http://crud-service"). - Get("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodGet, "/bindings/"). AddMatcher(func(req *http.Request, greq *gock.Request) (bool, error) { mongoQueryString := req.URL.Query().Get("_q") match := mongoQueryString == `{"$and":[{"resource.resourceId":{"$in":["mike"]},"resource.resourceType":"myResource"},{"$or":[{"subjects":{"$in":["piero"]}}]}]}` @@ -205,8 +200,7 @@ func TestRevokeHandler(t *testing.T) { Reply(http.StatusOK). JSON(bindingsFromCrud) - gock.New("http://crud-service"). - Delete("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodDelete, "/bindings/"). AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) { mongoQuery := req.URL.Query().Get("_q") match := mongoQuery == `{"bindingId":{"$in":["bindingToDelete"]}}` @@ -244,8 +238,8 @@ func TestRevokeHandler(t *testing.T) { }, } gock.DisableNetworking() - gock.New("http://crud-service"). - Get("/bindings/"). + + newGockScope(t, "http://crud-service", http.MethodGet, "/bindings/"). AddMatcher(func(req *http.Request, greq *gock.Request) (bool, error) { mongoQueryString := req.URL.Query().Get("_q") match := mongoQueryString == `{"$and":[{"resource.resourceId":{"$in":["mike"]},"resource.resourceType":"some-resource"},{"$or":[{"groups":{"$in":["litfiba"]}}]}]}` @@ -254,8 +248,7 @@ func TestRevokeHandler(t *testing.T) { Reply(http.StatusOK). JSON(bindingsFromCrud) - gock.New("http://crud-service"). - Delete("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodDelete, "/bindings/"). AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) { mongoQuery := req.URL.Query().Get("_q") match := mongoQuery == `{"bindingId":{"$in":["bindingToDelete"]}}` @@ -293,8 +286,7 @@ func TestRevokeHandler(t *testing.T) { }, } gock.DisableNetworking() - gock.New("http://crud-service"). - Get("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodGet, "/bindings/"). AddMatcher(func(req *http.Request, greq *gock.Request) (bool, error) { mongoQueryString := req.URL.Query().Get("_q") match := mongoQueryString == `{"$and":[{"resource.resourceId":{"$in":["mike"]},"resource.resourceType":"some-resource"},{"$or":[{"subjects":{"$in":["piero"]}}]}]}` @@ -303,20 +295,23 @@ func TestRevokeHandler(t *testing.T) { Reply(http.StatusOK). JSON(bindingsFromCrud) - gock.New("http://crud-service"). - Patch("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodPatch, "/bindings/bulk"). AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) { - var body []PatchItem + var body crud.PatchBulkBody err := json.NewDecoder(req.Body).Decode(&body) require.NoError(t, err, "unxpected error parsing body in matcher") - require.Equal(t, []PatchItem{ + require.Equal(t, crud.PatchBulkBody{ { - Filter: types.BindingFilter{BindingID: "litfiba"}, - Update: UpdateCommand{ - SetCommand: types.BindingUpdate{ - Subjects: []string{"ghigo"}, - Groups: []string{}, + Filter: crud.PatchBulkFilter{ + Fields: map[string]string{ + "bindingId": "litfiba", + }, + }, + Update: crud.PatchBody{ + Set: map[string]interface{}{ + "subjects": []any{"ghigo"}, + "groups": []any{}, }, }, }, @@ -365,8 +360,7 @@ func TestRevokeHandler(t *testing.T) { }, } gock.DisableNetworking() - gock.New("http://crud-service"). - Get("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodGet, "/bindings/"). AddMatcher(func(req *http.Request, greq *gock.Request) (bool, error) { mongoQueryString := req.URL.Query().Get("_q") match := mongoQueryString == `{"$and":[{"resource.resourceId":{"$in":["mike"]},"resource.resourceType":"resource"},{"$or":[{"subjects":{"$in":["piero","liam","noel"]}},{"groups":{"$in":["brutte_band"]}}]}]}` @@ -375,8 +369,7 @@ func TestRevokeHandler(t *testing.T) { Reply(http.StatusOK). JSON(bindingsFromCrud) - gock.New("http://crud-service"). - Delete("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodDelete, "/bindings/"). AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) { mongoQuery := req.URL.Query().Get("_q") match := mongoQuery == `{"bindingId":{"$in":["oasis"]}}` @@ -385,21 +378,24 @@ func TestRevokeHandler(t *testing.T) { Reply(http.StatusOK). BodyString("1") - gock.New("http://crud-service"). - Patch("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodPatch, "/bindings/bulk"). AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) { - var body []PatchItem + var body crud.PatchBulkBody err := json.NewDecoder(req.Body).Decode(&body) require.NoError(t, err, "unxpected error parsing body in matcher") - require.Equal(t, []PatchItem{ + require.Equal(t, crud.PatchBulkBody{ { - Filter: types.BindingFilter{BindingID: "litfiba"}, - Update: UpdateCommand{ - SetCommand: types.BindingUpdate{ - Subjects: []string{"ghigo"}, - Groups: []string{}, + Filter: crud.PatchBulkFilter{ + Fields: map[string]string{ + "bindingId": "litfiba", + }, + }, + Update: crud.PatchBody{ + Set: map[string]any{ + "subjects": []any{"ghigo"}, + "groups": []any{}, }, }, }, @@ -445,8 +441,8 @@ func TestRevokeHandler(t *testing.T) { }, } gock.DisableNetworking() - gock.New("http://crud-service"). - Get("/bindings/"). + + newGockScope(t, "http://crud-service", http.MethodGet, "/bindings/"). AddMatcher(func(req *http.Request, greq *gock.Request) (bool, error) { mongoQueryString := req.URL.Query().Get("_q") match := mongoQueryString == `{"$or":[{"subjects":{"$in":["piero","liam","noel"]}},{"groups":{"$in":["brutte_band"]}}]}` @@ -455,8 +451,7 @@ func TestRevokeHandler(t *testing.T) { Reply(http.StatusOK). JSON(bindingsFromCrud) - gock.New("http://crud-service"). - Delete("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodDelete, "/bindings/"). AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) { mongoQuery := req.URL.Query().Get("_q") match := mongoQuery == `{"bindingId":{"$in":["oasis"]}}` @@ -465,21 +460,24 @@ func TestRevokeHandler(t *testing.T) { Reply(http.StatusOK). BodyString("1") - gock.New("http://crud-service"). - Patch("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodPatch, "/bindings/bulk"). AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) { - var body []PatchItem + var body crud.PatchBulkBody err := json.NewDecoder(req.Body).Decode(&body) require.NoError(t, err, "unxpected error parsing body in matcher") - require.Equal(t, []PatchItem{ + require.Equal(t, crud.PatchBulkBody{ { - Filter: types.BindingFilter{BindingID: "litfiba"}, - Update: UpdateCommand{ - SetCommand: types.BindingUpdate{ - Subjects: []string{"ghigo"}, - Groups: []string{}, + Filter: crud.PatchBulkFilter{ + Fields: map[string]string{ + "bindingId": "litfiba", + }, + }, + Update: crud.PatchBody{ + Set: map[string]any{ + "subjects": []any{"ghigo"}, + "groups": []any{}, }, }, }, @@ -559,8 +557,7 @@ func TestGrantHandler(t *testing.T) { }) gock.DisableNetworking() - gock.New("http://crud-service"). - Post("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodPost, "/bindings/"). AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) { var body types.Binding bodyBytes, err := io.ReadAll(req.Body) @@ -616,8 +613,7 @@ func TestGrantHandler(t *testing.T) { }) gock.DisableNetworking() - gock.New("http://crud-service"). - Post("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodPost, "/bindings/"). AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) { var body types.Binding bodyBytes, err := io.ReadAll(req.Body) @@ -670,8 +666,7 @@ func TestGrantHandler(t *testing.T) { }) gock.DisableNetworking() - gock.New("http://crud-service"). - Post("/bindings/"). + newGockScope(t, "http://crud-service", http.MethodPost, "/bindings/"). AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) { var body types.Binding err := json.NewDecoder(req.Body).Decode(&body) @@ -843,3 +838,18 @@ func requestWithParams( } return req } + +func newGockScope(t *testing.T, baseURL, method, path string) *gock.Request { + t.Helper() + + scope := gock.New(baseURL) + scope.Method = strings.ToUpper(method) + scope.Path(fmt.Sprintf("%s$", path)) + + t.Cleanup(func() { + require.True(t, gock.IsDone()) + gock.OffAll() + }) + + return scope +} diff --git a/types/rbactypes.go b/types/rbactypes.go index 6c790755..054a38d7 100644 --- a/types/rbactypes.go +++ b/types/rbactypes.go @@ -49,10 +49,6 @@ type Binding struct { Roles []string `bson:"roles" json:"roles,omitempty"` } -type BindingFilter struct { - BindingID string `bson:"bindingId" json:"bindingId"` -} - type BindingUpdate struct { Groups []string `bson:"groups" json:"groups"` Subjects []string `bson:"subjects" json:"subjects"`