1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-24 13:48:49 +08:00

NOISSUE: Listing of shared things with users & Update SDK (#1923)

* NOISSUE - Fix Bugs (#20)

* fix bugs

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix bugs

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add Connect Disconnect endpoints (#23)

* fix bugs

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix bugs

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix list of things in a channel and Add connect disconnect endpoint

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix list of things in a channel and Add connect disconnect endpoint

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add: Things share with users (#25)

* fix list of things in a channel and Add connect disconnect endpoint

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add: things share with other users

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add: Listing of things, channels, groups, users  (#26)

* add: listing of channels, users, groups, things

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add: listing of channels, users, groups, things

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add: listing of channels, users, groups, things

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add: listing of channels, users, groups, things

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add: List of user groups & removed repeating code in groups (#29)

* removed repeating code in list groups

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add: list of user group

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix: otel handler operator name for endpoints

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* add: listing of shared things and users

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix: listing of shared things and users

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add: new SDK

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add: new SDK

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix: comment

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix: sdk function names

Signed-off-by: Arvindh <arvindh91@gmail.com>

* update: api spec

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix: channels connect request

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix: listing of clients and groups

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix: CLI

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix: array len comparision

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix: nginx

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
Co-authored-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
This commit is contained in:
Arvindh 2023-10-17 19:08:06 +05:30 committed by GitHub
parent b4e7e859fb
commit cd82cc5a43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 1074 additions and 2215 deletions

View File

@ -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

View File

@ -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

View File

@ -166,12 +166,152 @@ var cmdChannels = []cobra.Command{
logJSON(channel)
},
},
{
Use: "assign user <relation> <user_ids> <channel_id> <user_auth_token>",
Short: "Assign user",
Long: "Assign user to a channel\n" +
"Usage:\n" +
"\tmainflux-cli channels assign user <relation> '[\"<user_id_1>\", \"<user_id_2>\"]' <channel_id> $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 <relation> <user_ids> <channel_id> <user_auth_token>",
Short: "Unassign user",
Long: "Unassign user from a channel\n" +
"Usage:\n" +
"\tmainflux-cli channels unassign user <relation> '[\"<user_id_1>\", \"<user_id_2>\"]' <channel_id> $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 <group_ids> <channel_id> <user_auth_token>",
Short: "Assign group",
Long: "Assign group to a channel\n" +
"Usage:\n" +
"\tmainflux-cli channels assign group '[\"<group_id_1>\", \"<group_id_2>\"]' <channel_id> $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 <group_ids> <channel_id> <user_auth_token>",
Short: "Unassign group",
Long: "Unassign group from a channel\n" +
"Usage:\n" +
"\tmainflux-cli channels unassign group '[\"<group_id_1>\", \"<group_id_2>\"]' <channel_id> $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 <channel_id> <user_auth_token>",
Short: "List users",
Long: "List users of a channel\n" +
"Usage:\n" +
"\tmainflux-cli channels users <channel_id> $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 <channel_id> <user_auth_token>",
Short: "List groups",
Long: "List groups of a channel\n" +
"Usage:\n" +
"\tmainflux-cli channels groups <channel_id> $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`,
}

View File

@ -142,22 +142,22 @@ var cmdGroups = []cobra.Command{
},
},
{
Use: "assign <allowed_actions> <member_id> <group_id> <user_auth_token>",
Short: "Assign member",
Long: "Assign members to a group\n" +
Use: "assign user <relation> <user_ids> <group_id> <user_auth_token>",
Short: "Assign user",
Long: "Assign user to a group\n" +
"Usage:\n" +
"\tmainflux-cli groups assign '[\"<allowed_action>\", \"<allowed_action>\"]' <member_id> <group_id> $USERTOKEN\n",
"\tmainflux-cli groups assign user <relation> '[\"<user_id_1>\", \"<user_id_2>\"]' <group_id> $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 <member_id> <group_id> <user_auth_token>",
Short: "Unassign member",
Long: "Unassign member from a group\n" +
Use: "unassign user <relation> <user_ids> <group_id> <user_auth_token>",
Short: "Unassign user",
Long: "Unassign user from a group\n" +
"Usage:\n" +
"\tmainflux-cli groups unassign <member_id> <group_id> $USERTOKEN\n",
"\tmainflux-cli groups unassign user <relation> '[\"<user_id_1>\", \"<user_id_2>\"]' <group_id> $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 <group_id> <user_auth_token>",
Short: "Members list",
Long: "List group's members\n" +
Use: "users <group_id> <user_auth_token>",
Short: "List users",
Long: "List users in a group\n" +
"Usage:\n" +
"\tmainflux-cli groups members <group_id> $USERTOKEN",
"\tmainflux-cli groups users <group_id> $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 <member_id> <user_auth_token>",
Short: "Membership list",
Long: "List memberships of a member\n" +
Use: "channels <group_id> <user_auth_token>",
Short: "List channels",
Long: "List channels in a group\n" +
"Usage:\n" +
"\tmainflux-cli groups membership <member_id> $USERTOKEN",
"\tmainflux-cli groups channels <group_id> $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"`,
}

View File

@ -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 ] <subject_id> <object_id> <actions> <user_auth_token>",
Short: "Create policy",
Long: "Create a new policy\n" +
"Usage:\n" +
"\tmainflux-cli policies create users <user_id> <group_id> '[\"c_list\"]' $USERTOKEN\n" +
"\tmainflux-cli policies create things <thing_id> <channel_id> '[\"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 ] <subject_id> <object_id> <actions> <user_auth_token>",
Short: "Update policy",
Long: "Update policy\n" +
"Usage:\n" +
"\tmainflux-cli policies update users <user_id> <group_id> '[\"c_list\"]' $USERTOKEN\n" +
"\tmainflux-cli policies update things <thing_id> <channel_id> '[\"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 ] <user_auth_token>",
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 ] <subject_id> <object_id> <user_auth_token>",
Short: "Remove policy",
Long: "Removes a policy with the provided object and subject\n" +
"Usage:\n" +
"\tmainflux-cli policies remove users <user_id> <group_id> $USERTOKEN\n" +
"\tmainflux-cli policies remove things <thing_id> <channel_id> $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 ] <subject_id> <object_id> <action> <entity_type> <user_auth_token>",
Short: "Authorize access request",
Long: "Authorize subject over object with provided actions\n" +
"Usage:\n" +
"\tmainflux-cli policies authorize users <user_id> <group_id> \"c_list\" <entity_type> $USERTOKEN\n" +
"\tmainflux-cli policies authorize things <thing_id> <channel_id> \"m_read\" <entity_type> $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
}

View File

@ -215,23 +215,45 @@ var cmdThings = []cobra.Command{
},
},
{
Use: "share <channel_id> <user_id> <allowed_actions> <user_auth_token>",
Use: "share <thing_id> <user_id> <relation> <user_auth_token>",
Short: "Share thing with a user",
Long: "Share thing with a user\n" +
"Usage:\n" +
"\tmainflux-cli things share <channel_id> <user_id> '[\"c_list\", \"c_delete\"]' $USERTOKEN\n",
"\tmainflux-cli things share <thing_id> <user_id> <relation> $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 <thing_id> <user_id> <relation> <user_auth_token>",
Short: "Unshare thing with a user",
Long: "Unshare thing with a user\n" +
"Usage:\n" +
"\tmainflux-cli things share <thing_id> <user_id> <relation> $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 <thing_id> <user_auth_token>",
Short: "List users",
Long: "List users of a thing\n" +
"Usage:\n" +
"\tmainflux-cli things users <thing_id> $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`,
}

View File

@ -333,12 +333,92 @@ var cmdUsers = []cobra.Command{
logJSON(user)
},
},
{
Use: "channels <user_id> <user_auth_token>",
Short: "List channels",
Long: "List channels of user\n" +
"Usage:\n" +
"\tmainflux-cli users channels <user_id> <user_auth_token>\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 <user_id> <user_auth_token>",
Short: "List things",
Long: "List things of user\n" +
"Usage:\n" +
"\tmainflux-cli users things <user_id> <user_auth_token>\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 <user_id> <user_auth_token>",
Short: "List groups",
Long: "List groups of user\n" +
"Usage:\n" +
"\tmainflux-cli users groups <user_id> <user_auth_token>\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"`,
}

View File

@ -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

View File

@ -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) {

View File

@ -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...)
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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
}

File diff suppressed because it is too large Load Diff

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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.
//
// The `ShareThing` method calls the `Connect` method with the
// subject as `userID` rather than `thingID`.
// ShareThing shares 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.ShareThing("thing_id", req, "token")
// fmt.Println(err)
ShareThing(channelID, userID string, actions []string, token string) errors.SDKError
ShareThing(thingID string, req UsersRelationRequest, token string) errors.SDKError
// UnshareThing unshare a thing with other users.
//
// example:
// 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)
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`.
// AddUserToChannel add user to a channel.
//
// 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
// 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
// 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.
// RemoveUserFromChannel remove user from a group.
//
// 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)
// 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
// DeleteUserPolicy deletes policies.
//
// The subject in this case is the `userID` and the object is the `groupID`.
// ListChannelUsers list all users in a channel .
//
// 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
// 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)
// 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`.
// AddUserGroupToChannel add user group to a channel.
//
// 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
// 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
// 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`.
// RemoveUserGroupFromChannel remove user group from 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.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
// ListThingPolicies lists policies based on the given policy structure.
// ListChannelUserGroups list all user groups in a channel.
//
// 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)
// DeleteThingPolicy deletes policies.
//
// 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",
// }
// err := sdk.DeleteThingPolicy(policy, "token")
// fmt.Println(err)
DeleteThingPolicy(policy Policy, token string) 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`.
//
// 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)
// Assign assigns users to a group with the given actions.
//
// The `Assign` method calls the `CreateUserPolicy` method under the hood.
//
// example:
// err := sdk.Assign([]string{"g_add"}, "userID:1", "groupID:1", "token")
// fmt.Println(err)
Assign(action []string, userID, groupID, token string) errors.SDKError
// Unassign removes a user from a group.
//
// The `Unassign` method calls the `DeleteUserPolicy` method under the hood.
//
// 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:

View File

@ -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)
}
return sdk.CreateThingPolicy(policy, token)
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 up, nil
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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 {

View File

@ -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) {

View File

@ -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) {

View File

@ -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,

View File

@ -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 {

View File

@ -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
}
tids, err := svc.auth.ListAllObjects(ctx, &mainflux.ListObjectsReq{
SubjectType: userType,
Subject: userID,
Permission: pm.Permission,
ObjectType: thingType,
})
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
}
}
pm.IDs = tids.Policies
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: permission,
ObjectType: thingType,
})
if err != nil {
return nil, err
}
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

View File

@ -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))

View File

@ -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

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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) {
s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus)
func decodeListMembersByGroup(_ context.Context, r *http.Request) (interface{}, error) {
page, err := queryPageParams(r)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
return nil, err
}
o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset)
if err != nil {
return nil, 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)
}
m, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil)
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
n, err := apiutil.ReadStringQuery(r, api.NameKey, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
i, err := apiutil.ReadStringQuery(r, api.IdentityKey, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
t, err := apiutil.ReadStringQuery(r, api.TagKey, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
oid, err := apiutil.ReadStringQuery(r, api.OwnerKey, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
st, err := mfclients.ToStatus(s)
if err != nil {
return nil, 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"),
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 mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset)
if err != nil {
return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
l, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit)
if err != nil {
return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
m, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil)
if err != nil {
return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
n, err := apiutil.ReadStringQuery(r, api.NameKey, "")
if err != nil {
return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
i, err := apiutil.ReadStringQuery(r, api.IdentityKey, "")
if err != nil {
return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
t, err := apiutil.ReadStringQuery(r, api.TagKey, "")
if err != nil {
return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
oid, err := apiutil.ReadStringQuery(r, api.OwnerKey, "")
if err != nil {
return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
st, err := mfclients.ToStatus(s)
if err != nil {
return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err)
}
p, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission)
if err != 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
}

View File

@ -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
}

View File

@ -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,

View File

@ -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.

View File

@ -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.

View File

@ -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
}

View File

@ -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)

View File

@ -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 != "" {

View File

@ -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 {

View File

@ -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,

View File

@ -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 {

View File

@ -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.