From 9a705ae9c092c539c7bc04cd79094c336245ae73 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 20 Mar 2024 12:20:08 +0000 Subject: [PATCH 01/16] feat: add validation for `/groups` endpoints Signed-off-by: Babak K. Shandiz --- rebac-admin-backend/v1/groups_validation.go | 99 ++++ .../v1/groups_validation_test.go | 454 ++++++++++++++++++ .../v1/groups_validation_types.go | 70 +++ rebac-admin-backend/v1/util_test.go | 18 + rebac-admin-backend/v1/validation.go | 245 ++++++++++ rebac-admin-backend/v1/validation_util.go | 26 + 6 files changed, 912 insertions(+) create mode 100644 rebac-admin-backend/v1/groups_validation.go create mode 100644 rebac-admin-backend/v1/groups_validation_test.go create mode 100644 rebac-admin-backend/v1/groups_validation_types.go create mode 100644 rebac-admin-backend/v1/util_test.go create mode 100644 rebac-admin-backend/v1/validation.go create mode 100644 rebac-admin-backend/v1/validation_util.go diff --git a/rebac-admin-backend/v1/groups_validation.go b/rebac-admin-backend/v1/groups_validation.go new file mode 100644 index 000000000..fa6375b44 --- /dev/null +++ b/rebac-admin-backend/v1/groups_validation.go @@ -0,0 +1,99 @@ +// Copyright 2024 Canonical Ltd. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "net/http" + + "github.com/canonical/identity-platform-admin-ui/rebac-admin-backend/v1/resources" +) + +// GetGroups validates request body for the GetGroups method and delegates to the underlying handler. +func (v handlerWithValidation) GetGroups(w http.ResponseWriter, r *http.Request, params resources.GetGroupsParams) { + v.handler.GetGroups(w, r, params) +} + +// PostGroups validates request body for the PostGroups method and delegates to the underlying handler. +func (v handlerWithValidation) PostGroups(w http.ResponseWriter, r *http.Request) { + setRequestBodyInContext[resources.Group](w, r, func(w http.ResponseWriter, r *http.Request, body *resources.Group) { + if err := validateGroup(body); err != nil { + writeErrorResponse(w, err) + return + } + v.handler.PostGroups(w, r) + }) +} + +// DeleteGroupsItem validates request body for the DeleteGroupsItem method and delegates to the underlying handler. +func (v handlerWithValidation) DeleteGroupsItem(w http.ResponseWriter, r *http.Request, id string) { + v.handler.DeleteGroupsItem(w, r, id) +} + +// GetGroupsItem validates request body for the GetGroupsItem method and delegates to the underlying handler. +func (v handlerWithValidation) GetGroupsItem(w http.ResponseWriter, r *http.Request, id string) { + v.handler.GetGroupsItem(w, r, id) +} + +// PutGroupsItem validates request body for the PutGroupsItem method and delegates to the underlying handler. +func (v handlerWithValidation) PutGroupsItem(w http.ResponseWriter, r *http.Request, id string) { + setRequestBodyInContext[resources.Group](w, r, func(w http.ResponseWriter, r *http.Request, body *resources.Group) { + if err := validateGroup(body); err != nil { + writeErrorResponse(w, err) + return + } + if body.Id == nil || id != *body.Id { + writeErrorResponse(w, NewRequestBodyValidationError("group ID from path does not match the Group object")) + return + } + v.handler.PutGroupsItem(w, r, id) + }) +} + +// GetGroupsItemEntitlements validates request body for the GetGroupsItemEntitlements method and delegates to the underlying handler. +func (v handlerWithValidation) GetGroupsItemEntitlements(w http.ResponseWriter, r *http.Request, id string, params resources.GetGroupsItemEntitlementsParams) { + v.handler.GetGroupsItemEntitlements(w, r, id, params) +} + +// PatchGroupsItemEntitlements validates request body for the PatchGroupsItemEntitlements method and delegates to the underlying handler. +func (v handlerWithValidation) PatchGroupsItemEntitlements(w http.ResponseWriter, r *http.Request, id string) { + setRequestBodyInContext[resources.GroupEntitlementsPatchRequestBody](w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupEntitlementsPatchRequestBody) { + if err := validateGroupEntitlementsPatchRequestBody(body); err != nil { + writeErrorResponse(w, err) + return + } + v.handler.PatchGroupsItemEntitlements(w, r, id) + }) +} + +// GetGroupsItemIdentities validates request body for the GetGroupsItemIdentities method and delegates to the underlying handler. +func (v handlerWithValidation) GetGroupsItemIdentities(w http.ResponseWriter, r *http.Request, id string, params resources.GetGroupsItemIdentitiesParams) { + v.handler.GetGroupsItemIdentities(w, r, id, params) +} + +// PatchGroupsItemIdentities validates request body for the PatchGroupsItemIdentities method and delegates to the underlying handler. +func (v handlerWithValidation) PatchGroupsItemIdentities(w http.ResponseWriter, r *http.Request, id string) { + setRequestBodyInContext[resources.GroupIdentitiesPatchRequestBody](w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupIdentitiesPatchRequestBody) { + if err := validateGroupIdentitiesPatchRequestBody(body); err != nil { + writeErrorResponse(w, err) + return + } + v.handler.PatchGroupsItemIdentities(w, r, id) + }) +} + +// GetGroupsItemRoles validates request body for the GetGroupsItemRoles method and delegates to the underlying handler. +func (v handlerWithValidation) GetGroupsItemRoles(w http.ResponseWriter, r *http.Request, id string, params resources.GetGroupsItemRolesParams) { + v.handler.GetGroupsItemRoles(w, r, id, params) +} + +// PatchGroupsItemRoles validates request body for the PatchGroupsItemRoles method and delegates to the underlying handler. +func (v handlerWithValidation) PatchGroupsItemRoles(w http.ResponseWriter, r *http.Request, id string) { + setRequestBodyInContext[resources.GroupRolesPatchRequestBody](w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupRolesPatchRequestBody) { + if err := validateGroupRolesPatchRequestBody(body); err != nil { + writeErrorResponse(w, err) + return + } + v.handler.PatchGroupsItemRoles(w, r, id) + }) +} diff --git a/rebac-admin-backend/v1/groups_validation_test.go b/rebac-admin-backend/v1/groups_validation_test.go new file mode 100644 index 000000000..093e4500a --- /dev/null +++ b/rebac-admin-backend/v1/groups_validation_test.go @@ -0,0 +1,454 @@ +// Copyright 2024 Canonical Ltd. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/canonical/identity-platform-admin-ui/rebac-admin-backend/v1/resources" + qt "github.com/frankban/quicktest" + "go.uber.org/mock/gomock" +) + +//go:generate mockgen -package resources -destination ./resources/mock_generated_server.go -source=./resources/generated_server.go + +func TestHandlerWithValidation_Groups(t *testing.T) { + c := qt.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + writeResponse := func(w http.ResponseWriter, status int, body any) { + raw, _ := json.Marshal(body) + w.WriteHeader(status) + w.Write(raw) + } + + validEntitlement := resources.EntityEntitlement{ + EntitlementType: "some-entitlement-type", + EntityName: "some-entity-name", + EntityType: "some-entity-type", + } + + tests := []struct { + name string + requestBodyRaw string + requestBody any + setupHandlerMock func(mockHandler *resources.MockServerInterface) + triggerFunc func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) + expectedStatusCode int + expectedResponse any + }{{ + name: "PostGroups: success", + requestBody: resources.Group{Name: "foo"}, + setupHandlerMock: func(mockHandler *resources.MockServerInterface) { + mockHandler.EXPECT(). + PostGroups(gomock.Any(), gomock.Any()). + Do(func(w http.ResponseWriter, _ *http.Request) { + writeResponse(w, http.StatusOK, resources.Response{ + Status: http.StatusOK, + }) + }) + }, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PostGroups(w, r) + }, + expectedStatusCode: http.StatusOK, + expectedResponse: resources.Response{ + Status: http.StatusOK, + }, + }, { + name: "PostGroups: failure; invalid JSON", + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PostGroups(w, r) + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: missing request body: request body is not a valid JSON", + Status: http.StatusBadRequest, + }, + }, { + name: "PostGroups: failure; empty", + requestBody: resources.Group{}, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PostGroups(w, r) + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: empty group name", + Status: http.StatusBadRequest, + }, + }, { + name: "PutGroupsItem: success", + requestBody: resources.Group{Name: "foo", Id: stringPtr("some-id")}, + setupHandlerMock: func(mockHandler *resources.MockServerInterface) { + mockHandler.EXPECT(). + PutGroupsItem(gomock.Any(), gomock.Any(), "some-id"). + Do(func(w http.ResponseWriter, _ *http.Request, _ string) { + writeResponse(w, http.StatusOK, resources.Response{ + Status: http.StatusOK, + }) + }) + }, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PutGroupsItem(w, r, "some-id") + }, + expectedStatusCode: http.StatusOK, + expectedResponse: resources.Response{ + Status: http.StatusOK, + }, + }, { + name: "PutGroupsItem: failure; invalid JSON", + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PutGroupsItem(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: missing request body: request body is not a valid JSON", + Status: http.StatusBadRequest, + }, + }, { + name: "PutGroupsItem: failure; empty", + requestBody: resources.Group{}, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PutGroupsItem(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: empty group name", + Status: http.StatusBadRequest, + }, + }, { + name: "PutGroupsItem: failure; nil id", + requestBody: resources.Group{Name: "foo"}, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PutGroupsItem(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: group ID from path does not match the Group object", + Status: http.StatusBadRequest, + }, + }, { + name: "PutGroupsItem: failure; id mismatch", + requestBody: resources.Group{Name: "foo", Id: stringPtr("some-other-id")}, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PutGroupsItem(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: group ID from path does not match the Group object", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemEntitlements: success", + requestBody: resources.GroupEntitlementsPatchRequestBody{ + Patches: []resources.GroupEntitlementsPatchItem{{ + Op: "add", + Entitlement: validEntitlement, + }}, + }, + setupHandlerMock: func(mockHandler *resources.MockServerInterface) { + mockHandler.EXPECT(). + PatchGroupsItemEntitlements(gomock.Any(), gomock.Any(), "some-id"). + Do(func(w http.ResponseWriter, _ *http.Request, _ string) { + writeResponse(w, http.StatusOK, resources.Response{ + Status: http.StatusOK, + }) + }) + }, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemEntitlements(w, r, "some-id") + }, + expectedStatusCode: http.StatusOK, + expectedResponse: resources.Response{ + Status: http.StatusOK, + }, + }, { + name: "PatchGroupsItemEntitlements: failure; invalid JSON", + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemEntitlements(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: missing request body: request body is not a valid JSON", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemEntitlements: failure; nil patch array", + requestBody: resources.GroupEntitlementsPatchRequestBody{}, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemEntitlements(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: empty patch array", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemEntitlements: failure; empty patch array", + requestBody: resources.GroupEntitlementsPatchRequestBody{ + Patches: []resources.GroupEntitlementsPatchItem{}, + }, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemEntitlements(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: empty patch array", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemEntitlements: failure; invalid op", + requestBody: resources.GroupEntitlementsPatchRequestBody{ + Patches: []resources.GroupEntitlementsPatchItem{{ + Op: "some-invalid-op", + Entitlement: validEntitlement, + }}, + }, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemEntitlements(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: op value not allowed: \"some-invalid-op\"", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemEntitlements: failure; invalid entitlement", + requestBody: resources.GroupEntitlementsPatchRequestBody{ + Patches: []resources.GroupEntitlementsPatchItem{{ + Op: "some-invalid-op", + Entitlement: resources.EntityEntitlement{}, + }}, + }, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemEntitlements(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: empty entitlement type", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemIdentities: success", + requestBody: resources.GroupIdentitiesPatchRequestBody{ + Patches: []resources.GroupIdentitiesPatchItem{{ + Op: "add", + Identity: "some-identity", + }}, + }, + setupHandlerMock: func(mockHandler *resources.MockServerInterface) { + mockHandler.EXPECT(). + PatchGroupsItemIdentities(gomock.Any(), gomock.Any(), "some-id"). + Do(func(w http.ResponseWriter, _ *http.Request, _ string) { + writeResponse(w, http.StatusOK, resources.Response{ + Status: http.StatusOK, + }) + }) + }, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemIdentities(w, r, "some-id") + }, + expectedStatusCode: http.StatusOK, + expectedResponse: resources.Response{ + Status: http.StatusOK, + }, + }, { + name: "PatchGroupsItemIdentities: failure; invalid JSON", + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemIdentities(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: missing request body: request body is not a valid JSON", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemIdentities: failure; nil patch array", + requestBody: resources.GroupIdentitiesPatchRequestBody{}, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemIdentities(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: empty patch array", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemIdentities: failure; empty patch array", + requestBody: resources.GroupIdentitiesPatchRequestBody{ + Patches: []resources.GroupIdentitiesPatchItem{}, + }, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemIdentities(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: empty patch array", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemIdentities: failure; empty identity", + requestBody: resources.GroupIdentitiesPatchRequestBody{ + Patches: []resources.GroupIdentitiesPatchItem{{ + Op: "add", + Identity: "", + }}, + }, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemIdentities(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: empty identity name", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemIdentities: failure; invalid op", + requestBody: resources.GroupIdentitiesPatchRequestBody{ + Patches: []resources.GroupIdentitiesPatchItem{{ + Op: "some-invalid-op", + Identity: "some-identity", + }}, + }, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemIdentities(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: op value not allowed: \"some-invalid-op\"", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemRoles: success", + requestBody: resources.GroupRolesPatchRequestBody{ + Patches: []resources.GroupRolesPatchItem{{ + Op: "add", + Role: "some-role", + }}, + }, + setupHandlerMock: func(mockHandler *resources.MockServerInterface) { + mockHandler.EXPECT(). + PatchGroupsItemRoles(gomock.Any(), gomock.Any(), "some-id"). + Do(func(w http.ResponseWriter, _ *http.Request, _ string) { + writeResponse(w, http.StatusOK, resources.Response{ + Status: http.StatusOK, + }) + }) + }, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemRoles(w, r, "some-id") + }, + expectedStatusCode: http.StatusOK, + expectedResponse: resources.Response{ + Status: http.StatusOK, + }, + }, { + name: "PatchGroupsItemRoles: failure; invalid JSON", + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemRoles(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: missing request body: request body is not a valid JSON", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemRoles: failure; nil patch array", + requestBody: resources.GroupRolesPatchRequestBody{}, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemRoles(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: empty patch array", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemRoles: failure; empty patch array", + requestBody: resources.GroupRolesPatchRequestBody{ + Patches: []resources.GroupRolesPatchItem{}, + }, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemRoles(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: empty patch array", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemRoles: failure; empty role", + requestBody: resources.GroupRolesPatchRequestBody{ + Patches: []resources.GroupRolesPatchItem{{ + Op: "add", + Role: "", + }}, + }, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemRoles(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: empty role name", + Status: http.StatusBadRequest, + }, + }, { + name: "PatchGroupsItemRoles: failure; invalid op", + requestBody: resources.GroupRolesPatchRequestBody{ + Patches: []resources.GroupRolesPatchItem{{ + Op: "some-invalid-op", + Role: "some-role", + }}, + }, + triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { + sut.PatchGroupsItemRoles(w, r, "some-id") + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: resources.Response{ + Message: "Bad Request: invalid request body: op value not allowed: \"some-invalid-op\"", + Status: http.StatusBadRequest, + }, + }, + } + + for _, t := range tests { + tt := t + c.Run(tt.name, func(c *qt.C) { + mockHandler := resources.NewMockServerInterface(ctrl) + if tt.setupHandlerMock != nil { + tt.setupHandlerMock(mockHandler) + } + + sut := newHandlerWithValidation(mockHandler) + + var req *http.Request + if tt.requestBody != nil { + raw, err := json.Marshal(tt.requestBody) + c.Assert(err, qt.IsNil) + // Note that request method/URL shouldn't be important at the handler. + req, _ = http.NewRequest(http.MethodGet, "/blah", bytes.NewReader(raw)) + } else { + // Note that request method/URL shouldn't be important at the handler. + req, _ = http.NewRequest(http.MethodGet, "/blah", bytes.NewReader([]byte(tt.requestBodyRaw))) + } + + mockWriter := httptest.NewRecorder() + tt.triggerFunc(sut, mockWriter, req) + + response := mockWriter.Result() + c.Assert(response.StatusCode, qt.Equals, tt.expectedStatusCode) + + defer response.Body.Close() + responseBody, err := io.ReadAll(response.Body) + c.Assert(err, qt.IsNil) + c.Assert(string(responseBody), qt.JSONEquals, tt.expectedResponse) + }) + } +} diff --git a/rebac-admin-backend/v1/groups_validation_types.go b/rebac-admin-backend/v1/groups_validation_types.go new file mode 100644 index 000000000..f928ff084 --- /dev/null +++ b/rebac-admin-backend/v1/groups_validation_types.go @@ -0,0 +1,70 @@ +// Copyright 2024 Canonical Ltd. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "github.com/canonical/identity-platform-admin-ui/rebac-admin-backend/v1/resources" +) + +func validateGroup(value *resources.Group) error { + if len(value.Name) == 0 { + return NewRequestBodyValidationError("empty group name") + } + return nil +} + +func validateGroupEntitlementsPatchRequestBody(value *resources.GroupEntitlementsPatchRequestBody) error { + if len(value.Patches) == 0 { + return NewRequestBodyValidationError("empty patch array") + } + return validateSlice[resources.GroupEntitlementsPatchItem](value.Patches, validateGroupEntitlementsPatchItem) +} + +func validateGroupEntitlementsPatchItem(value *resources.GroupEntitlementsPatchItem) error { + if err := validateEntityEntitlement(&value.Entitlement); err != nil { + return err + } + return validateStringEnum("op", value.Op, resources.GroupEntitlementsPatchItemOpAdd, resources.GroupEntitlementsPatchItemOpRemove) +} + +func validateEntityEntitlement(value *resources.EntityEntitlement) error { + if len(value.EntitlementType) == 0 { + return NewRequestBodyValidationError("empty entitlement type") + } + if len(value.EntityName) == 0 { + return NewRequestBodyValidationError("empty entity name") + } + if len(value.EntityType) == 0 { + return NewRequestBodyValidationError("empty entity type") + } + return nil +} + +func validateGroupIdentitiesPatchRequestBody(value *resources.GroupIdentitiesPatchRequestBody) error { + if len(value.Patches) == 0 { + return NewRequestBodyValidationError("empty patch array") + } + return validateSlice[resources.GroupIdentitiesPatchItem](value.Patches, validateGroupIdentitiesPatchItem) +} + +func validateGroupIdentitiesPatchItem(value *resources.GroupIdentitiesPatchItem) error { + if len(value.Identity) == 0 { + return NewRequestBodyValidationError("empty identity name") + } + return validateStringEnum("op", value.Op, resources.GroupIdentitiesPatchItemOpAdd, resources.GroupIdentitiesPatchItemOpRemove) +} + +func validateGroupRolesPatchRequestBody(value *resources.GroupRolesPatchRequestBody) error { + if len(value.Patches) == 0 { + return NewRequestBodyValidationError("empty patch array") + } + return validateSlice[resources.GroupRolesPatchItem](value.Patches, validateGroupRolesPatchItem) +} + +func validateGroupRolesPatchItem(value *resources.GroupRolesPatchItem) error { + if len(value.Role) == 0 { + return NewRequestBodyValidationError("empty role name") + } + return validateStringEnum("op", value.Op, resources.GroupRolesPatchItemOpAdd, resources.GroupRolesPatchItemOpRemove) +} diff --git a/rebac-admin-backend/v1/util_test.go b/rebac-admin-backend/v1/util_test.go new file mode 100644 index 000000000..8572657a8 --- /dev/null +++ b/rebac-admin-backend/v1/util_test.go @@ -0,0 +1,18 @@ +// Copyright 2024 Canonical Ltd. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "net/http" + "net/http/httptest" +) + +func newTestRequest[T any](method, path string, body *T) *http.Request { + r := httptest.NewRequest(method, path, nil) + return newRequestWithBodyInContext(r, body) +} + +func stringPtr(s string) *string { + return &s +} diff --git a/rebac-admin-backend/v1/validation.go b/rebac-admin-backend/v1/validation.go new file mode 100644 index 000000000..270310efd --- /dev/null +++ b/rebac-admin-backend/v1/validation.go @@ -0,0 +1,245 @@ +// Copyright 2024 Canonical Ltd. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/canonical/identity-platform-admin-ui/rebac-admin-backend/v1/resources" + "github.com/go-playground/validator/v10" +) + +// handlerWithValidation decorates a given handler with validation logic. The +// request body is parsed into a safely-typed value and passed to the handler +// via context. +type handlerWithValidation struct { + // TODO(babakks) remove the embedded struct. + resources.Unimplemented + + handler resources.ServerInterface +} + +// newHandlerWithValidation returns a new instance of the validationHandlerDecorator struct. +func newHandlerWithValidation(handler resources.ServerInterface) *handlerWithValidation { + return &handlerWithValidation{ + handler: handler, + } +} + +// requestBodyContextKey is the context key to retrieve the parsed request body struct instance. +type requestBodyContextKey struct{} + +// getRequestBodyFromContext fetches request body from given context. +func getRequestBodyFromContext[T any](ctx context.Context) (*T, error) { + validator.New() + if body, ok := ctx.Value(requestBodyContextKey{}).(*T); ok { + return body, nil + } + return nil, NewMissingRequestBodyError("request body is not available") +} + +// newRequestWithBodyInContext sets the given body in a new request instance context +// and returns the new request. +// +// Note that, technically, this method could be an ordinary (non-generic) method, +// but it's defined as one to avoid confusion over value vs pointer arguments. +func newRequestWithBodyInContext[T any](r *http.Request, body *T) *http.Request { + return r.WithContext(context.WithValue(r.Context(), requestBodyContextKey{}, body)) +} + +// parseRequestBody parses request body as JSON and populates the given body instance. +func parseRequestBody[T any](r *http.Request) (*T, error) { + body := new(T) + defer r.Body.Close() + if err := json.NewDecoder(r.Body).Decode(body); err != nil { + return nil, NewMissingRequestBodyError("request body is not a valid JSON") + } + return body, nil +} + +// setRequestBodyInContext is a helper method to avoid repetition. It parses +// request body and if it's okay, will delegate to the provided callback with a +// new HTTP request instance with the parse body in the context. +func setRequestBodyInContext[T any](w http.ResponseWriter, r *http.Request, f func(w http.ResponseWriter, r *http.Request, body *T)) { + body, err := parseRequestBody[T](r) + if err != nil { + writeErrorResponse(w, err) + return + } + f(w, newRequestWithBodyInContext(r, body), body) +} + +// GetIdentityProviders validates request body for the GetIdentityProviders method and delegates to the underlying handler. +func (v handlerWithValidation) GetIdentityProviders(w http.ResponseWriter, r *http.Request, params resources.GetIdentityProvidersParams) { + v.handler.GetIdentityProviders(w, r, params) +} + +// PostIdentityProviders validates request body for the PostIdentityProviders method and delegates to the underlying handler. +func (v handlerWithValidation) PostIdentityProviders(w http.ResponseWriter, r *http.Request) { + body := &struct{}{} + v.handler.PostIdentityProviders(w, newRequestWithBodyInContext(r, body)) +} + +// GetAvailableIdentityProviders validates request body for the GetAvailableIdentityProviders method and delegates to the underlying handler. +func (v handlerWithValidation) GetAvailableIdentityProviders(w http.ResponseWriter, r *http.Request, params resources.GetAvailableIdentityProvidersParams) { + body := &struct{}{} + v.handler.GetAvailableIdentityProviders(w, newRequestWithBodyInContext(r, body), params) +} + +// DeleteIdentityProvidersItem validates request body for the DeleteIdentityProvidersItem method and delegates to the underlying handler. +func (v handlerWithValidation) DeleteIdentityProvidersItem(w http.ResponseWriter, r *http.Request, id string) { + body := &struct{}{} + v.handler.DeleteIdentityProvidersItem(w, newRequestWithBodyInContext(r, body), id) +} + +// GetIdentityProvidersItem validates request body for the GetIdentityProvidersItem method and delegates to the underlying handler. +func (v handlerWithValidation) GetIdentityProvidersItem(w http.ResponseWriter, r *http.Request, id string) { + body := &struct{}{} + v.handler.GetIdentityProvidersItem(w, newRequestWithBodyInContext(r, body), id) +} + +// PutIdentityProvidersItem validates request body for the PutIdentityProvidersItem method and delegates to the underlying handler. +func (v handlerWithValidation) PutIdentityProvidersItem(w http.ResponseWriter, r *http.Request, id string) { + body := &struct{}{} + v.handler.PutIdentityProvidersItem(w, newRequestWithBodyInContext(r, body), id) +} + +// GetCapabilities validates request body for the GetCapabilities method and delegates to the underlying handler. +func (v handlerWithValidation) GetCapabilities(w http.ResponseWriter, r *http.Request) { + body := &struct{}{} + v.handler.GetCapabilities(w, newRequestWithBodyInContext(r, body)) +} + +// GetEntitlements validates request body for the GetEntitlements method and delegates to the underlying handler. +func (v handlerWithValidation) GetEntitlements(w http.ResponseWriter, r *http.Request, params resources.GetEntitlementsParams) { + body := &struct{}{} + v.handler.GetEntitlements(w, newRequestWithBodyInContext(r, body), params) +} + +// GetRawEntitlements validates request body for the GetRawEntitlements method and delegates to the underlying handler. +func (v handlerWithValidation) GetRawEntitlements(w http.ResponseWriter, r *http.Request) { + body := &struct{}{} + v.handler.GetRawEntitlements(w, newRequestWithBodyInContext(r, body)) +} + +// GetIdentities validates request body for the GetIdentities method and delegates to the underlying handler. +func (v handlerWithValidation) GetIdentities(w http.ResponseWriter, r *http.Request, params resources.GetIdentitiesParams) { + body := &struct{}{} + v.handler.GetIdentities(w, newRequestWithBodyInContext(r, body), params) +} + +// PostIdentities validates request body for the PostIdentities method and delegates to the underlying handler. +func (v handlerWithValidation) PostIdentities(w http.ResponseWriter, r *http.Request) { + body := &struct{}{} + v.handler.PostIdentities(w, newRequestWithBodyInContext(r, body)) +} + +// DeleteIdentitiesItem validates request body for the DeleteIdentitiesItem method and delegates to the underlying handler. +func (v handlerWithValidation) DeleteIdentitiesItem(w http.ResponseWriter, r *http.Request, id string) { + body := &struct{}{} + v.handler.DeleteIdentitiesItem(w, newRequestWithBodyInContext(r, body), id) +} + +// GetIdentitiesItem validates request body for the GetIdentitiesItem method and delegates to the underlying handler. +func (v handlerWithValidation) GetIdentitiesItem(w http.ResponseWriter, r *http.Request, id string) { + body := &struct{}{} + v.handler.GetIdentitiesItem(w, newRequestWithBodyInContext(r, body), id) +} + +// PutIdentitiesItem validates request body for the PutIdentitiesItem method and delegates to the underlying handler. +func (v handlerWithValidation) PutIdentitiesItem(w http.ResponseWriter, r *http.Request, id string) { + body := &struct{}{} + v.handler.PutIdentitiesItem(w, newRequestWithBodyInContext(r, body), id) +} + +// GetIdentitiesItemEntitlements validates request body for the GetIdentitiesItemEntitlements method and delegates to the underlying handler. +func (v handlerWithValidation) GetIdentitiesItemEntitlements(w http.ResponseWriter, r *http.Request, id string, params resources.GetIdentitiesItemEntitlementsParams) { + body := &struct{}{} + v.handler.GetIdentitiesItemEntitlements(w, newRequestWithBodyInContext(r, body), id, params) +} + +// PatchIdentitiesItemEntitlements validates request body for the PatchIdentitiesItemEntitlements method and delegates to the underlying handler. +func (v handlerWithValidation) PatchIdentitiesItemEntitlements(w http.ResponseWriter, r *http.Request, id string) { + body := &struct{}{} + v.handler.PatchIdentitiesItemEntitlements(w, newRequestWithBodyInContext(r, body), id) +} + +// GetIdentitiesItemGroups validates request body for the GetIdentitiesItemGroups method and delegates to the underlying handler. +func (v handlerWithValidation) GetIdentitiesItemGroups(w http.ResponseWriter, r *http.Request, id string, params resources.GetIdentitiesItemGroupsParams) { + body := &struct{}{} + v.handler.GetIdentitiesItemGroups(w, newRequestWithBodyInContext(r, body), id, params) +} + +// PatchIdentitiesItemGroups validates request body for the PatchIdentitiesItemGroups method and delegates to the underlying handler. +func (v handlerWithValidation) PatchIdentitiesItemGroups(w http.ResponseWriter, r *http.Request, id string) { + body := &struct{}{} + v.handler.PatchIdentitiesItemGroups(w, newRequestWithBodyInContext(r, body), id) +} + +// GetIdentitiesItemRoles validates request body for the GetIdentitiesItemRoles method and delegates to the underlying handler. +func (v handlerWithValidation) GetIdentitiesItemRoles(w http.ResponseWriter, r *http.Request, id string, params resources.GetIdentitiesItemRolesParams) { + body := &struct{}{} + v.handler.GetIdentitiesItemRoles(w, newRequestWithBodyInContext(r, body), id, params) +} + +// PatchIdentitiesItemRoles validates request body for the PatchIdentitiesItemRoles method and delegates to the underlying handler. +func (v handlerWithValidation) PatchIdentitiesItemRoles(w http.ResponseWriter, r *http.Request, id string) { + body := &struct{}{} + v.handler.PatchIdentitiesItemRoles(w, newRequestWithBodyInContext(r, body), id) +} + +// GetResources validates request body for the GetResources method and delegates to the underlying handler. +func (v handlerWithValidation) GetResources(w http.ResponseWriter, r *http.Request, params resources.GetResourcesParams) { + body := &struct{}{} + v.handler.GetResources(w, newRequestWithBodyInContext(r, body), params) +} + +// GetRoles validates request body for the GetRoles method and delegates to the underlying handler. +func (v handlerWithValidation) GetRoles(w http.ResponseWriter, r *http.Request, params resources.GetRolesParams) { + body := &struct{}{} + v.handler.GetRoles(w, newRequestWithBodyInContext(r, body), params) +} + +// PostRoles validates request body for the PostRoles method and delegates to the underlying handler. +func (v handlerWithValidation) PostRoles(w http.ResponseWriter, r *http.Request) { + body := &struct{}{} + v.handler.PostRoles(w, newRequestWithBodyInContext(r, body)) +} + +// DeleteRolesItem validates request body for the DeleteRolesItem method and delegates to the underlying handler. +func (v handlerWithValidation) DeleteRolesItem(w http.ResponseWriter, r *http.Request, id string) { + body := &struct{}{} + v.handler.DeleteRolesItem(w, newRequestWithBodyInContext(r, body), id) +} + +// GetRolesItem validates request body for the GetRolesItem method and delegates to the underlying handler. +func (v handlerWithValidation) GetRolesItem(w http.ResponseWriter, r *http.Request, id string) { + body := &struct{}{} + v.handler.GetRolesItem(w, newRequestWithBodyInContext(r, body), id) +} + +// PutRolesItem validates request body for the PutRolesItem method and delegates to the underlying handler. +func (v handlerWithValidation) PutRolesItem(w http.ResponseWriter, r *http.Request, id string) { + body := &struct{}{} + v.handler.PutRolesItem(w, newRequestWithBodyInContext(r, body), id) +} + +// GetRolesItemEntitlements validates request body for the GetRolesItemEntitlements method and delegates to the underlying handler. +func (v handlerWithValidation) GetRolesItemEntitlements(w http.ResponseWriter, r *http.Request, id string, params resources.GetRolesItemEntitlementsParams) { + body := &struct{}{} + v.handler.GetRolesItemEntitlements(w, newRequestWithBodyInContext(r, body), id, params) +} + +// PatchRolesItemEntitlements validates request body for the PatchRolesItemEntitlements method and delegates to the underlying handler. +func (v handlerWithValidation) PatchRolesItemEntitlements(w http.ResponseWriter, r *http.Request, id string) { + body := &struct{}{} + v.handler.PatchRolesItemEntitlements(w, newRequestWithBodyInContext(r, body), id) +} + +// SwaggerJson validates request body for the SwaggerJson method and delegates to the underlying handler. +func (v handlerWithValidation) SwaggerJson(w http.ResponseWriter, r *http.Request) { + v.handler.SwaggerJson(w, r) +} diff --git a/rebac-admin-backend/v1/validation_util.go b/rebac-admin-backend/v1/validation_util.go new file mode 100644 index 000000000..6329f1453 --- /dev/null +++ b/rebac-admin-backend/v1/validation_util.go @@ -0,0 +1,26 @@ +// Copyright 2024 Canonical Ltd. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import "fmt" + +// validateStringEnum is a helper validator to validate string enums. +func validateStringEnum[T ~string](field string, value T, allowed ...T) error { + for _, element := range allowed { + if string(value) == string(element) { + return nil + } + } + return NewRequestBodyValidationError(fmt.Sprintf("%s value not allowed: %q", field, value)) +} + +// validateStringEnum is a helper validator to validate elements in a slice. +func validateSlice[T any](s []T, validator func(*T) error) error { + for _, element := range s { + if err := validator(&element); err != nil { + return err + } + } + return nil +} From c7f9faf1a4adefbbeada71fed64d81c94d234ec1 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 20 Mar 2024 12:21:34 +0000 Subject: [PATCH 02/16] feat: add new error creator functions Signed-off-by: Babak K. Shandiz --- rebac-admin-backend/v1/error.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/rebac-admin-backend/v1/error.go b/rebac-admin-backend/v1/error.go index f81224ccc..58ccdd6bc 100644 --- a/rebac-admin-backend/v1/error.go +++ b/rebac-admin-backend/v1/error.go @@ -46,6 +46,14 @@ func NewNotFoundError(message string) error { } } +// NewMissingRequestBodyError returns an error instance that represents a missing request body error. +func NewMissingRequestBodyError(message string) error { + return &errorWithStatus{ + status: http.StatusBadRequest, + message: fmt.Sprintf("missing request body: %s", message), + } +} + // NewValidationError returns an error instance that represents an input validation error. func NewValidationError(message string) error { return &errorWithStatus{ @@ -54,6 +62,14 @@ func NewValidationError(message string) error { } } +// NewRequestBodyValidationError returns an error instance that represents a request body validation error. +func NewRequestBodyValidationError(message string) error { + return &errorWithStatus{ + status: http.StatusBadRequest, + message: fmt.Sprintf("invalid request body: %s", message), + } +} + // NewUnknownError returns an error instance that represents an unknown internal error. func NewUnknownError(message string) error { return &errorWithStatus{ From e358b90e987f4d956a286370625d810b66ca3cf6 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 20 Mar 2024 12:22:49 +0000 Subject: [PATCH 03/16] fix: apply `handlerWithValidation` at composition Signed-off-by: Babak K. Shandiz --- rebac-admin-backend/v1/core.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rebac-admin-backend/v1/core.go b/rebac-admin-backend/v1/core.go index 6d03f3a96..4b219a358 100644 --- a/rebac-admin-backend/v1/core.go +++ b/rebac-admin-backend/v1/core.go @@ -99,7 +99,8 @@ func newReBACAdminBackendWithService(params ReBACAdminBackendParams, handler res // Handler returns HTTP handlers implementing the ReBAC Admin OpenAPI spec. func (b *ReBACAdminBackend) Handler(baseURL string) http.Handler { baseURL, _ = strings.CutSuffix(baseURL, "/") - return resources.HandlerWithOptions(b.handler, resources.ChiServerOptions{ + h := newHandlerWithValidation(b.handler) + return resources.HandlerWithOptions(h, resources.ChiServerOptions{ BaseURL: baseURL + "/v1", ErrorHandlerFunc: func(w http.ResponseWriter, _ *http.Request, err error) { writeErrorResponse(w, err) From 14379703b04e607d5c64317d6e8eae6180601b46 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 20 Mar 2024 12:29:19 +0000 Subject: [PATCH 04/16] fix: remove JSON parsing from handler Signed-off-by: Babak K. Shandiz --- rebac-admin-backend/v1/groups.go | 56 ++++++++++++-------------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/rebac-admin-backend/v1/groups.go b/rebac-admin-backend/v1/groups.go index 67ee6f2be..fec93fee5 100644 --- a/rebac-admin-backend/v1/groups.go +++ b/rebac-admin-backend/v1/groups.go @@ -4,7 +4,6 @@ package v1 import ( - "encoding/json" "net/http" "github.com/canonical/identity-platform-admin-ui/rebac-admin-backend/v1/resources" @@ -36,15 +35,13 @@ func (h handler) GetGroups(w http.ResponseWriter, req *http.Request, params reso func (h handler) PostGroups(w http.ResponseWriter, req *http.Request) { ctx := req.Context() - group := new(resources.Group) - defer req.Body.Close() - - if err := json.NewDecoder(req.Body).Decode(group); err != nil { - writeErrorResponse(w, NewValidationError("request doesn't match the expected schema")) + body, err := getRequestBodyFromContext[resources.Group](req.Context()) + if err != nil { + writeErrorResponse(w, err) return } - group, err := h.Groups.CreateGroup(ctx, group) + group, err := h.Groups.CreateGroup(ctx, body) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) return @@ -86,20 +83,13 @@ func (h handler) GetGroupsItem(w http.ResponseWriter, req *http.Request, id stri func (h handler) PutGroupsItem(w http.ResponseWriter, req *http.Request, id string) { ctx := req.Context() - group := new(resources.Group) - defer req.Body.Close() - - if err := json.NewDecoder(req.Body).Decode(group); err != nil { - writeErrorResponse(w, NewValidationError("request doesn't match the expected schema")) - return - } - - if id != *group.Id { - writeErrorResponse(w, NewValidationError("group ID from path does not match the Group object")) + body, err := getRequestBodyFromContext[resources.Group](req.Context()) + if err != nil { + writeErrorResponse(w, err) return } - group, err := h.Groups.UpdateGroup(ctx, group) + group, err := h.Groups.UpdateGroup(ctx, body) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) return @@ -134,15 +124,13 @@ func (h handler) GetGroupsItemEntitlements(w http.ResponseWriter, req *http.Requ func (h handler) PatchGroupsItemEntitlements(w http.ResponseWriter, req *http.Request, id string) { ctx := req.Context() - patchesRequest := new(resources.GroupEntitlementsPatchRequestBody) - defer req.Body.Close() - - if err := json.NewDecoder(req.Body).Decode(patchesRequest); err != nil { - writeErrorResponse(w, NewValidationError("request doesn't match the expected schema")) + body, err := getRequestBodyFromContext[resources.GroupEntitlementsPatchRequestBody](req.Context()) + if err != nil { + writeErrorResponse(w, err) return } - _, err := h.Groups.PatchGroupEntitlements(ctx, id, patchesRequest.Patches) + _, err = h.Groups.PatchGroupEntitlements(ctx, id, body.Patches) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) return @@ -177,15 +165,13 @@ func (h handler) GetGroupsItemIdentities(w http.ResponseWriter, req *http.Reques func (h handler) PatchGroupsItemIdentities(w http.ResponseWriter, req *http.Request, id string) { ctx := req.Context() - patchesRequest := new(resources.GroupIdentitiesPatchRequestBody) - defer req.Body.Close() - - if err := json.NewDecoder(req.Body).Decode(patchesRequest); err != nil { - writeErrorResponse(w, NewValidationError("request doesn't match the expected schema")) + body, err := getRequestBodyFromContext[resources.GroupIdentitiesPatchRequestBody](req.Context()) + if err != nil { + writeErrorResponse(w, err) return } - _, err := h.Groups.PatchGroupIdentities(ctx, id, patchesRequest.Patches) + _, err = h.Groups.PatchGroupIdentities(ctx, id, body.Patches) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) return @@ -220,15 +206,13 @@ func (h handler) GetGroupsItemRoles(w http.ResponseWriter, req *http.Request, id func (h handler) PatchGroupsItemRoles(w http.ResponseWriter, req *http.Request, id string) { ctx := req.Context() - patchesRequest := new(resources.GroupRolesPatchRequestBody) - defer req.Body.Close() - - if err := json.NewDecoder(req.Body).Decode(patchesRequest); err != nil { - writeErrorResponse(w, NewValidationError("request doesn't match the expected schema")) + body, err := getRequestBodyFromContext[resources.GroupRolesPatchRequestBody](req.Context()) + if err != nil { + writeErrorResponse(w, err) return } - _, err := h.Groups.PatchGroupRoles(ctx, id, patchesRequest.Patches) + _, err = h.Groups.PatchGroupRoles(ctx, id, body.Patches) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) return From 4fd1e40e2f9dbe3b2d03a8d36336448e943281df Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 20 Mar 2024 12:29:38 +0000 Subject: [PATCH 05/16] test: update tests Signed-off-by: Babak K. Shandiz --- rebac-admin-backend/v1/groups_test.go | 124 +++++--------------------- 1 file changed, 21 insertions(+), 103 deletions(-) diff --git a/rebac-admin-backend/v1/groups_test.go b/rebac-admin-backend/v1/groups_test.go index 530a5fcb9..636ad1d48 100644 --- a/rebac-admin-backend/v1/groups_test.go +++ b/rebac-admin-backend/v1/groups_test.go @@ -4,7 +4,6 @@ package v1 import ( - "bytes" "encoding/json" "errors" "fmt" @@ -102,8 +101,7 @@ func TestHandler_Groups_Success(t *testing.T) { Return(&mockGroupObject, nil) }, triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - groupBody, _ := json.Marshal(mockGroupObject) - mockRequest := httptest.NewRequest(http.MethodPost, "/groups", bytes.NewReader(groupBody)) + mockRequest := newTestRequest(http.MethodPost, "/groups", &mockGroupObject) h.PostGroups(w, mockRequest) }, expectedStatus: http.StatusCreated, @@ -131,8 +129,7 @@ func TestHandler_Groups_Success(t *testing.T) { Return(&mockGroupObject, nil) }, triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - groupBody, _ := json.Marshal(mockGroupObject) - mockRequest := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/groups/%s", mockGroupId), bytes.NewReader(groupBody)) + mockRequest := newTestRequest(http.MethodPut, fmt.Sprintf("/groups/%s", mockGroupId), &mockGroupObject) h.PutGroupsItem(w, mockRequest, mockGroupId) }, expectedStatus: http.StatusOK, @@ -176,15 +173,15 @@ func TestHandler_Groups_Success(t *testing.T) { Return(true, nil) }, triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - patchesBody, _ := json.Marshal(resources.GroupIdentitiesPatchRequestBody{ + patches := resources.GroupIdentitiesPatchRequestBody{ Patches: []resources.GroupIdentitiesPatchItem{ { Identity: *mockIdentities.Data[0].Id, Op: "add", }, }, - }) - mockRequest := httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/identities", mockGroupId), bytes.NewReader(patchesBody)) + } + mockRequest := newTestRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/identities", mockGroupId), &patches) h.PatchGroupsItemIdentities(w, mockRequest, mockGroupId) }, expectedStatus: http.StatusOK, @@ -214,15 +211,15 @@ func TestHandler_Groups_Success(t *testing.T) { Return(true, nil) }, triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - patchesBody, _ := json.Marshal(resources.GroupRolesPatchRequestBody{ + patches := resources.GroupRolesPatchRequestBody{ Patches: []resources.GroupRolesPatchItem{ { Role: *mockRoles.Data[0].Id, Op: "add", }, }, - }) - mockRequest := httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/roles", mockGroupId), bytes.NewReader(patchesBody)) + } + mockRequest := newTestRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/roles", mockGroupId), &patches) h.PatchGroupsItemRoles(w, mockRequest, mockGroupId) }, expectedStatus: http.StatusOK, @@ -252,15 +249,15 @@ func TestHandler_Groups_Success(t *testing.T) { Return(true, nil) }, triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - patchesBody, _ := json.Marshal(resources.GroupEntitlementsPatchRequestBody{ + patches := resources.GroupEntitlementsPatchRequestBody{ Patches: []resources.GroupEntitlementsPatchItem{ { Entitlement: mockEntitlements.Data[0], Op: "add", }, }, - }) - mockRequest := httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/entitlements", mockGroupId), bytes.NewReader(patchesBody)) + } + mockRequest := newTestRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/entitlements", mockGroupId), &patches) h.PatchGroupsItemEntitlements(w, mockRequest, mockGroupId) }, expectedStatus: http.StatusOK, @@ -298,85 +295,6 @@ func TestHandler_Groups_Success(t *testing.T) { } -func TestHandler_Groups_ValidationErrors(t *testing.T) { - c := qt.New(t) - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - // need a value that is not a struct to trigger Decode error - mockInvalidRequestBody := true - - invalidRequestBody, _ := json.Marshal(mockInvalidRequestBody) - - type EndpointTest struct { - name string - triggerFunc func(h handler, w *httptest.ResponseRecorder) - } - - tests := []EndpointTest{ - { - name: "TestPostGroupsFailureInvalidRequest", - triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - req := httptest.NewRequest(http.MethodPost, "/groups", bytes.NewReader(invalidRequestBody)) - h.PostGroups(w, req) - }, - }, - { - name: "TestPutGroupsFailureInvalidRequest", - triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/groups/%s", mockGroupId), bytes.NewReader(invalidRequestBody)) - h.PutGroupsItem(w, req, mockGroupId) - }, - }, - { - name: "TestPatchGroupsIdentitiesFailureInvalidRequest", - triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - req := httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/identities", mockGroupId), bytes.NewReader(invalidRequestBody)) - h.PatchGroupsItemIdentities(w, req, mockGroupId) - }, - }, - { - name: "TestPatchGroupsRolesFailureInvalidRequest", - triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - req := httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/roles", mockGroupId), bytes.NewReader(invalidRequestBody)) - h.PatchGroupsItemRoles(w, req, mockGroupId) - }, - }, - { - name: "TestPatchGroupsEntitlementsFailureInvalidRequest", - triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - req := httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/entitlements", mockGroupId), bytes.NewReader(invalidRequestBody)) - h.PatchGroupsItemEntitlements(w, req, mockGroupId) - }, - }, - } - for _, test := range tests { - tt := test - c.Run(tt.name, func(c *qt.C) { - mockWriter := httptest.NewRecorder() - sut := handler{} - - tt.triggerFunc(sut, mockWriter) - - result := mockWriter.Result() - defer result.Body.Close() - - c.Assert(result.StatusCode, qt.Equals, http.StatusBadRequest) - - data, err := io.ReadAll(result.Body) - c.Assert(err, qt.IsNil) - - response := new(resources.Response) - - err = json.Unmarshal(data, response) - c.Assert(err, qt.IsNil) - - c.Assert(response.Status, qt.Equals, http.StatusBadRequest) - c.Assert(response.Message, qt.Equals, "Bad Request: request doesn't match the expected schema") - }) - } -} - func TestHandler_Groups_ServiceBackendFailures(t *testing.T) { c := qt.New(t) ctrl := gomock.NewController(t) @@ -413,8 +331,8 @@ func TestHandler_Groups_ServiceBackendFailures(t *testing.T) { mockService.EXPECT().CreateGroup(gomock.Any(), gomock.Any()).Return(nil, mockError) }, triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - group, _ := json.Marshal(&resources.Group{}) - request := httptest.NewRequest(http.MethodPost, "/groups", bytes.NewReader(group)) + mockGroup := &resources.Group{} + request := newTestRequest(http.MethodPost, "/groups", mockGroup) h.PostGroups(w, request) }, }, @@ -444,8 +362,8 @@ func TestHandler_Groups_ServiceBackendFailures(t *testing.T) { mockService.EXPECT().UpdateGroup(gomock.Any(), gomock.Any()).Return(nil, mockError) }, triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - group, _ := json.Marshal(&resources.Group{Id: &mockGroupId}) - request := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/groups/%s", mockGroupId), bytes.NewReader(group)) + mockGroup := &resources.Group{Id: &mockGroupId} + request := newTestRequest(http.MethodPut, fmt.Sprintf("/groups/%s", mockGroupId), mockGroup) h.PutGroupsItem(w, request, mockGroupId) }, }, @@ -466,8 +384,8 @@ func TestHandler_Groups_ServiceBackendFailures(t *testing.T) { mockService.EXPECT().PatchGroupIdentities(gomock.Any(), gomock.Any(), gomock.Any()).Return(false, mockError) }, triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - patches, _ := json.Marshal(&resources.GroupIdentitiesPatchRequestBody{}) - request := httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/identities", mockGroupId), bytes.NewReader(patches)) + patches := &resources.GroupIdentitiesPatchRequestBody{} + request := newTestRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/identities", mockGroupId), patches) h.PatchGroupsItemIdentities(w, request, mockGroupId) }, }, @@ -488,8 +406,8 @@ func TestHandler_Groups_ServiceBackendFailures(t *testing.T) { mockService.EXPECT().PatchGroupRoles(gomock.Any(), gomock.Any(), gomock.Any()).Return(false, mockError) }, triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - patches, _ := json.Marshal(&resources.GroupRolesPatchRequestBody{}) - request := httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/roles", mockGroupId), bytes.NewReader(patches)) + patches := &resources.GroupRolesPatchRequestBody{} + request := newTestRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/roles", mockGroupId), patches) h.PatchGroupsItemRoles(w, request, mockGroupId) }, }, @@ -510,8 +428,8 @@ func TestHandler_Groups_ServiceBackendFailures(t *testing.T) { mockService.EXPECT().PatchGroupEntitlements(gomock.Any(), gomock.Any(), gomock.Any()).Return(false, mockError) }, triggerFunc: func(h handler, w *httptest.ResponseRecorder) { - patches, _ := json.Marshal(&resources.GroupEntitlementsPatchRequestBody{}) - request := httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/entitlements", mockGroupId), bytes.NewReader(patches)) + patches := &resources.GroupEntitlementsPatchRequestBody{} + request := newTestRequest(http.MethodPatch, fmt.Sprintf("/groups/%s/entitlements", mockGroupId), patches) h.PatchGroupsItemEntitlements(w, request, mockGroupId) }, }, From 9169348b492e7de8ee4b293f10848ea368760fb7 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 20 Mar 2024 12:30:20 +0000 Subject: [PATCH 06/16] chore: run `go mod tidy` Signed-off-by: Babak K. Shandiz --- go.mod | 12 +++++++++--- go.sum | 26 ++++++++++++++++++++------ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 3cf814531..bc7c3e791 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/getkin/kin-openapi v0.123.0 github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/cors v1.2.1 + github.com/go-playground/validator/v10 v10.19.0 github.com/google/uuid v1.5.0 github.com/kelseyhightower/envconfig v1.4.0 github.com/oapi-codegen/runtime v1.1.1 @@ -49,11 +50,14 @@ require ( github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.8 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic-models v0.6.8 // indirect @@ -69,6 +73,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -86,12 +91,13 @@ require ( go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.10.0 // indirect + golang.org/x/crypto v0.19.0 // indirect golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect - golang.org/x/net v0.19.0 // indirect + golang.org/x/net v0.21.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/appengine v1.6.8 // indirect diff --git a/go.sum b/go.sum index bd8b243bc..6a503f17a 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,8 @@ github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBd github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8= github.com/getkin/kin-openapi v0.123.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= @@ -43,6 +45,14 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= +github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= @@ -96,6 +106,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= @@ -197,6 +209,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -208,8 +222,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -226,12 +240,12 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= From fb477d175cf15b35f76a3fdfe36779422f2047c9 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 20 Mar 2024 12:45:07 +0000 Subject: [PATCH 07/16] fix: fix lint issues Signed-off-by: Babak K. Shandiz --- rebac-admin-backend/v1/groups_validation_test.go | 2 +- rebac-admin-backend/v1/response.go | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/rebac-admin-backend/v1/groups_validation_test.go b/rebac-admin-backend/v1/groups_validation_test.go index 093e4500a..112391387 100644 --- a/rebac-admin-backend/v1/groups_validation_test.go +++ b/rebac-admin-backend/v1/groups_validation_test.go @@ -26,7 +26,7 @@ func TestHandlerWithValidation_Groups(t *testing.T) { writeResponse := func(w http.ResponseWriter, status int, body any) { raw, _ := json.Marshal(body) w.WriteHeader(status) - w.Write(raw) + _, _ = w.Write(raw) } validEntitlement := resources.EntityEntitlement{ diff --git a/rebac-admin-backend/v1/response.go b/rebac-admin-backend/v1/response.go index 5b12c378d..c5e79f947 100644 --- a/rebac-admin-backend/v1/response.go +++ b/rebac-admin-backend/v1/response.go @@ -17,13 +17,19 @@ func writeErrorResponse(w http.ResponseWriter, err error) { body, err := json.Marshal(resp) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("unexpected marshalling error")) + if _, err := w.Write([]byte("unexpected marshalling error")); err != nil { + // TODO(CSS-7642): we should log the error. + return + } return } setJSONContentTypeHeader(w) w.WriteHeader(int(resp.Status)) - w.Write(body) + if _, err := w.Write(body); err != nil { + // TODO(CSS-7642): we should log the error. + return + } } // mapErrorResponse returns a Response instance filled with the given error. @@ -60,7 +66,10 @@ func writeResponse(w http.ResponseWriter, status int, responseObject interface{} setJSONContentTypeHeader(w) w.WriteHeader(status) - w.Write(data) + if _, err := w.Write(data); err != nil { + // TODO(CSS-7642): we should log the error. + return + } } // mapServiceErrorResponse maps errors thrown by services to the designated From f53173165c7c864e9af42c3f8e5d985e0eeade2f Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 20 Mar 2024 13:08:28 +0000 Subject: [PATCH 08/16] style: improve variable names Signed-off-by: Babak K. Shandiz --- rebac-admin-backend/v1/groups.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rebac-admin-backend/v1/groups.go b/rebac-admin-backend/v1/groups.go index fec93fee5..5a6376c62 100644 --- a/rebac-admin-backend/v1/groups.go +++ b/rebac-admin-backend/v1/groups.go @@ -35,19 +35,19 @@ func (h handler) GetGroups(w http.ResponseWriter, req *http.Request, params reso func (h handler) PostGroups(w http.ResponseWriter, req *http.Request) { ctx := req.Context() - body, err := getRequestBodyFromContext[resources.Group](req.Context()) + group, err := getRequestBodyFromContext[resources.Group](req.Context()) if err != nil { writeErrorResponse(w, err) return } - group, err := h.Groups.CreateGroup(ctx, body) + result, err := h.Groups.CreateGroup(ctx, group) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) return } - writeResponse(w, http.StatusCreated, group) + writeResponse(w, http.StatusCreated, result) } // DeleteGroupsItem deletes the specified group identified by the provided ID. @@ -83,19 +83,19 @@ func (h handler) GetGroupsItem(w http.ResponseWriter, req *http.Request, id stri func (h handler) PutGroupsItem(w http.ResponseWriter, req *http.Request, id string) { ctx := req.Context() - body, err := getRequestBodyFromContext[resources.Group](req.Context()) + group, err := getRequestBodyFromContext[resources.Group](req.Context()) if err != nil { writeErrorResponse(w, err) return } - group, err := h.Groups.UpdateGroup(ctx, body) + result, err := h.Groups.UpdateGroup(ctx, group) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) return } - writeResponse(w, http.StatusOK, group) + writeResponse(w, http.StatusOK, result) } // GetGroupsItemEntitlements returns the list of entitlements for a group identified by the provided ID. @@ -124,13 +124,13 @@ func (h handler) GetGroupsItemEntitlements(w http.ResponseWriter, req *http.Requ func (h handler) PatchGroupsItemEntitlements(w http.ResponseWriter, req *http.Request, id string) { ctx := req.Context() - body, err := getRequestBodyFromContext[resources.GroupEntitlementsPatchRequestBody](req.Context()) + groupEntitlements, err := getRequestBodyFromContext[resources.GroupEntitlementsPatchRequestBody](req.Context()) if err != nil { writeErrorResponse(w, err) return } - _, err = h.Groups.PatchGroupEntitlements(ctx, id, body.Patches) + _, err = h.Groups.PatchGroupEntitlements(ctx, id, groupEntitlements.Patches) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) return @@ -165,13 +165,13 @@ func (h handler) GetGroupsItemIdentities(w http.ResponseWriter, req *http.Reques func (h handler) PatchGroupsItemIdentities(w http.ResponseWriter, req *http.Request, id string) { ctx := req.Context() - body, err := getRequestBodyFromContext[resources.GroupIdentitiesPatchRequestBody](req.Context()) + groupIdentities, err := getRequestBodyFromContext[resources.GroupIdentitiesPatchRequestBody](req.Context()) if err != nil { writeErrorResponse(w, err) return } - _, err = h.Groups.PatchGroupIdentities(ctx, id, body.Patches) + _, err = h.Groups.PatchGroupIdentities(ctx, id, groupIdentities.Patches) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) return @@ -206,13 +206,13 @@ func (h handler) GetGroupsItemRoles(w http.ResponseWriter, req *http.Request, id func (h handler) PatchGroupsItemRoles(w http.ResponseWriter, req *http.Request, id string) { ctx := req.Context() - body, err := getRequestBodyFromContext[resources.GroupRolesPatchRequestBody](req.Context()) + groupRoles, err := getRequestBodyFromContext[resources.GroupRolesPatchRequestBody](req.Context()) if err != nil { writeErrorResponse(w, err) return } - _, err = h.Groups.PatchGroupRoles(ctx, id, body.Patches) + _, err = h.Groups.PatchGroupRoles(ctx, id, groupRoles.Patches) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) return From 7f8f398fe2aaade72b4eb74f364bfe8dc4bf6776 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 20 Mar 2024 14:34:14 +0000 Subject: [PATCH 09/16] fix: remove unused validator Signed-off-by: Babak K. Shandiz --- rebac-admin-backend/v1/validation.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/rebac-admin-backend/v1/validation.go b/rebac-admin-backend/v1/validation.go index 270310efd..758dc9ef7 100644 --- a/rebac-admin-backend/v1/validation.go +++ b/rebac-admin-backend/v1/validation.go @@ -9,7 +9,6 @@ import ( "net/http" "github.com/canonical/identity-platform-admin-ui/rebac-admin-backend/v1/resources" - "github.com/go-playground/validator/v10" ) // handlerWithValidation decorates a given handler with validation logic. The @@ -34,7 +33,6 @@ type requestBodyContextKey struct{} // getRequestBodyFromContext fetches request body from given context. func getRequestBodyFromContext[T any](ctx context.Context) (*T, error) { - validator.New() if body, ok := ctx.Value(requestBodyContextKey{}).(*T); ok { return body, nil } From 5712671d1c56b798a3d173e9ce6b1bf356f47a1f Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 20 Mar 2024 14:34:28 +0000 Subject: [PATCH 10/16] style: sort imports Signed-off-by: Babak K. Shandiz --- rebac-admin-backend/v1/groups_validation_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rebac-admin-backend/v1/groups_validation_test.go b/rebac-admin-backend/v1/groups_validation_test.go index 112391387..8100a96dc 100644 --- a/rebac-admin-backend/v1/groups_validation_test.go +++ b/rebac-admin-backend/v1/groups_validation_test.go @@ -11,9 +11,10 @@ import ( "net/http/httptest" "testing" - "github.com/canonical/identity-platform-admin-ui/rebac-admin-backend/v1/resources" qt "github.com/frankban/quicktest" "go.uber.org/mock/gomock" + + "github.com/canonical/identity-platform-admin-ui/rebac-admin-backend/v1/resources" ) //go:generate mockgen -package resources -destination ./resources/mock_generated_server.go -source=./resources/generated_server.go From f422948cc85af08078ac8ef184db164d0fa494ab Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 21 Mar 2024 11:48:46 +0000 Subject: [PATCH 11/16] fix: embed handler struct Signed-off-by: Babak K. Shandiz --- rebac-admin-backend/v1/groups_validation.go | 34 +--- rebac-admin-backend/v1/validation.go | 180 +------------------- 2 files changed, 10 insertions(+), 204 deletions(-) diff --git a/rebac-admin-backend/v1/groups_validation.go b/rebac-admin-backend/v1/groups_validation.go index fa6375b44..134e33997 100644 --- a/rebac-admin-backend/v1/groups_validation.go +++ b/rebac-admin-backend/v1/groups_validation.go @@ -9,11 +9,6 @@ import ( "github.com/canonical/identity-platform-admin-ui/rebac-admin-backend/v1/resources" ) -// GetGroups validates request body for the GetGroups method and delegates to the underlying handler. -func (v handlerWithValidation) GetGroups(w http.ResponseWriter, r *http.Request, params resources.GetGroupsParams) { - v.handler.GetGroups(w, r, params) -} - // PostGroups validates request body for the PostGroups method and delegates to the underlying handler. func (v handlerWithValidation) PostGroups(w http.ResponseWriter, r *http.Request) { setRequestBodyInContext[resources.Group](w, r, func(w http.ResponseWriter, r *http.Request, body *resources.Group) { @@ -21,18 +16,18 @@ func (v handlerWithValidation) PostGroups(w http.ResponseWriter, r *http.Request writeErrorResponse(w, err) return } - v.handler.PostGroups(w, r) + v.ServerInterface.PostGroups(w, r) }) } // DeleteGroupsItem validates request body for the DeleteGroupsItem method and delegates to the underlying handler. func (v handlerWithValidation) DeleteGroupsItem(w http.ResponseWriter, r *http.Request, id string) { - v.handler.DeleteGroupsItem(w, r, id) + v.ServerInterface.DeleteGroupsItem(w, r, id) } // GetGroupsItem validates request body for the GetGroupsItem method and delegates to the underlying handler. func (v handlerWithValidation) GetGroupsItem(w http.ResponseWriter, r *http.Request, id string) { - v.handler.GetGroupsItem(w, r, id) + v.ServerInterface.GetGroupsItem(w, r, id) } // PutGroupsItem validates request body for the PutGroupsItem method and delegates to the underlying handler. @@ -46,15 +41,10 @@ func (v handlerWithValidation) PutGroupsItem(w http.ResponseWriter, r *http.Requ writeErrorResponse(w, NewRequestBodyValidationError("group ID from path does not match the Group object")) return } - v.handler.PutGroupsItem(w, r, id) + v.ServerInterface.PutGroupsItem(w, r, id) }) } -// GetGroupsItemEntitlements validates request body for the GetGroupsItemEntitlements method and delegates to the underlying handler. -func (v handlerWithValidation) GetGroupsItemEntitlements(w http.ResponseWriter, r *http.Request, id string, params resources.GetGroupsItemEntitlementsParams) { - v.handler.GetGroupsItemEntitlements(w, r, id, params) -} - // PatchGroupsItemEntitlements validates request body for the PatchGroupsItemEntitlements method and delegates to the underlying handler. func (v handlerWithValidation) PatchGroupsItemEntitlements(w http.ResponseWriter, r *http.Request, id string) { setRequestBodyInContext[resources.GroupEntitlementsPatchRequestBody](w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupEntitlementsPatchRequestBody) { @@ -62,15 +52,10 @@ func (v handlerWithValidation) PatchGroupsItemEntitlements(w http.ResponseWriter writeErrorResponse(w, err) return } - v.handler.PatchGroupsItemEntitlements(w, r, id) + v.ServerInterface.PatchGroupsItemEntitlements(w, r, id) }) } -// GetGroupsItemIdentities validates request body for the GetGroupsItemIdentities method and delegates to the underlying handler. -func (v handlerWithValidation) GetGroupsItemIdentities(w http.ResponseWriter, r *http.Request, id string, params resources.GetGroupsItemIdentitiesParams) { - v.handler.GetGroupsItemIdentities(w, r, id, params) -} - // PatchGroupsItemIdentities validates request body for the PatchGroupsItemIdentities method and delegates to the underlying handler. func (v handlerWithValidation) PatchGroupsItemIdentities(w http.ResponseWriter, r *http.Request, id string) { setRequestBodyInContext[resources.GroupIdentitiesPatchRequestBody](w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupIdentitiesPatchRequestBody) { @@ -78,15 +63,10 @@ func (v handlerWithValidation) PatchGroupsItemIdentities(w http.ResponseWriter, writeErrorResponse(w, err) return } - v.handler.PatchGroupsItemIdentities(w, r, id) + v.ServerInterface.PatchGroupsItemIdentities(w, r, id) }) } -// GetGroupsItemRoles validates request body for the GetGroupsItemRoles method and delegates to the underlying handler. -func (v handlerWithValidation) GetGroupsItemRoles(w http.ResponseWriter, r *http.Request, id string, params resources.GetGroupsItemRolesParams) { - v.handler.GetGroupsItemRoles(w, r, id, params) -} - // PatchGroupsItemRoles validates request body for the PatchGroupsItemRoles method and delegates to the underlying handler. func (v handlerWithValidation) PatchGroupsItemRoles(w http.ResponseWriter, r *http.Request, id string) { setRequestBodyInContext[resources.GroupRolesPatchRequestBody](w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupRolesPatchRequestBody) { @@ -94,6 +74,6 @@ func (v handlerWithValidation) PatchGroupsItemRoles(w http.ResponseWriter, r *ht writeErrorResponse(w, err) return } - v.handler.PatchGroupsItemRoles(w, r, id) + v.ServerInterface.PatchGroupsItemRoles(w, r, id) }) } diff --git a/rebac-admin-backend/v1/validation.go b/rebac-admin-backend/v1/validation.go index 758dc9ef7..721b61a4a 100644 --- a/rebac-admin-backend/v1/validation.go +++ b/rebac-admin-backend/v1/validation.go @@ -15,16 +15,14 @@ import ( // request body is parsed into a safely-typed value and passed to the handler // via context. type handlerWithValidation struct { - // TODO(babakks) remove the embedded struct. - resources.Unimplemented - - handler resources.ServerInterface + // Wrapped/decorated handler + resources.ServerInterface } // newHandlerWithValidation returns a new instance of the validationHandlerDecorator struct. func newHandlerWithValidation(handler resources.ServerInterface) *handlerWithValidation { return &handlerWithValidation{ - handler: handler, + ServerInterface: handler, } } @@ -69,175 +67,3 @@ func setRequestBodyInContext[T any](w http.ResponseWriter, r *http.Request, f fu } f(w, newRequestWithBodyInContext(r, body), body) } - -// GetIdentityProviders validates request body for the GetIdentityProviders method and delegates to the underlying handler. -func (v handlerWithValidation) GetIdentityProviders(w http.ResponseWriter, r *http.Request, params resources.GetIdentityProvidersParams) { - v.handler.GetIdentityProviders(w, r, params) -} - -// PostIdentityProviders validates request body for the PostIdentityProviders method and delegates to the underlying handler. -func (v handlerWithValidation) PostIdentityProviders(w http.ResponseWriter, r *http.Request) { - body := &struct{}{} - v.handler.PostIdentityProviders(w, newRequestWithBodyInContext(r, body)) -} - -// GetAvailableIdentityProviders validates request body for the GetAvailableIdentityProviders method and delegates to the underlying handler. -func (v handlerWithValidation) GetAvailableIdentityProviders(w http.ResponseWriter, r *http.Request, params resources.GetAvailableIdentityProvidersParams) { - body := &struct{}{} - v.handler.GetAvailableIdentityProviders(w, newRequestWithBodyInContext(r, body), params) -} - -// DeleteIdentityProvidersItem validates request body for the DeleteIdentityProvidersItem method and delegates to the underlying handler. -func (v handlerWithValidation) DeleteIdentityProvidersItem(w http.ResponseWriter, r *http.Request, id string) { - body := &struct{}{} - v.handler.DeleteIdentityProvidersItem(w, newRequestWithBodyInContext(r, body), id) -} - -// GetIdentityProvidersItem validates request body for the GetIdentityProvidersItem method and delegates to the underlying handler. -func (v handlerWithValidation) GetIdentityProvidersItem(w http.ResponseWriter, r *http.Request, id string) { - body := &struct{}{} - v.handler.GetIdentityProvidersItem(w, newRequestWithBodyInContext(r, body), id) -} - -// PutIdentityProvidersItem validates request body for the PutIdentityProvidersItem method and delegates to the underlying handler. -func (v handlerWithValidation) PutIdentityProvidersItem(w http.ResponseWriter, r *http.Request, id string) { - body := &struct{}{} - v.handler.PutIdentityProvidersItem(w, newRequestWithBodyInContext(r, body), id) -} - -// GetCapabilities validates request body for the GetCapabilities method and delegates to the underlying handler. -func (v handlerWithValidation) GetCapabilities(w http.ResponseWriter, r *http.Request) { - body := &struct{}{} - v.handler.GetCapabilities(w, newRequestWithBodyInContext(r, body)) -} - -// GetEntitlements validates request body for the GetEntitlements method and delegates to the underlying handler. -func (v handlerWithValidation) GetEntitlements(w http.ResponseWriter, r *http.Request, params resources.GetEntitlementsParams) { - body := &struct{}{} - v.handler.GetEntitlements(w, newRequestWithBodyInContext(r, body), params) -} - -// GetRawEntitlements validates request body for the GetRawEntitlements method and delegates to the underlying handler. -func (v handlerWithValidation) GetRawEntitlements(w http.ResponseWriter, r *http.Request) { - body := &struct{}{} - v.handler.GetRawEntitlements(w, newRequestWithBodyInContext(r, body)) -} - -// GetIdentities validates request body for the GetIdentities method and delegates to the underlying handler. -func (v handlerWithValidation) GetIdentities(w http.ResponseWriter, r *http.Request, params resources.GetIdentitiesParams) { - body := &struct{}{} - v.handler.GetIdentities(w, newRequestWithBodyInContext(r, body), params) -} - -// PostIdentities validates request body for the PostIdentities method and delegates to the underlying handler. -func (v handlerWithValidation) PostIdentities(w http.ResponseWriter, r *http.Request) { - body := &struct{}{} - v.handler.PostIdentities(w, newRequestWithBodyInContext(r, body)) -} - -// DeleteIdentitiesItem validates request body for the DeleteIdentitiesItem method and delegates to the underlying handler. -func (v handlerWithValidation) DeleteIdentitiesItem(w http.ResponseWriter, r *http.Request, id string) { - body := &struct{}{} - v.handler.DeleteIdentitiesItem(w, newRequestWithBodyInContext(r, body), id) -} - -// GetIdentitiesItem validates request body for the GetIdentitiesItem method and delegates to the underlying handler. -func (v handlerWithValidation) GetIdentitiesItem(w http.ResponseWriter, r *http.Request, id string) { - body := &struct{}{} - v.handler.GetIdentitiesItem(w, newRequestWithBodyInContext(r, body), id) -} - -// PutIdentitiesItem validates request body for the PutIdentitiesItem method and delegates to the underlying handler. -func (v handlerWithValidation) PutIdentitiesItem(w http.ResponseWriter, r *http.Request, id string) { - body := &struct{}{} - v.handler.PutIdentitiesItem(w, newRequestWithBodyInContext(r, body), id) -} - -// GetIdentitiesItemEntitlements validates request body for the GetIdentitiesItemEntitlements method and delegates to the underlying handler. -func (v handlerWithValidation) GetIdentitiesItemEntitlements(w http.ResponseWriter, r *http.Request, id string, params resources.GetIdentitiesItemEntitlementsParams) { - body := &struct{}{} - v.handler.GetIdentitiesItemEntitlements(w, newRequestWithBodyInContext(r, body), id, params) -} - -// PatchIdentitiesItemEntitlements validates request body for the PatchIdentitiesItemEntitlements method and delegates to the underlying handler. -func (v handlerWithValidation) PatchIdentitiesItemEntitlements(w http.ResponseWriter, r *http.Request, id string) { - body := &struct{}{} - v.handler.PatchIdentitiesItemEntitlements(w, newRequestWithBodyInContext(r, body), id) -} - -// GetIdentitiesItemGroups validates request body for the GetIdentitiesItemGroups method and delegates to the underlying handler. -func (v handlerWithValidation) GetIdentitiesItemGroups(w http.ResponseWriter, r *http.Request, id string, params resources.GetIdentitiesItemGroupsParams) { - body := &struct{}{} - v.handler.GetIdentitiesItemGroups(w, newRequestWithBodyInContext(r, body), id, params) -} - -// PatchIdentitiesItemGroups validates request body for the PatchIdentitiesItemGroups method and delegates to the underlying handler. -func (v handlerWithValidation) PatchIdentitiesItemGroups(w http.ResponseWriter, r *http.Request, id string) { - body := &struct{}{} - v.handler.PatchIdentitiesItemGroups(w, newRequestWithBodyInContext(r, body), id) -} - -// GetIdentitiesItemRoles validates request body for the GetIdentitiesItemRoles method and delegates to the underlying handler. -func (v handlerWithValidation) GetIdentitiesItemRoles(w http.ResponseWriter, r *http.Request, id string, params resources.GetIdentitiesItemRolesParams) { - body := &struct{}{} - v.handler.GetIdentitiesItemRoles(w, newRequestWithBodyInContext(r, body), id, params) -} - -// PatchIdentitiesItemRoles validates request body for the PatchIdentitiesItemRoles method and delegates to the underlying handler. -func (v handlerWithValidation) PatchIdentitiesItemRoles(w http.ResponseWriter, r *http.Request, id string) { - body := &struct{}{} - v.handler.PatchIdentitiesItemRoles(w, newRequestWithBodyInContext(r, body), id) -} - -// GetResources validates request body for the GetResources method and delegates to the underlying handler. -func (v handlerWithValidation) GetResources(w http.ResponseWriter, r *http.Request, params resources.GetResourcesParams) { - body := &struct{}{} - v.handler.GetResources(w, newRequestWithBodyInContext(r, body), params) -} - -// GetRoles validates request body for the GetRoles method and delegates to the underlying handler. -func (v handlerWithValidation) GetRoles(w http.ResponseWriter, r *http.Request, params resources.GetRolesParams) { - body := &struct{}{} - v.handler.GetRoles(w, newRequestWithBodyInContext(r, body), params) -} - -// PostRoles validates request body for the PostRoles method and delegates to the underlying handler. -func (v handlerWithValidation) PostRoles(w http.ResponseWriter, r *http.Request) { - body := &struct{}{} - v.handler.PostRoles(w, newRequestWithBodyInContext(r, body)) -} - -// DeleteRolesItem validates request body for the DeleteRolesItem method and delegates to the underlying handler. -func (v handlerWithValidation) DeleteRolesItem(w http.ResponseWriter, r *http.Request, id string) { - body := &struct{}{} - v.handler.DeleteRolesItem(w, newRequestWithBodyInContext(r, body), id) -} - -// GetRolesItem validates request body for the GetRolesItem method and delegates to the underlying handler. -func (v handlerWithValidation) GetRolesItem(w http.ResponseWriter, r *http.Request, id string) { - body := &struct{}{} - v.handler.GetRolesItem(w, newRequestWithBodyInContext(r, body), id) -} - -// PutRolesItem validates request body for the PutRolesItem method and delegates to the underlying handler. -func (v handlerWithValidation) PutRolesItem(w http.ResponseWriter, r *http.Request, id string) { - body := &struct{}{} - v.handler.PutRolesItem(w, newRequestWithBodyInContext(r, body), id) -} - -// GetRolesItemEntitlements validates request body for the GetRolesItemEntitlements method and delegates to the underlying handler. -func (v handlerWithValidation) GetRolesItemEntitlements(w http.ResponseWriter, r *http.Request, id string, params resources.GetRolesItemEntitlementsParams) { - body := &struct{}{} - v.handler.GetRolesItemEntitlements(w, newRequestWithBodyInContext(r, body), id, params) -} - -// PatchRolesItemEntitlements validates request body for the PatchRolesItemEntitlements method and delegates to the underlying handler. -func (v handlerWithValidation) PatchRolesItemEntitlements(w http.ResponseWriter, r *http.Request, id string) { - body := &struct{}{} - v.handler.PatchRolesItemEntitlements(w, newRequestWithBodyInContext(r, body), id) -} - -// SwaggerJson validates request body for the SwaggerJson method and delegates to the underlying handler. -func (v handlerWithValidation) SwaggerJson(w http.ResponseWriter, r *http.Request) { - v.handler.SwaggerJson(w, r) -} From 562f0bcbf2414149c7653951b18f005cd724af14 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 21 Mar 2024 15:25:11 +0000 Subject: [PATCH 12/16] chore: pull latest spec Signed-off-by: Babak K. Shandiz --- .../v1/resources/generated_server.go | 294 ++++++++++++++++++ .../v1/resources/generated_types.go | 236 ++++++++------ 2 files changed, 436 insertions(+), 94 deletions(-) diff --git a/rebac-admin-backend/v1/resources/generated_server.go b/rebac-admin-backend/v1/resources/generated_server.go index bc5c73b31..32209e019 100644 --- a/rebac-admin-backend/v1/resources/generated_server.go +++ b/rebac-admin-backend/v1/resources/generated_server.go @@ -421,6 +421,27 @@ func (siw *ServerInterfaceWrapper) GetIdentityProviders(w http.ResponseWriter, r return } + headers := r.Header + + // ------------- Optional header parameter "Next-Page-Token" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Next-Page-Token")]; found { + var NextPageToken PaginationNextTokenHeader + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Next-Page-Token", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Next-Page-Token", valueList[0], &NextPageToken, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Next-Page-Token", Err: err}) + return + } + + params.NextPageToken = &NextPageToken + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetIdentityProviders(w, r, params) })) @@ -480,6 +501,27 @@ func (siw *ServerInterfaceWrapper) GetAvailableIdentityProviders(w http.Response return } + headers := r.Header + + // ------------- Optional header parameter "Next-Page-Token" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Next-Page-Token")]; found { + var NextPageToken PaginationNextTokenHeader + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Next-Page-Token", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Next-Page-Token", valueList[0], &NextPageToken, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Next-Page-Token", Err: err}) + return + } + + params.NextPageToken = &NextPageToken + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetAvailableIdentityProviders(w, r, params) })) @@ -625,6 +667,27 @@ func (siw *ServerInterfaceWrapper) GetEntitlements(w http.ResponseWriter, r *htt return } + headers := r.Header + + // ------------- Optional header parameter "Next-Page-Token" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Next-Page-Token")]; found { + var NextPageToken PaginationNextTokenHeader + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Next-Page-Token", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Next-Page-Token", valueList[0], &NextPageToken, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Next-Page-Token", Err: err}) + return + } + + params.NextPageToken = &NextPageToken + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetEntitlements(w, r, params) })) @@ -692,6 +755,27 @@ func (siw *ServerInterfaceWrapper) GetGroups(w http.ResponseWriter, r *http.Requ return } + headers := r.Header + + // ------------- Optional header parameter "Next-Page-Token" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Next-Page-Token")]; found { + var NextPageToken PaginationNextTokenHeader + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Next-Page-Token", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Next-Page-Token", valueList[0], &NextPageToken, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Next-Page-Token", Err: err}) + return + } + + params.NextPageToken = &NextPageToken + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetGroups(w, r, params) })) @@ -838,6 +922,27 @@ func (siw *ServerInterfaceWrapper) GetGroupsItemEntitlements(w http.ResponseWrit return } + headers := r.Header + + // ------------- Optional header parameter "Next-Page-Token" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Next-Page-Token")]; found { + var NextPageToken PaginationNextTokenHeader + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Next-Page-Token", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Next-Page-Token", valueList[0], &NextPageToken, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Next-Page-Token", Err: err}) + return + } + + params.NextPageToken = &NextPageToken + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetGroupsItemEntitlements(w, r, id, params) })) @@ -917,6 +1022,27 @@ func (siw *ServerInterfaceWrapper) GetGroupsItemIdentities(w http.ResponseWriter return } + headers := r.Header + + // ------------- Optional header parameter "Next-Page-Token" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Next-Page-Token")]; found { + var NextPageToken PaginationNextTokenHeader + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Next-Page-Token", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Next-Page-Token", valueList[0], &NextPageToken, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Next-Page-Token", Err: err}) + return + } + + params.NextPageToken = &NextPageToken + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetGroupsItemIdentities(w, r, id, params) })) @@ -996,6 +1122,27 @@ func (siw *ServerInterfaceWrapper) GetGroupsItemRoles(w http.ResponseWriter, r * return } + headers := r.Header + + // ------------- Optional header parameter "Next-Page-Token" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Next-Page-Token")]; found { + var NextPageToken PaginationNextTokenHeader + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Next-Page-Token", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Next-Page-Token", valueList[0], &NextPageToken, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Next-Page-Token", Err: err}) + return + } + + params.NextPageToken = &NextPageToken + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetGroupsItemRoles(w, r, id, params) })) @@ -1074,6 +1221,27 @@ func (siw *ServerInterfaceWrapper) GetIdentities(w http.ResponseWriter, r *http. return } + headers := r.Header + + // ------------- Optional header parameter "Next-Page-Token" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Next-Page-Token")]; found { + var NextPageToken PaginationNextTokenHeader + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Next-Page-Token", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Next-Page-Token", valueList[0], &NextPageToken, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Next-Page-Token", Err: err}) + return + } + + params.NextPageToken = &NextPageToken + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetIdentities(w, r, params) })) @@ -1220,6 +1388,27 @@ func (siw *ServerInterfaceWrapper) GetIdentitiesItemEntitlements(w http.Response return } + headers := r.Header + + // ------------- Optional header parameter "Next-Page-Token" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Next-Page-Token")]; found { + var NextPageToken PaginationNextTokenHeader + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Next-Page-Token", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Next-Page-Token", valueList[0], &NextPageToken, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Next-Page-Token", Err: err}) + return + } + + params.NextPageToken = &NextPageToken + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetIdentitiesItemEntitlements(w, r, id, params) })) @@ -1299,6 +1488,27 @@ func (siw *ServerInterfaceWrapper) GetIdentitiesItemGroups(w http.ResponseWriter return } + headers := r.Header + + // ------------- Optional header parameter "Next-Page-Token" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Next-Page-Token")]; found { + var NextPageToken PaginationNextTokenHeader + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Next-Page-Token", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Next-Page-Token", valueList[0], &NextPageToken, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Next-Page-Token", Err: err}) + return + } + + params.NextPageToken = &NextPageToken + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetIdentitiesItemGroups(w, r, id, params) })) @@ -1378,6 +1588,27 @@ func (siw *ServerInterfaceWrapper) GetIdentitiesItemRoles(w http.ResponseWriter, return } + headers := r.Header + + // ------------- Optional header parameter "Next-Page-Token" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Next-Page-Token")]; found { + var NextPageToken PaginationNextTokenHeader + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Next-Page-Token", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Next-Page-Token", valueList[0], &NextPageToken, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Next-Page-Token", Err: err}) + return + } + + params.NextPageToken = &NextPageToken + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetIdentitiesItemRoles(w, r, id, params) })) @@ -1456,6 +1687,27 @@ func (siw *ServerInterfaceWrapper) GetResources(w http.ResponseWriter, r *http.R return } + headers := r.Header + + // ------------- Optional header parameter "Next-Page-Token" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Next-Page-Token")]; found { + var NextPageToken PaginationNextTokenHeader + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Next-Page-Token", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Next-Page-Token", valueList[0], &NextPageToken, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Next-Page-Token", Err: err}) + return + } + + params.NextPageToken = &NextPageToken + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetResources(w, r, params) })) @@ -1508,6 +1760,27 @@ func (siw *ServerInterfaceWrapper) GetRoles(w http.ResponseWriter, r *http.Reque return } + headers := r.Header + + // ------------- Optional header parameter "Next-Page-Token" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Next-Page-Token")]; found { + var NextPageToken PaginationNextTokenHeader + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Next-Page-Token", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Next-Page-Token", valueList[0], &NextPageToken, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Next-Page-Token", Err: err}) + return + } + + params.NextPageToken = &NextPageToken + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetRoles(w, r, params) })) @@ -1654,6 +1927,27 @@ func (siw *ServerInterfaceWrapper) GetRolesItemEntitlements(w http.ResponseWrite return } + headers := r.Header + + // ------------- Optional header parameter "Next-Page-Token" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Next-Page-Token")]; found { + var NextPageToken PaginationNextTokenHeader + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Next-Page-Token", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Next-Page-Token", valueList[0], &NextPageToken, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Next-Page-Token", Err: err}) + return + } + + params.NextPageToken = &NextPageToken + + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetRolesItemEntitlements(w, r, id, params) })) diff --git a/rebac-admin-backend/v1/resources/generated_types.go b/rebac-admin-backend/v1/resources/generated_types.go index 1f283b07b..43be77a92 100644 --- a/rebac-admin-backend/v1/resources/generated_types.go +++ b/rebac-admin-backend/v1/resources/generated_types.go @@ -21,6 +21,7 @@ const ( GET CapabilityMethods = "GET" PATCH CapabilityMethods = "PATCH" POST CapabilityMethods = "POST" + PUT CapabilityMethods = "PUT" ) // Defines values for GroupEntitlementsPatchItemOp. @@ -97,15 +98,15 @@ type CapabilityMethods string // Entity defines model for Entity. type Entity struct { - Id string `json:"id"` - Type string `json:"type"` + Id string `json:"id" validate:"required"` + Type string `json:"type" validate:"required"` } // EntityEntitlement defines model for EntityEntitlement. type EntityEntitlement struct { - EntitlementType string `json:"entitlement_type"` - EntityName string `json:"entity_name"` - EntityType string `json:"entity_type"` + EntitlementType string `json:"entitlement_type" validate:"required"` + EntityName string `json:"entity_name" validate:"required"` + EntityType string `json:"entity_type" validate:"required"` } // EntityEntitlementItem defines model for EntityEntitlementItem. @@ -251,13 +252,13 @@ type GetRolesResponse struct { // Group defines model for Group. type Group struct { Id *string `json:"id,omitempty"` - Name string `json:"name"` + Name string `json:"name" validate:"required"` } // GroupEntitlementsPatchItem defines model for GroupEntitlementsPatchItem. type GroupEntitlementsPatchItem struct { Entitlement EntityEntitlement `json:"entitlement"` - Op GroupEntitlementsPatchItemOp `json:"op"` + Op GroupEntitlementsPatchItemOp `json:"op" validate:"required,oneof=add remove"` } // GroupEntitlementsPatchItemOp defines model for GroupEntitlementsPatchItem.Op. @@ -265,13 +266,13 @@ type GroupEntitlementsPatchItemOp string // GroupEntitlementsPatchRequestBody defines model for GroupEntitlementsPatchRequestBody. type GroupEntitlementsPatchRequestBody struct { - Patches []GroupEntitlementsPatchItem `json:"patches"` + Patches []GroupEntitlementsPatchItem `json:"patches" validate:"required,dive"` } // GroupIdentitiesPatchItem defines model for GroupIdentitiesPatchItem. type GroupIdentitiesPatchItem struct { - Identity string `json:"identity"` - Op GroupIdentitiesPatchItemOp `json:"op"` + Identity string `json:"identity" validate:"required"` + Op GroupIdentitiesPatchItemOp `json:"op" validate:"required,oneof=add remove"` } // GroupIdentitiesPatchItemOp defines model for GroupIdentitiesPatchItem.Op. @@ -279,13 +280,13 @@ type GroupIdentitiesPatchItemOp string // GroupIdentitiesPatchRequestBody defines model for GroupIdentitiesPatchRequestBody. type GroupIdentitiesPatchRequestBody struct { - Patches []GroupIdentitiesPatchItem `json:"patches"` + Patches []GroupIdentitiesPatchItem `json:"patches" validate:"required,dive"` } // GroupRolesPatchItem defines model for GroupRolesPatchItem. type GroupRolesPatchItem struct { - Op GroupRolesPatchItemOp `json:"op"` - Role string `json:"role"` + Op GroupRolesPatchItemOp `json:"op" validate:"required,oneof=add remove"` + Role string `json:"role" validate:"required"` } // GroupRolesPatchItemOp defines model for GroupRolesPatchItem.Op. @@ -293,7 +294,7 @@ type GroupRolesPatchItemOp string // GroupRolesPatchRequestBody defines model for GroupRolesPatchRequestBody. type GroupRolesPatchRequestBody struct { - Patches []GroupRolesPatchItem `json:"patches"` + Patches []GroupRolesPatchItem `json:"patches" validate:"required,dive"` } // Groups defines model for Groups. @@ -308,9 +309,9 @@ type Identities struct { // Identity defines model for Identity. type Identity struct { - AddedBy string `json:"addedBy"` + AddedBy string `json:"addedBy" validate:"required"` Certificate *string `json:"certificate,omitempty"` - Email string `json:"email"` + Email string `json:"email" validate:"required"` FirstName *string `json:"firstName,omitempty"` Groups *int `json:"groups,omitempty"` Id *string `json:"id,omitempty"` @@ -319,13 +320,13 @@ type Identity struct { LastName *string `json:"lastName,omitempty"` Permissions *int `json:"permissions,omitempty"` Roles *int `json:"roles,omitempty"` - Source string `json:"source"` + Source string `json:"source" validate:"required"` } // IdentityEntitlementsPatchItem defines model for IdentityEntitlementsPatchItem. type IdentityEntitlementsPatchItem struct { Entitlement EntityEntitlement `json:"entitlement"` - Op IdentityEntitlementsPatchItemOp `json:"op"` + Op IdentityEntitlementsPatchItemOp `json:"op" validate:"required,oneof=add remove"` } // IdentityEntitlementsPatchItemOp defines model for IdentityEntitlementsPatchItem.Op. @@ -333,13 +334,13 @@ type IdentityEntitlementsPatchItemOp string // IdentityEntitlementsPatchRequestBody defines model for IdentityEntitlementsPatchRequestBody. type IdentityEntitlementsPatchRequestBody struct { - Patches []IdentityEntitlementsPatchItem `json:"patches"` + Patches []IdentityEntitlementsPatchItem `json:"patches" validate:"required,dive"` } // IdentityGroupsPatchItem defines model for IdentityGroupsPatchItem. type IdentityGroupsPatchItem struct { - Group string `json:"group"` - Op IdentityGroupsPatchItemOp `json:"op"` + Group string `json:"group" validate:"required"` + Op IdentityGroupsPatchItemOp `json:"op" validate:"required,oneof=add remove"` } // IdentityGroupsPatchItemOp defines model for IdentityGroupsPatchItem.Op. @@ -347,7 +348,7 @@ type IdentityGroupsPatchItemOp string // IdentityGroupsPatchRequestBody defines model for IdentityGroupsPatchRequestBody. type IdentityGroupsPatchRequestBody struct { - Patches []IdentityGroupsPatchItem `json:"patches"` + Patches []IdentityGroupsPatchItem `json:"patches" validate:"required,dive"` } // IdentityProvider defines model for IdentityProvider. @@ -364,7 +365,7 @@ type IdentityProvider struct { RedirectUrl *string `json:"redirectUrl,omitempty"` StoreTokens *bool `json:"storeTokens,omitempty"` StoreTokensReadable *bool `json:"storeTokensReadable,omitempty"` - SyncMode *IdentityProviderSyncMode `json:"syncMode,omitempty"` + SyncMode *IdentityProviderSyncMode `json:"syncMode,omitempty" validate:"oneof=import"` TrustEmail *bool `json:"trustEmail,omitempty"` } @@ -378,8 +379,8 @@ type IdentityProviders struct { // IdentityRolesPatchItem defines model for IdentityRolesPatchItem. type IdentityRolesPatchItem struct { - Op IdentityRolesPatchItemOp `json:"op"` - Role string `json:"role"` + Op IdentityRolesPatchItemOp `json:"op" validate:"required,oneof=add remove"` + Role string `json:"role" validate:"required"` } // IdentityRolesPatchItemOp defines model for IdentityRolesPatchItem.Op. @@ -387,7 +388,7 @@ type IdentityRolesPatchItemOp string // IdentityRolesPatchRequestBody defines model for IdentityRolesPatchRequestBody. type IdentityRolesPatchRequestBody struct { - Patches []IdentityRolesPatchItem `json:"patches"` + Patches []IdentityRolesPatchItem `json:"patches" validate:"required,dive"` } // Resource defines model for Resource. @@ -431,9 +432,9 @@ type ResponseMeta struct { // Role defines model for Role. type Role struct { - Entitlements *[]RoleEntitlement `json:"entitlements,omitempty"` + Entitlements *[]RoleEntitlement `json:"entitlements,omitempty" validate:"dive"` Id *string `json:"id,omitempty"` - Name string `json:"name"` + Name string `json:"name" validate:"required"` } // RoleEntitlement defines model for RoleEntitlement. @@ -446,7 +447,7 @@ type RoleEntitlement struct { // RoleEntitlementsPatchItem defines model for RoleEntitlementsPatchItem. type RoleEntitlementsPatchItem struct { Entitlement EntityEntitlement `json:"entitlement"` - Op RoleEntitlementsPatchItemOp `json:"op"` + Op RoleEntitlementsPatchItemOp `json:"op" validate:"required,oneof=add remove"` } // RoleEntitlementsPatchItemOp defines model for RoleEntitlementsPatchItem.Op. @@ -454,7 +455,7 @@ type RoleEntitlementsPatchItemOp string // RoleEntitlementsPatchRequestBody defines model for RoleEntitlementsPatchRequestBody. type RoleEntitlementsPatchRequestBody struct { - Patches []RoleEntitlementsPatchItem `json:"patches"` + Patches []RoleEntitlementsPatchItem `json:"patches" validate:"required,dive"` } // Roles defines model for Roles. @@ -468,6 +469,9 @@ type FilterParam = string // PaginationNextToken defines model for PaginationNextToken. type PaginationNextToken = string +// PaginationNextTokenHeader defines model for PaginationNextTokenHeader. +type PaginationNextTokenHeader = string + // PaginationPage defines model for PaginationPage. type PaginationPage = int @@ -496,6 +500,9 @@ type GetIdentityProvidersParams struct { // NextToken The continuation token to retrieve the next set of results NextToken *PaginationNextToken `form:"nextToken,omitempty" json:"nextToken,omitempty"` + + // NextPageToken The continuation token to retrieve the next set of results + NextPageToken *PaginationNextTokenHeader `json:"Next-Page-Token,omitempty"` } // GetAvailableIdentityProvidersParams defines parameters for GetAvailableIdentityProviders. @@ -508,6 +515,9 @@ type GetAvailableIdentityProvidersParams struct { // NextToken The continuation token to retrieve the next set of results NextToken *PaginationNextToken `form:"nextToken,omitempty" json:"nextToken,omitempty"` + + // NextPageToken The continuation token to retrieve the next set of results + NextPageToken *PaginationNextTokenHeader `json:"Next-Page-Token,omitempty"` } // GetEntitlementsParams defines parameters for GetEntitlements. @@ -523,6 +533,9 @@ type GetEntitlementsParams struct { // Filter A string to filter results by Filter *FilterParam `form:"filter,omitempty" json:"filter,omitempty"` + + // NextPageToken The continuation token to retrieve the next set of results + NextPageToken *PaginationNextTokenHeader `json:"Next-Page-Token,omitempty"` } // GetGroupsParams defines parameters for GetGroups. @@ -538,6 +551,9 @@ type GetGroupsParams struct { // Filter A string to filter results by Filter *FilterParam `form:"filter,omitempty" json:"filter,omitempty"` + + // NextPageToken The continuation token to retrieve the next set of results + NextPageToken *PaginationNextTokenHeader `json:"Next-Page-Token,omitempty"` } // GetGroupsItemEntitlementsParams defines parameters for GetGroupsItemEntitlements. @@ -550,6 +566,9 @@ type GetGroupsItemEntitlementsParams struct { // NextToken The continuation token to retrieve the next set of results NextToken *PaginationNextToken `form:"nextToken,omitempty" json:"nextToken,omitempty"` + + // NextPageToken The continuation token to retrieve the next set of results + NextPageToken *PaginationNextTokenHeader `json:"Next-Page-Token,omitempty"` } // GetGroupsItemIdentitiesParams defines parameters for GetGroupsItemIdentities. @@ -562,6 +581,9 @@ type GetGroupsItemIdentitiesParams struct { // NextToken The continuation token to retrieve the next set of results NextToken *PaginationNextToken `form:"nextToken,omitempty" json:"nextToken,omitempty"` + + // NextPageToken The continuation token to retrieve the next set of results + NextPageToken *PaginationNextTokenHeader `json:"Next-Page-Token,omitempty"` } // GetGroupsItemRolesParams defines parameters for GetGroupsItemRoles. @@ -574,6 +596,9 @@ type GetGroupsItemRolesParams struct { // NextToken The continuation token to retrieve the next set of results NextToken *PaginationNextToken `form:"nextToken,omitempty" json:"nextToken,omitempty"` + + // NextPageToken The continuation token to retrieve the next set of results + NextPageToken *PaginationNextTokenHeader `json:"Next-Page-Token,omitempty"` } // GetIdentitiesParams defines parameters for GetIdentities. @@ -589,6 +614,9 @@ type GetIdentitiesParams struct { // Filter A string to filter results by Filter *FilterParam `form:"filter,omitempty" json:"filter,omitempty"` + + // NextPageToken The continuation token to retrieve the next set of results + NextPageToken *PaginationNextTokenHeader `json:"Next-Page-Token,omitempty"` } // GetIdentitiesItemEntitlementsParams defines parameters for GetIdentitiesItemEntitlements. @@ -601,6 +629,9 @@ type GetIdentitiesItemEntitlementsParams struct { // NextToken The continuation token to retrieve the next set of results NextToken *PaginationNextToken `form:"nextToken,omitempty" json:"nextToken,omitempty"` + + // NextPageToken The continuation token to retrieve the next set of results + NextPageToken *PaginationNextTokenHeader `json:"Next-Page-Token,omitempty"` } // GetIdentitiesItemGroupsParams defines parameters for GetIdentitiesItemGroups. @@ -613,6 +644,9 @@ type GetIdentitiesItemGroupsParams struct { // NextToken The continuation token to retrieve the next set of results NextToken *PaginationNextToken `form:"nextToken,omitempty" json:"nextToken,omitempty"` + + // NextPageToken The continuation token to retrieve the next set of results + NextPageToken *PaginationNextTokenHeader `json:"Next-Page-Token,omitempty"` } // GetIdentitiesItemRolesParams defines parameters for GetIdentitiesItemRoles. @@ -625,6 +659,9 @@ type GetIdentitiesItemRolesParams struct { // NextToken The continuation token to retrieve the next set of results NextToken *PaginationNextToken `form:"nextToken,omitempty" json:"nextToken,omitempty"` + + // NextPageToken The continuation token to retrieve the next set of results + NextPageToken *PaginationNextTokenHeader `json:"Next-Page-Token,omitempty"` } // GetResourcesParams defines parameters for GetResources. @@ -638,6 +675,9 @@ type GetResourcesParams struct { // NextToken The continuation token to retrieve the next set of results NextToken *PaginationNextToken `form:"nextToken,omitempty" json:"nextToken,omitempty"` EntityType *string `form:"entityType,omitempty" json:"entityType,omitempty"` + + // NextPageToken The continuation token to retrieve the next set of results + NextPageToken *PaginationNextTokenHeader `json:"Next-Page-Token,omitempty"` } // GetRolesParams defines parameters for GetRoles. @@ -653,6 +693,9 @@ type GetRolesParams struct { // Filter A string to filter results by Filter *FilterParam `form:"filter,omitempty" json:"filter,omitempty"` + + // NextPageToken The continuation token to retrieve the next set of results + NextPageToken *PaginationNextTokenHeader `json:"Next-Page-Token,omitempty"` } // GetRolesItemEntitlementsParams defines parameters for GetRolesItemEntitlements. @@ -665,6 +708,9 @@ type GetRolesItemEntitlementsParams struct { // NextToken The continuation token to retrieve the next set of results NextToken *PaginationNextToken `form:"nextToken,omitempty" json:"nextToken,omitempty"` + + // NextPageToken The continuation token to retrieve the next set of results + NextPageToken *PaginationNextTokenHeader `json:"Next-Page-Token,omitempty"` } // PostIdentityProvidersJSONRequestBody defines body for PostIdentityProviders for application/json ContentType. @@ -715,70 +761,72 @@ type PatchRolesItemEntitlementsJSONRequestBody = RoleEntitlementsPatchRequestBod // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file From bcadfcf88498819fea25a802ebb13eee3f722099 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Tue, 26 Mar 2024 12:32:46 +0000 Subject: [PATCH 13/16] chore: pull latest spec Signed-off-by: Babak K. Shandiz --- .../v1/resources/generated_types.go | 147 +++++++++--------- 1 file changed, 74 insertions(+), 73 deletions(-) diff --git a/rebac-admin-backend/v1/resources/generated_types.go b/rebac-admin-backend/v1/resources/generated_types.go index 43be77a92..d45726b52 100644 --- a/rebac-admin-backend/v1/resources/generated_types.go +++ b/rebac-admin-backend/v1/resources/generated_types.go @@ -266,7 +266,7 @@ type GroupEntitlementsPatchItemOp string // GroupEntitlementsPatchRequestBody defines model for GroupEntitlementsPatchRequestBody. type GroupEntitlementsPatchRequestBody struct { - Patches []GroupEntitlementsPatchItem `json:"patches" validate:"required,dive"` + Patches []GroupEntitlementsPatchItem `json:"patches" validate:"required,gt=0,dive"` } // GroupIdentitiesPatchItem defines model for GroupIdentitiesPatchItem. @@ -280,7 +280,7 @@ type GroupIdentitiesPatchItemOp string // GroupIdentitiesPatchRequestBody defines model for GroupIdentitiesPatchRequestBody. type GroupIdentitiesPatchRequestBody struct { - Patches []GroupIdentitiesPatchItem `json:"patches" validate:"required,dive"` + Patches []GroupIdentitiesPatchItem `json:"patches" validate:"required,gt=0,dive"` } // GroupRolesPatchItem defines model for GroupRolesPatchItem. @@ -294,7 +294,7 @@ type GroupRolesPatchItemOp string // GroupRolesPatchRequestBody defines model for GroupRolesPatchRequestBody. type GroupRolesPatchRequestBody struct { - Patches []GroupRolesPatchItem `json:"patches" validate:"required,dive"` + Patches []GroupRolesPatchItem `json:"patches" validate:"required,gt=0,dive"` } // Groups defines model for Groups. @@ -334,7 +334,7 @@ type IdentityEntitlementsPatchItemOp string // IdentityEntitlementsPatchRequestBody defines model for IdentityEntitlementsPatchRequestBody. type IdentityEntitlementsPatchRequestBody struct { - Patches []IdentityEntitlementsPatchItem `json:"patches" validate:"required,dive"` + Patches []IdentityEntitlementsPatchItem `json:"patches" validate:"required,gt=0,dive"` } // IdentityGroupsPatchItem defines model for IdentityGroupsPatchItem. @@ -348,7 +348,7 @@ type IdentityGroupsPatchItemOp string // IdentityGroupsPatchRequestBody defines model for IdentityGroupsPatchRequestBody. type IdentityGroupsPatchRequestBody struct { - Patches []IdentityGroupsPatchItem `json:"patches" validate:"required,dive"` + Patches []IdentityGroupsPatchItem `json:"patches" validate:"required,gt=0,dive"` } // IdentityProvider defines model for IdentityProvider. @@ -388,7 +388,7 @@ type IdentityRolesPatchItemOp string // IdentityRolesPatchRequestBody defines model for IdentityRolesPatchRequestBody. type IdentityRolesPatchRequestBody struct { - Patches []IdentityRolesPatchItem `json:"patches" validate:"required,dive"` + Patches []IdentityRolesPatchItem `json:"patches" validate:"required,gt=0,dive"` } // Resource defines model for Resource. @@ -455,7 +455,7 @@ type RoleEntitlementsPatchItemOp string // RoleEntitlementsPatchRequestBody defines model for RoleEntitlementsPatchRequestBody. type RoleEntitlementsPatchRequestBody struct { - Patches []RoleEntitlementsPatchItem `json:"patches" validate:"required,dive"` + Patches []RoleEntitlementsPatchItem `json:"patches" validate:"required,gt=0,dive"` } // Roles defines model for Roles. @@ -761,72 +761,73 @@ type PatchRolesItemEntitlementsJSONRequestBody = RoleEntitlementsPatchRequestBod // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "zrSWoAAA", } // GetSwagger returns the content of the embedded swagger specification file From 0a6c21560c0e8e19c37f28380a424286b8d8794f Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Tue, 26 Mar 2024 12:35:21 +0000 Subject: [PATCH 14/16] fix: use `validator` for request body validation Signed-off-by: Babak K. Shandiz --- rebac-admin-backend/v1/groups_validation.go | 40 ++--------- .../v1/groups_validation_types.go | 70 ------------------- rebac-admin-backend/v1/validation.go | 21 ++++-- rebac-admin-backend/v1/validation_util.go | 26 ------- 4 files changed, 22 insertions(+), 135 deletions(-) delete mode 100644 rebac-admin-backend/v1/groups_validation_types.go delete mode 100644 rebac-admin-backend/v1/validation_util.go diff --git a/rebac-admin-backend/v1/groups_validation.go b/rebac-admin-backend/v1/groups_validation.go index 134e33997..c7333cbe0 100644 --- a/rebac-admin-backend/v1/groups_validation.go +++ b/rebac-admin-backend/v1/groups_validation.go @@ -11,32 +11,14 @@ import ( // PostGroups validates request body for the PostGroups method and delegates to the underlying handler. func (v handlerWithValidation) PostGroups(w http.ResponseWriter, r *http.Request) { - setRequestBodyInContext[resources.Group](w, r, func(w http.ResponseWriter, r *http.Request, body *resources.Group) { - if err := validateGroup(body); err != nil { - writeErrorResponse(w, err) - return - } + validateRequestBody(v.validate, w, r, func(w http.ResponseWriter, r *http.Request, _ *resources.Group) { v.ServerInterface.PostGroups(w, r) }) } -// DeleteGroupsItem validates request body for the DeleteGroupsItem method and delegates to the underlying handler. -func (v handlerWithValidation) DeleteGroupsItem(w http.ResponseWriter, r *http.Request, id string) { - v.ServerInterface.DeleteGroupsItem(w, r, id) -} - -// GetGroupsItem validates request body for the GetGroupsItem method and delegates to the underlying handler. -func (v handlerWithValidation) GetGroupsItem(w http.ResponseWriter, r *http.Request, id string) { - v.ServerInterface.GetGroupsItem(w, r, id) -} - // PutGroupsItem validates request body for the PutGroupsItem method and delegates to the underlying handler. func (v handlerWithValidation) PutGroupsItem(w http.ResponseWriter, r *http.Request, id string) { - setRequestBodyInContext[resources.Group](w, r, func(w http.ResponseWriter, r *http.Request, body *resources.Group) { - if err := validateGroup(body); err != nil { - writeErrorResponse(w, err) - return - } + validateRequestBody(v.validate, w, r, func(w http.ResponseWriter, r *http.Request, body *resources.Group) { if body.Id == nil || id != *body.Id { writeErrorResponse(w, NewRequestBodyValidationError("group ID from path does not match the Group object")) return @@ -47,33 +29,21 @@ func (v handlerWithValidation) PutGroupsItem(w http.ResponseWriter, r *http.Requ // PatchGroupsItemEntitlements validates request body for the PatchGroupsItemEntitlements method and delegates to the underlying handler. func (v handlerWithValidation) PatchGroupsItemEntitlements(w http.ResponseWriter, r *http.Request, id string) { - setRequestBodyInContext[resources.GroupEntitlementsPatchRequestBody](w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupEntitlementsPatchRequestBody) { - if err := validateGroupEntitlementsPatchRequestBody(body); err != nil { - writeErrorResponse(w, err) - return - } + validateRequestBody(v.validate, w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupEntitlementsPatchRequestBody) { v.ServerInterface.PatchGroupsItemEntitlements(w, r, id) }) } // PatchGroupsItemIdentities validates request body for the PatchGroupsItemIdentities method and delegates to the underlying handler. func (v handlerWithValidation) PatchGroupsItemIdentities(w http.ResponseWriter, r *http.Request, id string) { - setRequestBodyInContext[resources.GroupIdentitiesPatchRequestBody](w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupIdentitiesPatchRequestBody) { - if err := validateGroupIdentitiesPatchRequestBody(body); err != nil { - writeErrorResponse(w, err) - return - } + validateRequestBody(v.validate, w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupIdentitiesPatchRequestBody) { v.ServerInterface.PatchGroupsItemIdentities(w, r, id) }) } // PatchGroupsItemRoles validates request body for the PatchGroupsItemRoles method and delegates to the underlying handler. func (v handlerWithValidation) PatchGroupsItemRoles(w http.ResponseWriter, r *http.Request, id string) { - setRequestBodyInContext[resources.GroupRolesPatchRequestBody](w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupRolesPatchRequestBody) { - if err := validateGroupRolesPatchRequestBody(body); err != nil { - writeErrorResponse(w, err) - return - } + validateRequestBody(v.validate, w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupRolesPatchRequestBody) { v.ServerInterface.PatchGroupsItemRoles(w, r, id) }) } diff --git a/rebac-admin-backend/v1/groups_validation_types.go b/rebac-admin-backend/v1/groups_validation_types.go deleted file mode 100644 index f928ff084..000000000 --- a/rebac-admin-backend/v1/groups_validation_types.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2024 Canonical Ltd. -// SPDX-License-Identifier: Apache-2.0 - -package v1 - -import ( - "github.com/canonical/identity-platform-admin-ui/rebac-admin-backend/v1/resources" -) - -func validateGroup(value *resources.Group) error { - if len(value.Name) == 0 { - return NewRequestBodyValidationError("empty group name") - } - return nil -} - -func validateGroupEntitlementsPatchRequestBody(value *resources.GroupEntitlementsPatchRequestBody) error { - if len(value.Patches) == 0 { - return NewRequestBodyValidationError("empty patch array") - } - return validateSlice[resources.GroupEntitlementsPatchItem](value.Patches, validateGroupEntitlementsPatchItem) -} - -func validateGroupEntitlementsPatchItem(value *resources.GroupEntitlementsPatchItem) error { - if err := validateEntityEntitlement(&value.Entitlement); err != nil { - return err - } - return validateStringEnum("op", value.Op, resources.GroupEntitlementsPatchItemOpAdd, resources.GroupEntitlementsPatchItemOpRemove) -} - -func validateEntityEntitlement(value *resources.EntityEntitlement) error { - if len(value.EntitlementType) == 0 { - return NewRequestBodyValidationError("empty entitlement type") - } - if len(value.EntityName) == 0 { - return NewRequestBodyValidationError("empty entity name") - } - if len(value.EntityType) == 0 { - return NewRequestBodyValidationError("empty entity type") - } - return nil -} - -func validateGroupIdentitiesPatchRequestBody(value *resources.GroupIdentitiesPatchRequestBody) error { - if len(value.Patches) == 0 { - return NewRequestBodyValidationError("empty patch array") - } - return validateSlice[resources.GroupIdentitiesPatchItem](value.Patches, validateGroupIdentitiesPatchItem) -} - -func validateGroupIdentitiesPatchItem(value *resources.GroupIdentitiesPatchItem) error { - if len(value.Identity) == 0 { - return NewRequestBodyValidationError("empty identity name") - } - return validateStringEnum("op", value.Op, resources.GroupIdentitiesPatchItemOpAdd, resources.GroupIdentitiesPatchItemOpRemove) -} - -func validateGroupRolesPatchRequestBody(value *resources.GroupRolesPatchRequestBody) error { - if len(value.Patches) == 0 { - return NewRequestBodyValidationError("empty patch array") - } - return validateSlice[resources.GroupRolesPatchItem](value.Patches, validateGroupRolesPatchItem) -} - -func validateGroupRolesPatchItem(value *resources.GroupRolesPatchItem) error { - if len(value.Role) == 0 { - return NewRequestBodyValidationError("empty role name") - } - return validateStringEnum("op", value.Op, resources.GroupRolesPatchItemOpAdd, resources.GroupRolesPatchItemOpRemove) -} diff --git a/rebac-admin-backend/v1/validation.go b/rebac-admin-backend/v1/validation.go index 721b61a4a..d57c8071e 100644 --- a/rebac-admin-backend/v1/validation.go +++ b/rebac-admin-backend/v1/validation.go @@ -8,6 +8,8 @@ import ( "encoding/json" "net/http" + "github.com/go-playground/validator/v10" + "github.com/canonical/identity-platform-admin-ui/rebac-admin-backend/v1/resources" ) @@ -17,12 +19,15 @@ import ( type handlerWithValidation struct { // Wrapped/decorated handler resources.ServerInterface + + validate *validator.Validate } // newHandlerWithValidation returns a new instance of the validationHandlerDecorator struct. func newHandlerWithValidation(handler resources.ServerInterface) *handlerWithValidation { return &handlerWithValidation{ ServerInterface: handler, + validate: validator.New(), } } @@ -56,14 +61,22 @@ func parseRequestBody[T any](r *http.Request) (*T, error) { return body, nil } -// setRequestBodyInContext is a helper method to avoid repetition. It parses -// request body and if it's okay, will delegate to the provided callback with a -// new HTTP request instance with the parse body in the context. -func setRequestBodyInContext[T any](w http.ResponseWriter, r *http.Request, f func(w http.ResponseWriter, r *http.Request, body *T)) { +// validateRequestBody is a helper method to avoid repetition. It parses +// request body, validates it against the given validator instance and if it's +// okay, will delegate to the provided callback with a new HTTP request instance +// with the parse body in the context. +// +// Note that, technically, this method could be an ordinary (non-generic) method, +// but it's defined as one to avoid confusion over value vs pointer arguments. +func validateRequestBody[T any](v *validator.Validate, w http.ResponseWriter, r *http.Request, f func(w http.ResponseWriter, r *http.Request, body *T)) { body, err := parseRequestBody[T](r) if err != nil { writeErrorResponse(w, err) return } + if err := v.Struct(body); err != nil { + writeErrorResponse(w, NewRequestBodyValidationError(err.Error())) + return + } f(w, newRequestWithBodyInContext(r, body), body) } diff --git a/rebac-admin-backend/v1/validation_util.go b/rebac-admin-backend/v1/validation_util.go deleted file mode 100644 index 6329f1453..000000000 --- a/rebac-admin-backend/v1/validation_util.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2024 Canonical Ltd. -// SPDX-License-Identifier: Apache-2.0 - -package v1 - -import "fmt" - -// validateStringEnum is a helper validator to validate string enums. -func validateStringEnum[T ~string](field string, value T, allowed ...T) error { - for _, element := range allowed { - if string(value) == string(element) { - return nil - } - } - return NewRequestBodyValidationError(fmt.Sprintf("%s value not allowed: %q", field, value)) -} - -// validateStringEnum is a helper validator to validate elements in a slice. -func validateSlice[T any](s []T, validator func(*T) error) error { - for _, element := range s { - if err := validator(&element); err != nil { - return err - } - } - return nil -} From 19092f3a64499ad4222641c4c319895a6330e54d Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Tue, 26 Mar 2024 12:35:37 +0000 Subject: [PATCH 15/16] test: update validation tests Signed-off-by: Babak K. Shandiz --- .../v1/groups_validation_test.go | 256 +++++++----------- 1 file changed, 94 insertions(+), 162 deletions(-) diff --git a/rebac-admin-backend/v1/groups_validation_test.go b/rebac-admin-backend/v1/groups_validation_test.go index 8100a96dc..9e64555d2 100644 --- a/rebac-admin-backend/v1/groups_validation_test.go +++ b/rebac-admin-backend/v1/groups_validation_test.go @@ -6,9 +6,11 @@ package v1 import ( "bytes" "encoding/json" + "fmt" "io" "net/http" "net/http/httptest" + "regexp" "testing" qt "github.com/frankban/quicktest" @@ -21,8 +23,6 @@ import ( func TestHandlerWithValidation_Groups(t *testing.T) { c := qt.New(t) - ctrl := gomock.NewController(t) - defer ctrl.Finish() writeResponse := func(w http.ResponseWriter, status int, body any) { raw, _ := json.Marshal(body) @@ -36,16 +36,23 @@ func TestHandlerWithValidation_Groups(t *testing.T) { EntityType: "some-entity-type", } + const ( + kindValidationFailure int = 0 + kindSuccessful int = 1 + kindBadJSON int = 2 + ) + tests := []struct { - name string - requestBodyRaw string - requestBody any - setupHandlerMock func(mockHandler *resources.MockServerInterface) - triggerFunc func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) - expectedStatusCode int - expectedResponse any + name string + requestBodyRaw string + requestBody any + setupHandlerMock func(mockHandler *resources.MockServerInterface) + triggerFunc func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) + kind int + expectedPatterns []string }{{ name: "PostGroups: success", + kind: kindSuccessful, requestBody: resources.Group{Name: "foo"}, setupHandlerMock: func(mockHandler *resources.MockServerInterface) { mockHandler.EXPECT(). @@ -59,33 +66,22 @@ func TestHandlerWithValidation_Groups(t *testing.T) { triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PostGroups(w, r) }, - expectedStatusCode: http.StatusOK, - expectedResponse: resources.Response{ - Status: http.StatusOK, - }, }, { name: "PostGroups: failure; invalid JSON", + kind: kindBadJSON, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PostGroups(w, r) }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: missing request body: request body is not a valid JSON", - Status: http.StatusBadRequest, - }, }, { - name: "PostGroups: failure; empty", - requestBody: resources.Group{}, + name: "PostGroups: failure; empty", + expectedPatterns: []string{"'Name' failed on the 'required' tag"}, + requestBody: resources.Group{}, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PostGroups(w, r) }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: empty group name", - Status: http.StatusBadRequest, - }, }, { name: "PutGroupsItem: success", + kind: kindSuccessful, requestBody: resources.Group{Name: "foo", Id: stringPtr("some-id")}, setupHandlerMock: func(mockHandler *resources.MockServerInterface) { mockHandler.EXPECT(). @@ -99,55 +95,36 @@ func TestHandlerWithValidation_Groups(t *testing.T) { triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PutGroupsItem(w, r, "some-id") }, - expectedStatusCode: http.StatusOK, - expectedResponse: resources.Response{ - Status: http.StatusOK, - }, }, { name: "PutGroupsItem: failure; invalid JSON", + kind: kindBadJSON, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PutGroupsItem(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: missing request body: request body is not a valid JSON", - Status: http.StatusBadRequest, - }, }, { - name: "PutGroupsItem: failure; empty", - requestBody: resources.Group{}, + name: "PutGroupsItem: failure; empty", + expectedPatterns: []string{"'Name' failed on the 'required' tag"}, + requestBody: resources.Group{}, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PutGroupsItem(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: empty group name", - Status: http.StatusBadRequest, - }, }, { - name: "PutGroupsItem: failure; nil id", - requestBody: resources.Group{Name: "foo"}, + name: "PutGroupsItem: failure; nil id", + expectedPatterns: []string{"group ID from path does not match the Group object"}, + requestBody: resources.Group{Name: "foo"}, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PutGroupsItem(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: group ID from path does not match the Group object", - Status: http.StatusBadRequest, - }, }, { - name: "PutGroupsItem: failure; id mismatch", - requestBody: resources.Group{Name: "foo", Id: stringPtr("some-other-id")}, + name: "PutGroupsItem: failure; id mismatch", + expectedPatterns: []string{"group ID from path does not match the Group object"}, + requestBody: resources.Group{Name: "foo", Id: stringPtr("some-other-id")}, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PutGroupsItem(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: group ID from path does not match the Group object", - Status: http.StatusBadRequest, - }, }, { name: "PatchGroupsItemEntitlements: success", + kind: kindSuccessful, requestBody: resources.GroupEntitlementsPatchRequestBody{ Patches: []resources.GroupEntitlementsPatchItem{{ Op: "add", @@ -166,46 +143,31 @@ func TestHandlerWithValidation_Groups(t *testing.T) { triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemEntitlements(w, r, "some-id") }, - expectedStatusCode: http.StatusOK, - expectedResponse: resources.Response{ - Status: http.StatusOK, - }, }, { name: "PatchGroupsItemEntitlements: failure; invalid JSON", + kind: kindBadJSON, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemEntitlements(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: missing request body: request body is not a valid JSON", - Status: http.StatusBadRequest, - }, }, { - name: "PatchGroupsItemEntitlements: failure; nil patch array", - requestBody: resources.GroupEntitlementsPatchRequestBody{}, + name: "PatchGroupsItemEntitlements: failure; nil patch array", + expectedPatterns: []string{"'Patches' failed on the 'required' tag"}, + requestBody: resources.GroupEntitlementsPatchRequestBody{}, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemEntitlements(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: empty patch array", - Status: http.StatusBadRequest, - }, }, { - name: "PatchGroupsItemEntitlements: failure; empty patch array", + name: "PatchGroupsItemEntitlements: failure; empty patch array", + expectedPatterns: []string{"'Patches' failed on the 'gt' tag"}, requestBody: resources.GroupEntitlementsPatchRequestBody{ Patches: []resources.GroupEntitlementsPatchItem{}, }, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemEntitlements(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: empty patch array", - Status: http.StatusBadRequest, - }, }, { - name: "PatchGroupsItemEntitlements: failure; invalid op", + name: "PatchGroupsItemEntitlements: failure; invalid op", + expectedPatterns: []string{"'Op' failed on the 'oneof' tag"}, requestBody: resources.GroupEntitlementsPatchRequestBody{ Patches: []resources.GroupEntitlementsPatchItem{{ Op: "some-invalid-op", @@ -215,29 +177,25 @@ func TestHandlerWithValidation_Groups(t *testing.T) { triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemEntitlements(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: op value not allowed: \"some-invalid-op\"", - Status: http.StatusBadRequest, - }, }, { name: "PatchGroupsItemEntitlements: failure; invalid entitlement", + expectedPatterns: []string{ + "'EntitlementType' failed on the 'required' tag", + "'EntityName' failed on the 'required' tag", + "'EntityType' failed on the 'required' tag", + }, requestBody: resources.GroupEntitlementsPatchRequestBody{ Patches: []resources.GroupEntitlementsPatchItem{{ - Op: "some-invalid-op", + Op: "add", Entitlement: resources.EntityEntitlement{}, }}, }, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemEntitlements(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: empty entitlement type", - Status: http.StatusBadRequest, - }, }, { name: "PatchGroupsItemIdentities: success", + kind: kindSuccessful, requestBody: resources.GroupIdentitiesPatchRequestBody{ Patches: []resources.GroupIdentitiesPatchItem{{ Op: "add", @@ -256,46 +214,31 @@ func TestHandlerWithValidation_Groups(t *testing.T) { triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemIdentities(w, r, "some-id") }, - expectedStatusCode: http.StatusOK, - expectedResponse: resources.Response{ - Status: http.StatusOK, - }, }, { name: "PatchGroupsItemIdentities: failure; invalid JSON", + kind: kindBadJSON, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemIdentities(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: missing request body: request body is not a valid JSON", - Status: http.StatusBadRequest, - }, }, { - name: "PatchGroupsItemIdentities: failure; nil patch array", - requestBody: resources.GroupIdentitiesPatchRequestBody{}, + name: "PatchGroupsItemIdentities: failure; nil patch array", + expectedPatterns: []string{"'Patches' failed on the 'required' tag"}, + requestBody: resources.GroupIdentitiesPatchRequestBody{}, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemIdentities(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: empty patch array", - Status: http.StatusBadRequest, - }, }, { - name: "PatchGroupsItemIdentities: failure; empty patch array", + name: "PatchGroupsItemIdentities: failure; empty patch array", + expectedPatterns: []string{"'Patches' failed on the 'gt' tag"}, requestBody: resources.GroupIdentitiesPatchRequestBody{ Patches: []resources.GroupIdentitiesPatchItem{}, }, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemIdentities(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: empty patch array", - Status: http.StatusBadRequest, - }, }, { - name: "PatchGroupsItemIdentities: failure; empty identity", + name: "PatchGroupsItemIdentities: failure; empty identity", + expectedPatterns: []string{"'Identity' failed on the 'required' tag"}, requestBody: resources.GroupIdentitiesPatchRequestBody{ Patches: []resources.GroupIdentitiesPatchItem{{ Op: "add", @@ -305,13 +248,9 @@ func TestHandlerWithValidation_Groups(t *testing.T) { triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemIdentities(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: empty identity name", - Status: http.StatusBadRequest, - }, }, { - name: "PatchGroupsItemIdentities: failure; invalid op", + name: "PatchGroupsItemIdentities: failure; invalid op", + expectedPatterns: []string{"'Op' failed on the 'oneof' tag"}, requestBody: resources.GroupIdentitiesPatchRequestBody{ Patches: []resources.GroupIdentitiesPatchItem{{ Op: "some-invalid-op", @@ -321,13 +260,9 @@ func TestHandlerWithValidation_Groups(t *testing.T) { triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemIdentities(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: op value not allowed: \"some-invalid-op\"", - Status: http.StatusBadRequest, - }, }, { name: "PatchGroupsItemRoles: success", + kind: kindSuccessful, requestBody: resources.GroupRolesPatchRequestBody{ Patches: []resources.GroupRolesPatchItem{{ Op: "add", @@ -346,46 +281,31 @@ func TestHandlerWithValidation_Groups(t *testing.T) { triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemRoles(w, r, "some-id") }, - expectedStatusCode: http.StatusOK, - expectedResponse: resources.Response{ - Status: http.StatusOK, - }, }, { name: "PatchGroupsItemRoles: failure; invalid JSON", + kind: kindBadJSON, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemRoles(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: missing request body: request body is not a valid JSON", - Status: http.StatusBadRequest, - }, }, { - name: "PatchGroupsItemRoles: failure; nil patch array", - requestBody: resources.GroupRolesPatchRequestBody{}, + name: "PatchGroupsItemRoles: failure; nil patch array", + expectedPatterns: []string{"'Patches' failed on the 'required' tag"}, + requestBody: resources.GroupRolesPatchRequestBody{}, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemRoles(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: empty patch array", - Status: http.StatusBadRequest, - }, }, { - name: "PatchGroupsItemRoles: failure; empty patch array", + name: "PatchGroupsItemRoles: failure; empty patch array", + expectedPatterns: []string{"'Patches' failed on the 'gt' tag"}, requestBody: resources.GroupRolesPatchRequestBody{ Patches: []resources.GroupRolesPatchItem{}, }, triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemRoles(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: empty patch array", - Status: http.StatusBadRequest, - }, }, { - name: "PatchGroupsItemRoles: failure; empty role", + name: "PatchGroupsItemRoles: failure; empty role", + expectedPatterns: []string{"'Role' failed on the 'required' tag"}, requestBody: resources.GroupRolesPatchRequestBody{ Patches: []resources.GroupRolesPatchItem{{ Op: "add", @@ -395,13 +315,9 @@ func TestHandlerWithValidation_Groups(t *testing.T) { triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemRoles(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: empty role name", - Status: http.StatusBadRequest, - }, }, { - name: "PatchGroupsItemRoles: failure; invalid op", + name: "PatchGroupsItemRoles: failure; invalid op", + expectedPatterns: []string{"'Op' failed on the 'oneof' tag"}, requestBody: resources.GroupRolesPatchRequestBody{ Patches: []resources.GroupRolesPatchItem{{ Op: "some-invalid-op", @@ -411,17 +327,15 @@ func TestHandlerWithValidation_Groups(t *testing.T) { triggerFunc: func(sut *handlerWithValidation, w http.ResponseWriter, r *http.Request) { sut.PatchGroupsItemRoles(w, r, "some-id") }, - expectedStatusCode: http.StatusBadRequest, - expectedResponse: resources.Response{ - Message: "Bad Request: invalid request body: op value not allowed: \"some-invalid-op\"", - Status: http.StatusBadRequest, - }, }, } for _, t := range tests { tt := t c.Run(tt.name, func(c *qt.C) { + ctrl := gomock.NewController(c) + defer ctrl.Finish() + mockHandler := resources.NewMockServerInterface(ctrl) if tt.setupHandlerMock != nil { tt.setupHandlerMock(mockHandler) @@ -444,12 +358,30 @@ func TestHandlerWithValidation_Groups(t *testing.T) { tt.triggerFunc(sut, mockWriter, req) response := mockWriter.Result() - c.Assert(response.StatusCode, qt.Equals, tt.expectedStatusCode) + if tt.kind == kindSuccessful { + c.Assert(response.StatusCode, qt.Equals, http.StatusOK) + } else { + c.Assert(response.StatusCode, qt.Equals, http.StatusBadRequest) + + defer response.Body.Close() + responseBody, err := io.ReadAll(response.Body) + c.Assert(err, qt.IsNil) - defer response.Body.Close() - responseBody, err := io.ReadAll(response.Body) - c.Assert(err, qt.IsNil) - c.Assert(string(responseBody), qt.JSONEquals, tt.expectedResponse) + parsedResponse := &resources.Response{} + err = json.Unmarshal(responseBody, parsedResponse) + c.Assert(err, qt.IsNil) + c.Assert(parsedResponse.Status, qt.Equals, http.StatusBadRequest) + + if tt.kind == kindBadJSON { + c.Assert(parsedResponse.Message, qt.Matches, "Bad Request: missing request body: request body is not a valid JSON") + } else if tt.kind == kindValidationFailure { + c.Assert(parsedResponse.Message, qt.Matches, regexp.MustCompile("Bad Request: invalid request body: .+")) + } + + for _, pattern := range tt.expectedPatterns { + c.Assert(parsedResponse.Message, qt.Matches, regexp.MustCompile(fmt.Sprintf(".*%s.*", pattern))) + } + } }) } } From cd28d7be2ce5c426d6326dd16adc6209a4f3db90 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Tue, 26 Mar 2024 14:12:26 +0000 Subject: [PATCH 16/16] fix: refactor toward removing generics Signed-off-by: Babak K. Shandiz --- rebac-admin-backend/v1/groups.go | 40 ++++++++++++++++++--- rebac-admin-backend/v1/groups_validation.go | 15 +++++--- rebac-admin-backend/v1/validation.go | 35 ++++++++---------- 3 files changed, 60 insertions(+), 30 deletions(-) diff --git a/rebac-admin-backend/v1/groups.go b/rebac-admin-backend/v1/groups.go index 5a6376c62..4c5fb4bd3 100644 --- a/rebac-admin-backend/v1/groups.go +++ b/rebac-admin-backend/v1/groups.go @@ -35,12 +35,18 @@ func (h handler) GetGroups(w http.ResponseWriter, req *http.Request, params reso func (h handler) PostGroups(w http.ResponseWriter, req *http.Request) { ctx := req.Context() - group, err := getRequestBodyFromContext[resources.Group](req.Context()) + body, err := getRequestBodyFromContext(req.Context()) if err != nil { writeErrorResponse(w, err) return } + group, ok := body.(*resources.Group) + if !ok { + writeErrorResponse(w, NewMissingRequestBodyError("")) + return + } + result, err := h.Groups.CreateGroup(ctx, group) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) @@ -83,12 +89,18 @@ func (h handler) GetGroupsItem(w http.ResponseWriter, req *http.Request, id stri func (h handler) PutGroupsItem(w http.ResponseWriter, req *http.Request, id string) { ctx := req.Context() - group, err := getRequestBodyFromContext[resources.Group](req.Context()) + body, err := getRequestBodyFromContext(req.Context()) if err != nil { writeErrorResponse(w, err) return } + group, ok := body.(*resources.Group) + if !ok { + writeErrorResponse(w, NewMissingRequestBodyError("")) + return + } + result, err := h.Groups.UpdateGroup(ctx, group) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) @@ -124,12 +136,18 @@ func (h handler) GetGroupsItemEntitlements(w http.ResponseWriter, req *http.Requ func (h handler) PatchGroupsItemEntitlements(w http.ResponseWriter, req *http.Request, id string) { ctx := req.Context() - groupEntitlements, err := getRequestBodyFromContext[resources.GroupEntitlementsPatchRequestBody](req.Context()) + body, err := getRequestBodyFromContext(req.Context()) if err != nil { writeErrorResponse(w, err) return } + groupEntitlements, ok := body.(*resources.GroupEntitlementsPatchRequestBody) + if !ok { + writeErrorResponse(w, NewMissingRequestBodyError("")) + return + } + _, err = h.Groups.PatchGroupEntitlements(ctx, id, groupEntitlements.Patches) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) @@ -165,12 +183,18 @@ func (h handler) GetGroupsItemIdentities(w http.ResponseWriter, req *http.Reques func (h handler) PatchGroupsItemIdentities(w http.ResponseWriter, req *http.Request, id string) { ctx := req.Context() - groupIdentities, err := getRequestBodyFromContext[resources.GroupIdentitiesPatchRequestBody](req.Context()) + body, err := getRequestBodyFromContext(req.Context()) if err != nil { writeErrorResponse(w, err) return } + groupIdentities, ok := body.(*resources.GroupIdentitiesPatchRequestBody) + if !ok { + writeErrorResponse(w, NewMissingRequestBodyError("")) + return + } + _, err = h.Groups.PatchGroupIdentities(ctx, id, groupIdentities.Patches) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) @@ -206,12 +230,18 @@ func (h handler) GetGroupsItemRoles(w http.ResponseWriter, req *http.Request, id func (h handler) PatchGroupsItemRoles(w http.ResponseWriter, req *http.Request, id string) { ctx := req.Context() - groupRoles, err := getRequestBodyFromContext[resources.GroupRolesPatchRequestBody](req.Context()) + body, err := getRequestBodyFromContext(req.Context()) if err != nil { writeErrorResponse(w, err) return } + groupRoles, ok := body.(*resources.GroupRolesPatchRequestBody) + if !ok { + writeErrorResponse(w, NewMissingRequestBodyError("")) + return + } + _, err = h.Groups.PatchGroupRoles(ctx, id, groupRoles.Patches) if err != nil { writeServiceErrorResponse(w, h.GroupsErrorMapper, err) diff --git a/rebac-admin-backend/v1/groups_validation.go b/rebac-admin-backend/v1/groups_validation.go index c7333cbe0..2c323a358 100644 --- a/rebac-admin-backend/v1/groups_validation.go +++ b/rebac-admin-backend/v1/groups_validation.go @@ -11,14 +11,16 @@ import ( // PostGroups validates request body for the PostGroups method and delegates to the underlying handler. func (v handlerWithValidation) PostGroups(w http.ResponseWriter, r *http.Request) { - validateRequestBody(v.validate, w, r, func(w http.ResponseWriter, r *http.Request, _ *resources.Group) { + body := &resources.Group{} + v.validateRequestBody(body, w, r, func(w http.ResponseWriter, r *http.Request) { v.ServerInterface.PostGroups(w, r) }) } // PutGroupsItem validates request body for the PutGroupsItem method and delegates to the underlying handler. func (v handlerWithValidation) PutGroupsItem(w http.ResponseWriter, r *http.Request, id string) { - validateRequestBody(v.validate, w, r, func(w http.ResponseWriter, r *http.Request, body *resources.Group) { + body := &resources.Group{} + v.validateRequestBody(body, w, r, func(w http.ResponseWriter, r *http.Request) { if body.Id == nil || id != *body.Id { writeErrorResponse(w, NewRequestBodyValidationError("group ID from path does not match the Group object")) return @@ -29,21 +31,24 @@ func (v handlerWithValidation) PutGroupsItem(w http.ResponseWriter, r *http.Requ // PatchGroupsItemEntitlements validates request body for the PatchGroupsItemEntitlements method and delegates to the underlying handler. func (v handlerWithValidation) PatchGroupsItemEntitlements(w http.ResponseWriter, r *http.Request, id string) { - validateRequestBody(v.validate, w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupEntitlementsPatchRequestBody) { + body := &resources.GroupEntitlementsPatchRequestBody{} + v.validateRequestBody(body, w, r, func(w http.ResponseWriter, r *http.Request) { v.ServerInterface.PatchGroupsItemEntitlements(w, r, id) }) } // PatchGroupsItemIdentities validates request body for the PatchGroupsItemIdentities method and delegates to the underlying handler. func (v handlerWithValidation) PatchGroupsItemIdentities(w http.ResponseWriter, r *http.Request, id string) { - validateRequestBody(v.validate, w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupIdentitiesPatchRequestBody) { + body := &resources.GroupIdentitiesPatchRequestBody{} + v.validateRequestBody(body, w, r, func(w http.ResponseWriter, r *http.Request) { v.ServerInterface.PatchGroupsItemIdentities(w, r, id) }) } // PatchGroupsItemRoles validates request body for the PatchGroupsItemRoles method and delegates to the underlying handler. func (v handlerWithValidation) PatchGroupsItemRoles(w http.ResponseWriter, r *http.Request, id string) { - validateRequestBody(v.validate, w, r, func(w http.ResponseWriter, r *http.Request, body *resources.GroupRolesPatchRequestBody) { + body := &resources.GroupRolesPatchRequestBody{} + v.validateRequestBody(body, w, r, func(w http.ResponseWriter, r *http.Request) { v.ServerInterface.PatchGroupsItemRoles(w, r, id) }) } diff --git a/rebac-admin-backend/v1/validation.go b/rebac-admin-backend/v1/validation.go index d57c8071e..078939b55 100644 --- a/rebac-admin-backend/v1/validation.go +++ b/rebac-admin-backend/v1/validation.go @@ -34,49 +34,44 @@ func newHandlerWithValidation(handler resources.ServerInterface) *handlerWithVal // requestBodyContextKey is the context key to retrieve the parsed request body struct instance. type requestBodyContextKey struct{} -// getRequestBodyFromContext fetches request body from given context. -func getRequestBodyFromContext[T any](ctx context.Context) (*T, error) { - if body, ok := ctx.Value(requestBodyContextKey{}).(*T); ok { - return body, nil +// getRequestBodyFromContext fetches request body from given context. If the value +// was not found in the given context, this will return an error. +func getRequestBodyFromContext(ctx context.Context) (any, error) { + body := ctx.Value(requestBodyContextKey{}) + if body == nil { + return nil, NewMissingRequestBodyError("request body is not available") } - return nil, NewMissingRequestBodyError("request body is not available") + return body, nil } // newRequestWithBodyInContext sets the given body in a new request instance context // and returns the new request. -// -// Note that, technically, this method could be an ordinary (non-generic) method, -// but it's defined as one to avoid confusion over value vs pointer arguments. -func newRequestWithBodyInContext[T any](r *http.Request, body *T) *http.Request { +func newRequestWithBodyInContext(r *http.Request, body any) *http.Request { return r.WithContext(context.WithValue(r.Context(), requestBodyContextKey{}, body)) } // parseRequestBody parses request body as JSON and populates the given body instance. -func parseRequestBody[T any](r *http.Request) (*T, error) { - body := new(T) +func parseRequestBody(body any, r *http.Request) error { defer r.Body.Close() if err := json.NewDecoder(r.Body).Decode(body); err != nil { - return nil, NewMissingRequestBodyError("request body is not a valid JSON") + return NewMissingRequestBodyError("request body is not a valid JSON") } - return body, nil + return nil } // validateRequestBody is a helper method to avoid repetition. It parses // request body, validates it against the given validator instance and if it's // okay, will delegate to the provided callback with a new HTTP request instance // with the parse body in the context. -// -// Note that, technically, this method could be an ordinary (non-generic) method, -// but it's defined as one to avoid confusion over value vs pointer arguments. -func validateRequestBody[T any](v *validator.Validate, w http.ResponseWriter, r *http.Request, f func(w http.ResponseWriter, r *http.Request, body *T)) { - body, err := parseRequestBody[T](r) +func (v handlerWithValidation) validateRequestBody(body any, w http.ResponseWriter, r *http.Request, f func(w http.ResponseWriter, r *http.Request)) { + err := parseRequestBody(body, r) if err != nil { writeErrorResponse(w, err) return } - if err := v.Struct(body); err != nil { + if err := v.validate.Struct(body); err != nil { writeErrorResponse(w, NewRequestBodyValidationError(err.Error())) return } - f(w, newRequestWithBodyInContext(r, body), body) + f(w, newRequestWithBodyInContext(r, body)) }