MF-1394 - SDK groups (#1396)

* remove owner id

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix groups sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix groups sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix groups sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small fix, revert some changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test fail

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix unassign request

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* decouple util method form group repo

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolve comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix param parsing

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
This commit is contained in:
Mirko Teodorovic 2021-03-26 22:51:02 +01:00 committed by GitHub
parent f9f51470b1
commit 0cdcf28683
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 187 additions and 68 deletions

View File

@ -210,7 +210,7 @@ func assignEndpoint(svc auth.Service) endpoint.Endpoint {
func unassignEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(assignReq)
req := request.(unassignReq)
if err := req.validate(); err != nil {
return nil, err
}

View File

@ -128,6 +128,22 @@ func (req assignReq) validate() error {
return nil
}
type unassignReq struct {
assignReq
}
func (req unassignReq) validate() error {
if req.token == "" {
return auth.ErrUnauthorizedAccess
}
if req.groupID == "" || len(req.Members) == 0 {
return auth.ErrMalformedEntity
}
return nil
}
type groupReq struct {
token string
id string

View File

@ -99,7 +99,7 @@ func MakeHandler(svc auth.Service, mux *bone.Mux, tracer opentracing.Tracer) *bo
mux.Delete("/groups/:groupID/members", kithttp.NewServer(
kitot.TraceServer(tracer, "unassign")(unassignEndpoint(svc)),
decodeAssignRequest,
decodeUnassignRequest,
encodeResponse,
opts...,
))
@ -282,6 +282,21 @@ func decodeAssignRequest(_ context.Context, r *http.Request) (interface{}, error
return req, nil
}
func decodeUnassignRequest(_ context.Context, r *http.Request) (interface{}, error) {
req := unassignReq{
assignReq{
token: r.Header.Get("Authorization"),
groupID: bone.GetValue(r, "groupID"),
},
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(auth.ErrMalformedEntity, err)
}
return req, nil
}
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("Content-Type", contentType)

View File

@ -199,9 +199,9 @@ func (lm *loggingMiddleware) ListMembers(ctx context.Context, token, groupID, gr
return lm.svc.ListMembers(ctx, token, groupID, groupType, pm)
}
func (lm *loggingMiddleware) ListMemberships(ctx context.Context, token, groupID string, pm auth.PageMetadata) (gp auth.GroupPage, err error) {
func (lm *loggingMiddleware) ListMemberships(ctx context.Context, token, memberID string, pm auth.PageMetadata) (gp auth.GroupPage, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method list_memberships for token %s and group id %s took %s to complete", token, groupID, time.Since(begin))
message := fmt.Sprintf("Method list_memberships for token %s and member id %s took %s to complete", token, memberID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@ -209,7 +209,7 @@ func (lm *loggingMiddleware) ListMemberships(ctx context.Context, token, groupID
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.ListMemberships(ctx, token, groupID, pm)
return lm.svc.ListMemberships(ctx, token, memberID, pm)
}
func (lm *loggingMiddleware) Assign(ctx context.Context, token, groupID, groupType string, memberIDs ...string) (err error) {

View File

@ -142,13 +142,13 @@ func (ms *metricsMiddleware) ListMembers(ctx context.Context, token, groupID, gr
return ms.svc.ListMembers(ctx, token, groupID, groupType, pm)
}
func (ms *metricsMiddleware) ListMemberships(ctx context.Context, token, groupID string, pm auth.PageMetadata) (gp auth.GroupPage, err error) {
func (ms *metricsMiddleware) ListMemberships(ctx context.Context, token, memberID string, pm auth.PageMetadata) (gp auth.GroupPage, err error) {
defer func(begin time.Time) {
ms.counter.With("method", "list_memberships").Add(1)
ms.latency.With("method", "list_memberships").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.ListMemberships(ctx, token, groupID, pm)
return ms.svc.ListMemberships(ctx, token, memberID, pm)
}
func (ms *metricsMiddleware) Assign(ctx context.Context, token, groupID, groupType string, memberIDs ...string) (err error) {

View File

@ -56,7 +56,7 @@ func (gr groupRepository) Save(ctx context.Context, g auth.Group) (auth.Group, e
RETURNING id, name, owner_id, parent_id, description, metadata, path, nlevel(path) as level, created_at, updated_at`
}
dbg, err := gr.toDBGroup(g)
dbg, err := toDBGroup(g)
if err != nil {
return auth.Group{}, err
}
@ -92,7 +92,7 @@ func (gr groupRepository) Update(ctx context.Context, g auth.Group) (auth.Group,
q := `UPDATE groups SET name = :name, description = :description, metadata = :metadata, updated_at = :updated_at WHERE id = :id
RETURNING id, name, owner_id, parent_id, description, metadata, path, nlevel(path) as level, created_at, updated_at`
dbu, err := gr.toDBGroup(g)
dbu, err := toDBGroup(g)
if err != nil {
return auth.Group{}, errors.Wrap(auth.ErrUpdateGroup, err)
}
@ -126,7 +126,7 @@ func (gr groupRepository) Delete(ctx context.Context, groupID string) error {
group := auth.Group{
ID: groupID,
}
dbg, err := gr.toDBGroup(group)
dbg, err := toDBGroup(group)
if err != nil {
return errors.Wrap(auth.ErrUpdateGroup, err)
}
@ -315,7 +315,7 @@ func (gr groupRepository) Members(ctx context.Context, groupID, groupType string
WHERE gr.group_id = :group_id %s`, mq)
}
params, err := gr.toDBMemberPage("", groupID, groupType, pm)
params, err := toDBMemberPage("", groupID, groupType, pm)
if err != nil {
return auth.MemberPage{}, err
}
@ -375,7 +375,7 @@ func (gr groupRepository) Memberships(ctx context.Context, memberID string, pm a
WHERE gr.group_id = g.id and gr.member_id = :member_id
%s ORDER BY id LIMIT :limit OFFSET :offset;`, mq)
params, err := gr.toDBMemberPage("", "", "", pm)
params, err := toDBMemberPage(memberID, "", "", pm)
if err != nil {
return auth.GroupPage{}, err
}
@ -562,7 +562,7 @@ func toString(id uuid.NullUUID) (string, error) {
return "", errStringToUUID
}
func (gr groupRepository) toDBGroup(g auth.Group) (dbGroup, error) {
func toDBGroup(g auth.Group) (dbGroup, error) {
ownerID, err := toUUID(g.OwnerID)
if err != nil {
return dbGroup{}, err
@ -604,7 +604,7 @@ func toDBGroupPage(id, path string, pm auth.PageMetadata) (dbGroupPage, error) {
}, nil
}
func (gr groupRepository) toDBMemberPage(memberID, groupID, groupType string, pm auth.PageMetadata) (dbMemberPage, error) {
func toDBMemberPage(memberID, groupID, groupType string, pm auth.PageMetadata) (dbMemberPage, error) {
return dbMemberPage{
GroupID: groupID,
MemberID: memberID,

View File

@ -45,7 +45,7 @@ var cmdGroups = []cobra.Command{
},
cobra.Command{
Use: "get",
Short: "get [all | children <group_id> | group_id] <user_auth_token>",
Short: "get [all | children <group_id> | parents <group_id> | group_id] <user_auth_token>",
Long: `Get all users groups, group children or group by id.
all - lists all groups
children <group_id> - lists all children groups of <group_id>
@ -56,7 +56,11 @@ var cmdGroups = []cobra.Command{
return
}
if args[0] == "all" {
l, err := sdk.Groups(args[1], uint64(Offset), uint64(Limit), "")
if len(args) > 2 {
logUsage(cmd.Short)
return
}
l, err := sdk.Groups(uint64(Offset), uint64(Limit), args[1])
if err != nil {
logError(err)
return
@ -65,7 +69,11 @@ var cmdGroups = []cobra.Command{
return
}
if args[0] == "children" {
l, err := sdk.Groups(args[2], uint64(Offset), uint64(Limit), args[1])
if len(args) > 3 {
logUsage(cmd.Short)
return
}
l, err := sdk.Children(args[1], uint64(Offset), uint64(Limit), args[2])
if err != nil {
logError(err)
return
@ -73,6 +81,23 @@ var cmdGroups = []cobra.Command{
logJSON(l)
return
}
if args[0] == "parents" {
if len(args) > 3 {
logUsage(cmd.Short)
return
}
l, err := sdk.Parents(args[1], uint64(Offset), uint64(Limit), args[2])
if err != nil {
logError(err)
return
}
logJSON(l)
return
}
if len(args) > 2 {
logUsage(cmd.Short)
return
}
t, err := sdk.Group(args[0], args[1])
if err != nil {
logError(err)
@ -83,14 +108,20 @@ var cmdGroups = []cobra.Command{
},
cobra.Command{
Use: "assign",
Short: "assign <user_id> <group_id> <user_auth_token>",
Long: `Assign user to a group.`,
Short: "assign <member_ids> <member_type> <group_id> <user_auth_token>",
Long: `Assign members to a group.
member_ids - '["member_id",...]`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 3 {
if len(args) != 4 {
logUsage(cmd.Short)
return
}
if err := sdk.Assign(args[0], args[1], args[2]); err != nil {
var ids []string
if err := json.Unmarshal([]byte(args[0]), &ids); err != nil {
logError(err)
return
}
if err := sdk.Assign(ids, args[1], args[2], args[3]); err != nil {
logError(err)
return
}
@ -99,14 +130,20 @@ var cmdGroups = []cobra.Command{
},
cobra.Command{
Use: "unassign",
Short: "unassign <user_id> <group_id> <user_auth_token>",
Long: `Unassign user from a group.`,
Short: "unassign <member_ids> <group_id> <user_auth_token>",
Long: `Unassign members from a group
member_ids - '["member_id",...]`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 3 {
logUsage(cmd.Short)
return
}
if err := sdk.Unassign(args[0], args[1], args[2]); err != nil {
var ids []string
if err := json.Unmarshal([]byte(args[0]), &ids); err != nil {
logError(err)
return
}
if err := sdk.Unassign(args[2], args[1], ids...); err != nil {
logError(err)
return
}
@ -116,7 +153,7 @@ var cmdGroups = []cobra.Command{
cobra.Command{
Use: "delete",
Short: "delete <group_id> <user_auth_token>",
Long: `Delete users group.`,
Long: `Delete group.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 2 {
logUsage(cmd.Short)
@ -132,7 +169,7 @@ var cmdGroups = []cobra.Command{
cobra.Command{
Use: "members",
Short: "members <group_id> <user_auth_token>",
Long: `Lists all user members of a group.`,
Long: `Lists all members of a group.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 2 {
logUsage(cmd.Short)
@ -148,8 +185,8 @@ var cmdGroups = []cobra.Command{
},
cobra.Command{
Use: "membership",
Short: "membership <user_id> <user_auth_token>",
Long: `List user groups membership`,
Short: "membership <member_id> <user_auth_token>",
Long: `List member group's membership`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 2 {
logUsage(cmd.Short)
@ -170,7 +207,7 @@ func NewGroupsCmd() *cobra.Command {
cmd := cobra.Command{
Use: "groups",
Short: "Groups management",
Long: `Groups management: create groups and assigns user to groups"`,
Long: `Groups management: create groups and assigns member to groups"`,
Run: func(cmd *cobra.Command, args []string) {
logUsage("groups [create | get | delete | assign | unassign | members | membership]")
},

View File

@ -11,11 +11,17 @@ import (
"net/http"
"strings"
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/pkg/errors"
)
const groupsEndpoint = "groups"
type assignRequest struct {
Type string `json:"type,omitempty"`
Members []string `json:"members"`
}
func (sdk mfSDK) CreateGroup(g Group, token string) (string, error) {
data, err := json.Marshal(g)
if err != nil {
@ -65,10 +71,23 @@ func (sdk mfSDK) DeleteGroup(id, token string) error {
return nil
}
func (sdk mfSDK) Assign(userID, groupID, token string) error {
endpoint := fmt.Sprintf("%s/%s/users/%s", groupsEndpoint, groupID, userID)
func (sdk mfSDK) Assign(memberIDs []string, memberType, groupID string, token string) error {
var ids []string
endpoint := fmt.Sprintf("%s/%s/members", groupsEndpoint, groupID)
url := createURL(sdk.baseURL, sdk.groupsPrefix, endpoint)
req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader([]byte{}))
ids = append(ids, memberIDs...)
assignReq := assignRequest{
Type: memberType,
Members: ids,
}
data, err := json.Marshal(assignReq)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return err
}
@ -78,21 +97,33 @@ func (sdk mfSDK) Assign(userID, groupID, token string) error {
return err
}
if resp.StatusCode != http.StatusNoContent {
return errors.Wrap(ErrFailedUserAdd, errors.New(resp.Status))
if resp.StatusCode != http.StatusOK {
return errors.Wrap(ErrMemberAdd, errors.New(resp.Status))
}
return nil
}
func (sdk mfSDK) Unassign(userID, groupID, token string) error {
endpoint := fmt.Sprintf("%s/%s/users/%s", groupsEndpoint, groupID, userID)
func (sdk mfSDK) Unassign(token, groupID string, memberIDs ...string) error {
var ids []string
endpoint := fmt.Sprintf("%s/%s/members", groupsEndpoint, groupID)
url := createURL(sdk.baseURL, sdk.groupsPrefix, endpoint)
req, err := http.NewRequest(http.MethodDelete, url, bytes.NewReader([]byte{}))
ids = append(ids, memberIDs...)
assignReq := assignRequest{
Members: ids,
}
data, err := json.Marshal(assignReq)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodDelete, url, bytes.NewReader(data))
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
@ -105,69 +136,80 @@ func (sdk mfSDK) Unassign(userID, groupID, token string) error {
return nil
}
func (sdk mfSDK) Members(groupID, token string, offset, limit uint64) (UsersPage, error) {
endpoint := fmt.Sprintf("%s/%s/users?offset=%d&limit=%d&", groupsEndpoint, groupID, offset, limit)
func (sdk mfSDK) Members(groupID, token string, offset, limit uint64) (auth.MemberPage, error) {
endpoint := fmt.Sprintf("%s/%s/members?offset=%d&limit=%d&", groupsEndpoint, groupID, offset, limit)
url := createURL(sdk.baseURL, sdk.groupsPrefix, endpoint)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return UsersPage{}, err
return auth.MemberPage{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return UsersPage{}, err
return auth.MemberPage{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return UsersPage{}, err
return auth.MemberPage{}, err
}
if resp.StatusCode != http.StatusOK {
return UsersPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
return auth.MemberPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var tp UsersPage
var tp auth.MemberPage
if err := json.Unmarshal(body, &tp); err != nil {
return UsersPage{}, err
return auth.MemberPage{}, err
}
return tp, nil
}
func (sdk mfSDK) Groups(token string, offset, limit uint64, id string) (GroupsPage, error) {
endpoint := fmt.Sprintf("%s?offset=%d&limit=%d", groupsEndpoint, offset, limit)
if id != "" {
endpoint = fmt.Sprintf("%s/%s/groups?offset=%d&limit=%d", groupsEndpoint, id, offset, limit)
}
func (sdk mfSDK) Groups(offset, limit uint64, token string) (auth.GroupPage, error) {
endpoint := fmt.Sprintf("%s?offset=%d&limit=%d&tree=false", groupsEndpoint, offset, limit)
url := createURL(sdk.baseURL, sdk.groupsPrefix, endpoint)
return sdk.getGroups(token, url)
}
func (sdk mfSDK) Parents(id string, offset, limit uint64, token string) (auth.GroupPage, error) {
endpoint := fmt.Sprintf("%s/%s/parents?offset=%d&limit=%d&tree=false&level=%d", groupsEndpoint, id, offset, limit, auth.MaxLevel)
url := createURL(sdk.baseURL, sdk.groupsPrefix, endpoint)
return sdk.getGroups(token, url)
}
func (sdk mfSDK) Children(id string, offset, limit uint64, token string) (auth.GroupPage, error) {
endpoint := fmt.Sprintf("%s/%s/children?offset=%d&limit=%d&tree=false&level=%d", groupsEndpoint, id, offset, limit, auth.MaxLevel)
url := createURL(sdk.baseURL, sdk.groupsPrefix, endpoint)
return sdk.getGroups(token, url)
}
func (sdk mfSDK) getGroups(token, url string) (auth.GroupPage, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return GroupsPage{}, err
return auth.GroupPage{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return GroupsPage{}, err
return auth.GroupPage{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return GroupsPage{}, err
return auth.GroupPage{}, err
}
if resp.StatusCode != http.StatusOK {
return GroupsPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
return auth.GroupPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var tp GroupsPage
var tp auth.GroupPage
if err := json.Unmarshal(body, &tp); err != nil {
return GroupsPage{}, err
return auth.GroupPage{}, err
}
return tp, nil
}

View File

@ -8,6 +8,8 @@ import (
"errors"
"fmt"
"net/http"
"github.com/mainflux/mainflux/auth"
)
const (
@ -70,8 +72,8 @@ var (
// ErrFailedCertUpdate failed to update certs in bootstrap config
ErrFailedCertUpdate = errors.New("failed to update certs in bootstrap config")
// ErrFailedUserAdd failed to add user to a group.
ErrFailedUserAdd = errors.New("failed to add user to group")
// ErrMemberAdd failed to add member to a group.
ErrMemberAdd = errors.New("failed to add member to group")
)
// ContentType represents all possible content types.
@ -158,19 +160,25 @@ type SDK interface {
DeleteGroup(id, token string) error
// Groups returns page of users groups.
Groups(token string, offset, limit uint64, name string) (GroupsPage, error)
Groups(offset, limit uint64, token string) (auth.GroupPage, error)
// Parents returns page of users groups.
Parents(id string, offset, limit uint64, token string) (auth.GroupPage, error)
// Children returns page of users groups.
Children(id string, offset, limit uint64, token string) (auth.GroupPage, error)
// Group returns users group object by id.
Group(id, token string) (Group, error)
// Assign assigns user to a group.
Assign(userID, groupID, token string) error
// Assign assigns member of member type (thing or user) to a group.
Assign(memberIDs []string, memberType, groupID string, token string) error
// Unassign removes user from a group.
Unassign(userID, groupID, token string) error
// Unassign removes member from a group.
Unassign(token, groupID string, memberIDs ...string) error
// Members lists member users of a group.
Members(groupID, token string, offset, limit uint64) (UsersPage, error)
// Members lists members of a group.
Members(groupID, token string, offset, limit uint64) (auth.MemberPage, error)
// Memberships lists groups for user.
Memberships(userID, token string, offset, limit uint64) (GroupsPage, error)

View File

@ -18,6 +18,7 @@ const (
usersEndpoint = "users"
tokensEndpoint = "tokens"
passwordEndpoint = "password"
membersEndpoint = "members"
)
func (sdk mfSDK) CreateUser(u User) (string, error) {
@ -156,8 +157,8 @@ func (sdk mfSDK) UpdatePassword(oldPass, newPass, token string) error {
return nil
}
func (sdk mfSDK) Memberships(userID, token string, offset, limit uint64) (GroupsPage, error) {
endpoint := fmt.Sprintf("%s/%s/groups?offset=%d&limit=%d&", usersEndpoint, userID, offset, limit)
func (sdk mfSDK) Memberships(memberID, token string, offset, limit uint64) (GroupsPage, error) {
endpoint := fmt.Sprintf("%s/%s/groups?offset=%d&limit=%d&", membersEndpoint, memberID, offset, limit)
url := createURL(sdk.baseURL, sdk.groupsPrefix, endpoint)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {