diff --git a/api/openapi/things.yml b/api/openapi/things.yml index 9e9179439a..f9b2912a1c 100644 --- a/api/openapi/things.yml +++ b/api/openapi/things.yml @@ -540,60 +540,6 @@ paths: "500": $ref: "#/components/responses/ServiceError" - /channels/{chanID}/assign: - post: - summary: Assigns a member to a channel - description: | - Assigns a specific member to a channel that is identifier by the channel ID. - tags: - - Channels - parameters: - - $ref: "#/components/parameters/chanID" - requestBody: - $ref: "#/components/requestBodies/AssignReq" - security: - - bearerAuth: [] - responses: - "200": - description: Thing shared. - "400": - description: Failed due to malformed thing's ID. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /channels/{chanID}/unassign: - post: - summary: Unassigns a member from a channel - description: | - Unassigns a specific member from a channel that is identifier by the channel ID. - tags: - - Channels - parameters: - - $ref: "#/components/parameters/chanID" - requestBody: - $ref: "#/components/requestBodies/AssignReq" - security: - - bearerAuth: [] - responses: - "200": - description: Thing unshared. - "400": - description: Failed due to malformed thing's ID. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /channels/{chanID}/users/assign: post: summary: Assigns a member to a channel diff --git a/api/openapi/users.yml b/api/openapi/users.yml index 381e1718a7..a9e2ba57c2 100644 --- a/api/openapi/users.yml +++ b/api/openapi/users.yml @@ -690,60 +690,6 @@ paths: "500": $ref: "#/components/responses/ServiceError" - /groups/{groupID}/members/assign: - post: - summary: Assigns a member to a group - description: | - Assigns a specific member to a group that is identifier by the group ID. - tags: - - Groups - parameters: - - $ref: "#/components/parameters/GroupID" - requestBody: - $ref: "#/components/requestBodies/AssignReq" - security: - - bearerAuth: [] - responses: - "200": - description: Member assigned. - "400": - description: Failed due to malformed group's ID. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /groups/{groupID}/members/unassign: - post: - summary: Unassigns a member to a group - description: | - Unassigns a specific member to a group that is identifier by the group ID. - tags: - - Groups - parameters: - - $ref: "#/components/parameters/GroupID" - requestBody: - $ref: "#/components/requestBodies/AssignReq" - security: - - bearerAuth: [] - responses: - "200": - description: Member assigned. - "400": - description: Failed due to malformed group's ID. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /groups/{groupID}/users/assign: post: summary: Assigns a user to a group diff --git a/cli/channels.go b/cli/channels.go index 729eb49cb0..6af6ec6cd9 100644 --- a/cli/channels.go +++ b/cli/channels.go @@ -166,12 +166,152 @@ var cmdChannels = []cobra.Command{ logJSON(channel) }, }, + { + Use: "assign user ", + Short: "Assign user", + Long: "Assign user to a channel\n" + + "Usage:\n" + + "\tmainflux-cli channels assign user '[\"\", \"\"]' $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 5 { + logUsage(cmd.Use) + return + } + var userIDs []string + if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { + logError(err) + return + } + if err := sdk.AddUserToChannel(args[2], mfxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { + logError(err) + return + } + logOK() + }, + }, + { + Use: "unassign user ", + Short: "Unassign user", + Long: "Unassign user from a channel\n" + + "Usage:\n" + + "\tmainflux-cli channels unassign user '[\"\", \"\"]' $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 5 { + logUsage(cmd.Use) + return + } + var userIDs []string + if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { + logError(err) + return + } + if err := sdk.RemoveUserFromChannel(args[2], mfxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { + logError(err) + return + } + logOK() + }, + }, + { + Use: "assign group ", + Short: "Assign group", + Long: "Assign group to a channel\n" + + "Usage:\n" + + "\tmainflux-cli channels assign group '[\"\", \"\"]' $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 5 { + logUsage(cmd.Use) + return + } + var groupIDs []string + if err := json.Unmarshal([]byte(args[0]), &groupIDs); err != nil { + logError(err) + return + } + if err := sdk.AddUserGroupToChannel(args[1], mfxsdk.UserGroupsRequest{UserGroupIDs: groupIDs}, args[2]); err != nil { + logError(err) + return + } + logOK() + }, + }, + { + Use: "unassign group ", + Short: "Unassign group", + Long: "Unassign group from a channel\n" + + "Usage:\n" + + "\tmainflux-cli channels unassign group '[\"\", \"\"]' $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 5 { + logUsage(cmd.Use) + return + } + var groupIDs []string + if err := json.Unmarshal([]byte(args[0]), &groupIDs); err != nil { + logError(err) + return + } + if err := sdk.RemoveUserGroupFromChannel(args[1], mfxsdk.UserGroupsRequest{UserGroupIDs: groupIDs}, args[2]); err != nil { + logError(err) + return + } + logOK() + }, + }, + { + Use: "users ", + Short: "List users", + Long: "List users of a channel\n" + + "Usage:\n" + + "\tmainflux-cli channels users $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + logUsage(cmd.Use) + return + } + pm := mfxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + ul, err := sdk.ListChannelUsers(args[0], pm, args[1]) + if err != nil { + logError(err) + return + } + + logJSON(ul) + }, + }, + { + Use: "groups ", + Short: "List groups", + Long: "List groups of a channel\n" + + "Usage:\n" + + "\tmainflux-cli channels groups $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + logUsage(cmd.Use) + return + } + pm := mfxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + ul, err := sdk.ListChannelUserGroups(args[0], pm, args[1]) + if err != nil { + logError(err) + return + } + + logJSON(ul) + }, + }, } // NewChannelsCmd returns channels command. func NewChannelsCmd() *cobra.Command { cmd := cobra.Command{ - Use: "channels [create | get | update | delete | connections | not-connected]", + Use: "channels [create | get | update | delete | connections | not-connected | assign | unassign | users | groups]", Short: "Channels management", Long: `Channels management: create, get, update or delete Channel and get list of Things connected or not connected to a Channel`, } diff --git a/cli/groups.go b/cli/groups.go index 1af24912df..2f73cb02a0 100644 --- a/cli/groups.go +++ b/cli/groups.go @@ -142,22 +142,22 @@ var cmdGroups = []cobra.Command{ }, }, { - Use: "assign ", - Short: "Assign member", - Long: "Assign members to a group\n" + + Use: "assign user ", + Short: "Assign user", + Long: "Assign user to a group\n" + "Usage:\n" + - "\tmainflux-cli groups assign '[\"\", \"\"]' $USERTOKEN\n", + "\tmainflux-cli groups assign user '[\"\", \"\"]' $USERTOKEN\n", Run: func(cmd *cobra.Command, args []string) { - if len(args) != 4 { + if len(args) != 5 { logUsage(cmd.Use) return } - var actions []string - if err := json.Unmarshal([]byte(args[0]), &actions); err != nil { + var userIDs []string + if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { logError(err) return } - if err := sdk.Assign(actions, args[1], args[2], args[3]); err != nil { + if err := sdk.AddUserToGroup(args[2], mfxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { logError(err) return } @@ -165,29 +165,35 @@ var cmdGroups = []cobra.Command{ }, }, { - Use: "unassign ", - Short: "Unassign member", - Long: "Unassign member from a group\n" + + Use: "unassign user ", + Short: "Unassign user", + Long: "Unassign user from a group\n" + "Usage:\n" + - "\tmainflux-cli groups unassign $USERTOKEN\n", + "\tmainflux-cli groups unassign user '[\"\", \"\"]' $USERTOKEN\n", Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { + if len(args) != 5 { logUsage(cmd.Use) return } - if err := sdk.Unassign(args[0], args[1], args[2]); err != nil { + var userIDs []string + if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { + logError(err) + return + } + if err := sdk.RemoveUserFromGroup(args[2], mfxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { logError(err) return } logOK() }, }, + { - Use: "members ", - Short: "Members list", - Long: "List group's members\n" + + Use: "users ", + Short: "List users", + Long: "List users in a group\n" + "Usage:\n" + - "\tmainflux-cli groups members $USERTOKEN", + "\tmainflux-cli groups users $USERTOKEN", Run: func(cmd *cobra.Command, args []string) { if len(args) != 2 { logUsage(cmd.Use) @@ -198,20 +204,20 @@ var cmdGroups = []cobra.Command{ Limit: Limit, Status: Status, } - up, err := sdk.Members(args[0], pm, args[1]) + users, err := sdk.ListGroupUsers(args[0], pm, args[1]) if err != nil { logError(err) return } - logJSON(up) + logJSON(users) }, }, { - Use: "membership ", - Short: "Membership list", - Long: "List memberships of a member\n" + + Use: "channels ", + Short: "List channels", + Long: "List channels in a group\n" + "Usage:\n" + - "\tmainflux-cli groups membership $USERTOKEN", + "\tmainflux-cli groups channels $USERTOKEN", Run: func(cmd *cobra.Command, args []string) { if len(args) != 2 { logUsage(cmd.Use) @@ -220,13 +226,14 @@ var cmdGroups = []cobra.Command{ pm := mfxsdk.PageMetadata{ Offset: Offset, Limit: Limit, + Status: Status, } - up, err := sdk.Memberships(args[0], pm, args[1]) + channels, err := sdk.ListGroupChannels(args[0], pm, args[1]) if err != nil { logError(err) return } - logJSON(up) + logJSON(channels) }, }, { @@ -276,7 +283,7 @@ var cmdGroups = []cobra.Command{ // NewGroupsCmd returns users command. func NewGroupsCmd() *cobra.Command { cmd := cobra.Command{ - Use: "groups [create | get | update | delete | assign | unassign | members | membership]", + Use: "groups [create | get | update | delete | assign | unassign | users | channels ]", Short: "Groups management", Long: `Groups management: create, update, delete group and assign and unassign member to groups"`, } diff --git a/cli/policies.go b/cli/policies.go deleted file mode 100644 index 9f784f7439..0000000000 --- a/cli/policies.go +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import ( - "encoding/json" - - mfxsdk "github.com/mainflux/mainflux/pkg/sdk/go" - "github.com/spf13/cobra" -) - -const ( - users = "users" - things = "things" -) - -var cmdPolicies = []cobra.Command{ - { - Use: "create [ users | things ] ", - Short: "Create policy", - Long: "Create a new policy\n" + - "Usage:\n" + - "\tmainflux-cli policies create users '[\"c_list\"]' $USERTOKEN\n" + - "\tmainflux-cli policies create things '[\"m_write\"]' $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { - logUsage(cmd.Use) - return - } - - var actions []string - if err := json.Unmarshal([]byte(args[3]), &actions); err != nil { - logError(err) - return - } - - policy := mfxsdk.Policy{ - Subject: args[1], - Object: args[2], - Actions: actions, - } - - switch args[0] { - case things: - if err := sdk.CreateThingPolicy(policy, args[4]); err != nil { - logError(err) - return - } - case users: - if err := sdk.CreateUserPolicy(policy, args[4]); err != nil { - logError(err) - return - } - default: - logUsage(cmd.Use) - } - }, - }, - { - Use: "update [ users | things ] ", - Short: "Update policy", - Long: "Update policy\n" + - "Usage:\n" + - "\tmainflux-cli policies update users '[\"c_list\"]' $USERTOKEN\n" + - "\tmainflux-cli policies update things '[\"m_write\"]' $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { - logUsage(cmd.Use) - return - } - - var actions []string - if err := json.Unmarshal([]byte(args[3]), &actions); err != nil { - logError(err) - return - } - - policy := mfxsdk.Policy{ - Subject: args[1], - Object: args[2], - Actions: actions, - } - - switch args[0] { - case things: - if err := sdk.UpdateThingPolicy(policy, args[4]); err != nil { - logError(err) - return - } - case users: - if err := sdk.UpdateUserPolicy(policy, args[4]); err != nil { - logError(err) - return - } - default: - logUsage(cmd.Use) - } - }, - }, - { - Use: "list [ users | things ] ", - Short: "List policies", - Long: "List policies\n" + - "Usage:\n" + - "\tmainflux-cli policies list users $USERTOKEN\n" + - "\tmainflux-cli policies list things $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsage(cmd.Use) - return - } - pm := mfxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - } - switch args[0] { - case things: - policies, err := sdk.ListThingPolicies(pm, args[1]) - if err != nil { - logError(err) - return - } - logJSON(policies) - return - case users: - policies, err := sdk.ListUserPolicies(pm, args[1]) - if err != nil { - logError(err) - return - } - - logJSON(policies) - return - default: - logUsage(cmd.Use) - } - }, - }, - { - Use: "remove [ users | things ] ", - Short: "Remove policy", - Long: "Removes a policy with the provided object and subject\n" + - "Usage:\n" + - "\tmainflux-cli policies remove users $USERTOKEN\n" + - "\tmainflux-cli policies remove things $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 4 { - logUsage(cmd.Use) - return - } - - policy := mfxsdk.Policy{ - Subject: args[1], - Object: args[2], - } - switch args[0] { - case things: - if err := sdk.DeleteThingPolicy(policy, args[3]); err != nil { - logError(err) - return - } - case users: - if err := sdk.DeleteUserPolicy(policy, args[3]); err != nil { - logError(err) - return - } - default: - logUsage(cmd.Use) - } - }, - }, - { - Use: "authorize [ users | things ] ", - Short: "Authorize access request", - Long: "Authorize subject over object with provided actions\n" + - "Usage:\n" + - "\tmainflux-cli policies authorize users \"c_list\" $USERTOKEN\n" + - "\tmainflux-cli policies authorize things \"m_read\" $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 6 { - logUsage(cmd.Use) - return - } - - areq := mfxsdk.AccessRequest{ - Subject: args[1], - Object: args[2], - Action: args[3], - EntityType: args[4], - } - - switch args[0] { - case users: - ok, err := sdk.AuthorizeUser(areq, args[5]) - if err != nil { - logError(err) - return - } - logJSON(ok) - case things: - ok, _, err := sdk.AuthorizeThing(areq, args[5]) - if err != nil { - logError(err) - return - } - logJSON(ok) - default: - logUsage(cmd.Use) - } - }, - }, -} - -// NewPolicyCmd returns policies command. -func NewPolicyCmd() *cobra.Command { - cmd := cobra.Command{ - Use: "policies [create | update | list | remove | authorize ]", - Short: "Policies management", - Long: `Policies management: create or update or list or delete or check policies`, - } - - for i := range cmdPolicies { - cmd.AddCommand(&cmdPolicies[i]) - } - - return &cmd -} diff --git a/cli/things.go b/cli/things.go index 2a6bd45ed2..5ac8299fd5 100644 --- a/cli/things.go +++ b/cli/things.go @@ -215,23 +215,45 @@ var cmdThings = []cobra.Command{ }, }, { - Use: "share ", + Use: "share ", Short: "Share thing with a user", Long: "Share thing with a user\n" + "Usage:\n" + - "\tmainflux-cli things share '[\"c_list\", \"c_delete\"]' $USERTOKEN\n", + "\tmainflux-cli things share $USERTOKEN\n", Run: func(cmd *cobra.Command, args []string) { if len(args) != 4 { logUsage(cmd.Use) return } - var actions []string - if err := json.Unmarshal([]byte(args[2]), &actions); err != nil { + req := mfxsdk.UsersRelationRequest{ + Relation: args[2], + UserIDs: []string{args[1]}, + } + err := sdk.ShareThing(args[0], req, args[3]) + if err != nil { logError(err) return } - err := sdk.ShareThing(args[0], args[1], actions, args[3]) + logOK() + }, + }, + { + Use: "unshare ", + Short: "Unshare thing with a user", + Long: "Unshare thing with a user\n" + + "Usage:\n" + + "\tmainflux-cli things share $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 4 { + logUsage(cmd.Use) + return + } + req := mfxsdk.UsersRelationRequest{ + Relation: args[2], + UserIDs: []string{args[1]}, + } + err := sdk.UnshareThing(args[0], req, args[3]) if err != nil { logError(err) return @@ -312,12 +334,36 @@ var cmdThings = []cobra.Command{ logJSON(cl) }, }, + { + Use: "users ", + Short: "List users", + Long: "List users of a thing\n" + + "Usage:\n" + + "\tmainflux-cli things users $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + logUsage(cmd.Use) + return + } + pm := mfxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + ul, err := sdk.ListThingUsers(args[0], pm, args[1]) + if err != nil { + logError(err) + return + } + + logJSON(ul) + }, + }, } // NewThingsCmd returns things command. func NewThingsCmd() *cobra.Command { cmd := cobra.Command{ - Use: "things [create | get | update | delete | share | connect | disconnect | connections | not-connected]", + Use: "things [create | get | update | delete | share | connect | disconnect | connections | not-connected | users ]", Short: "Things management", Long: `Things management: create, get, update, delete or share Thing, connect or disconnect Thing from Channel and get the list of Channels connected or disconnected from a Thing`, } diff --git a/cli/users.go b/cli/users.go index e0fff52ec6..658fb04db9 100644 --- a/cli/users.go +++ b/cli/users.go @@ -333,12 +333,92 @@ var cmdUsers = []cobra.Command{ logJSON(user) }, }, + + { + Use: "channels ", + Short: "List channels", + Long: "List channels of user\n" + + "Usage:\n" + + "\tmainflux-cli users channels \n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + logUsage(cmd.Use) + return + } + + pm := mfxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + + users, err := sdk.ListUserChannels(args[0], pm, args[1]) + if err != nil { + logError(err) + return + } + + logJSON(users) + }, + }, + + { + Use: "things ", + Short: "List things", + Long: "List things of user\n" + + "Usage:\n" + + "\tmainflux-cli users things \n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + logUsage(cmd.Use) + return + } + + pm := mfxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + + users, err := sdk.ListUserThings(args[0], pm, args[1]) + if err != nil { + logError(err) + return + } + + logJSON(users) + }, + }, + { + Use: "groups ", + Short: "List groups", + Long: "List groups of user\n" + + "Usage:\n" + + "\tmainflux-cli users groups \n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + logUsage(cmd.Use) + return + } + + pm := mfxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + + users, err := sdk.ListUserGroups(args[0], pm, args[1]) + if err != nil { + logError(err) + return + } + + logJSON(users) + }, + }, } // NewUsersCmd returns users command. func NewUsersCmd() *cobra.Command { cmd := cobra.Command{ - Use: "users [create | get | update | token | password | enable | disable]", + Use: "users [create | get | update | token | password | enable | disable | channels | things | groups]", Short: "Users management", Long: `Users management: create accounts and tokens"`, } diff --git a/cmd/cli/main.go b/cmd/cli/main.go index dc43d2453f..c0d837d13e 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -61,7 +61,6 @@ func main() { bootstrapCmd := cli.NewBootstrapCmd() certsCmd := cli.NewCertsCmd() subscriptionsCmd := cli.NewSubscriptionCmd() - policiesCmd := cli.NewPolicyCmd() configCmd := cli.NewConfigCmd() // Root Commands @@ -75,7 +74,6 @@ func main() { rootCmd.AddCommand(bootstrapCmd) rootCmd.AddCommand(certsCmd) rootCmd.AddCommand(subscriptionsCmd) - rootCmd.AddCommand(policiesCmd) rootCmd.AddCommand(configCmd) // Root Flags diff --git a/docker/nginx/nginx-key.conf b/docker/nginx/nginx-key.conf index 683eac18db..8e7f37a55c 100644 --- a/docker/nginx/nginx-key.conf +++ b/docker/nginx/nginx-key.conf @@ -50,10 +50,15 @@ http { server_name localhost; + location ~ ^/(channels)/(.+)/(things)/(.+) { + include snippets/proxy-headers.conf; + add_header Access-Control-Expose-Headers Location; + proxy_pass http://things:${MF_THINGS_HTTP_PORT}; + } # Proxy pass to users & groups id to things service for listing of channels # /users/{userID}/channels - Listing of channels belongs to userID # /groups/{userGroupID}/channels - Listing of channels belongs to userGroupID - location ~ ^/(users|groups)/(.+)/channels { + location ~ ^/(users|groups)/(.+)/(channels|things) { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; if ($request_method = GET) { @@ -66,14 +71,14 @@ http { # Proxy pass to channel id to users service for listing of channels # /channels/{channelID}/users - Listing of Users belongs to channelID # /channels/{channelID}/groups - Listing of User Groups belongs to channelID - location ~ ^/channels/(.+)/(users|groups) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://users:${MF_USERS_HTTP_PORT}; - break; - } - proxy_pass http://things:${MF_THINGS_HTTP_PORT}; + location ~ ^/(channels|things)/(.+)/(users|groups) { + include snippets/proxy-headers.conf; + add_header Access-Control-Expose-Headers Location; + if ($request_method = GET) { + proxy_pass http://users:${MF_USERS_HTTP_PORT}; + break; + } + proxy_pass http://things:${MF_THINGS_HTTP_PORT}; } # Proxy pass to users service location ~ ^/(users|groups|password|authorize) { diff --git a/internal/groups/service.go b/internal/groups/service.go index 0c8920e14b..23534d2047 100644 --- a/internal/groups/service.go +++ b/internal/groups/service.go @@ -216,8 +216,10 @@ func (svc service) ListGroups(ctx context.Context, token string, memberKind, mem return groups.Page{}, fmt.Errorf("invalid member kind") } - if len(ids) <= 0 { - return groups.Page{}, errors.ErrNotFound + if len(ids) == 0 { + return groups.Page{ + PageMeta: gm.PageMeta, + }, nil } return svc.groups.RetrieveByIDs(ctx, gm, ids...) } diff --git a/pkg/sdk/go/channels.go b/pkg/sdk/go/channels.go index fa19f32eea..dfa405bfff 100644 --- a/pkg/sdk/go/channels.go +++ b/pkg/sdk/go/channels.go @@ -146,6 +146,131 @@ func (sdk mfSDK) UpdateChannel(c Channel, token string) (Channel, errors.SDKErro return c, nil } +func (sdk mfSDK) AddUserToChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError { + data, err := json.Marshal(req) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, channelsEndpoint, channelID, usersEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) + return sdkerr +} + +func (sdk mfSDK) RemoveUserFromChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError { + data, err := json.Marshal(req) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, channelsEndpoint, channelID, usersEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, data, nil, http.StatusOK) + return sdkerr +} + +func (sdk mfSDK) ListChannelUsers(channelID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.thingsURL, fmt.Sprintf("%s/%s/%s", channelsEndpoint, channelID, usersEndpoint), pm) + if err != nil { + return UsersPage{}, errors.NewSDKError(err) + } + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return UsersPage{}, sdkerr + } + up := UsersPage{} + if err := json.Unmarshal(body, &up); err != nil { + return UsersPage{}, errors.NewSDKError(err) + } + + return up, nil +} + +func (sdk mfSDK) AddUserGroupToChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError { + data, err := json.Marshal(req) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, channelsEndpoint, channelID, groupsEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) + return sdkerr +} + +func (sdk mfSDK) RemoveUserGroupFromChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError { + data, err := json.Marshal(req) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, channelsEndpoint, channelID, groupsEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, data, nil, http.StatusOK) + return sdkerr +} + +func (sdk mfSDK) ListChannelUserGroups(channelID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.thingsURL, fmt.Sprintf("%s/%s/%s", channelsEndpoint, channelID, groupsEndpoint), pm) + if err != nil { + return GroupsPage{}, errors.NewSDKError(err) + } + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return GroupsPage{}, sdkerr + } + gp := GroupsPage{} + if err := json.Unmarshal(body, &gp); err != nil { + return GroupsPage{}, errors.NewSDKError(err) + } + + return gp, nil +} + +func (sdk mfSDK) Connect(conn Connection, token string) errors.SDKError { + data, err := json.Marshal(conn) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s", sdk.thingsURL, connectEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) + + return sdkerr +} + +func (sdk mfSDK) Disconnect(connIDs Connection, token string) errors.SDKError { + data, err := json.Marshal(connIDs) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s", sdk.thingsURL, disconnectEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) + + return sdkerr +} + +func (sdk mfSDK) ConnectThing(thingID, channelID, token string) errors.SDKError { + url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.thingsURL, channelsEndpoint, channelID, thingsEndpoint, thingID) + + _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, nil, nil, http.StatusNoContent) + + return sdkerr + +} + +func (sdk mfSDK) DisconnectThing(thingID, channelID, token string) errors.SDKError { + url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.thingsURL, channelsEndpoint, channelID, thingsEndpoint, thingID) + + _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) + + return sdkerr +} + func (sdk mfSDK) EnableChannel(id, token string) (Channel, errors.SDKError) { return sdk.changeChannelStatus(id, enableEndpoint, token) } diff --git a/pkg/sdk/go/groups.go b/pkg/sdk/go/groups.go index 5980bd2c79..5973677d2a 100644 --- a/pkg/sdk/go/groups.go +++ b/pkg/sdk/go/groups.go @@ -57,25 +57,6 @@ func (sdk mfSDK) CreateGroup(g Group, token string) (Group, errors.SDKError) { return g, nil } -func (sdk mfSDK) Memberships(clientID string, pm PageMetadata, token string) (MembershipsPage, errors.SDKError) { - url, err := sdk.withQueryParams(fmt.Sprintf("%s/%s/%s", sdk.usersURL, usersEndpoint, clientID), "memberships", pm) - if err != nil { - return MembershipsPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return MembershipsPage{}, sdkerr - } - - var tp MembershipsPage - if err := json.Unmarshal(body, &tp); err != nil { - return MembershipsPage{}, errors.NewSDKError(err) - } - - return tp, nil -} - func (sdk mfSDK) Groups(pm PageMetadata, token string) (GroupsPage, errors.SDKError) { url, err := sdk.withQueryParams(sdk.usersURL, groupsEndpoint, pm) if err != nil { @@ -164,6 +145,64 @@ func (sdk mfSDK) DisableGroup(id, token string) (Group, errors.SDKError) { return sdk.changeGroupStatus(id, disableEndpoint, token) } +func (sdk mfSDK) AddUserToGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError { + data, err := json.Marshal(req) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s", sdk.usersURL, groupsEndpoint, groupID, usersEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) + return sdkerr +} + +func (sdk mfSDK) RemoveUserFromGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError { + data, err := json.Marshal(req) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s", sdk.usersURL, groupsEndpoint, groupID, usersEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, data, nil, http.StatusOK) + return sdkerr +} + +func (sdk mfSDK) ListGroupUsers(groupID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s", groupsEndpoint, groupID, usersEndpoint), pm) + if err != nil { + return GroupsPage{}, errors.NewSDKError(err) + } + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return GroupsPage{}, sdkerr + } + gp := GroupsPage{} + if err := json.Unmarshal(body, &gp); err != nil { + return GroupsPage{}, errors.NewSDKError(err) + } + + return gp, nil +} + +func (sdk mfSDK) ListGroupChannels(groupID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s", groupsEndpoint, groupID, channelsEndpoint), pm) + if err != nil { + return GroupsPage{}, errors.NewSDKError(err) + } + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return GroupsPage{}, sdkerr + } + gp := GroupsPage{} + if err := json.Unmarshal(body, &gp); err != nil { + return GroupsPage{}, errors.NewSDKError(err) + } + + return gp, nil +} + func (sdk mfSDK) changeGroupStatus(id, status, token string) (Group, errors.SDKError) { url := fmt.Sprintf("%s/%s/%s/%s", sdk.usersURL, groupsEndpoint, id, status) diff --git a/pkg/sdk/go/policies.go b/pkg/sdk/go/policies.go deleted file mode 100644 index a2a62b0057..0000000000 --- a/pkg/sdk/go/policies.go +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package sdk - -import ( - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/mainflux/mainflux/pkg/errors" -) - -const ( - policyEndpoint = "policies" - authorizeEndpoint = "authorize" - accessEndpoint = "access" -) - -// Policy represents an argument struct for making a policy related function calls. -type Policy struct { - OwnerID string `json:"owner_id"` - Subject string `json:"subject"` - Object string `json:"object"` - Actions []string `json:"actions"` - External bool `json:"external,omitempty"` // This is specificially used in things service. If set to true, it means the subject is userID otherwise it is thingID. - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -type AccessRequest struct { - Subject string `json:"subject,omitempty"` - Object string `json:"object,omitempty"` - Action string `json:"action,omitempty"` - EntityType string `json:"entity_type,omitempty"` -} - -func (sdk mfSDK) AuthorizeUser(accessReq AccessRequest, token string) (bool, errors.SDKError) { - data, err := json.Marshal(accessReq) - if err != nil { - return false, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.usersURL, authorizeEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return false, sdkerr - } - - return true, nil -} - -func (sdk mfSDK) CreateUserPolicy(p Policy, token string) errors.SDKError { - data, err := json.Marshal(p) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.usersURL, policyEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - if sdkerr != nil { - return sdkerr - } - - return nil -} - -func (sdk mfSDK) UpdateUserPolicy(p Policy, token string) errors.SDKError { - data, err := json.Marshal(p) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.usersURL, policyEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, data, nil, http.StatusNoContent) - if sdkerr != nil { - return sdkerr - } - - return nil -} - -func (sdk mfSDK) ListUserPolicies(pm PageMetadata, token string) (PolicyPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, policyEndpoint, pm) - if err != nil { - return PolicyPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return PolicyPage{}, sdkerr - } - - var pp PolicyPage - if err := json.Unmarshal(body, &pp); err != nil { - return PolicyPage{}, errors.NewSDKError(err) - } - - return pp, nil -} - -func (sdk mfSDK) DeleteUserPolicy(p Policy, token string) errors.SDKError { - url := fmt.Sprintf("%s/%s/%s/%s", sdk.usersURL, policyEndpoint, p.Subject, p.Object) - - _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) - - return sdkerr -} - -func (sdk mfSDK) CreateThingPolicy(p Policy, token string) errors.SDKError { - data, err := json.Marshal(p) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.thingsURL, policyEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - if sdkerr != nil { - return sdkerr - } - - return nil -} - -func (sdk mfSDK) UpdateThingPolicy(p Policy, token string) errors.SDKError { - data, err := json.Marshal(p) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.thingsURL, policyEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, data, nil, http.StatusNoContent) - if sdkerr != nil { - return sdkerr - } - - return nil -} - -func (sdk mfSDK) ListThingPolicies(pm PageMetadata, token string) (PolicyPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.thingsURL, policyEndpoint, pm) - if err != nil { - return PolicyPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return PolicyPage{}, sdkerr - } - - var pp PolicyPage - if err := json.Unmarshal(body, &pp); err != nil { - return PolicyPage{}, errors.NewSDKError(err) - } - - return pp, nil -} - -func (sdk mfSDK) DeleteThingPolicy(p Policy, token string) errors.SDKError { - url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, policyEndpoint, p.Subject, p.Object) - - _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) - - return sdkerr -} - -func (sdk mfSDK) Assign(actions []string, userID, groupID, token string) errors.SDKError { - policy := Policy{ - Subject: userID, - Object: groupID, - Actions: actions, - } - return sdk.CreateUserPolicy(policy, token) -} - -func (sdk mfSDK) Unassign(userID, groupID, token string) errors.SDKError { - policy := Policy{ - Subject: userID, - Object: groupID, - } - - return sdk.DeleteUserPolicy(policy, token) -} - -func (sdk mfSDK) Connect(conn Connection, token string) errors.SDKError { - data, err := json.Marshal(conn) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.thingsURL, connectEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) - - return sdkerr -} - -func (sdk mfSDK) Disconnect(connIDs Connection, token string) errors.SDKError { - data, err := json.Marshal(connIDs) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.thingsURL, disconnectEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) - - return sdkerr -} - -func (sdk mfSDK) ConnectThing(thingID, channelID, token string) errors.SDKError { - policy := Policy{ - Subject: thingID, - Object: channelID, - } - - return sdk.CreateThingPolicy(policy, token) -} - -func (sdk mfSDK) DisconnectThing(thingID, channelID, token string) errors.SDKError { - policy := Policy{ - Subject: thingID, - Object: channelID, - } - - return sdk.DeleteThingPolicy(policy, token) -} - -func (sdk mfSDK) AuthorizeThing(accessReq AccessRequest, token string) (bool, string, errors.SDKError) { - data, err := json.Marshal(accessReq) - if err != nil { - return false, "", errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, channelsEndpoint, accessReq.Object, accessEndpoint) - - _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return false, "", sdkerr - } - resp := canAccessRes{} - if err := json.Unmarshal(body, &resp); err != nil { - return false, "", errors.NewSDKError(err) - } - - return resp.Authorized, resp.ThingID, nil -} diff --git a/pkg/sdk/go/policies_test.go b/pkg/sdk/go/policies_test.go deleted file mode 100644 index ace5472969..0000000000 --- a/pkg/sdk/go/policies_test.go +++ /dev/null @@ -1,1093 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -// import ( -// "fmt" -// "net/http" -// "net/http/httptest" -// "testing" -// "time" - -// "github.com/go-zoo/bone" -// "github.com/mainflux/mainflux/internal/apiutil" -// mflog "github.com/mainflux/mainflux/logger" -// "github.com/mainflux/mainflux/pkg/errors" -// sdk "github.com/mainflux/mainflux/pkg/sdk/go" -// tclients "github.com/mainflux/mainflux/things/clients" -// tmocks "github.com/mainflux/mainflux/things/clients/mocks" -// tgmocks "github.com/mainflux/mainflux/things/groups/mocks" -// tpolicies "github.com/mainflux/mainflux/things/policies" -// tapi "github.com/mainflux/mainflux/things/policies/api/http" -// tpmocks "github.com/mainflux/mainflux/things/policies/mocks" -// uclients "github.com/mainflux/mainflux/users/clients" -// umocks "github.com/mainflux/mainflux/users/clients/mocks" -// "github.com/mainflux/mainflux/users/jwt" -// upolicies "github.com/mainflux/mainflux/users/policies" -// uapi "github.com/mainflux/mainflux/users/policies/api/http" -// upmocks "github.com/mainflux/mainflux/users/policies/mocks" -// "github.com/stretchr/testify/assert" -// "github.com/stretchr/testify/mock" -// ) - -// var utadminPolicy = umocks.SubjectSet{Subject: "things", Relation: []string{"g_add"}} - -// func newUsersPolicyServer(svc upolicies.Service) *httptest.Server { -// logger := mflog.NewMock() -// mux := bone.New() -// uapi.MakeHandler(svc, mux, logger) - -// return httptest.NewServer(mux) -// } - -// func newThingsPolicyServer(svc tclients.Service, psvc tpolicies.Service) *httptest.Server { -// logger := mflog.NewMock() -// mux := bone.New() -// tapi.MakeHandler(svc, psvc, mux, logger) - -// return httptest.NewServer(mux) -// } - -// func TestCreatePolicyUser(t *testing.T) { -// cRepo := new(umocks.Repository) -// pRepo := new(upmocks.Repository) -// tokenizer := jwt.NewRepository([]byte(secret), accessDuration, refreshDuration) - -// csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) -// svc := upolicies.NewService(pRepo, tokenizer, idProvider) -// ts := newUsersPolicyServer(svc) -// defer ts.Close() -// conf := sdk.Config{ -// UsersURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// clientPolicy := sdk.Policy{Object: object, Actions: []string{"m_write", "g_add"}, Subject: subject} - -// cases := []struct { -// desc string -// policy sdk.Policy -// page sdk.PolicyPage -// token string -// err errors.SDKError -// }{ -// { -// desc: "add new policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{}, -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// }, -// { -// desc: "add existing policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{Policies: []sdk.Policy{clientPolicy}}, -// token: generateValidToken(t, csvc, cRepo), -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, sdk.ErrFailedCreation), http.StatusInternalServerError), -// }, -// { -// desc: "add a new policy with owner", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// OwnerID: generateUUID(t), -// Object: "objwithowner", -// Actions: []string{"m_read"}, -// Subject: "subwithowner", -// }, -// err: nil, -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with more actions", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj2", -// Actions: []string{"c_delete", "c_update", "c_list"}, -// Subject: "sub2", -// }, -// err: nil, -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with wrong action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj3", -// Actions: []string{"wrong"}, -// Subject: "sub3", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with empty object", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Subject: "sub4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPolicyObj), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with empty subject", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Object: "obj4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPolicySub), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with empty action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Subject: "sub5", -// Object: "obj5", -// }, -// err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// } - -// for _, tc := range cases { -// repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 := pRepo.On("Save", mock.Anything, mock.Anything).Return(tc.err) -// err := mfsdk.CreateUserPolicy(tc.policy, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// if tc.err == nil { -// ok := repoCall1.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) -// } -// repoCall.Unset() -// repoCall1.Unset() -// } -// } - -// func TestAuthorizeUser(t *testing.T) { -// cRepo := new(umocks.Repository) -// pRepo := new(upmocks.Repository) -// tokenizer := jwt.NewRepository([]byte(secret), accessDuration, refreshDuration) - -// csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) -// svc := upolicies.NewService(pRepo, tokenizer, idProvider) -// ts := newUsersPolicyServer(svc) -// defer ts.Close() -// conf := sdk.Config{ -// UsersURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// cases := []struct { -// desc string -// policy sdk.AccessRequest -// page sdk.PolicyPage -// token string -// err errors.SDKError -// }{ -// { -// desc: "authorize a valid policy with client entity", -// policy: sdk.AccessRequest{ -// Subject: subject, -// Object: object, -// Action: "c_list", -// EntityType: "client", -// }, -// page: sdk.PolicyPage{}, -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// }, -// { -// desc: "authorize a valid policy with group entity", -// policy: sdk.AccessRequest{ -// Subject: subject, -// Object: object, -// Action: "g_add", -// EntityType: "group", -// }, -// page: sdk.PolicyPage{}, -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// }, -// { -// desc: "authorize a policy with wrong action", -// page: sdk.PolicyPage{}, -// policy: sdk.AccessRequest{ -// Object: "obj3", -// Action: "wrong", -// Subject: "sub3", -// EntityType: "client", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "authorize a policy with empty object", -// page: sdk.PolicyPage{}, -// policy: sdk.AccessRequest{ -// Action: "c_delete", -// Subject: "sub4", -// EntityType: "client", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPolicyObj), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "authorize a policy with empty subject", -// page: sdk.PolicyPage{}, -// policy: sdk.AccessRequest{ -// Action: "c_delete", -// Object: "obj4", -// EntityType: "client", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPolicySub), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "authorize a policy with empty action", -// page: sdk.PolicyPage{}, -// policy: sdk.AccessRequest{ -// Subject: "sub5", -// Object: "obj5", -// EntityType: "client", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// } - -// for _, tc := range cases { -// repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// ok, err := mfsdk.AuthorizeUser(tc.policy, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// if tc.err == nil { -// assert.True(t, ok, fmt.Sprintf("%s: expected true, got false", tc.desc)) -// ok := repoCall.Parent.AssertCalled(t, "CheckAdmin", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("CheckAdmin was not called on %s", tc.desc)) -// } -// repoCall.Unset() -// } -// } - -// func TestAssign(t *testing.T) { -// cRepo := new(umocks.Repository) -// pRepo := new(upmocks.Repository) -// tokenizer := jwt.NewRepository([]byte(secret), accessDuration, refreshDuration) - -// csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) -// svc := upolicies.NewService(pRepo, tokenizer, idProvider) -// ts := newUsersPolicyServer(svc) -// defer ts.Close() -// conf := sdk.Config{ -// UsersURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// clientPolicy := sdk.Policy{Object: object, Actions: []string{"m_write", "g_add"}, Subject: subject} - -// cases := []struct { -// desc string -// policy sdk.Policy -// page sdk.PolicyPage -// token string -// err errors.SDKError -// }{ -// { -// desc: "add new policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{}, -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// }, -// { -// desc: "add existing policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{Policies: []sdk.Policy{clientPolicy}}, -// token: generateValidToken(t, csvc, cRepo), -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, sdk.ErrFailedCreation), http.StatusInternalServerError), -// }, -// { -// desc: "add a new policy with owner", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// OwnerID: generateUUID(t), -// Object: "objwithowner", -// Actions: []string{"m_read"}, -// Subject: "subwithowner", -// }, -// err: nil, -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with more actions", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj2", -// Actions: []string{"c_delete", "c_update", "c_list"}, -// Subject: "sub2", -// }, -// err: nil, -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with wrong action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj3", -// Actions: []string{"wrong"}, -// Subject: "sub3", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with empty object", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Subject: "sub4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPolicyObj), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with empty subject", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Object: "obj4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPolicySub), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with empty action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Subject: "sub5", -// Object: "obj5", -// }, -// err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// } - -// for _, tc := range cases { -// repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 := pRepo.On("Save", mock.Anything, mock.Anything).Return(tc.err) -// err := mfsdk.Assign(tc.policy.Actions, tc.policy.Subject, tc.policy.Object, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// if tc.err == nil { -// ok := repoCall1.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) -// } -// repoCall.Unset() -// repoCall1.Unset() -// } -// } - -// func TestUpdatePolicy(t *testing.T) { -// cRepo := new(umocks.Repository) -// pRepo := new(upmocks.Repository) -// tokenizer := jwt.NewRepository([]byte(secret), accessDuration, refreshDuration) - -// csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) -// svc := upolicies.NewService(pRepo, tokenizer, idProvider) -// ts := newUsersPolicyServer(svc) -// defer ts.Close() - -// conf := sdk.Config{ -// UsersURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// policy := sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// } - -// cases := []struct { -// desc string -// action []string -// token string -// err errors.SDKError -// }{ -// { -// desc: "update policy actions with valid token", -// action: []string{"m_write", "m_read", "g_add"}, -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// }, -// { -// desc: "update policy action with invalid token", -// action: []string{"m_write"}, -// token: "non-existent", -// err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthentication, sdk.ErrInvalidJWT), http.StatusUnauthorized), -// }, -// { -// desc: "update policy action with wrong policy action", -// action: []string{"wrong"}, -// token: generateValidToken(t, csvc, cRepo), -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// }, -// } - -// for _, tc := range cases { -// policy.Actions = tc.action -// policy.CreatedAt = time.Now() -// repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 := pRepo.On("RetrieveAll", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(upolicies.PolicyPage{}, nil) -// repoCall2 := pRepo.On("Update", mock.Anything, mock.Anything).Return(tc.err) -// err := mfsdk.UpdateUserPolicy(policy, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// ok := repoCall1.Parent.AssertCalled(t, "Update", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) -// repoCall.Unset() -// repoCall1.Unset() -// repoCall2.Unset() -// } -// } - -// func TestUpdateThingsPolicy(t *testing.T) { -// cRepo := new(tmocks.Repository) -// gRepo := new(tgmocks.Repository) -// uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) -// thingCache := tmocks.NewCache() -// policiesCache := tpmocks.NewCache() - -// pRepo := new(tpmocks.Repository) -// psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) - -// svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) -// ts := newThingsPolicyServer(svc, psvc) -// defer ts.Close() - -// conf := sdk.Config{ -// ThingsURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// policy := sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// } - -// cases := []struct { -// desc string -// action []string -// token string -// err errors.SDKError -// }{ -// { -// desc: "update policy actions with valid token", -// action: []string{"m_write", "m_read"}, -// token: adminToken, -// err: nil, -// }, -// { -// desc: "update policy action with invalid token", -// action: []string{"m_write"}, -// token: "non-existent", -// err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthorization, errors.ErrAuthentication), http.StatusUnauthorized), -// }, -// { -// desc: "update policy action with wrong policy action", -// action: []string{"wrong"}, -// token: adminToken, -// err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), -// }, -// } - -// for _, tc := range cases { -// policy.Actions = tc.action -// policy.CreatedAt = time.Now() -// repoCall := pRepo.On("RetrieveAll", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tpolicies.PolicyPage{}, nil) -// repoCall1 := pRepo.On("Update", mock.Anything, mock.Anything).Return(tpolicies.Policy{}, tc.err) -// err := mfsdk.UpdateThingPolicy(policy, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// ok := repoCall.Parent.AssertCalled(t, "Update", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) -// repoCall.Unset() -// repoCall1.Unset() -// } -// } - -// func TestListPolicies(t *testing.T) { -// cRepo := new(umocks.Repository) -// pRepo := new(upmocks.Repository) -// tokenizer := jwt.NewRepository([]byte(secret), accessDuration, refreshDuration) - -// csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) -// svc := upolicies.NewService(pRepo, tokenizer, idProvider) -// ts := newUsersPolicyServer(svc) -// defer ts.Close() - -// conf := sdk.Config{ -// UsersURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) -// id := generateUUID(t) - -// nPolicy := uint64(10) -// aPolicies := []sdk.Policy{} -// for i := uint64(0); i < nPolicy; i++ { -// pr := sdk.Policy{ -// OwnerID: id, -// Actions: []string{"m_read"}, -// Subject: fmt.Sprintf("thing_%d", i), -// Object: fmt.Sprintf("client_%d", i), -// } -// if i%3 == 0 { -// pr.Actions = []string{"m_write"} -// } -// aPolicies = append(aPolicies, pr) -// } - -// cases := []struct { -// desc string -// token string -// page sdk.PageMetadata -// response []sdk.Policy -// err errors.SDKError -// }{ -// { -// desc: "list policies with authorized token", -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// response: aPolicies, -// }, -// { -// desc: "list policies with invalid token", -// token: invalidToken, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthentication, sdk.ErrInvalidJWT), http.StatusUnauthorized), -// response: []sdk.Policy(nil), -// }, -// { -// desc: "list policies with offset and limit", -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// page: sdk.PageMetadata{ -// Offset: 6, -// Limit: nPolicy, -// }, -// response: aPolicies[6:10], -// }, -// { -// desc: "list policies with given name", -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// page: sdk.PageMetadata{ -// Offset: 6, -// Limit: nPolicy, -// }, -// response: aPolicies[6:10], -// }, -// { -// desc: "list policies with given identifier", -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// page: sdk.PageMetadata{ -// Offset: 6, -// Limit: nPolicy, -// }, -// response: aPolicies[6:10], -// }, -// { -// desc: "list policies with given ownerID", -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// page: sdk.PageMetadata{ -// Offset: 6, -// Limit: nPolicy, -// }, -// response: aPolicies[6:10], -// }, -// { -// desc: "list policies with given subject", -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// page: sdk.PageMetadata{ -// Offset: 6, -// Limit: nPolicy, -// }, -// response: aPolicies[6:10], -// }, -// { -// desc: "list policies with given object", -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// page: sdk.PageMetadata{ -// Offset: 6, -// Limit: nPolicy, -// }, -// response: aPolicies[6:10], -// }, -// { -// desc: "list policies with wrong action", -// token: generateValidToken(t, csvc, cRepo), -// page: sdk.PageMetadata{ -// Action: "wrong", -// }, -// response: []sdk.Policy(nil), -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// }, -// } - -// for _, tc := range cases { -// repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 := pRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: tc.response}), tc.err) -// pp, err := mfsdk.ListUserPolicies(tc.page, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// assert.Equal(t, tc.response, pp.Policies, fmt.Sprintf("%s: expected %v, got %v", tc.desc, tc.response, pp)) -// ok := repoCall.Parent.AssertCalled(t, "RetrieveAll", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("RetrieveAll was not called on %s", tc.desc)) -// repoCall.Unset() -// repoCall1.Unset() -// } -// } - -// func TestDeletePolicy(t *testing.T) { -// cRepo := new(umocks.Repository) -// pRepo := new(upmocks.Repository) -// tokenizer := jwt.NewRepository([]byte(secret), accessDuration, refreshDuration) - -// csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) -// svc := upolicies.NewService(pRepo, tokenizer, idProvider) -// ts := newUsersPolicyServer(svc) -// defer ts.Close() - -// conf := sdk.Config{ -// UsersURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// sub := generateUUID(t) -// pr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} -// cpr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} - -// repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 := pRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: []sdk.Policy{cpr}}), nil) -// repoCall2 := pRepo.On("Delete", mock.Anything, mock.Anything).Return(nil) -// err := mfsdk.DeleteUserPolicy(pr, generateValidToken(t, csvc, cRepo)) -// assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) -// ok := repoCall1.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on valid policy") -// repoCall2.Unset() -// repoCall1.Unset() -// repoCall.Unset() - -// repoCall = pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 = pRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: []sdk.Policy{cpr}}), nil) -// repoCall2 = pRepo.On("Delete", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) -// err = mfsdk.DeleteUserPolicy(pr, invalidToken) -// assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthentication, sdk.ErrInvalidJWT), http.StatusUnauthorized), fmt.Sprintf("expected %v got %s", pr, err)) -// ok = repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on invalid policy") -// repoCall2.Unset() -// repoCall1.Unset() -// repoCall.Unset() -// } - -// func TestUnassign(t *testing.T) { -// cRepo := new(umocks.Repository) -// pRepo := new(upmocks.Repository) -// tokenizer := jwt.NewRepository([]byte(secret), accessDuration, refreshDuration) - -// csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) -// svc := upolicies.NewService(pRepo, tokenizer, idProvider) -// ts := newUsersPolicyServer(svc) -// defer ts.Close() - -// conf := sdk.Config{ -// UsersURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// sub := generateUUID(t) -// pr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} -// cpr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} - -// repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 := pRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: []sdk.Policy{cpr}}), nil) -// repoCall2 := pRepo.On("Delete", mock.Anything, mock.Anything).Return(nil) -// err := mfsdk.Unassign(pr.Subject, pr.Object, generateValidToken(t, csvc, cRepo)) -// assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) -// ok := repoCall1.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on valid policy") -// repoCall2.Unset() -// repoCall1.Unset() -// repoCall.Unset() - -// repoCall = pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 = pRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: []sdk.Policy{cpr}}), nil) -// repoCall2 = pRepo.On("Delete", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) -// err = mfsdk.Unassign(pr.Subject, pr.Object, invalidToken) -// assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthentication, sdk.ErrInvalidJWT), http.StatusUnauthorized), fmt.Sprintf("expected %v got %s", pr, err)) -// ok = repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on invalid policy") -// repoCall2.Unset() -// repoCall1.Unset() -// repoCall.Unset() -// } - -// func TestConnect(t *testing.T) { -// cRepo := new(tmocks.Repository) -// gRepo := new(tgmocks.Repository) -// uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) -// thingCache := tmocks.NewCache() -// policiesCache := tpmocks.NewCache() - -// pRepo := new(tpmocks.Repository) -// psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) - -// svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) -// ts := newThingsPolicyServer(svc, psvc) -// defer ts.Close() - -// conf := sdk.Config{ -// ThingsURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// clientPolicy := sdk.Policy{Object: object, Actions: []string{"m_write", "g_add"}, Subject: subject} - -// cases := []struct { -// desc string -// policy sdk.Policy -// page sdk.PolicyPage -// token string -// err errors.SDKError -// tcerr errors.SDKError -// }{ -// { -// desc: "add new policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{}, -// token: adminToken, -// err: nil, -// tcerr: nil, -// }, -// { -// desc: "add existing policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{Policies: []sdk.Policy{clientPolicy}}, -// token: adminToken, -// err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusInternalServerError), -// tcerr: errors.NewSDKError(sdk.ErrFailedCreation), -// }, -// { -// desc: "add a new policy with owner", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// OwnerID: generateUUID(t), -// Object: "objwithowner", -// Actions: []string{"m_read"}, -// Subject: "subwithowner", -// }, -// err: nil, -// tcerr: nil, -// token: adminToken, -// }, -// { -// desc: "add a new policy with more actions", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj2", -// Actions: []string{"c_delete", "c_update", "c_list"}, -// Subject: "sub2", -// }, -// err: nil, -// tcerr: nil, -// token: adminToken, -// }, -// { -// desc: "add a new policy with wrong action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj3", -// Actions: []string{"wrong"}, -// Subject: "sub3", -// }, -// err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), -// tcerr: errors.NewSDKError(apiutil.ErrMalformedPolicyAct), -// token: adminToken, -// }, -// { -// desc: "add a new policy with empty object", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Subject: "sub4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), -// tcerr: errors.NewSDKError(apiutil.ErrMissingID), -// token: adminToken, -// }, -// { -// desc: "add a new policy with empty subject", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Object: "obj4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), -// tcerr: errors.NewSDKError(apiutil.ErrMissingID), -// token: adminToken, -// }, -// { -// desc: "add a new policy with empty action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Subject: "sub5", -// Object: "obj5", -// }, -// err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), -// tcerr: errors.NewSDKError(apiutil.ErrMalformedPolicyAct), -// token: adminToken, -// }, -// } - -// for _, tc := range cases { -// repoCall := pRepo.On("Save", mock.Anything, mock.Anything).Return(convertThingPolicy(tc.policy), tc.tcerr) -// conn := sdk.ConnectionIDs{ChannelIDs: []string{tc.policy.Object}, ThingIDs: []string{tc.policy.Subject}, Actions: tc.policy.Actions} -// err := mfsdk.Connect(conn, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// if tc.err == nil { -// ok := repoCall.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) -// } -// repoCall.Unset() -// } -// } - -// func TestConnectThing(t *testing.T) { -// cRepo := new(tmocks.Repository) -// gRepo := new(tgmocks.Repository) -// uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) -// thingCache := tmocks.NewCache() -// policiesCache := tpmocks.NewCache() - -// pRepo := new(tpmocks.Repository) -// psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) - -// svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) -// ts := newThingsPolicyServer(svc, psvc) -// defer ts.Close() - -// conf := sdk.Config{ -// ThingsURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// clientPolicy := sdk.Policy{Object: object, Actions: []string{"m_write", "g_add"}, Subject: subject} - -// cases := []struct { -// desc string -// policy sdk.Policy -// page sdk.PolicyPage -// token string -// err errors.SDKError -// }{ -// { -// desc: "add new policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{}, -// token: adminToken, -// err: nil, -// }, -// { -// desc: "add existing policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{Policies: []sdk.Policy{clientPolicy}}, -// token: adminToken, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, sdk.ErrFailedCreation), http.StatusInternalServerError), -// }, -// { -// desc: "add a new policy with owner", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// OwnerID: generateUUID(t), -// Object: "objwithowner", -// Actions: []string{"m_read"}, -// Subject: "subwithowner", -// }, -// err: nil, -// token: adminToken, -// }, -// { -// desc: "add a new policy with more actions", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj2", -// Actions: []string{"c_delete", "c_update", "c_list"}, -// Subject: "sub2", -// }, -// err: nil, -// token: adminToken, -// }, -// { -// desc: "add a new policy with wrong action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj3", -// Actions: []string{"wrong"}, -// Subject: "sub3", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// token: adminToken, -// }, -// { -// desc: "add a new policy with empty object", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Subject: "sub4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), -// token: adminToken, -// }, -// { -// desc: "add a new policy with empty subject", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Object: "obj4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), -// token: adminToken, -// }, -// { -// desc: "add a new policy with empty action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Subject: "sub5", -// Object: "obj5", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// token: adminToken, -// }, -// } - -// for _, tc := range cases { -// repoCall := pRepo.On("Save", mock.Anything, mock.Anything).Return(convertThingPolicy(tc.policy), tc.err) -// err := mfsdk.ConnectThing(tc.policy.Subject, tc.policy.Object, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// if tc.err == nil { -// ok := repoCall.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) -// } -// repoCall.Unset() -// } -// } - -// func TestDisconnectThing(t *testing.T) { -// cRepo := new(tmocks.Repository) -// gRepo := new(tgmocks.Repository) -// uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) -// thingCache := tmocks.NewCache() -// policiesCache := tpmocks.NewCache() - -// pRepo := new(tpmocks.Repository) -// psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) - -// svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) -// ts := newThingsPolicyServer(svc, psvc) -// defer ts.Close() - -// conf := sdk.Config{ -// ThingsURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// sub := generateUUID(t) -// pr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} - -// repoCall := pRepo.On("Delete", mock.Anything, mock.Anything).Return(nil) -// err := mfsdk.DisconnectThing(pr.Subject, pr.Object, adminToken) -// assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) -// ok := repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on valid policy") -// repoCall.Unset() - -// repoCall = pRepo.On("Delete", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) -// err = mfsdk.DisconnectThing(pr.Subject, pr.Object, invalidToken) -// assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthorization, errors.ErrAuthentication), http.StatusUnauthorized), fmt.Sprintf("expected %v got %s", pr, err)) -// ok = repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on invalid policy") -// repoCall.Unset() -// } - -// func TestDisconnect(t *testing.T) { -// cRepo := new(tmocks.Repository) -// gRepo := new(tgmocks.Repository) -// uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) -// thingCache := tmocks.NewCache() -// policiesCache := tpmocks.NewCache() - -// pRepo := new(tpmocks.Repository) -// psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) - -// svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) -// ts := newThingsPolicyServer(svc, psvc) -// defer ts.Close() - -// conf := sdk.Config{ -// ThingsURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// sub := generateUUID(t) -// pr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} - -// repoCall := pRepo.On("Delete", mock.Anything, mock.Anything).Return(nil) -// conn := sdk.ConnectionIDs{ChannelIDs: []string{pr.Object}, ThingIDs: []string{pr.Subject}} -// err := mfsdk.Disconnect(conn, adminToken) -// assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) -// ok := repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on valid policy") -// repoCall.Unset() - -// repoCall = pRepo.On("Delete", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) -// conn = sdk.ConnectionIDs{ChannelIDs: []string{pr.Object}, ThingIDs: []string{pr.Subject}} -// err = mfsdk.Disconnect(conn, invalidToken) -// assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthorization, errors.ErrAuthentication), http.StatusUnauthorized), fmt.Sprintf("expected %v got %s", pr, err)) -// ok = repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on invalid policy") -// repoCall.Unset() -// } diff --git a/pkg/sdk/go/requests.go b/pkg/sdk/go/requests.go index 918fbd8920..aabe9ce0e9 100644 --- a/pkg/sdk/go/requests.go +++ b/pkg/sdk/go/requests.go @@ -39,12 +39,20 @@ type UserPasswordReq struct { // Connection contains thing and channel ID that are connected. type Connection struct { - ThingID string `json:"thing_id,omitempty"` - ChannelID string `json:"channel_id,omitempty"` - Permission string `json:"permission,omitempty"` + ThingID string `json:"thing_id,omitempty"` + ChannelID string `json:"channel_id,omitempty"` } type tokenReq struct { Identity string `json:"identity"` Secret string `json:"secret"` } + +type UsersRelationRequest struct { + Relation string `json:"relation"` + UserIDs []string `json:"user_ids"` +} + +type UserGroupsRequest struct { + UserGroupIDs []string `json:"group_ids"` +} diff --git a/pkg/sdk/go/responses.go b/pkg/sdk/go/responses.go index 05c7001fd5..d88d2c63cf 100644 --- a/pkg/sdk/go/responses.go +++ b/pkg/sdk/go/responses.go @@ -63,13 +63,6 @@ type MembershipsPage struct { Memberships []Group `json:"memberships"` } -// PolicyPage contains page related metadata as well as list -// of Policies that belong to the page. -type PolicyPage struct { - PageMetadata - Policies []Policy `json:"policies"` -} - type revokeCertsRes struct { RevocationTime time.Time `json:"revocation_time"` } diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go index 5a85e5cd7f..5d6e7d0dfb 100644 --- a/pkg/sdk/go/sdk.go +++ b/pkg/sdk/go/sdk.go @@ -82,6 +82,7 @@ type PageMetadata struct { Action string `json:"action,omitempty"` Subject string `json:"subject,omitempty"` Object string `json:"object,omitempty"` + Permission string `json:"permission,omitempty"` Tag string `json:"tag,omitempty"` Owner string `json:"owner,omitempty"` SharedBy string `json:"shared_by,omitempty"` @@ -135,17 +136,6 @@ type SDK interface { // fmt.Println(users) Users(pm PageMetadata, token string) (UsersPage, errors.SDKError) - // Members retrieves everything that is assigned to a group identified by groupID. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // } - // members, _ := sdk.Members("groupID", pm, "token") - // fmt.Println(members) - Members(groupID string, meta PageMetadata, token string) (MembersPage, errors.SDKError) - // UserProfile returns user logged in. // // example: @@ -257,6 +247,42 @@ type SDK interface { // fmt.Println(token) RefreshToken(token string) (Token, errors.SDKError) + // ListUserChannels list all channels belongs a particular user id. + // + // example: + // pm := sdk.PageMetadata{ + // Offset: 0, + // Limit: 10, + // Permission: "edit", // available Options: "administrator", "delete", edit", "view", "share", "owner", "admin", "editor", "viewer" + // } + // channels, _ := sdk.ListUserChannels("user_id_1", pm, "token") + // fmt.Println(channels) + ListUserChannels(userID string, pm PageMetadata, token string) (ChannelsPage, errors.SDKError) + + // ListUserGroups list all groups belongs a particular user id. + // + // example: + // pm := sdk.PageMetadata{ + // Offset: 0, + // Limit: 10, + // Permission: "edit", // available Options: "administrator", "delete", edit", "view", "share", "owner", "admin", "editor", "viewer" + // } + // groups, _ := sdk.ListUserGroups("user_id_1", pm, "token") + // fmt.Println(channels) + ListUserGroups(userID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) + + // ListUserThings list all things belongs a particular user id. + // + // example: + // pm := sdk.PageMetadata{ + // Offset: 0, + // Limit: 10, + // Permission: "edit", // available Options: "administrator", "delete", edit", "view", "share", "owner", "admin", "editor", "viewer" + // } + // things, _ := sdk.ListUserThings("user_id_1", pm, "token") + // fmt.Println(things) + ListUserThings(userID string, pm PageMetadata, token string) (ThingsPage, errors.SDKError) + // CreateThing registers new thing and returns its id. // // example: @@ -386,17 +412,39 @@ type SDK interface { // fmt.Println(id) IdentifyThing(key string) (string, errors.SDKError) - // ShareThing shares thing with other user. It assumes that you have - // already created a group and added things to it. It also assumes that - // you have required policy to share a thing with the specified user. + // ShareThing shares thing with other users. // - // The `ShareThing` method calls the `Connect` method with the - // subject as `userID` rather than `thingID`. + // example: + // req := sdk.UsersRelationRequest{ + // Relation: "viewer", // available options: "owner", "admin", "editor", "viewer" + // UserIDs: ["user_id_1", "user_id_2", "user_id_3"] + // } + // err := sdk.ShareThing("thing_id", req, "token") + // fmt.Println(err) + ShareThing(thingID string, req UsersRelationRequest, token string) errors.SDKError + + // UnshareThing unshare a thing with other users. // // example: - // err := sdk.ShareThing("channelID", "userID", []string{"c_list", "c_delete"}, "token") + // req := sdk.UsersRelationRequest{ + // Relation: "viewer", // available options: "owner", "admin", "editor", "viewer" + // UserIDs: ["user_id_1", "user_id_2", "user_id_3"] + // } + // err := sdk.UnshareThing("thing_id", req, "token") // fmt.Println(err) - ShareThing(channelID, userID string, actions []string, token string) errors.SDKError + UnshareThing(thingID string, req UsersRelationRequest, token string) errors.SDKError + + // ListThingUsers all users in a thing. + // + // example: + // pm := sdk.PageMetadata{ + // Offset: 0, + // Limit: 10, + // Permission: "edit", // available Options: "administrator", "delete", edit", "view", "share", "owner", "admin", "editor", "viewer" + // } + // users, _ := sdk.ListThingUsers("thing_id", pm, "token") + // fmt.Println(users) + ListThingUsers(thingID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) // CreateGroup creates new group and returns its id. // @@ -411,18 +459,6 @@ type SDK interface { // fmt.Println(group) CreateGroup(group Group, token string) (Group, errors.SDKError) - // Memberships - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Name: "My Group", - // } - // groups, _ := sdk.Memberships("userID", pm, "token") - // fmt.Println(groups) - Memberships(clientID string, pm PageMetadata, token string) (MembershipsPage, errors.SDKError) - // Groups returns page of groups. // // example: @@ -494,6 +530,52 @@ type SDK interface { // fmt.Println(group) DisableGroup(id, token string) (Group, errors.SDKError) + // AddUserToGroup add user to a group. + // + // example: + // req := sdk.UsersRelationRequest{ + // Relation: "viewer", // available options: "owner", "admin", "editor", "viewer" + // UserIDs: ["user_id_1", "user_id_2", "user_id_3"] + // } + // group, _ := sdk.AddUserToGroup("groupID",req, "token") + // fmt.Println(group) + AddUserToGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError + + // RemoveUserFromGroup remove user from a group. + // + // example: + // req := sdk.UsersRelationRequest{ + // Relation: "viewer", // available options: "owner", "admin", "editor", "viewer" + // UserIDs: ["user_id_1", "user_id_2", "user_id_3"] + // } + // group, _ := sdk.RemoveUserFromGroup("groupID",req, "token") + // fmt.Println(group) + RemoveUserFromGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError + + // ListGroupUsers list all users in the group id . + // + // example: + // pm := sdk.PageMetadata{ + // Offset: 0, + // Limit: 10, + // Permission: "edit", // available Options: "administrator", "delete", edit", "view", "share", "owner", "admin", "editor", "viewer" + // } + // groups, _ := sdk.ListGroupUsers("groupID", pm, "token") + // fmt.Println(groups) + ListGroupUsers(groupID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) + + // ListGroupChannels list all channels in the group id . + // + // example: + // pm := sdk.PageMetadata{ + // Offset: 0, + // Limit: 10, + // Permission: "edit", // available Options: "administrator", "delete", edit", "view", "share", "owner", "admin", "editor", "viewer" + // } + // groups, _ := sdk.ListGroupChannels("groupID", pm, "token") + // fmt.Println(groups) + ListGroupChannels(groupID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) + // CreateChannel creates new channel and returns its id. // // example: @@ -587,156 +669,78 @@ type SDK interface { // fmt.Println(channel) DisableChannel(id, token string) (Channel, errors.SDKError) - // CreateUserPolicy creates a policy for the given subject, so that, after - // CreateUserPolicy, `subject` has a `relation` on `object`. Returns a non-nil - // error in case of failures. - // - // The subject in this case is the `userID` and the object is the `groupID`. - // - // example: - // policy := sdk.Policy{ - // Subject: "userID:1", - // Object: "groupID:1", - // Actions: []string{"g_add"}, - // } - // err := sdk.CreateUserPolicy(policy, "token") - // fmt.Println(err) - CreateUserPolicy(policy Policy, token string) errors.SDKError - - // UpdateUserPolicy updates policies based on the given policy structure. - // - // The subject in this case is the `userID` and the object is the `groupID`. - - // example: - // policy := sdk.Policy{ - // Subject: "userID:1", - // Object: "groupID:1", - // Actions: []string{"g_add"}, - // } - // err := sdk.UpdateUserPolicy(policy, "token") - // fmt.Println(err) - UpdateUserPolicy(p Policy, token string) errors.SDKError - - // ListUserPolicies lists policies based on the given policy structure. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Subject: "userID:1", - // } - // policies, _ := sdk.ListUserPolicies(pm, "token") - // fmt.Println(policies) - ListUserPolicies(pm PageMetadata, token string) (PolicyPage, errors.SDKError) - - // DeleteUserPolicy deletes policies. - // - // The subject in this case is the `userID` and the object is the `groupID`. - // - // example: - // policy := sdk.Policy{ - // Subject: "userID:1", - // Object: "groupID:1", - // } - // err := sdk.DeleteUserPolicy(policy, "token") - // fmt.Println(err) - DeleteUserPolicy(policy Policy, token string) errors.SDKError - - // CreateThingPolicy creates a policy for the given subject, so that, after - // CreateThingPolicy, `subject` has a `relation` on `object`. Returns a non-nil - // error in case of failures. - // - // The subject in this case can be a `thingID` or a `userID` and the object is the `channelID`. - // - // example: - // policy := sdk.Policy{ - // Subject: "thingID:1", - // Object: "channelID:1", - // Actions: []string{"m_write"}, - // } - // err := sdk.CreateThingPolicy(policy, "token") - // fmt.Println(err) - CreateThingPolicy(policy Policy, token string) errors.SDKError - - // UpdateThingPolicy updates policies based on the given policy structure. - // - // The subject in this case can be a `thingID` or a `userID` and the object is the `channelID`. + // AddUserToChannel add user to a channel. // // example: - // policy := sdk.Policy{ - // Subject: "thingID:1", - // Object: "channelID:1", - // Actions: []string{"m_write"}, - // } - // err := sdk.UpdateThingPolicy(policy, "token") - // fmt.Println(err) - UpdateThingPolicy(p Policy, token string) errors.SDKError + // req := sdk.UsersRelationRequest{ + // Relation: "viewer", // available options: "owner", "admin", "editor", "viewer" + // UserIDs: ["user_id_1", "user_id_2", "user_id_3"] + // } + // err := sdk.AddUserToChannel("channel_id", req, "token") + // fmt.Println(err) + AddUserToChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError - // ListThingPolicies lists policies based on the given policy structure. + // RemoveUserFromChannel remove user from a group. // // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Subject: "thingID:1", - // } - // policies, _ := sdk.ListThingPolicies(pm, "token") - // fmt.Println(policies) - ListThingPolicies(pm PageMetadata, token string) (PolicyPage, errors.SDKError) + // req := sdk.UsersRelationRequest{ + // Relation: "viewer", // available options: "owner", "admin", "editor", "viewer" + // UserIDs: ["user_id_1", "user_id_2", "user_id_3"] + // } + // err := sdk.RemoveUserFromChannel("channel_id", req, "token") + // fmt.Println(err) + RemoveUserFromChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError - // DeleteThingPolicy deletes policies. - // - // The subject in this case can be a `thingID` or a `userID` and the object is the `channelID`. + // ListChannelUsers list all users in a channel . // // example: - // policy := sdk.Policy{ - // Subject: "thingID:1", - // Object: "channelID:1", - // } - // err := sdk.DeleteThingPolicy(policy, "token") - // fmt.Println(err) - DeleteThingPolicy(policy Policy, token string) errors.SDKError + // pm := sdk.PageMetadata{ + // Offset: 0, + // Limit: 10, + // Permission: "edit", // available Options: "administrator", "delete", edit", "view", "share", "owner", "admin", "editor", "viewer" + // } + // users, _ := sdk.ListChannelUsers("channel_id", pm, "token") + // fmt.Println(users) + ListChannelUsers(channelID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) - // AuthorizeUser returns true if the given policy structure allows the action. - // - // The subject in this case is the `userID` and the object is the `groupID`. + // AddUserGroupToChannel add user group to a channel. // // example: - // aReq := sdk.AccessRequest{ - // Subject: "userID:1", - // Object: "groupID:1", - // Actions: "g_add", - // EntityType: "clients", - // } - // ok, _ := sdk.AuthorizeUser(aReq, "token") - // fmt.Println(ok) - AuthorizeUser(accessReq AccessRequest, token string) (bool, errors.SDKError) + // req := sdk.UserGroupsRequest{ + // GroupsIDs: ["group_id_1", "group_id_2", "group_id_3"] + // } + // err := sdk.AddUserGroupToChannel("channel_id",req, "token") + // fmt.Println(err) + AddUserGroupToChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError - // Assign assigns users to a group with the given actions. - // - // The `Assign` method calls the `CreateUserPolicy` method under the hood. + // RemoveUserGroupFromChannel remove user group from a channel. // // example: - // err := sdk.Assign([]string{"g_add"}, "userID:1", "groupID:1", "token") - // fmt.Println(err) - Assign(action []string, userID, groupID, token string) errors.SDKError + // req := sdk.UserGroupsRequest{ + // GroupsIDs: ["group_id_1", "group_id_2", "group_id_3"] + // } + // err := sdk.RemoveUserGroupFromChannel("channel_id",req, "token") + // fmt.Println(err) + RemoveUserGroupFromChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError - // Unassign removes a user from a group. - // - // The `Unassign` method calls the `DeleteUserPolicy` method under the hood. + // ListChannelUserGroups list all user groups in a channel. // // example: - // err := sdk.Unassign("userID:1", "groupID:1", "token") - // fmt.Println(err) - Unassign(userID, groupID, token string) errors.SDKError + // pm := sdk.PageMetadata{ + // Offset: 0, + // Limit: 10, + // Permission: "view", + // } + // groups, _ := sdk.ListChannelUserGroups("channel_id_1", pm, "token") + // fmt.Println(groups) + ListChannelUserGroups(channelID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) // Connect bulk connects things to channels specified by id. // // example: // conns := sdk.Connection{ - // ChannelIDs: []string{"thingID:1", "thingID:2"}, - // ThingIDs: []string{"channelID:1", "channelID:2"}, - // Actions: []string{"m_read"}, + // ChannelID: "channel_id_1", + // ThingID: "thing_id_1", // } // err := sdk.Connect(conns, "token") // fmt.Println(err) @@ -746,8 +750,8 @@ type SDK interface { // // example: // conns := sdk.Connection{ - // ChannelIDs: []string{"thingID:1", "thingID:2"}, - // ThingIDs: []string{"channelID:1", "channelID:2"}, + // ChannelID: "channel_id_1", + // ThingID: "thing_id_1", // } // err := sdk.Disconnect(conns, "token") // fmt.Println(err) @@ -771,19 +775,6 @@ type SDK interface { // fmt.Println(err) DisconnectThing(thingID, chanID, token string) errors.SDKError - // AuthorizeThing returns true if the given policy structure allows the action. - // - // example: - // aReq := sdk.AccessRequest{ - // Subject: "thingID", - // Object: "channelID", - // Actions: "m_read", - // EntityType: "things", - // } - // ok, _ := sdk.AuthorizeThing(aReq "token") - // fmt.Println(ok) - AuthorizeThing(accessReq AccessRequest, token string) (bool, string, errors.SDKError) - // SendMessage send message to specified channel. // // example: diff --git a/pkg/sdk/go/things.go b/pkg/sdk/go/things.go index 99307ec58a..353c05e551 100644 --- a/pkg/sdk/go/things.go +++ b/pkg/sdk/go/things.go @@ -17,6 +17,8 @@ const ( connectEndpoint = "connect" disconnectEndpoint = "disconnect" identifyEndpoint = "identify" + shareEndpoint = "share" + unshareEndpoint = "unshare" ) // Thing represents mainflux thing. @@ -254,13 +256,44 @@ func (sdk mfSDK) IdentifyThing(key string) (string, errors.SDKError) { return i.ID, nil } -func (sdk mfSDK) ShareThing(groupID, userID string, actions []string, token string) errors.SDKError { - policy := Policy{ - Subject: userID, - Object: groupID, - Actions: actions, - External: true, +func (sdk mfSDK) ShareThing(thingID string, req UsersRelationRequest, token string) errors.SDKError { + data, err := json.Marshal(req) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, thingsEndpoint, thingID, unshareEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) + return sdkerr +} + +func (sdk mfSDK) UnshareThing(thingID string, req UsersRelationRequest, token string) errors.SDKError { + data, err := json.Marshal(req) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, thingsEndpoint, thingID, shareEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) + return sdkerr +} + +func (sdk mfSDK) ListThingUsers(thingID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.thingsURL, fmt.Sprintf("%s/%s/%s", thingsEndpoint, thingID, usersEndpoint), pm) + if err != nil { + return UsersPage{}, errors.NewSDKError(err) + } + + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return UsersPage{}, sdkerr + } + up := UsersPage{} + if err := json.Unmarshal(body, &up); err != nil { + return UsersPage{}, errors.NewSDKError(err) } - return sdk.CreateThingPolicy(policy, token) + return up, nil } diff --git a/pkg/sdk/go/users.go b/pkg/sdk/go/users.go index f39460f546..35b48cee2a 100644 --- a/pkg/sdk/go/users.go +++ b/pkg/sdk/go/users.go @@ -267,6 +267,58 @@ func (sdk mfSDK) UpdateUserOwner(user User, token string) (User, errors.SDKError return user, nil } +func (sdk mfSDK) ListUserChannels(userID string, pm PageMetadata, token string) (ChannelsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s", usersEndpoint, userID, channelsEndpoint), pm) + if err != nil { + return ChannelsPage{}, errors.NewSDKError(err) + } + + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return ChannelsPage{}, sdkerr + } + cp := ChannelsPage{} + if err := json.Unmarshal(body, &cp); err != nil { + return ChannelsPage{}, errors.NewSDKError(err) + } + + return cp, nil +} + +func (sdk mfSDK) ListUserGroups(userID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s", usersEndpoint, userID, groupsEndpoint), pm) + if err != nil { + return GroupsPage{}, errors.NewSDKError(err) + } + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return GroupsPage{}, sdkerr + } + gp := GroupsPage{} + if err := json.Unmarshal(body, &gp); err != nil { + return GroupsPage{}, errors.NewSDKError(err) + } + + return gp, nil +} + +func (sdk mfSDK) ListUserThings(userID string, pm PageMetadata, token string) (ThingsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s", usersEndpoint, userID, thingsEndpoint), pm) + if err != nil { + return ThingsPage{}, errors.NewSDKError(err) + } + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return ThingsPage{}, sdkerr + } + tp := ThingsPage{} + if err := json.Unmarshal(body, &tp); err != nil { + return ThingsPage{}, errors.NewSDKError(err) + } + + return tp, nil +} + func (sdk mfSDK) EnableUser(id, token string) (User, errors.SDKError) { return sdk.changeClientStatus(token, id, enableEndpoint) } diff --git a/things/api/http/channels.go b/things/api/http/channels.go index 20a4d02a90..ac56496b6b 100644 --- a/things/api/http/channels.go +++ b/things/api/http/channels.go @@ -67,26 +67,6 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha opts..., ), "disable_channel").ServeHTTP) - // Instead of having this endpoint /channels/{groupID}/assign separately, - // we can have two separate endpoints for each member kind - // users (/channels/{groupID}/users) & user_groups (/channels/{groupID}/groups) - r.Post("/{groupID}/assign", otelhttp.NewHandler(kithttp.NewServer( - assignUsersGroupsEndpoint(svc), - decodeAssignUsersGroupsRequest, - api.EncodeResponse, - opts..., - ), "assign_members").ServeHTTP) - - // Instead of having this endpoint /channels/{groupID}/unassign separately, - // we can have two separate endpoints for each member kind - // users (/channels/{groupID}/users) & user_groups (/channels/{groupID}/groups) - r.Post("/{groupID}/unassign", otelhttp.NewHandler(kithttp.NewServer( - unassignUsersGroupsEndpoint(svc), - decodeUnassignUsersGroupsRequest, - api.EncodeResponse, - opts..., - ), "unassign_members").ServeHTTP) - // Request to add users to a channel // This endpoint can be used alternative to /channels/{groupID}/members r.Post("/{groupID}/users/assign", otelhttp.NewHandler(kithttp.NewServer( @@ -192,38 +172,6 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha return r } -func decodeAssignUsersGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := assignUsersGroupsRequest{ - token: apiutil.ExtractBearerToken(r), - groupID: chi.URLParam(r, "groupID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - - return req, nil -} - -func decodeUnassignUsersGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := unassignUsersGroupsRequest{ - token: apiutil.ExtractBearerToken(r), - groupID: chi.URLParam(r, "groupID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - - return req, nil -} - func decodeAssignUsersRequest(_ context.Context, r *http.Request) (interface{}, error) { if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) diff --git a/things/api/http/clients.go b/things/api/http/clients.go index 93c29b3712..c05da3ad2c 100644 --- a/things/api/http/clients.go +++ b/things/api/http/clients.go @@ -108,6 +108,7 @@ func clientsHandler(svc things.Service, r *chi.Mux, logger mflog.Logger) http.Ha api.EncodeResponse, opts..., ), "thing_delete_share").ServeHTTP) + }) // Ideal location: things service, channels endpoint @@ -122,6 +123,12 @@ func clientsHandler(svc things.Service, r *chi.Mux, logger mflog.Logger) http.Ha opts..., ), "list_things_by_channel_id").ServeHTTP) + r.Get("/users/{userID}/things", otelhttp.NewHandler(kithttp.NewServer( + listClientsEndpoint(svc), + decodeListClients, + api.EncodeResponse, + opts..., + ), "list_user_things").ServeHTTP) return r } @@ -185,6 +192,7 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) name: n, tag: t, permission: p, + userID: chi.URLParam(r, "userID"), owner: ownerID, } return req, nil diff --git a/things/api/http/endpoints.go b/things/api/http/endpoints.go index 94a0316bb7..d0b1350867 100644 --- a/things/api/http/endpoints.go +++ b/things/api/http/endpoints.go @@ -92,7 +92,7 @@ func listClientsEndpoint(svc things.Service) endpoint.Endpoint { Permission: req.permission, Metadata: req.metadata, } - page, err := svc.ListClients(ctx, req.token, pm) + page, err := svc.ListClients(ctx, req.token, req.userID, pm) if err != nil { return mfclients.ClientsPage{}, err } @@ -254,36 +254,6 @@ func buildMembersResponse(cp mfclients.MembersPage) memberPageRes { return res } -func assignUsersGroupsEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(assignUsersGroupsRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - if err := svc.Assign(ctx, req.token, req.groupID, req.Relation, req.MemberKind, req.Members...); err != nil { - return nil, err - } - - return assignUsersGroupsRes{}, nil - } -} - -func unassignUsersGroupsEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(unassignUsersGroupsRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - if err := svc.Unassign(ctx, req.token, req.groupID, req.Relation, req.MemberKind, req.Members...); err != nil { - return nil, err - } - - return unassignUsersGroupsRes{}, nil - } -} - func assignUsersEndpoint(svc groups.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(assignUsersRequest) diff --git a/things/api/http/requests.go b/things/api/http/requests.go index 5ce30759da..6fc6616289 100644 --- a/things/api/http/requests.go +++ b/things/api/http/requests.go @@ -75,6 +75,7 @@ type listClientsReq struct { owner string permission string visibility string + userID string metadata mfclients.Metadata } @@ -378,10 +379,9 @@ func (req *connectChannelThingRequest) validate() error { } type disconnectChannelThingRequest struct { - token string - ThingID string `json:"thing_id,omitempty"` - ChannelID string `json:"channel_id,omitempty"` - Permission string `json:"permission,omitempty"` + token string + ThingID string `json:"thing_id,omitempty"` + ChannelID string `json:"channel_id,omitempty"` } func (req *disconnectChannelThingRequest) validate() error { diff --git a/things/api/logging.go b/things/api/logging.go index b1bec13bc3..d2e471fa66 100644 --- a/things/api/logging.go +++ b/things/api/logging.go @@ -49,7 +49,7 @@ func (lm *loggingMiddleware) ViewClient(ctx context.Context, token, id string) ( return lm.svc.ViewClient(ctx, token, id) } -func (lm *loggingMiddleware) ListClients(ctx context.Context, token string, pm mfclients.Page) (cp mfclients.ClientsPage, err error) { +func (lm *loggingMiddleware) ListClients(ctx context.Context, token string, reqUserID string, pm mfclients.Page) (cp mfclients.ClientsPage, err error) { defer func(begin time.Time) { message := fmt.Sprintf("Method list_things using token %s took %s to complete", token, time.Since(begin)) if err != nil { @@ -58,7 +58,7 @@ func (lm *loggingMiddleware) ListClients(ctx context.Context, token string, pm m } lm.logger.Info(fmt.Sprintf("%s without errors.", message)) }(time.Now()) - return lm.svc.ListClients(ctx, token, pm) + return lm.svc.ListClients(ctx, token, reqUserID, pm) } func (lm *loggingMiddleware) UpdateClient(ctx context.Context, token string, client mfclients.Client) (c mfclients.Client, err error) { diff --git a/things/api/metrics.go b/things/api/metrics.go index 91f9ab2d9e..f20c09d8bd 100644 --- a/things/api/metrics.go +++ b/things/api/metrics.go @@ -46,12 +46,12 @@ func (ms *metricsMiddleware) ViewClient(ctx context.Context, token, id string) ( return ms.svc.ViewClient(ctx, token, id) } -func (ms *metricsMiddleware) ListClients(ctx context.Context, token string, pm mfclients.Page) (mfclients.ClientsPage, error) { +func (ms *metricsMiddleware) ListClients(ctx context.Context, token string, reqUserID string, pm mfclients.Page) (mfclients.ClientsPage, error) { defer func(begin time.Time) { ms.counter.With("method", "list_things").Add(1) ms.latency.With("method", "list_things").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.ListClients(ctx, token, pm) + return ms.svc.ListClients(ctx, token, reqUserID, pm) } func (ms *metricsMiddleware) UpdateClient(ctx context.Context, token string, client mfclients.Client) (mfclients.Client, error) { diff --git a/things/events/events.go b/things/events/events.go index 1aea40b79b..5077ba4914 100644 --- a/things/events/events.go +++ b/things/events/events.go @@ -188,12 +188,14 @@ func (vce viewClientEvent) Encode() (map[string]interface{}, error) { } type listClientEvent struct { + reqUserID string mfclients.Page } func (lce listClientEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ "operation": clientList, + "reqUserID": lce.reqUserID, "total": lce.Total, "offset": lce.Offset, "limit": lce.Limit, diff --git a/things/events/streams.go b/things/events/streams.go index 7c62612d0c..5ef9f842f5 100644 --- a/things/events/streams.go +++ b/things/events/streams.go @@ -118,12 +118,13 @@ func (es *eventStore) ViewClient(ctx context.Context, token, id string) (mfclien return cli, nil } -func (es *eventStore) ListClients(ctx context.Context, token string, pm mfclients.Page) (mfclients.ClientsPage, error) { - cp, err := es.svc.ListClients(ctx, token, pm) +func (es *eventStore) ListClients(ctx context.Context, token string, reqUserID string, pm mfclients.Page) (mfclients.ClientsPage, error) { + cp, err := es.svc.ListClients(ctx, token, reqUserID, pm) if err != nil { return cp, err } event := listClientEvent{ + reqUserID, pm, } if err := es.Publish(ctx, event); err != nil { diff --git a/things/service.go b/things/service.go index efe6a00711..e46287ab94 100644 --- a/things/service.go +++ b/things/service.go @@ -133,27 +133,78 @@ func (svc service) ViewClient(ctx context.Context, token string, id string) (mfc return svc.clients.RetrieveByID(ctx, id) } -func (svc service) ListClients(ctx context.Context, token string, pm mfclients.Page) (mfclients.ClientsPage, error) { +func (svc service) ListClients(ctx context.Context, token string, reqUserID string, pm mfclients.Page) (mfclients.ClientsPage, error) { + var ids []string + userID, err := svc.identify(ctx, token) if err != nil { return mfclients.ClientsPage{}, err } + switch { + case (reqUserID != "" && reqUserID != userID): + if _, err := svc.authorize(ctx, userType, tokenKind, userID, ownerPermission, userType, reqUserID); err != nil { + return mfclients.ClientsPage{}, err + } + rtids, err := svc.listClientIDs(ctx, reqUserID, pm.Permission) + if err != nil { + return mfclients.ClientsPage{}, err + } + ids, err = svc.filterAllowedThingIDs(ctx, userID, pm.Permission, rtids) + if err != nil { + return mfclients.ClientsPage{}, err + } + default: + ids, err = svc.listClientIDs(ctx, userID, pm.Permission) + if err != nil { + return mfclients.ClientsPage{}, err + } + } + + if len(ids) == 0 { + return mfclients.ClientsPage{ + Page: mfclients.Page{Total: 0, Limit: pm.Limit, Offset: pm.Offset}, + }, nil + } + + pm.IDs = ids + + return svc.clients.RetrieveAllByIDs(ctx, pm) +} + +func (svc service) listClientIDs(ctx context.Context, userID, permission string) ([]string, error) { tids, err := svc.auth.ListAllObjects(ctx, &mainflux.ListObjectsReq{ SubjectType: userType, Subject: userID, - Permission: pm.Permission, + Permission: permission, ObjectType: thingType, }) if err != nil { - return mfclients.ClientsPage{}, err + return nil, err } - - pm.IDs = tids.Policies - - return svc.clients.RetrieveAllByIDs(ctx, pm) + return tids.Policies, nil } +func (svc service) filterAllowedThingIDs(ctx context.Context, userID, permission string, thingIDs []string) ([]string, error) { + var ids []string + tids, err := svc.auth.ListAllObjects(ctx, &mainflux.ListObjectsReq{ + SubjectType: userType, + Subject: userID, + Permission: permission, + ObjectType: thingType, + }) + if err != nil { + return nil, err + } + for _, thingID := range thingIDs { + for _, tid := range tids.Policies { + if thingID == tid { + ids = append(ids, thingID) + } + } + } + return ids, nil +} func (svc service) UpdateClient(ctx context.Context, token string, cli mfclients.Client) (mfclients.Client, error) { userID, err := svc.authorize(ctx, userType, tokenKind, token, editPermission, thingType, cli.ID) if err != nil { @@ -258,6 +309,7 @@ func (svc service) Share(ctx context.Context, token, id, relation string, userid } for _, userid := range userids { + addPolicyReq := &mainflux.AddPolicyReq{ SubjectType: userType, Subject: userid, @@ -265,6 +317,7 @@ func (svc service) Share(ctx context.Context, token, id, relation string, userid ObjectType: thingType, Object: id, } + res, err := svc.auth.AddPolicy(ctx, addPolicyReq) if err != nil { return err @@ -273,7 +326,6 @@ func (svc service) Share(ctx context.Context, token, id, relation string, userid return errors.ErrAuthorization } } - return nil } @@ -284,6 +336,7 @@ func (svc service) Unshare(ctx context.Context, token, id, relation string, user } for _, userid := range userids { + delPolicyReq := &mainflux.DeletePolicyReq{ SubjectType: userType, Subject: userid, @@ -291,6 +344,7 @@ func (svc service) Unshare(ctx context.Context, token, id, relation string, user ObjectType: thingType, Object: id, } + res, err := svc.auth.DeletePolicy(ctx, delPolicyReq) if err != nil { return err @@ -299,7 +353,6 @@ func (svc service) Unshare(ctx context.Context, token, id, relation string, user return errors.ErrAuthorization } } - return nil } @@ -336,6 +389,7 @@ func (svc service) ListClientsByGroup(ctx context.Context, token, groupID string } pm.IDs = tids.Policies + cp, err := svc.clients.RetrieveAllByIDs(ctx, pm) if err != nil { return mfclients.MembersPage{}, err diff --git a/things/service_test.go b/things/service_test.go index 81ea98cf00..041d2295f8 100644 --- a/things/service_test.go +++ b/things/service_test.go @@ -591,7 +591,7 @@ func TestListClients(t *testing.T) { repoCall1 = auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&mainflux.ListObjectsRes{}, errors.ErrAuthorization) } repoCall2 := cRepo.On("RetrieveAllByIDs", context.Background(), mock.Anything).Return(tc.response, tc.err) - page, err := svc.ListClients(context.Background(), tc.token, tc.page) + page, err := svc.ListClients(context.Background(), tc.token, "", tc.page) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) repoCall.Unset() @@ -949,7 +949,7 @@ func TestEnableClient(t *testing.T) { repoCall := auth.On("Identify", mock.Anything, &mainflux.IdentityReq{Token: validToken}).Return(&mainflux.IdentityRes{Id: validID}, nil) repoCall1 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&mainflux.ListObjectsRes{}, nil) repoCall2 := cRepo.On("RetrieveAllByIDs", context.Background(), mock.Anything).Return(tc.response, nil) - page, err := svc.ListClients(context.Background(), validToken, pm) + page, err := svc.ListClients(context.Background(), validToken, "", pm) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) size := uint64(len(page.Clients)) assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", tc.desc, tc.size, size)) @@ -1074,7 +1074,7 @@ func TestDisableClient(t *testing.T) { repoCall := auth.On("Identify", mock.Anything, &mainflux.IdentityReq{Token: validToken}).Return(&mainflux.IdentityRes{Id: validID}, nil) repoCall1 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&mainflux.ListObjectsRes{}, nil) repoCall2 := cRepo.On("RetrieveAllByIDs", context.Background(), mock.Anything).Return(tc.response, nil) - page, err := svc.ListClients(context.Background(), validToken, pm) + page, err := svc.ListClients(context.Background(), validToken, "", pm) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) size := uint64(len(page.Clients)) assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", tc.desc, tc.size, size)) diff --git a/things/things.go b/things/things.go index 0da5ec9aa4..2ad4700f1f 100644 --- a/things/things.go +++ b/things/things.go @@ -21,7 +21,7 @@ type Service interface { ViewClient(ctx context.Context, token, id string) (clients.Client, error) // ListClients retrieves clients list for a valid auth token. - ListClients(ctx context.Context, token string, pm clients.Page) (clients.ClientsPage, error) + ListClients(ctx context.Context, token string, reqUserID string, pm clients.Page) (clients.ClientsPage, error) // ListClientsByGroup retrieves data about subset of things that are // connected or not connected to specified channel and belong to the user identified by diff --git a/things/tracing/tracing.go b/things/tracing/tracing.go index 096c834370..b9b2023e60 100644 --- a/things/tracing/tracing.go +++ b/things/tracing/tracing.go @@ -41,10 +41,10 @@ func (tm *tracingMiddleware) ViewClient(ctx context.Context, token string, id st } // ListClients traces the "ListClients" operation of the wrapped policies.Service. -func (tm *tracingMiddleware) ListClients(ctx context.Context, token string, pm mfclients.Page) (mfclients.ClientsPage, error) { +func (tm *tracingMiddleware) ListClients(ctx context.Context, token string, reqUserID string, pm mfclients.Page) (mfclients.ClientsPage, error) { ctx, span := tm.tracer.Start(ctx, "svc_list_clients") defer span.End() - return tm.svc.ListClients(ctx, token, pm) + return tm.svc.ListClients(ctx, token, reqUserID, pm) } // UpdateClient traces the "UpdateClient" operation of the wrapped policies.Service. @@ -128,7 +128,6 @@ func (tm *tracingMiddleware) Authorize(ctx context.Context, req *mainflux.Author func (tm *tracingMiddleware) Share(ctx context.Context, token, id string, relation string, userids ...string) error { ctx, span := tm.tracer.Start(ctx, "share", trace.WithAttributes(attribute.String("id", id), attribute.String("relation", relation), attribute.StringSlice("user_ids", userids))) defer span.End() - return tm.svc.Share(ctx, token, id, relation, userids...) } @@ -136,6 +135,5 @@ func (tm *tracingMiddleware) Share(ctx context.Context, token, id string, relati func (tm *tracingMiddleware) Unshare(ctx context.Context, token, id string, relation string, userids ...string) error { ctx, span := tm.tracer.Start(ctx, "unshare", trace.WithAttributes(attribute.String("id", id), attribute.String("relation", relation), attribute.StringSlice("user_ids", userids))) defer span.End() - return tm.svc.Unshare(ctx, token, id, relation, userids...) } diff --git a/tools/provision/provision.go b/tools/provision/provision.go index 20a1c20f29..86aafe20da 100644 --- a/tools/provision/provision.go +++ b/tools/provision/provision.go @@ -224,17 +224,19 @@ func Provision(conf Config) error { fmt.Printf("[[channels]]\nchannel_id = \"%s\"\n\n", cIDs[i]) } - for i := 0; i < conf.Num; i++ { - conIDs := sdk.Connection{ - ChannelID: cIDs[i], - ThingID: tIDs[i], - } - - if err := s.Connect(conIDs, token.AccessToken); err != nil { - log.Fatalf("Failed to connect thing %s to channel %s: %s", conIDs.ThingID, conIDs.ChannelID, err) + for _, cID := range cIDs { + for _, tID := range tIDs { + conIDs := sdk.Connection{ + ThingID: tID, + ChannelID: cID, + } + if err := s.Connect(conIDs, token.AccessToken); err != nil { + log.Fatalf("Failed to connect things %s to channels %s: %s", tID, cID, err) + } } } + return nil } diff --git a/users/api/clients.go b/users/api/clients.go index 506ca10d5b..e7d4da5824 100644 --- a/users/api/clients.go +++ b/users/api/clients.go @@ -138,8 +138,8 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger mflog.Logger) http.Han // and users service can access spiceDB and get the user list with user_group_id. // Request to get list of users present in the user_group_id {groupID} r.Get("/groups/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( - listMembersEndpoint(svc), - decodeListMembersRequest, + listMembersByGroupEndpoint(svc), + decodeListMembersByGroup, api.EncodeResponse, opts..., ), "list_users_by_user_group_id").ServeHTTP) @@ -148,16 +148,20 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger mflog.Logger) http.Han // Reason for placing here : // SpiceDB provides list of user ids in given channel_id // and users service can access spiceDB and get the user list with channel_id. - // Request to get list of users present in the user_group_id {groupID} - // The ideal placeholder name should be {channelID}, but gapi.DecodeListGroupsRequest uses {groupID} as a placeholder for the ID. - // So here, we are using {groupID} as the placeholder. - r.Get("/channels/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( - listMembersEndpoint(svc), - decodeListMembersRequest, + // Request to get list of users present in the user_group_id {channelID} + r.Get("/channels/{channelID}/users", otelhttp.NewHandler(kithttp.NewServer( + listMembersByChannelEndpoint(svc), + decodeListMembersByChannel, api.EncodeResponse, opts..., ), "list_users_by_channel_id").ServeHTTP) + r.Get("/things/{thingID}/users", otelhttp.NewHandler(kithttp.NewServer( + listMembersByThingEndpoint(svc), + decodeListMembersByThing, + api.EncodeResponse, + opts..., + ), "list_users_by_thing_id").ServeHTTP) return r } @@ -398,62 +402,98 @@ func decodeChangeClientStatus(_ context.Context, r *http.Request) (interface{}, return req, nil } -func decodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { +func decodeListMembersByGroup(_ context.Context, r *http.Request) (interface{}, error) { + page, err := queryPageParams(r) + if err != nil { + return nil, err + } + req := listMembersByObjectReq{ + token: apiutil.ExtractBearerToken(r), + Page: page, + objectID: chi.URLParam(r, "groupID"), + } + + return req, nil +} + +func decodeListMembersByChannel(_ context.Context, r *http.Request) (interface{}, error) { + page, err := queryPageParams(r) + if err != nil { + return nil, err + } + req := listMembersByObjectReq{ + token: apiutil.ExtractBearerToken(r), + Page: page, + objectID: chi.URLParam(r, "channelID"), + } + + return req, nil +} + +func decodeListMembersByThing(_ context.Context, r *http.Request) (interface{}, error) { + page, err := queryPageParams(r) + if err != nil { + return nil, err + } + req := listMembersByObjectReq{ + token: apiutil.ExtractBearerToken(r), + Page: page, + objectID: chi.URLParam(r, "thingID"), + } + + return req, nil +} + +func queryPageParams(r *http.Request) (mfclients.Page, error) { s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } l, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } m, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } n, err := apiutil.ReadStringQuery(r, api.NameKey, "") if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } i, err := apiutil.ReadStringQuery(r, api.IdentityKey, "") if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } t, err := apiutil.ReadStringQuery(r, api.TagKey, "") if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } oid, err := apiutil.ReadStringQuery(r, api.OwnerKey, "") if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } st, err := mfclients.ToStatus(s) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } p, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - req := listMembersReq{ - token: apiutil.ExtractBearerToken(r), - Page: mfclients.Page{ - Status: st, - Offset: o, - Limit: l, - Metadata: m, - Identity: i, - Name: n, - Tag: t, - Owner: oid, - Permission: p, - }, - groupID: chi.URLParam(r, "groupID"), - } - - return req, nil + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) + } + return mfclients.Page{ + Status: st, + Offset: o, + Limit: l, + Metadata: m, + Identity: i, + Name: n, + Tag: t, + Owner: oid, + Permission: p, + }, nil } diff --git a/users/api/endpoints.go b/users/api/endpoints.go index f59f79aea5..d8a8f43e30 100644 --- a/users/api/endpoints.go +++ b/users/api/endpoints.go @@ -102,14 +102,50 @@ func listClientsEndpoint(svc users.Service) endpoint.Endpoint { } } -func listMembersEndpoint(svc users.Service) endpoint.Endpoint { +func listMembersByGroupEndpoint(svc users.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersReq) + req := request.(listMembersByObjectReq) + req.objectKind = "groups" if err := req.validate(); err != nil { return memberPageRes{}, errors.Wrap(apiutil.ErrValidation, err) } - page, err := svc.ListMembers(ctx, req.token, req.groupID, req.Page) + page, err := svc.ListMembers(ctx, req.token, req.objectKind, req.objectID, req.Page) + if err != nil { + return memberPageRes{}, err + } + + return buildMembersResponse(page), nil + } +} + +func listMembersByChannelEndpoint(svc users.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listMembersByObjectReq) + // In spiceDB schema, using the same 'group' type for both channels and groups, rather than having a separate type for channels. + req.objectKind = "groups" + if err := req.validate(); err != nil { + return memberPageRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + + page, err := svc.ListMembers(ctx, req.token, req.objectKind, req.objectID, req.Page) + if err != nil { + return memberPageRes{}, err + } + + return buildMembersResponse(page), nil + } +} + +func listMembersByThingEndpoint(svc users.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listMembersByObjectReq) + req.objectKind = "things" + if err := req.validate(); err != nil { + return memberPageRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + + page, err := svc.ListMembers(ctx, req.token, req.objectKind, req.objectID, req.Page) if err != nil { return memberPageRes{}, err } diff --git a/users/api/groups.go b/users/api/groups.go index 114fe6ca49..4c22266ef3 100644 --- a/users/api/groups.go +++ b/users/api/groups.go @@ -83,24 +83,6 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha opts..., ), "disable_group").ServeHTTP) - // Instead of this endpoint /{groupID}/members/assign separately, we can simply use /{groupID}/users - // because this group is intended exclusively for users. No other entity could not be added - r.Post("/{groupID}/members/assign", otelhttp.NewHandler(kithttp.NewServer( - gapi.AssignMembersEndpoint(svc, "", "users"), - gapi.DecodeAssignMembersRequest, - api.EncodeResponse, - opts..., - ), "assign_members").ServeHTTP) - - // Instead of maintaining this endpoint /{groupID}/members/unassign separately, we can simply use /{groupID}/users - // because this group is intended exclusively for users. No other entity could not be added - r.Post("/{groupID}/members/unassign", otelhttp.NewHandler(kithttp.NewServer( - gapi.UnassignMembersEndpoint(svc, "", "users"), - gapi.DecodeUnassignMembersRequest, - api.EncodeResponse, - opts..., - ), "unassign_members").ServeHTTP) - r.Post("/{groupID}/users/assign", otelhttp.NewHandler(kithttp.NewServer( assignUsersEndpoint(svc), decodeAssignUsersRequest, diff --git a/users/api/logging.go b/users/api/logging.go index fe7bf81a34..85d161fc34 100644 --- a/users/api/logging.go +++ b/users/api/logging.go @@ -252,16 +252,16 @@ func (lm *loggingMiddleware) DisableClient(ctx context.Context, token, id string // ListMembers logs the list_members request. It logs the group id, token and the time it took to complete the request. // If the request fails, it logs the error. -func (lm *loggingMiddleware) ListMembers(ctx context.Context, token, groupID string, cp mfclients.Page) (mp mfclients.MembersPage, err error) { +func (lm *loggingMiddleware) ListMembers(ctx context.Context, token, objectKind, objectID string, cp mfclients.Page) (mp mfclients.MembersPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_members %d members for group with id %s and token %s took %s to complete", mp.Total, groupID, token, time.Since(begin)) + message := fmt.Sprintf("Method list_members %d members for object kind %s and object id %s and token %s took %s to complete", mp.Total, objectKind, objectID, token, time.Since(begin)) if err != nil { lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) return } lm.logger.Info(fmt.Sprintf("%s without errors.", message)) }(time.Now()) - return lm.svc.ListMembers(ctx, token, groupID, cp) + return lm.svc.ListMembers(ctx, token, objectKind, objectID, cp) } // Identify logs the identify request. It logs the token and the time it took to complete the request. diff --git a/users/api/metrics.go b/users/api/metrics.go index b055215966..409c52046d 100644 --- a/users/api/metrics.go +++ b/users/api/metrics.go @@ -175,12 +175,12 @@ func (ms *metricsMiddleware) DisableClient(ctx context.Context, token string, id } // ListMembers instruments ListMembers method with metrics. -func (ms *metricsMiddleware) ListMembers(ctx context.Context, token, groupID string, pm mfclients.Page) (mp mfclients.MembersPage, err error) { +func (ms *metricsMiddleware) ListMembers(ctx context.Context, token, objectKind, objectID string, pm mfclients.Page) (mp mfclients.MembersPage, err error) { defer func(begin time.Time) { ms.counter.With("method", "list_members").Add(1) ms.latency.With("method", "list_members").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.ListMembers(ctx, token, groupID, pm) + return ms.svc.ListMembers(ctx, token, objectKind, objectID, pm) } // Identify instruments Identify method with metrics. diff --git a/users/api/requests.go b/users/api/requests.go index f323aa7c43..ce183d4b2e 100644 --- a/users/api/requests.go +++ b/users/api/requests.go @@ -83,19 +83,23 @@ func (req listClientsReq) validate() error { return nil } -type listMembersReq struct { +type listMembersByObjectReq struct { mfclients.Page - token string - groupID string + token string + objectKind string + objectID string } -func (req listMembersReq) validate() error { +func (req listMembersByObjectReq) validate() error { if req.token == "" { return apiutil.ErrBearerToken } - if req.groupID == "" { + if req.objectID == "" { return apiutil.ErrMissingID } + if req.objectKind == "" { + return apiutil.ErrMissingMemberKind + } return nil } diff --git a/users/clients.go b/users/clients.go index 1d17b28997..ce63a4c62d 100644 --- a/users/clients.go +++ b/users/clients.go @@ -26,8 +26,8 @@ type Service interface { // ListClients retrieves clients list for a valid auth token. ListClients(ctx context.Context, token string, pm clients.Page) (clients.ClientsPage, error) - // ListMembers retrieves everything that is assigned to a group identified by groupID. - ListMembers(ctx context.Context, token, groupID string, pm clients.Page) (clients.MembersPage, error) + // ListMembers retrieves everything that is assigned to a group/thing identified by objectID. + ListMembers(ctx context.Context, token, objectKind, objectID string, pm clients.Page) (clients.MembersPage, error) // UpdateClient updates the client's name and metadata. UpdateClient(ctx context.Context, token string, client clients.Client) (clients.Client, error) diff --git a/users/events/events.go b/users/events/events.go index 18d0fb6995..ed514153a5 100644 --- a/users/events/events.go +++ b/users/events/events.go @@ -290,16 +290,18 @@ func (lce listClientEvent) Encode() (map[string]interface{}, error) { type listClientByGroupEvent struct { mfclients.Page - channelID string + objectKind string + objectID string } func (lcge listClientByGroupEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": clientListByGroup, - "total": lcge.Total, - "offset": lcge.Offset, - "limit": lcge.Limit, - "channel_id": lcge.channelID, + "operation": clientListByGroup, + "total": lcge.Total, + "offset": lcge.Offset, + "limit": lcge.Limit, + "object_kind": lcge.objectKind, + "object_id": lcge.objectID, } if lcge.Name != "" { diff --git a/users/events/streams.go b/users/events/streams.go index 870ca437e2..5e2942bfee 100644 --- a/users/events/streams.go +++ b/users/events/streams.go @@ -160,13 +160,13 @@ func (es *eventStore) ListClients(ctx context.Context, token string, pm mfclient return cp, nil } -func (es *eventStore) ListMembers(ctx context.Context, token, groupID string, pm mfclients.Page) (mfclients.MembersPage, error) { - mp, err := es.svc.ListMembers(ctx, token, groupID, pm) +func (es *eventStore) ListMembers(ctx context.Context, token, objectKind, objectID string, pm mfclients.Page) (mfclients.MembersPage, error) { + mp, err := es.svc.ListMembers(ctx, token, objectKind, objectID, pm) if err != nil { return mp, err } event := listClientByGroupEvent{ - pm, groupID, + pm, objectKind, objectID, } if err := es.Publish(ctx, event); err != nil { diff --git a/users/service.go b/users/service.go index 438c6b5f47..2700e28a62 100644 --- a/users/service.go +++ b/users/service.go @@ -17,11 +17,13 @@ import ( ) const ( - userKind = "users" - tokenKind = "token" + userKind = "users" + tokenKind = "token" + thingsKind = "things" userType = "user" groupType = "group" + thingType = "thing" ) var ( @@ -145,52 +147,11 @@ func (svc service) ListClients(ctx context.Context, token string, pm mfclients.P if err != nil { return mfclients.ClientsPage{}, err } - - // switch err := svc.authorize(ctx, id, clientsObjectKey, listRelationKey); err { - // // If the user is admin, fetch all users from database. - // case nil: - // switch { - // // visibility = all - // case pm.SharedBy == myKey && pm.Owner == myKey: - // pm.SharedBy = "" - // pm.Owner = "" - // // visibility = shared - // case pm.SharedBy == myKey && pm.Owner != myKey: - // pm.SharedBy = id - // pm.Owner = "" - // // visibility = mine - // case pm.Owner == myKey && pm.SharedBy != myKey: - // pm.Owner = id - // pm.SharedBy = "" - // } - - // // If the user is not admin, fetch users that they own or are shared with them. - // default: - // switch { - // // visibility = all - // case pm.SharedBy == myKey && pm.Owner == myKey: - // pm.SharedBy = id - // pm.Owner = id - // // visibility = shared - // case pm.SharedBy == myKey && pm.Owner != myKey: - // pm.SharedBy = id - // pm.Owner = "" - // // visibility = mine - // case pm.Owner == myKey && pm.SharedBy != myKey: - // pm.Owner = id - // pm.SharedBy = "" - // default: - // pm.Owner = id - // } - // pm.Action = listRelationKey - // } pm.Owner = id - clients, err := svc.clients.RetrieveAll(ctx, pm) if err != nil { return mfclients.ClientsPage{}, err } - return clients, nil } @@ -413,19 +374,32 @@ func (svc service) changeClientStatus(ctx context.Context, token string, client return svc.clients.ChangeStatus(ctx, client) } -func (svc service) ListMembers(ctx context.Context, token, groupID string, pm mfclients.Page) (mfclients.MembersPage, error) { - if _, err := svc.authorize(ctx, userType, tokenKind, token, pm.Permission, groupType, groupID); err != nil { +func (svc service) ListMembers(ctx context.Context, token, objectKind string, objectID string, pm mfclients.Page) (mfclients.MembersPage, error) { + var objectType string + switch objectKind { + case thingsKind: + objectType = thingType + default: + objectType = groupType + } + + if _, err := svc.authorize(ctx, userType, tokenKind, token, pm.Permission, objectType, objectID); err != nil { return mfclients.MembersPage{}, err } uids, err := svc.auth.ListAllSubjects(ctx, &mainflux.ListSubjectsReq{ SubjectType: userType, Permission: pm.Permission, - Object: groupID, - ObjectType: groupType, + Object: objectID, + ObjectType: objectType, }) if err != nil { return mfclients.MembersPage{}, err } + if len(uids.Policies) == 0 { + return mfclients.MembersPage{ + Page: mfclients.Page{Total: 0, Offset: pm.Offset, Limit: pm.Limit}, + }, nil + } pm.IDs = uids.Policies @@ -433,6 +407,7 @@ func (svc service) ListMembers(ctx context.Context, token, groupID string, pm mf if err != nil { return mfclients.MembersPage{}, err } + return mfclients.MembersPage{ Page: cp.Page, Members: cp.Clients, diff --git a/users/service_test.go b/users/service_test.go index 7002b25abf..d815e71935 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -1321,7 +1321,7 @@ func TestListMembers(t *testing.T) { repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&mainflux.AuthorizeRes{Authorized: true}, nil) repoCall2 := auth.On("ListAllSubjects", mock.Anything, mock.Anything).Return(&mainflux.ListSubjectsRes{}, nil) repoCall3 := cRepo.On("RetrieveAll", context.Background(), tc.page).Return(mfclients.ClientsPage{Page: tc.response.Page, Clients: tc.response.Members}, tc.err) - page, err := svc.ListMembers(context.Background(), tc.token, tc.groupID, tc.page) + page, err := svc.ListMembers(context.Background(), tc.token, "groups", tc.groupID, tc.page) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) if tc.err == nil { diff --git a/users/tracing/tracing.go b/users/tracing/tracing.go index dc4b298022..92cf513a79 100644 --- a/users/tracing/tracing.go +++ b/users/tracing/tracing.go @@ -172,11 +172,11 @@ func (tm *tracingMiddleware) DisableClient(ctx context.Context, token, id string } // ListMembers traces the "ListMembers" operation of the wrapped clients.Service. -func (tm *tracingMiddleware) ListMembers(ctx context.Context, token, groupID string, pm mfclients.Page) (mfclients.MembersPage, error) { - ctx, span := tm.tracer.Start(ctx, "svc_list_members", trace.WithAttributes(attribute.String("group_id", groupID))) +func (tm *tracingMiddleware) ListMembers(ctx context.Context, token, objectKind, objectID string, pm mfclients.Page) (mfclients.MembersPage, error) { + ctx, span := tm.tracer.Start(ctx, "svc_list_members", trace.WithAttributes(attribute.String("object_kind", objectKind)), trace.WithAttributes(attribute.String("object_id", objectID))) defer span.End() - return tm.svc.ListMembers(ctx, token, groupID, pm) + return tm.svc.ListMembers(ctx, token, objectKind, objectID, pm) } // Identify traces the "Identify" operation of the wrapped clients.Service.