diff --git a/http/api_handler.go b/http/api_handler.go index 7eecd57569..55a7958a2e 100644 --- a/http/api_handler.go +++ b/http/api_handler.go @@ -75,11 +75,13 @@ func NewAPIHandler(b *APIBackend) *APIHandler { h.BucketHandler = NewBucketHandler(b.UserResourceMappingService, b.LabelService) h.BucketHandler.BucketService = b.BucketService h.BucketHandler.BucketOperationLogService = b.BucketOperationLogService + h.BucketHandler.UserService = b.UserService h.OrgHandler = NewOrgHandler(b.UserResourceMappingService, b.LabelService) h.OrgHandler.OrganizationService = b.OrganizationService h.OrgHandler.BucketService = b.BucketService h.OrgHandler.OrganizationOperationLogService = b.OrganizationOperationLogService + h.OrgHandler.UserService = b.UserService h.UserHandler = NewUserHandler() h.UserHandler.UserService = b.UserService @@ -89,9 +91,11 @@ func NewAPIHandler(b *APIBackend) *APIHandler { h.DashboardHandler = NewDashboardHandler(b.UserResourceMappingService, b.LabelService) h.DashboardHandler.DashboardService = b.DashboardService h.DashboardHandler.DashboardOperationLogService = b.DashboardOperationLogService + h.DashboardHandler.UserService = b.UserService h.ViewHandler = NewViewHandler(b.UserResourceMappingService, b.LabelService) h.ViewHandler.ViewService = b.ViewService + h.ViewHandler.UserService = b.UserService h.MacroHandler = NewMacroHandler() h.MacroHandler.MacroService = b.MacroService @@ -112,6 +116,7 @@ func NewAPIHandler(b *APIBackend) *APIHandler { h.TaskHandler.TaskService = b.TaskService h.TaskHandler.AuthorizationService = b.AuthorizationService h.TaskHandler.UserResourceMappingService = b.UserResourceMappingService + h.TaskHandler.UserService = b.UserService h.TelegrafHandler = NewTelegrafHandler( b.Logger.With(zap.String("handler", "telegraf")), @@ -119,6 +124,7 @@ func NewAPIHandler(b *APIBackend) *APIHandler { b.LabelService, b.TelegrafService, ) + h.TelegrafHandler.UserService = b.UserService h.WriteHandler = NewWriteHandler(b.PointsWriter) h.WriteHandler.OrganizationService = b.OrganizationService diff --git a/http/bucket_service.go b/http/bucket_service.go index 7b5bdf4653..f6338ed6d6 100644 --- a/http/bucket_service.go +++ b/http/bucket_service.go @@ -23,6 +23,7 @@ type BucketHandler struct { BucketOperationLogService platform.BucketOperationLogService UserResourceMappingService platform.UserResourceMappingService LabelService platform.LabelService + UserService platform.UserService } const ( @@ -52,12 +53,12 @@ func NewBucketHandler(mappingService platform.UserResourceMappingService, labelS h.HandlerFunc("PATCH", bucketsIDPath, h.handlePatchBucket) h.HandlerFunc("DELETE", bucketsIDPath, h.handleDeleteBucket) - h.HandlerFunc("POST", bucketsIDMembersPath, newPostMemberHandler(h.UserResourceMappingService, platform.BucketResourceType, platform.Member)) - h.HandlerFunc("GET", bucketsIDMembersPath, newGetMembersHandler(h.UserResourceMappingService, platform.Member)) + h.HandlerFunc("POST", bucketsIDMembersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.BucketResourceType, platform.Member)) + h.HandlerFunc("GET", bucketsIDMembersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.BucketResourceType, platform.Member)) h.HandlerFunc("DELETE", bucketsIDMembersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Member)) - h.HandlerFunc("POST", bucketsIDOwnersPath, newPostMemberHandler(h.UserResourceMappingService, platform.BucketResourceType, platform.Owner)) - h.HandlerFunc("GET", bucketsIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, platform.Owner)) + h.HandlerFunc("POST", bucketsIDOwnersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.BucketResourceType, platform.Owner)) + h.HandlerFunc("GET", bucketsIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.BucketResourceType, platform.Owner)) h.HandlerFunc("DELETE", bucketsIDOwnersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Owner)) h.HandlerFunc("GET", bucketsIDLabelsPath, newGetLabelsHandler(h.LabelService)) diff --git a/http/dashboard_service.go b/http/dashboard_service.go index ca81740c2a..abf6550b95 100644 --- a/http/dashboard_service.go +++ b/http/dashboard_service.go @@ -23,6 +23,7 @@ type DashboardHandler struct { DashboardOperationLogService platform.DashboardOperationLogService UserResourceMappingService platform.UserResourceMappingService LabelService platform.LabelService + UserService platform.UserService } const ( @@ -59,12 +60,12 @@ func NewDashboardHandler(mappingService platform.UserResourceMappingService, lab h.HandlerFunc("DELETE", dashboardsIDCellsIDPath, h.handleDeleteDashboardCell) h.HandlerFunc("PATCH", dashboardsIDCellsIDPath, h.handlePatchDashboardCell) - h.HandlerFunc("POST", dashboardsIDMembersPath, newPostMemberHandler(h.UserResourceMappingService, platform.DashboardResourceType, platform.Member)) - h.HandlerFunc("GET", dashboardsIDMembersPath, newGetMembersHandler(h.UserResourceMappingService, platform.Member)) + h.HandlerFunc("POST", dashboardsIDMembersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.DashboardResourceType, platform.Member)) + h.HandlerFunc("GET", dashboardsIDMembersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.DashboardResourceType, platform.Member)) h.HandlerFunc("DELETE", dashboardsIDMembersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Member)) - h.HandlerFunc("POST", dashboardsIDOwnersPath, newPostMemberHandler(h.UserResourceMappingService, platform.DashboardResourceType, platform.Owner)) - h.HandlerFunc("GET", dashboardsIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, platform.Owner)) + h.HandlerFunc("POST", dashboardsIDOwnersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.DashboardResourceType, platform.Owner)) + h.HandlerFunc("GET", dashboardsIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.DashboardResourceType, platform.Owner)) h.HandlerFunc("DELETE", dashboardsIDOwnersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Owner)) h.HandlerFunc("GET", dashboardsIDLabelsPath, newGetLabelsHandler(h.LabelService)) diff --git a/http/org_service.go b/http/org_service.go index a4bd3061d8..e2a431af14 100644 --- a/http/org_service.go +++ b/http/org_service.go @@ -24,6 +24,7 @@ type OrgHandler struct { UserResourceMappingService platform.UserResourceMappingService SecretService platform.SecretService LabelService platform.LabelService + UserService platform.UserService } const ( @@ -57,12 +58,12 @@ func NewOrgHandler(mappingService platform.UserResourceMappingService, h.HandlerFunc("PATCH", organizationsIDPath, h.handlePatchOrg) h.HandlerFunc("DELETE", organizationsIDPath, h.handleDeleteOrg) - h.HandlerFunc("POST", organizationsIDMembersPath, newPostMemberHandler(h.UserResourceMappingService, platform.OrgResourceType, platform.Member)) - h.HandlerFunc("GET", organizationsIDMembersPath, newGetMembersHandler(h.UserResourceMappingService, platform.Member)) + h.HandlerFunc("POST", organizationsIDMembersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.OrgResourceType, platform.Member)) + h.HandlerFunc("GET", organizationsIDMembersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.OrgResourceType, platform.Member)) h.HandlerFunc("DELETE", organizationsIDMembersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Member)) - h.HandlerFunc("POST", organizationsIDOwnersPath, newPostMemberHandler(h.UserResourceMappingService, platform.OrgResourceType, platform.Owner)) - h.HandlerFunc("GET", organizationsIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, platform.Owner)) + h.HandlerFunc("POST", organizationsIDOwnersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.OrgResourceType, platform.Owner)) + h.HandlerFunc("GET", organizationsIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.OrgResourceType, platform.Owner)) h.HandlerFunc("DELETE", organizationsIDOwnersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Owner)) h.HandlerFunc("GET", organizationsIDSecretsPath, h.handleGetSecrets) diff --git a/http/swagger.yml b/http/swagger.yml index 6bd81f13a1..f3594cdb1a 100644 --- a/http/swagger.yml +++ b/http/swagger.yml @@ -316,7 +316,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Users" + $ref: "#/components/schemas/ResourceMembers" default: description: unexpected error content: @@ -341,14 +341,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/AddResourceMemberRequestBody" responses: '201': description: member added to telegraf content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/ResourceMember" default: description: unexpected error content: @@ -402,7 +402,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Users" + $ref: "#/components/schemas/ResourceOwners" default: description: unexpected error content: @@ -427,14 +427,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/AddResourceMemberRequestBody" responses: '201': description: telegraf config owner added content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/ResourceOwner" default: description: unexpected error content: @@ -1252,7 +1252,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Users" + $ref: "#/components/schemas/ResourceMembers" default: description: unexpected error content: @@ -1277,14 +1277,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/AddResourceMemberRequestBody" responses: '201': description: added to view members content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/ResourceMember" default: description: unexpected error content: @@ -1338,7 +1338,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Users" + $ref: "#/components/schemas/ResourceOwners" default: description: unexpected error content: @@ -1363,14 +1363,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/AddResourceMemberRequestBody" responses: '201': description: added to view owners content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/ResourceOwner" default: description: unexpected error content: @@ -1817,7 +1817,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Users" + $ref: "#/components/schemas/ResourceMembers" default: description: unexpected error content: @@ -1842,14 +1842,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/AddResourceMemberRequestBody" responses: '201': description: added to dashboard members content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/ResourceMember" default: description: unexpected error content: @@ -1903,7 +1903,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Users" + $ref: "#/components/schemas/ResourceOwners" default: description: unexpected error content: @@ -1928,14 +1928,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/AddResourceMemberRequestBody" responses: '201': description: added to dashboard owners content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/ResourceOwner" default: description: unexpected error content: @@ -2584,7 +2584,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Users" + $ref: "#/components/schemas/ResourceOwners" default: description: unexpected error content: @@ -2609,14 +2609,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/AddResourceMemberRequestBody" responses: '201': description: member added to bucket content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/ResourceMember" default: description: unexpected error content: @@ -2670,7 +2670,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Users" + $ref: "#/components/schemas/ResourceMembers" default: description: unexpected error content: @@ -2695,14 +2695,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/AddResourceMemberRequestBody" responses: '201': description: bucket owner added content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/ResourceOwner" default: description: unexpected error content: @@ -3069,7 +3069,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Users" + $ref: "#/components/schemas/ResourceOwners" default: description: unexpected error content: @@ -3094,14 +3094,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/AddResourceMemberRequestBody" responses: '201': description: added to organization created content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/ResourceMember" default: description: unexpected error content: @@ -3155,7 +3155,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Users" + $ref: "#/components/schemas/ResourceMembers" default: description: unexpected error content: @@ -3180,14 +3180,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/AddResourceMemberRequestBody" responses: '201': description: organization owner added content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/ResourceOwner" default: description: unexpected error content: @@ -3601,7 +3601,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Users" + $ref: "#/components/schemas/ResourceMembers" default: description: unexpected error content: @@ -3626,14 +3626,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/AddResourceMemberRequestBody" responses: '201': description: added to task members content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/ResourceMember" default: description: unexpected error content: @@ -3687,7 +3687,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Users" + $ref: "#/components/schemas/ResourceOwners" default: description: unexpected error content: @@ -3712,14 +3712,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/AddResourceMemberRequestBody" responses: '201': description: added to task owners content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/ResourceOwner" default: description: unexpected error content: @@ -3789,7 +3789,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/UserResponse" + $ref: "#/components/schemas/User" default: description: unexpected error content: @@ -3814,7 +3814,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/UserResponse" + $ref: "#/components/schemas/User" default: description: unexpected error content: @@ -3899,7 +3899,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/UserResponse" + $ref: "#/components/schemas/User" default: description: unsuccessful authentication content: @@ -4421,25 +4421,9 @@ components: format: uri required: [name, organization, flux] Tasks: - type: object - properties: - tasks: - type: array - items: - $ref: "#/components/schemas/Task" - links: - $ref: "#/components/schemas/Links" - UserResponse: - type: object - properties: - user: - $ref: "#/components/schemas/User" - links: - type: object - properties: - self: - type: string - format: uri + type: array + items: + $ref: "#/components/schemas/Task" User: properties: id: @@ -4454,6 +4438,19 @@ components: enum: - active - inactive + links: + type: object + readOnly: true + example: + self: "/api/v2/users/1" + log: "/api/v2/users/1/log" + properties: + self: + type: string + format: uri + log: + type: string + format: uri required: [name] Users: type: object @@ -4467,7 +4464,53 @@ components: users: type: array items: - $ref: "#/components/schemas/UserResponse" + $ref: "#/components/schemas/User" + ResourceMember: + allOf: + - $ref: "#/components/schemas/User" + - type: object + properties: + role: + type: string + default: member + enum: + - member + ResourceMembers: + type: object + properties: + links: + type: object + properties: + self: + type: string + format: uri + users: + type: array + items: + $ref: "#/components/schemas/ResourceMember" + ResourceOwner: + allOf: + - $ref: "#/components/schemas/User" + - type: object + properties: + role: + type: string + default: owner + enum: + - owner + ResourceOwners: + type: object + properties: + links: + type: object + properties: + self: + type: string + format: uri + users: + type: array + items: + $ref: "#/components/schemas/ResourceOwner" FluxSuggestions: type: object properties: @@ -6045,6 +6088,15 @@ components: type: string required: - password + AddResourceMemberRequestBody: + type: object + properties: + id: + type: string + name: + type: string + required: + - id Check: type: object required: diff --git a/http/task_service.go b/http/task_service.go index b66361475c..1d14fa6ab9 100644 --- a/http/task_service.go +++ b/http/task_service.go @@ -30,6 +30,7 @@ type TaskHandler struct { OrganizationService platform.OrganizationService UserResourceMappingService platform.UserResourceMappingService LabelService platform.LabelService + UserService platform.UserService } const ( @@ -68,12 +69,12 @@ func NewTaskHandler(mappingService platform.UserResourceMappingService, labelSer h.HandlerFunc("GET", tasksIDLogsPath, h.handleGetLogs) h.HandlerFunc("GET", tasksIDRunsIDLogsPath, h.handleGetLogs) - h.HandlerFunc("POST", tasksIDMembersPath, newPostMemberHandler(h.UserResourceMappingService, platform.TaskResourceType, platform.Member)) - h.HandlerFunc("GET", tasksIDMembersPath, newGetMembersHandler(h.UserResourceMappingService, platform.Member)) + h.HandlerFunc("POST", tasksIDMembersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.TaskResourceType, platform.Member)) + h.HandlerFunc("GET", tasksIDMembersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.TaskResourceType, platform.Member)) h.HandlerFunc("DELETE", tasksIDMembersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Member)) - h.HandlerFunc("POST", tasksIDOwnersPath, newPostMemberHandler(h.UserResourceMappingService, platform.TaskResourceType, platform.Owner)) - h.HandlerFunc("GET", tasksIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, platform.Owner)) + h.HandlerFunc("POST", tasksIDOwnersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.TaskResourceType, platform.Owner)) + h.HandlerFunc("GET", tasksIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.TaskResourceType, platform.Owner)) h.HandlerFunc("DELETE", tasksIDOwnersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Owner)) h.HandlerFunc("GET", tasksIDRunsPath, h.handleGetRuns) diff --git a/http/telegraf.go b/http/telegraf.go index 5affd83bfe..b8149028b3 100644 --- a/http/telegraf.go +++ b/http/telegraf.go @@ -23,11 +23,13 @@ type TelegrafHandler struct { TelegrafService platform.TelegrafConfigStore UserResourceMappingService platform.UserResourceMappingService LabelService platform.LabelService + UserService platform.UserService } const ( telegrafsPath = "/api/v2/telegrafs" telegrafsIDPath = "/api/v2/telegrafs/:id" + telegrafsIDMembersPath = "/api/v2/telegrafs/:id/members" telegrafsIDMembersIDPath = "/api/v2/telegrafs/:id/members/:userID" telegrafsIDOwnersPath = "/api/v2/telegrafs/:id/owners" telegrafsIDOwnersIDPath = "/api/v2/telegrafs/:id/owners/:userID" @@ -56,12 +58,12 @@ func NewTelegrafHandler( h.HandlerFunc("DELETE", telegrafsIDPath, h.handleDeleteTelegraf) h.HandlerFunc("PUT", telegrafsIDPath, h.handlePutTelegraf) - h.HandlerFunc("POST", telegrafsIDMembersIDPath, newPostMemberHandler(h.UserResourceMappingService, platform.TelegrafResourceType, platform.Member)) - h.HandlerFunc("GET", telegrafsIDMembersIDPath, newGetMembersHandler(h.UserResourceMappingService, platform.Member)) + h.HandlerFunc("POST", telegrafsIDMembersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.TelegrafResourceType, platform.Member)) + h.HandlerFunc("GET", telegrafsIDMembersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.TelegrafResourceType, platform.Member)) h.HandlerFunc("DELETE", telegrafsIDMembersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Member)) - h.HandlerFunc("POST", telegrafsIDOwnersPath, newPostMemberHandler(h.UserResourceMappingService, platform.TelegrafResourceType, platform.Owner)) - h.HandlerFunc("GET", telegrafsIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, platform.Owner)) + h.HandlerFunc("POST", telegrafsIDOwnersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.TelegrafResourceType, platform.Owner)) + h.HandlerFunc("GET", telegrafsIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.TelegrafResourceType, platform.Owner)) h.HandlerFunc("DELETE", telegrafsIDOwnersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Owner)) h.HandlerFunc("GET", telegrafsIDLabelsPath, newGetLabelsHandler(h.LabelService)) diff --git a/http/user_resource_mapping_service.go b/http/user_resource_mapping_service.go index 14383896e6..bdace2b3f7 100644 --- a/http/user_resource_mapping_service.go +++ b/http/user_resource_mapping_service.go @@ -21,37 +21,39 @@ type UserResourceMappingService struct { BasePath string } -type userResourceResponse struct { - Links map[string]string `json:"links"` - platform.UserResourceMapping +type resourceUserResponse struct { + Role platform.UserType `json:"role"` + *userResponse } -func newUserResourceResponse(u *platform.UserResourceMapping) *userResourceResponse { - return &userResourceResponse{ +func newResourceUserResponse(u *platform.User, userType platform.UserType) *resourceUserResponse { + return &resourceUserResponse{ + Role: userType, + userResponse: newUserResponse(u), + } +} + +type resourceUsersResponse struct { + Links map[string]string `json:"links"` + Users []*resourceUserResponse `json:"users"` +} + +func newResourceUsersResponse(opts platform.FindOptions, f platform.UserResourceMappingFilter, users []*platform.User) *resourceUsersResponse { + rs := resourceUsersResponse{ Links: map[string]string{ - "user": fmt.Sprintf("/api/v2/users/%s", u.UserID), - "resource": fmt.Sprintf("/api/v2/%ss/%s", u.ResourceType, u.ResourceID), + "self": fmt.Sprintf("/api/v2/%ss/%s/%ss", f.ResourceType, f.ResourceID, f.UserType), }, - UserResourceMapping: *u, + Users: make([]*resourceUserResponse, 0, len(users)), } -} -type userResourcesResponse struct { - UserResourceMappings []*userResourceResponse `json:"userResourceMappings"` -} - -func newUserResourcesResponse(opt platform.FindOptions, f platform.UserResourceMappingFilter, ms []*platform.UserResourceMapping) *userResourcesResponse { - rs := make([]*userResourceResponse, 0, len(ms)) - for _, m := range ms { - rs = append(rs, newUserResourceResponse(m)) - } - return &userResourcesResponse{ - UserResourceMappings: rs, + for _, user := range users { + rs.Users = append(rs.Users, newResourceUserResponse(user, f.UserType)) } + return &rs } // newPostMemberHandler returns a handler func for a POST to /members or /owners endpoints -func newPostMemberHandler(s platform.UserResourceMappingService, resourceType platform.ResourceType, userType platform.UserType) http.HandlerFunc { +func newPostMemberHandler(s platform.UserResourceMappingService, userService platform.UserService, resourceType platform.ResourceType, userType platform.UserType) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -61,6 +63,12 @@ func newPostMemberHandler(s platform.UserResourceMappingService, resourceType pl return } + user, err := userService.FindUserByID(ctx, req.MemberID) + if err != nil { + EncodeError(ctx, err, w) + return + } + mapping := &platform.UserResourceMapping{ ResourceID: req.ResourceID, ResourceType: resourceType, @@ -73,7 +81,7 @@ func newPostMemberHandler(s platform.UserResourceMappingService, resourceType pl return } - if err := encodeResponse(ctx, w, http.StatusCreated, newUserResourceResponse(mapping)); err != nil { + if err := encodeResponse(ctx, w, http.StatusCreated, newResourceUserResponse(user, userType)); err != nil { EncodeError(ctx, err, w) return } @@ -112,8 +120,8 @@ func decodePostMemberRequest(ctx context.Context, r *http.Request) (*postMemberR }, nil } -// newPostMemberHandler returns a handler func for a GET to /members or /owners endpoints -func newGetMembersHandler(s platform.UserResourceMappingService, userType platform.UserType) http.HandlerFunc { +// newGetMembersHandler returns a handler func for a GET to /members or /owners endpoints +func newGetMembersHandler(s platform.UserResourceMappingService, userService platform.UserService, resourceType platform.ResourceType, userType platform.UserType) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -124,8 +132,9 @@ func newGetMembersHandler(s platform.UserResourceMappingService, userType platfo } filter := platform.UserResourceMappingFilter{ - ResourceID: req.ResourceID, - UserType: userType, + ResourceID: req.ResourceID, + ResourceType: resourceType, + UserType: userType, } opts := platform.FindOptions{} @@ -135,7 +144,18 @@ func newGetMembersHandler(s platform.UserResourceMappingService, userType platfo return } - if err := encodeResponse(ctx, w, http.StatusOK, newUserResourcesResponse(opts, filter, mappings)); err != nil { + users := make([]*platform.User, 0, len(mappings)) + for _, m := range mappings { + user, err := userService.FindUserByID(ctx, m.UserID) + if err != nil { + EncodeError(ctx, err, w) + return + } + + users = append(users, user) + } + + if err := encodeResponse(ctx, w, http.StatusOK, newResourceUsersResponse(opts, filter, users)); err != nil { EncodeError(ctx, err, w) return } diff --git a/http/user_resource_mapping_test.go b/http/user_resource_mapping_test.go new file mode 100644 index 0000000000..4f6feacd84 --- /dev/null +++ b/http/user_resource_mapping_test.go @@ -0,0 +1,359 @@ +package http + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/influxdata/platform" + "github.com/influxdata/platform/mock" + "github.com/julienschmidt/httprouter" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" +) + +func TestUserResourceMappingService_GetMembersHandler(t *testing.T) { + type fields struct { + userService platform.UserService + userResourceMappingService platform.UserResourceMappingService + } + type args struct { + resourceID string + userType platform.UserType + } + type wants struct { + statusCode int + contentType string + body string + } + + tests := []struct { + name string + fields fields + args args + wants wants + }{ + { + name: "get members", + fields: fields{ + userService: &mock.UserService{ + FindUserByIDFn: func(ctx context.Context, id platform.ID) (*platform.User, error) { + return &platform.User{ID: id, Name: fmt.Sprintf("user%s", id)}, nil + }, + }, + userResourceMappingService: &mock.UserResourceMappingService{ + FindMappingsFn: func(ctx context.Context, filter platform.UserResourceMappingFilter) ([]*platform.UserResourceMapping, int, error) { + ms := []*platform.UserResourceMapping{ + { + ResourceID: filter.ResourceID, + ResourceType: filter.ResourceType, + UserType: filter.UserType, + UserID: 1, + }, + { + ResourceID: filter.ResourceID, + ResourceType: filter.ResourceType, + UserType: filter.UserType, + UserID: 2, + }, + } + return ms, len(ms), nil + }, + }, + }, + args: args{ + resourceID: "0000000000000099", + userType: platform.Member, + }, + wants: wants{ + statusCode: http.StatusOK, + contentType: "application/json; charset=utf-8", + body: ` +{ + "links": { + "self": "/api/v2/%ss/0000000000000099/members" + }, + "users": [ + { + "links": { + "log": "/api/v2/users/0000000000000001/log", + "self": "/api/v2/users/0000000000000001" + }, + "id": "0000000000000001", + "name": "user0000000000000001", + "role": "member" + }, + { + "links": { + "log": "/api/v2/users/0000000000000002/log", + "self": "/api/v2/users/0000000000000002" + }, + "id": "0000000000000002", + "name": "user0000000000000002", + "role": "member" + } + ] +}`, + }, + }, + + { + name: "get owners", + fields: fields{ + userService: &mock.UserService{ + FindUserByIDFn: func(ctx context.Context, id platform.ID) (*platform.User, error) { + return &platform.User{ID: id, Name: fmt.Sprintf("user%s", id)}, nil + }, + }, + userResourceMappingService: &mock.UserResourceMappingService{ + FindMappingsFn: func(ctx context.Context, filter platform.UserResourceMappingFilter) ([]*platform.UserResourceMapping, int, error) { + ms := []*platform.UserResourceMapping{ + { + ResourceID: filter.ResourceID, + ResourceType: filter.ResourceType, + UserType: filter.UserType, + UserID: 1, + }, + { + ResourceID: filter.ResourceID, + ResourceType: filter.ResourceType, + UserType: filter.UserType, + UserID: 2, + }, + } + return ms, len(ms), nil + }, + }, + }, + args: args{ + resourceID: "0000000000000099", + userType: platform.Owner, + }, + wants: wants{ + statusCode: http.StatusOK, + contentType: "application/json; charset=utf-8", + body: ` +{ + "links": { + "self": "/api/v2/%ss/0000000000000099/owners" + }, + "users": [ + { + "links": { + "log": "/api/v2/users/0000000000000001/log", + "self": "/api/v2/users/0000000000000001" + }, + "id": "0000000000000001", + "name": "user0000000000000001", + "role": "owner" + }, + { + "links": { + "log": "/api/v2/users/0000000000000002/log", + "self": "/api/v2/users/0000000000000002" + }, + "id": "0000000000000002", + "name": "user0000000000000002", + "role": "owner" + } + ] +}`, + }, + }, + } + + for _, tt := range tests { + resourceTypes := []platform.ResourceType{ + platform.BucketResourceType, + platform.DashboardResourceType, + platform.OrgResourceType, + platform.TaskResourceType, + platform.TelegrafResourceType, + platform.ViewResourceType, + } + + for _, resourceType := range resourceTypes { + t.Run(tt.name+"_"+string(resourceType), func(t *testing.T) { + r := httptest.NewRequest("GET", "http://any.url", nil) + r = r.WithContext(context.WithValue( + context.TODO(), + httprouter.ParamsKey, + httprouter.Params{ + { + Key: "id", + Value: tt.args.resourceID, + }, + })) + + w := httptest.NewRecorder() + h := newGetMembersHandler(tt.fields.userResourceMappingService, tt.fields.userService, resourceType, tt.args.userType) + h.ServeHTTP(w, r) + + res := w.Result() + content := res.Header.Get("Content-Type") + body, _ := ioutil.ReadAll(res.Body) + + if res.StatusCode != tt.wants.statusCode { + t.Errorf("%q. GetMembersHandler() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) + } + if tt.wants.contentType != "" && content != tt.wants.contentType { + t.Errorf("%q. GetMembersHandler() = %v, want %v", tt.name, content, tt.wants.contentType) + } + if eq, _ := jsonEqual(string(body), fmt.Sprintf(tt.wants.body, resourceType)); tt.wants.body != "" && !eq { + t.Errorf("%q. GetMembersHandler() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), fmt.Sprintf(tt.wants.body, resourceType)) + } + }) + } + } +} + +func TestUserResourceMappingService_PostMembersHandler(t *testing.T) { + type fields struct { + userService platform.UserService + userResourceMappingService platform.UserResourceMappingService + } + type args struct { + resourceID string + userType platform.UserType + user platform.User + } + type wants struct { + statusCode int + contentType string + body string + } + + tests := []struct { + name string + fields fields + args args + wants wants + }{ + { + name: "post members", + fields: fields{ + userService: &mock.UserService{ + FindUserByIDFn: func(ctx context.Context, id platform.ID) (*platform.User, error) { + return &platform.User{ID: id, Name: fmt.Sprintf("user%s", id)}, nil + }, + }, + userResourceMappingService: &mock.UserResourceMappingService{ + CreateMappingFn: func(ctx context.Context, m *platform.UserResourceMapping) error { + return nil + }, + }, + }, + args: args{ + resourceID: "0000000000000099", + user: platform.User{ + ID: 1, + Name: "user0000000000000001", + }, + userType: platform.Member, + }, + wants: wants{ + statusCode: http.StatusCreated, + contentType: "application/json; charset=utf-8", + body: ` +{ + "links": { + "log": "/api/v2/users/0000000000000001/log", + "self": "/api/v2/users/0000000000000001" + }, + "id": "0000000000000001", + "name": "user0000000000000001", + "role": "member" +}`, + }, + }, + + { + name: "post owners", + fields: fields{ + userService: &mock.UserService{ + FindUserByIDFn: func(ctx context.Context, id platform.ID) (*platform.User, error) { + return &platform.User{ID: id, Name: fmt.Sprintf("user%s", id)}, nil + }, + }, + userResourceMappingService: &mock.UserResourceMappingService{ + CreateMappingFn: func(ctx context.Context, m *platform.UserResourceMapping) error { + return nil + }, + }, + }, + args: args{ + resourceID: "0000000000000099", + user: platform.User{ + ID: 2, + Name: "user0000000000000002", + }, + userType: platform.Owner, + }, + wants: wants{ + statusCode: http.StatusCreated, + contentType: "application/json; charset=utf-8", + body: ` +{ + "links": { + "log": "/api/v2/users/0000000000000002/log", + "self": "/api/v2/users/0000000000000002" + }, + "id": "0000000000000002", + "name": "user0000000000000002", + "role": "owner" +}`, + }, + }, + } + + for _, tt := range tests { + resourceTypes := []platform.ResourceType{ + platform.BucketResourceType, + platform.DashboardResourceType, + platform.OrgResourceType, + platform.TaskResourceType, + platform.TelegrafResourceType, + platform.ViewResourceType, + } + + for _, resourceType := range resourceTypes { + t.Run(tt.name+"_"+string(resourceType), func(t *testing.T) { + b, err := json.Marshal(tt.args.user) + if err != nil { + t.Fatalf("failed to unmarshal user: %v", err) + } + + r := httptest.NewRequest("POST", "http://any.url", bytes.NewReader(b)) + r = r.WithContext(context.WithValue( + context.TODO(), + httprouter.ParamsKey, + httprouter.Params{ + { + Key: "id", + Value: tt.args.resourceID, + }, + })) + + w := httptest.NewRecorder() + h := newPostMemberHandler(tt.fields.userResourceMappingService, tt.fields.userService, resourceType, tt.args.userType) + h.ServeHTTP(w, r) + + res := w.Result() + content := res.Header.Get("Content-Type") + body, _ := ioutil.ReadAll(res.Body) + + if res.StatusCode != tt.wants.statusCode { + t.Errorf("%q. PostMembersHandler() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) + } + if tt.wants.contentType != "" && content != tt.wants.contentType { + t.Errorf("%q. PostMembersHandler() = %v, want %v", tt.name, content, tt.wants.contentType) + } + if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { + t.Errorf("%q. PostMembersHandler() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) + } + }) + } + } +} diff --git a/http/view_service.go b/http/view_service.go index 086191071c..9119c88e03 100644 --- a/http/view_service.go +++ b/http/view_service.go @@ -18,6 +18,7 @@ type ViewHandler struct { ViewService platform.ViewService UserResourceMappingService platform.UserResourceMappingService LabelService platform.LabelService + UserService platform.UserService } const ( @@ -46,12 +47,12 @@ func NewViewHandler(mappingService platform.UserResourceMappingService, labelSer h.HandlerFunc("DELETE", viewsIDPath, h.handleDeleteView) h.HandlerFunc("PATCH", viewsIDPath, h.handlePatchView) - h.HandlerFunc("POST", viewsIDMembersPath, newPostMemberHandler(h.UserResourceMappingService, platform.ViewResourceType, platform.Member)) - h.HandlerFunc("GET", viewsIDMembersPath, newGetMembersHandler(h.UserResourceMappingService, platform.Member)) + h.HandlerFunc("POST", viewsIDMembersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.ViewResourceType, platform.Member)) + h.HandlerFunc("GET", viewsIDMembersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.ViewResourceType, platform.Member)) h.HandlerFunc("DELETE", viewsIDMembersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Member)) - h.HandlerFunc("POST", viewsIDOwnersPath, newPostMemberHandler(h.UserResourceMappingService, platform.ViewResourceType, platform.Owner)) - h.HandlerFunc("GET", viewsIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, platform.Owner)) + h.HandlerFunc("POST", viewsIDOwnersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.ViewResourceType, platform.Owner)) + h.HandlerFunc("GET", viewsIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.ViewResourceType, platform.Owner)) h.HandlerFunc("DELETE", viewsIDOwnersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Owner)) h.HandlerFunc("GET", viewsIDLabelsPath, newGetLabelsHandler(h.LabelService)) diff --git a/mock/user_service.go b/mock/user_service.go new file mode 100644 index 0000000000..d884937ae4 --- /dev/null +++ b/mock/user_service.go @@ -0,0 +1,41 @@ +package mock + +import ( + "context" + + "github.com/influxdata/platform" +) + +var _ platform.UserService = &UserService{} + +type UserService struct { + FindUserByIDFn func(context.Context, platform.ID) (*platform.User, error) + FindUserFn func(context.Context, platform.UserFilter) (*platform.User, error) + FindUsersFn func(context.Context, platform.UserFilter, ...platform.FindOptions) ([]*platform.User, int, error) + CreateUserFn func(context.Context, *platform.User) error + UpdateUserFn func(context.Context, platform.ID, platform.UserUpdate) (*platform.User, error) + DeleteUserFn func(context.Context, platform.ID) error +} + +func (s *UserService) FindUserByID(ctx context.Context, id platform.ID) (*platform.User, error) { + return s.FindUserByIDFn(ctx, id) +} + +func (s *UserService) FindUser(ctx context.Context, filter platform.UserFilter) (*platform.User, error) { + return s.FindUserFn(ctx, filter) +} + +func (s *UserService) FindUsers(ctx context.Context, filter platform.UserFilter, opt ...platform.FindOptions) ([]*platform.User, int, error) { + return s.FindUsersFn(ctx, filter, opt...) +} +func (s *UserService) CreateUser(ctx context.Context, u *platform.User) error { + return s.CreateUserFn(ctx, u) +} + +func (s *UserService) UpdateUser(ctx context.Context, id platform.ID, update platform.UserUpdate) (*platform.User, error) { + return s.UpdateUserFn(ctx, id, update) +} + +func (s *UserService) DeleteUser(ctx context.Context, id platform.ID) error { + return s.DeleteUserFn(ctx, id) +}