From 05029d8060fa1aafd2a33c2b6bf2894d02ec09ac Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Fri, 6 Oct 2023 19:39:00 +0530 Subject: [PATCH 01/17] NOISSUE - Fix Bugs (#20) * fix bugs Signed-off-by: Arvindh * fix bugs Signed-off-by: Arvindh --------- Signed-off-by: Arvindh Signed-off-by: dusanb94 --- internal/groups/service.go | 25 ++ things/api/channels.go | 122 ++++++++++ things/api/http/endpoints.go | 435 ----------------------------------- things/api/policies.go | 110 +++++++++ things/api/requests.go | 275 ++++++++++++++++++++++ things/api/responses.go | 184 +++++++++++++++ 6 files changed, 716 insertions(+), 435 deletions(-) create mode 100644 things/api/channels.go create mode 100644 things/api/policies.go create mode 100644 things/api/requests.go create mode 100644 things/api/responses.go diff --git a/internal/groups/service.go b/internal/groups/service.go index 0c8920e14b..8b46fb14b6 100644 --- a/internal/groups/service.go +++ b/internal/groups/service.go @@ -212,6 +212,31 @@ func (svc service) ListGroups(ctx context.Context, token string, memberKind, mem return groups.Page{}, err } } + case usersKind: + if memberID != "" && userID != memberID { + if _, err := svc.authorizeKind(ctx, userType, usersKind, userID, ownerRelation, userType, memberID); err != nil { + return groups.Page{}, err + } + gids, err := svc.auth.ListAllObjects(ctx, &mainflux.ListObjectsReq{ + SubjectType: userType, + Subject: memberID, + Permission: viewPermission, + ObjectType: groupType, + }) + + if err != nil { + return groups.Page{}, err + } + for _, gid := range gids.Policies { + for _, id := range allowedIDs.Policies { + if id == gid { + ids = append(ids, id) + } + } + } + } else { + ids = allowedIDs.Policies + } default: return groups.Page{}, fmt.Errorf("invalid member kind") } diff --git a/things/api/channels.go b/things/api/channels.go new file mode 100644 index 0000000000..a013d04e7c --- /dev/null +++ b/things/api/channels.go @@ -0,0 +1,122 @@ +// Copyright (c) Mainflux +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/go-chi/chi/v5" + kithttp "github.com/go-kit/kit/transport/http" + "github.com/mainflux/mainflux/internal/api" + "github.com/mainflux/mainflux/internal/apiutil" + gapi "github.com/mainflux/mainflux/internal/groups/api" + "github.com/mainflux/mainflux/logger" + "github.com/mainflux/mainflux/pkg/errors" + "github.com/mainflux/mainflux/pkg/groups" + "github.com/mainflux/mainflux/things" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" +) + +func groupsHandler(svc groups.Service, tscv things.Service, r *chi.Mux, logger logger.Logger) http.Handler { + opts := []kithttp.ServerOption{ + kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), + } + r.Route("/channels", func(r chi.Router) { + r.Post("/", otelhttp.NewHandler(kithttp.NewServer( + gapi.CreateGroupEndpoint(svc), + gapi.DecodeGroupCreate, + api.EncodeResponse, + opts..., + ), "create_channel").ServeHTTP) + + r.Get("/{groupID}", otelhttp.NewHandler(kithttp.NewServer( + gapi.ViewGroupEndpoint(svc), + gapi.DecodeGroupRequest, + api.EncodeResponse, + opts..., + ), "view_channel").ServeHTTP) + + r.Put("/{groupID}", otelhttp.NewHandler(kithttp.NewServer( + gapi.UpdateGroupEndpoint(svc), + gapi.DecodeGroupUpdate, + api.EncodeResponse, + opts..., + ), "update_channel").ServeHTTP) + + r.Get("/{groupID}/things", otelhttp.NewHandler(kithttp.NewServer( + listMembersEndpoint(tscv), + decodeListMembersRequest, + api.EncodeResponse, + opts..., + ), "list_things_by_channel").ServeHTTP) + + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + gapi.ListGroupsEndpoint(svc, "users"), + gapi.DecodeListGroupsRequest, + api.EncodeResponse, + opts..., + ), "list_channels").ServeHTTP) + + r.Post("/{groupID}/enable", otelhttp.NewHandler(kithttp.NewServer( + gapi.EnableGroupEndpoint(svc), + gapi.DecodeChangeGroupStatus, + api.EncodeResponse, + opts..., + ), "enable_channel").ServeHTTP) + + r.Post("/{groupID}/disable", otelhttp.NewHandler(kithttp.NewServer( + gapi.DisableGroupEndpoint(svc), + gapi.DecodeChangeGroupStatus, + api.EncodeResponse, + opts..., + ), "disable_channel").ServeHTTP) + + r.Post("/{groupID}/members", otelhttp.NewHandler(kithttp.NewServer( + assignUsersGroupsEndpoint(svc), + decodeAssignUsersGroupsRequest, + api.EncodeResponse, + opts..., + ), "assign_members").ServeHTTP) + + r.Delete("/{groupID}/members", otelhttp.NewHandler(kithttp.NewServer( + unassignUsersGroupsEndpoint(svc), + decodeUnassignUsersGroupsRequest, + api.EncodeResponse, + opts..., + ), "unassign_members").ServeHTTP) + }) + + r.Get("/things/{memberID}/channels", otelhttp.NewHandler(kithttp.NewServer( + gapi.ListGroupsEndpoint(svc, "things"), + gapi.DecodeListGroupsRequest, + api.EncodeResponse, + opts..., + ), "list_channel_by_things").ServeHTTP) + + return r +} + +func decodeAssignUsersGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { + 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) { + 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 +} diff --git a/things/api/http/endpoints.go b/things/api/http/endpoints.go index 94a0316bb7..e69de29bb2 100644 --- a/things/api/http/endpoints.go +++ b/things/api/http/endpoints.go @@ -1,435 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package http - -import ( - "context" - - "github.com/go-kit/kit/endpoint" - "github.com/mainflux/mainflux/internal/apiutil" - mfclients "github.com/mainflux/mainflux/pkg/clients" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/mainflux/mainflux/pkg/groups" - "github.com/mainflux/mainflux/things" -) - -func createClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(createClientReq) - if err := req.validate(); err != nil { - return createClientRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - client, err := svc.CreateThings(ctx, req.token, req.client) - if err != nil { - return createClientRes{}, err - } - - return createClientRes{ - Client: client[0], - created: true, - }, nil - } -} - -func createClientsEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(createClientsReq) - if err := req.validate(); err != nil { - return clientsPageRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - page, err := svc.CreateThings(ctx, req.token, req.Clients...) - if err != nil { - return clientsPageRes{}, err - } - - res := clientsPageRes{ - pageRes: pageRes{ - Total: uint64(len(page)), - }, - Clients: []viewClientRes{}, - } - for _, c := range page { - res.Clients = append(res.Clients, viewClientRes{Client: c}) - } - - return res, nil - } -} - -func viewClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(viewClientReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - c, err := svc.ViewClient(ctx, req.token, req.id) - if err != nil { - return nil, err - } - - return viewClientRes{Client: c}, nil - } -} - -func listClientsEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listClientsReq) - if err := req.validate(); err != nil { - return mfclients.ClientsPage{}, errors.Wrap(apiutil.ErrValidation, err) - } - - pm := mfclients.Page{ - Status: req.status, - Offset: req.offset, - Limit: req.limit, - Owner: req.owner, - Name: req.name, - Tag: req.tag, - Permission: req.permission, - Metadata: req.metadata, - } - page, err := svc.ListClients(ctx, req.token, pm) - if err != nil { - return mfclients.ClientsPage{}, err - } - - res := clientsPageRes{ - pageRes: pageRes{ - Total: page.Total, - Offset: page.Offset, - Limit: page.Limit, - }, - Clients: []viewClientRes{}, - } - for _, c := range page.Clients { - res.Clients = append(res.Clients, viewClientRes{Client: c}) - } - - return res, nil - } -} - -func listMembersEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersReq) - if err := req.validate(); err != nil { - return memberPageRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - page, err := svc.ListClientsByGroup(ctx, req.token, req.groupID, req.Page) - if err != nil { - return memberPageRes{}, err - } - - return buildMembersResponse(page), nil - } -} - -func updateClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - cli := mfclients.Client{ - ID: req.id, - Name: req.Name, - Metadata: req.Metadata, - } - client, err := svc.UpdateClient(ctx, req.token, cli) - if err != nil { - return nil, err - } - - return updateClientRes{Client: client}, nil - } -} - -func updateClientTagsEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientTagsReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - cli := mfclients.Client{ - ID: req.id, - Tags: req.Tags, - } - client, err := svc.UpdateClientTags(ctx, req.token, cli) - if err != nil { - return nil, err - } - - return updateClientRes{Client: client}, nil - } -} - -func updateClientSecretEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientCredentialsReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - client, err := svc.UpdateClientSecret(ctx, req.token, req.id, req.Secret) - if err != nil { - return nil, err - } - - return updateClientRes{Client: client}, nil - } -} - -func updateClientOwnerEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientOwnerReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - cli := mfclients.Client{ - ID: req.id, - Owner: req.Owner, - } - client, err := svc.UpdateClientOwner(ctx, req.token, cli) - if err != nil { - return nil, err - } - - return updateClientRes{Client: client}, nil - } -} - -func enableClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeClientStatusReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - client, err := svc.EnableClient(ctx, req.token, req.id) - if err != nil { - return nil, err - } - - return deleteClientRes{Client: client}, nil - } -} - -func disableClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeClientStatusReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - client, err := svc.DisableClient(ctx, req.token, req.id) - if err != nil { - return nil, err - } - - return deleteClientRes{Client: client}, nil - } -} - -func buildMembersResponse(cp mfclients.MembersPage) memberPageRes { - res := memberPageRes{ - pageRes: pageRes{ - Total: cp.Total, - Offset: cp.Offset, - Limit: cp.Limit, - }, - Members: []viewMembersRes{}, - } - for _, c := range cp.Members { - res.Members = append(res.Members, viewMembersRes{Client: c}) - } - - 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) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - if err := svc.Assign(ctx, req.token, req.groupID, req.Relation, "users", req.UserIDs...); err != nil { - return nil, err - } - - return assignUsersRes{}, nil - } -} - -func unassignUsersEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(unassignUsersRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - if err := svc.Unassign(ctx, req.token, req.groupID, req.Relation, "users", req.UserIDs...); err != nil { - return nil, err - } - - return unassignUsersRes{}, nil - } -} - -func assignUserGroupsEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(assignUserGroupsRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - if err := svc.Assign(ctx, req.token, req.groupID, "parent_group", "groups", req.UserGroupIDs...); err != nil { - return nil, err - } - - return assignUserGroupsRes{}, nil - } -} - -func unassignUserGroupsEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(unassignUserGroupsRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - if err := svc.Unassign(ctx, req.token, req.groupID, "parent_group", "groups", req.UserGroupIDs...); err != nil { - return nil, err - } - - return unassignUserGroupsRes{}, nil - } -} - -func connectChannelThingEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(connectChannelThingRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - if err := svc.Assign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { - return nil, err - } - - return connectChannelThingRes{}, nil - } -} - -func disconnectChannelThingEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(disconnectChannelThingRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - if err := svc.Unassign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { - return nil, err - } - - return disconnectChannelThingRes{}, nil - } -} - -func connectEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(connectChannelThingRequest) - if err := req.validate(); err != nil { - return connectChannelThingRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - if err := svc.Assign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { - return connectChannelThingRes{}, err - } - - return connectChannelThingRes{}, nil - } -} - -func disconnectEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(disconnectChannelThingRequest) - if err := req.validate(); err != nil { - return disconnectChannelThingRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - if err := svc.Unassign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { - return disconnectChannelThingRes{}, err - } - - return disconnectChannelThingRes{}, nil - } -} - -func thingShareEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(thingShareRequest) - if err := req.validate(); err != nil { - return thingShareRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - if err := svc.Share(ctx, req.token, req.thingID, req.Relation, req.UserIDs...); err != nil { - return thingShareRes{}, err - } - - return thingShareRes{}, nil - } -} - -func thingUnshareEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(thingUnshareRequest) - if err := req.validate(); err != nil { - return thingUnshareRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - if err := svc.Unshare(ctx, req.token, req.thingID, req.Relation, req.UserIDs...); err != nil { - return thingShareRes{}, err - } - - return thingUnshareRes{}, nil - } -} diff --git a/things/api/policies.go b/things/api/policies.go new file mode 100644 index 0000000000..c82e70f9d1 --- /dev/null +++ b/things/api/policies.go @@ -0,0 +1,110 @@ +package api + +import ( + "context" + "encoding/json" + "net/http" + "strings" + + "github.com/go-chi/chi/v5" + "github.com/go-kit/kit/endpoint" + kithttp "github.com/go-kit/kit/transport/http" + "github.com/mainflux/mainflux/internal/api" + "github.com/mainflux/mainflux/internal/apiutil" + mflog "github.com/mainflux/mainflux/logger" + "github.com/mainflux/mainflux/pkg/errors" + "github.com/mainflux/mainflux/things" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" +) + +func policiesHandler(svc things.Service, r *chi.Mux, logger mflog.Logger) http.Handler { + opts := []kithttp.ServerOption{ + kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), + } + + r.Post("/connect", otelhttp.NewHandler(kithttp.NewServer( + connectEndpoint(svc), + decodeConnReq, + api.EncodeResponse, + opts..., + ), "create_thing").ServeHTTP) + + r.Post("/disconnect", otelhttp.NewHandler(kithttp.NewServer( + disconnectEndpoint(svc), + decodeConnReq, + api.EncodeResponse, + opts..., + ), "create_things").ServeHTTP) + + return r +} + +func connectEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(connReq) + if err := req.validate(); err != nil { + return connnRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + if err := svc.Connect(ctx, req.token, req.ThingID, req.ChannelID, req.Permission); err != nil { + return connnRes{}, err + } + + return connnRes{}, nil + } +} + +func disconnectEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(connReq) + if err := req.validate(); err != nil { + return connnRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + if err := svc.Disconnect(ctx, req.token, req.ThingID, req.ChannelID, req.Permission); err != nil { + return connnRes{}, err + } + + return connnRes{}, nil + } +} + +func decodeConnReq(_ 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 := connReq{ + token: apiutil.ExtractBearerToken(r), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + return req, nil +} + +type connReq struct { + token string + ThingID string `json:"thing_id,omitempty"` + ChannelID string `json:"channel_id,omitempty"` + Permission string `json:"permission,omitempty"` +} + +func (req *connReq) validate() error { + if req.ThingID == "" || req.ChannelID == "" { + return errors.ErrCreateEntity + } + return nil +} + +type connnRes struct{} + +func (res *connnRes) Code() int { + return http.StatusOK +} + +func (res *connnRes) Headers() map[string]string { + return map[string]string{} +} + +func (res *connnRes) Empty() bool { + return false +} diff --git a/things/api/requests.go b/things/api/requests.go new file mode 100644 index 0000000000..832e8984f1 --- /dev/null +++ b/things/api/requests.go @@ -0,0 +1,275 @@ +// Copyright (c) Mainflux +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "github.com/mainflux/mainflux/internal/api" + "github.com/mainflux/mainflux/internal/apiutil" + mfclients "github.com/mainflux/mainflux/pkg/clients" + "golang.org/x/exp/slices" +) + +type createClientReq struct { + client mfclients.Client + token string +} + +func (req createClientReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if len(req.client.Name) > api.MaxNameSize { + return apiutil.ErrNameSize + } + // Do the validation only if request contains ID + if req.client.ID != "" { + return api.ValidateUUID(req.client.ID) + } + + return nil +} + +type createClientsReq struct { + token string + Clients []mfclients.Client +} + +func (req createClientsReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + + if len(req.Clients) == 0 { + return apiutil.ErrEmptyList + } + + for _, client := range req.Clients { + if client.ID != "" { + if err := api.ValidateUUID(client.ID); err != nil { + return err + } + } + if len(client.Name) > api.MaxNameSize { + return apiutil.ErrNameSize + } + } + + return nil +} + +type viewClientReq struct { + token string + id string +} + +func (req viewClientReq) validate() error { + return nil +} + +type listClientsReq struct { + token string + status mfclients.Status + offset uint64 + limit uint64 + name string + tag string + owner string + permission string + visibility string + metadata mfclients.Metadata +} + +func (req listClientsReq) validate() error { + if req.limit > api.MaxLimitSize || req.limit < 1 { + return apiutil.ErrLimitSize + } + if req.visibility != "" && + req.visibility != api.AllVisibility && + req.visibility != api.MyVisibility && + req.visibility != api.SharedVisibility { + return apiutil.ErrInvalidVisibilityType + } + if req.limit > api.MaxLimitSize || req.limit < 1 { + return apiutil.ErrLimitSize + } + + if len(req.name) > api.MaxNameSize { + return apiutil.ErrNameSize + } + + return nil +} + +type listMembersReq struct { + mfclients.Page + token string + groupID string +} + +func (req listMembersReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + + if req.groupID == "" { + return apiutil.ErrMissingID + } + + return nil +} + +type updateClientReq struct { + token string + id string + Name string `json:"name,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +func (req updateClientReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + + if req.id == "" { + return apiutil.ErrMissingID + } + if len(req.Name) > api.MaxNameSize { + return apiutil.ErrNameSize + } + return nil +} + +type updateClientTagsReq struct { + id string + token string + Tags []string `json:"tags,omitempty"` +} + +func (req updateClientTagsReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + + if req.id == "" { + return apiutil.ErrMissingID + } + return nil +} + +type updateClientOwnerReq struct { + id string + token string + Owner string `json:"owner,omitempty"` +} + +func (req updateClientOwnerReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.id == "" { + return apiutil.ErrMissingID + } + if req.Owner == "" { + return apiutil.ErrMissingOwner + } + return nil +} + +type updateClientCredentialsReq struct { + token string + id string + Secret string `json:"secret,omitempty"` +} + +func (req updateClientCredentialsReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.id == "" { + return apiutil.ErrMissingID + } + + if req.Secret == "" { + return apiutil.ErrBearerKey + } + + return nil +} + +type changeClientStatusReq struct { + token string + id string +} + +func (req changeClientStatusReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + return nil +} + +type assignUsersGroupsRequest struct { + token string + groupID string + Relation string `json:"relation,omitempty"` + MemberKind string `json:"member_kind,omitempty"` + Members []string `json:"members"` +} + +func (req assignUsersGroupsRequest) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + + if req.MemberKind == "" { + return apiutil.ErrMissingMemberKind + } + + if !slices.Contains([]string{"users", "groups"}, req.MemberKind) { + return apiutil.ErrInvalidMemberKind + } + + if req.groupID == "" { + return apiutil.ErrMissingID + } + + if len(req.Members) == 0 { + return apiutil.ErrEmptyList + } + + return nil +} + +type unassignUsersGroupsRequest struct { + token string + groupID string + Relation string `json:"relation,omitempty"` + MemberKind string `json:"member_kind,omitempty"` + Members []string `json:"members"` +} + +func (req unassignUsersGroupsRequest) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + + if req.MemberKind == "" { + return apiutil.ErrMissingMemberKind + } + + if !slices.Contains([]string{"users", "groups"}, req.MemberKind) { + return apiutil.ErrInvalidMemberKind + } + + if req.groupID == "" { + return apiutil.ErrMissingID + } + + if len(req.Members) == 0 { + return apiutil.ErrEmptyList + } + + return nil +} diff --git a/things/api/responses.go b/things/api/responses.go new file mode 100644 index 0000000000..beb7559192 --- /dev/null +++ b/things/api/responses.go @@ -0,0 +1,184 @@ +// Copyright (c) Mainflux +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "fmt" + "net/http" + + "github.com/mainflux/mainflux" + mfclients "github.com/mainflux/mainflux/pkg/clients" +) + +var ( + _ mainflux.Response = (*viewClientRes)(nil) + _ mainflux.Response = (*createClientRes)(nil) + _ mainflux.Response = (*deleteClientRes)(nil) + _ mainflux.Response = (*clientsPageRes)(nil) + _ mainflux.Response = (*viewMembersRes)(nil) + _ mainflux.Response = (*memberPageRes)(nil) + _ mainflux.Response = (*assignUsersGroupsRes)(nil) + _ mainflux.Response = (*unassignUsersGroupsRes)(nil) +) + +type pageRes struct { + Limit uint64 `json:"limit,omitempty"` + Offset uint64 `json:"offset,omitempty"` + Total uint64 `json:"total,omitempty"` +} + +type createClientRes struct { + mfclients.Client + created bool +} + +func (res createClientRes) Code() int { + if res.created { + return http.StatusCreated + } + + return http.StatusOK +} + +func (res createClientRes) Headers() map[string]string { + if res.created { + return map[string]string{ + "Location": fmt.Sprintf("/things/%s", res.ID), + } + } + + return map[string]string{} +} + +func (res createClientRes) Empty() bool { + return false +} + +type updateClientRes struct { + mfclients.Client +} + +func (res updateClientRes) Code() int { + return http.StatusOK +} + +func (res updateClientRes) Headers() map[string]string { + return map[string]string{} +} + +func (res updateClientRes) Empty() bool { + return false +} + +type viewClientRes struct { + mfclients.Client +} + +func (res viewClientRes) Code() int { + return http.StatusOK +} + +func (res viewClientRes) Headers() map[string]string { + return map[string]string{} +} + +func (res viewClientRes) Empty() bool { + return false +} + +type clientsPageRes struct { + pageRes + Clients []viewClientRes `json:"things"` +} + +func (res clientsPageRes) Code() int { + return http.StatusOK +} + +func (res clientsPageRes) Headers() map[string]string { + return map[string]string{} +} + +func (res clientsPageRes) Empty() bool { + return false +} + +type viewMembersRes struct { + mfclients.Client +} + +func (res viewMembersRes) Code() int { + return http.StatusOK +} + +func (res viewMembersRes) Headers() map[string]string { + return map[string]string{} +} + +func (res viewMembersRes) Empty() bool { + return false +} + +type memberPageRes struct { + pageRes + Members []viewMembersRes `json:"things"` +} + +func (res memberPageRes) Code() int { + return http.StatusOK +} + +func (res memberPageRes) Headers() map[string]string { + return map[string]string{} +} + +func (res memberPageRes) Empty() bool { + return false +} + +type deleteClientRes struct { + mfclients.Client +} + +func (res deleteClientRes) Code() int { + return http.StatusOK +} + +func (res deleteClientRes) Headers() map[string]string { + return map[string]string{} +} + +func (res deleteClientRes) Empty() bool { + return false +} + +type assignUsersGroupsRes struct { +} + +func (res assignUsersGroupsRes) Code() int { + return http.StatusOK +} + +func (res assignUsersGroupsRes) Headers() map[string]string { + return map[string]string{} +} + +func (res assignUsersGroupsRes) Empty() bool { + return true +} + +type unassignUsersGroupsRes struct { +} + +func (res unassignUsersGroupsRes) Code() int { + return http.StatusNoContent +} + +func (res unassignUsersGroupsRes) Headers() map[string]string { + return map[string]string{} +} + +func (res unassignUsersGroupsRes) Empty() bool { + return true +} From 0c7f79888c85df56c0c91c83cca0b574821d4f4f Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Mon, 9 Oct 2023 18:15:06 +0530 Subject: [PATCH 02/17] Add Connect Disconnect endpoints (#23) * fix bugs Signed-off-by: Arvindh * fix bugs Signed-off-by: Arvindh * fix list of things in a channel and Add connect disconnect endpoint Signed-off-by: Arvindh * fix list of things in a channel and Add connect disconnect endpoint Signed-off-by: Arvindh --------- Signed-off-by: Arvindh Signed-off-by: dusanb94 --- things/api/channels.go | 75 ++++++++ things/api/endpoints.go | 320 +++++++++++++++++++++++++++++++++++ things/api/http/transport.go | 26 --- things/api/logging.go | 22 --- things/api/policies.go | 110 ------------ things/api/requests.go | 29 ++++ things/api/responses.go | 32 ++++ things/service.go | 1 + 8 files changed, 457 insertions(+), 158 deletions(-) create mode 100644 things/api/endpoints.go delete mode 100644 things/api/policies.go diff --git a/things/api/channels.go b/things/api/channels.go index a013d04e7c..0dc36f0c9d 100644 --- a/things/api/channels.go +++ b/things/api/channels.go @@ -7,6 +7,7 @@ import ( "context" "encoding/json" "net/http" + "strings" "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" @@ -87,6 +88,20 @@ func groupsHandler(svc groups.Service, tscv things.Service, r *chi.Mux, logger l api.EncodeResponse, opts..., ), "unassign_members").ServeHTTP) + + r.Post("/{groupID}/things/{thingID}", otelhttp.NewHandler(kithttp.NewServer( + connectChannelThingEndpoint(svc), + decodeConnectChannelThingRequest, + api.EncodeResponse, + opts..., + ), "connect_channel_thing").ServeHTTP) + + r.Delete("/{groupID}/things/{thingID}", otelhttp.NewHandler(kithttp.NewServer( + disconnectChannelThingEndpoint(svc), + decodeDisconnectChannelThingRequest, + api.EncodeResponse, + opts..., + ), "disconnect_channel_thing").ServeHTTP) }) r.Get("/things/{memberID}/channels", otelhttp.NewHandler(kithttp.NewServer( @@ -96,6 +111,20 @@ func groupsHandler(svc groups.Service, tscv things.Service, r *chi.Mux, logger l opts..., ), "list_channel_by_things").ServeHTTP) + r.Post("/connect", otelhttp.NewHandler(kithttp.NewServer( + connectEndpoint(svc), + decodeConnectRequest, + api.EncodeResponse, + opts..., + ), "connect").ServeHTTP) + + r.Post("/disconnect", otelhttp.NewHandler(kithttp.NewServer( + disconnectEndpoint(svc), + decodeDisconnectRequest, + api.EncodeResponse, + opts..., + ), "disconnect").ServeHTTP) + return r } @@ -120,3 +149,49 @@ func decodeUnassignUsersGroupsRequest(_ context.Context, r *http.Request) (inter } return req, nil } + +func decodeConnectChannelThingRequest(_ context.Context, r *http.Request) (interface{}, error) { + req := connectChannelThingRequest{ + token: apiutil.ExtractBearerToken(r), + ThingID: chi.URLParam(r, "thingID"), + ChannelID: chi.URLParam(r, "groupID"), + } + return req, nil +} + +func decodeDisconnectChannelThingRequest(_ context.Context, r *http.Request) (interface{}, error) { + req := disconnectChannelThingRequest{ + token: apiutil.ExtractBearerToken(r), + ThingID: chi.URLParam(r, "thingID"), + ChannelID: chi.URLParam(r, "groupID"), + } + return req, nil +} + +func decodeConnectRequest(_ 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 := connectChannelThingRequest{ + token: apiutil.ExtractBearerToken(r), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + return req, nil +} + +func decodeDisconnectRequest(_ 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 := disconnectChannelThingRequest{ + token: apiutil.ExtractBearerToken(r), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + return req, nil +} diff --git a/things/api/endpoints.go b/things/api/endpoints.go new file mode 100644 index 0000000000..45ca247acc --- /dev/null +++ b/things/api/endpoints.go @@ -0,0 +1,320 @@ +// Copyright (c) Mainflux +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "context" + + "github.com/go-kit/kit/endpoint" + "github.com/mainflux/mainflux/internal/apiutil" + mfclients "github.com/mainflux/mainflux/pkg/clients" + "github.com/mainflux/mainflux/pkg/errors" + "github.com/mainflux/mainflux/pkg/groups" + "github.com/mainflux/mainflux/things" +) + +func createClientEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(createClientReq) + if err := req.validate(); err != nil { + return createClientRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + client, err := svc.CreateThings(ctx, req.token, req.client) + if err != nil { + return createClientRes{}, err + } + ucr := createClientRes{ + Client: client[0], + created: true, + } + + return ucr, nil + } +} + +func createClientsEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(createClientsReq) + if err := req.validate(); err != nil { + return clientsPageRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + page, err := svc.CreateThings(ctx, req.token, req.Clients...) + if err != nil { + return clientsPageRes{}, err + } + res := clientsPageRes{ + pageRes: pageRes{ + Total: uint64(len(page)), + }, + Clients: []viewClientRes{}, + } + for _, c := range page { + res.Clients = append(res.Clients, viewClientRes{Client: c}) + } + return res, nil + } +} + +func viewClientEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(viewClientReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + c, err := svc.ViewClient(ctx, req.token, req.id) + if err != nil { + return nil, err + } + return viewClientRes{Client: c}, nil + } +} + +func listClientsEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listClientsReq) + if err := req.validate(); err != nil { + return mfclients.ClientsPage{}, errors.Wrap(apiutil.ErrValidation, err) + } + + pm := mfclients.Page{ + Status: req.status, + Offset: req.offset, + Limit: req.limit, + Owner: req.owner, + Name: req.name, + Tag: req.tag, + Permission: req.permission, + Metadata: req.metadata, + } + page, err := svc.ListClients(ctx, req.token, pm) + if err != nil { + return mfclients.ClientsPage{}, err + } + + res := clientsPageRes{ + pageRes: pageRes{ + Total: page.Total, + Offset: page.Offset, + Limit: page.Limit, + }, + Clients: []viewClientRes{}, + } + for _, c := range page.Clients { + res.Clients = append(res.Clients, viewClientRes{Client: c}) + } + + return res, nil + } +} + +func listMembersEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listMembersReq) + if err := req.validate(); err != nil { + return memberPageRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + page, err := svc.ListClientsByGroup(ctx, req.token, req.groupID, req.Page) + if err != nil { + return memberPageRes{}, err + } + return buildMembersResponse(page), nil + } +} + +func updateClientEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(updateClientReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + cli := mfclients.Client{ + ID: req.id, + Name: req.Name, + Metadata: req.Metadata, + } + client, err := svc.UpdateClient(ctx, req.token, cli) + if err != nil { + return nil, err + } + return updateClientRes{Client: client}, nil + } +} + +func updateClientTagsEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(updateClientTagsReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + cli := mfclients.Client{ + ID: req.id, + Tags: req.Tags, + } + client, err := svc.UpdateClientTags(ctx, req.token, cli) + if err != nil { + return nil, err + } + return updateClientRes{Client: client}, nil + } +} + +func updateClientSecretEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(updateClientCredentialsReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + client, err := svc.UpdateClientSecret(ctx, req.token, req.id, req.Secret) + if err != nil { + return nil, err + } + return updateClientRes{Client: client}, nil + } +} + +func updateClientOwnerEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(updateClientOwnerReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + cli := mfclients.Client{ + ID: req.id, + Owner: req.Owner, + } + + client, err := svc.UpdateClientOwner(ctx, req.token, cli) + if err != nil { + return nil, err + } + return updateClientRes{Client: client}, nil + } +} + +func enableClientEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(changeClientStatusReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + client, err := svc.EnableClient(ctx, req.token, req.id) + if err != nil { + return nil, err + } + return deleteClientRes{Client: client}, nil + } +} + +func disableClientEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(changeClientStatusReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + client, err := svc.DisableClient(ctx, req.token, req.id) + if err != nil { + return nil, err + } + return deleteClientRes{Client: client}, nil + } +} + +func buildMembersResponse(cp mfclients.MembersPage) memberPageRes { + res := memberPageRes{ + pageRes: pageRes{ + Total: cp.Total, + Offset: cp.Offset, + Limit: cp.Limit, + }, + Members: []viewMembersRes{}, + } + for _, c := range cp.Members { + res.Members = append(res.Members, viewMembersRes{Client: c}) + } + 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 connectChannelThingEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(connectChannelThingRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + if err := svc.Assign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { + return nil, err + } + return connectChannelThingRes{}, nil + } +} + +func disconnectChannelThingEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(disconnectChannelThingRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + if err := svc.Unassign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { + return nil, err + } + return disconnectChannelThingRes{}, nil + } +} + +func connectEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(connectChannelThingRequest) + if err := req.validate(); err != nil { + return connectChannelThingRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + if err := svc.Assign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { + return connectChannelThingRes{}, err + } + + return connectChannelThingRes{}, nil + } +} + +func disconnectEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(disconnectChannelThingRequest) + if err := req.validate(); err != nil { + return disconnectChannelThingRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + if err := svc.Unassign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { + return disconnectChannelThingRes{}, err + } + + return disconnectChannelThingRes{}, nil + } +} diff --git a/things/api/http/transport.go b/things/api/http/transport.go index 25507eac68..e69de29bb2 100644 --- a/things/api/http/transport.go +++ b/things/api/http/transport.go @@ -1,26 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package http - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - "github.com/mainflux/mainflux" - mflog "github.com/mainflux/mainflux/logger" - "github.com/mainflux/mainflux/pkg/groups" - "github.com/mainflux/mainflux/things" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -// MakeHandler returns a HTTP handler for Things and Groups API endpoints. -func MakeHandler(tsvc things.Service, grps groups.Service, mux *chi.Mux, logger mflog.Logger, instanceID string) http.Handler { - clientsHandler(tsvc, mux, logger) - groupsHandler(grps, mux, logger) - - mux.Get("/health", mainflux.Health("things", instanceID)) - mux.Handle("/metrics", promhttp.Handler()) - - return mux -} diff --git a/things/api/logging.go b/things/api/logging.go index b1bec13bc3..5ce2dde172 100644 --- a/things/api/logging.go +++ b/things/api/logging.go @@ -157,29 +157,7 @@ func (lm *loggingMiddleware) Identify(ctx context.Context, key string) (id strin return lm.svc.Identify(ctx, key) } -func (lm *loggingMiddleware) Authorize(ctx context.Context, req *mainflux.AuthorizeReq) (id string, err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method authorize for thing key %s and channnel %s took %s to complete", req.Subject, req.Object, 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.Authorize(ctx, req) -} -func (lm *loggingMiddleware) Share(ctx context.Context, token, id string, relation string, userids ...string) (err error) { - defer func(begin time.Time) { - message := fmt.Sprintf("Method share for thing id %s with relation %s for users %v took %s to complete", id, relation, userids, 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.Share(ctx, token, id, relation, userids...) -} func (lm *loggingMiddleware) Unshare(ctx context.Context, token, id string, relation string, userids ...string) (err error) { defer func(begin time.Time) { diff --git a/things/api/policies.go b/things/api/policies.go deleted file mode 100644 index c82e70f9d1..0000000000 --- a/things/api/policies.go +++ /dev/null @@ -1,110 +0,0 @@ -package api - -import ( - "context" - "encoding/json" - "net/http" - "strings" - - "github.com/go-chi/chi/v5" - "github.com/go-kit/kit/endpoint" - kithttp "github.com/go-kit/kit/transport/http" - "github.com/mainflux/mainflux/internal/api" - "github.com/mainflux/mainflux/internal/apiutil" - mflog "github.com/mainflux/mainflux/logger" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/mainflux/mainflux/things" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -func policiesHandler(svc things.Service, r *chi.Mux, logger mflog.Logger) http.Handler { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - - r.Post("/connect", otelhttp.NewHandler(kithttp.NewServer( - connectEndpoint(svc), - decodeConnReq, - api.EncodeResponse, - opts..., - ), "create_thing").ServeHTTP) - - r.Post("/disconnect", otelhttp.NewHandler(kithttp.NewServer( - disconnectEndpoint(svc), - decodeConnReq, - api.EncodeResponse, - opts..., - ), "create_things").ServeHTTP) - - return r -} - -func connectEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(connReq) - if err := req.validate(); err != nil { - return connnRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - if err := svc.Connect(ctx, req.token, req.ThingID, req.ChannelID, req.Permission); err != nil { - return connnRes{}, err - } - - return connnRes{}, nil - } -} - -func disconnectEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(connReq) - if err := req.validate(); err != nil { - return connnRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - if err := svc.Disconnect(ctx, req.token, req.ThingID, req.ChannelID, req.Permission); err != nil { - return connnRes{}, err - } - - return connnRes{}, nil - } -} - -func decodeConnReq(_ 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 := connReq{ - token: apiutil.ExtractBearerToken(r), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - return req, nil -} - -type connReq struct { - token string - ThingID string `json:"thing_id,omitempty"` - ChannelID string `json:"channel_id,omitempty"` - Permission string `json:"permission,omitempty"` -} - -func (req *connReq) validate() error { - if req.ThingID == "" || req.ChannelID == "" { - return errors.ErrCreateEntity - } - return nil -} - -type connnRes struct{} - -func (res *connnRes) Code() int { - return http.StatusOK -} - -func (res *connnRes) Headers() map[string]string { - return map[string]string{} -} - -func (res *connnRes) Empty() bool { - return false -} diff --git a/things/api/requests.go b/things/api/requests.go index 832e8984f1..6c4021ff71 100644 --- a/things/api/requests.go +++ b/things/api/requests.go @@ -7,6 +7,7 @@ import ( "github.com/mainflux/mainflux/internal/api" "github.com/mainflux/mainflux/internal/apiutil" mfclients "github.com/mainflux/mainflux/pkg/clients" + "github.com/mainflux/mainflux/pkg/errors" "golang.org/x/exp/slices" ) @@ -273,3 +274,31 @@ func (req unassignUsersGroupsRequest) validate() error { return nil } + +type connectChannelThingRequest struct { + token string + ThingID string `json:"thing_id,omitempty"` + ChannelID string `json:"channel_id,omitempty"` + Permission string `json:"permission,omitempty"` +} + +func (req *connectChannelThingRequest) validate() error { + if req.ThingID == "" || req.ChannelID == "" { + return errors.ErrCreateEntity + } + return nil +} + +type disconnectChannelThingRequest struct { + token string + ThingID string `json:"thing_id,omitempty"` + ChannelID string `json:"channel_id,omitempty"` + Permission string `json:"permission,omitempty"` +} + +func (req *disconnectChannelThingRequest) validate() error { + if req.ThingID == "" || req.ChannelID == "" { + return errors.ErrCreateEntity + } + return nil +} diff --git a/things/api/responses.go b/things/api/responses.go index beb7559192..1d54adaed9 100644 --- a/things/api/responses.go +++ b/things/api/responses.go @@ -20,6 +20,8 @@ var ( _ mainflux.Response = (*memberPageRes)(nil) _ mainflux.Response = (*assignUsersGroupsRes)(nil) _ mainflux.Response = (*unassignUsersGroupsRes)(nil) + _ mainflux.Response = (*connectChannelThingRes)(nil) + _ mainflux.Response = (*disconnectChannelThingRes)(nil) ) type pageRes struct { @@ -182,3 +184,33 @@ func (res unassignUsersGroupsRes) Headers() map[string]string { func (res unassignUsersGroupsRes) Empty() bool { return true } + +type connectChannelThingRes struct { +} + +func (res connectChannelThingRes) Code() int { + return http.StatusOK +} + +func (res connectChannelThingRes) Headers() map[string]string { + return map[string]string{} +} + +func (res connectChannelThingRes) Empty() bool { + return true +} + +type disconnectChannelThingRes struct { +} + +func (res disconnectChannelThingRes) Code() int { + return http.StatusNoContent +} + +func (res disconnectChannelThingRes) Headers() map[string]string { + return map[string]string{} +} + +func (res disconnectChannelThingRes) Empty() bool { + return true +} diff --git a/things/service.go b/things/service.go index efe6a00711..96cae5c6a3 100644 --- a/things/service.go +++ b/things/service.go @@ -336,6 +336,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 From afa1d974b89730a24f90e6447204c875e5ef24af Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Tue, 10 Oct 2023 18:10:19 +0530 Subject: [PATCH 03/17] Add: Things share with users (#25) * fix list of things in a channel and Add connect disconnect endpoint Signed-off-by: Arvindh * add: things share with other users Signed-off-by: Arvindh --------- Signed-off-by: Arvindh Signed-off-by: dusanb94 --- things/api/endpoints.go | 26 ++++++++++++++++++++++++++ things/api/logging.go | 22 ++++++++++++++++++++++ things/api/requests.go | 34 ++++++++++++++++++++++++++++++++++ things/api/responses.go | 28 ++++++++++++++++++++++++++++ things/events/streams.go | 8 ++++++++ things/service.go | 6 ++++-- things/tracing/tracing.go | 2 -- 7 files changed, 122 insertions(+), 4 deletions(-) diff --git a/things/api/endpoints.go b/things/api/endpoints.go index 45ca247acc..ae80b1e215 100644 --- a/things/api/endpoints.go +++ b/things/api/endpoints.go @@ -318,3 +318,29 @@ func disconnectEndpoint(svc groups.Service) endpoint.Endpoint { return disconnectChannelThingRes{}, nil } } + +func thingShareEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(thingShareRequest) + if err := req.validate(); err != nil { + return thingShareRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + if err := svc.Share(ctx, req.token, req.thingID, req.Relation, req.UserIDs...); err != nil { + return thingShareRes{}, err + } + return thingShareRes{}, nil + } +} + +func thingUnshareEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(thingUnshareRequest) + if err := req.validate(); err != nil { + return thingUnshareRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + if err := svc.Unshare(ctx, req.token, req.thingID, req.Relation, req.UserIDs...); err != nil { + return thingShareRes{}, err + } + return thingUnshareRes{}, nil + } +} diff --git a/things/api/logging.go b/things/api/logging.go index 5ce2dde172..09dae54de8 100644 --- a/things/api/logging.go +++ b/things/api/logging.go @@ -157,7 +157,29 @@ func (lm *loggingMiddleware) Identify(ctx context.Context, key string) (id strin return lm.svc.Identify(ctx, key) } +func (lm *loggingMiddleware) Authorize(ctx context.Context, req *mainflux.AuthorizeReq) (id string, err error) { + defer func(begin time.Time) { + message := fmt.Sprintf("Method unshare for thing id %s with relation %s for users %v took %s to complete", id, relation, userids, 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.Unshare(ctx, token, id, relation, userids...) +} +func (lm *loggingMiddleware) Share(ctx context.Context, token, id string, relation string, userids ...string) (err error) { + defer func(begin time.Time) { + message := fmt.Sprintf("Method share for thing id %s with relation %s for users %v took %s to complete", id, relation, userids, 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.Share(ctx, token, id, relation, userids...) +} func (lm *loggingMiddleware) Unshare(ctx context.Context, token, id string, relation string, userids ...string) (err error) { defer func(begin time.Time) { diff --git a/things/api/requests.go b/things/api/requests.go index 6c4021ff71..9666794485 100644 --- a/things/api/requests.go +++ b/things/api/requests.go @@ -302,3 +302,37 @@ func (req *disconnectChannelThingRequest) validate() error { } return nil } + +type thingShareRequest struct { + token string + thingID string + Relation string `json:"relation,omitempty"` + UserIDs []string `json:"user_ids,omitempty"` +} + +func (req *thingShareRequest) validate() error { + if req.thingID == "" { + return errors.ErrMalformedEntity + } + if req.Relation == "" || len(req.UserIDs) <= 0 { + return errors.ErrCreateEntity + } + return nil +} + +type thingUnshareRequest struct { + token string + thingID string + Relation string `json:"relation,omitempty"` + UserIDs []string `json:"user_ids,omitempty"` +} + +func (req *thingUnshareRequest) validate() error { + if req.thingID == "" { + return errors.ErrMalformedEntity + } + if req.Relation == "" || len(req.UserIDs) <= 0 { + return errors.ErrCreateEntity + } + return nil +} diff --git a/things/api/responses.go b/things/api/responses.go index 1d54adaed9..a8f6e54139 100644 --- a/things/api/responses.go +++ b/things/api/responses.go @@ -214,3 +214,31 @@ func (res disconnectChannelThingRes) Headers() map[string]string { func (res disconnectChannelThingRes) Empty() bool { return true } + +type thingShareRes struct{} + +func (res thingShareRes) Code() int { + return http.StatusOK +} + +func (res thingShareRes) Headers() map[string]string { + return map[string]string{} +} + +func (res thingShareRes) Empty() bool { + return true +} + +type thingUnshareRes struct{} + +func (res thingUnshareRes) Code() int { + return http.StatusNoContent +} + +func (res thingUnshareRes) Headers() map[string]string { + return map[string]string{} +} + +func (res thingUnshareRes) Empty() bool { + return true +} diff --git a/things/events/streams.go b/things/events/streams.go index 7c62612d0c..5521a6e589 100644 --- a/things/events/streams.go +++ b/things/events/streams.go @@ -243,3 +243,11 @@ func (es *eventStore) Unshare(ctx context.Context, token, id string, relation st return es.Publish(ctx, event) } + +func (es *eventStore) Share(ctx context.Context, token, id string, relation string, userids ...string) error { + return es.svc.Share(ctx, token, id, relation, userids...) +} + +func (es *eventStore) Unshare(ctx context.Context, token, id string, relation string, userids ...string) error { + return es.svc.Unshare(ctx, token, id, relation, userids...) +} diff --git a/things/service.go b/things/service.go index 96cae5c6a3..6f3069fece 100644 --- a/things/service.go +++ b/things/service.go @@ -258,6 +258,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 +266,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 +275,6 @@ func (svc service) Share(ctx context.Context, token, id, relation string, userid return errors.ErrAuthorization } } - return nil } @@ -284,6 +285,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 +293,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 +302,6 @@ func (svc service) Unshare(ctx context.Context, token, id, relation string, user return errors.ErrAuthorization } } - return nil } diff --git a/things/tracing/tracing.go b/things/tracing/tracing.go index 096c834370..b14bc2d363 100644 --- a/things/tracing/tracing.go +++ b/things/tracing/tracing.go @@ -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...) } From ed513c9bdb687418478a05def401e178c9f6ac19 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:10:55 +0530 Subject: [PATCH 04/17] Add: Listing of things, channels, groups, users (#26) * add: listing of channels, users, groups, things Signed-off-by: Arvindh * add: listing of channels, users, groups, things Signed-off-by: Arvindh * add: listing of channels, users, groups, things Signed-off-by: Arvindh * add: listing of channels, users, groups, things Signed-off-by: Arvindh --------- Signed-off-by: Arvindh Signed-off-by: dusanb94 --- internal/groups/service.go | 81 ++++---- things/api/channels.go | 126 ++++++++++++- things/api/endpoints.go | 51 ++++++ things/api/http/clients.go | 365 ------------------------------------- things/api/requests.go | 97 ++++++++++ things/api/responses.go | 60 ++++++ things/api/transport.go | 26 +++ users/api/clients.go | 4 +- users/api/groups.go | 78 +++++++- 9 files changed, 454 insertions(+), 434 deletions(-) create mode 100644 things/api/transport.go diff --git a/internal/groups/service.go b/internal/groups/service.go index 8b46fb14b6..21883b256a 100644 --- a/internal/groups/service.go +++ b/internal/groups/service.go @@ -148,10 +148,17 @@ func (svc service) ListGroups(ctx context.Context, token string, memberKind, mem if err != nil { return groups.Page{}, err } - ids, err = svc.filterAllowedGroupIDsOfUserID(ctx, userID, gm.Permission, cids.Policies) + allowedIDs, err := svc.listAllGroupsOfUserID(ctx, userID, gm.Permission) if err != nil { return groups.Page{}, err } + for _, cid := range cids.Policies { + for _, id := range allowedIDs { + if id == cid { + ids = append(ids, id) + } + } + } case groupsKind: if _, err := svc.authorizeKind(ctx, userType, usersKind, userID, gm.Permission, groupType, memberID); err != nil { return groups.Page{}, err @@ -166,9 +173,14 @@ func (svc service) ListGroups(ctx context.Context, token string, memberKind, mem if err != nil { return groups.Page{}, err } - ids, err = svc.filterAllowedGroupIDsOfUserID(ctx, userID, gm.Permission, gids.Policies) - if err != nil { - return groups.Page{}, err + + allowedIDs, err := svc.listAllGroupsOfUserID(ctx, userID, gm.Permission) + for _, gid := range gids.Policies { + for _, id := range allowedIDs { + if id == gid { + ids = append(ids, id) + } + } } case channelsKind: if _, err := svc.authorizeKind(ctx, userType, usersKind, userID, viewPermission, groupType, memberID); err != nil { @@ -184,11 +196,19 @@ func (svc service) ListGroups(ctx context.Context, token string, memberKind, mem return groups.Page{}, err } - ids, err = svc.filterAllowedGroupIDsOfUserID(ctx, userID, gm.Permission, gids.Policies) + allowedIDs, err := svc.listAllGroupsOfUserID(ctx, userID, gm.Permission) + for _, gid := range gids.Policies { + for _, id := range allowedIDs { + if id == gid { + ids = append(ids, id) + } + } + } + case usersKind: + allowedIDs, err := svc.listAllGroupsOfUserID(ctx, userID, gm.Permission) if err != nil { return groups.Page{}, err } - case usersKind: if memberID != "" && userID != memberID { if _, err := svc.authorizeKind(ctx, userType, usersKind, userID, ownerRelation, userType, memberID); err != nil { return groups.Page{}, err @@ -199,48 +219,26 @@ func (svc service) ListGroups(ctx context.Context, token string, memberKind, mem Permission: gm.Permission, ObjectType: groupType, }) - if err != nil { - return groups.Page{}, err - } - ids, err = svc.filterAllowedGroupIDsOfUserID(ctx, userID, gm.Permission, gids.Policies) - if err != nil { - return groups.Page{}, err - } - } else { - ids, err = svc.listAllGroupsOfUserID(ctx, userID, gm.Permission) - if err != nil { - return groups.Page{}, err - } - } - case usersKind: - if memberID != "" && userID != memberID { - if _, err := svc.authorizeKind(ctx, userType, usersKind, userID, ownerRelation, userType, memberID); err != nil { - return groups.Page{}, err - } - gids, err := svc.auth.ListAllObjects(ctx, &mainflux.ListObjectsReq{ - SubjectType: userType, - Subject: memberID, - Permission: viewPermission, - ObjectType: groupType, - }) - if err != nil { return groups.Page{}, err } for _, gid := range gids.Policies { - for _, id := range allowedIDs.Policies { + for _, id := range allowedIDs { if id == gid { ids = append(ids, id) } } } } else { - ids = allowedIDs.Policies + ids = allowedIDs } default: return groups.Page{}, fmt.Errorf("invalid member kind") } + if len(ids) <= 0 { + return groups.Page{}, errors.ErrNotFound + } if len(ids) <= 0 { return groups.Page{}, errors.ErrNotFound } @@ -448,23 +446,6 @@ func (svc service) Unassign(ctx context.Context, token, groupID, relation, membe return nil } -func (svc service) filterAllowedGroupIDsOfUserID(ctx context.Context, userID string, permission string, groupIDs []string) ([]string, error) { - var ids []string - allowedIDs, err := svc.listAllGroupsOfUserID(ctx, userID, permission) - if err != nil { - return []string{}, err - } - - for _, gid := range groupIDs { - for _, id := range allowedIDs { - if id == gid { - ids = append(ids, id) - } - } - } - return ids, nil -} - func (svc service) listAllGroupsOfUserID(ctx context.Context, userID string, permission string) ([]string, error) { allowedIDs, err := svc.auth.ListAllObjects(ctx, &mainflux.ListObjectsReq{ SubjectType: userType, diff --git a/things/api/channels.go b/things/api/channels.go index 0dc36f0c9d..b4b4d40388 100644 --- a/things/api/channels.go +++ b/things/api/channels.go @@ -17,11 +17,10 @@ import ( "github.com/mainflux/mainflux/logger" "github.com/mainflux/mainflux/pkg/errors" "github.com/mainflux/mainflux/pkg/groups" - "github.com/mainflux/mainflux/things" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) -func groupsHandler(svc groups.Service, tscv things.Service, r *chi.Mux, logger logger.Logger) http.Handler { +func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } @@ -47,13 +46,6 @@ func groupsHandler(svc groups.Service, tscv things.Service, r *chi.Mux, logger l opts..., ), "update_channel").ServeHTTP) - r.Get("/{groupID}/things", otelhttp.NewHandler(kithttp.NewServer( - listMembersEndpoint(tscv), - decodeListMembersRequest, - api.EncodeResponse, - opts..., - ), "list_things_by_channel").ServeHTTP) - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( gapi.ListGroupsEndpoint(svc, "users"), gapi.DecodeListGroupsRequest, @@ -75,6 +67,9 @@ func groupsHandler(svc groups.Service, tscv things.Service, r *chi.Mux, logger l opts..., ), "disable_channel").ServeHTTP) + // Instead of having this endpoint /channels/{groupID}/members separately, + // we can have two separate endpoints for each member kind + // users (/channels/{groupID}/users) & user_groups (/channels/{groupID}/groups) r.Post("/{groupID}/members", otelhttp.NewHandler(kithttp.NewServer( assignUsersGroupsEndpoint(svc), decodeAssignUsersGroupsRequest, @@ -82,6 +77,9 @@ func groupsHandler(svc groups.Service, tscv things.Service, r *chi.Mux, logger l opts..., ), "assign_members").ServeHTTP) + // Instead of having this endpoint /channels/{groupID}/members separately, + // we can have two separate endpoints for each member kind + // users (/channels/{groupID}/users) & user_groups (/channels/{groupID}/groups) r.Delete("/{groupID}/members", otelhttp.NewHandler(kithttp.NewServer( unassignUsersGroupsEndpoint(svc), decodeUnassignUsersGroupsRequest, @@ -89,6 +87,42 @@ func groupsHandler(svc groups.Service, tscv things.Service, r *chi.Mux, logger l 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", otelhttp.NewHandler(kithttp.NewServer( + assignUsersEndpoint(svc), + decodeAssignUsersRequest, + api.EncodeResponse, + opts..., + ), "assign_users").ServeHTTP) + + // Request to remove users from a channel + // This endpoint can be used alternative to /channels/{groupID}/members + r.Delete("/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( + unassignUsersEndpoint(svc), + decodeUnassignUsersRequest, + api.EncodeResponse, + opts..., + ), "unassign_users").ServeHTTP) + + // Request to add user_groups to a channel + // This endpoint can be used alternative to /channels/{groupID}/members + r.Post("/{groupID}/groups", otelhttp.NewHandler(kithttp.NewServer( + assignUserGroupsEndpoint(svc), + decodeAssignUserGroupsRequest, + api.EncodeResponse, + opts..., + ), "assign_groups").ServeHTTP) + + // Request to remove user_groups from a channel + // This endpoint can be used alternative to /channels/{groupID}/members + r.Delete("/{groupID}/groups", otelhttp.NewHandler(kithttp.NewServer( + unassignUserGroupsEndpoint(svc), + decodeUnassignUserGroupsRequest, + api.EncodeResponse, + opts..., + ), "unassign_groups").ServeHTTP) + r.Post("/{groupID}/things/{thingID}", otelhttp.NewHandler(kithttp.NewServer( connectChannelThingEndpoint(svc), decodeConnectChannelThingRequest, @@ -104,6 +138,11 @@ func groupsHandler(svc groups.Service, tscv things.Service, r *chi.Mux, logger l ), "disconnect_channel_thing").ServeHTTP) }) + // Ideal location: things service, things endpoint + // Reason for placing here : + // SpiceDB provides list of channel ids to which thing id attached + // and channel service can access spiceDB and get this channel ids list with given thing id. + // Request to get list of channels to which thingID ({memberID}) belongs r.Get("/things/{memberID}/channels", otelhttp.NewHandler(kithttp.NewServer( gapi.ListGroupsEndpoint(svc, "things"), gapi.DecodeListGroupsRequest, @@ -111,6 +150,30 @@ func groupsHandler(svc groups.Service, tscv things.Service, r *chi.Mux, logger l opts..., ), "list_channel_by_things").ServeHTTP) + // Ideal location: users service, users endpoint + // Reason for placing here : + // SpiceDB provides list of channel ids attached to given user id + // and channel service can access spiceDB and get this user ids list with given thing id. + // Request to get list of channels to which userID ({memberID}) have permission. + r.Get("/users/{memberID}/channels", otelhttp.NewHandler(kithttp.NewServer( + gapi.ListGroupsEndpoint(svc, "users"), + gapi.DecodeListGroupsRequest, + api.EncodeResponse, + opts..., + ), "list_channel_by_things").ServeHTTP) + + // Ideal location: users service, groups endpoint + // SpiceDB provides list of channel ids attached to given user_group id + // and channel service can access spiceDB and get this user ids list with given user_group id. + // Request to get list of channels to which user_group_id ({memberID}) attached. + r.Get("/groups/{memberID}/channels", otelhttp.NewHandler(kithttp.NewServer( + gapi.ListGroupsEndpoint(svc, "groups"), + gapi.DecodeListGroupsRequest, + api.EncodeResponse, + opts..., + ), "list_channel_by_things").ServeHTTP) + + // Connect channel and thing r.Post("/connect", otelhttp.NewHandler(kithttp.NewServer( connectEndpoint(svc), decodeConnectRequest, @@ -118,6 +181,7 @@ func groupsHandler(svc groups.Service, tscv things.Service, r *chi.Mux, logger l opts..., ), "connect").ServeHTTP) + // Disconnect channel and thing r.Post("/disconnect", otelhttp.NewHandler(kithttp.NewServer( disconnectEndpoint(svc), decodeDisconnectRequest, @@ -150,6 +214,50 @@ func decodeUnassignUsersGroupsRequest(_ context.Context, r *http.Request) (inter return req, nil } +func decodeAssignUsersRequest(_ context.Context, r *http.Request) (interface{}, error) { + req := assignUsersRequest{ + 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 decodeUnassignUsersRequest(_ context.Context, r *http.Request) (interface{}, error) { + req := unassignUsersRequest{ + 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 decodeAssignUserGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { + req := assignUserGroupsRequest{ + 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 decodeUnassignUserGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { + req := unassignUserGroupsRequest{ + 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 decodeConnectChannelThingRequest(_ context.Context, r *http.Request) (interface{}, error) { req := connectChannelThingRequest{ token: apiutil.ExtractBearerToken(r), diff --git a/things/api/endpoints.go b/things/api/endpoints.go index ae80b1e215..74d24c5742 100644 --- a/things/api/endpoints.go +++ b/things/api/endpoints.go @@ -264,6 +264,57 @@ func unassignUsersGroupsEndpoint(svc groups.Service) endpoint.Endpoint { return unassignUsersGroupsRes{}, nil } } +func assignUsersEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(assignUsersRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + if err := svc.Assign(ctx, req.token, req.groupID, req.Relation, "users", req.UserIDs...); err != nil { + return nil, err + } + return assignUsersRes{}, nil + } +} + +func unassignUsersEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(unassignUsersRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + if err := svc.Unassign(ctx, req.token, req.groupID, req.Relation, "users", req.UserIDs...); err != nil { + return nil, err + } + return unassignUsersRes{}, nil + } +} + +func assignUserGroupsEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(assignUserGroupsRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + if err := svc.Assign(ctx, req.token, req.groupID, "parent_group", "groups", req.UserGroupIDs...); err != nil { + return nil, err + } + return assignUserGroupsRes{}, nil + } +} + +func unassignUserGroupsEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(unassignUserGroupsRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + if err := svc.Unassign(ctx, req.token, req.groupID, "parent_group", "groups", req.UserGroupIDs...); err != nil { + return nil, err + } + return unassignUserGroupsRes{}, nil + } +} func connectChannelThingEndpoint(svc groups.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { diff --git a/things/api/http/clients.go b/things/api/http/clients.go index 93c29b3712..e69de29bb2 100644 --- a/things/api/http/clients.go +++ b/things/api/http/clients.go @@ -1,365 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package http - -import ( - "context" - "encoding/json" - "net/http" - "strings" - - "github.com/go-chi/chi/v5" - kithttp "github.com/go-kit/kit/transport/http" - "github.com/mainflux/mainflux/internal/api" - "github.com/mainflux/mainflux/internal/apiutil" - mflog "github.com/mainflux/mainflux/logger" - mfclients "github.com/mainflux/mainflux/pkg/clients" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/mainflux/mainflux/things" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -func clientsHandler(svc things.Service, r *chi.Mux, logger mflog.Logger) http.Handler { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - r.Route("/things", func(r chi.Router) { - r.Post("/", otelhttp.NewHandler(kithttp.NewServer( - createClientEndpoint(svc), - decodeCreateClientReq, - api.EncodeResponse, - opts..., - ), "create_thing").ServeHTTP) - - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( - listClientsEndpoint(svc), - decodeListClients, - api.EncodeResponse, - opts..., - ), "list_things").ServeHTTP) - - r.Post("/bulk", otelhttp.NewHandler(kithttp.NewServer( - createClientsEndpoint(svc), - decodeCreateClientsReq, - api.EncodeResponse, - opts..., - ), "create_things").ServeHTTP) - - r.Get("/{thingID}", otelhttp.NewHandler(kithttp.NewServer( - viewClientEndpoint(svc), - decodeViewClient, - api.EncodeResponse, - opts..., - ), "view_thing").ServeHTTP) - - r.Patch("/{thingID}", otelhttp.NewHandler(kithttp.NewServer( - updateClientEndpoint(svc), - decodeUpdateClient, - api.EncodeResponse, - opts..., - ), "update_thing").ServeHTTP) - - r.Patch("/{thingID}/tags", otelhttp.NewHandler(kithttp.NewServer( - updateClientTagsEndpoint(svc), - decodeUpdateClientTags, - api.EncodeResponse, - opts..., - ), "update_thing_tags").ServeHTTP) - - r.Patch("/{thingID}/secret", otelhttp.NewHandler(kithttp.NewServer( - updateClientSecretEndpoint(svc), - decodeUpdateClientCredentials, - api.EncodeResponse, - opts..., - ), "update_thing_credentials").ServeHTTP) - - r.Patch("/{thingID}/owner", otelhttp.NewHandler(kithttp.NewServer( - updateClientOwnerEndpoint(svc), - decodeUpdateClientOwner, - api.EncodeResponse, - opts..., - ), "update_thing_owner").ServeHTTP) - - r.Post("/{thingID}/enable", otelhttp.NewHandler(kithttp.NewServer( - enableClientEndpoint(svc), - decodeChangeClientStatus, - api.EncodeResponse, - opts..., - ), "enable_thing").ServeHTTP) - - r.Post("/{thingID}/disable", otelhttp.NewHandler(kithttp.NewServer( - disableClientEndpoint(svc), - decodeChangeClientStatus, - api.EncodeResponse, - opts..., - ), "disable_thing").ServeHTTP) - - r.Post("/{thingID}/share", otelhttp.NewHandler(kithttp.NewServer( - thingShareEndpoint(svc), - decodeThingShareRequest, - api.EncodeResponse, - opts..., - ), "thing_share").ServeHTTP) - - r.Post("/{thingID}/unshare", otelhttp.NewHandler(kithttp.NewServer( - thingUnshareEndpoint(svc), - decodeThingUnshareRequest, - api.EncodeResponse, - opts..., - ), "thing_delete_share").ServeHTTP) - }) - - // Ideal location: things service, channels endpoint - // Reason for placing here : - // SpiceDB provides list of thing ids present in given channel id - // and things service can access spiceDB and get the list of thing ids present in given channel id. - // Request to get list of things present in channelID ({groupID}) . - r.Get("/channels/{groupID}/things", otelhttp.NewHandler(kithttp.NewServer( - listMembersEndpoint(svc), - decodeListMembersRequest, - api.EncodeResponse, - opts..., - ), "list_things_by_channel_id").ServeHTTP) - - return r -} - -func decodeViewClient(_ context.Context, r *http.Request) (interface{}, error) { - req := viewClientReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "thingID"), - } - - return req, nil -} - -func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) { - var ownerID string - s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - 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) - } - 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, err - } - - p, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - if oid != "" { - ownerID = oid - } - st, err := mfclients.ToStatus(s) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - req := listClientsReq{ - token: apiutil.ExtractBearerToken(r), - status: st, - offset: o, - limit: l, - metadata: m, - name: n, - tag: t, - permission: p, - owner: ownerID, - } - return req, nil -} - -func decodeUpdateClient(_ 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 := updateClientReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "thingID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} - -func decodeUpdateClientTags(_ 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 := updateClientTagsReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "thingID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} - -func decodeUpdateClientCredentials(_ 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 := updateClientCredentialsReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "thingID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} - -func decodeUpdateClientOwner(_ 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 := updateClientOwnerReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "thingID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} - -func decodeCreateClientReq(_ 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) - } - - var c mfclients.Client - if err := json.NewDecoder(r.Body).Decode(&c); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - req := createClientReq{ - client: c, - token: apiutil.ExtractBearerToken(r), - } - - return req, nil -} - -func decodeCreateClientsReq(_ 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) - } - - c := createClientsReq{token: apiutil.ExtractBearerToken(r)} - if err := json.NewDecoder(r.Body).Decode(&c.Clients); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return c, nil -} - -func decodeChangeClientStatus(_ context.Context, r *http.Request) (interface{}, error) { - req := changeClientStatusReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "thingID"), - } - - return req, nil -} - -func decodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { - s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - 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) - } - 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, - Permission: p, - Metadata: m, - }, - groupID: chi.URLParam(r, "groupID"), - } - return req, nil -} - -func decodeThingShareRequest(_ 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 := thingShareRequest{ - token: apiutil.ExtractBearerToken(r), - thingID: chi.URLParam(r, "thingID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} - -func decodeThingUnshareRequest(_ 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 := thingUnshareRequest{ - token: apiutil.ExtractBearerToken(r), - thingID: chi.URLParam(r, "thingID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} diff --git a/things/api/requests.go b/things/api/requests.go index 9666794485..6f6d77e94c 100644 --- a/things/api/requests.go +++ b/things/api/requests.go @@ -275,6 +275,103 @@ func (req unassignUsersGroupsRequest) validate() error { return nil } +type assignUsersRequest struct { + token string + groupID string + Relation string `json:"relation"` + UserIDs []string `json:"user_ids"` +} + +func (req assignUsersRequest) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + + if req.Relation == "" { + return apiutil.ErrMissingRelation + } + + if req.groupID == "" { + return apiutil.ErrMissingID + } + + if len(req.UserIDs) == 0 { + return apiutil.ErrEmptyList + } + + return nil +} + +type unassignUsersRequest struct { + token string + groupID string + Relation string `json:"relation"` + UserIDs []string `json:"user_ids"` +} + +func (req unassignUsersRequest) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + + if req.Relation == "" { + return apiutil.ErrMissingRelation + } + + if req.groupID == "" { + return apiutil.ErrMissingID + } + + if len(req.UserIDs) == 0 { + return apiutil.ErrEmptyList + } + return nil +} + +type assignUserGroupsRequest struct { + token string + groupID string + UserGroupIDs []string `json:"group_ids"` +} + +func (req assignUserGroupsRequest) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + + if req.groupID == "" { + return apiutil.ErrMissingID + } + + if len(req.UserGroupIDs) == 0 { + return apiutil.ErrEmptyList + } + + return nil +} + +type unassignUserGroupsRequest struct { + token string + groupID string + UserGroupIDs []string `json:"group_ids"` +} + +func (req unassignUserGroupsRequest) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + + if req.groupID == "" { + return apiutil.ErrMissingID + } + + if len(req.UserGroupIDs) == 0 { + return apiutil.ErrEmptyList + } + + return nil +} + type connectChannelThingRequest struct { token string ThingID string `json:"thing_id,omitempty"` diff --git a/things/api/responses.go b/things/api/responses.go index a8f6e54139..8a9eaab2aa 100644 --- a/things/api/responses.go +++ b/things/api/responses.go @@ -185,6 +185,66 @@ func (res unassignUsersGroupsRes) Empty() bool { return true } +type assignUsersRes struct { +} + +func (res assignUsersRes) Code() int { + return http.StatusOK +} + +func (res assignUsersRes) Headers() map[string]string { + return map[string]string{} +} + +func (res assignUsersRes) Empty() bool { + return true +} + +type unassignUsersRes struct { +} + +func (res unassignUsersRes) Code() int { + return http.StatusNoContent +} + +func (res unassignUsersRes) Headers() map[string]string { + return map[string]string{} +} + +func (res unassignUsersRes) Empty() bool { + return true +} + +type assignUserGroupsRes struct { +} + +func (res assignUserGroupsRes) Code() int { + return http.StatusOK +} + +func (res assignUserGroupsRes) Headers() map[string]string { + return map[string]string{} +} + +func (res assignUserGroupsRes) Empty() bool { + return true +} + +type unassignUserGroupsRes struct { +} + +func (res unassignUserGroupsRes) Code() int { + return http.StatusNoContent +} + +func (res unassignUserGroupsRes) Headers() map[string]string { + return map[string]string{} +} + +func (res unassignUserGroupsRes) Empty() bool { + return true +} + type connectChannelThingRes struct { } diff --git a/things/api/transport.go b/things/api/transport.go new file mode 100644 index 0000000000..6d4356db94 --- /dev/null +++ b/things/api/transport.go @@ -0,0 +1,26 @@ +// Copyright (c) Mainflux +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/mainflux/mainflux" + mflog "github.com/mainflux/mainflux/logger" + "github.com/mainflux/mainflux/pkg/groups" + "github.com/mainflux/mainflux/things" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// MakeHandler returns a HTTP handler for Things and Groups API endpoints. +func MakeHandler(tsvc things.Service, grps groups.Service, mux *chi.Mux, logger mflog.Logger, instanceID string) http.Handler { + clientsHandler(tsvc, mux, logger) + groupsHandler(grps, mux, logger) + + mux.Get("/health", mainflux.Health("things", instanceID)) + mux.Handle("/metrics", promhttp.Handler()) + + return mux +} diff --git a/users/api/clients.go b/users/api/clients.go index 506ca10d5b..73933b91c3 100644 --- a/users/api/clients.go +++ b/users/api/clients.go @@ -142,7 +142,7 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger mflog.Logger) http.Han decodeListMembersRequest, api.EncodeResponse, opts..., - ), "list_users_by_user_group_id").ServeHTTP) + ), "list_users").ServeHTTP) // Ideal location: things service, channels endpoint. // Reason for placing here : @@ -156,7 +156,7 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger mflog.Logger) http.Han decodeListMembersRequest, api.EncodeResponse, opts..., - ), "list_users_by_channel_id").ServeHTTP) + ), "list_users_of_a_channel").ServeHTTP) return r } diff --git a/users/api/groups.go b/users/api/groups.go index 4f98ce50a4..0be1cbb394 100644 --- a/users/api/groups.go +++ b/users/api/groups.go @@ -4,18 +4,22 @@ package api import ( + "context" + "encoding/json" "context" "encoding/json" "net/http" "github.com/go-chi/chi/v5" "github.com/go-kit/kit/endpoint" + "github.com/go-kit/kit/endpoint" kithttp "github.com/go-kit/kit/transport/http" "github.com/mainflux/mainflux/internal/api" "github.com/mainflux/mainflux/internal/apiutil" gapi "github.com/mainflux/mainflux/internal/groups/api" "github.com/mainflux/mainflux/logger" "github.com/mainflux/mainflux/pkg/errors" + "github.com/mainflux/mainflux/pkg/errors" "github.com/mainflux/mainflux/pkg/groups" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) @@ -83,6 +87,8 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha opts..., ), "disable_group").ServeHTTP) + // Instead of this endpoint /{groupID}/members separately, we can simply use /{groupID}/users + // because this group is intended exclusively for users. No other entity could not be added // Instead of this endpoint /{groupID}/members 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", otelhttp.NewHandler(kithttp.NewServer( @@ -92,6 +98,8 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha opts..., ), "assign_members").ServeHTTP) + // Instead of maintaining this endpoint /{groupID}/members separately, we can simply use /{groupID}/users + // because this group is intended exclusively for users. No other entity could not be added // Instead of maintaining this endpoint /{groupID}/members separately, we can simply use /{groupID}/users // because this group is intended exclusively for users. No other entity could not be added r.Delete("/{groupID}/members", otelhttp.NewHandler(kithttp.NewServer( @@ -108,6 +116,16 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha opts..., ), "assign_users").ServeHTTP) + r.Delete("/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( + unassignUsersEndpoint(svc), + decodeUnassignUsersRequest, + r.Post("/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( + assignUsersEndpoint(svc), + decodeAssignUsersRequest, + api.EncodeResponse, + opts..., + ), "assign_users").ServeHTTP) + r.Delete("/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( unassignUsersEndpoint(svc), decodeUnassignUsersRequest, @@ -123,14 +141,7 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha gapi.DecodeListGroupsRequest, api.EncodeResponse, opts..., - ), "list_groups_by_channel_id").ServeHTTP) - - r.Get("/users/{memberID}/groups", otelhttp.NewHandler(kithttp.NewServer( - gapi.ListGroupsEndpoint(svc, "users"), - gapi.DecodeListGroupsRequest, - api.EncodeResponse, - opts..., - ), "list_groups_by_user_id").ServeHTTP) + ), "list_groups_of_a_channel").ServeHTTP) return r } @@ -184,3 +195,54 @@ func unassignUsersEndpoint(svc groups.Service) endpoint.Endpoint { return unassignUsersRes{}, nil } } + +func decodeAssignUsersRequest(_ context.Context, r *http.Request) (interface{}, error) { + req := assignUsersReq{ + 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 decodeUnassignUsersRequest(_ context.Context, r *http.Request) (interface{}, error) { + req := unassignUsersReq{ + 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 assignUsersEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(assignUsersReq) + + if err := req.validate(); err != nil { + return assignUsersRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + if err := svc.Assign(ctx, req.token, req.groupID, req.Relation, "users", req.UserIDs...); err != nil { + return assignUsersRes{}, err + } + return assignUsersRes{}, nil + } +} + +func unassignUsersEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(unassignUsersReq) + + if err := req.validate(); err != nil { + return unassignUsersRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + + if err := svc.Unassign(ctx, req.token, req.groupID, req.Relation, "users", req.UserIDs...); err != nil { + return nil, err + } + return unassignUsersRes{}, nil + } +} From 4ad61533aee874fcd964462647968ed2839d8372 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Thu, 12 Oct 2023 20:51:06 +0530 Subject: [PATCH 05/17] Add: List of user groups & removed repeating code in groups (#29) * removed repeating code in list groups Signed-off-by: Arvindh * add: list of user group Signed-off-by: Arvindh * fix: otel handler operator name for endpoints Signed-off-by: Arvindh --------- Signed-off-by: Arvindh Signed-off-by: dusanb94 --- internal/groups/service.go | 63 +++---- things/api/channels.go | 6 +- things/api/clients.go | 362 +++++++++++++++++++++++++++++++++++++ users/api/clients.go | 4 +- users/api/groups.go | 9 +- 5 files changed, 405 insertions(+), 39 deletions(-) create mode 100644 things/api/clients.go diff --git a/internal/groups/service.go b/internal/groups/service.go index 21883b256a..40da6b1ee6 100644 --- a/internal/groups/service.go +++ b/internal/groups/service.go @@ -148,17 +148,10 @@ func (svc service) ListGroups(ctx context.Context, token string, memberKind, mem if err != nil { return groups.Page{}, err } - allowedIDs, err := svc.listAllGroupsOfUserID(ctx, userID, gm.Permission) + ids, err = svc.filterAllowedGroupIDsOfUserID(ctx, userID, gm.Permission, cids.Policies) if err != nil { return groups.Page{}, err } - for _, cid := range cids.Policies { - for _, id := range allowedIDs { - if id == cid { - ids = append(ids, id) - } - } - } case groupsKind: if _, err := svc.authorizeKind(ctx, userType, usersKind, userID, gm.Permission, groupType, memberID); err != nil { return groups.Page{}, err @@ -173,14 +166,9 @@ func (svc service) ListGroups(ctx context.Context, token string, memberKind, mem if err != nil { return groups.Page{}, err } - - allowedIDs, err := svc.listAllGroupsOfUserID(ctx, userID, gm.Permission) - for _, gid := range gids.Policies { - for _, id := range allowedIDs { - if id == gid { - ids = append(ids, id) - } - } + ids, err = svc.filterAllowedGroupIDsOfUserID(ctx, userID, gm.Permission, gids.Policies) + if err != nil { + return groups.Page{}, err } case channelsKind: if _, err := svc.authorizeKind(ctx, userType, usersKind, userID, viewPermission, groupType, memberID); err != nil { @@ -196,19 +184,11 @@ func (svc service) ListGroups(ctx context.Context, token string, memberKind, mem return groups.Page{}, err } - allowedIDs, err := svc.listAllGroupsOfUserID(ctx, userID, gm.Permission) - for _, gid := range gids.Policies { - for _, id := range allowedIDs { - if id == gid { - ids = append(ids, id) - } - } - } - case usersKind: - allowedIDs, err := svc.listAllGroupsOfUserID(ctx, userID, gm.Permission) + ids, err = svc.filterAllowedGroupIDsOfUserID(ctx, userID, gm.Permission, gids.Policies) if err != nil { return groups.Page{}, err } + case usersKind: if memberID != "" && userID != memberID { if _, err := svc.authorizeKind(ctx, userType, usersKind, userID, ownerRelation, userType, memberID); err != nil { return groups.Page{}, err @@ -222,15 +202,15 @@ func (svc service) ListGroups(ctx context.Context, token string, memberKind, mem if err != nil { return groups.Page{}, err } - for _, gid := range gids.Policies { - for _, id := range allowedIDs { - if id == gid { - ids = append(ids, id) - } - } + ids, err = svc.filterAllowedGroupIDsOfUserID(ctx, userID, gm.Permission, gids.Policies) + if err != nil { + return groups.Page{}, err } } else { - ids = allowedIDs + ids, err = svc.listAllGroupsOfUserID(ctx, userID, gm.Permission) + if err != nil { + return groups.Page{}, err + } } default: return groups.Page{}, fmt.Errorf("invalid member kind") @@ -446,6 +426,23 @@ func (svc service) Unassign(ctx context.Context, token, groupID, relation, membe return nil } +func (svc service) filterAllowedGroupIDsOfUserID(ctx context.Context, userID string, permission string, groupIDs []string) ([]string, error) { + var ids []string + allowedIDs, err := svc.listAllGroupsOfUserID(ctx, userID, permission) + if err != nil { + return []string{}, err + } + + for _, gid := range groupIDs { + for _, id := range allowedIDs { + if id == gid { + ids = append(ids, id) + } + } + } + return ids, nil +} + func (svc service) listAllGroupsOfUserID(ctx context.Context, userID string, permission string) ([]string, error) { allowedIDs, err := svc.auth.ListAllObjects(ctx, &mainflux.ListObjectsReq{ SubjectType: userType, diff --git a/things/api/channels.go b/things/api/channels.go index b4b4d40388..2259f35e18 100644 --- a/things/api/channels.go +++ b/things/api/channels.go @@ -148,7 +148,7 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha gapi.DecodeListGroupsRequest, api.EncodeResponse, opts..., - ), "list_channel_by_things").ServeHTTP) + ), "list_channel_by_thing_id").ServeHTTP) // Ideal location: users service, users endpoint // Reason for placing here : @@ -160,7 +160,7 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha gapi.DecodeListGroupsRequest, api.EncodeResponse, opts..., - ), "list_channel_by_things").ServeHTTP) + ), "list_channel_by_user_id").ServeHTTP) // Ideal location: users service, groups endpoint // SpiceDB provides list of channel ids attached to given user_group id @@ -171,7 +171,7 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha gapi.DecodeListGroupsRequest, api.EncodeResponse, opts..., - ), "list_channel_by_things").ServeHTTP) + ), "list_channel_by_user_group_id").ServeHTTP) // Connect channel and thing r.Post("/connect", otelhttp.NewHandler(kithttp.NewServer( diff --git a/things/api/clients.go b/things/api/clients.go new file mode 100644 index 0000000000..2e88e8ca14 --- /dev/null +++ b/things/api/clients.go @@ -0,0 +1,362 @@ +// Copyright (c) Mainflux +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "context" + "encoding/json" + "net/http" + "strings" + + "github.com/go-chi/chi/v5" + kithttp "github.com/go-kit/kit/transport/http" + "github.com/mainflux/mainflux/internal/api" + "github.com/mainflux/mainflux/internal/apiutil" + mflog "github.com/mainflux/mainflux/logger" + mfclients "github.com/mainflux/mainflux/pkg/clients" + "github.com/mainflux/mainflux/pkg/errors" + "github.com/mainflux/mainflux/things" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" +) + +func clientsHandler(svc things.Service, r *chi.Mux, logger mflog.Logger) http.Handler { + opts := []kithttp.ServerOption{ + kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), + } + r.Route("/things", func(r chi.Router) { + r.Post("/", otelhttp.NewHandler(kithttp.NewServer( + createClientEndpoint(svc), + decodeCreateClientReq, + api.EncodeResponse, + opts..., + ), "create_thing").ServeHTTP) + + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + listClientsEndpoint(svc), + decodeListClients, + api.EncodeResponse, + opts..., + ), "list_things").ServeHTTP) + + r.Post("/bulk", otelhttp.NewHandler(kithttp.NewServer( + createClientsEndpoint(svc), + decodeCreateClientsReq, + api.EncodeResponse, + opts..., + ), "create_things").ServeHTTP) + + r.Get("/{thingID}", otelhttp.NewHandler(kithttp.NewServer( + viewClientEndpoint(svc), + decodeViewClient, + api.EncodeResponse, + opts..., + ), "view_thing").ServeHTTP) + + r.Patch("/{thingID}", otelhttp.NewHandler(kithttp.NewServer( + updateClientEndpoint(svc), + decodeUpdateClient, + api.EncodeResponse, + opts..., + ), "update_thing").ServeHTTP) + + r.Patch("/{thingID}/tags", otelhttp.NewHandler(kithttp.NewServer( + updateClientTagsEndpoint(svc), + decodeUpdateClientTags, + api.EncodeResponse, + opts..., + ), "update_thing_tags").ServeHTTP) + + r.Patch("/{thingID}/secret", otelhttp.NewHandler(kithttp.NewServer( + updateClientSecretEndpoint(svc), + decodeUpdateClientCredentials, + api.EncodeResponse, + opts..., + ), "update_thing_credentials").ServeHTTP) + + r.Patch("/{thingID}/owner", otelhttp.NewHandler(kithttp.NewServer( + updateClientOwnerEndpoint(svc), + decodeUpdateClientOwner, + api.EncodeResponse, + opts..., + ), "update_thing_owner").ServeHTTP) + + r.Post("/{thingID}/enable", otelhttp.NewHandler(kithttp.NewServer( + enableClientEndpoint(svc), + decodeChangeClientStatus, + api.EncodeResponse, + opts..., + ), "enable_thing").ServeHTTP) + + r.Post("/{thingID}/disable", otelhttp.NewHandler(kithttp.NewServer( + disableClientEndpoint(svc), + decodeChangeClientStatus, + api.EncodeResponse, + opts..., + ), "disable_thing").ServeHTTP) + + r.Post("/{thingID}/share", otelhttp.NewHandler(kithttp.NewServer( + thingShareEndpoint(svc), + decodeThingShareRequest, + api.EncodeResponse, + opts..., + ), "thing_share").ServeHTTP) + + r.Post("/{thingID}/unshare", otelhttp.NewHandler(kithttp.NewServer( + thingUnshareEndpoint(svc), + decodeThingUnshareRequest, + api.EncodeResponse, + opts..., + ), "thing_delete_share").ServeHTTP) + + }) + + // Ideal location: things service, channels endpoint + // Reason for placing here : + // SpiceDB provides list of thing ids present in given channel id + // and things service can access spiceDB and get the list of thing ids present in given channel id. + // Request to get list of things present in channelID ({groupID}) . + r.Get("/channels/{groupID}/things", otelhttp.NewHandler(kithttp.NewServer( + listMembersEndpoint(svc), + decodeListMembersRequest, + api.EncodeResponse, + opts..., + ), "list_things_by_channel_id").ServeHTTP) + + return r +} + +func decodeViewClient(_ context.Context, r *http.Request) (interface{}, error) { + req := viewClientReq{ + token: apiutil.ExtractBearerToken(r), + id: chi.URLParam(r, "thingID"), + } + + return req, nil +} + +func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) { + var ownerID string + s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + 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) + } + 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, err + } + + p, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + if oid != "" { + ownerID = oid + } + st, err := mfclients.ToStatus(s) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + req := listClientsReq{ + token: apiutil.ExtractBearerToken(r), + status: st, + offset: o, + limit: l, + metadata: m, + name: n, + tag: t, + permission: p, + owner: ownerID, + } + return req, nil +} + +func decodeUpdateClient(_ 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 := updateClientReq{ + token: apiutil.ExtractBearerToken(r), + id: chi.URLParam(r, "thingID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeUpdateClientTags(_ 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 := updateClientTagsReq{ + token: apiutil.ExtractBearerToken(r), + id: chi.URLParam(r, "thingID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeUpdateClientCredentials(_ 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 := updateClientCredentialsReq{ + token: apiutil.ExtractBearerToken(r), + id: chi.URLParam(r, "thingID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeUpdateClientOwner(_ 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 := updateClientOwnerReq{ + token: apiutil.ExtractBearerToken(r), + id: chi.URLParam(r, "thingID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeCreateClientReq(_ 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) + } + + var c mfclients.Client + if err := json.NewDecoder(r.Body).Decode(&c); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + req := createClientReq{ + client: c, + token: apiutil.ExtractBearerToken(r), + } + + return req, nil +} + +func decodeCreateClientsReq(_ 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) + } + + c := createClientsReq{token: apiutil.ExtractBearerToken(r)} + if err := json.NewDecoder(r.Body).Decode(&c.Clients); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return c, nil +} + +func decodeChangeClientStatus(_ context.Context, r *http.Request) (interface{}, error) { + req := changeClientStatusReq{ + token: apiutil.ExtractBearerToken(r), + id: chi.URLParam(r, "thingID"), + } + + return req, nil +} + +func decodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { + s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + 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) + } + 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, + Permission: p, + Metadata: m, + }, + groupID: chi.URLParam(r, "groupID"), + } + return req, nil +} + +func decodeThingShareRequest(_ 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 := thingShareRequest{ + token: apiutil.ExtractBearerToken(r), + thingID: chi.URLParam(r, "thingID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeThingUnshareRequest(_ 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 := thingUnshareRequest{ + token: apiutil.ExtractBearerToken(r), + thingID: chi.URLParam(r, "thingID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} diff --git a/users/api/clients.go b/users/api/clients.go index 73933b91c3..506ca10d5b 100644 --- a/users/api/clients.go +++ b/users/api/clients.go @@ -142,7 +142,7 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger mflog.Logger) http.Han decodeListMembersRequest, api.EncodeResponse, opts..., - ), "list_users").ServeHTTP) + ), "list_users_by_user_group_id").ServeHTTP) // Ideal location: things service, channels endpoint. // Reason for placing here : @@ -156,7 +156,7 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger mflog.Logger) http.Han decodeListMembersRequest, api.EncodeResponse, opts..., - ), "list_users_of_a_channel").ServeHTTP) + ), "list_users_by_channel_id").ServeHTTP) return r } diff --git a/users/api/groups.go b/users/api/groups.go index 0be1cbb394..4bd82a0c98 100644 --- a/users/api/groups.go +++ b/users/api/groups.go @@ -141,7 +141,14 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha gapi.DecodeListGroupsRequest, api.EncodeResponse, opts..., - ), "list_groups_of_a_channel").ServeHTTP) + ), "list_groups_by_channel_id").ServeHTTP) + + r.Get("/users/{memberID}/groups", otelhttp.NewHandler(kithttp.NewServer( + gapi.ListGroupsEndpoint(svc, "users"), + gapi.DecodeListGroupsRequest, + api.EncodeResponse, + opts..., + ), "list_groups_by_user_id").ServeHTTP) return r } From ac8e14305f6685d5ce5d1c2b6e3ab7af06cbb25f Mon Sep 17 00:00:00 2001 From: Arvindh Date: Fri, 13 Oct 2023 18:36:44 +0530 Subject: [PATCH 06/17] add: listing of shared things and users Signed-off-by: Arvindh --- api/openapi/things.yml | 54 ----- docker/nginx/nginx-key.conf | 18 +- pkg/sdk/go/channels.go | 125 +++++++++++ pkg/sdk/go/groups.go | 77 +++++-- pkg/sdk/go/policies.go | 253 ---------------------- pkg/sdk/go/policies_test.go | 1 + pkg/sdk/go/requests.go | 38 ++++ pkg/sdk/go/responses.go | 7 - pkg/sdk/go/sdk.go | 211 +++--------------- pkg/sdk/go/things.go | 44 +++- pkg/sdk/go/users.go | 52 +++++ things/api/http/channels.go | 52 ----- things/api/http/clients.go | 373 ++++++++++++++++++++++++++++++++ things/api/http/endpoints.go | 405 +++++++++++++++++++++++++++++++++++ things/api/http/requests.go | 1 + things/api/logging.go | 4 +- things/api/metrics.go | 4 +- things/events/events.go | 2 + things/events/streams.go | 5 +- things/service.go | 64 +++++- things/service_test.go | 6 +- things/things.go | 2 +- things/tracing/tracing.go | 4 +- users/api/clients.go | 114 ++++++---- users/api/endpoints.go | 42 +++- users/api/groups.go | 22 -- users/api/logging.go | 6 +- users/api/metrics.go | 4 +- users/api/requests.go | 14 +- users/clients.go | 4 +- users/events/events.go | 14 +- users/events/streams.go | 6 +- users/service.go | 26 ++- users/service_test.go | 2 +- users/tracing/tracing.go | 6 +- 35 files changed, 1368 insertions(+), 694 deletions(-) diff --git a/api/openapi/things.yml b/api/openapi/things.yml index 42ba39d798..f867a80220 100644 --- a/api/openapi/things.yml +++ b/api/openapi/things.yml @@ -564,60 +564,6 @@ paths: "500": $ref: "#/components/responses/ServiceError" - /channels/{chanID}/assign: - post: - summary: Assigns a member to a channel - description: | - Assigns a specific member to a channel that is identifier by the channel ID. - tags: - - Channels - parameters: - - $ref: "#/components/parameters/chanID" - requestBody: - $ref: "#/components/requestBodies/AssignReq" - security: - - bearerAuth: [] - responses: - "200": - description: Thing shared. - "400": - description: Failed due to malformed thing's ID. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /channels/{chanID}/unassign: - post: - summary: Unassigns a member from a channel - description: | - Unassigns a specific member from a channel that is identifier by the channel ID. - tags: - - Channels - parameters: - - $ref: "#/components/parameters/chanID" - requestBody: - $ref: "#/components/requestBodies/AssignReq" - security: - - bearerAuth: [] - responses: - "200": - description: Thing unshared. - "400": - description: Failed due to malformed thing's ID. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /channels/{chanID}/users/assign: post: summary: Assigns a member to a channel diff --git a/docker/nginx/nginx-key.conf b/docker/nginx/nginx-key.conf index 683eac18db..a36037eb23 100644 --- a/docker/nginx/nginx-key.conf +++ b/docker/nginx/nginx-key.conf @@ -53,7 +53,7 @@ http { # 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 +66,14 @@ http { # Proxy pass to channel id to users service for listing of channels # /channels/{channelID}/users - Listing of Users belongs to channelID # /channels/{channelID}/groups - Listing of User Groups belongs to channelID - location ~ ^/channels/(.+)/(users|groups) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://users:${MF_USERS_HTTP_PORT}; - break; - } - proxy_pass http://things:${MF_THINGS_HTTP_PORT}; + location ~ ^/(channels|things)/(.+)/(users|groups) { + include snippets/proxy-headers.conf; + add_header Access-Control-Expose-Headers Location; + if ($request_method = GET) { + proxy_pass http://users:${MF_USERS_HTTP_PORT}; + break; + } + proxy_pass http://things:${MF_THINGS_HTTP_PORT}; } # Proxy pass to users service location ~ ^/(users|groups|password|authorize) { diff --git a/pkg/sdk/go/channels.go b/pkg/sdk/go/channels.go index fa19f32eea..885615cad6 100644 --- a/pkg/sdk/go/channels.go +++ b/pkg/sdk/go/channels.go @@ -146,6 +146,131 @@ func (sdk mfSDK) UpdateChannel(c Channel, token string) (Channel, errors.SDKErro return c, nil } +func (sdk mfSDK) AddUsersToChannel(token, channelID string, req addUsersToChannelReq) 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) RemoveUsersFromChannel(token, channelID string, req removeUsersFromChannelReq) 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) (ChannelsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.thingsURL, fmt.Sprintf("%s/%s/%s", channelsEndpoint, channelID, usersEndpoint), 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) AddUserGroupsToChannel(token, channelID string, req addUserGroupsToChannelReq) 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) RemoveUserGroupsToChannel(token, channelID string, req removeUserGroupsFromChannelReq) 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) (ChannelsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.thingsURL, fmt.Sprintf("%s/%s/%s", channelsEndpoint, channelID, groupsEndpoint), 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) Connect(conn Connection, token string) errors.SDKError { + data, err := json.Marshal(conn) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s", sdk.thingsURL, connectEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) + + return sdkerr +} + +func (sdk mfSDK) Disconnect(connIDs Connection, token string) errors.SDKError { + data, err := json.Marshal(connIDs) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s", sdk.thingsURL, disconnectEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) + + return sdkerr +} + +func (sdk mfSDK) ConnectThing(thingID, channelID, token string) errors.SDKError { + url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.thingsURL, channelsEndpoint, channelID, thingsEndpoint, thingID) + + _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, nil, nil, http.StatusNoContent) + + return sdkerr + +} + +func (sdk mfSDK) DisconnectThing(thingID, channelID, token string) errors.SDKError { + url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.thingsURL, channelsEndpoint, channelID, thingsEndpoint, thingID) + + _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) + + return sdkerr +} + func (sdk mfSDK) EnableChannel(id, token string) (Channel, errors.SDKError) { return sdk.changeChannelStatus(id, enableEndpoint, token) } diff --git a/pkg/sdk/go/groups.go b/pkg/sdk/go/groups.go index 5980bd2c79..55c9395c14 100644 --- a/pkg/sdk/go/groups.go +++ b/pkg/sdk/go/groups.go @@ -57,25 +57,6 @@ func (sdk mfSDK) CreateGroup(g Group, token string) (Group, errors.SDKError) { return g, nil } -func (sdk mfSDK) Memberships(clientID string, pm PageMetadata, token string) (MembershipsPage, errors.SDKError) { - url, err := sdk.withQueryParams(fmt.Sprintf("%s/%s/%s", sdk.usersURL, usersEndpoint, clientID), "memberships", pm) - if err != nil { - return MembershipsPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return MembershipsPage{}, sdkerr - } - - var tp MembershipsPage - if err := json.Unmarshal(body, &tp); err != nil { - return MembershipsPage{}, errors.NewSDKError(err) - } - - return tp, nil -} - func (sdk mfSDK) Groups(pm PageMetadata, token string) (GroupsPage, errors.SDKError) { url, err := sdk.withQueryParams(sdk.usersURL, groupsEndpoint, pm) if err != nil { @@ -164,6 +145,64 @@ func (sdk mfSDK) DisableGroup(id, token string) (Group, errors.SDKError) { return sdk.changeGroupStatus(id, disableEndpoint, token) } +func (sdk mfSDK) AddUsersToGroup(groupID string, req addUsersToGroupReq, 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) RemoveUsersToGroup(groupID string, req removeUsersToGroupReq, token string) errors.SDKError { + data, err := json.Marshal(req) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s", sdk.usersURL, groupsEndpoint, groupID, usersEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, data, nil, http.StatusOK) + return sdkerr +} + +func (sdk mfSDK) ListGroupUsers(groupID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s", groupsEndpoint, groupID, usersEndpoint), pm) + if err != nil { + return GroupsPage{}, errors.NewSDKError(err) + } + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return GroupsPage{}, sdkerr + } + gp := GroupsPage{} + if err := json.Unmarshal(body, &gp); err != nil { + return GroupsPage{}, errors.NewSDKError(err) + } + + return gp, nil +} + +func (sdk mfSDK) ListGroupChannels(groupID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s", groupsEndpoint, groupID, channelsEndpoint), pm) + if err != nil { + return GroupsPage{}, errors.NewSDKError(err) + } + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return GroupsPage{}, sdkerr + } + gp := GroupsPage{} + if err := json.Unmarshal(body, &gp); err != nil { + return GroupsPage{}, errors.NewSDKError(err) + } + + return gp, nil +} + func (sdk mfSDK) changeGroupStatus(id, status, token string) (Group, errors.SDKError) { url := fmt.Sprintf("%s/%s/%s/%s", sdk.usersURL, groupsEndpoint, id, status) diff --git a/pkg/sdk/go/policies.go b/pkg/sdk/go/policies.go index a2a62b0057..e69de29bb2 100644 --- a/pkg/sdk/go/policies.go +++ b/pkg/sdk/go/policies.go @@ -1,253 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package sdk - -import ( - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/mainflux/mainflux/pkg/errors" -) - -const ( - policyEndpoint = "policies" - authorizeEndpoint = "authorize" - accessEndpoint = "access" -) - -// Policy represents an argument struct for making a policy related function calls. -type Policy struct { - OwnerID string `json:"owner_id"` - Subject string `json:"subject"` - Object string `json:"object"` - Actions []string `json:"actions"` - External bool `json:"external,omitempty"` // This is specificially used in things service. If set to true, it means the subject is userID otherwise it is thingID. - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -type AccessRequest struct { - Subject string `json:"subject,omitempty"` - Object string `json:"object,omitempty"` - Action string `json:"action,omitempty"` - EntityType string `json:"entity_type,omitempty"` -} - -func (sdk mfSDK) AuthorizeUser(accessReq AccessRequest, token string) (bool, errors.SDKError) { - data, err := json.Marshal(accessReq) - if err != nil { - return false, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.usersURL, authorizeEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return false, sdkerr - } - - return true, nil -} - -func (sdk mfSDK) CreateUserPolicy(p Policy, token string) errors.SDKError { - data, err := json.Marshal(p) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.usersURL, policyEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - if sdkerr != nil { - return sdkerr - } - - return nil -} - -func (sdk mfSDK) UpdateUserPolicy(p Policy, token string) errors.SDKError { - data, err := json.Marshal(p) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.usersURL, policyEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, data, nil, http.StatusNoContent) - if sdkerr != nil { - return sdkerr - } - - return nil -} - -func (sdk mfSDK) ListUserPolicies(pm PageMetadata, token string) (PolicyPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, policyEndpoint, pm) - if err != nil { - return PolicyPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return PolicyPage{}, sdkerr - } - - var pp PolicyPage - if err := json.Unmarshal(body, &pp); err != nil { - return PolicyPage{}, errors.NewSDKError(err) - } - - return pp, nil -} - -func (sdk mfSDK) DeleteUserPolicy(p Policy, token string) errors.SDKError { - url := fmt.Sprintf("%s/%s/%s/%s", sdk.usersURL, policyEndpoint, p.Subject, p.Object) - - _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) - - return sdkerr -} - -func (sdk mfSDK) CreateThingPolicy(p Policy, token string) errors.SDKError { - data, err := json.Marshal(p) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.thingsURL, policyEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - if sdkerr != nil { - return sdkerr - } - - return nil -} - -func (sdk mfSDK) UpdateThingPolicy(p Policy, token string) errors.SDKError { - data, err := json.Marshal(p) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.thingsURL, policyEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, data, nil, http.StatusNoContent) - if sdkerr != nil { - return sdkerr - } - - return nil -} - -func (sdk mfSDK) ListThingPolicies(pm PageMetadata, token string) (PolicyPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.thingsURL, policyEndpoint, pm) - if err != nil { - return PolicyPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return PolicyPage{}, sdkerr - } - - var pp PolicyPage - if err := json.Unmarshal(body, &pp); err != nil { - return PolicyPage{}, errors.NewSDKError(err) - } - - return pp, nil -} - -func (sdk mfSDK) DeleteThingPolicy(p Policy, token string) errors.SDKError { - url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, policyEndpoint, p.Subject, p.Object) - - _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) - - return sdkerr -} - -func (sdk mfSDK) Assign(actions []string, userID, groupID, token string) errors.SDKError { - policy := Policy{ - Subject: userID, - Object: groupID, - Actions: actions, - } - return sdk.CreateUserPolicy(policy, token) -} - -func (sdk mfSDK) Unassign(userID, groupID, token string) errors.SDKError { - policy := Policy{ - Subject: userID, - Object: groupID, - } - - return sdk.DeleteUserPolicy(policy, token) -} - -func (sdk mfSDK) Connect(conn Connection, token string) errors.SDKError { - data, err := json.Marshal(conn) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.thingsURL, connectEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) - - return sdkerr -} - -func (sdk mfSDK) Disconnect(connIDs Connection, token string) errors.SDKError { - data, err := json.Marshal(connIDs) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.thingsURL, disconnectEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) - - return sdkerr -} - -func (sdk mfSDK) ConnectThing(thingID, channelID, token string) errors.SDKError { - policy := Policy{ - Subject: thingID, - Object: channelID, - } - - return sdk.CreateThingPolicy(policy, token) -} - -func (sdk mfSDK) DisconnectThing(thingID, channelID, token string) errors.SDKError { - policy := Policy{ - Subject: thingID, - Object: channelID, - } - - return sdk.DeleteThingPolicy(policy, token) -} - -func (sdk mfSDK) AuthorizeThing(accessReq AccessRequest, token string) (bool, string, errors.SDKError) { - data, err := json.Marshal(accessReq) - if err != nil { - return false, "", errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, channelsEndpoint, accessReq.Object, accessEndpoint) - - _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return false, "", sdkerr - } - resp := canAccessRes{} - if err := json.Unmarshal(body, &resp); err != nil { - return false, "", errors.NewSDKError(err) - } - - return resp.Authorized, resp.ThingID, nil -} diff --git a/pkg/sdk/go/policies_test.go b/pkg/sdk/go/policies_test.go index ace5472969..c5e1a4c753 100644 --- a/pkg/sdk/go/policies_test.go +++ b/pkg/sdk/go/policies_test.go @@ -1091,3 +1091,4 @@ package sdk_test // assert.True(t, ok, "Delete was not called on invalid policy") // repoCall.Unset() // } + diff --git a/pkg/sdk/go/requests.go b/pkg/sdk/go/requests.go index 918fbd8920..0a89d741f0 100644 --- a/pkg/sdk/go/requests.go +++ b/pkg/sdk/go/requests.go @@ -48,3 +48,41 @@ type tokenReq struct { Identity string `json:"identity"` Secret string `json:"secret"` } + +type shareThingReq struct { + Relation string `json:"relation"` + UserIDs []string `json:"user_ids"` +} + +type unshareThingReq struct { + Relation string `json:"relation"` + UserIDs []string `json:"user_ids"` +} + +type addUsersToChannelReq struct { + Relation string `json:"relation"` + UserIDs []string `json:"user_ids"` +} + +type removeUsersFromChannelReq struct { + Relation string `json:"relation"` + UserIDs []string `json:"user_ids"` +} + +type addUserGroupsToChannelReq struct { + UserGroupIDs []string `json:"group_ids"` +} + +type removeUserGroupsFromChannelReq struct { + UserGroupIDs []string `json:"group_ids"` +} + +type addUsersToGroupReq struct { + Relation string `json:"relation"` + UserIDs []string `json:"user_ids"` +} + +type removeUsersToGroupReq struct { + Relation string `json:"relation"` + UserIDs []string `json:"user_ids"` +} diff --git a/pkg/sdk/go/responses.go b/pkg/sdk/go/responses.go index 05c7001fd5..d88d2c63cf 100644 --- a/pkg/sdk/go/responses.go +++ b/pkg/sdk/go/responses.go @@ -63,13 +63,6 @@ type MembershipsPage struct { Memberships []Group `json:"memberships"` } -// PolicyPage contains page related metadata as well as list -// of Policies that belong to the page. -type PolicyPage struct { - PageMetadata - Policies []Policy `json:"policies"` -} - type revokeCertsRes struct { RevocationTime time.Time `json:"revocation_time"` } diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go index 5a85e5cd7f..63331b471a 100644 --- a/pkg/sdk/go/sdk.go +++ b/pkg/sdk/go/sdk.go @@ -82,6 +82,7 @@ type PageMetadata struct { Action string `json:"action,omitempty"` Subject string `json:"subject,omitempty"` Object string `json:"object,omitempty"` + Permission string `json:"permission,omitempty"` Tag string `json:"tag,omitempty"` Owner string `json:"owner,omitempty"` SharedBy string `json:"shared_by,omitempty"` @@ -135,17 +136,6 @@ type SDK interface { // fmt.Println(users) Users(pm PageMetadata, token string) (UsersPage, errors.SDKError) - // Members retrieves everything that is assigned to a group identified by groupID. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // } - // members, _ := sdk.Members("groupID", pm, "token") - // fmt.Println(members) - Members(groupID string, meta PageMetadata, token string) (MembersPage, errors.SDKError) - // UserProfile returns user logged in. // // example: @@ -257,6 +247,11 @@ type SDK interface { // fmt.Println(token) RefreshToken(token string) (Token, errors.SDKError) + ListUserChannels(userID string, pm PageMetadata, token string) (ChannelsPage, errors.SDKError) + + ListUserGroups(userID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) + + ListUserThings(userID string, pm PageMetadata, token string) (ThingsPage, errors.SDKError) // CreateThing registers new thing and returns its id. // // example: @@ -386,17 +381,21 @@ type SDK interface { // fmt.Println(id) IdentifyThing(key string) (string, errors.SDKError) - // ShareThing shares thing with other user. It assumes that you have - // already created a group and added things to it. It also assumes that - // you have required policy to share a thing with the specified user. + // ShareThing shares thing with other users. // - // The `ShareThing` method calls the `Connect` method with the - // subject as `userID` rather than `thingID`. + // example: + // err := sdk.ShareThing("thingID", "token", shareThingReq{Relation: "viewer" , UserIDs: ["user_id_1","user_id_2","user_id_3"] }) + // fmt.Println(err) + ShareThing(thingID, token string, req shareThingReq) errors.SDKError + + // UnshareThing unshare a thing with other users. // // example: - // err := sdk.ShareThing("channelID", "userID", []string{"c_list", "c_delete"}, "token") + // err := sdk.UnshareThing("thingID", "token", shareThingReq{Relation: "viewer" , UserIDs: ["user_id_1","user_id_2","user_id_3"] }) // fmt.Println(err) - ShareThing(channelID, userID string, actions []string, token string) errors.SDKError + UnshareThing(thingID, token string, req unshareThingReq) errors.SDKError + + ListThingUsers(token, thingID string) (UsersPage, errors.SDKError) // CreateGroup creates new group and returns its id. // @@ -411,18 +410,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 +481,13 @@ type SDK interface { // fmt.Println(group) DisableGroup(id, token string) (Group, errors.SDKError) + AddUsersToGroup(groupID string, req addUsersToGroupReq, token string) errors.SDKError + + RemoveUsersToGroup(groupID string, req removeUsersToGroupReq, token string) errors.SDKError + + ListGroupUsers(groupID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) + + ListGroupChannels(groupID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) // CreateChannel creates new channel and returns its id. // // example: @@ -587,149 +581,17 @@ type SDK interface { // fmt.Println(channel) DisableChannel(id, token string) (Channel, errors.SDKError) - // CreateUserPolicy creates a policy for the given subject, so that, after - // CreateUserPolicy, `subject` has a `relation` on `object`. Returns a non-nil - // error in case of failures. - // - // The subject in this case is the `userID` and the object is the `groupID`. - // - // example: - // policy := sdk.Policy{ - // Subject: "userID:1", - // Object: "groupID:1", - // Actions: []string{"g_add"}, - // } - // err := sdk.CreateUserPolicy(policy, "token") - // fmt.Println(err) - CreateUserPolicy(policy Policy, token string) errors.SDKError + AddUsersToChannel(token, channelID string, req addUsersToChannelReq) 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`. + RemoveUsersFromChannel(token, channelID string, req removeUsersFromChannelReq) errors.SDKError - // 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 + ListChannelUsers(channelID string, pm PageMetadata, token string) (ChannelsPage, errors.SDKError) - // ListUserPolicies lists policies based on the given policy structure. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Subject: "userID:1", - // } - // policies, _ := sdk.ListUserPolicies(pm, "token") - // fmt.Println(policies) - ListUserPolicies(pm PageMetadata, token string) (PolicyPage, errors.SDKError) + AddUserGroupsToChannel(token, channelID string, req addUserGroupsToChannelReq) errors.SDKError - // DeleteUserPolicy deletes policies. - // - // The subject in this case is the `userID` and the object is the `groupID`. - // - // example: - // policy := sdk.Policy{ - // Subject: "userID:1", - // Object: "groupID:1", - // } - // err := sdk.DeleteUserPolicy(policy, "token") - // fmt.Println(err) - DeleteUserPolicy(policy Policy, token string) errors.SDKError - - // CreateThingPolicy creates a policy for the given subject, so that, after - // CreateThingPolicy, `subject` has a `relation` on `object`. Returns a non-nil - // error in case of failures. - // - // The subject in this case can be a `thingID` or a `userID` and the object is the `channelID`. - // - // example: - // policy := sdk.Policy{ - // Subject: "thingID:1", - // Object: "channelID:1", - // Actions: []string{"m_write"}, - // } - // err := sdk.CreateThingPolicy(policy, "token") - // fmt.Println(err) - CreateThingPolicy(policy Policy, token string) errors.SDKError - - // UpdateThingPolicy updates policies based on the given policy structure. - // - // The subject in this case can be a `thingID` or a `userID` and the object is the `channelID`. - // - // 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 - - // ListThingPolicies lists policies based on the given policy structure. - // - // 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 + RemoveUserGroupsToChannel(token, channelID string, req removeUserGroupsFromChannelReq) errors.SDKError + ListChannelUserGroups(channelID string, pm PageMetadata, token string) (ChannelsPage, errors.SDKError) // Connect bulk connects things to channels specified by id. // // example: @@ -771,19 +633,6 @@ type SDK interface { // fmt.Println(err) DisconnectThing(thingID, chanID, token string) errors.SDKError - // AuthorizeThing returns true if the given policy structure allows the action. - // - // example: - // aReq := sdk.AccessRequest{ - // Subject: "thingID", - // Object: "channelID", - // Actions: "m_read", - // EntityType: "things", - // } - // ok, _ := sdk.AuthorizeThing(aReq "token") - // fmt.Println(ok) - AuthorizeThing(accessReq AccessRequest, token string) (bool, string, errors.SDKError) - // SendMessage send message to specified channel. // // example: diff --git a/pkg/sdk/go/things.go b/pkg/sdk/go/things.go index 99307ec58a..6ead5b66ca 100644 --- a/pkg/sdk/go/things.go +++ b/pkg/sdk/go/things.go @@ -17,6 +17,8 @@ const ( connectEndpoint = "connect" disconnectEndpoint = "disconnect" identifyEndpoint = "identify" + shareEndpoint = "share" + unshareEndpoint = "unshare" ) // Thing represents mainflux thing. @@ -254,13 +256,41 @@ 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, token string, req shareThingReq) errors.SDKError { + data, err := json.Marshal(req) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, thingsEndpoint, thingID, unshareEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) + return sdkerr +} + +func (sdk mfSDK) UnshareThing(thingID, token string, req unshareThingReq) 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(token, thingID string) (UsersPage, errors.SDKError) { + url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, thingsEndpoint, thingID, usersEndpoint) + + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return UsersPage{}, sdkerr + } + up := UsersPage{} + if err := json.Unmarshal(body, &up); err != nil { + return UsersPage{}, errors.NewSDKError(err) } - return sdk.CreateThingPolicy(policy, token) + return up, nil } diff --git a/pkg/sdk/go/users.go b/pkg/sdk/go/users.go index f39460f546..35b48cee2a 100644 --- a/pkg/sdk/go/users.go +++ b/pkg/sdk/go/users.go @@ -267,6 +267,58 @@ func (sdk mfSDK) UpdateUserOwner(user User, token string) (User, errors.SDKError return user, nil } +func (sdk mfSDK) ListUserChannels(userID string, pm PageMetadata, token string) (ChannelsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s", usersEndpoint, userID, channelsEndpoint), pm) + if err != nil { + return ChannelsPage{}, errors.NewSDKError(err) + } + + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return ChannelsPage{}, sdkerr + } + cp := ChannelsPage{} + if err := json.Unmarshal(body, &cp); err != nil { + return ChannelsPage{}, errors.NewSDKError(err) + } + + return cp, nil +} + +func (sdk mfSDK) ListUserGroups(userID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s", usersEndpoint, userID, groupsEndpoint), pm) + if err != nil { + return GroupsPage{}, errors.NewSDKError(err) + } + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return GroupsPage{}, sdkerr + } + gp := GroupsPage{} + if err := json.Unmarshal(body, &gp); err != nil { + return GroupsPage{}, errors.NewSDKError(err) + } + + return gp, nil +} + +func (sdk mfSDK) ListUserThings(userID string, pm PageMetadata, token string) (ThingsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s", usersEndpoint, userID, thingsEndpoint), pm) + if err != nil { + return ThingsPage{}, errors.NewSDKError(err) + } + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return ThingsPage{}, sdkerr + } + tp := ThingsPage{} + if err := json.Unmarshal(body, &tp); err != nil { + return ThingsPage{}, errors.NewSDKError(err) + } + + return tp, nil +} + func (sdk mfSDK) EnableUser(id, token string) (User, errors.SDKError) { return sdk.changeClientStatus(token, id, enableEndpoint) } diff --git a/things/api/http/channels.go b/things/api/http/channels.go index 20a4d02a90..ac56496b6b 100644 --- a/things/api/http/channels.go +++ b/things/api/http/channels.go @@ -67,26 +67,6 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha opts..., ), "disable_channel").ServeHTTP) - // Instead of having this endpoint /channels/{groupID}/assign separately, - // we can have two separate endpoints for each member kind - // users (/channels/{groupID}/users) & user_groups (/channels/{groupID}/groups) - r.Post("/{groupID}/assign", otelhttp.NewHandler(kithttp.NewServer( - assignUsersGroupsEndpoint(svc), - decodeAssignUsersGroupsRequest, - api.EncodeResponse, - opts..., - ), "assign_members").ServeHTTP) - - // Instead of having this endpoint /channels/{groupID}/unassign separately, - // we can have two separate endpoints for each member kind - // users (/channels/{groupID}/users) & user_groups (/channels/{groupID}/groups) - r.Post("/{groupID}/unassign", otelhttp.NewHandler(kithttp.NewServer( - unassignUsersGroupsEndpoint(svc), - decodeUnassignUsersGroupsRequest, - api.EncodeResponse, - opts..., - ), "unassign_members").ServeHTTP) - // Request to add users to a channel // This endpoint can be used alternative to /channels/{groupID}/members r.Post("/{groupID}/users/assign", otelhttp.NewHandler(kithttp.NewServer( @@ -192,38 +172,6 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha return r } -func decodeAssignUsersGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := assignUsersGroupsRequest{ - token: apiutil.ExtractBearerToken(r), - groupID: chi.URLParam(r, "groupID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - - return req, nil -} - -func decodeUnassignUsersGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := unassignUsersGroupsRequest{ - token: apiutil.ExtractBearerToken(r), - groupID: chi.URLParam(r, "groupID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - - return req, nil -} - func decodeAssignUsersRequest(_ context.Context, r *http.Request) (interface{}, error) { if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) diff --git a/things/api/http/clients.go b/things/api/http/clients.go index e69de29bb2..c05da3ad2c 100644 --- a/things/api/http/clients.go +++ b/things/api/http/clients.go @@ -0,0 +1,373 @@ +// Copyright (c) Mainflux +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "context" + "encoding/json" + "net/http" + "strings" + + "github.com/go-chi/chi/v5" + kithttp "github.com/go-kit/kit/transport/http" + "github.com/mainflux/mainflux/internal/api" + "github.com/mainflux/mainflux/internal/apiutil" + mflog "github.com/mainflux/mainflux/logger" + mfclients "github.com/mainflux/mainflux/pkg/clients" + "github.com/mainflux/mainflux/pkg/errors" + "github.com/mainflux/mainflux/things" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" +) + +func clientsHandler(svc things.Service, r *chi.Mux, logger mflog.Logger) http.Handler { + opts := []kithttp.ServerOption{ + kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), + } + r.Route("/things", func(r chi.Router) { + r.Post("/", otelhttp.NewHandler(kithttp.NewServer( + createClientEndpoint(svc), + decodeCreateClientReq, + api.EncodeResponse, + opts..., + ), "create_thing").ServeHTTP) + + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + listClientsEndpoint(svc), + decodeListClients, + api.EncodeResponse, + opts..., + ), "list_things").ServeHTTP) + + r.Post("/bulk", otelhttp.NewHandler(kithttp.NewServer( + createClientsEndpoint(svc), + decodeCreateClientsReq, + api.EncodeResponse, + opts..., + ), "create_things").ServeHTTP) + + r.Get("/{thingID}", otelhttp.NewHandler(kithttp.NewServer( + viewClientEndpoint(svc), + decodeViewClient, + api.EncodeResponse, + opts..., + ), "view_thing").ServeHTTP) + + r.Patch("/{thingID}", otelhttp.NewHandler(kithttp.NewServer( + updateClientEndpoint(svc), + decodeUpdateClient, + api.EncodeResponse, + opts..., + ), "update_thing").ServeHTTP) + + r.Patch("/{thingID}/tags", otelhttp.NewHandler(kithttp.NewServer( + updateClientTagsEndpoint(svc), + decodeUpdateClientTags, + api.EncodeResponse, + opts..., + ), "update_thing_tags").ServeHTTP) + + r.Patch("/{thingID}/secret", otelhttp.NewHandler(kithttp.NewServer( + updateClientSecretEndpoint(svc), + decodeUpdateClientCredentials, + api.EncodeResponse, + opts..., + ), "update_thing_credentials").ServeHTTP) + + r.Patch("/{thingID}/owner", otelhttp.NewHandler(kithttp.NewServer( + updateClientOwnerEndpoint(svc), + decodeUpdateClientOwner, + api.EncodeResponse, + opts..., + ), "update_thing_owner").ServeHTTP) + + r.Post("/{thingID}/enable", otelhttp.NewHandler(kithttp.NewServer( + enableClientEndpoint(svc), + decodeChangeClientStatus, + api.EncodeResponse, + opts..., + ), "enable_thing").ServeHTTP) + + r.Post("/{thingID}/disable", otelhttp.NewHandler(kithttp.NewServer( + disableClientEndpoint(svc), + decodeChangeClientStatus, + api.EncodeResponse, + opts..., + ), "disable_thing").ServeHTTP) + + r.Post("/{thingID}/share", otelhttp.NewHandler(kithttp.NewServer( + thingShareEndpoint(svc), + decodeThingShareRequest, + api.EncodeResponse, + opts..., + ), "thing_share").ServeHTTP) + + r.Post("/{thingID}/unshare", otelhttp.NewHandler(kithttp.NewServer( + thingUnshareEndpoint(svc), + decodeThingUnshareRequest, + api.EncodeResponse, + opts..., + ), "thing_delete_share").ServeHTTP) + + }) + + // Ideal location: things service, channels endpoint + // Reason for placing here : + // SpiceDB provides list of thing ids present in given channel id + // and things service can access spiceDB and get the list of thing ids present in given channel id. + // Request to get list of things present in channelID ({groupID}) . + r.Get("/channels/{groupID}/things", otelhttp.NewHandler(kithttp.NewServer( + listMembersEndpoint(svc), + decodeListMembersRequest, + api.EncodeResponse, + 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 +} + +func decodeViewClient(_ context.Context, r *http.Request) (interface{}, error) { + req := viewClientReq{ + token: apiutil.ExtractBearerToken(r), + id: chi.URLParam(r, "thingID"), + } + + return req, nil +} + +func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) { + var ownerID string + s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + 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) + } + 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, err + } + + p, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + if oid != "" { + ownerID = oid + } + st, err := mfclients.ToStatus(s) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + req := listClientsReq{ + token: apiutil.ExtractBearerToken(r), + status: st, + offset: o, + limit: l, + metadata: m, + name: n, + tag: t, + permission: p, + userID: chi.URLParam(r, "userID"), + owner: ownerID, + } + return req, nil +} + +func decodeUpdateClient(_ 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 := updateClientReq{ + token: apiutil.ExtractBearerToken(r), + id: chi.URLParam(r, "thingID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeUpdateClientTags(_ 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 := updateClientTagsReq{ + token: apiutil.ExtractBearerToken(r), + id: chi.URLParam(r, "thingID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeUpdateClientCredentials(_ 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 := updateClientCredentialsReq{ + token: apiutil.ExtractBearerToken(r), + id: chi.URLParam(r, "thingID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeUpdateClientOwner(_ 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 := updateClientOwnerReq{ + token: apiutil.ExtractBearerToken(r), + id: chi.URLParam(r, "thingID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeCreateClientReq(_ 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) + } + + var c mfclients.Client + if err := json.NewDecoder(r.Body).Decode(&c); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + req := createClientReq{ + client: c, + token: apiutil.ExtractBearerToken(r), + } + + return req, nil +} + +func decodeCreateClientsReq(_ 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) + } + + c := createClientsReq{token: apiutil.ExtractBearerToken(r)} + if err := json.NewDecoder(r.Body).Decode(&c.Clients); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return c, nil +} + +func decodeChangeClientStatus(_ context.Context, r *http.Request) (interface{}, error) { + req := changeClientStatusReq{ + token: apiutil.ExtractBearerToken(r), + id: chi.URLParam(r, "thingID"), + } + + return req, nil +} + +func decodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { + s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + 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) + } + 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, + Permission: p, + Metadata: m, + }, + groupID: chi.URLParam(r, "groupID"), + } + return req, nil +} + +func decodeThingShareRequest(_ 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 := thingShareRequest{ + token: apiutil.ExtractBearerToken(r), + thingID: chi.URLParam(r, "thingID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeThingUnshareRequest(_ 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 := thingUnshareRequest{ + token: apiutil.ExtractBearerToken(r), + thingID: chi.URLParam(r, "thingID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} diff --git a/things/api/http/endpoints.go b/things/api/http/endpoints.go index e69de29bb2..d0b1350867 100644 --- a/things/api/http/endpoints.go +++ b/things/api/http/endpoints.go @@ -0,0 +1,405 @@ +// Copyright (c) Mainflux +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "context" + + "github.com/go-kit/kit/endpoint" + "github.com/mainflux/mainflux/internal/apiutil" + mfclients "github.com/mainflux/mainflux/pkg/clients" + "github.com/mainflux/mainflux/pkg/errors" + "github.com/mainflux/mainflux/pkg/groups" + "github.com/mainflux/mainflux/things" +) + +func createClientEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(createClientReq) + if err := req.validate(); err != nil { + return createClientRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + + client, err := svc.CreateThings(ctx, req.token, req.client) + if err != nil { + return createClientRes{}, err + } + + return createClientRes{ + Client: client[0], + created: true, + }, nil + } +} + +func createClientsEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(createClientsReq) + if err := req.validate(); err != nil { + return clientsPageRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + + page, err := svc.CreateThings(ctx, req.token, req.Clients...) + if err != nil { + return clientsPageRes{}, err + } + + res := clientsPageRes{ + pageRes: pageRes{ + Total: uint64(len(page)), + }, + Clients: []viewClientRes{}, + } + for _, c := range page { + res.Clients = append(res.Clients, viewClientRes{Client: c}) + } + + return res, nil + } +} + +func viewClientEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(viewClientReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + c, err := svc.ViewClient(ctx, req.token, req.id) + if err != nil { + return nil, err + } + + return viewClientRes{Client: c}, nil + } +} + +func listClientsEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listClientsReq) + if err := req.validate(); err != nil { + return mfclients.ClientsPage{}, errors.Wrap(apiutil.ErrValidation, err) + } + + pm := mfclients.Page{ + Status: req.status, + Offset: req.offset, + Limit: req.limit, + Owner: req.owner, + Name: req.name, + Tag: req.tag, + Permission: req.permission, + Metadata: req.metadata, + } + page, err := svc.ListClients(ctx, req.token, req.userID, pm) + if err != nil { + return mfclients.ClientsPage{}, err + } + + res := clientsPageRes{ + pageRes: pageRes{ + Total: page.Total, + Offset: page.Offset, + Limit: page.Limit, + }, + Clients: []viewClientRes{}, + } + for _, c := range page.Clients { + res.Clients = append(res.Clients, viewClientRes{Client: c}) + } + + return res, nil + } +} + +func listMembersEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listMembersReq) + if err := req.validate(); err != nil { + return memberPageRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + + page, err := svc.ListClientsByGroup(ctx, req.token, req.groupID, req.Page) + if err != nil { + return memberPageRes{}, err + } + + return buildMembersResponse(page), nil + } +} + +func updateClientEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(updateClientReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + cli := mfclients.Client{ + ID: req.id, + Name: req.Name, + Metadata: req.Metadata, + } + client, err := svc.UpdateClient(ctx, req.token, cli) + if err != nil { + return nil, err + } + + return updateClientRes{Client: client}, nil + } +} + +func updateClientTagsEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(updateClientTagsReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + cli := mfclients.Client{ + ID: req.id, + Tags: req.Tags, + } + client, err := svc.UpdateClientTags(ctx, req.token, cli) + if err != nil { + return nil, err + } + + return updateClientRes{Client: client}, nil + } +} + +func updateClientSecretEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(updateClientCredentialsReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + client, err := svc.UpdateClientSecret(ctx, req.token, req.id, req.Secret) + if err != nil { + return nil, err + } + + return updateClientRes{Client: client}, nil + } +} + +func updateClientOwnerEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(updateClientOwnerReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + cli := mfclients.Client{ + ID: req.id, + Owner: req.Owner, + } + client, err := svc.UpdateClientOwner(ctx, req.token, cli) + if err != nil { + return nil, err + } + + return updateClientRes{Client: client}, nil + } +} + +func enableClientEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(changeClientStatusReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + client, err := svc.EnableClient(ctx, req.token, req.id) + if err != nil { + return nil, err + } + + return deleteClientRes{Client: client}, nil + } +} + +func disableClientEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(changeClientStatusReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + client, err := svc.DisableClient(ctx, req.token, req.id) + if err != nil { + return nil, err + } + + return deleteClientRes{Client: client}, nil + } +} + +func buildMembersResponse(cp mfclients.MembersPage) memberPageRes { + res := memberPageRes{ + pageRes: pageRes{ + Total: cp.Total, + Offset: cp.Offset, + Limit: cp.Limit, + }, + Members: []viewMembersRes{}, + } + for _, c := range cp.Members { + res.Members = append(res.Members, viewMembersRes{Client: c}) + } + + return res +} + +func assignUsersEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(assignUsersRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + if err := svc.Assign(ctx, req.token, req.groupID, req.Relation, "users", req.UserIDs...); err != nil { + return nil, err + } + + return assignUsersRes{}, nil + } +} + +func unassignUsersEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(unassignUsersRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + if err := svc.Unassign(ctx, req.token, req.groupID, req.Relation, "users", req.UserIDs...); err != nil { + return nil, err + } + + return unassignUsersRes{}, nil + } +} + +func assignUserGroupsEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(assignUserGroupsRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + if err := svc.Assign(ctx, req.token, req.groupID, "parent_group", "groups", req.UserGroupIDs...); err != nil { + return nil, err + } + + return assignUserGroupsRes{}, nil + } +} + +func unassignUserGroupsEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(unassignUserGroupsRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + if err := svc.Unassign(ctx, req.token, req.groupID, "parent_group", "groups", req.UserGroupIDs...); err != nil { + return nil, err + } + + return unassignUserGroupsRes{}, nil + } +} + +func connectChannelThingEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(connectChannelThingRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + if err := svc.Assign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { + return nil, err + } + + return connectChannelThingRes{}, nil + } +} + +func disconnectChannelThingEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(disconnectChannelThingRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + if err := svc.Unassign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { + return nil, err + } + + return disconnectChannelThingRes{}, nil + } +} + +func connectEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(connectChannelThingRequest) + if err := req.validate(); err != nil { + return connectChannelThingRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + + if err := svc.Assign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { + return connectChannelThingRes{}, err + } + + return connectChannelThingRes{}, nil + } +} + +func disconnectEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(disconnectChannelThingRequest) + if err := req.validate(); err != nil { + return disconnectChannelThingRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + + if err := svc.Unassign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { + return disconnectChannelThingRes{}, err + } + + return disconnectChannelThingRes{}, nil + } +} + +func thingShareEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(thingShareRequest) + if err := req.validate(); err != nil { + return thingShareRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + + if err := svc.Share(ctx, req.token, req.thingID, req.Relation, req.UserIDs...); err != nil { + return thingShareRes{}, err + } + + return thingShareRes{}, nil + } +} + +func thingUnshareEndpoint(svc things.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(thingUnshareRequest) + if err := req.validate(); err != nil { + return thingUnshareRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + + if err := svc.Unshare(ctx, req.token, req.thingID, req.Relation, req.UserIDs...); err != nil { + return thingShareRes{}, err + } + + return thingUnshareRes{}, nil + } +} diff --git a/things/api/http/requests.go b/things/api/http/requests.go index 0546bf8e12..4b4e8e4425 100644 --- a/things/api/http/requests.go +++ b/things/api/http/requests.go @@ -75,6 +75,7 @@ type listClientsReq struct { owner string permission string visibility string + userID string metadata mfclients.Metadata } diff --git a/things/api/logging.go b/things/api/logging.go index 09dae54de8..65f172473a 100644 --- a/things/api/logging.go +++ b/things/api/logging.go @@ -49,7 +49,7 @@ func (lm *loggingMiddleware) ViewClient(ctx context.Context, token, id string) ( return lm.svc.ViewClient(ctx, token, id) } -func (lm *loggingMiddleware) ListClients(ctx context.Context, token string, pm mfclients.Page) (cp mfclients.ClientsPage, err error) { +func (lm *loggingMiddleware) ListClients(ctx context.Context, token string, reqUserID string, pm mfclients.Page) (cp mfclients.ClientsPage, err error) { defer func(begin time.Time) { message := fmt.Sprintf("Method list_things using token %s took %s to complete", token, time.Since(begin)) if err != nil { @@ -58,7 +58,7 @@ func (lm *loggingMiddleware) ListClients(ctx context.Context, token string, pm m } lm.logger.Info(fmt.Sprintf("%s without errors.", message)) }(time.Now()) - return lm.svc.ListClients(ctx, token, pm) + return lm.svc.ListClients(ctx, token, reqUserID, pm) } func (lm *loggingMiddleware) UpdateClient(ctx context.Context, token string, client mfclients.Client) (c mfclients.Client, err error) { diff --git a/things/api/metrics.go b/things/api/metrics.go index 91f9ab2d9e..f20c09d8bd 100644 --- a/things/api/metrics.go +++ b/things/api/metrics.go @@ -46,12 +46,12 @@ func (ms *metricsMiddleware) ViewClient(ctx context.Context, token, id string) ( return ms.svc.ViewClient(ctx, token, id) } -func (ms *metricsMiddleware) ListClients(ctx context.Context, token string, pm mfclients.Page) (mfclients.ClientsPage, error) { +func (ms *metricsMiddleware) ListClients(ctx context.Context, token string, reqUserID string, pm mfclients.Page) (mfclients.ClientsPage, error) { defer func(begin time.Time) { ms.counter.With("method", "list_things").Add(1) ms.latency.With("method", "list_things").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.ListClients(ctx, token, pm) + return ms.svc.ListClients(ctx, token, reqUserID, pm) } func (ms *metricsMiddleware) UpdateClient(ctx context.Context, token string, client mfclients.Client) (mfclients.Client, error) { diff --git a/things/events/events.go b/things/events/events.go index 1aea40b79b..5077ba4914 100644 --- a/things/events/events.go +++ b/things/events/events.go @@ -188,12 +188,14 @@ func (vce viewClientEvent) Encode() (map[string]interface{}, error) { } type listClientEvent struct { + reqUserID string mfclients.Page } func (lce listClientEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ "operation": clientList, + "reqUserID": lce.reqUserID, "total": lce.Total, "offset": lce.Offset, "limit": lce.Limit, diff --git a/things/events/streams.go b/things/events/streams.go index 5521a6e589..a7db1e612c 100644 --- a/things/events/streams.go +++ b/things/events/streams.go @@ -118,12 +118,13 @@ func (es *eventStore) ViewClient(ctx context.Context, token, id string) (mfclien return cli, nil } -func (es *eventStore) ListClients(ctx context.Context, token string, pm mfclients.Page) (mfclients.ClientsPage, error) { - cp, err := es.svc.ListClients(ctx, token, pm) +func (es *eventStore) ListClients(ctx context.Context, token string, reqUserID string, pm mfclients.Page) (mfclients.ClientsPage, error) { + cp, err := es.svc.ListClients(ctx, token, reqUserID, pm) if err != nil { return cp, err } event := listClientEvent{ + reqUserID, pm, } if err := es.Publish(ctx, event); err != nil { diff --git a/things/service.go b/things/service.go index 6f3069fece..e12037f183 100644 --- a/things/service.go +++ b/things/service.go @@ -133,27 +133,77 @@ func (svc service) ViewClient(ctx context.Context, token string, id string) (mfc return svc.clients.RetrieveByID(ctx, id) } -func (svc service) ListClients(ctx context.Context, token string, pm mfclients.Page) (mfclients.ClientsPage, error) { +func (svc service) ListClients(ctx context.Context, token string, reqUserID string, pm mfclients.Page) (mfclients.ClientsPage, error) { + var ids []string + userID, err := svc.identify(ctx, token) if err != nil { return mfclients.ClientsPage{}, err } + switch { + case (reqUserID != "" && reqUserID != userID): + if _, err := svc.authorize(ctx, userType, tokenKind, userID, ownerPermission, userType, reqUserID); err != nil { + return mfclients.ClientsPage{}, err + } + rtids, err := svc.listClientIDs(ctx, reqUserID, pm.Permission) + if err != nil { + return mfclients.ClientsPage{}, err + } + ids, err = svc.filterAllowedThingIDs(ctx, userID, pm.Permission, rtids) + if err != nil { + return mfclients.ClientsPage{}, err + } + default: + ids, err = svc.listClientIDs(ctx, userID, pm.Permission) + if err != nil { + return mfclients.ClientsPage{}, err + } + } + + if len(ids) <= 0 { + return mfclients.ClientsPage{}, errors.ErrNotFound + } + + pm.IDs = ids + + return svc.clients.RetrieveAllByIDs(ctx, pm) +} + +func (svc service) listClientIDs(ctx context.Context, userID, permission string) ([]string, error) { + tids, err := svc.auth.ListAllObjects(ctx, &mainflux.ListObjectsReq{ SubjectType: userType, Subject: userID, - Permission: pm.Permission, + Permission: permission, ObjectType: thingType, }) if err != nil { - return mfclients.ClientsPage{}, err + return nil, err } - - pm.IDs = tids.Policies - - return svc.clients.RetrieveAllByIDs(ctx, pm) + return tids.Policies, nil } +func (svc service) filterAllowedThingIDs(ctx context.Context, userID, permission string, thingIDs []string) ([]string, error) { + var ids []string + tids, err := svc.auth.ListAllObjects(ctx, &mainflux.ListObjectsReq{ + SubjectType: userType, + Subject: userID, + Permission: permission, + ObjectType: thingType, + }) + if err != nil { + return nil, err + } + for _, thingID := range thingIDs { + for _, tid := range tids.Policies { + if thingID == tid { + ids = append(ids, thingID) + } + } + } + return ids, nil +} func (svc service) UpdateClient(ctx context.Context, token string, cli mfclients.Client) (mfclients.Client, error) { userID, err := svc.authorize(ctx, userType, tokenKind, token, editPermission, thingType, cli.ID) if err != nil { diff --git a/things/service_test.go b/things/service_test.go index 81ea98cf00..041d2295f8 100644 --- a/things/service_test.go +++ b/things/service_test.go @@ -591,7 +591,7 @@ func TestListClients(t *testing.T) { repoCall1 = auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&mainflux.ListObjectsRes{}, errors.ErrAuthorization) } repoCall2 := cRepo.On("RetrieveAllByIDs", context.Background(), mock.Anything).Return(tc.response, tc.err) - page, err := svc.ListClients(context.Background(), tc.token, tc.page) + page, err := svc.ListClients(context.Background(), tc.token, "", tc.page) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) repoCall.Unset() @@ -949,7 +949,7 @@ func TestEnableClient(t *testing.T) { repoCall := auth.On("Identify", mock.Anything, &mainflux.IdentityReq{Token: validToken}).Return(&mainflux.IdentityRes{Id: validID}, nil) repoCall1 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&mainflux.ListObjectsRes{}, nil) repoCall2 := cRepo.On("RetrieveAllByIDs", context.Background(), mock.Anything).Return(tc.response, nil) - page, err := svc.ListClients(context.Background(), validToken, pm) + page, err := svc.ListClients(context.Background(), validToken, "", pm) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) size := uint64(len(page.Clients)) assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", tc.desc, tc.size, size)) @@ -1074,7 +1074,7 @@ func TestDisableClient(t *testing.T) { repoCall := auth.On("Identify", mock.Anything, &mainflux.IdentityReq{Token: validToken}).Return(&mainflux.IdentityRes{Id: validID}, nil) repoCall1 := auth.On("ListAllObjects", mock.Anything, mock.Anything).Return(&mainflux.ListObjectsRes{}, nil) repoCall2 := cRepo.On("RetrieveAllByIDs", context.Background(), mock.Anything).Return(tc.response, nil) - page, err := svc.ListClients(context.Background(), validToken, pm) + page, err := svc.ListClients(context.Background(), validToken, "", pm) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) size := uint64(len(page.Clients)) assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", tc.desc, tc.size, size)) diff --git a/things/things.go b/things/things.go index 0da5ec9aa4..2ad4700f1f 100644 --- a/things/things.go +++ b/things/things.go @@ -21,7 +21,7 @@ type Service interface { ViewClient(ctx context.Context, token, id string) (clients.Client, error) // ListClients retrieves clients list for a valid auth token. - ListClients(ctx context.Context, token string, pm clients.Page) (clients.ClientsPage, error) + ListClients(ctx context.Context, token string, reqUserID string, pm clients.Page) (clients.ClientsPage, error) // ListClientsByGroup retrieves data about subset of things that are // connected or not connected to specified channel and belong to the user identified by diff --git a/things/tracing/tracing.go b/things/tracing/tracing.go index b14bc2d363..b9b2023e60 100644 --- a/things/tracing/tracing.go +++ b/things/tracing/tracing.go @@ -41,10 +41,10 @@ func (tm *tracingMiddleware) ViewClient(ctx context.Context, token string, id st } // ListClients traces the "ListClients" operation of the wrapped policies.Service. -func (tm *tracingMiddleware) ListClients(ctx context.Context, token string, pm mfclients.Page) (mfclients.ClientsPage, error) { +func (tm *tracingMiddleware) ListClients(ctx context.Context, token string, reqUserID string, pm mfclients.Page) (mfclients.ClientsPage, error) { ctx, span := tm.tracer.Start(ctx, "svc_list_clients") defer span.End() - return tm.svc.ListClients(ctx, token, pm) + return tm.svc.ListClients(ctx, token, reqUserID, pm) } // UpdateClient traces the "UpdateClient" operation of the wrapped policies.Service. diff --git a/users/api/clients.go b/users/api/clients.go index 506ca10d5b..e7d4da5824 100644 --- a/users/api/clients.go +++ b/users/api/clients.go @@ -138,8 +138,8 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger mflog.Logger) http.Han // and users service can access spiceDB and get the user list with user_group_id. // Request to get list of users present in the user_group_id {groupID} r.Get("/groups/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( - listMembersEndpoint(svc), - decodeListMembersRequest, + listMembersByGroupEndpoint(svc), + decodeListMembersByGroup, api.EncodeResponse, opts..., ), "list_users_by_user_group_id").ServeHTTP) @@ -148,16 +148,20 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger mflog.Logger) http.Han // Reason for placing here : // SpiceDB provides list of user ids in given channel_id // and users service can access spiceDB and get the user list with channel_id. - // Request to get list of users present in the user_group_id {groupID} - // The ideal placeholder name should be {channelID}, but gapi.DecodeListGroupsRequest uses {groupID} as a placeholder for the ID. - // So here, we are using {groupID} as the placeholder. - r.Get("/channels/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( - listMembersEndpoint(svc), - decodeListMembersRequest, + // Request to get list of users present in the user_group_id {channelID} + r.Get("/channels/{channelID}/users", otelhttp.NewHandler(kithttp.NewServer( + listMembersByChannelEndpoint(svc), + decodeListMembersByChannel, api.EncodeResponse, opts..., ), "list_users_by_channel_id").ServeHTTP) + r.Get("/things/{thingID}/users", otelhttp.NewHandler(kithttp.NewServer( + listMembersByThingEndpoint(svc), + decodeListMembersByThing, + api.EncodeResponse, + opts..., + ), "list_users_by_thing_id").ServeHTTP) return r } @@ -398,62 +402,98 @@ func decodeChangeClientStatus(_ context.Context, r *http.Request) (interface{}, return req, nil } -func decodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { +func decodeListMembersByGroup(_ context.Context, r *http.Request) (interface{}, error) { + page, err := queryPageParams(r) + if err != nil { + return nil, err + } + req := listMembersByObjectReq{ + token: apiutil.ExtractBearerToken(r), + Page: page, + objectID: chi.URLParam(r, "groupID"), + } + + return req, nil +} + +func decodeListMembersByChannel(_ context.Context, r *http.Request) (interface{}, error) { + page, err := queryPageParams(r) + if err != nil { + return nil, err + } + req := listMembersByObjectReq{ + token: apiutil.ExtractBearerToken(r), + Page: page, + objectID: chi.URLParam(r, "channelID"), + } + + return req, nil +} + +func decodeListMembersByThing(_ context.Context, r *http.Request) (interface{}, error) { + page, err := queryPageParams(r) + if err != nil { + return nil, err + } + req := listMembersByObjectReq{ + token: apiutil.ExtractBearerToken(r), + Page: page, + objectID: chi.URLParam(r, "thingID"), + } + + return req, nil +} + +func queryPageParams(r *http.Request) (mfclients.Page, error) { s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } l, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } m, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } n, err := apiutil.ReadStringQuery(r, api.NameKey, "") if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } i, err := apiutil.ReadStringQuery(r, api.IdentityKey, "") if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } t, err := apiutil.ReadStringQuery(r, api.TagKey, "") if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } oid, err := apiutil.ReadStringQuery(r, api.OwnerKey, "") if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } st, err := mfclients.ToStatus(s) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) } p, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - req := listMembersReq{ - token: apiutil.ExtractBearerToken(r), - Page: mfclients.Page{ - Status: st, - Offset: o, - Limit: l, - Metadata: m, - Identity: i, - Name: n, - Tag: t, - Owner: oid, - Permission: p, - }, - groupID: chi.URLParam(r, "groupID"), - } - - return req, nil + return mfclients.Page{}, errors.Wrap(apiutil.ErrValidation, err) + } + return mfclients.Page{ + Status: st, + Offset: o, + Limit: l, + Metadata: m, + Identity: i, + Name: n, + Tag: t, + Owner: oid, + Permission: p, + }, nil } diff --git a/users/api/endpoints.go b/users/api/endpoints.go index f59f79aea5..369bdce036 100644 --- a/users/api/endpoints.go +++ b/users/api/endpoints.go @@ -102,14 +102,50 @@ func listClientsEndpoint(svc users.Service) endpoint.Endpoint { } } -func listMembersEndpoint(svc users.Service) endpoint.Endpoint { +func listMembersByGroupEndpoint(svc users.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersReq) + req := request.(listMembersByObjectReq) + req.objectKind = "groups" if err := req.validate(); err != nil { return memberPageRes{}, errors.Wrap(apiutil.ErrValidation, err) } - page, err := svc.ListMembers(ctx, req.token, req.groupID, req.Page) + page, err := svc.ListMembers(ctx, req.token, req.objectKind, req.objectID, req.Page) + if err != nil { + return memberPageRes{}, err + } + + return buildMembersResponse(page), nil + } +} + +func listMembersByChannelEndpoint(svc users.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listMembersByObjectReq) + // in spiceDB channel is group kind + req.objectKind = "groups" + if err := req.validate(); err != nil { + return memberPageRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + + page, err := svc.ListMembers(ctx, req.token, req.objectKind, req.objectID, req.Page) + if err != nil { + return memberPageRes{}, err + } + + return buildMembersResponse(page), nil + } +} + +func listMembersByThingEndpoint(svc users.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listMembersByObjectReq) + req.objectKind = "things" + if err := req.validate(); err != nil { + return memberPageRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + + page, err := svc.ListMembers(ctx, req.token, req.objectKind, req.objectID, req.Page) if err != nil { return memberPageRes{}, err } diff --git a/users/api/groups.go b/users/api/groups.go index 4bd82a0c98..42200e4151 100644 --- a/users/api/groups.go +++ b/users/api/groups.go @@ -87,28 +87,6 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha opts..., ), "disable_group").ServeHTTP) - // Instead of this endpoint /{groupID}/members separately, we can simply use /{groupID}/users - // because this group is intended exclusively for users. No other entity could not be added - // Instead of this endpoint /{groupID}/members 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", otelhttp.NewHandler(kithttp.NewServer( - gapi.AssignMembersEndpoint(svc, "", "users"), - gapi.DecodeAssignMembersRequest, - api.EncodeResponse, - opts..., - ), "assign_members").ServeHTTP) - - // Instead of maintaining this endpoint /{groupID}/members separately, we can simply use /{groupID}/users - // because this group is intended exclusively for users. No other entity could not be added - // Instead of maintaining this endpoint /{groupID}/members separately, we can simply use /{groupID}/users - // because this group is intended exclusively for users. No other entity could not be added - r.Delete("/{groupID}/members", otelhttp.NewHandler(kithttp.NewServer( - gapi.UnassignMembersEndpoint(svc, "", "users"), - gapi.DecodeUnassignMembersRequest, - api.EncodeResponse, - opts..., - ), "unassign_members").ServeHTTP) - r.Post("/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( assignUsersEndpoint(svc), decodeAssignUsersRequest, diff --git a/users/api/logging.go b/users/api/logging.go index fe7bf81a34..85d161fc34 100644 --- a/users/api/logging.go +++ b/users/api/logging.go @@ -252,16 +252,16 @@ func (lm *loggingMiddleware) DisableClient(ctx context.Context, token, id string // ListMembers logs the list_members request. It logs the group id, token and the time it took to complete the request. // If the request fails, it logs the error. -func (lm *loggingMiddleware) ListMembers(ctx context.Context, token, groupID string, cp mfclients.Page) (mp mfclients.MembersPage, err error) { +func (lm *loggingMiddleware) ListMembers(ctx context.Context, token, objectKind, objectID string, cp mfclients.Page) (mp mfclients.MembersPage, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method list_members %d members for group with id %s and token %s took %s to complete", mp.Total, groupID, token, time.Since(begin)) + message := fmt.Sprintf("Method list_members %d members for object kind %s and object id %s and token %s took %s to complete", mp.Total, objectKind, objectID, token, time.Since(begin)) if err != nil { lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) return } lm.logger.Info(fmt.Sprintf("%s without errors.", message)) }(time.Now()) - return lm.svc.ListMembers(ctx, token, groupID, cp) + return lm.svc.ListMembers(ctx, token, objectKind, objectID, cp) } // Identify logs the identify request. It logs the token and the time it took to complete the request. diff --git a/users/api/metrics.go b/users/api/metrics.go index b055215966..409c52046d 100644 --- a/users/api/metrics.go +++ b/users/api/metrics.go @@ -175,12 +175,12 @@ func (ms *metricsMiddleware) DisableClient(ctx context.Context, token string, id } // ListMembers instruments ListMembers method with metrics. -func (ms *metricsMiddleware) ListMembers(ctx context.Context, token, groupID string, pm mfclients.Page) (mp mfclients.MembersPage, err error) { +func (ms *metricsMiddleware) ListMembers(ctx context.Context, token, objectKind, objectID string, pm mfclients.Page) (mp mfclients.MembersPage, err error) { defer func(begin time.Time) { ms.counter.With("method", "list_members").Add(1) ms.latency.With("method", "list_members").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.ListMembers(ctx, token, groupID, pm) + return ms.svc.ListMembers(ctx, token, objectKind, objectID, pm) } // Identify instruments Identify method with metrics. diff --git a/users/api/requests.go b/users/api/requests.go index f323aa7c43..ce183d4b2e 100644 --- a/users/api/requests.go +++ b/users/api/requests.go @@ -83,19 +83,23 @@ func (req listClientsReq) validate() error { return nil } -type listMembersReq struct { +type listMembersByObjectReq struct { mfclients.Page - token string - groupID string + token string + objectKind string + objectID string } -func (req listMembersReq) validate() error { +func (req listMembersByObjectReq) validate() error { if req.token == "" { return apiutil.ErrBearerToken } - if req.groupID == "" { + if req.objectID == "" { return apiutil.ErrMissingID } + if req.objectKind == "" { + return apiutil.ErrMissingMemberKind + } return nil } diff --git a/users/clients.go b/users/clients.go index 1d17b28997..ce63a4c62d 100644 --- a/users/clients.go +++ b/users/clients.go @@ -26,8 +26,8 @@ type Service interface { // ListClients retrieves clients list for a valid auth token. ListClients(ctx context.Context, token string, pm clients.Page) (clients.ClientsPage, error) - // ListMembers retrieves everything that is assigned to a group identified by groupID. - ListMembers(ctx context.Context, token, groupID string, pm clients.Page) (clients.MembersPage, error) + // ListMembers retrieves everything that is assigned to a group/thing identified by objectID. + ListMembers(ctx context.Context, token, objectKind, objectID string, pm clients.Page) (clients.MembersPage, error) // UpdateClient updates the client's name and metadata. UpdateClient(ctx context.Context, token string, client clients.Client) (clients.Client, error) diff --git a/users/events/events.go b/users/events/events.go index 18d0fb6995..ed514153a5 100644 --- a/users/events/events.go +++ b/users/events/events.go @@ -290,16 +290,18 @@ func (lce listClientEvent) Encode() (map[string]interface{}, error) { type listClientByGroupEvent struct { mfclients.Page - channelID string + objectKind string + objectID string } func (lcge listClientByGroupEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": clientListByGroup, - "total": lcge.Total, - "offset": lcge.Offset, - "limit": lcge.Limit, - "channel_id": lcge.channelID, + "operation": clientListByGroup, + "total": lcge.Total, + "offset": lcge.Offset, + "limit": lcge.Limit, + "object_kind": lcge.objectKind, + "object_id": lcge.objectID, } if lcge.Name != "" { diff --git a/users/events/streams.go b/users/events/streams.go index 870ca437e2..5e2942bfee 100644 --- a/users/events/streams.go +++ b/users/events/streams.go @@ -160,13 +160,13 @@ func (es *eventStore) ListClients(ctx context.Context, token string, pm mfclient return cp, nil } -func (es *eventStore) ListMembers(ctx context.Context, token, groupID string, pm mfclients.Page) (mfclients.MembersPage, error) { - mp, err := es.svc.ListMembers(ctx, token, groupID, pm) +func (es *eventStore) ListMembers(ctx context.Context, token, objectKind, objectID string, pm mfclients.Page) (mfclients.MembersPage, error) { + mp, err := es.svc.ListMembers(ctx, token, objectKind, objectID, pm) if err != nil { return mp, err } event := listClientByGroupEvent{ - pm, groupID, + pm, objectKind, objectID, } if err := es.Publish(ctx, event); err != nil { diff --git a/users/service.go b/users/service.go index 438c6b5f47..52d1bb8396 100644 --- a/users/service.go +++ b/users/service.go @@ -17,11 +17,13 @@ import ( ) const ( - userKind = "users" - tokenKind = "token" + userKind = "users" + tokenKind = "token" + thingsKind = "things" userType = "user" groupType = "group" + thingType = "thing" ) var ( @@ -413,19 +415,30 @@ 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{}, errors.ErrNotFound + } pm.IDs = uids.Policies @@ -433,6 +446,7 @@ func (svc service) ListMembers(ctx context.Context, token, groupID string, pm mf if err != nil { return mfclients.MembersPage{}, err } + return mfclients.MembersPage{ Page: cp.Page, Members: cp.Clients, diff --git a/users/service_test.go b/users/service_test.go index 7002b25abf..d815e71935 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -1321,7 +1321,7 @@ func TestListMembers(t *testing.T) { repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&mainflux.AuthorizeRes{Authorized: true}, nil) repoCall2 := auth.On("ListAllSubjects", mock.Anything, mock.Anything).Return(&mainflux.ListSubjectsRes{}, nil) repoCall3 := cRepo.On("RetrieveAll", context.Background(), tc.page).Return(mfclients.ClientsPage{Page: tc.response.Page, Clients: tc.response.Members}, tc.err) - page, err := svc.ListMembers(context.Background(), tc.token, tc.groupID, tc.page) + page, err := svc.ListMembers(context.Background(), tc.token, "groups", tc.groupID, tc.page) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) if tc.err == nil { diff --git a/users/tracing/tracing.go b/users/tracing/tracing.go index dc4b298022..92cf513a79 100644 --- a/users/tracing/tracing.go +++ b/users/tracing/tracing.go @@ -172,11 +172,11 @@ func (tm *tracingMiddleware) DisableClient(ctx context.Context, token, id string } // ListMembers traces the "ListMembers" operation of the wrapped clients.Service. -func (tm *tracingMiddleware) ListMembers(ctx context.Context, token, groupID string, pm mfclients.Page) (mfclients.MembersPage, error) { - ctx, span := tm.tracer.Start(ctx, "svc_list_members", trace.WithAttributes(attribute.String("group_id", groupID))) +func (tm *tracingMiddleware) ListMembers(ctx context.Context, token, objectKind, objectID string, pm mfclients.Page) (mfclients.MembersPage, error) { + ctx, span := tm.tracer.Start(ctx, "svc_list_members", trace.WithAttributes(attribute.String("object_kind", objectKind)), trace.WithAttributes(attribute.String("object_id", objectID))) defer span.End() - return tm.svc.ListMembers(ctx, token, groupID, pm) + return tm.svc.ListMembers(ctx, token, objectKind, objectID, pm) } // Identify traces the "Identify" operation of the wrapped clients.Service. From 0d674f44454357a02a505f73f5e74501bc2507b6 Mon Sep 17 00:00:00 2001 From: Arvindh Date: Mon, 16 Oct 2023 14:15:47 +0530 Subject: [PATCH 07/17] fix: listing of shared things and users Signed-off-by: Arvindh --- things/api/channels.go | 305 ------------------------ things/api/clients.go | 362 ----------------------------- things/api/endpoints.go | 397 -------------------------------- things/api/http/transport.go | 26 +++ things/api/logging.go | 4 +- things/api/requests.go | 435 ----------------------------------- things/api/responses.go | 304 ------------------------ things/api/transport.go | 26 --- things/events/streams.go | 8 - users/api/groups.go | 65 ------ 10 files changed, 28 insertions(+), 1904 deletions(-) delete mode 100644 things/api/channels.go delete mode 100644 things/api/clients.go delete mode 100644 things/api/endpoints.go delete mode 100644 things/api/requests.go delete mode 100644 things/api/responses.go delete mode 100644 things/api/transport.go diff --git a/things/api/channels.go b/things/api/channels.go deleted file mode 100644 index 2259f35e18..0000000000 --- a/things/api/channels.go +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "encoding/json" - "net/http" - "strings" - - "github.com/go-chi/chi/v5" - kithttp "github.com/go-kit/kit/transport/http" - "github.com/mainflux/mainflux/internal/api" - "github.com/mainflux/mainflux/internal/apiutil" - gapi "github.com/mainflux/mainflux/internal/groups/api" - "github.com/mainflux/mainflux/logger" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/mainflux/mainflux/pkg/groups" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Handler { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - r.Route("/channels", func(r chi.Router) { - r.Post("/", otelhttp.NewHandler(kithttp.NewServer( - gapi.CreateGroupEndpoint(svc), - gapi.DecodeGroupCreate, - api.EncodeResponse, - opts..., - ), "create_channel").ServeHTTP) - - r.Get("/{groupID}", otelhttp.NewHandler(kithttp.NewServer( - gapi.ViewGroupEndpoint(svc), - gapi.DecodeGroupRequest, - api.EncodeResponse, - opts..., - ), "view_channel").ServeHTTP) - - r.Put("/{groupID}", otelhttp.NewHandler(kithttp.NewServer( - gapi.UpdateGroupEndpoint(svc), - gapi.DecodeGroupUpdate, - api.EncodeResponse, - opts..., - ), "update_channel").ServeHTTP) - - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( - gapi.ListGroupsEndpoint(svc, "users"), - gapi.DecodeListGroupsRequest, - api.EncodeResponse, - opts..., - ), "list_channels").ServeHTTP) - - r.Post("/{groupID}/enable", otelhttp.NewHandler(kithttp.NewServer( - gapi.EnableGroupEndpoint(svc), - gapi.DecodeChangeGroupStatus, - api.EncodeResponse, - opts..., - ), "enable_channel").ServeHTTP) - - r.Post("/{groupID}/disable", otelhttp.NewHandler(kithttp.NewServer( - gapi.DisableGroupEndpoint(svc), - gapi.DecodeChangeGroupStatus, - api.EncodeResponse, - opts..., - ), "disable_channel").ServeHTTP) - - // Instead of having this endpoint /channels/{groupID}/members separately, - // we can have two separate endpoints for each member kind - // users (/channels/{groupID}/users) & user_groups (/channels/{groupID}/groups) - r.Post("/{groupID}/members", otelhttp.NewHandler(kithttp.NewServer( - assignUsersGroupsEndpoint(svc), - decodeAssignUsersGroupsRequest, - api.EncodeResponse, - opts..., - ), "assign_members").ServeHTTP) - - // Instead of having this endpoint /channels/{groupID}/members separately, - // we can have two separate endpoints for each member kind - // users (/channels/{groupID}/users) & user_groups (/channels/{groupID}/groups) - r.Delete("/{groupID}/members", 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", otelhttp.NewHandler(kithttp.NewServer( - assignUsersEndpoint(svc), - decodeAssignUsersRequest, - api.EncodeResponse, - opts..., - ), "assign_users").ServeHTTP) - - // Request to remove users from a channel - // This endpoint can be used alternative to /channels/{groupID}/members - r.Delete("/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( - unassignUsersEndpoint(svc), - decodeUnassignUsersRequest, - api.EncodeResponse, - opts..., - ), "unassign_users").ServeHTTP) - - // Request to add user_groups to a channel - // This endpoint can be used alternative to /channels/{groupID}/members - r.Post("/{groupID}/groups", otelhttp.NewHandler(kithttp.NewServer( - assignUserGroupsEndpoint(svc), - decodeAssignUserGroupsRequest, - api.EncodeResponse, - opts..., - ), "assign_groups").ServeHTTP) - - // Request to remove user_groups from a channel - // This endpoint can be used alternative to /channels/{groupID}/members - r.Delete("/{groupID}/groups", otelhttp.NewHandler(kithttp.NewServer( - unassignUserGroupsEndpoint(svc), - decodeUnassignUserGroupsRequest, - api.EncodeResponse, - opts..., - ), "unassign_groups").ServeHTTP) - - r.Post("/{groupID}/things/{thingID}", otelhttp.NewHandler(kithttp.NewServer( - connectChannelThingEndpoint(svc), - decodeConnectChannelThingRequest, - api.EncodeResponse, - opts..., - ), "connect_channel_thing").ServeHTTP) - - r.Delete("/{groupID}/things/{thingID}", otelhttp.NewHandler(kithttp.NewServer( - disconnectChannelThingEndpoint(svc), - decodeDisconnectChannelThingRequest, - api.EncodeResponse, - opts..., - ), "disconnect_channel_thing").ServeHTTP) - }) - - // Ideal location: things service, things endpoint - // Reason for placing here : - // SpiceDB provides list of channel ids to which thing id attached - // and channel service can access spiceDB and get this channel ids list with given thing id. - // Request to get list of channels to which thingID ({memberID}) belongs - r.Get("/things/{memberID}/channels", otelhttp.NewHandler(kithttp.NewServer( - gapi.ListGroupsEndpoint(svc, "things"), - gapi.DecodeListGroupsRequest, - api.EncodeResponse, - opts..., - ), "list_channel_by_thing_id").ServeHTTP) - - // Ideal location: users service, users endpoint - // Reason for placing here : - // SpiceDB provides list of channel ids attached to given user id - // and channel service can access spiceDB and get this user ids list with given thing id. - // Request to get list of channels to which userID ({memberID}) have permission. - r.Get("/users/{memberID}/channels", otelhttp.NewHandler(kithttp.NewServer( - gapi.ListGroupsEndpoint(svc, "users"), - gapi.DecodeListGroupsRequest, - api.EncodeResponse, - opts..., - ), "list_channel_by_user_id").ServeHTTP) - - // Ideal location: users service, groups endpoint - // SpiceDB provides list of channel ids attached to given user_group id - // and channel service can access spiceDB and get this user ids list with given user_group id. - // Request to get list of channels to which user_group_id ({memberID}) attached. - r.Get("/groups/{memberID}/channels", otelhttp.NewHandler(kithttp.NewServer( - gapi.ListGroupsEndpoint(svc, "groups"), - gapi.DecodeListGroupsRequest, - api.EncodeResponse, - opts..., - ), "list_channel_by_user_group_id").ServeHTTP) - - // Connect channel and thing - r.Post("/connect", otelhttp.NewHandler(kithttp.NewServer( - connectEndpoint(svc), - decodeConnectRequest, - api.EncodeResponse, - opts..., - ), "connect").ServeHTTP) - - // Disconnect channel and thing - r.Post("/disconnect", otelhttp.NewHandler(kithttp.NewServer( - disconnectEndpoint(svc), - decodeDisconnectRequest, - api.EncodeResponse, - opts..., - ), "disconnect").ServeHTTP) - - return r -} - -func decodeAssignUsersGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { - 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) { - 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) { - req := assignUsersRequest{ - 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 decodeUnassignUsersRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := unassignUsersRequest{ - 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 decodeAssignUserGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := assignUserGroupsRequest{ - 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 decodeUnassignUserGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := unassignUserGroupsRequest{ - 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 decodeConnectChannelThingRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := connectChannelThingRequest{ - token: apiutil.ExtractBearerToken(r), - ThingID: chi.URLParam(r, "thingID"), - ChannelID: chi.URLParam(r, "groupID"), - } - return req, nil -} - -func decodeDisconnectChannelThingRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := disconnectChannelThingRequest{ - token: apiutil.ExtractBearerToken(r), - ThingID: chi.URLParam(r, "thingID"), - ChannelID: chi.URLParam(r, "groupID"), - } - return req, nil -} - -func decodeConnectRequest(_ 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 := connectChannelThingRequest{ - token: apiutil.ExtractBearerToken(r), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - return req, nil -} - -func decodeDisconnectRequest(_ 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 := disconnectChannelThingRequest{ - token: apiutil.ExtractBearerToken(r), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - return req, nil -} diff --git a/things/api/clients.go b/things/api/clients.go deleted file mode 100644 index 2e88e8ca14..0000000000 --- a/things/api/clients.go +++ /dev/null @@ -1,362 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "encoding/json" - "net/http" - "strings" - - "github.com/go-chi/chi/v5" - kithttp "github.com/go-kit/kit/transport/http" - "github.com/mainflux/mainflux/internal/api" - "github.com/mainflux/mainflux/internal/apiutil" - mflog "github.com/mainflux/mainflux/logger" - mfclients "github.com/mainflux/mainflux/pkg/clients" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/mainflux/mainflux/things" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -func clientsHandler(svc things.Service, r *chi.Mux, logger mflog.Logger) http.Handler { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - r.Route("/things", func(r chi.Router) { - r.Post("/", otelhttp.NewHandler(kithttp.NewServer( - createClientEndpoint(svc), - decodeCreateClientReq, - api.EncodeResponse, - opts..., - ), "create_thing").ServeHTTP) - - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( - listClientsEndpoint(svc), - decodeListClients, - api.EncodeResponse, - opts..., - ), "list_things").ServeHTTP) - - r.Post("/bulk", otelhttp.NewHandler(kithttp.NewServer( - createClientsEndpoint(svc), - decodeCreateClientsReq, - api.EncodeResponse, - opts..., - ), "create_things").ServeHTTP) - - r.Get("/{thingID}", otelhttp.NewHandler(kithttp.NewServer( - viewClientEndpoint(svc), - decodeViewClient, - api.EncodeResponse, - opts..., - ), "view_thing").ServeHTTP) - - r.Patch("/{thingID}", otelhttp.NewHandler(kithttp.NewServer( - updateClientEndpoint(svc), - decodeUpdateClient, - api.EncodeResponse, - opts..., - ), "update_thing").ServeHTTP) - - r.Patch("/{thingID}/tags", otelhttp.NewHandler(kithttp.NewServer( - updateClientTagsEndpoint(svc), - decodeUpdateClientTags, - api.EncodeResponse, - opts..., - ), "update_thing_tags").ServeHTTP) - - r.Patch("/{thingID}/secret", otelhttp.NewHandler(kithttp.NewServer( - updateClientSecretEndpoint(svc), - decodeUpdateClientCredentials, - api.EncodeResponse, - opts..., - ), "update_thing_credentials").ServeHTTP) - - r.Patch("/{thingID}/owner", otelhttp.NewHandler(kithttp.NewServer( - updateClientOwnerEndpoint(svc), - decodeUpdateClientOwner, - api.EncodeResponse, - opts..., - ), "update_thing_owner").ServeHTTP) - - r.Post("/{thingID}/enable", otelhttp.NewHandler(kithttp.NewServer( - enableClientEndpoint(svc), - decodeChangeClientStatus, - api.EncodeResponse, - opts..., - ), "enable_thing").ServeHTTP) - - r.Post("/{thingID}/disable", otelhttp.NewHandler(kithttp.NewServer( - disableClientEndpoint(svc), - decodeChangeClientStatus, - api.EncodeResponse, - opts..., - ), "disable_thing").ServeHTTP) - - r.Post("/{thingID}/share", otelhttp.NewHandler(kithttp.NewServer( - thingShareEndpoint(svc), - decodeThingShareRequest, - api.EncodeResponse, - opts..., - ), "thing_share").ServeHTTP) - - r.Post("/{thingID}/unshare", otelhttp.NewHandler(kithttp.NewServer( - thingUnshareEndpoint(svc), - decodeThingUnshareRequest, - api.EncodeResponse, - opts..., - ), "thing_delete_share").ServeHTTP) - - }) - - // Ideal location: things service, channels endpoint - // Reason for placing here : - // SpiceDB provides list of thing ids present in given channel id - // and things service can access spiceDB and get the list of thing ids present in given channel id. - // Request to get list of things present in channelID ({groupID}) . - r.Get("/channels/{groupID}/things", otelhttp.NewHandler(kithttp.NewServer( - listMembersEndpoint(svc), - decodeListMembersRequest, - api.EncodeResponse, - opts..., - ), "list_things_by_channel_id").ServeHTTP) - - return r -} - -func decodeViewClient(_ context.Context, r *http.Request) (interface{}, error) { - req := viewClientReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "thingID"), - } - - return req, nil -} - -func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) { - var ownerID string - s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - 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) - } - 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, err - } - - p, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - if oid != "" { - ownerID = oid - } - st, err := mfclients.ToStatus(s) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - req := listClientsReq{ - token: apiutil.ExtractBearerToken(r), - status: st, - offset: o, - limit: l, - metadata: m, - name: n, - tag: t, - permission: p, - owner: ownerID, - } - return req, nil -} - -func decodeUpdateClient(_ 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 := updateClientReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "thingID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} - -func decodeUpdateClientTags(_ 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 := updateClientTagsReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "thingID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} - -func decodeUpdateClientCredentials(_ 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 := updateClientCredentialsReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "thingID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} - -func decodeUpdateClientOwner(_ 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 := updateClientOwnerReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "thingID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} - -func decodeCreateClientReq(_ 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) - } - - var c mfclients.Client - if err := json.NewDecoder(r.Body).Decode(&c); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - req := createClientReq{ - client: c, - token: apiutil.ExtractBearerToken(r), - } - - return req, nil -} - -func decodeCreateClientsReq(_ 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) - } - - c := createClientsReq{token: apiutil.ExtractBearerToken(r)} - if err := json.NewDecoder(r.Body).Decode(&c.Clients); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return c, nil -} - -func decodeChangeClientStatus(_ context.Context, r *http.Request) (interface{}, error) { - req := changeClientStatusReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "thingID"), - } - - return req, nil -} - -func decodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { - s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - 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) - } - 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, - Permission: p, - Metadata: m, - }, - groupID: chi.URLParam(r, "groupID"), - } - return req, nil -} - -func decodeThingShareRequest(_ 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 := thingShareRequest{ - token: apiutil.ExtractBearerToken(r), - thingID: chi.URLParam(r, "thingID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} - -func decodeThingUnshareRequest(_ 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 := thingUnshareRequest{ - token: apiutil.ExtractBearerToken(r), - thingID: chi.URLParam(r, "thingID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} diff --git a/things/api/endpoints.go b/things/api/endpoints.go deleted file mode 100644 index 74d24c5742..0000000000 --- a/things/api/endpoints.go +++ /dev/null @@ -1,397 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - - "github.com/go-kit/kit/endpoint" - "github.com/mainflux/mainflux/internal/apiutil" - mfclients "github.com/mainflux/mainflux/pkg/clients" - "github.com/mainflux/mainflux/pkg/errors" - "github.com/mainflux/mainflux/pkg/groups" - "github.com/mainflux/mainflux/things" -) - -func createClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(createClientReq) - if err := req.validate(); err != nil { - return createClientRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - client, err := svc.CreateThings(ctx, req.token, req.client) - if err != nil { - return createClientRes{}, err - } - ucr := createClientRes{ - Client: client[0], - created: true, - } - - return ucr, nil - } -} - -func createClientsEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(createClientsReq) - if err := req.validate(); err != nil { - return clientsPageRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - page, err := svc.CreateThings(ctx, req.token, req.Clients...) - if err != nil { - return clientsPageRes{}, err - } - res := clientsPageRes{ - pageRes: pageRes{ - Total: uint64(len(page)), - }, - Clients: []viewClientRes{}, - } - for _, c := range page { - res.Clients = append(res.Clients, viewClientRes{Client: c}) - } - return res, nil - } -} - -func viewClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(viewClientReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - c, err := svc.ViewClient(ctx, req.token, req.id) - if err != nil { - return nil, err - } - return viewClientRes{Client: c}, nil - } -} - -func listClientsEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listClientsReq) - if err := req.validate(); err != nil { - return mfclients.ClientsPage{}, errors.Wrap(apiutil.ErrValidation, err) - } - - pm := mfclients.Page{ - Status: req.status, - Offset: req.offset, - Limit: req.limit, - Owner: req.owner, - Name: req.name, - Tag: req.tag, - Permission: req.permission, - Metadata: req.metadata, - } - page, err := svc.ListClients(ctx, req.token, pm) - if err != nil { - return mfclients.ClientsPage{}, err - } - - res := clientsPageRes{ - pageRes: pageRes{ - Total: page.Total, - Offset: page.Offset, - Limit: page.Limit, - }, - Clients: []viewClientRes{}, - } - for _, c := range page.Clients { - res.Clients = append(res.Clients, viewClientRes{Client: c}) - } - - return res, nil - } -} - -func listMembersEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersReq) - if err := req.validate(); err != nil { - return memberPageRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - page, err := svc.ListClientsByGroup(ctx, req.token, req.groupID, req.Page) - if err != nil { - return memberPageRes{}, err - } - return buildMembersResponse(page), nil - } -} - -func updateClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - cli := mfclients.Client{ - ID: req.id, - Name: req.Name, - Metadata: req.Metadata, - } - client, err := svc.UpdateClient(ctx, req.token, cli) - if err != nil { - return nil, err - } - return updateClientRes{Client: client}, nil - } -} - -func updateClientTagsEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientTagsReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - cli := mfclients.Client{ - ID: req.id, - Tags: req.Tags, - } - client, err := svc.UpdateClientTags(ctx, req.token, cli) - if err != nil { - return nil, err - } - return updateClientRes{Client: client}, nil - } -} - -func updateClientSecretEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientCredentialsReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - client, err := svc.UpdateClientSecret(ctx, req.token, req.id, req.Secret) - if err != nil { - return nil, err - } - return updateClientRes{Client: client}, nil - } -} - -func updateClientOwnerEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientOwnerReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - cli := mfclients.Client{ - ID: req.id, - Owner: req.Owner, - } - - client, err := svc.UpdateClientOwner(ctx, req.token, cli) - if err != nil { - return nil, err - } - return updateClientRes{Client: client}, nil - } -} - -func enableClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeClientStatusReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - client, err := svc.EnableClient(ctx, req.token, req.id) - if err != nil { - return nil, err - } - return deleteClientRes{Client: client}, nil - } -} - -func disableClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeClientStatusReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - client, err := svc.DisableClient(ctx, req.token, req.id) - if err != nil { - return nil, err - } - return deleteClientRes{Client: client}, nil - } -} - -func buildMembersResponse(cp mfclients.MembersPage) memberPageRes { - res := memberPageRes{ - pageRes: pageRes{ - Total: cp.Total, - Offset: cp.Offset, - Limit: cp.Limit, - }, - Members: []viewMembersRes{}, - } - for _, c := range cp.Members { - res.Members = append(res.Members, viewMembersRes{Client: c}) - } - 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) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - if err := svc.Assign(ctx, req.token, req.groupID, req.Relation, "users", req.UserIDs...); err != nil { - return nil, err - } - return assignUsersRes{}, nil - } -} - -func unassignUsersEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(unassignUsersRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - if err := svc.Unassign(ctx, req.token, req.groupID, req.Relation, "users", req.UserIDs...); err != nil { - return nil, err - } - return unassignUsersRes{}, nil - } -} - -func assignUserGroupsEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(assignUserGroupsRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - if err := svc.Assign(ctx, req.token, req.groupID, "parent_group", "groups", req.UserGroupIDs...); err != nil { - return nil, err - } - return assignUserGroupsRes{}, nil - } -} - -func unassignUserGroupsEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(unassignUserGroupsRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - if err := svc.Unassign(ctx, req.token, req.groupID, "parent_group", "groups", req.UserGroupIDs...); err != nil { - return nil, err - } - return unassignUserGroupsRes{}, nil - } -} - -func connectChannelThingEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(connectChannelThingRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - if err := svc.Assign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { - return nil, err - } - return connectChannelThingRes{}, nil - } -} - -func disconnectChannelThingEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(disconnectChannelThingRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - if err := svc.Unassign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { - return nil, err - } - return disconnectChannelThingRes{}, nil - } -} - -func connectEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(connectChannelThingRequest) - if err := req.validate(); err != nil { - return connectChannelThingRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - if err := svc.Assign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { - return connectChannelThingRes{}, err - } - - return connectChannelThingRes{}, nil - } -} - -func disconnectEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(disconnectChannelThingRequest) - if err := req.validate(); err != nil { - return disconnectChannelThingRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - if err := svc.Unassign(ctx, req.token, req.ChannelID, "group", "things", req.ThingID); err != nil { - return disconnectChannelThingRes{}, err - } - - return disconnectChannelThingRes{}, nil - } -} - -func thingShareEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(thingShareRequest) - if err := req.validate(); err != nil { - return thingShareRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - if err := svc.Share(ctx, req.token, req.thingID, req.Relation, req.UserIDs...); err != nil { - return thingShareRes{}, err - } - return thingShareRes{}, nil - } -} - -func thingUnshareEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(thingUnshareRequest) - if err := req.validate(); err != nil { - return thingUnshareRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - if err := svc.Unshare(ctx, req.token, req.thingID, req.Relation, req.UserIDs...); err != nil { - return thingShareRes{}, err - } - return thingUnshareRes{}, nil - } -} diff --git a/things/api/http/transport.go b/things/api/http/transport.go index e69de29bb2..25507eac68 100644 --- a/things/api/http/transport.go +++ b/things/api/http/transport.go @@ -0,0 +1,26 @@ +// Copyright (c) Mainflux +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/mainflux/mainflux" + mflog "github.com/mainflux/mainflux/logger" + "github.com/mainflux/mainflux/pkg/groups" + "github.com/mainflux/mainflux/things" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// MakeHandler returns a HTTP handler for Things and Groups API endpoints. +func MakeHandler(tsvc things.Service, grps groups.Service, mux *chi.Mux, logger mflog.Logger, instanceID string) http.Handler { + clientsHandler(tsvc, mux, logger) + groupsHandler(grps, mux, logger) + + mux.Get("/health", mainflux.Health("things", instanceID)) + mux.Handle("/metrics", promhttp.Handler()) + + return mux +} diff --git a/things/api/logging.go b/things/api/logging.go index 65f172473a..d2e471fa66 100644 --- a/things/api/logging.go +++ b/things/api/logging.go @@ -159,14 +159,14 @@ func (lm *loggingMiddleware) Identify(ctx context.Context, key string) (id strin func (lm *loggingMiddleware) Authorize(ctx context.Context, req *mainflux.AuthorizeReq) (id string, err error) { defer func(begin time.Time) { - message := fmt.Sprintf("Method unshare for thing id %s with relation %s for users %v took %s to complete", id, relation, userids, time.Since(begin)) + message := fmt.Sprintf("Method authorize for thing key %s and channnel %s took %s to complete", req.Subject, req.Object, 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.Unshare(ctx, token, id, relation, userids...) + return lm.svc.Authorize(ctx, req) } func (lm *loggingMiddleware) Share(ctx context.Context, token, id string, relation string, userids ...string) (err error) { diff --git a/things/api/requests.go b/things/api/requests.go deleted file mode 100644 index 6f6d77e94c..0000000000 --- a/things/api/requests.go +++ /dev/null @@ -1,435 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "github.com/mainflux/mainflux/internal/api" - "github.com/mainflux/mainflux/internal/apiutil" - mfclients "github.com/mainflux/mainflux/pkg/clients" - "github.com/mainflux/mainflux/pkg/errors" - "golang.org/x/exp/slices" -) - -type createClientReq struct { - client mfclients.Client - token string -} - -func (req createClientReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - if len(req.client.Name) > api.MaxNameSize { - return apiutil.ErrNameSize - } - // Do the validation only if request contains ID - if req.client.ID != "" { - return api.ValidateUUID(req.client.ID) - } - - return nil -} - -type createClientsReq struct { - token string - Clients []mfclients.Client -} - -func (req createClientsReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if len(req.Clients) == 0 { - return apiutil.ErrEmptyList - } - - for _, client := range req.Clients { - if client.ID != "" { - if err := api.ValidateUUID(client.ID); err != nil { - return err - } - } - if len(client.Name) > api.MaxNameSize { - return apiutil.ErrNameSize - } - } - - return nil -} - -type viewClientReq struct { - token string - id string -} - -func (req viewClientReq) validate() error { - return nil -} - -type listClientsReq struct { - token string - status mfclients.Status - offset uint64 - limit uint64 - name string - tag string - owner string - permission string - visibility string - metadata mfclients.Metadata -} - -func (req listClientsReq) validate() error { - if req.limit > api.MaxLimitSize || req.limit < 1 { - return apiutil.ErrLimitSize - } - if req.visibility != "" && - req.visibility != api.AllVisibility && - req.visibility != api.MyVisibility && - req.visibility != api.SharedVisibility { - return apiutil.ErrInvalidVisibilityType - } - if req.limit > api.MaxLimitSize || req.limit < 1 { - return apiutil.ErrLimitSize - } - - if len(req.name) > api.MaxNameSize { - return apiutil.ErrNameSize - } - - return nil -} - -type listMembersReq struct { - mfclients.Page - token string - groupID string -} - -func (req listMembersReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type updateClientReq struct { - token string - id string - Name string `json:"name,omitempty"` - Metadata map[string]interface{} `json:"metadata,omitempty"` - Tags []string `json:"tags,omitempty"` -} - -func (req updateClientReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.id == "" { - return apiutil.ErrMissingID - } - if len(req.Name) > api.MaxNameSize { - return apiutil.ErrNameSize - } - return nil -} - -type updateClientTagsReq struct { - id string - token string - Tags []string `json:"tags,omitempty"` -} - -func (req updateClientTagsReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.id == "" { - return apiutil.ErrMissingID - } - return nil -} - -type updateClientOwnerReq struct { - id string - token string - Owner string `json:"owner,omitempty"` -} - -func (req updateClientOwnerReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - if req.id == "" { - return apiutil.ErrMissingID - } - if req.Owner == "" { - return apiutil.ErrMissingOwner - } - return nil -} - -type updateClientCredentialsReq struct { - token string - id string - Secret string `json:"secret,omitempty"` -} - -func (req updateClientCredentialsReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - if req.id == "" { - return apiutil.ErrMissingID - } - - if req.Secret == "" { - return apiutil.ErrBearerKey - } - - return nil -} - -type changeClientStatusReq struct { - token string - id string -} - -func (req changeClientStatusReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - return nil -} - -type assignUsersGroupsRequest struct { - token string - groupID string - Relation string `json:"relation,omitempty"` - MemberKind string `json:"member_kind,omitempty"` - Members []string `json:"members"` -} - -func (req assignUsersGroupsRequest) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.MemberKind == "" { - return apiutil.ErrMissingMemberKind - } - - if !slices.Contains([]string{"users", "groups"}, req.MemberKind) { - return apiutil.ErrInvalidMemberKind - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.Members) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} - -type unassignUsersGroupsRequest struct { - token string - groupID string - Relation string `json:"relation,omitempty"` - MemberKind string `json:"member_kind,omitempty"` - Members []string `json:"members"` -} - -func (req unassignUsersGroupsRequest) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.MemberKind == "" { - return apiutil.ErrMissingMemberKind - } - - if !slices.Contains([]string{"users", "groups"}, req.MemberKind) { - return apiutil.ErrInvalidMemberKind - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.Members) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} - -type assignUsersRequest struct { - token string - groupID string - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` -} - -func (req assignUsersRequest) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.Relation == "" { - return apiutil.ErrMissingRelation - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.UserIDs) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} - -type unassignUsersRequest struct { - token string - groupID string - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` -} - -func (req unassignUsersRequest) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.Relation == "" { - return apiutil.ErrMissingRelation - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.UserIDs) == 0 { - return apiutil.ErrEmptyList - } - return nil -} - -type assignUserGroupsRequest struct { - token string - groupID string - UserGroupIDs []string `json:"group_ids"` -} - -func (req assignUserGroupsRequest) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.UserGroupIDs) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} - -type unassignUserGroupsRequest struct { - token string - groupID string - UserGroupIDs []string `json:"group_ids"` -} - -func (req unassignUserGroupsRequest) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.UserGroupIDs) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} - -type connectChannelThingRequest struct { - token string - ThingID string `json:"thing_id,omitempty"` - ChannelID string `json:"channel_id,omitempty"` - Permission string `json:"permission,omitempty"` -} - -func (req *connectChannelThingRequest) validate() error { - if req.ThingID == "" || req.ChannelID == "" { - return errors.ErrCreateEntity - } - return nil -} - -type disconnectChannelThingRequest struct { - token string - ThingID string `json:"thing_id,omitempty"` - ChannelID string `json:"channel_id,omitempty"` - Permission string `json:"permission,omitempty"` -} - -func (req *disconnectChannelThingRequest) validate() error { - if req.ThingID == "" || req.ChannelID == "" { - return errors.ErrCreateEntity - } - return nil -} - -type thingShareRequest struct { - token string - thingID string - Relation string `json:"relation,omitempty"` - UserIDs []string `json:"user_ids,omitempty"` -} - -func (req *thingShareRequest) validate() error { - if req.thingID == "" { - return errors.ErrMalformedEntity - } - if req.Relation == "" || len(req.UserIDs) <= 0 { - return errors.ErrCreateEntity - } - return nil -} - -type thingUnshareRequest struct { - token string - thingID string - Relation string `json:"relation,omitempty"` - UserIDs []string `json:"user_ids,omitempty"` -} - -func (req *thingUnshareRequest) validate() error { - if req.thingID == "" { - return errors.ErrMalformedEntity - } - if req.Relation == "" || len(req.UserIDs) <= 0 { - return errors.ErrCreateEntity - } - return nil -} diff --git a/things/api/responses.go b/things/api/responses.go deleted file mode 100644 index 8a9eaab2aa..0000000000 --- a/things/api/responses.go +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "fmt" - "net/http" - - "github.com/mainflux/mainflux" - mfclients "github.com/mainflux/mainflux/pkg/clients" -) - -var ( - _ mainflux.Response = (*viewClientRes)(nil) - _ mainflux.Response = (*createClientRes)(nil) - _ mainflux.Response = (*deleteClientRes)(nil) - _ mainflux.Response = (*clientsPageRes)(nil) - _ mainflux.Response = (*viewMembersRes)(nil) - _ mainflux.Response = (*memberPageRes)(nil) - _ mainflux.Response = (*assignUsersGroupsRes)(nil) - _ mainflux.Response = (*unassignUsersGroupsRes)(nil) - _ mainflux.Response = (*connectChannelThingRes)(nil) - _ mainflux.Response = (*disconnectChannelThingRes)(nil) -) - -type pageRes struct { - Limit uint64 `json:"limit,omitempty"` - Offset uint64 `json:"offset,omitempty"` - Total uint64 `json:"total,omitempty"` -} - -type createClientRes struct { - mfclients.Client - created bool -} - -func (res createClientRes) Code() int { - if res.created { - return http.StatusCreated - } - - return http.StatusOK -} - -func (res createClientRes) Headers() map[string]string { - if res.created { - return map[string]string{ - "Location": fmt.Sprintf("/things/%s", res.ID), - } - } - - return map[string]string{} -} - -func (res createClientRes) Empty() bool { - return false -} - -type updateClientRes struct { - mfclients.Client -} - -func (res updateClientRes) Code() int { - return http.StatusOK -} - -func (res updateClientRes) Headers() map[string]string { - return map[string]string{} -} - -func (res updateClientRes) Empty() bool { - return false -} - -type viewClientRes struct { - mfclients.Client -} - -func (res viewClientRes) Code() int { - return http.StatusOK -} - -func (res viewClientRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewClientRes) Empty() bool { - return false -} - -type clientsPageRes struct { - pageRes - Clients []viewClientRes `json:"things"` -} - -func (res clientsPageRes) Code() int { - return http.StatusOK -} - -func (res clientsPageRes) Headers() map[string]string { - return map[string]string{} -} - -func (res clientsPageRes) Empty() bool { - return false -} - -type viewMembersRes struct { - mfclients.Client -} - -func (res viewMembersRes) Code() int { - return http.StatusOK -} - -func (res viewMembersRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewMembersRes) Empty() bool { - return false -} - -type memberPageRes struct { - pageRes - Members []viewMembersRes `json:"things"` -} - -func (res memberPageRes) Code() int { - return http.StatusOK -} - -func (res memberPageRes) Headers() map[string]string { - return map[string]string{} -} - -func (res memberPageRes) Empty() bool { - return false -} - -type deleteClientRes struct { - mfclients.Client -} - -func (res deleteClientRes) Code() int { - return http.StatusOK -} - -func (res deleteClientRes) Headers() map[string]string { - return map[string]string{} -} - -func (res deleteClientRes) Empty() bool { - return false -} - -type assignUsersGroupsRes struct { -} - -func (res assignUsersGroupsRes) Code() int { - return http.StatusOK -} - -func (res assignUsersGroupsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res assignUsersGroupsRes) Empty() bool { - return true -} - -type unassignUsersGroupsRes struct { -} - -func (res unassignUsersGroupsRes) Code() int { - return http.StatusNoContent -} - -func (res unassignUsersGroupsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res unassignUsersGroupsRes) Empty() bool { - return true -} - -type assignUsersRes struct { -} - -func (res assignUsersRes) Code() int { - return http.StatusOK -} - -func (res assignUsersRes) Headers() map[string]string { - return map[string]string{} -} - -func (res assignUsersRes) Empty() bool { - return true -} - -type unassignUsersRes struct { -} - -func (res unassignUsersRes) Code() int { - return http.StatusNoContent -} - -func (res unassignUsersRes) Headers() map[string]string { - return map[string]string{} -} - -func (res unassignUsersRes) Empty() bool { - return true -} - -type assignUserGroupsRes struct { -} - -func (res assignUserGroupsRes) Code() int { - return http.StatusOK -} - -func (res assignUserGroupsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res assignUserGroupsRes) Empty() bool { - return true -} - -type unassignUserGroupsRes struct { -} - -func (res unassignUserGroupsRes) Code() int { - return http.StatusNoContent -} - -func (res unassignUserGroupsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res unassignUserGroupsRes) Empty() bool { - return true -} - -type connectChannelThingRes struct { -} - -func (res connectChannelThingRes) Code() int { - return http.StatusOK -} - -func (res connectChannelThingRes) Headers() map[string]string { - return map[string]string{} -} - -func (res connectChannelThingRes) Empty() bool { - return true -} - -type disconnectChannelThingRes struct { -} - -func (res disconnectChannelThingRes) Code() int { - return http.StatusNoContent -} - -func (res disconnectChannelThingRes) Headers() map[string]string { - return map[string]string{} -} - -func (res disconnectChannelThingRes) Empty() bool { - return true -} - -type thingShareRes struct{} - -func (res thingShareRes) Code() int { - return http.StatusOK -} - -func (res thingShareRes) Headers() map[string]string { - return map[string]string{} -} - -func (res thingShareRes) Empty() bool { - return true -} - -type thingUnshareRes struct{} - -func (res thingUnshareRes) Code() int { - return http.StatusNoContent -} - -func (res thingUnshareRes) Headers() map[string]string { - return map[string]string{} -} - -func (res thingUnshareRes) Empty() bool { - return true -} diff --git a/things/api/transport.go b/things/api/transport.go deleted file mode 100644 index 6d4356db94..0000000000 --- a/things/api/transport.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - "github.com/mainflux/mainflux" - mflog "github.com/mainflux/mainflux/logger" - "github.com/mainflux/mainflux/pkg/groups" - "github.com/mainflux/mainflux/things" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -// MakeHandler returns a HTTP handler for Things and Groups API endpoints. -func MakeHandler(tsvc things.Service, grps groups.Service, mux *chi.Mux, logger mflog.Logger, instanceID string) http.Handler { - clientsHandler(tsvc, mux, logger) - groupsHandler(grps, mux, logger) - - mux.Get("/health", mainflux.Health("things", instanceID)) - mux.Handle("/metrics", promhttp.Handler()) - - return mux -} diff --git a/things/events/streams.go b/things/events/streams.go index a7db1e612c..5ef9f842f5 100644 --- a/things/events/streams.go +++ b/things/events/streams.go @@ -244,11 +244,3 @@ func (es *eventStore) Unshare(ctx context.Context, token, id string, relation st return es.Publish(ctx, event) } - -func (es *eventStore) Share(ctx context.Context, token, id string, relation string, userids ...string) error { - return es.svc.Share(ctx, token, id, relation, userids...) -} - -func (es *eventStore) Unshare(ctx context.Context, token, id string, relation string, userids ...string) error { - return es.svc.Unshare(ctx, token, id, relation, userids...) -} diff --git a/users/api/groups.go b/users/api/groups.go index 42200e4151..d76852d39a 100644 --- a/users/api/groups.go +++ b/users/api/groups.go @@ -4,22 +4,18 @@ package api import ( - "context" - "encoding/json" "context" "encoding/json" "net/http" "github.com/go-chi/chi/v5" "github.com/go-kit/kit/endpoint" - "github.com/go-kit/kit/endpoint" kithttp "github.com/go-kit/kit/transport/http" "github.com/mainflux/mainflux/internal/api" "github.com/mainflux/mainflux/internal/apiutil" gapi "github.com/mainflux/mainflux/internal/groups/api" "github.com/mainflux/mainflux/logger" "github.com/mainflux/mainflux/pkg/errors" - "github.com/mainflux/mainflux/pkg/errors" "github.com/mainflux/mainflux/pkg/groups" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) @@ -94,16 +90,6 @@ func groupsHandler(svc groups.Service, r *chi.Mux, logger logger.Logger) http.Ha opts..., ), "assign_users").ServeHTTP) - r.Delete("/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( - unassignUsersEndpoint(svc), - decodeUnassignUsersRequest, - r.Post("/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( - assignUsersEndpoint(svc), - decodeAssignUsersRequest, - api.EncodeResponse, - opts..., - ), "assign_users").ServeHTTP) - r.Delete("/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( unassignUsersEndpoint(svc), decodeUnassignUsersRequest, @@ -180,54 +166,3 @@ func unassignUsersEndpoint(svc groups.Service) endpoint.Endpoint { return unassignUsersRes{}, nil } } - -func decodeAssignUsersRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := assignUsersReq{ - 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 decodeUnassignUsersRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := unassignUsersReq{ - 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 assignUsersEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(assignUsersReq) - - if err := req.validate(); err != nil { - return assignUsersRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - if err := svc.Assign(ctx, req.token, req.groupID, req.Relation, "users", req.UserIDs...); err != nil { - return assignUsersRes{}, err - } - return assignUsersRes{}, nil - } -} - -func unassignUsersEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(unassignUsersReq) - - if err := req.validate(); err != nil { - return unassignUsersRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - if err := svc.Unassign(ctx, req.token, req.groupID, req.Relation, "users", req.UserIDs...); err != nil { - return nil, err - } - return unassignUsersRes{}, nil - } -} From 91603a0d3c415e86ddcc9482f08e02d7dc270776 Mon Sep 17 00:00:00 2001 From: Arvindh Date: Mon, 16 Oct 2023 15:31:14 +0530 Subject: [PATCH 08/17] add: new SDK Signed-off-by: Arvindh --- pkg/sdk/go/channels.go | 36 ++++---- pkg/sdk/go/groups.go | 4 +- pkg/sdk/go/requests.go | 33 +------ pkg/sdk/go/sdk.go | 169 ++++++++++++++++++++++++++++++++--- pkg/sdk/go/things.go | 11 ++- tools/provision/provision.go | 18 ++-- 6 files changed, 195 insertions(+), 76 deletions(-) diff --git a/pkg/sdk/go/channels.go b/pkg/sdk/go/channels.go index 885615cad6..7a041d755b 100644 --- a/pkg/sdk/go/channels.go +++ b/pkg/sdk/go/channels.go @@ -146,7 +146,7 @@ func (sdk mfSDK) UpdateChannel(c Channel, token string) (Channel, errors.SDKErro return c, nil } -func (sdk mfSDK) AddUsersToChannel(token, channelID string, req addUsersToChannelReq) errors.SDKError { +func (sdk mfSDK) AddUsersToChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError { data, err := json.Marshal(req) if err != nil { return errors.NewSDKError(err) @@ -158,7 +158,7 @@ func (sdk mfSDK) AddUsersToChannel(token, channelID string, req addUsersToChanne return sdkerr } -func (sdk mfSDK) RemoveUsersFromChannel(token, channelID string, req removeUsersFromChannelReq) errors.SDKError { +func (sdk mfSDK) RemoveUsersFromChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError { data, err := json.Marshal(req) if err != nil { return errors.NewSDKError(err) @@ -170,24 +170,24 @@ func (sdk mfSDK) RemoveUsersFromChannel(token, channelID string, req removeUsers return sdkerr } -func (sdk mfSDK) ListChannelUsers(channelID string, pm PageMetadata, token string) (ChannelsPage, errors.SDKError) { +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 ChannelsPage{}, errors.NewSDKError(err) + return UsersPage{}, errors.NewSDKError(err) } _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) if sdkerr != nil { - return ChannelsPage{}, sdkerr + return UsersPage{}, sdkerr } - cp := ChannelsPage{} - if err := json.Unmarshal(body, &cp); err != nil { - return ChannelsPage{}, errors.NewSDKError(err) + up := UsersPage{} + if err := json.Unmarshal(body, &up); err != nil { + return UsersPage{}, errors.NewSDKError(err) } - return cp, nil + return up, nil } -func (sdk mfSDK) AddUserGroupsToChannel(token, channelID string, req addUserGroupsToChannelReq) errors.SDKError { +func (sdk mfSDK) AddUserGroupsToChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError { data, err := json.Marshal(req) if err != nil { return errors.NewSDKError(err) @@ -199,7 +199,7 @@ func (sdk mfSDK) AddUserGroupsToChannel(token, channelID string, req addUserGrou return sdkerr } -func (sdk mfSDK) RemoveUserGroupsToChannel(token, channelID string, req removeUserGroupsFromChannelReq) errors.SDKError { +func (sdk mfSDK) RemoveUserGroupsToChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError { data, err := json.Marshal(req) if err != nil { return errors.NewSDKError(err) @@ -211,21 +211,21 @@ func (sdk mfSDK) RemoveUserGroupsToChannel(token, channelID string, req removeUs return sdkerr } -func (sdk mfSDK) ListChannelUserGroups(channelID string, pm PageMetadata, token string) (ChannelsPage, errors.SDKError) { +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 ChannelsPage{}, errors.NewSDKError(err) + return GroupsPage{}, errors.NewSDKError(err) } _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) if sdkerr != nil { - return ChannelsPage{}, sdkerr + return GroupsPage{}, sdkerr } - cp := ChannelsPage{} - if err := json.Unmarshal(body, &cp); err != nil { - return ChannelsPage{}, errors.NewSDKError(err) + gp := GroupsPage{} + if err := json.Unmarshal(body, &gp); err != nil { + return GroupsPage{}, errors.NewSDKError(err) } - return cp, nil + return gp, nil } func (sdk mfSDK) Connect(conn Connection, token string) errors.SDKError { diff --git a/pkg/sdk/go/groups.go b/pkg/sdk/go/groups.go index 55c9395c14..4b61587237 100644 --- a/pkg/sdk/go/groups.go +++ b/pkg/sdk/go/groups.go @@ -145,7 +145,7 @@ func (sdk mfSDK) DisableGroup(id, token string) (Group, errors.SDKError) { return sdk.changeGroupStatus(id, disableEndpoint, token) } -func (sdk mfSDK) AddUsersToGroup(groupID string, req addUsersToGroupReq, token string) errors.SDKError { +func (sdk mfSDK) AddUsersToGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError { data, err := json.Marshal(req) if err != nil { return errors.NewSDKError(err) @@ -157,7 +157,7 @@ func (sdk mfSDK) AddUsersToGroup(groupID string, req addUsersToGroupReq, token s return sdkerr } -func (sdk mfSDK) RemoveUsersToGroup(groupID string, req removeUsersToGroupReq, token string) errors.SDKError { +func (sdk mfSDK) RemoveUsersToGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError { data, err := json.Marshal(req) if err != nil { return errors.NewSDKError(err) diff --git a/pkg/sdk/go/requests.go b/pkg/sdk/go/requests.go index 0a89d741f0..eccc46d5c8 100644 --- a/pkg/sdk/go/requests.go +++ b/pkg/sdk/go/requests.go @@ -49,40 +49,11 @@ type tokenReq struct { Secret string `json:"secret"` } -type shareThingReq struct { +type UsersRelationRequest struct { Relation string `json:"relation"` UserIDs []string `json:"user_ids"` } -type unshareThingReq struct { - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` -} - -type addUsersToChannelReq struct { - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` -} - -type removeUsersFromChannelReq struct { - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` -} - -type addUserGroupsToChannelReq struct { +type UserGroupsRequest struct { UserGroupIDs []string `json:"group_ids"` } - -type removeUserGroupsFromChannelReq struct { - UserGroupIDs []string `json:"group_ids"` -} - -type addUsersToGroupReq struct { - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` -} - -type removeUsersToGroupReq struct { - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` -} diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go index 63331b471a..6d273042da 100644 --- a/pkg/sdk/go/sdk.go +++ b/pkg/sdk/go/sdk.go @@ -247,11 +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: @@ -384,18 +415,36 @@ type SDK interface { // ShareThing shares thing with other users. // // example: - // err := sdk.ShareThing("thingID", "token", shareThingReq{Relation: "viewer" , UserIDs: ["user_id_1","user_id_2","user_id_3"] }) + // req := sdk.UsersRelationRequest{ + // Relation: "viewer", // available options: "owner", "admin", "editor", "viewer" + // UserIDs: ["user_id_1", "user_id_2", "user_id_3"] + // } + // err := sdk.ShareThing("thing_id", req, "token") // fmt.Println(err) - ShareThing(thingID, token string, req shareThingReq) errors.SDKError + ShareThing(thingID string, req UsersRelationRequest, token string) errors.SDKError // UnshareThing unshare a thing with other users. // // example: - // err := sdk.UnshareThing("thingID", "token", shareThingReq{Relation: "viewer" , UserIDs: ["user_id_1","user_id_2","user_id_3"] }) + // 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, token string, req unshareThingReq) errors.SDKError + UnshareThing(thingID string, req UsersRelationRequest, token string) errors.SDKError - ListThingUsers(token, thingID string) (UsersPage, 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. // @@ -481,13 +530,52 @@ type SDK interface { // fmt.Println(group) DisableGroup(id, token string) (Group, errors.SDKError) - AddUsersToGroup(groupID string, req addUsersToGroupReq, token string) errors.SDKError + // AddUsersToGroup add users 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.AddUsersToGroup("groupID",req, "token") + // fmt.Println(group) + AddUsersToGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError - RemoveUsersToGroup(groupID string, req removeUsersToGroupReq, token string) errors.SDKError + // RemoveUsersToGroup remove users 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.RemoveUsersToGroup("groupID",req, "token") + // fmt.Println(group) + RemoveUsersToGroup(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("user_id_1", 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("user_id_1", pm, "token") + // fmt.Println(groups) ListGroupChannels(groupID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) + // CreateChannel creates new channel and returns its id. // // example: @@ -581,17 +669,72 @@ type SDK interface { // fmt.Println(channel) DisableChannel(id, token string) (Channel, errors.SDKError) - AddUsersToChannel(token, channelID string, req addUsersToChannelReq) errors.SDKError + // AddUsersToChannel add users to a channel. + // + // example: + // req := sdk.UsersRelationRequest{ + // Relation: "viewer", // available options: "owner", "admin", "editor", "viewer" + // UserIDs: ["user_id_1", "user_id_2", "user_id_3"] + // } + // err := sdk.AddUsersToChannel("channel_id", req, "token") + // fmt.Println(err) + AddUsersToChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError + + // RemoveUsersFromChannel remove users 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"] + // } + // err := sdk.RemoveUsersFromChannel("channel_id", req, "token") + // fmt.Println(err) + RemoveUsersFromChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError - RemoveUsersFromChannel(token, channelID string, req removeUsersFromChannelReq) errors.SDKError + // ListChannelUsers list all users in a channel . + // + // example: + // 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) - ListChannelUsers(channelID string, pm PageMetadata, token string) (ChannelsPage, errors.SDKError) + // AddUserGroupsToChannel add user group to a channel. + // + // example: + // req := sdk.UserGroupsRequest{ + // GroupsIDs: ["group_id_1", "group_id_2", "group_id_3"] + // } + // err := sdk.AddUserGroupsToChannel("channel_id",req, "token") + // fmt.Println(err) + AddUserGroupsToChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError - AddUserGroupsToChannel(token, channelID string, req addUserGroupsToChannelReq) errors.SDKError + // RemoveUserGroupsToChannel remove user group from a channel. + // + // example: + // req := sdk.UserGroupsRequest{ + // GroupsIDs: ["group_id_1", "group_id_2", "group_id_3"] + // } + // err := sdk.RemoveUserGroupsToChannel("channel_id",req, "token") + // fmt.Println(err) + RemoveUserGroupsToChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError - RemoveUserGroupsToChannel(token, channelID string, req removeUserGroupsFromChannelReq) errors.SDKError + // ListChannelUserGroups list all user groups in a channel. + // + // example: + // 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) - ListChannelUserGroups(channelID string, pm PageMetadata, token string) (ChannelsPage, errors.SDKError) // Connect bulk connects things to channels specified by id. // // example: diff --git a/pkg/sdk/go/things.go b/pkg/sdk/go/things.go index 6ead5b66ca..353c05e551 100644 --- a/pkg/sdk/go/things.go +++ b/pkg/sdk/go/things.go @@ -256,7 +256,7 @@ func (sdk mfSDK) IdentifyThing(key string) (string, errors.SDKError) { return i.ID, nil } -func (sdk mfSDK) ShareThing(thingID, token string, req shareThingReq) errors.SDKError { +func (sdk mfSDK) ShareThing(thingID string, req UsersRelationRequest, token string) errors.SDKError { data, err := json.Marshal(req) if err != nil { return errors.NewSDKError(err) @@ -268,7 +268,7 @@ func (sdk mfSDK) ShareThing(thingID, token string, req shareThingReq) errors.SDK return sdkerr } -func (sdk mfSDK) UnshareThing(thingID, token string, req unshareThingReq) errors.SDKError { +func (sdk mfSDK) UnshareThing(thingID string, req UsersRelationRequest, token string) errors.SDKError { data, err := json.Marshal(req) if err != nil { return errors.NewSDKError(err) @@ -280,8 +280,11 @@ func (sdk mfSDK) UnshareThing(thingID, token string, req unshareThingReq) errors return sdkerr } -func (sdk mfSDK) ListThingUsers(token, thingID string) (UsersPage, errors.SDKError) { - url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, thingsEndpoint, thingID, usersEndpoint) +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 { diff --git a/tools/provision/provision.go b/tools/provision/provision.go index 20a1c20f29..86aafe20da 100644 --- a/tools/provision/provision.go +++ b/tools/provision/provision.go @@ -224,17 +224,19 @@ func Provision(conf Config) error { fmt.Printf("[[channels]]\nchannel_id = \"%s\"\n\n", cIDs[i]) } - for i := 0; i < conf.Num; i++ { - conIDs := sdk.Connection{ - ChannelID: cIDs[i], - ThingID: tIDs[i], - } - - if err := s.Connect(conIDs, token.AccessToken); err != nil { - log.Fatalf("Failed to connect thing %s to channel %s: %s", conIDs.ThingID, conIDs.ChannelID, err) + for _, cID := range cIDs { + for _, tID := range tIDs { + conIDs := sdk.Connection{ + ThingID: tID, + ChannelID: cID, + } + if err := s.Connect(conIDs, token.AccessToken); err != nil { + log.Fatalf("Failed to connect things %s to channels %s: %s", tID, cID, err) + } } } + return nil } From 26c455000ff08137cb99abee6a0c1809467c526d Mon Sep 17 00:00:00 2001 From: Arvindh Date: Mon, 16 Oct 2023 15:42:10 +0530 Subject: [PATCH 09/17] add: new SDK Signed-off-by: Arvindh --- pkg/sdk/go/policies.go | 0 pkg/sdk/go/policies_test.go | 1094 ----------------------------------- 2 files changed, 1094 deletions(-) delete mode 100644 pkg/sdk/go/policies.go delete mode 100644 pkg/sdk/go/policies_test.go diff --git a/pkg/sdk/go/policies.go b/pkg/sdk/go/policies.go deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pkg/sdk/go/policies_test.go b/pkg/sdk/go/policies_test.go deleted file mode 100644 index c5e1a4c753..0000000000 --- a/pkg/sdk/go/policies_test.go +++ /dev/null @@ -1,1094 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -// import ( -// "fmt" -// "net/http" -// "net/http/httptest" -// "testing" -// "time" - -// "github.com/go-zoo/bone" -// "github.com/mainflux/mainflux/internal/apiutil" -// mflog "github.com/mainflux/mainflux/logger" -// "github.com/mainflux/mainflux/pkg/errors" -// sdk "github.com/mainflux/mainflux/pkg/sdk/go" -// tclients "github.com/mainflux/mainflux/things/clients" -// tmocks "github.com/mainflux/mainflux/things/clients/mocks" -// tgmocks "github.com/mainflux/mainflux/things/groups/mocks" -// tpolicies "github.com/mainflux/mainflux/things/policies" -// tapi "github.com/mainflux/mainflux/things/policies/api/http" -// tpmocks "github.com/mainflux/mainflux/things/policies/mocks" -// uclients "github.com/mainflux/mainflux/users/clients" -// umocks "github.com/mainflux/mainflux/users/clients/mocks" -// "github.com/mainflux/mainflux/users/jwt" -// upolicies "github.com/mainflux/mainflux/users/policies" -// uapi "github.com/mainflux/mainflux/users/policies/api/http" -// upmocks "github.com/mainflux/mainflux/users/policies/mocks" -// "github.com/stretchr/testify/assert" -// "github.com/stretchr/testify/mock" -// ) - -// var utadminPolicy = umocks.SubjectSet{Subject: "things", Relation: []string{"g_add"}} - -// func newUsersPolicyServer(svc upolicies.Service) *httptest.Server { -// logger := mflog.NewMock() -// mux := bone.New() -// uapi.MakeHandler(svc, mux, logger) - -// return httptest.NewServer(mux) -// } - -// func newThingsPolicyServer(svc tclients.Service, psvc tpolicies.Service) *httptest.Server { -// logger := mflog.NewMock() -// mux := bone.New() -// tapi.MakeHandler(svc, psvc, mux, logger) - -// return httptest.NewServer(mux) -// } - -// func TestCreatePolicyUser(t *testing.T) { -// cRepo := new(umocks.Repository) -// pRepo := new(upmocks.Repository) -// tokenizer := jwt.NewRepository([]byte(secret), accessDuration, refreshDuration) - -// csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) -// svc := upolicies.NewService(pRepo, tokenizer, idProvider) -// ts := newUsersPolicyServer(svc) -// defer ts.Close() -// conf := sdk.Config{ -// UsersURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// clientPolicy := sdk.Policy{Object: object, Actions: []string{"m_write", "g_add"}, Subject: subject} - -// cases := []struct { -// desc string -// policy sdk.Policy -// page sdk.PolicyPage -// token string -// err errors.SDKError -// }{ -// { -// desc: "add new policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{}, -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// }, -// { -// desc: "add existing policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{Policies: []sdk.Policy{clientPolicy}}, -// token: generateValidToken(t, csvc, cRepo), -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, sdk.ErrFailedCreation), http.StatusInternalServerError), -// }, -// { -// desc: "add a new policy with owner", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// OwnerID: generateUUID(t), -// Object: "objwithowner", -// Actions: []string{"m_read"}, -// Subject: "subwithowner", -// }, -// err: nil, -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with more actions", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj2", -// Actions: []string{"c_delete", "c_update", "c_list"}, -// Subject: "sub2", -// }, -// err: nil, -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with wrong action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj3", -// Actions: []string{"wrong"}, -// Subject: "sub3", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with empty object", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Subject: "sub4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPolicyObj), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with empty subject", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Object: "obj4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPolicySub), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with empty action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Subject: "sub5", -// Object: "obj5", -// }, -// err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// } - -// for _, tc := range cases { -// repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 := pRepo.On("Save", mock.Anything, mock.Anything).Return(tc.err) -// err := mfsdk.CreateUserPolicy(tc.policy, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// if tc.err == nil { -// ok := repoCall1.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) -// } -// repoCall.Unset() -// repoCall1.Unset() -// } -// } - -// func TestAuthorizeUser(t *testing.T) { -// cRepo := new(umocks.Repository) -// pRepo := new(upmocks.Repository) -// tokenizer := jwt.NewRepository([]byte(secret), accessDuration, refreshDuration) - -// csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) -// svc := upolicies.NewService(pRepo, tokenizer, idProvider) -// ts := newUsersPolicyServer(svc) -// defer ts.Close() -// conf := sdk.Config{ -// UsersURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// cases := []struct { -// desc string -// policy sdk.AccessRequest -// page sdk.PolicyPage -// token string -// err errors.SDKError -// }{ -// { -// desc: "authorize a valid policy with client entity", -// policy: sdk.AccessRequest{ -// Subject: subject, -// Object: object, -// Action: "c_list", -// EntityType: "client", -// }, -// page: sdk.PolicyPage{}, -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// }, -// { -// desc: "authorize a valid policy with group entity", -// policy: sdk.AccessRequest{ -// Subject: subject, -// Object: object, -// Action: "g_add", -// EntityType: "group", -// }, -// page: sdk.PolicyPage{}, -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// }, -// { -// desc: "authorize a policy with wrong action", -// page: sdk.PolicyPage{}, -// policy: sdk.AccessRequest{ -// Object: "obj3", -// Action: "wrong", -// Subject: "sub3", -// EntityType: "client", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "authorize a policy with empty object", -// page: sdk.PolicyPage{}, -// policy: sdk.AccessRequest{ -// Action: "c_delete", -// Subject: "sub4", -// EntityType: "client", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPolicyObj), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "authorize a policy with empty subject", -// page: sdk.PolicyPage{}, -// policy: sdk.AccessRequest{ -// Action: "c_delete", -// Object: "obj4", -// EntityType: "client", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPolicySub), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "authorize a policy with empty action", -// page: sdk.PolicyPage{}, -// policy: sdk.AccessRequest{ -// Subject: "sub5", -// Object: "obj5", -// EntityType: "client", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// } - -// for _, tc := range cases { -// repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// ok, err := mfsdk.AuthorizeUser(tc.policy, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// if tc.err == nil { -// assert.True(t, ok, fmt.Sprintf("%s: expected true, got false", tc.desc)) -// ok := repoCall.Parent.AssertCalled(t, "CheckAdmin", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("CheckAdmin was not called on %s", tc.desc)) -// } -// repoCall.Unset() -// } -// } - -// func TestAssign(t *testing.T) { -// cRepo := new(umocks.Repository) -// pRepo := new(upmocks.Repository) -// tokenizer := jwt.NewRepository([]byte(secret), accessDuration, refreshDuration) - -// csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) -// svc := upolicies.NewService(pRepo, tokenizer, idProvider) -// ts := newUsersPolicyServer(svc) -// defer ts.Close() -// conf := sdk.Config{ -// UsersURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// clientPolicy := sdk.Policy{Object: object, Actions: []string{"m_write", "g_add"}, Subject: subject} - -// cases := []struct { -// desc string -// policy sdk.Policy -// page sdk.PolicyPage -// token string -// err errors.SDKError -// }{ -// { -// desc: "add new policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{}, -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// }, -// { -// desc: "add existing policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{Policies: []sdk.Policy{clientPolicy}}, -// token: generateValidToken(t, csvc, cRepo), -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, sdk.ErrFailedCreation), http.StatusInternalServerError), -// }, -// { -// desc: "add a new policy with owner", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// OwnerID: generateUUID(t), -// Object: "objwithowner", -// Actions: []string{"m_read"}, -// Subject: "subwithowner", -// }, -// err: nil, -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with more actions", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj2", -// Actions: []string{"c_delete", "c_update", "c_list"}, -// Subject: "sub2", -// }, -// err: nil, -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with wrong action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj3", -// Actions: []string{"wrong"}, -// Subject: "sub3", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with empty object", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Subject: "sub4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPolicyObj), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with empty subject", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Object: "obj4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPolicySub), http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// { -// desc: "add a new policy with empty action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Subject: "sub5", -// Object: "obj5", -// }, -// err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), -// token: generateValidToken(t, csvc, cRepo), -// }, -// } - -// for _, tc := range cases { -// repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 := pRepo.On("Save", mock.Anything, mock.Anything).Return(tc.err) -// err := mfsdk.Assign(tc.policy.Actions, tc.policy.Subject, tc.policy.Object, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// if tc.err == nil { -// ok := repoCall1.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) -// } -// repoCall.Unset() -// repoCall1.Unset() -// } -// } - -// func TestUpdatePolicy(t *testing.T) { -// cRepo := new(umocks.Repository) -// pRepo := new(upmocks.Repository) -// tokenizer := jwt.NewRepository([]byte(secret), accessDuration, refreshDuration) - -// csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) -// svc := upolicies.NewService(pRepo, tokenizer, idProvider) -// ts := newUsersPolicyServer(svc) -// defer ts.Close() - -// conf := sdk.Config{ -// UsersURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// policy := sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// } - -// cases := []struct { -// desc string -// action []string -// token string -// err errors.SDKError -// }{ -// { -// desc: "update policy actions with valid token", -// action: []string{"m_write", "m_read", "g_add"}, -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// }, -// { -// desc: "update policy action with invalid token", -// action: []string{"m_write"}, -// token: "non-existent", -// err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthentication, sdk.ErrInvalidJWT), http.StatusUnauthorized), -// }, -// { -// desc: "update policy action with wrong policy action", -// action: []string{"wrong"}, -// token: generateValidToken(t, csvc, cRepo), -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// }, -// } - -// for _, tc := range cases { -// policy.Actions = tc.action -// policy.CreatedAt = time.Now() -// repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 := pRepo.On("RetrieveAll", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(upolicies.PolicyPage{}, nil) -// repoCall2 := pRepo.On("Update", mock.Anything, mock.Anything).Return(tc.err) -// err := mfsdk.UpdateUserPolicy(policy, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// ok := repoCall1.Parent.AssertCalled(t, "Update", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) -// repoCall.Unset() -// repoCall1.Unset() -// repoCall2.Unset() -// } -// } - -// func TestUpdateThingsPolicy(t *testing.T) { -// cRepo := new(tmocks.Repository) -// gRepo := new(tgmocks.Repository) -// uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) -// thingCache := tmocks.NewCache() -// policiesCache := tpmocks.NewCache() - -// pRepo := new(tpmocks.Repository) -// psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) - -// svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) -// ts := newThingsPolicyServer(svc, psvc) -// defer ts.Close() - -// conf := sdk.Config{ -// ThingsURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// policy := sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// } - -// cases := []struct { -// desc string -// action []string -// token string -// err errors.SDKError -// }{ -// { -// desc: "update policy actions with valid token", -// action: []string{"m_write", "m_read"}, -// token: adminToken, -// err: nil, -// }, -// { -// desc: "update policy action with invalid token", -// action: []string{"m_write"}, -// token: "non-existent", -// err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthorization, errors.ErrAuthentication), http.StatusUnauthorized), -// }, -// { -// desc: "update policy action with wrong policy action", -// action: []string{"wrong"}, -// token: adminToken, -// err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), -// }, -// } - -// for _, tc := range cases { -// policy.Actions = tc.action -// policy.CreatedAt = time.Now() -// repoCall := pRepo.On("RetrieveAll", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tpolicies.PolicyPage{}, nil) -// repoCall1 := pRepo.On("Update", mock.Anything, mock.Anything).Return(tpolicies.Policy{}, tc.err) -// err := mfsdk.UpdateThingPolicy(policy, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// ok := repoCall.Parent.AssertCalled(t, "Update", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) -// repoCall.Unset() -// repoCall1.Unset() -// } -// } - -// func TestListPolicies(t *testing.T) { -// cRepo := new(umocks.Repository) -// pRepo := new(upmocks.Repository) -// tokenizer := jwt.NewRepository([]byte(secret), accessDuration, refreshDuration) - -// csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) -// svc := upolicies.NewService(pRepo, tokenizer, idProvider) -// ts := newUsersPolicyServer(svc) -// defer ts.Close() - -// conf := sdk.Config{ -// UsersURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) -// id := generateUUID(t) - -// nPolicy := uint64(10) -// aPolicies := []sdk.Policy{} -// for i := uint64(0); i < nPolicy; i++ { -// pr := sdk.Policy{ -// OwnerID: id, -// Actions: []string{"m_read"}, -// Subject: fmt.Sprintf("thing_%d", i), -// Object: fmt.Sprintf("client_%d", i), -// } -// if i%3 == 0 { -// pr.Actions = []string{"m_write"} -// } -// aPolicies = append(aPolicies, pr) -// } - -// cases := []struct { -// desc string -// token string -// page sdk.PageMetadata -// response []sdk.Policy -// err errors.SDKError -// }{ -// { -// desc: "list policies with authorized token", -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// response: aPolicies, -// }, -// { -// desc: "list policies with invalid token", -// token: invalidToken, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthentication, sdk.ErrInvalidJWT), http.StatusUnauthorized), -// response: []sdk.Policy(nil), -// }, -// { -// desc: "list policies with offset and limit", -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// page: sdk.PageMetadata{ -// Offset: 6, -// Limit: nPolicy, -// }, -// response: aPolicies[6:10], -// }, -// { -// desc: "list policies with given name", -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// page: sdk.PageMetadata{ -// Offset: 6, -// Limit: nPolicy, -// }, -// response: aPolicies[6:10], -// }, -// { -// desc: "list policies with given identifier", -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// page: sdk.PageMetadata{ -// Offset: 6, -// Limit: nPolicy, -// }, -// response: aPolicies[6:10], -// }, -// { -// desc: "list policies with given ownerID", -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// page: sdk.PageMetadata{ -// Offset: 6, -// Limit: nPolicy, -// }, -// response: aPolicies[6:10], -// }, -// { -// desc: "list policies with given subject", -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// page: sdk.PageMetadata{ -// Offset: 6, -// Limit: nPolicy, -// }, -// response: aPolicies[6:10], -// }, -// { -// desc: "list policies with given object", -// token: generateValidToken(t, csvc, cRepo), -// err: nil, -// page: sdk.PageMetadata{ -// Offset: 6, -// Limit: nPolicy, -// }, -// response: aPolicies[6:10], -// }, -// { -// desc: "list policies with wrong action", -// token: generateValidToken(t, csvc, cRepo), -// page: sdk.PageMetadata{ -// Action: "wrong", -// }, -// response: []sdk.Policy(nil), -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// }, -// } - -// for _, tc := range cases { -// repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 := pRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: tc.response}), tc.err) -// pp, err := mfsdk.ListUserPolicies(tc.page, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// assert.Equal(t, tc.response, pp.Policies, fmt.Sprintf("%s: expected %v, got %v", tc.desc, tc.response, pp)) -// ok := repoCall.Parent.AssertCalled(t, "RetrieveAll", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("RetrieveAll was not called on %s", tc.desc)) -// repoCall.Unset() -// repoCall1.Unset() -// } -// } - -// func TestDeletePolicy(t *testing.T) { -// cRepo := new(umocks.Repository) -// pRepo := new(upmocks.Repository) -// tokenizer := jwt.NewRepository([]byte(secret), accessDuration, refreshDuration) - -// csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) -// svc := upolicies.NewService(pRepo, tokenizer, idProvider) -// ts := newUsersPolicyServer(svc) -// defer ts.Close() - -// conf := sdk.Config{ -// UsersURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// sub := generateUUID(t) -// pr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} -// cpr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} - -// repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 := pRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: []sdk.Policy{cpr}}), nil) -// repoCall2 := pRepo.On("Delete", mock.Anything, mock.Anything).Return(nil) -// err := mfsdk.DeleteUserPolicy(pr, generateValidToken(t, csvc, cRepo)) -// assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) -// ok := repoCall1.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on valid policy") -// repoCall2.Unset() -// repoCall1.Unset() -// repoCall.Unset() - -// repoCall = pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 = pRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: []sdk.Policy{cpr}}), nil) -// repoCall2 = pRepo.On("Delete", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) -// err = mfsdk.DeleteUserPolicy(pr, invalidToken) -// assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthentication, sdk.ErrInvalidJWT), http.StatusUnauthorized), fmt.Sprintf("expected %v got %s", pr, err)) -// ok = repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on invalid policy") -// repoCall2.Unset() -// repoCall1.Unset() -// repoCall.Unset() -// } - -// func TestUnassign(t *testing.T) { -// cRepo := new(umocks.Repository) -// pRepo := new(upmocks.Repository) -// tokenizer := jwt.NewRepository([]byte(secret), accessDuration, refreshDuration) - -// csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) -// svc := upolicies.NewService(pRepo, tokenizer, idProvider) -// ts := newUsersPolicyServer(svc) -// defer ts.Close() - -// conf := sdk.Config{ -// UsersURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// sub := generateUUID(t) -// pr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} -// cpr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} - -// repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 := pRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: []sdk.Policy{cpr}}), nil) -// repoCall2 := pRepo.On("Delete", mock.Anything, mock.Anything).Return(nil) -// err := mfsdk.Unassign(pr.Subject, pr.Object, generateValidToken(t, csvc, cRepo)) -// assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) -// ok := repoCall1.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on valid policy") -// repoCall2.Unset() -// repoCall1.Unset() -// repoCall.Unset() - -// repoCall = pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) -// repoCall1 = pRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: []sdk.Policy{cpr}}), nil) -// repoCall2 = pRepo.On("Delete", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) -// err = mfsdk.Unassign(pr.Subject, pr.Object, invalidToken) -// assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthentication, sdk.ErrInvalidJWT), http.StatusUnauthorized), fmt.Sprintf("expected %v got %s", pr, err)) -// ok = repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on invalid policy") -// repoCall2.Unset() -// repoCall1.Unset() -// repoCall.Unset() -// } - -// func TestConnect(t *testing.T) { -// cRepo := new(tmocks.Repository) -// gRepo := new(tgmocks.Repository) -// uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) -// thingCache := tmocks.NewCache() -// policiesCache := tpmocks.NewCache() - -// pRepo := new(tpmocks.Repository) -// psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) - -// svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) -// ts := newThingsPolicyServer(svc, psvc) -// defer ts.Close() - -// conf := sdk.Config{ -// ThingsURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// clientPolicy := sdk.Policy{Object: object, Actions: []string{"m_write", "g_add"}, Subject: subject} - -// cases := []struct { -// desc string -// policy sdk.Policy -// page sdk.PolicyPage -// token string -// err errors.SDKError -// tcerr errors.SDKError -// }{ -// { -// desc: "add new policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{}, -// token: adminToken, -// err: nil, -// tcerr: nil, -// }, -// { -// desc: "add existing policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{Policies: []sdk.Policy{clientPolicy}}, -// token: adminToken, -// err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusInternalServerError), -// tcerr: errors.NewSDKError(sdk.ErrFailedCreation), -// }, -// { -// desc: "add a new policy with owner", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// OwnerID: generateUUID(t), -// Object: "objwithowner", -// Actions: []string{"m_read"}, -// Subject: "subwithowner", -// }, -// err: nil, -// tcerr: nil, -// token: adminToken, -// }, -// { -// desc: "add a new policy with more actions", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj2", -// Actions: []string{"c_delete", "c_update", "c_list"}, -// Subject: "sub2", -// }, -// err: nil, -// tcerr: nil, -// token: adminToken, -// }, -// { -// desc: "add a new policy with wrong action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj3", -// Actions: []string{"wrong"}, -// Subject: "sub3", -// }, -// err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), -// tcerr: errors.NewSDKError(apiutil.ErrMalformedPolicyAct), -// token: adminToken, -// }, -// { -// desc: "add a new policy with empty object", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Subject: "sub4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), -// tcerr: errors.NewSDKError(apiutil.ErrMissingID), -// token: adminToken, -// }, -// { -// desc: "add a new policy with empty subject", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Object: "obj4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), -// tcerr: errors.NewSDKError(apiutil.ErrMissingID), -// token: adminToken, -// }, -// { -// desc: "add a new policy with empty action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Subject: "sub5", -// Object: "obj5", -// }, -// err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), -// tcerr: errors.NewSDKError(apiutil.ErrMalformedPolicyAct), -// token: adminToken, -// }, -// } - -// for _, tc := range cases { -// repoCall := pRepo.On("Save", mock.Anything, mock.Anything).Return(convertThingPolicy(tc.policy), tc.tcerr) -// conn := sdk.ConnectionIDs{ChannelIDs: []string{tc.policy.Object}, ThingIDs: []string{tc.policy.Subject}, Actions: tc.policy.Actions} -// err := mfsdk.Connect(conn, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// if tc.err == nil { -// ok := repoCall.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) -// } -// repoCall.Unset() -// } -// } - -// func TestConnectThing(t *testing.T) { -// cRepo := new(tmocks.Repository) -// gRepo := new(tgmocks.Repository) -// uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) -// thingCache := tmocks.NewCache() -// policiesCache := tpmocks.NewCache() - -// pRepo := new(tpmocks.Repository) -// psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) - -// svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) -// ts := newThingsPolicyServer(svc, psvc) -// defer ts.Close() - -// conf := sdk.Config{ -// ThingsURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// clientPolicy := sdk.Policy{Object: object, Actions: []string{"m_write", "g_add"}, Subject: subject} - -// cases := []struct { -// desc string -// policy sdk.Policy -// page sdk.PolicyPage -// token string -// err errors.SDKError -// }{ -// { -// desc: "add new policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{}, -// token: adminToken, -// err: nil, -// }, -// { -// desc: "add existing policy", -// policy: sdk.Policy{ -// Subject: subject, -// Object: object, -// Actions: []string{"m_write", "g_add"}, -// }, -// page: sdk.PolicyPage{Policies: []sdk.Policy{clientPolicy}}, -// token: adminToken, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, sdk.ErrFailedCreation), http.StatusInternalServerError), -// }, -// { -// desc: "add a new policy with owner", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// OwnerID: generateUUID(t), -// Object: "objwithowner", -// Actions: []string{"m_read"}, -// Subject: "subwithowner", -// }, -// err: nil, -// token: adminToken, -// }, -// { -// desc: "add a new policy with more actions", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj2", -// Actions: []string{"c_delete", "c_update", "c_list"}, -// Subject: "sub2", -// }, -// err: nil, -// token: adminToken, -// }, -// { -// desc: "add a new policy with wrong action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Object: "obj3", -// Actions: []string{"wrong"}, -// Subject: "sub3", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// token: adminToken, -// }, -// { -// desc: "add a new policy with empty object", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Subject: "sub4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), -// token: adminToken, -// }, -// { -// desc: "add a new policy with empty subject", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Actions: []string{"c_delete"}, -// Object: "obj4", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), -// token: adminToken, -// }, -// { -// desc: "add a new policy with empty action", -// page: sdk.PolicyPage{}, -// policy: sdk.Policy{ -// Subject: "sub5", -// Object: "obj5", -// }, -// err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicyAct), http.StatusInternalServerError), -// token: adminToken, -// }, -// } - -// for _, tc := range cases { -// repoCall := pRepo.On("Save", mock.Anything, mock.Anything).Return(convertThingPolicy(tc.policy), tc.err) -// err := mfsdk.ConnectThing(tc.policy.Subject, tc.policy.Object, tc.token) -// assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) -// if tc.err == nil { -// ok := repoCall.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) -// assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) -// } -// repoCall.Unset() -// } -// } - -// func TestDisconnectThing(t *testing.T) { -// cRepo := new(tmocks.Repository) -// gRepo := new(tgmocks.Repository) -// uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) -// thingCache := tmocks.NewCache() -// policiesCache := tpmocks.NewCache() - -// pRepo := new(tpmocks.Repository) -// psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) - -// svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) -// ts := newThingsPolicyServer(svc, psvc) -// defer ts.Close() - -// conf := sdk.Config{ -// ThingsURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// sub := generateUUID(t) -// pr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} - -// repoCall := pRepo.On("Delete", mock.Anything, mock.Anything).Return(nil) -// err := mfsdk.DisconnectThing(pr.Subject, pr.Object, adminToken) -// assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) -// ok := repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on valid policy") -// repoCall.Unset() - -// repoCall = pRepo.On("Delete", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) -// err = mfsdk.DisconnectThing(pr.Subject, pr.Object, invalidToken) -// assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthorization, errors.ErrAuthentication), http.StatusUnauthorized), fmt.Sprintf("expected %v got %s", pr, err)) -// ok = repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on invalid policy") -// repoCall.Unset() -// } - -// func TestDisconnect(t *testing.T) { -// cRepo := new(tmocks.Repository) -// gRepo := new(tgmocks.Repository) -// uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) -// thingCache := tmocks.NewCache() -// policiesCache := tpmocks.NewCache() - -// pRepo := new(tpmocks.Repository) -// psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) - -// svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) -// ts := newThingsPolicyServer(svc, psvc) -// defer ts.Close() - -// conf := sdk.Config{ -// ThingsURL: ts.URL, -// } -// mfsdk := sdk.NewSDK(conf) - -// sub := generateUUID(t) -// pr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} - -// repoCall := pRepo.On("Delete", mock.Anything, mock.Anything).Return(nil) -// conn := sdk.ConnectionIDs{ChannelIDs: []string{pr.Object}, ThingIDs: []string{pr.Subject}} -// err := mfsdk.Disconnect(conn, adminToken) -// assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) -// ok := repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on valid policy") -// repoCall.Unset() - -// repoCall = pRepo.On("Delete", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) -// conn = sdk.ConnectionIDs{ChannelIDs: []string{pr.Object}, ThingIDs: []string{pr.Subject}} -// err = mfsdk.Disconnect(conn, invalidToken) -// assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.Wrap(errors.ErrAuthorization, errors.ErrAuthentication), http.StatusUnauthorized), fmt.Sprintf("expected %v got %s", pr, err)) -// ok = repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) -// assert.True(t, ok, "Delete was not called on invalid policy") -// repoCall.Unset() -// } - From 7de68790cb1c98c73307125ff80e4cbc4696b136 Mon Sep 17 00:00:00 2001 From: Arvindh Date: Mon, 16 Oct 2023 16:28:57 +0530 Subject: [PATCH 10/17] fix: comment Signed-off-by: Arvindh --- users/api/endpoints.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/api/endpoints.go b/users/api/endpoints.go index 369bdce036..d8a8f43e30 100644 --- a/users/api/endpoints.go +++ b/users/api/endpoints.go @@ -122,7 +122,7 @@ func listMembersByGroupEndpoint(svc users.Service) endpoint.Endpoint { func listMembersByChannelEndpoint(svc users.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(listMembersByObjectReq) - // in spiceDB channel is group kind + // 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) From 35c7a59bbf097d6b71eb89f4c96c792bddcdd89f Mon Sep 17 00:00:00 2001 From: Arvindh Date: Mon, 16 Oct 2023 17:05:22 +0530 Subject: [PATCH 11/17] fix: sdk function names Signed-off-by: Arvindh --- pkg/sdk/go/channels.go | 8 ++++---- pkg/sdk/go/groups.go | 4 ++-- pkg/sdk/go/sdk.go | 36 ++++++++++++++++++------------------ 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/pkg/sdk/go/channels.go b/pkg/sdk/go/channels.go index 7a041d755b..dfa405bfff 100644 --- a/pkg/sdk/go/channels.go +++ b/pkg/sdk/go/channels.go @@ -146,7 +146,7 @@ func (sdk mfSDK) UpdateChannel(c Channel, token string) (Channel, errors.SDKErro return c, nil } -func (sdk mfSDK) AddUsersToChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError { +func (sdk mfSDK) AddUserToChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError { data, err := json.Marshal(req) if err != nil { return errors.NewSDKError(err) @@ -158,7 +158,7 @@ func (sdk mfSDK) AddUsersToChannel(channelID string, req UsersRelationRequest, t return sdkerr } -func (sdk mfSDK) RemoveUsersFromChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError { +func (sdk mfSDK) RemoveUserFromChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError { data, err := json.Marshal(req) if err != nil { return errors.NewSDKError(err) @@ -187,7 +187,7 @@ func (sdk mfSDK) ListChannelUsers(channelID string, pm PageMetadata, token strin return up, nil } -func (sdk mfSDK) AddUserGroupsToChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError { +func (sdk mfSDK) AddUserGroupToChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError { data, err := json.Marshal(req) if err != nil { return errors.NewSDKError(err) @@ -199,7 +199,7 @@ func (sdk mfSDK) AddUserGroupsToChannel(channelID string, req UserGroupsRequest, return sdkerr } -func (sdk mfSDK) RemoveUserGroupsToChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError { +func (sdk mfSDK) RemoveUserGroupFromChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError { data, err := json.Marshal(req) if err != nil { return errors.NewSDKError(err) diff --git a/pkg/sdk/go/groups.go b/pkg/sdk/go/groups.go index 4b61587237..5973677d2a 100644 --- a/pkg/sdk/go/groups.go +++ b/pkg/sdk/go/groups.go @@ -145,7 +145,7 @@ func (sdk mfSDK) DisableGroup(id, token string) (Group, errors.SDKError) { return sdk.changeGroupStatus(id, disableEndpoint, token) } -func (sdk mfSDK) AddUsersToGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError { +func (sdk mfSDK) AddUserToGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError { data, err := json.Marshal(req) if err != nil { return errors.NewSDKError(err) @@ -157,7 +157,7 @@ func (sdk mfSDK) AddUsersToGroup(groupID string, req UsersRelationRequest, token return sdkerr } -func (sdk mfSDK) RemoveUsersToGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError { +func (sdk mfSDK) RemoveUserFromGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError { data, err := json.Marshal(req) if err != nil { return errors.NewSDKError(err) diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go index 6d273042da..594302a288 100644 --- a/pkg/sdk/go/sdk.go +++ b/pkg/sdk/go/sdk.go @@ -530,27 +530,27 @@ type SDK interface { // fmt.Println(group) DisableGroup(id, token string) (Group, errors.SDKError) - // AddUsersToGroup add users to a group. + // 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.AddUsersToGroup("groupID",req, "token") + // group, _ := sdk.AddUserToGroup("groupID",req, "token") // fmt.Println(group) - AddUsersToGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError + AddUserToGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError - // RemoveUsersToGroup remove users to a group. + // 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.RemoveUsersToGroup("groupID",req, "token") + // group, _ := sdk.RemoveUserFromGroup("groupID",req, "token") // fmt.Println(group) - RemoveUsersToGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError + RemoveUserFromGroup(groupID string, req UsersRelationRequest, token string) errors.SDKError // ListGroupUsers list all users in the group id . // @@ -669,27 +669,27 @@ type SDK interface { // fmt.Println(channel) DisableChannel(id, token string) (Channel, errors.SDKError) - // AddUsersToChannel add users to a channel. + // AddUserToChannel add user to a channel. // // example: // req := sdk.UsersRelationRequest{ // Relation: "viewer", // available options: "owner", "admin", "editor", "viewer" // UserIDs: ["user_id_1", "user_id_2", "user_id_3"] // } - // err := sdk.AddUsersToChannel("channel_id", req, "token") + // err := sdk.AddUserToChannel("channel_id", req, "token") // fmt.Println(err) - AddUsersToChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError + AddUserToChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError - // RemoveUsersFromChannel remove users from a group. + // RemoveUserFromChannel 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"] // } - // err := sdk.RemoveUsersFromChannel("channel_id", req, "token") + // err := sdk.RemoveUserFromChannel("channel_id", req, "token") // fmt.Println(err) - RemoveUsersFromChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError + RemoveUserFromChannel(channelID string, req UsersRelationRequest, token string) errors.SDKError // ListChannelUsers list all users in a channel . // @@ -703,25 +703,25 @@ type SDK interface { // fmt.Println(users) ListChannelUsers(channelID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) - // AddUserGroupsToChannel add user group to a channel. + // AddUserGroupToChannel add user group to a channel. // // example: // req := sdk.UserGroupsRequest{ // GroupsIDs: ["group_id_1", "group_id_2", "group_id_3"] // } - // err := sdk.AddUserGroupsToChannel("channel_id",req, "token") + // err := sdk.AddUserGroupToChannel("channel_id",req, "token") // fmt.Println(err) - AddUserGroupsToChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError + AddUserGroupToChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError - // RemoveUserGroupsToChannel remove user group from a channel. + // RemoveUserGroupFromChannel remove user group from a channel. // // example: // req := sdk.UserGroupsRequest{ // GroupsIDs: ["group_id_1", "group_id_2", "group_id_3"] // } - // err := sdk.RemoveUserGroupsToChannel("channel_id",req, "token") + // err := sdk.RemoveUserGroupFromChannel("channel_id",req, "token") // fmt.Println(err) - RemoveUserGroupsToChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError + RemoveUserGroupFromChannel(channelID string, req UserGroupsRequest, token string) errors.SDKError // ListChannelUserGroups list all user groups in a channel. // From a202c25297506c170eb0886d73119131a3409126 Mon Sep 17 00:00:00 2001 From: Arvindh Date: Mon, 16 Oct 2023 18:59:08 +0530 Subject: [PATCH 12/17] update: api spec Signed-off-by: Arvindh --- api/openapi/users.yml | 54 ------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/api/openapi/users.yml b/api/openapi/users.yml index 381e1718a7..a9e2ba57c2 100644 --- a/api/openapi/users.yml +++ b/api/openapi/users.yml @@ -690,60 +690,6 @@ paths: "500": $ref: "#/components/responses/ServiceError" - /groups/{groupID}/members/assign: - post: - summary: Assigns a member to a group - description: | - Assigns a specific member to a group that is identifier by the group ID. - tags: - - Groups - parameters: - - $ref: "#/components/parameters/GroupID" - requestBody: - $ref: "#/components/requestBodies/AssignReq" - security: - - bearerAuth: [] - responses: - "200": - description: Member assigned. - "400": - description: Failed due to malformed group's ID. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /groups/{groupID}/members/unassign: - post: - summary: Unassigns a member to a group - description: | - Unassigns a specific member to a group that is identifier by the group ID. - tags: - - Groups - parameters: - - $ref: "#/components/parameters/GroupID" - requestBody: - $ref: "#/components/requestBodies/AssignReq" - security: - - bearerAuth: [] - responses: - "200": - description: Member assigned. - "400": - description: Failed due to malformed group's ID. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /groups/{groupID}/users/assign: post: summary: Assigns a user to a group From 03bc3a94a70b03f8858469eafafeaccfa307f974 Mon Sep 17 00:00:00 2001 From: Arvindh Date: Mon, 16 Oct 2023 20:40:55 +0530 Subject: [PATCH 13/17] fix: channels connect request Signed-off-by: Arvindh --- things/api/http/requests.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/things/api/http/requests.go b/things/api/http/requests.go index 4b4e8e4425..6fc6616289 100644 --- a/things/api/http/requests.go +++ b/things/api/http/requests.go @@ -367,8 +367,8 @@ func (req unassignUserGroupsRequest) validate() error { type connectChannelThingRequest struct { token string - ThingID string - ChannelID string + ThingID string `json:"thing_id,omitempty"` + ChannelID string `json:"channel_id,omitempty"` } func (req *connectChannelThingRequest) validate() error { @@ -379,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 { From 3d8a097951e30729b7f08ba370b5dc5137025d6e Mon Sep 17 00:00:00 2001 From: Arvindh Date: Tue, 17 Oct 2023 14:00:55 +0530 Subject: [PATCH 14/17] fix: listing of clients and groups Signed-off-by: Arvindh --- internal/groups/service.go | 7 +++---- things/service.go | 5 +++-- users/service.go | 41 -------------------------------------- 3 files changed, 6 insertions(+), 47 deletions(-) diff --git a/internal/groups/service.go b/internal/groups/service.go index 40da6b1ee6..4c7f19d657 100644 --- a/internal/groups/service.go +++ b/internal/groups/service.go @@ -217,10 +217,9 @@ func (svc service) ListGroups(ctx context.Context, token string, memberKind, mem } if len(ids) <= 0 { - return groups.Page{}, errors.ErrNotFound - } - if len(ids) <= 0 { - return groups.Page{}, errors.ErrNotFound + return groups.Page{ + PageMeta: gm.PageMeta, + }, nil } return svc.groups.RetrieveByIDs(ctx, gm, ids...) } diff --git a/things/service.go b/things/service.go index e12037f183..8c04375b35 100644 --- a/things/service.go +++ b/things/service.go @@ -162,7 +162,9 @@ func (svc service) ListClients(ctx context.Context, token string, reqUserID stri } if len(ids) <= 0 { - return mfclients.ClientsPage{}, errors.ErrNotFound + return mfclients.ClientsPage{ + Page: mfclients.Page{Total: 0, Limit: pm.Limit, Offset: pm.Offset}, + }, nil } pm.IDs = ids @@ -171,7 +173,6 @@ func (svc service) ListClients(ctx context.Context, token string, reqUserID stri } func (svc service) listClientIDs(ctx context.Context, userID, permission string) ([]string, error) { - tids, err := svc.auth.ListAllObjects(ctx, &mainflux.ListObjectsReq{ SubjectType: userType, Subject: userID, diff --git a/users/service.go b/users/service.go index 52d1bb8396..43b1e5a66d 100644 --- a/users/service.go +++ b/users/service.go @@ -147,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 } From 571fdafc155eee59ffd571cf22f92a7e98110bb6 Mon Sep 17 00:00:00 2001 From: Arvindh Date: Tue, 17 Oct 2023 15:29:55 +0530 Subject: [PATCH 15/17] fix: CLI Signed-off-by: Arvindh --- cli/channels.go | 142 ++++++++++++++++++++++++- cli/groups.go | 61 ++++++----- cli/policies.go | 228 ----------------------------------------- cli/things.go | 58 +++++++++-- cli/users.go | 82 ++++++++++++++- cmd/cli/main.go | 2 - pkg/sdk/go/requests.go | 5 +- pkg/sdk/go/sdk.go | 13 ++- 8 files changed, 316 insertions(+), 275 deletions(-) delete mode 100644 cli/policies.go diff --git a/cli/channels.go b/cli/channels.go index 729eb49cb0..6af6ec6cd9 100644 --- a/cli/channels.go +++ b/cli/channels.go @@ -166,12 +166,152 @@ var cmdChannels = []cobra.Command{ logJSON(channel) }, }, + { + Use: "assign user ", + Short: "Assign user", + Long: "Assign user to a channel\n" + + "Usage:\n" + + "\tmainflux-cli channels assign user '[\"\", \"\"]' $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 5 { + logUsage(cmd.Use) + return + } + var userIDs []string + if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { + logError(err) + return + } + if err := sdk.AddUserToChannel(args[2], mfxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { + logError(err) + return + } + logOK() + }, + }, + { + Use: "unassign user ", + Short: "Unassign user", + Long: "Unassign user from a channel\n" + + "Usage:\n" + + "\tmainflux-cli channels unassign user '[\"\", \"\"]' $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 5 { + logUsage(cmd.Use) + return + } + var userIDs []string + if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { + logError(err) + return + } + if err := sdk.RemoveUserFromChannel(args[2], mfxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { + logError(err) + return + } + logOK() + }, + }, + { + Use: "assign group ", + Short: "Assign group", + Long: "Assign group to a channel\n" + + "Usage:\n" + + "\tmainflux-cli channels assign group '[\"\", \"\"]' $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 5 { + logUsage(cmd.Use) + return + } + var groupIDs []string + if err := json.Unmarshal([]byte(args[0]), &groupIDs); err != nil { + logError(err) + return + } + if err := sdk.AddUserGroupToChannel(args[1], mfxsdk.UserGroupsRequest{UserGroupIDs: groupIDs}, args[2]); err != nil { + logError(err) + return + } + logOK() + }, + }, + { + Use: "unassign group ", + Short: "Unassign group", + Long: "Unassign group from a channel\n" + + "Usage:\n" + + "\tmainflux-cli channels unassign group '[\"\", \"\"]' $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 5 { + logUsage(cmd.Use) + return + } + var groupIDs []string + if err := json.Unmarshal([]byte(args[0]), &groupIDs); err != nil { + logError(err) + return + } + if err := sdk.RemoveUserGroupFromChannel(args[1], mfxsdk.UserGroupsRequest{UserGroupIDs: groupIDs}, args[2]); err != nil { + logError(err) + return + } + logOK() + }, + }, + { + Use: "users ", + Short: "List users", + Long: "List users of a channel\n" + + "Usage:\n" + + "\tmainflux-cli channels users $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + logUsage(cmd.Use) + return + } + pm := mfxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + ul, err := sdk.ListChannelUsers(args[0], pm, args[1]) + if err != nil { + logError(err) + return + } + + logJSON(ul) + }, + }, + { + Use: "groups ", + Short: "List groups", + Long: "List groups of a channel\n" + + "Usage:\n" + + "\tmainflux-cli channels groups $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + logUsage(cmd.Use) + return + } + pm := mfxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + ul, err := sdk.ListChannelUserGroups(args[0], pm, args[1]) + if err != nil { + logError(err) + return + } + + logJSON(ul) + }, + }, } // NewChannelsCmd returns channels command. func NewChannelsCmd() *cobra.Command { cmd := cobra.Command{ - Use: "channels [create | get | update | delete | connections | not-connected]", + Use: "channels [create | get | update | delete | connections | not-connected | assign | unassign | users | groups]", Short: "Channels management", Long: `Channels management: create, get, update or delete Channel and get list of Things connected or not connected to a Channel`, } diff --git a/cli/groups.go b/cli/groups.go index 1af24912df..2f73cb02a0 100644 --- a/cli/groups.go +++ b/cli/groups.go @@ -142,22 +142,22 @@ var cmdGroups = []cobra.Command{ }, }, { - Use: "assign ", - Short: "Assign member", - Long: "Assign members to a group\n" + + Use: "assign user ", + Short: "Assign user", + Long: "Assign user to a group\n" + "Usage:\n" + - "\tmainflux-cli groups assign '[\"\", \"\"]' $USERTOKEN\n", + "\tmainflux-cli groups assign user '[\"\", \"\"]' $USERTOKEN\n", Run: func(cmd *cobra.Command, args []string) { - if len(args) != 4 { + if len(args) != 5 { logUsage(cmd.Use) return } - var actions []string - if err := json.Unmarshal([]byte(args[0]), &actions); err != nil { + var userIDs []string + if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { logError(err) return } - if err := sdk.Assign(actions, args[1], args[2], args[3]); err != nil { + if err := sdk.AddUserToGroup(args[2], mfxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { logError(err) return } @@ -165,29 +165,35 @@ var cmdGroups = []cobra.Command{ }, }, { - Use: "unassign ", - Short: "Unassign member", - Long: "Unassign member from a group\n" + + Use: "unassign user ", + Short: "Unassign user", + Long: "Unassign user from a group\n" + "Usage:\n" + - "\tmainflux-cli groups unassign $USERTOKEN\n", + "\tmainflux-cli groups unassign user '[\"\", \"\"]' $USERTOKEN\n", Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { + if len(args) != 5 { logUsage(cmd.Use) return } - if err := sdk.Unassign(args[0], args[1], args[2]); err != nil { + var userIDs []string + if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { + logError(err) + return + } + if err := sdk.RemoveUserFromGroup(args[2], mfxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { logError(err) return } logOK() }, }, + { - Use: "members ", - Short: "Members list", - Long: "List group's members\n" + + Use: "users ", + Short: "List users", + Long: "List users in a group\n" + "Usage:\n" + - "\tmainflux-cli groups members $USERTOKEN", + "\tmainflux-cli groups users $USERTOKEN", Run: func(cmd *cobra.Command, args []string) { if len(args) != 2 { logUsage(cmd.Use) @@ -198,20 +204,20 @@ var cmdGroups = []cobra.Command{ Limit: Limit, Status: Status, } - up, err := sdk.Members(args[0], pm, args[1]) + users, err := sdk.ListGroupUsers(args[0], pm, args[1]) if err != nil { logError(err) return } - logJSON(up) + logJSON(users) }, }, { - Use: "membership ", - Short: "Membership list", - Long: "List memberships of a member\n" + + Use: "channels ", + Short: "List channels", + Long: "List channels in a group\n" + "Usage:\n" + - "\tmainflux-cli groups membership $USERTOKEN", + "\tmainflux-cli groups channels $USERTOKEN", Run: func(cmd *cobra.Command, args []string) { if len(args) != 2 { logUsage(cmd.Use) @@ -220,13 +226,14 @@ var cmdGroups = []cobra.Command{ pm := mfxsdk.PageMetadata{ Offset: Offset, Limit: Limit, + Status: Status, } - up, err := sdk.Memberships(args[0], pm, args[1]) + channels, err := sdk.ListGroupChannels(args[0], pm, args[1]) if err != nil { logError(err) return } - logJSON(up) + logJSON(channels) }, }, { @@ -276,7 +283,7 @@ var cmdGroups = []cobra.Command{ // NewGroupsCmd returns users command. func NewGroupsCmd() *cobra.Command { cmd := cobra.Command{ - Use: "groups [create | get | update | delete | assign | unassign | members | membership]", + Use: "groups [create | get | update | delete | assign | unassign | users | channels ]", Short: "Groups management", Long: `Groups management: create, update, delete group and assign and unassign member to groups"`, } diff --git a/cli/policies.go b/cli/policies.go deleted file mode 100644 index 9f784f7439..0000000000 --- a/cli/policies.go +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (c) Mainflux -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import ( - "encoding/json" - - mfxsdk "github.com/mainflux/mainflux/pkg/sdk/go" - "github.com/spf13/cobra" -) - -const ( - users = "users" - things = "things" -) - -var cmdPolicies = []cobra.Command{ - { - Use: "create [ users | things ] ", - Short: "Create policy", - Long: "Create a new policy\n" + - "Usage:\n" + - "\tmainflux-cli policies create users '[\"c_list\"]' $USERTOKEN\n" + - "\tmainflux-cli policies create things '[\"m_write\"]' $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { - logUsage(cmd.Use) - return - } - - var actions []string - if err := json.Unmarshal([]byte(args[3]), &actions); err != nil { - logError(err) - return - } - - policy := mfxsdk.Policy{ - Subject: args[1], - Object: args[2], - Actions: actions, - } - - switch args[0] { - case things: - if err := sdk.CreateThingPolicy(policy, args[4]); err != nil { - logError(err) - return - } - case users: - if err := sdk.CreateUserPolicy(policy, args[4]); err != nil { - logError(err) - return - } - default: - logUsage(cmd.Use) - } - }, - }, - { - Use: "update [ users | things ] ", - Short: "Update policy", - Long: "Update policy\n" + - "Usage:\n" + - "\tmainflux-cli policies update users '[\"c_list\"]' $USERTOKEN\n" + - "\tmainflux-cli policies update things '[\"m_write\"]' $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { - logUsage(cmd.Use) - return - } - - var actions []string - if err := json.Unmarshal([]byte(args[3]), &actions); err != nil { - logError(err) - return - } - - policy := mfxsdk.Policy{ - Subject: args[1], - Object: args[2], - Actions: actions, - } - - switch args[0] { - case things: - if err := sdk.UpdateThingPolicy(policy, args[4]); err != nil { - logError(err) - return - } - case users: - if err := sdk.UpdateUserPolicy(policy, args[4]); err != nil { - logError(err) - return - } - default: - logUsage(cmd.Use) - } - }, - }, - { - Use: "list [ users | things ] ", - Short: "List policies", - Long: "List policies\n" + - "Usage:\n" + - "\tmainflux-cli policies list users $USERTOKEN\n" + - "\tmainflux-cli policies list things $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsage(cmd.Use) - return - } - pm := mfxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - } - switch args[0] { - case things: - policies, err := sdk.ListThingPolicies(pm, args[1]) - if err != nil { - logError(err) - return - } - logJSON(policies) - return - case users: - policies, err := sdk.ListUserPolicies(pm, args[1]) - if err != nil { - logError(err) - return - } - - logJSON(policies) - return - default: - logUsage(cmd.Use) - } - }, - }, - { - Use: "remove [ users | things ] ", - Short: "Remove policy", - Long: "Removes a policy with the provided object and subject\n" + - "Usage:\n" + - "\tmainflux-cli policies remove users $USERTOKEN\n" + - "\tmainflux-cli policies remove things $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 4 { - logUsage(cmd.Use) - return - } - - policy := mfxsdk.Policy{ - Subject: args[1], - Object: args[2], - } - switch args[0] { - case things: - if err := sdk.DeleteThingPolicy(policy, args[3]); err != nil { - logError(err) - return - } - case users: - if err := sdk.DeleteUserPolicy(policy, args[3]); err != nil { - logError(err) - return - } - default: - logUsage(cmd.Use) - } - }, - }, - { - Use: "authorize [ users | things ] ", - Short: "Authorize access request", - Long: "Authorize subject over object with provided actions\n" + - "Usage:\n" + - "\tmainflux-cli policies authorize users \"c_list\" $USERTOKEN\n" + - "\tmainflux-cli policies authorize things \"m_read\" $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 6 { - logUsage(cmd.Use) - return - } - - areq := mfxsdk.AccessRequest{ - Subject: args[1], - Object: args[2], - Action: args[3], - EntityType: args[4], - } - - switch args[0] { - case users: - ok, err := sdk.AuthorizeUser(areq, args[5]) - if err != nil { - logError(err) - return - } - logJSON(ok) - case things: - ok, _, err := sdk.AuthorizeThing(areq, args[5]) - if err != nil { - logError(err) - return - } - logJSON(ok) - default: - logUsage(cmd.Use) - } - }, - }, -} - -// NewPolicyCmd returns policies command. -func NewPolicyCmd() *cobra.Command { - cmd := cobra.Command{ - Use: "policies [create | update | list | remove | authorize ]", - Short: "Policies management", - Long: `Policies management: create or update or list or delete or check policies`, - } - - for i := range cmdPolicies { - cmd.AddCommand(&cmdPolicies[i]) - } - - return &cmd -} diff --git a/cli/things.go b/cli/things.go index 2a6bd45ed2..5ac8299fd5 100644 --- a/cli/things.go +++ b/cli/things.go @@ -215,23 +215,45 @@ var cmdThings = []cobra.Command{ }, }, { - Use: "share ", + Use: "share ", Short: "Share thing with a user", Long: "Share thing with a user\n" + "Usage:\n" + - "\tmainflux-cli things share '[\"c_list\", \"c_delete\"]' $USERTOKEN\n", + "\tmainflux-cli things share $USERTOKEN\n", Run: func(cmd *cobra.Command, args []string) { if len(args) != 4 { logUsage(cmd.Use) return } - var actions []string - if err := json.Unmarshal([]byte(args[2]), &actions); err != nil { + req := mfxsdk.UsersRelationRequest{ + Relation: args[2], + UserIDs: []string{args[1]}, + } + err := sdk.ShareThing(args[0], req, args[3]) + if err != nil { logError(err) return } - err := sdk.ShareThing(args[0], args[1], actions, args[3]) + logOK() + }, + }, + { + Use: "unshare ", + Short: "Unshare thing with a user", + Long: "Unshare thing with a user\n" + + "Usage:\n" + + "\tmainflux-cli things share $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 4 { + logUsage(cmd.Use) + return + } + req := mfxsdk.UsersRelationRequest{ + Relation: args[2], + UserIDs: []string{args[1]}, + } + err := sdk.UnshareThing(args[0], req, args[3]) if err != nil { logError(err) return @@ -312,12 +334,36 @@ var cmdThings = []cobra.Command{ logJSON(cl) }, }, + { + Use: "users ", + Short: "List users", + Long: "List users of a thing\n" + + "Usage:\n" + + "\tmainflux-cli things users $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + logUsage(cmd.Use) + return + } + pm := mfxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + ul, err := sdk.ListThingUsers(args[0], pm, args[1]) + if err != nil { + logError(err) + return + } + + logJSON(ul) + }, + }, } // NewThingsCmd returns things command. func NewThingsCmd() *cobra.Command { cmd := cobra.Command{ - Use: "things [create | get | update | delete | share | connect | disconnect | connections | not-connected]", + Use: "things [create | get | update | delete | share | connect | disconnect | connections | not-connected | users ]", Short: "Things management", Long: `Things management: create, get, update, delete or share Thing, connect or disconnect Thing from Channel and get the list of Channels connected or disconnected from a Thing`, } diff --git a/cli/users.go b/cli/users.go index e0fff52ec6..658fb04db9 100644 --- a/cli/users.go +++ b/cli/users.go @@ -333,12 +333,92 @@ var cmdUsers = []cobra.Command{ logJSON(user) }, }, + + { + Use: "channels ", + Short: "List channels", + Long: "List channels of user\n" + + "Usage:\n" + + "\tmainflux-cli users channels \n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + logUsage(cmd.Use) + return + } + + pm := mfxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + + users, err := sdk.ListUserChannels(args[0], pm, args[1]) + if err != nil { + logError(err) + return + } + + logJSON(users) + }, + }, + + { + Use: "things ", + Short: "List things", + Long: "List things of user\n" + + "Usage:\n" + + "\tmainflux-cli users things \n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + logUsage(cmd.Use) + return + } + + pm := mfxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + + users, err := sdk.ListUserThings(args[0], pm, args[1]) + if err != nil { + logError(err) + return + } + + logJSON(users) + }, + }, + { + Use: "groups ", + Short: "List groups", + Long: "List groups of user\n" + + "Usage:\n" + + "\tmainflux-cli users groups \n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + logUsage(cmd.Use) + return + } + + pm := mfxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + + users, err := sdk.ListUserGroups(args[0], pm, args[1]) + if err != nil { + logError(err) + return + } + + logJSON(users) + }, + }, } // NewUsersCmd returns users command. func NewUsersCmd() *cobra.Command { cmd := cobra.Command{ - Use: "users [create | get | update | token | password | enable | disable]", + Use: "users [create | get | update | token | password | enable | disable | channels | things | groups]", Short: "Users management", Long: `Users management: create accounts and tokens"`, } diff --git a/cmd/cli/main.go b/cmd/cli/main.go index dc43d2453f..c0d837d13e 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -61,7 +61,6 @@ func main() { bootstrapCmd := cli.NewBootstrapCmd() certsCmd := cli.NewCertsCmd() subscriptionsCmd := cli.NewSubscriptionCmd() - policiesCmd := cli.NewPolicyCmd() configCmd := cli.NewConfigCmd() // Root Commands @@ -75,7 +74,6 @@ func main() { rootCmd.AddCommand(bootstrapCmd) rootCmd.AddCommand(certsCmd) rootCmd.AddCommand(subscriptionsCmd) - rootCmd.AddCommand(policiesCmd) rootCmd.AddCommand(configCmd) // Root Flags diff --git a/pkg/sdk/go/requests.go b/pkg/sdk/go/requests.go index eccc46d5c8..aabe9ce0e9 100644 --- a/pkg/sdk/go/requests.go +++ b/pkg/sdk/go/requests.go @@ -39,9 +39,8 @@ 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 { diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go index 594302a288..5d6e7d0dfb 100644 --- a/pkg/sdk/go/sdk.go +++ b/pkg/sdk/go/sdk.go @@ -560,7 +560,7 @@ type SDK interface { // Limit: 10, // Permission: "edit", // available Options: "administrator", "delete", edit", "view", "share", "owner", "admin", "editor", "viewer" // } - // groups, _ := sdk.ListGroupUsers("user_id_1", pm, "token") + // groups, _ := sdk.ListGroupUsers("groupID", pm, "token") // fmt.Println(groups) ListGroupUsers(groupID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) @@ -572,7 +572,7 @@ type SDK interface { // Limit: 10, // Permission: "edit", // available Options: "administrator", "delete", edit", "view", "share", "owner", "admin", "editor", "viewer" // } - // groups, _ := sdk.ListGroupChannels("user_id_1", pm, "token") + // groups, _ := sdk.ListGroupChannels("groupID", pm, "token") // fmt.Println(groups) ListGroupChannels(groupID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) @@ -739,9 +739,8 @@ type SDK interface { // // 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) @@ -751,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) From f9b8377c71c907e27c2e210baecb66d51dc153f4 Mon Sep 17 00:00:00 2001 From: Arvindh Date: Tue, 17 Oct 2023 16:26:11 +0530 Subject: [PATCH 16/17] fix: array len comparision Signed-off-by: Arvindh --- internal/groups/service.go | 2 +- things/service.go | 2 +- users/service.go | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/groups/service.go b/internal/groups/service.go index 4c7f19d657..23534d2047 100644 --- a/internal/groups/service.go +++ b/internal/groups/service.go @@ -216,7 +216,7 @@ func (svc service) ListGroups(ctx context.Context, token string, memberKind, mem return groups.Page{}, fmt.Errorf("invalid member kind") } - if len(ids) <= 0 { + if len(ids) == 0 { return groups.Page{ PageMeta: gm.PageMeta, }, nil diff --git a/things/service.go b/things/service.go index 8c04375b35..e46287ab94 100644 --- a/things/service.go +++ b/things/service.go @@ -161,7 +161,7 @@ func (svc service) ListClients(ctx context.Context, token string, reqUserID stri } } - if len(ids) <= 0 { + if len(ids) == 0 { return mfclients.ClientsPage{ Page: mfclients.Page{Total: 0, Limit: pm.Limit, Offset: pm.Offset}, }, nil diff --git a/users/service.go b/users/service.go index 43b1e5a66d..2700e28a62 100644 --- a/users/service.go +++ b/users/service.go @@ -395,8 +395,10 @@ func (svc service) ListMembers(ctx context.Context, token, objectKind string, ob if err != nil { return mfclients.MembersPage{}, err } - if len(uids.Policies) <= 0 { - return mfclients.MembersPage{}, errors.ErrNotFound + if len(uids.Policies) == 0 { + return mfclients.MembersPage{ + Page: mfclients.Page{Total: 0, Offset: pm.Offset, Limit: pm.Limit}, + }, nil } pm.IDs = uids.Policies From 33dbdab5deb89f10cb5b1cefb5e38f65519d7c00 Mon Sep 17 00:00:00 2001 From: Arvindh Date: Tue, 17 Oct 2023 18:50:03 +0530 Subject: [PATCH 17/17] fix: nginx Signed-off-by: Arvindh --- docker/nginx/nginx-key.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docker/nginx/nginx-key.conf b/docker/nginx/nginx-key.conf index a36037eb23..8e7f37a55c 100644 --- a/docker/nginx/nginx-key.conf +++ b/docker/nginx/nginx-key.conf @@ -50,6 +50,11 @@ 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