From c8ad98793a3d094ce79a43632050e886cb89a8fe Mon Sep 17 00:00:00 2001 From: Arvindh Date: Thu, 30 Jan 2025 19:23:09 +0530 Subject: [PATCH 1/3] list entity users Signed-off-by: Arvindh --- api/http/common.go | 113 +-- api/http/util/errors.go | 3 + channels/mocks/repository.go | 46 ++ channels/mocks/service.go | 46 ++ channels/postgres/channels.go | 3 +- clients/mocks/repository.go | 46 ++ clients/mocks/service.go | 46 ++ clients/postgres/clients.go | 3 +- domains/mocks/repository.go | 46 ++ domains/mocks/service.go | 46 ++ domains/postgres/domains.go | 3 +- groups/mocks/repository.go | 46 ++ groups/mocks/service.go | 46 ++ groups/postgres/groups.go | 3 +- pkg/domains/events/consumer/stream.go | 47 +- pkg/groups/events/consumer/streams.go | 51 +- pkg/roles/mocks/rolemanager.go | 46 ++ pkg/roles/mocks/rolesRepo.go | 46 ++ pkg/roles/provisionmanage.go | 17 +- pkg/roles/repo/postgres/init.go | 15 +- pkg/roles/repo/postgres/roles.go | 654 +++++++++++++++++- pkg/roles/rolemanager/api/decoders.go | 85 +++ pkg/roles/rolemanager/api/endpoints.go | 51 ++ pkg/roles/rolemanager/api/requests.go | 46 ++ pkg/roles/rolemanager/api/responses.go | 30 + pkg/roles/rolemanager/api/router.go | 14 + .../rolemanager/events/consumer/handler.go | 81 ++- pkg/roles/rolemanager/events/events.go | 104 ++- pkg/roles/rolemanager/events/streams.go | 34 + .../rolemanager/middleware/authoirzation.go | 38 +- pkg/roles/rolemanager/middleware/logging.go | 41 ++ pkg/roles/rolemanager/middleware/meterics.go | 8 + pkg/roles/rolemanager/tracing/tracing.go | 8 + pkg/roles/roles.go | 39 ++ 34 files changed, 1746 insertions(+), 205 deletions(-) diff --git a/api/http/common.go b/api/http/common.go index 08b8e64ef1..7f3bc69df3 100644 --- a/api/http/common.go +++ b/api/http/common.go @@ -20,62 +20,63 @@ import ( ) const ( - MemberKindKey = "member_kind" - PermissionKey = "permission" - RelationKey = "relation" - StatusKey = "status" - OffsetKey = "offset" - OrderKey = "order" - LimitKey = "limit" - MetadataKey = "metadata" - ParentKey = "parent_id" - OwnerKey = "owner_id" - ClientKey = "client" - UsernameKey = "username" - NameKey = "name" - GroupKey = "group" - ActionKey = "action" - ActionsKey = "actions" - RoleIDKey = "role_id" - RoleNameKey = "role_name" - AccessTypeKey = "access_type" - TagKey = "tag" - FirstNameKey = "first_name" - LastNameKey = "last_name" - TotalKey = "total" - SubjectKey = "subject" - ObjectKey = "object" - LevelKey = "level" - StartLevelKey = "start_level" - EndLevelKey = "end_level" - TreeKey = "tree" - DirKey = "dir" - ListPerms = "list_perms" - VisibilityKey = "visibility" - EmailKey = "email" - SharedByKey = "shared_by" - TokenKey = "token" - UserKey = "user" - DomainKey = "domain" - ChannelKey = "channel" - ConnTypeKey = "connection_type" - DefPermission = "read_permission" - DefTotal = uint64(100) - DefOffset = 0 - DefOrder = "updated_at" - DefDir = "asc" - DefLimit = 10 - DefLevel = 0 - DefStartLevel = 1 - DefEndLevel = 0 - DefStatus = "enabled" - DefClientStatus = clients.Enabled - DefUserStatus = users.Enabled - DefGroupStatus = groups.Enabled - DefListPerms = false - SharedVisibility = "shared" - MyVisibility = "mine" - AllVisibility = "all" + MemberKindKey = "member_kind" + PermissionKey = "permission" + RelationKey = "relation" + StatusKey = "status" + OffsetKey = "offset" + OrderKey = "order" + LimitKey = "limit" + MetadataKey = "metadata" + ParentKey = "parent_id" + OwnerKey = "owner_id" + ClientKey = "client" + UsernameKey = "username" + NameKey = "name" + GroupKey = "group" + ActionKey = "action" + ActionsKey = "actions" + RoleIDKey = "role_id" + RoleNameKey = "role_name" + AccessProviderIDKey = "access_provider_id" + AccessTypeKey = "access_type" + TagKey = "tag" + FirstNameKey = "first_name" + LastNameKey = "last_name" + TotalKey = "total" + SubjectKey = "subject" + ObjectKey = "object" + LevelKey = "level" + StartLevelKey = "start_level" + EndLevelKey = "end_level" + TreeKey = "tree" + DirKey = "dir" + ListPerms = "list_perms" + VisibilityKey = "visibility" + EmailKey = "email" + SharedByKey = "shared_by" + TokenKey = "token" + UserKey = "user" + DomainKey = "domain" + ChannelKey = "channel" + ConnTypeKey = "connection_type" + DefPermission = "read_permission" + DefTotal = uint64(100) + DefOffset = 0 + DefOrder = "updated_at" + DefDir = "asc" + DefLimit = 10 + DefLevel = 0 + DefStartLevel = 1 + DefEndLevel = 0 + DefStatus = "enabled" + DefClientStatus = clients.Enabled + DefUserStatus = users.Enabled + DefGroupStatus = groups.Enabled + DefListPerms = false + SharedVisibility = "shared" + MyVisibility = "mine" + AllVisibility = "all" // ContentType represents JSON content type. ContentType = "application/json" diff --git a/api/http/util/errors.go b/api/http/util/errors.go index 8857c0ec75..ab493fec80 100644 --- a/api/http/util/errors.go +++ b/api/http/util/errors.go @@ -141,6 +141,9 @@ var ( // ErrInvalidComparator indicates an invalid comparator. ErrInvalidComparator = errors.New("invalid comparator") + // ErrMissingMemberIDs indicates missing group member type. + ErrMissingMemberIDs = errors.New("missing member ids") + // ErrMissingMemberType indicates missing group member type. ErrMissingMemberType = errors.New("missing group member type") diff --git a/channels/mocks/repository.go b/channels/mocks/repository.go index b22ab4dcce..af480c83fe 100644 --- a/channels/mocks/repository.go +++ b/channels/mocks/repository.go @@ -187,6 +187,34 @@ func (_m *Repository) DoesChannelHaveConnections(ctx context.Context, id string) return r0, r1 } +// ListEntityMembers provides a mock function with given fields: ctx, entityID, pageQuery +func (_m *Repository) ListEntityMembers(ctx context.Context, entityID string, pageQuery roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + ret := _m.Called(ctx, entityID, pageQuery) + + if len(ret) == 0 { + panic("no return value specified for ListEntityMembers") + } + + var r0 roles.MembersRolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, roles.MembersRolePageQuery) (roles.MembersRolePage, error)); ok { + return rf(ctx, entityID, pageQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, string, roles.MembersRolePageQuery) roles.MembersRolePage); ok { + r0 = rf(ctx, entityID, pageQuery) + } else { + r0 = ret.Get(0).(roles.MembersRolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, roles.MembersRolePageQuery) error); ok { + r1 = rf(ctx, entityID, pageQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Remove provides a mock function with given fields: ctx, ids func (_m *Repository) Remove(ctx context.Context, ids ...string) error { _va := make([]interface{}, len(ids)) @@ -266,6 +294,24 @@ func (_m *Repository) RemoveConnections(ctx context.Context, conns []channels.Co return r0 } +// RemoveEntityMembers provides a mock function with given fields: ctx, entityID, members +func (_m *Repository) RemoveEntityMembers(ctx context.Context, entityID string, members []string) error { + ret := _m.Called(ctx, entityID, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveEntityMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok { + r0 = rf(ctx, entityID, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // RemoveMemberFromAllRoles provides a mock function with given fields: ctx, memberID func (_m *Repository) RemoveMemberFromAllRoles(ctx context.Context, memberID string) error { ret := _m.Called(ctx, memberID) diff --git a/channels/mocks/service.go b/channels/mocks/service.go index 0aa25aca3f..a0f1fee131 100644 --- a/channels/mocks/service.go +++ b/channels/mocks/service.go @@ -246,6 +246,34 @@ func (_m *Service) ListChannels(ctx context.Context, session authn.Session, pm c return r0, r1 } +// ListEntityMembers provides a mock function with given fields: ctx, session, entityID, pq +func (_m *Service) ListEntityMembers(ctx context.Context, session authn.Session, entityID string, pq roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + ret := _m.Called(ctx, session, entityID, pq) + + if len(ret) == 0 { + panic("no return value specified for ListEntityMembers") + } + + var r0 roles.MembersRolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) (roles.MembersRolePage, error)); ok { + return rf(ctx, session, entityID, pq) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) roles.MembersRolePage); ok { + r0 = rf(ctx, session, entityID, pq) + } else { + r0 = ret.Get(0).(roles.MembersRolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) error); ok { + r1 = rf(ctx, session, entityID, pq) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ListUserChannels provides a mock function with given fields: ctx, session, userID, pm func (_m *Service) ListUserChannels(ctx context.Context, session authn.Session, userID string, pm channels.PageMetadata) (channels.Page, error) { ret := _m.Called(ctx, session, userID, pm) @@ -292,6 +320,24 @@ func (_m *Service) RemoveChannel(ctx context.Context, session authn.Session, id return r0 } +// RemoveEntityMembers provides a mock function with given fields: ctx, session, entityID, members +func (_m *Service) RemoveEntityMembers(ctx context.Context, session authn.Session, entityID string, members []string) error { + ret := _m.Called(ctx, session, entityID, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveEntityMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, []string) error); ok { + r0 = rf(ctx, session, entityID, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // RemoveMemberFromAllRoles provides a mock function with given fields: ctx, session, memberID func (_m *Service) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) error { ret := _m.Called(ctx, session, memberID) diff --git a/channels/postgres/channels.go b/channels/postgres/channels.go index a64e66d364..caa111e3c5 100644 --- a/channels/postgres/channels.go +++ b/channels/postgres/channels.go @@ -18,6 +18,7 @@ import ( "github.com/absmach/supermq/pkg/connections" "github.com/absmach/supermq/pkg/errors" repoerr "github.com/absmach/supermq/pkg/errors/repository" + "github.com/absmach/supermq/pkg/policies" "github.com/absmach/supermq/pkg/postgres" rolesPostgres "github.com/absmach/supermq/pkg/roles/repo/postgres" "github.com/jackc/pgtype" @@ -40,7 +41,7 @@ type channelRepository struct { // NewChannelRepository instantiates a PostgreSQL implementation of channel // repository. func NewRepository(db postgres.Database) channels.Repository { - rolesRepo := rolesPostgres.NewRepository(db, rolesTableNamePrefix, entityTableName, entityIDColumnName) + rolesRepo := rolesPostgres.NewRepository(db, policies.ChannelType, rolesTableNamePrefix, entityTableName, entityIDColumnName) return &channelRepository{ db: db, Repository: rolesRepo, diff --git a/clients/mocks/repository.go b/clients/mocks/repository.go index fc45044a2e..dab002e409 100644 --- a/clients/mocks/repository.go +++ b/clients/mocks/repository.go @@ -176,6 +176,34 @@ func (_m *Repository) DoesClientHaveConnections(ctx context.Context, id string) return r0, r1 } +// ListEntityMembers provides a mock function with given fields: ctx, entityID, pageQuery +func (_m *Repository) ListEntityMembers(ctx context.Context, entityID string, pageQuery roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + ret := _m.Called(ctx, entityID, pageQuery) + + if len(ret) == 0 { + panic("no return value specified for ListEntityMembers") + } + + var r0 roles.MembersRolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, roles.MembersRolePageQuery) (roles.MembersRolePage, error)); ok { + return rf(ctx, entityID, pageQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, string, roles.MembersRolePageQuery) roles.MembersRolePage); ok { + r0 = rf(ctx, entityID, pageQuery) + } else { + r0 = ret.Get(0).(roles.MembersRolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, roles.MembersRolePageQuery) error); ok { + r1 = rf(ctx, entityID, pageQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // RemoveChannelConnections provides a mock function with given fields: ctx, channelID func (_m *Repository) RemoveChannelConnections(ctx context.Context, channelID string) error { ret := _m.Called(ctx, channelID) @@ -230,6 +258,24 @@ func (_m *Repository) RemoveConnections(ctx context.Context, conns []clients.Con return r0 } +// RemoveEntityMembers provides a mock function with given fields: ctx, entityID, members +func (_m *Repository) RemoveEntityMembers(ctx context.Context, entityID string, members []string) error { + ret := _m.Called(ctx, entityID, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveEntityMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok { + r0 = rf(ctx, entityID, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // RemoveMemberFromAllRoles provides a mock function with given fields: ctx, memberID func (_m *Repository) RemoveMemberFromAllRoles(ctx context.Context, memberID string) error { ret := _m.Called(ctx, memberID) diff --git a/clients/mocks/service.go b/clients/mocks/service.go index 3d64f162cd..1ea36255f0 100644 --- a/clients/mocks/service.go +++ b/clients/mocks/service.go @@ -226,6 +226,34 @@ func (_m *Service) ListClients(ctx context.Context, session authn.Session, pm cl return r0, r1 } +// ListEntityMembers provides a mock function with given fields: ctx, session, entityID, pq +func (_m *Service) ListEntityMembers(ctx context.Context, session authn.Session, entityID string, pq roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + ret := _m.Called(ctx, session, entityID, pq) + + if len(ret) == 0 { + panic("no return value specified for ListEntityMembers") + } + + var r0 roles.MembersRolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) (roles.MembersRolePage, error)); ok { + return rf(ctx, session, entityID, pq) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) roles.MembersRolePage); ok { + r0 = rf(ctx, session, entityID, pq) + } else { + r0 = ret.Get(0).(roles.MembersRolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) error); ok { + r1 = rf(ctx, session, entityID, pq) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ListUserClients provides a mock function with given fields: ctx, session, userID, pm func (_m *Service) ListUserClients(ctx context.Context, session authn.Session, userID string, pm clients.Page) (clients.ClientsPage, error) { ret := _m.Called(ctx, session, userID, pm) @@ -254,6 +282,24 @@ func (_m *Service) ListUserClients(ctx context.Context, session authn.Session, u return r0, r1 } +// RemoveEntityMembers provides a mock function with given fields: ctx, session, entityID, members +func (_m *Service) RemoveEntityMembers(ctx context.Context, session authn.Session, entityID string, members []string) error { + ret := _m.Called(ctx, session, entityID, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveEntityMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, []string) error); ok { + r0 = rf(ctx, session, entityID, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // RemoveMemberFromAllRoles provides a mock function with given fields: ctx, session, memberID func (_m *Service) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) error { ret := _m.Called(ctx, session, memberID) diff --git a/clients/postgres/clients.go b/clients/postgres/clients.go index bc3bb51dcf..51a1c29fe0 100644 --- a/clients/postgres/clients.go +++ b/clients/postgres/clients.go @@ -17,6 +17,7 @@ import ( "github.com/absmach/supermq/pkg/connections" "github.com/absmach/supermq/pkg/errors" repoerr "github.com/absmach/supermq/pkg/errors/repository" + "github.com/absmach/supermq/pkg/policies" "github.com/absmach/supermq/pkg/postgres" rolesPostgres "github.com/absmach/supermq/pkg/roles/repo/postgres" "github.com/jackc/pgtype" @@ -39,7 +40,7 @@ type clientRepo struct { // NewRepository instantiates a PostgreSQL // implementation of Clients repository. func NewRepository(db postgres.Database) clients.Repository { - repo := rolesPostgres.NewRepository(db, rolesTableNamePrefix, entityTableName, entityIDColumnName) + repo := rolesPostgres.NewRepository(db, policies.ClientType, rolesTableNamePrefix, entityTableName, entityIDColumnName) return &clientRepo{ DB: db, diff --git a/domains/mocks/repository.go b/domains/mocks/repository.go index 4ea469638b..e8aad64c13 100644 --- a/domains/mocks/repository.go +++ b/domains/mocks/repository.go @@ -94,6 +94,52 @@ func (_m *Repository) ListDomains(ctx context.Context, pm domains.Page) (domains return r0, r1 } +// ListEntityMembers provides a mock function with given fields: ctx, entityID, pageQuery +func (_m *Repository) ListEntityMembers(ctx context.Context, entityID string, pageQuery roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + ret := _m.Called(ctx, entityID, pageQuery) + + if len(ret) == 0 { + panic("no return value specified for ListEntityMembers") + } + + var r0 roles.MembersRolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, roles.MembersRolePageQuery) (roles.MembersRolePage, error)); ok { + return rf(ctx, entityID, pageQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, string, roles.MembersRolePageQuery) roles.MembersRolePage); ok { + r0 = rf(ctx, entityID, pageQuery) + } else { + r0 = ret.Get(0).(roles.MembersRolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, roles.MembersRolePageQuery) error); ok { + r1 = rf(ctx, entityID, pageQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveEntityMembers provides a mock function with given fields: ctx, entityID, members +func (_m *Repository) RemoveEntityMembers(ctx context.Context, entityID string, members []string) error { + ret := _m.Called(ctx, entityID, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveEntityMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok { + r0 = rf(ctx, entityID, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // RemoveMemberFromAllRoles provides a mock function with given fields: ctx, memberID func (_m *Repository) RemoveMemberFromAllRoles(ctx context.Context, memberID string) error { ret := _m.Called(ctx, memberID) diff --git a/domains/mocks/service.go b/domains/mocks/service.go index 6025633c30..5523941a87 100644 --- a/domains/mocks/service.go +++ b/domains/mocks/service.go @@ -228,6 +228,52 @@ func (_m *Service) ListDomains(ctx context.Context, sesssion authn.Session, page return r0, r1 } +// ListEntityMembers provides a mock function with given fields: ctx, session, entityID, pq +func (_m *Service) ListEntityMembers(ctx context.Context, session authn.Session, entityID string, pq roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + ret := _m.Called(ctx, session, entityID, pq) + + if len(ret) == 0 { + panic("no return value specified for ListEntityMembers") + } + + var r0 roles.MembersRolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) (roles.MembersRolePage, error)); ok { + return rf(ctx, session, entityID, pq) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) roles.MembersRolePage); ok { + r0 = rf(ctx, session, entityID, pq) + } else { + r0 = ret.Get(0).(roles.MembersRolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) error); ok { + r1 = rf(ctx, session, entityID, pq) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveEntityMembers provides a mock function with given fields: ctx, session, entityID, members +func (_m *Service) RemoveEntityMembers(ctx context.Context, session authn.Session, entityID string, members []string) error { + ret := _m.Called(ctx, session, entityID, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveEntityMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, []string) error); ok { + r0 = rf(ctx, session, entityID, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // RemoveMemberFromAllRoles provides a mock function with given fields: ctx, session, memberID func (_m *Service) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) error { ret := _m.Called(ctx, session, memberID) diff --git a/domains/postgres/domains.go b/domains/postgres/domains.go index d6cd2be831..e1892573b3 100644 --- a/domains/postgres/domains.go +++ b/domains/postgres/domains.go @@ -15,6 +15,7 @@ import ( "github.com/absmach/supermq/domains" "github.com/absmach/supermq/pkg/errors" repoerr "github.com/absmach/supermq/pkg/errors/repository" + "github.com/absmach/supermq/pkg/policies" "github.com/absmach/supermq/pkg/postgres" rolesPostgres "github.com/absmach/supermq/pkg/roles/repo/postgres" "github.com/jackc/pgtype" @@ -38,7 +39,7 @@ type domainRepo struct { // New instantiates a PostgreSQL // implementation of Domain repository. func New(db postgres.Database) domains.Repository { - rmsvcRepo := rolesPostgres.NewRepository(db, rolesTableNamePrefix, entityTableName, entityIDColumnName) + rmsvcRepo := rolesPostgres.NewRepository(db, policies.DomainType, rolesTableNamePrefix, entityTableName, entityIDColumnName) return &domainRepo{ db: db, Repository: rmsvcRepo, diff --git a/groups/mocks/repository.go b/groups/mocks/repository.go index e98a449db7..b785736918 100644 --- a/groups/mocks/repository.go +++ b/groups/mocks/repository.go @@ -112,6 +112,52 @@ func (_m *Repository) Delete(ctx context.Context, groupID string) error { return r0 } +// ListEntityMembers provides a mock function with given fields: ctx, entityID, pageQuery +func (_m *Repository) ListEntityMembers(ctx context.Context, entityID string, pageQuery roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + ret := _m.Called(ctx, entityID, pageQuery) + + if len(ret) == 0 { + panic("no return value specified for ListEntityMembers") + } + + var r0 roles.MembersRolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, roles.MembersRolePageQuery) (roles.MembersRolePage, error)); ok { + return rf(ctx, entityID, pageQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, string, roles.MembersRolePageQuery) roles.MembersRolePage); ok { + r0 = rf(ctx, entityID, pageQuery) + } else { + r0 = ret.Get(0).(roles.MembersRolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, roles.MembersRolePageQuery) error); ok { + r1 = rf(ctx, entityID, pageQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveEntityMembers provides a mock function with given fields: ctx, entityID, members +func (_m *Repository) RemoveEntityMembers(ctx context.Context, entityID string, members []string) error { + ret := _m.Called(ctx, entityID, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveEntityMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok { + r0 = rf(ctx, entityID, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // RemoveMemberFromAllRoles provides a mock function with given fields: ctx, memberID func (_m *Repository) RemoveMemberFromAllRoles(ctx context.Context, memberID string) error { ret := _m.Called(ctx, memberID) diff --git a/groups/mocks/service.go b/groups/mocks/service.go index e0726aead8..6bfa972130 100644 --- a/groups/mocks/service.go +++ b/groups/mocks/service.go @@ -254,6 +254,34 @@ func (_m *Service) ListChildrenGroups(ctx context.Context, session authn.Session return r0, r1 } +// ListEntityMembers provides a mock function with given fields: ctx, session, entityID, pq +func (_m *Service) ListEntityMembers(ctx context.Context, session authn.Session, entityID string, pq roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + ret := _m.Called(ctx, session, entityID, pq) + + if len(ret) == 0 { + panic("no return value specified for ListEntityMembers") + } + + var r0 roles.MembersRolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) (roles.MembersRolePage, error)); ok { + return rf(ctx, session, entityID, pq) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) roles.MembersRolePage); ok { + r0 = rf(ctx, session, entityID, pq) + } else { + r0 = ret.Get(0).(roles.MembersRolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) error); ok { + r1 = rf(ctx, session, entityID, pq) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ListGroups provides a mock function with given fields: ctx, session, pm func (_m *Service) ListGroups(ctx context.Context, session authn.Session, pm groups.PageMeta) (groups.Page, error) { ret := _m.Called(ctx, session, pm) @@ -346,6 +374,24 @@ func (_m *Service) RemoveChildrenGroups(ctx context.Context, session authn.Sessi return r0 } +// RemoveEntityMembers provides a mock function with given fields: ctx, session, entityID, members +func (_m *Service) RemoveEntityMembers(ctx context.Context, session authn.Session, entityID string, members []string) error { + ret := _m.Called(ctx, session, entityID, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveEntityMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, []string) error); ok { + r0 = rf(ctx, session, entityID, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // RemoveMemberFromAllRoles provides a mock function with given fields: ctx, session, memberID func (_m *Service) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) error { ret := _m.Called(ctx, session, memberID) diff --git a/groups/postgres/groups.go b/groups/postgres/groups.go index 5202ebf9ef..a0654870e2 100644 --- a/groups/postgres/groups.go +++ b/groups/postgres/groups.go @@ -14,6 +14,7 @@ import ( groups "github.com/absmach/supermq/groups" "github.com/absmach/supermq/pkg/errors" repoerr "github.com/absmach/supermq/pkg/errors/repository" + "github.com/absmach/supermq/pkg/policies" "github.com/absmach/supermq/pkg/postgres" rolesPostgres "github.com/absmach/supermq/pkg/roles/repo/postgres" "github.com/jmoiron/sqlx" @@ -42,7 +43,7 @@ type groupRepository struct { // New instantiates a PostgreSQL implementation of group // repository. func New(db postgres.Database) groups.Repository { - roleRepo := rolesPostgres.NewRepository(db, rolesTableNamePrefix, entityTableName, entityIDColumnName) + roleRepo := rolesPostgres.NewRepository(db, policies.GroupType, rolesTableNamePrefix, entityTableName, entityIDColumnName) return &groupRepository{ db: db, diff --git a/pkg/domains/events/consumer/stream.go b/pkg/domains/events/consumer/stream.go index ec89234b40..70412368c3 100644 --- a/pkg/domains/events/consumer/stream.go +++ b/pkg/domains/events/consumer/stream.go @@ -19,23 +19,13 @@ import ( const ( stream = "events.supermq.domains" - create = "domain.create" - update = "domain.update" - enable = "domain.enable" - disable = "domain.disable" - freeze = "domain.freeze" - delete = "domain.delete" - userDelete = "domain.user_delete" - addRole = "domain.role.add" - removeRole = "domain.role.remove" - updateRole = "domain.role.update" - addRoleActions = "domain.role.actions.add" - removeRoleActions = "domain.role.actions.remove" - removeAllRoleActions = "domain.role.actions.remove_all" - addRoleMembers = "domain.role.members.add" - removeRoleMembers = "domain.role.members.remove" - removeRoleAllMembers = "domain.role.members.remove_all" - removeMemberFromAllRoles = "domain.role.members.remove_from_all_roles" + create = "domain.create" + update = "domain.update" + enable = "domain.enable" + disable = "domain.disable" + freeze = "domain.freeze" + delete = "domain.delete" + userDelete = "domain.user_delete" ) var ( @@ -105,28 +95,9 @@ func (es *eventHandler) Handle(ctx context.Context, event events.Event) error { return es.userDeleteDomainHandler(ctx, msg) case delete: return es.deleteDomainHandler(ctx, msg) - case addRole: - return es.rolesEventHandler.AddEntityRoleHandler(ctx, msg) - case updateRole: - return es.rolesEventHandler.UpdateEntityRoleHandler(ctx, msg) - case removeRole: - return es.rolesEventHandler.RemoveEntityRoleHandler(ctx, msg) - case addRoleActions: - return es.rolesEventHandler.AddEntityRoleActionsHandler(ctx, msg) - case removeRoleActions: - return es.rolesEventHandler.RemoveEntityRoleActionsHandler(ctx, msg) - case removeAllRoleActions: - return es.rolesEventHandler.RemoveAllEntityRoleActionsHandler(ctx, msg) - case addRoleMembers: - return es.rolesEventHandler.AddEntityRoleMembersHandler(ctx, msg) - case removeRoleMembers: - return es.rolesEventHandler.RemoveEntityRoleMembersHandler(ctx, msg) - case removeRoleAllMembers: - return es.rolesEventHandler.RemoveAllEntityRoleMembersHandler(ctx, msg) - case removeMemberFromAllRoles: - return es.rolesEventHandler.RemoveMemberFromAllEntityHandler(ctx, msg) } - return nil + + return es.rolesEventHandler.Handle(ctx, op, msg) } func (es *eventHandler) createDomainHandler(ctx context.Context, data map[string]interface{}) error { diff --git a/pkg/groups/events/consumer/streams.go b/pkg/groups/events/consumer/streams.go index 322c0a4a53..6e1b49b68c 100644 --- a/pkg/groups/events/consumer/streams.go +++ b/pkg/groups/events/consumer/streams.go @@ -18,25 +18,15 @@ import ( const ( stream = "events.supermq.groups" - create = "group.create" - update = "group.update" - changeStatus = "group.change_status" - remove = "group.remove" - addParentGroup = "group.add_parent_group" - removeParentGroup = "group.remove_parent_group" - addChildrenGroups = "group.add_children_groups" - removeChildrenGroups = "group.remove_children_groups" - removeAllChildrenGroups = "group.remove_all_children_groups" - addRole = "group.role.add" - removeRole = "group.role.remove" - updateRole = "group.role.update" - addRoleActions = "group.role.actions.add" - removeRoleActions = "group.role.actions.remove" - removeAllRoleActions = "group.role.actions.remove_all" - addRoleMembers = "group.role.members.add" - removeRoleMembers = "group.role.members.remove" - removeRoleAllMembers = "group.role.members.remove_all" - removeMemberFromAllRoles = "group.role.members.remove_from_all_roles" + create = "group.create" + update = "group.update" + changeStatus = "group.change_status" + remove = "group.remove" + addParentGroup = "group.add_parent_group" + removeParentGroup = "group.remove_parent_group" + addChildrenGroups = "group.add_children_groups" + removeChildrenGroups = "group.remove_children_groups" + removeAllChildrenGroups = "group.remove_all_children_groups" ) var ( @@ -111,28 +101,9 @@ func (es *eventHandler) Handle(ctx context.Context, event events.Event) error { return es.removeChildrenGroupsHandler(ctx, msg) case removeAllChildrenGroups: return es.removeAllChildrenGroupsHandler(ctx, msg) - case addRole: - return es.rolesEventHandler.AddEntityRoleHandler(ctx, msg) - case updateRole: - return es.rolesEventHandler.UpdateEntityRoleHandler(ctx, msg) - case removeRole: - return es.rolesEventHandler.RemoveEntityRoleHandler(ctx, msg) - case addRoleActions: - return es.rolesEventHandler.AddEntityRoleActionsHandler(ctx, msg) - case removeRoleActions: - return es.rolesEventHandler.RemoveEntityRoleActionsHandler(ctx, msg) - case removeAllRoleActions: - return es.rolesEventHandler.RemoveAllEntityRoleActionsHandler(ctx, msg) - case addRoleMembers: - return es.rolesEventHandler.AddEntityRoleMembersHandler(ctx, msg) - case removeRoleMembers: - return es.rolesEventHandler.RemoveEntityRoleMembersHandler(ctx, msg) - case removeRoleAllMembers: - return es.rolesEventHandler.RemoveAllEntityRoleMembersHandler(ctx, msg) - case removeMemberFromAllRoles: - return es.rolesEventHandler.RemoveMemberFromAllEntityHandler(ctx, msg) } - return nil + + return es.rolesEventHandler.Handle(ctx, op, msg) } func (es *eventHandler) createGroupHandler(ctx context.Context, data map[string]interface{}) error { diff --git a/pkg/roles/mocks/rolemanager.go b/pkg/roles/mocks/rolemanager.go index 36c881ccd4..3e7d843890 100644 --- a/pkg/roles/mocks/rolemanager.go +++ b/pkg/roles/mocks/rolemanager.go @@ -77,6 +77,52 @@ func (_m *RoleManager) ListAvailableActions(ctx context.Context, session authn.S return r0, r1 } +// ListEntityMembers provides a mock function with given fields: ctx, session, entityID, pq +func (_m *RoleManager) ListEntityMembers(ctx context.Context, session authn.Session, entityID string, pq roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + ret := _m.Called(ctx, session, entityID, pq) + + if len(ret) == 0 { + panic("no return value specified for ListEntityMembers") + } + + var r0 roles.MembersRolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) (roles.MembersRolePage, error)); ok { + return rf(ctx, session, entityID, pq) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) roles.MembersRolePage); ok { + r0 = rf(ctx, session, entityID, pq) + } else { + r0 = ret.Get(0).(roles.MembersRolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, roles.MembersRolePageQuery) error); ok { + r1 = rf(ctx, session, entityID, pq) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveEntityMembers provides a mock function with given fields: ctx, session, entityID, members +func (_m *RoleManager) RemoveEntityMembers(ctx context.Context, session authn.Session, entityID string, members []string) error { + ret := _m.Called(ctx, session, entityID, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveEntityMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, []string) error); ok { + r0 = rf(ctx, session, entityID, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // RemoveMemberFromAllRoles provides a mock function with given fields: ctx, session, memberID func (_m *RoleManager) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) error { ret := _m.Called(ctx, session, memberID) diff --git a/pkg/roles/mocks/rolesRepo.go b/pkg/roles/mocks/rolesRepo.go index b8f7bd1d48..2756369ba0 100644 --- a/pkg/roles/mocks/rolesRepo.go +++ b/pkg/roles/mocks/rolesRepo.go @@ -46,6 +46,52 @@ func (_m *Repository) AddRoles(ctx context.Context, rps []roles.RoleProvision) ( return r0, r1 } +// ListEntityMembers provides a mock function with given fields: ctx, entityID, pageQuery +func (_m *Repository) ListEntityMembers(ctx context.Context, entityID string, pageQuery roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + ret := _m.Called(ctx, entityID, pageQuery) + + if len(ret) == 0 { + panic("no return value specified for ListEntityMembers") + } + + var r0 roles.MembersRolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, roles.MembersRolePageQuery) (roles.MembersRolePage, error)); ok { + return rf(ctx, entityID, pageQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, string, roles.MembersRolePageQuery) roles.MembersRolePage); ok { + r0 = rf(ctx, entityID, pageQuery) + } else { + r0 = ret.Get(0).(roles.MembersRolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, roles.MembersRolePageQuery) error); ok { + r1 = rf(ctx, entityID, pageQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveEntityMembers provides a mock function with given fields: ctx, entityID, members +func (_m *Repository) RemoveEntityMembers(ctx context.Context, entityID string, members []string) error { + ret := _m.Called(ctx, entityID, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveEntityMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok { + r0 = rf(ctx, entityID, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // RemoveMemberFromAllRoles provides a mock function with given fields: ctx, memberID func (_m *Repository) RemoveMemberFromAllRoles(ctx context.Context, memberID string) error { ret := _m.Called(ctx, memberID) diff --git a/pkg/roles/provisionmanage.go b/pkg/roles/provisionmanage.go index 602a9b8a58..381dbd2489 100644 --- a/pkg/roles/provisionmanage.go +++ b/pkg/roles/provisionmanage.go @@ -579,7 +579,7 @@ func (r ProvisionManageService) RoleRemoveMembers(ctx context.Context, session a } ro.UpdatedAt = time.Now() - // ro.UpdatedBy = userID + ro.UpdatedBy = session.UserID if err := r.repo.RoleRemoveMembers(ctx, ro, members); err != nil { return errors.Wrap(svcerr.ErrRemoveEntity, err) } @@ -611,6 +611,21 @@ func (r ProvisionManageService) RoleRemoveAllMembers(ctx context.Context, sessio return nil } +func (r ProvisionManageService) ListEntityMembers(ctx context.Context, session authn.Session, entityID string, pageQuery MembersRolePageQuery) (MembersRolePage, error) { + mp, err := r.repo.ListEntityMembers(ctx, entityID, pageQuery) + if err != nil { + return MembersRolePage{}, err + } + return mp, nil +} + +func (r ProvisionManageService) RemoveEntityMembers(ctx context.Context, session authn.Session, entityID string, members []string) error { + if err := r.repo.RemoveEntityMembers(ctx, entityID, members); err != nil { + return err + } + return nil +} + func (r ProvisionManageService) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, member string) (err error) { if err := r.repo.RemoveMemberFromAllRoles(ctx, member); err != nil { return errors.Wrap(svcerr.ErrRemoveEntity, err) diff --git a/pkg/roles/repo/postgres/init.go b/pkg/roles/repo/postgres/init.go index ebe1a4da43..f263169218 100644 --- a/pkg/roles/repo/postgres/init.go +++ b/pkg/roles/repo/postgres/init.go @@ -29,29 +29,30 @@ func Migration(rolesTableNamePrefix, entityTableName, entityIDColumnName string) updated_at TIMESTAMP, updated_by VARCHAR(254), created_by VARCHAR(254), - CONSTRAINT %s_roles_unique_role_name_entity_id_constraint UNIQUE ( name, entity_id), + CONSTRAINT %s_roles_unique_role_name_entity_id_constraint UNIQUE (name, entity_id), CONSTRAINT %s_roles_fk_entity_id FOREIGN KEY(entity_id) REFERENCES %s(%s) ON DELETE CASCADE );`, rolesTableNamePrefix, rolesTableNamePrefix, rolesTableNamePrefix, entityTableName, entityIDColumnName), fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s_role_actions ( role_id VARCHAR(254) NOT NULL, - action VARCHAR(254) NOT NULL, - CONSTRAINT %s_role_actions_unique_role_action_constraint UNIQUE ( role_id, action), + action VARCHAR(254) NOT NULL, + CONSTRAINT %s_role_actions_unique_role_action_constraint UNIQUE (role_id, action), CONSTRAINT %s_role_actions_fk_roles_id FOREIGN KEY(role_id) REFERENCES %s_roles(id) ON DELETE CASCADE - );`, rolesTableNamePrefix, rolesTableNamePrefix, rolesTableNamePrefix, rolesTableNamePrefix), fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s_role_members ( role_id VARCHAR(254) NOT NULL, member_id VARCHAR(254) NOT NULL, + entity_id VARCHAR(36) NOT NULL, CONSTRAINT %s_role_members_unique_role_member_constraint UNIQUE (role_id, member_id), + CONSTRAINT %s_role_members_unique_entity_member_constraint UNIQUE (member_id, entity_id), CONSTRAINT %s_role_members_fk_roles_id FOREIGN KEY(role_id) REFERENCES %s_roles(id) ON DELETE CASCADE - );`, rolesTableNamePrefix, rolesTableNamePrefix, rolesTableNamePrefix, rolesTableNamePrefix), + );`, rolesTableNamePrefix, rolesTableNamePrefix, rolesTableNamePrefix, rolesTableNamePrefix, rolesTableNamePrefix), }, Down: []string{ fmt.Sprintf(`DROP TABLE IF EXISTS %s_roles`, rolesTableNamePrefix), - fmt.Sprintf(`DROP TABLE IF EXISTS %s_roles_actions`, rolesTableNamePrefix), - fmt.Sprintf(`DROP TABLE IF EXISTS %s_roles_members`, rolesTableNamePrefix), + fmt.Sprintf(`DROP TABLE IF EXISTS %s_role_actions`, rolesTableNamePrefix), + fmt.Sprintf(`DROP TABLE IF EXISTS %s_role_members`, rolesTableNamePrefix), }, }, }, diff --git a/pkg/roles/repo/postgres/roles.go b/pkg/roles/repo/postgres/roles.go index 0695da91a0..17f3c09173 100644 --- a/pkg/roles/repo/postgres/roles.go +++ b/pkg/roles/repo/postgres/roles.go @@ -6,13 +6,16 @@ package postgres import ( "context" "database/sql" + "encoding/json" "fmt" "strings" "time" + api "github.com/absmach/supermq/api/http" apiutil "github.com/absmach/supermq/api/http/util" "github.com/absmach/supermq/pkg/errors" repoerr "github.com/absmach/supermq/pkg/errors/repository" + "github.com/absmach/supermq/pkg/policies" "github.com/absmach/supermq/pkg/postgres" "github.com/absmach/supermq/pkg/roles" ) @@ -20,20 +23,35 @@ import ( var _ roles.Repository = (*Repository)(nil) type Repository struct { - db postgres.Database - tableNamePrefix string - entityTableName string - entityIDColumnName string + db postgres.Database + tableNamePrefix string + entityTableName string + entityIDColumnName string + membersListBaseQuery string } // NewRepository instantiates a PostgreSQL // implementation of Roles repository. -func NewRepository(db postgres.Database, tableNamePrefix, entityTableName, entityIDColumnName string) Repository { +func NewRepository(db postgres.Database, entityType, tableNamePrefix, entityTableName, entityIDColumnName string) Repository { + var membersListBaseQuery string + + switch entityType { + case policies.ChannelType: + membersListBaseQuery = channelMembersListBaseQuery() + case policies.ClientType: + membersListBaseQuery = clientMembersListBaseQuery() + case policies.GroupType: + membersListBaseQuery = groupMembersListBaseQuery() + case policies.DomainType: + membersListBaseQuery = domainMembersListBaseQuery() + } + return Repository{ - db: db, - tableNamePrefix: tableNamePrefix, - entityTableName: entityTableName, - entityIDColumnName: entityIDColumnName, + db: db, + tableNamePrefix: tableNamePrefix, + entityTableName: entityTableName, + entityIDColumnName: entityIDColumnName, + membersListBaseQuery: membersListBaseQuery, } } @@ -55,6 +73,11 @@ type dbRole struct { UpdatedAt sql.NullTime `db:"updated_at"` } +type dbMemberRoles struct { + MemberID string `db:"member_id,omitempty"` + Roles json.RawMessage `db:"roles,omitempty"` +} + type dbEntityActionRole struct { EntityID string `db:"entity_id"` Action string `db:"action"` @@ -97,6 +120,7 @@ type dbRoleAction struct { type dbRoleMember struct { RoleID string `db:"role_id"` + EntityID string `db:"entity_id"` MemberID string `db:"member_id"` } @@ -199,15 +223,16 @@ func (repo *Repository) AddRoles(ctx context.Context, rps []roles.RoleProvision) } if len(rp.OptionalMembers) > 0 { - mq := fmt.Sprintf(`INSERT INTO %s_role_members (role_id, member_id) - VALUES (:role_id, :member_id) - RETURNING role_id, member_id`, repo.tableNamePrefix) + mq := fmt.Sprintf(`INSERT INTO %s_role_members (role_id, entity_id, member_id) + VALUES (:role_id, :entity_id, :member_id) + RETURNING role_id, entity_id, member_id`, repo.tableNamePrefix) rMems := []dbRoleMember{} for _, m := range rp.OptionalMembers { rMems = append(rMems, dbRoleMember{ RoleID: rp.ID, MemberID: m, + EntityID: rp.EntityID, }) } if _, err := tx.NamedExec(mq, rMems); err != nil { @@ -533,9 +558,9 @@ func (repo *Repository) RoleRemoveAllActions(ctx context.Context, role roles.Rol } func (repo *Repository) RoleAddMembers(ctx context.Context, role roles.Role, members []string) ([]string, error) { - mq := fmt.Sprintf(`INSERT INTO %s_role_members (role_id, member_id) - VALUES (:role_id, :member_id) - RETURNING role_id, member_id`, repo.tableNamePrefix) + mq := fmt.Sprintf(`INSERT INTO %s_role_members (role_id, entity_id, member_id) + VALUES (:role_id, :entity_id, :member_id) + RETURNING role_id, :entity_id, member_id`, repo.tableNamePrefix) tx, err := repo.db.BeginTxx(ctx, nil) if err != nil { @@ -553,6 +578,7 @@ func (repo *Repository) RoleAddMembers(ctx context.Context, role roles.Role, mem for _, m := range members { rMems = append(rMems, dbRoleMember{ RoleID: role.ID, + EntityID: role.EntityID, MemberID: m, }) } @@ -757,6 +783,604 @@ func (repo *Repository) RetrieveEntitiesRolesActionsMembers(ctx context.Context, return dbToEntityActionRole(dbears), dbToEntityMemberRole(dbemrs), nil } +func (repo *Repository) ListEntityMembers(ctx context.Context, entityID string, pageQuery roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + dbPageQuery, err := toDBMembersRolePageQuery(pageQuery) + if err != nil { + return roles.MembersRolePage{}, err + } + dbPageQuery.EntityID = entityID + + entityMembersQuery := fmt.Sprintf(` + %s + SELECT + member_id, + roles + FROM + members + `, repo.membersListBaseQuery) + + entityMembersQuery = applyConditions(entityMembersQuery, pageQuery) + entityMembersQuery = applyOrdering(entityMembersQuery, pageQuery) + entityMembersQuery = applyLimitOffset(entityMembersQuery) + + rows, err := repo.db.NamedQueryContext(ctx, entityMembersQuery, dbPageQuery) + if err != nil { + return roles.MembersRolePage{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + + defer rows.Close() + mems := []roles.MemberRoles{} + for rows.Next() { + var dbmr dbMemberRoles + if err = rows.StructScan(&dbmr); err != nil { + return roles.MembersRolePage{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + + var roleActions []roles.MemberRoleActions + if err := json.Unmarshal(dbmr.Roles, &roleActions); err != nil { + return roles.MembersRolePage{}, fmt.Errorf("failed to unmarshal roles JSON: %w", err) + } + mems = append(mems, roles.MemberRoles{MemberID: dbmr.MemberID, Roles: roleActions}) + } + + entityMembersCountQuery := fmt.Sprintf(` + %s + SELECT + COUNT(*) + FROM + members + `, repo.membersListBaseQuery) + + entityMembersCountQuery = applyConditions(entityMembersCountQuery, pageQuery) + + total, err := postgres.Total(ctx, repo.db, entityMembersCountQuery, dbPageQuery) + if err != nil { + return roles.MembersRolePage{}, err + } + + return roles.MembersRolePage{ + Total: total, + Limit: pageQuery.Limit, + Offset: pageQuery.Offset, + Members: mems, + }, nil +} + +func (repo *Repository) RemoveEntityMembers(ctx context.Context, entityID string, memberIDs []string) error { + return nil +} + func (repo *Repository) RemoveMemberFromAllRoles(ctx context.Context, memberID string) (err error) { return nil } + +func applyConditions(query string, pageQuery roles.MembersRolePageQuery) string { + var whereClause []string + + if pageQuery.RoleID != "" { + whereClause = append(whereClause, " roles @> :role_id ") + + } + if pageQuery.RoleName != "" { + whereClause = append(whereClause, " roles @> :role_name ") + + } + if len(pageQuery.Actions) != 0 { + whereClause = append(whereClause, " roles @> :actions ") + } + if pageQuery.AccessType != "" { + whereClause = append(whereClause, " roles @> :access_type ") + + } + if pageQuery.AccessProviderID != "" { + whereClause = append(whereClause, " roles @> :access_provider_id ") + } + + var whereCondition string + if len(whereClause) != 0 { + whereCondition = "WHERE " + strings.Join(whereClause, " AND ") + } + + return fmt.Sprintf(`%s + %s`, query, whereCondition) +} +func applyOrdering(query string, pageQuery roles.MembersRolePageQuery) string { + switch pageQuery.Order { + case "access_provider_id", "role_name", "role_id", "access_type": + query = fmt.Sprintf("%s ORDER BY %s", query, pageQuery.Order) + if pageQuery.Dir == api.AscDir || pageQuery.Dir == api.DescDir { + query = fmt.Sprintf("%s %s", query, pageQuery.Dir) + } + } + return query +} + +func applyLimitOffset(query string) string { + return fmt.Sprintf(`%s + LIMIT :limit OFFSET :offset`, query) +} + +type dbMembersRolePageQuery struct { + Offset uint64 `db:"offset"` + Limit uint64 `db:"limit"` + OrderBy string `db:"order_by"` + Direction string `db:"dir"` + AccessProviderID json.RawMessage `db:"access_provider_id"` + RoleId json.RawMessage `db:"role_id"` + RoleName json.RawMessage `db:"role_name"` + Actions json.RawMessage `db:"actions"` + AccessType json.RawMessage `db:"access_type"` + EntityID string `db:"entity_id"` +} + +func toDBMembersRolePageQuery(pageQuery roles.MembersRolePageQuery) (dbMembersRolePageQuery, error) { + actions := []byte("{}") + if len(pageQuery.Actions) != 0 { + var err error + jactions := []struct { + Actions []string `json:"actions"` + }{ + { + Actions: pageQuery.Actions, + }, + } + actions, err = json.Marshal(jactions) + if err != nil { + return dbMembersRolePageQuery{}, err + } + } + + accessProviderID := []byte("{}") + if pageQuery.AccessProviderID != "" { + accessProviderID = []byte(fmt.Sprintf("[{\"access_provider_id\" : \"%s\"}]", pageQuery.AccessProviderID)) + } + + roleID := []byte("{}") + if pageQuery.RoleID != "" { + roleID = []byte(fmt.Sprintf("[{\"role_id\" : \"%s\"}]", pageQuery.RoleID)) + } + + roleName := []byte("{}") + if pageQuery.RoleName != "" { + roleName = []byte(fmt.Sprintf("[{\"role_name\" : \"%s\"}]", pageQuery.RoleName)) + } + + accessType := []byte("{}") + if pageQuery.AccessType != "" { + accessType = []byte(fmt.Sprintf("[{\"access_type\" : \"%s\"}]", pageQuery.AccessType)) + } + + return dbMembersRolePageQuery{ + Offset: pageQuery.Offset, + Limit: pageQuery.Limit, + OrderBy: pageQuery.Order, + Direction: pageQuery.Dir, + AccessProviderID: accessProviderID, + RoleId: roleID, + RoleName: roleName, + Actions: actions, + AccessType: accessType, + }, nil +} + +func domainMembersListBaseQuery() string { + return ` +WITH ungrouped_members AS ( + SELECT + dr.id, + dr.name, + drm.member_id, + ARRAY_AGG(DISTINCT all_actions.action) AS actions, + 'direct' AS access_type, + '' AS access_provider_id + FROM + domains_role_members drm + JOIN domains_roles dr ON + dr.id = drm.role_id + JOIN domains_role_actions dra ON + dra.role_id = dr.id + JOIN domains_role_actions all_actions ON + all_actions.role_id = drm.role_id + WHERE + dr.entity_id = :entity_id + GROUP BY + dr.id, + drm.member_id +), +members AS ( + SELECT + um.member_id, + JSONB_AGG( + JSON_BUILD_OBJECT( + 'role_id', um.id, + 'role_name', um.name, + 'actions', um.actions, + 'access_type', um.access_type, + 'access_provider_id', um.access_provider_id + ) + ) AS roles + FROM + ungrouped_members um + GROUP BY + um.member_id +) + ` +} + +func groupMembersListBaseQuery() string { + return ` +WITH ungrouped_members AS ( + SELECT + gr."name", + gr.id, + grm.member_id, + ARRAY_AGG(DISTINCT agg_gra."action") AS actions, + CASE + WHEN g.id = :entity_id THEN 'direct' + ELSE 'indirect_group' + END AS access_type, + CASE + WHEN g.id = :entity_id THEN '' + ELSE g.id + END AS access_provider_id + FROM + "groups" g + JOIN + groups_roles gr ON + gr.entity_id = g.id + JOIN + groups_role_members grm ON + grm.role_id = gr.id + JOIN + groups_role_actions gra ON + gra.role_id = gr.id + JOIN + groups_role_actions agg_gra ON + agg_gra.role_id = gr.id + WHERE + g.path @> ( + SELECT + "path" + FROM + "groups" + WHERE + id = :entity_id + LIMIT 1 + ) + AND ( + g.id = :entity_id + OR gra."action" LIKE 'subgroup%' + ) -- -- If g.id = , it allows all actions. If g.id <> , it only allows actions matching 'subgroup%'. + GROUP BY + gr.id, + grm.member_id, + g.id +UNION + SELECT + dr."name", + dr.id, + drm.member_id, + ARRAY_AGG(DISTINCT agg_dra."action") AS actions, + 'domain' AS access_type, + d.id AS access_provider_id + FROM + "groups" g + JOIN + domains d ON + d.id = g.domain_id + JOIN + domains_roles dr ON + dr.entity_id = d.id + JOIN + domains_role_members drm ON + dr.id = drm.role_id + JOIN + domains_role_actions dra ON + dr.id = dra.role_id + JOIN + domains_role_actions agg_dra ON + agg_dra.role_id = dr.id + WHERE + g.id = :entity_id + AND + dra."action" LIKE 'group%' + GROUP BY + dr.id, + drm.member_id, + d.id +), +members AS ( + SELECT + um.member_id, + JSONB_AGG( + JSON_BUILD_OBJECT( + 'role_id', um.id, + 'role_name', um.name, + 'actions', um.actions, + 'access_type', um.access_type, + 'access_provider_id', um.access_provider_id + ) + ) AS roles + FROM + ungrouped_members um + GROUP BY + um.member_id +) + ` +} + +func clientMembersListBaseQuery() string { + return ` +WITH client_group AS ( + SELECT + c.id, + c.parent_group_id, + c.domain_id, + g."path" AS parent_group_path + FROM + clients c + LEFT JOIN + "groups" g ON + g.id = c.parent_group_id + WHERE + c.id = :entity_id + LIMIT 1 +), +ungrouped_members AS ( + SELECT + cr."name", + cr.id, + crm.member_id, + ARRAY_AGG(DISTINCT cra."action") AS actions, + 'direct' AS access_type, + '' AS access_provider_id, + ''::::LTREE AS access_provider_path + FROM + client_group cg + JOIN + clients_roles cr ON + cr.entity_id = cg.id + JOIN + clients_role_members crm ON + crm.role_id = cr.id + JOIN + clients_role_actions cra ON + cra.role_id = cr.id + GROUP BY + cr.id, + crm.member_id + UNION + SELECT + gr."name", + gr.id, + grm.member_id, + ARRAY_AGG(DISTINCT agg_gra."action") AS actions, + CASE + WHEN g.id = cg.parent_group_id THEN 'direct_group' + ELSE 'indirect_group' + END AS access_type, + g.id AS access_provider_id, + g.path AS access_provider_path + FROM + client_group cg + JOIN + "groups" g ON + g.PATH @> cg.parent_group_path + JOIN + groups_roles gr ON + g.id = gr.entity_id + JOIN + groups_role_members grm ON + grm.role_id = gr.id + JOIN + groups_role_actions gra ON + gra.role_id = gr.id + JOIN + groups_role_actions agg_gra ON + agg_gra.role_id = gr.id + WHERE + ( + gra."action" LIKE 'client%%' + AND g.id = cg.parent_group_id + ) + OR + ( + gra."action" LIKE 'subgroup_client%%' + AND g.id <> cg.parent_group_id + ) + GROUP BY + gr.id, + grm.member_id, + g.id, + cg.parent_group_id + UNION + SELECT + dr."name", + dr.id, + drm.member_id, + ARRAY_AGG(DISTINCT agg_dra."action") AS actions, + 'domain' AS access_type, + d.id AS access_provider_id, + ''::::LTREE AS access_provider_path + FROM + client_group cg + JOIN + domains d ON + d.id = cg.domain_id + JOIN + domains_roles dr ON + dr.entity_id = d.id + JOIN + domains_role_members drm ON + dr.id = drm.role_id + JOIN + domains_role_actions dra ON + dr.id = dra.role_id + JOIN + domains_role_actions agg_dra ON + agg_dra.role_id = dr.id + WHERE + dra."action" LIKE 'client%' + GROUP BY + dr.id, + drm.member_id, + d.id +), +members AS ( + SELECT + um.member_id, + JSONB_AGG( + JSON_BUILD_OBJECT( + 'role_id', um.id, + 'role_name', um.name, + 'actions', um.actions, + 'access_type', um.access_type, + 'access_provider_id', um.access_provider_id, + 'access_provider_path', um.access_provider_path + ) + ) AS roles + FROM + ungrouped_members um + GROUP BY + um.member_id +) + ` +} + +func channelMembersListBaseQuery() string { + return ` +WITH channel_group AS ( + SELECT + c.id, + c.parent_group_id, + c.domain_id, + g."path" AS parent_group_path + FROM + channels c + LEFT JOIN + "groups" g ON + g.id = c.parent_group_id + WHERE + c.id = :entity_id + LIMIT 1 +), +ungrouped_members AS ( + SELECT + cr."name", + cr.id, + crm.member_id, + ARRAY_AGG(DISTINCT cra."action") AS actions, + 'direct' AS access_type, + '' AS access_provider_id, + ''::::LTREE AS access_provider_path + FROM + channel_group cg + JOIN + channels_roles cr ON + cr.entity_id = cg.id + JOIN + channels_role_members crm ON + crm.role_id = cr.id + JOIN + channels_role_actions cra ON + cra.role_id = cr.id + GROUP BY + cr.id, + crm.member_id + UNION + SELECT + gr."name", + gr.id, + grm.member_id, + ARRAY_AGG(DISTINCT agg_gra."action") AS actions, + CASE + WHEN g.id = cg.parent_group_id THEN 'direct_group' + ELSE 'indirect_group' + END AS access_type, + g.id AS access_provider_id, + g.path AS access_provider_path + FROM + channel_group cg + JOIN + "groups" g ON + g.PATH @> cg.parent_group_path + JOIN + groups_roles gr ON + g.id = gr.entity_id + JOIN + groups_role_members grm ON + grm.role_id = gr.id + JOIN + groups_role_actions gra ON + gra.role_id = gr.id + JOIN + groups_role_actions agg_gra ON + agg_gra.role_id = gr.id + WHERE + ( + gra."action" LIKE 'channel%%' + AND g.id = cg.parent_group_id + ) + OR + ( + gra."action" LIKE 'subgroup_channel%%' + AND g.id <> cg.parent_group_id + ) + GROUP BY + gr.id, + grm.member_id, + g.id, + cg.parent_group_id + UNION + SELECT + dr."name", + dr.id, + drm.member_id, + ARRAY_AGG(DISTINCT agg_dra."action") AS actions, + 'domain' AS access_type, + d.id AS access_provider_id, + ''::::LTREE AS access_provider_path + FROM + channel_group cg + JOIN + domains d ON + d.id = cg.domain_id + JOIN + domains_roles dr ON + dr.entity_id = d.id + JOIN + domains_role_members drm ON + dr.id = drm.role_id + JOIN + domains_role_actions dra ON + dr.id = dra.role_id + JOIN + domains_role_actions agg_dra ON + agg_dra.role_id = dr.id + WHERE + dra."action" LIKE 'channel%' + GROUP BY + dr.id, + drm.member_id, + d.id +), +members AS ( + SELECT + um.member_id, + JSONB_AGG( + JSON_BUILD_OBJECT( + 'role_id', um.id, + 'role_name', um.name, + 'actions', um.actions, + 'access_type', um.access_type, + 'access_provider_id', um.access_provider_id, + 'access_provider_path', um.access_provider_path + ) + ) AS roles + FROM + ungrouped_members um + GROUP BY + um.member_id +) + ` +} diff --git a/pkg/roles/rolemanager/api/decoders.go b/pkg/roles/rolemanager/api/decoders.go index 289818121a..853349804f 100644 --- a/pkg/roles/rolemanager/api/decoders.go +++ b/pkg/roles/rolemanager/api/decoders.go @@ -55,6 +55,91 @@ func (d Decoder) DecodeListRoles(_ context.Context, r *http.Request) (interface{ return req, nil } +func (d Decoder) DecodeListEntityMembers(_ context.Context, r *http.Request) (interface{}, error) { + 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) + } + + order, err := apiutil.ReadStringQuery(r, api.OrderKey, "") + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + dir, err := apiutil.ReadStringQuery(r, api.LimitKey, "") + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + accessProviderID, err := apiutil.ReadStringQuery(r, api.AccessProviderIDKey, "") + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + accessType, err := apiutil.ReadStringQuery(r, api.AccessTypeKey, "") + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + roleId, err := apiutil.ReadStringQuery(r, api.RoleIDKey, "") + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + roleName, err := apiutil.ReadStringQuery(r, api.RoleNameKey, "") + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + allActions, err := apiutil.ReadStringQuery(r, api.ActionsKey, "") + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + actions := []string{} + + allActions = strings.TrimSpace(allActions) + if allActions != "" { + actions = strings.Split(allActions, ",") + } + + req := listEntityMembersReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + limit: l, + offset: o, + order: order, + dir: dir, + accessProviderID: accessProviderID, + roleId: roleId, + roleName: roleName, + actions: actions, + accessType: accessType, + } + return req, nil +} + +func (d Decoder) DecodeRemoveEntityMembers(_ 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 := removeEntityMembersReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + } + + 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 (d Decoder) DecodeViewRole(_ context.Context, r *http.Request) (interface{}, error) { req := viewRoleReq{ token: apiutil.ExtractBearerToken(r), diff --git a/pkg/roles/rolemanager/api/endpoints.go b/pkg/roles/rolemanager/api/endpoints.go index 982b83affc..a21224d0d2 100644 --- a/pkg/roles/rolemanager/api/endpoints.go +++ b/pkg/roles/rolemanager/api/endpoints.go @@ -55,6 +55,57 @@ func ListRolesEndpoint(svc roles.RoleManager) endpoint.Endpoint { } } +func ListEntityMembersEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listEntityMembersReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + pageQuery := roles.MembersRolePageQuery{ + Offset: req.offset, + Limit: req.limit, + AccessProviderID: req.accessProviderID, + Order: req.order, + Dir: req.dir, + RoleID: req.roleId, + RoleName: req.roleName, + Actions: req.actions, + AccessType: req.accessType, + } + + mems, err := svc.ListEntityMembers(ctx, session, req.entityID, pageQuery) + if err != nil { + return nil, err + } + return listEntityMembersRes{mems}, nil + } +} + +func RemoveEntityMembersEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(removeEntityMembersReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + if err := svc.RemoveEntityMembers(ctx, session, req.entityID, req.MemberIDs); err != nil { + return nil, err + } + return deleteEntityMembersRes{}, nil + } +} + func ViewRoleEndpoint(svc roles.RoleManager) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(viewRoleReq) diff --git a/pkg/roles/rolemanager/api/requests.go b/pkg/roles/rolemanager/api/requests.go index cf8bc611cb..56fb4255bf 100644 --- a/pkg/roles/rolemanager/api/requests.go +++ b/pkg/roles/rolemanager/api/requests.go @@ -53,6 +53,52 @@ func (req listRolesReq) validate() error { return nil } +type listEntityMembersReq struct { + token string + entityID string + limit uint64 + offset uint64 + dir string + order string + accessProviderID string + roleId string + roleName string + actions []string + accessType string +} + +func (req listEntityMembersReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.entityID == "" { + return apiutil.ErrMissingID + } + if req.limit > api.MaxLimitSize || req.limit < 1 { + return apiutil.ErrLimitSize + } + return nil +} + +type removeEntityMembersReq struct { + token string + entityID string + MemberIDs []string `json:"member_ids"` +} + +func (req removeEntityMembersReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.entityID == "" { + return apiutil.ErrMissingID + } + if len(req.MemberIDs) == 0 { + return apiutil.ErrMissingMemberIDs + } + return nil +} + type viewRoleReq struct { token string entityID string diff --git a/pkg/roles/rolemanager/api/responses.go b/pkg/roles/rolemanager/api/responses.go index 1c14b4bbda..2f62591751 100644 --- a/pkg/roles/rolemanager/api/responses.go +++ b/pkg/roles/rolemanager/api/responses.go @@ -59,6 +59,36 @@ func (res listRolesRes) Empty() bool { return false } +type listEntityMembersRes struct { + roles.MembersRolePage +} + +func (res listEntityMembersRes) Code() int { + return http.StatusOK +} + +func (res listEntityMembersRes) Headers() map[string]string { + return map[string]string{} +} + +func (res listEntityMembersRes) Empty() bool { + return false +} + +type deleteEntityMembersRes struct{} + +func (res deleteEntityMembersRes) Code() int { + return http.StatusNoContent +} + +func (res deleteEntityMembersRes) Headers() map[string]string { + return map[string]string{} +} + +func (res deleteEntityMembersRes) Empty() bool { + return true +} + type viewRoleRes struct { roles.Role } diff --git a/pkg/roles/rolemanager/api/router.go b/pkg/roles/rolemanager/api/router.go index 1effdd6b0f..899e235b3b 100644 --- a/pkg/roles/rolemanager/api/router.go +++ b/pkg/roles/rolemanager/api/router.go @@ -27,6 +27,20 @@ func EntityRoleMangerRouter(svc roles.RoleManager, d Decoder, r chi.Router, opts opts..., ), "list_roles").ServeHTTP) + r.Get("/members", otelhttp.NewHandler(kithttp.NewServer( + ListEntityMembersEndpoint(svc), + d.DecodeListEntityMembers, + api.EncodeResponse, + opts..., + ), "list_entity_members").ServeHTTP) + + r.Delete("/", otelhttp.NewHandler(kithttp.NewServer( + RemoveEntityMembersEndpoint(svc), + d.DecodeListEntityMembers, + api.EncodeResponse, + opts..., + ), "delete_entity_members").ServeHTTP) + r.Route("/{roleID}", func(r chi.Router) { r.Get("/", otelhttp.NewHandler(kithttp.NewServer( ViewRoleEndpoint(svc), diff --git a/pkg/roles/rolemanager/events/consumer/handler.go b/pkg/roles/rolemanager/events/consumer/handler.go index ebb3d8a654..8d9907876e 100644 --- a/pkg/roles/rolemanager/events/consumer/handler.go +++ b/pkg/roles/rolemanager/events/consumer/handler.go @@ -10,6 +10,7 @@ import ( "github.com/absmach/supermq/pkg/errors" repoerr "github.com/absmach/supermq/pkg/errors/repository" "github.com/absmach/supermq/pkg/roles" + "github.com/absmach/supermq/pkg/roles/rolemanager/events" ) const ( @@ -25,17 +26,67 @@ const ( ) type EventHandler struct { - entityType string - repo roles.Repository + entityType string + repo roles.Repository + addRole string + removeRole string + updateRole string + addRoleActions string + removeRoleActions string + removeAllRoleActions string + addRoleMembers string + removeRoleMembers string + removeRoleAllMembers string + removeMemberFromAllRoles string + removeEntityMembers string } func NewEventHandler(entityType string, repo roles.Repository) EventHandler { return EventHandler{ - entityType: entityType, - repo: repo, + entityType: entityType, + repo: repo, + addRole: entityType + "." + events.AddRole, + removeRole: entityType + "." + events.RemoveRole, + updateRole: entityType + "." + events.UpdateRole, + addRoleActions: entityType + "." + events.AddRoleActions, + removeRoleActions: entityType + "." + events.RemoveRoleActions, + removeAllRoleActions: entityType + "." + events.RemoveAllRoleActions, + addRoleMembers: entityType + "." + events.AddRoleMembers, + removeRoleMembers: entityType + "." + events.RemoveRoleMembers, + removeRoleAllMembers: entityType + "." + events.RemoveRoleAllMembers, + removeMemberFromAllRoles: entityType + "." + events.RemoveMemberFromAllRoles, + removeEntityMembers: entityType + "." + events.RemoveEntityMembers, } } +func (es *EventHandler) Handle(ctx context.Context, op interface{}, msg map[string]interface{}) error { + switch op { + case es.addRole: + return es.AddEntityRoleHandler(ctx, msg) + case es.removeRole: + return es.RemoveEntityRoleHandler(ctx, msg) + case es.updateRole: + return es.UpdateEntityRoleHandler(ctx, msg) + case es.addRoleActions: + return es.AddEntityRoleActionsHandler(ctx, msg) + case es.removeRoleActions: + return es.RemoveEntityRoleActionsHandler(ctx, msg) + case es.removeAllRoleActions: + return es.RemoveAllEntityRoleActionsHandler(ctx, msg) + case es.addRoleMembers: + return es.AddEntityRoleMembersHandler(ctx, msg) + case es.removeRoleMembers: + return es.RemoveEntityRoleMembersHandler(ctx, msg) + case es.removeRoleAllMembers: + return es.RemoveAllMembersFromEntityRoleHandler(ctx, msg) + case es.removeEntityMembers: + return es.RemoveEntityMembersHandler(ctx, msg) + case es.removeMemberFromAllRoles: + return es.RemoveMemberFromAllEntityHandler(ctx, msg) + } + return nil +} + func (es *EventHandler) AddEntityRoleHandler(ctx context.Context, data map[string]interface{}) error { rps, err := ToRoleProvision(data) if err != nil { @@ -171,7 +222,7 @@ func (es *EventHandler) RemoveEntityRoleMembersHandler(ctx context.Context, data return nil } -func (es *EventHandler) RemoveAllEntityRoleMembersHandler(ctx context.Context, data map[string]interface{}) error { +func (es *EventHandler) RemoveAllMembersFromEntityRoleHandler(ctx context.Context, data map[string]interface{}) error { id, ok := data["role_id"].(string) if !ok { return fmt.Errorf(errRemoveEntityRoleAllMembersEvent, es.entityType, errRoleID) @@ -183,6 +234,26 @@ func (es *EventHandler) RemoveAllEntityRoleMembersHandler(ctx context.Context, d return nil } +func (es *EventHandler) RemoveEntityMembersHandler(ctx context.Context, data map[string]interface{}) error { + entityID, ok := data["entity_id"].(string) + if !ok { + return fmt.Errorf(errRemoveEntityRoleAllMembersEvent, es.entityType, errEntityID) + } + imems, ok := data["members"].([]interface{}) + if !ok { + return fmt.Errorf(errRemoveEntityRoleMembersEvent, es.entityType, errMembers) + } + mems, err := ToStrings(imems) + if err != nil { + return fmt.Errorf(errRemoveEntityRoleMembersEvent, es.entityType, err) + } + + // ToDo: added when repo is implemented. + _ = entityID + _ = mems + return nil +} + func (es *EventHandler) RemoveMemberFromAllEntityHandler(ctx context.Context, data map[string]interface{}) error { return nil } diff --git a/pkg/roles/rolemanager/events/events.go b/pkg/roles/rolemanager/events/events.go index 44de0de898..e7365705ec 100644 --- a/pkg/roles/rolemanager/events/events.go +++ b/pkg/roles/rolemanager/events/events.go @@ -9,23 +9,25 @@ import ( ) const ( - addRole = "role.add" - removeRole = "role.remove" - updateRole = "role.update" - viewRole = "role.view" - viewAllRole = "role.view_all" - listAvailableActions = "role.list_available_actions" - addRoleActions = "role.actions.add" - listRoleActions = "role.actions.ist" - checkRoleActions = "role.actions.check" - removeRoleActions = "role.actions.remove" - removeAllRoleActions = "role.actions.remove_all" - addRoleMembers = "role.members.add" - listRoleMembers = "role.members.list" - checkRoleMembers = "role.members.check" - removeRoleMembers = "role.members.remove" - removeRoleAllMembers = "role.members.remove_all" - removeMemberFromAllRoles = "role.members.remove_from_all_roles" + AddRole = "role.add" + RemoveRole = "role.remove" + UpdateRole = "role.update" + ViewRole = "role.view" + ViewAllRole = "role.view_all" + ListAvailableActions = "role.list_available_actions" + AddRoleActions = "role.actions.add" + ListRoleActions = "role.actions.ist" + CheckRoleActions = "role.actions.check" + RemoveRoleActions = "role.actions.remove" + RemoveAllRoleActions = "role.actions.remove_all" + AddRoleMembers = "role.members.add" + ListRoleMembers = "role.members.list" + CheckRoleMembers = "role.members.check" + RemoveRoleMembers = "role.members.remove" + RemoveRoleAllMembers = "role.members.remove_all" + ListEntityMembers = "members.list" + RemoveEntityMembers = "members.remove" + RemoveMemberFromAllRoles = "role.members.remove_from_all_roles" ) var ( @@ -45,6 +47,8 @@ var ( _ events.Event = (*roleCheckMembersExistsEvent)(nil) _ events.Event = (*roleRemoveMembersEvent)(nil) _ events.Event = (*roleRemoveAllMembersEvent)(nil) + _ events.Event = (*listEntityMembersEvent)(nil) + _ events.Event = (*removeEntityMembersEvent)(nil) _ events.Event = (*removeMemberFromAllRolesEvent)(nil) ) @@ -55,7 +59,7 @@ type addRoleEvent struct { func (are addRoleEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": are.operationPrefix + addRole, + "operation": are.operationPrefix + AddRole, "id": are.ID, "name": are.Name, "entity_id": are.EntityID, @@ -77,7 +81,7 @@ type removeRoleEvent struct { func (rre removeRoleEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": rre.operationPrefix + removeRole, + "operation": rre.operationPrefix + RemoveRole, "entity_id": rre.entityID, "role_id": rre.roleID, } @@ -91,7 +95,7 @@ type updateRoleEvent struct { func (ure updateRoleEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": ure.operationPrefix + updateRole, + "operation": ure.operationPrefix + UpdateRole, "id": ure.ID, "name": ure.Name, "entity_id": ure.EntityID, @@ -110,7 +114,7 @@ type retrieveRoleEvent struct { func (rre retrieveRoleEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": rre.operationPrefix + viewRole, + "operation": rre.operationPrefix + ViewRole, "id": rre.ID, "name": rre.Name, "entity_id": rre.EntityID, @@ -131,7 +135,7 @@ type retrieveAllRolesEvent struct { func (rare retrieveAllRolesEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": rare.operationPrefix + viewAllRole, + "operation": rare.operationPrefix + ViewAllRole, "entity_id": rare.entityID, "limit": rare.limit, "offset": rare.offset, @@ -145,7 +149,7 @@ type listAvailableActionsEvent struct { func (laae listAvailableActionsEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": laae.operationPrefix + listAvailableActions, + "operation": laae.operationPrefix + ListAvailableActions, } return val, nil } @@ -159,7 +163,7 @@ type roleAddActionsEvent struct { func (raae roleAddActionsEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": raae.operationPrefix + addRoleActions, + "operation": raae.operationPrefix + AddRoleActions, "entity_id": raae.entityID, "role_id": raae.roleID, "actions": raae.actions, @@ -175,7 +179,7 @@ type roleListActionsEvent struct { func (rlae roleListActionsEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": rlae.operationPrefix + listRoleActions, + "operation": rlae.operationPrefix + ListRoleActions, "entity_id": rlae.entityID, "role_id": rlae.roleID, } @@ -192,7 +196,7 @@ type roleCheckActionsExistsEvent struct { func (rcaee roleCheckActionsExistsEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": rcaee.operationPrefix + checkRoleActions, + "operation": rcaee.operationPrefix + CheckRoleActions, "entity_id": rcaee.entityID, "role_id": rcaee.roleID, "actions": rcaee.actions, @@ -210,7 +214,7 @@ type roleRemoveActionsEvent struct { func (rrae roleRemoveActionsEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": rrae.operationPrefix + removeRoleActions, + "operation": rrae.operationPrefix + RemoveRoleActions, "entity_id": rrae.entityID, "role_id": rrae.roleID, "actions": rrae.actions, @@ -226,7 +230,7 @@ type roleRemoveAllActionsEvent struct { func (rraae roleRemoveAllActionsEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": rraae.operationPrefix + removeAllRoleActions, + "operation": rraae.operationPrefix + RemoveAllRoleActions, "entity_id": rraae.entityID, "role_id": rraae.roleID, } @@ -242,7 +246,7 @@ type roleAddMembersEvent struct { func (rame roleAddMembersEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": rame.operationPrefix + addRoleMembers, + "operation": rame.operationPrefix + AddRoleMembers, "entity_id": rame.entityID, "role_id": rame.roleID, "members": rame.members, @@ -260,7 +264,7 @@ type roleListMembersEvent struct { func (rlme roleListMembersEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": rlme.operationPrefix + listRoleMembers, + "operation": rlme.operationPrefix + ListRoleMembers, "entity_id": rlme.entityID, "role_id": rlme.roleID, "limit": rlme.limit, @@ -278,7 +282,7 @@ type roleCheckMembersExistsEvent struct { func (rcmee roleCheckMembersExistsEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": rcmee.operationPrefix + checkRoleMembers, + "operation": rcmee.operationPrefix + CheckRoleMembers, "entity_id": rcmee.entityID, "role_id": rcmee.roleID, "members": rcmee.members, @@ -295,7 +299,7 @@ type roleRemoveMembersEvent struct { func (rrme roleRemoveMembersEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": rrme.operationPrefix + removeRoleMembers, + "operation": rrme.operationPrefix + RemoveRoleMembers, "entity_id": rrme.entityID, "role_id": rrme.roleID, "members": rrme.members, @@ -311,13 +315,45 @@ type roleRemoveAllMembersEvent struct { func (rrame roleRemoveAllMembersEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": rrame.operationPrefix + removeRoleAllMembers, + "operation": rrame.operationPrefix + RemoveRoleAllMembers, "entity_id": rrame.entityID, "role_id": rrame.roleID, } return val, nil } +type listEntityMembersEvent struct { + operationPrefix string + entityID string + limit uint64 + offset uint64 +} + +func (leme listEntityMembersEvent) Encode() (map[string]interface{}, error) { + val := map[string]interface{}{ + "operation": leme.operationPrefix + ListEntityMembers, + "entity_id": leme.entityID, + "limit": leme.limit, + "offset": leme.offset, + } + return val, nil +} + +type removeEntityMembersEvent struct { + operationPrefix string + entityID string + members []string +} + +func (reme removeEntityMembersEvent) Encode() (map[string]interface{}, error) { + val := map[string]interface{}{ + "operation": reme.operationPrefix + RemoveEntityMembers, + "entity_id": reme.entityID, + "members": reme.members, + } + return val, nil +} + type removeMemberFromAllRolesEvent struct { operationPrefix string memberID string @@ -325,7 +361,7 @@ type removeMemberFromAllRolesEvent struct { func (rmare removeMemberFromAllRolesEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": rmare.operationPrefix + removeMemberFromAllRoles, + "operation": rmare.operationPrefix + RemoveMemberFromAllRoles, "member_id": rmare.memberID, } return val, nil diff --git a/pkg/roles/rolemanager/events/streams.go b/pkg/roles/rolemanager/events/streams.go index 94c06a84e6..9eab57bc85 100644 --- a/pkg/roles/rolemanager/events/streams.go +++ b/pkg/roles/rolemanager/events/streams.go @@ -299,6 +299,40 @@ func (rmes *RoleManagerEventStore) RoleRemoveAllMembers(ctx context.Context, ses return nil } +func (rmes *RoleManagerEventStore) ListEntityMembers(ctx context.Context, session authn.Session, entityID string, pageQuery roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + mems, err := rmes.svc.ListEntityMembers(ctx, session, entityID, pageQuery) + if err != nil { + return mems, err + } + + e := listEntityMembersEvent{ + operationPrefix: rmes.operationPrefix, + entityID: entityID, + limit: pageQuery.Limit, + offset: pageQuery.Offset, + } + if err := rmes.Publish(ctx, e); err != nil { + return mems, err + } + return mems, nil +} + +func (rmes *RoleManagerEventStore) RemoveEntityMembers(ctx context.Context, session authn.Session, entityID string, members []string) error { + if err := rmes.svc.RemoveEntityMembers(ctx, session, entityID, members); err != nil { + return err + } + + e := removeEntityMembersEvent{ + operationPrefix: rmes.operationPrefix, + entityID: entityID, + members: members, + } + if err := rmes.Publish(ctx, e); err != nil { + return err + } + return nil +} + func (rmes *RoleManagerEventStore) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) (err error) { if err := rmes.svc.RemoveMemberFromAllRoles(ctx, session, memberID); err != nil { return err diff --git a/pkg/roles/rolemanager/middleware/authoirzation.go b/pkg/roles/rolemanager/middleware/authoirzation.go index 4f4f6fd7c3..02df36d3a4 100644 --- a/pkg/roles/rolemanager/middleware/authoirzation.go +++ b/pkg/roles/rolemanager/middleware/authoirzation.go @@ -240,8 +240,8 @@ func (ram RoleManagerAuthorizationMiddleware) RoleCheckMembersExists(ctx context return ram.svc.RoleCheckMembersExists(ctx, session, entityID, roleID, members) } -func (ram RoleManagerAuthorizationMiddleware) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID, roleID string, members []string) (err error) { - if err := ram.authorize(ctx, roles.OpRoleRemoveMembers, smqauthz.PolicyReq{ +func (ram RoleManagerAuthorizationMiddleware) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleID string) (err error) { + if err := ram.authorize(ctx, roles.OpRoleRemoveAllMembers, smqauthz.PolicyReq{ Domain: session.DomainID, Subject: session.DomainUserID, SubjectType: policies.UserType, @@ -251,10 +251,24 @@ func (ram RoleManagerAuthorizationMiddleware) RoleRemoveMembers(ctx context.Cont }); err != nil { return err } - return ram.svc.RoleRemoveMembers(ctx, session, entityID, roleID, members) + return ram.svc.RoleRemoveAllMembers(ctx, session, entityID, roleID) } -func (ram RoleManagerAuthorizationMiddleware) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleID string) (err error) { +func (ram RoleManagerAuthorizationMiddleware) ListEntityMembers(ctx context.Context, session authn.Session, entityID string, pageQuery roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + if err := ram.authorize(ctx, roles.OpRoleListMembers, smqauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return roles.MembersRolePage{}, err + } + return ram.svc.ListEntityMembers(ctx, session, entityID, pageQuery) +} + +func (ram RoleManagerAuthorizationMiddleware) RemoveEntityMembers(ctx context.Context, session authn.Session, entityID string, members []string) error { if err := ram.authorize(ctx, roles.OpRoleRemoveAllMembers, smqauthz.PolicyReq{ Domain: session.DomainID, Subject: session.DomainUserID, @@ -265,7 +279,21 @@ func (ram RoleManagerAuthorizationMiddleware) RoleRemoveAllMembers(ctx context.C }); err != nil { return err } - return ram.svc.RoleRemoveAllMembers(ctx, session, entityID, roleID) + return ram.svc.RemoveEntityMembers(ctx, session, entityID, members) +} + +func (ram RoleManagerAuthorizationMiddleware) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID, roleID string, members []string) (err error) { + if err := ram.authorize(ctx, roles.OpRoleRemoveMembers, smqauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return err + } + return ram.svc.RoleRemoveMembers(ctx, session, entityID, roleID, members) } func (ram RoleManagerAuthorizationMiddleware) authorize(ctx context.Context, op svcutil.Operation, pr smqauthz.PolicyReq) error { diff --git a/pkg/roles/rolemanager/middleware/logging.go b/pkg/roles/rolemanager/middleware/logging.go index 4e4af3aa58..0282c686ba 100644 --- a/pkg/roles/rolemanager/middleware/logging.go +++ b/pkg/roles/rolemanager/middleware/logging.go @@ -326,6 +326,47 @@ func (lm *RoleManagerLoggingMiddleware) RoleRemoveAllMembers(ctx context.Context return lm.svc.RoleRemoveAllMembers(ctx, session, entityID, roleID) } +func (lm *RoleManagerLoggingMiddleware) ListEntityMembers(ctx context.Context, session authn.Session, entityID string, pageQuery roles.MembersRolePageQuery) (mems roles.MembersRolePage, err error) { + prefix := fmt.Sprintf("%s list entity members", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_remove_entity_members", + slog.String("entity_id", entityID), + slog.Uint64("limit", pageQuery.Limit), + slog.Uint64("offset", pageQuery.Offset), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.ListEntityMembers(ctx, session, entityID, pageQuery) +} + +func (lm *RoleManagerLoggingMiddleware) RemoveEntityMembers(ctx context.Context, session authn.Session, entityID string, members []string) (err error) { + prefix := fmt.Sprintf("%s remove entity members", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_remove_entity_members", + slog.String("entity_id", entityID), + slog.Any("member_ids", members), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.RemoveEntityMembers(ctx, session, entityID, members) +} + func (lm *RoleManagerLoggingMiddleware) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) (err error) { prefix := fmt.Sprintf("%s remove members from all roles", lm.svcName) defer func(begin time.Time) { diff --git a/pkg/roles/rolemanager/middleware/meterics.go b/pkg/roles/rolemanager/middleware/meterics.go index 6a1fa43ace..6220ff7587 100644 --- a/pkg/roles/rolemanager/middleware/meterics.go +++ b/pkg/roles/rolemanager/middleware/meterics.go @@ -95,6 +95,14 @@ func (rmm *RoleManagerMetricsMiddleware) RoleRemoveAllMembers(ctx context.Contex return rmm.svc.RoleRemoveAllMembers(ctx, session, entityID, roleID) } +func (rmm *RoleManagerMetricsMiddleware) ListEntityMembers(ctx context.Context, session authn.Session, entityID string, pageQuery roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + return rmm.svc.ListEntityMembers(ctx, session, entityID, pageQuery) +} + +func (rmm *RoleManagerMetricsMiddleware) RemoveEntityMembers(ctx context.Context, session authn.Session, entityID string, members []string) error { + return rmm.svc.RemoveEntityMembers(ctx, session, entityID, members) +} + func (rmm *RoleManagerMetricsMiddleware) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) (err error) { return rmm.svc.RemoveMemberFromAllRoles(ctx, session, memberID) } diff --git a/pkg/roles/rolemanager/tracing/tracing.go b/pkg/roles/rolemanager/tracing/tracing.go index 009ff46486..41885f194d 100644 --- a/pkg/roles/rolemanager/tracing/tracing.go +++ b/pkg/roles/rolemanager/tracing/tracing.go @@ -87,6 +87,14 @@ func (rtm *RoleManagerTracing) RoleRemoveAllMembers(ctx context.Context, session return rtm.roles.RoleRemoveAllMembers(ctx, session, entityID, roleID) } +func (rtm *RoleManagerTracing) ListEntityMembers(ctx context.Context, session authn.Session, entityID string, pageQuery roles.MembersRolePageQuery) (roles.MembersRolePage, error) { + return rtm.roles.ListEntityMembers(ctx, session, entityID, pageQuery) +} + +func (rtm *RoleManagerTracing) RemoveEntityMembers(ctx context.Context, session authn.Session, entityID string, members []string) error { + return rtm.roles.RemoveEntityMembers(ctx, session, entityID, members) +} + func (rtm *RoleManagerTracing) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) (err error) { return rtm.roles.RemoveMemberFromAllRoles(ctx, session, memberID) } diff --git a/pkg/roles/roles.go b/pkg/roles/roles.go index 54cc926111..cf0fb89334 100644 --- a/pkg/roles/roles.go +++ b/pkg/roles/roles.go @@ -63,6 +63,39 @@ type RolePage struct { Roles []Role `json:"roles"` } +type MemberRoleActions struct { + RoleID string `json:"role_id"` + RoleName string `json:"role_name"` + Actions []string `json:"actions,omitempty"` + AccessProviderID string `json:"access_provider_id,omitempty"` + AccessProviderPath string `json:"access_provider_path,omitempty"` + AccessType string `json:"access_type,omitempty"` +} +type MemberRoles struct { + MemberID string `json:"member_id,omitempty"` + Roles []MemberRoleActions `json:"roles,omitempty"` +} + +type MembersRolePage struct { + Total uint64 `json:"total"` + Offset uint64 `json:"offset"` + Limit uint64 `json:"limit"` + Members []MemberRoles `json:"members"` +} + +type MembersRolePageQuery struct { + Total uint64 `json:"total"` + Offset uint64 `json:"offset"` + Limit uint64 `json:"limit"` + Order string `json:"order_by"` + Dir string `json:"dir"` + AccessProviderID string `json:"access_provider_id"` + RoleID string `json:"role_id"` + RoleName string `json:"role_name"` + Actions []string `json:"actions"` + AccessType string `json:"access_type"` +} + type MembersPage struct { Total uint64 `json:"total"` Offset uint64 `json:"offset"` @@ -124,6 +157,10 @@ type RoleManager interface { RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleID string) (err error) + ListEntityMembers(ctx context.Context, session authn.Session, entityID string, pq MembersRolePageQuery) (MembersRolePage, error) + + RemoveEntityMembers(ctx context.Context, session authn.Session, entityID string, members []string) (err error) + RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) (err error) } @@ -146,6 +183,8 @@ type Repository interface { RoleRemoveMembers(ctx context.Context, role Role, members []string) (err error) RoleRemoveAllMembers(ctx context.Context, role Role) (err error) RetrieveEntitiesRolesActionsMembers(ctx context.Context, entityIDs []string) ([]EntityActionRole, []EntityMemberRole, error) + ListEntityMembers(ctx context.Context, entityID string, pageQuery MembersRolePageQuery) (MembersRolePage, error) + RemoveEntityMembers(ctx context.Context, entityID string, members []string) error RemoveMemberFromAllRoles(ctx context.Context, memberID string) (err error) } From 498d0065611267d1354953c2f1bcccc86a865761 Mon Sep 17 00:00:00 2001 From: Arvindh Date: Thu, 30 Jan 2025 19:28:03 +0530 Subject: [PATCH 2/3] remove lints Signed-off-by: Arvindh --- pkg/roles/repo/postgres/roles.go | 4 +--- pkg/roles/rolemanager/events/consumer/handler.go | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/roles/repo/postgres/roles.go b/pkg/roles/repo/postgres/roles.go index 17f3c09173..22b8a5e4af 100644 --- a/pkg/roles/repo/postgres/roles.go +++ b/pkg/roles/repo/postgres/roles.go @@ -859,18 +859,15 @@ func applyConditions(query string, pageQuery roles.MembersRolePageQuery) string if pageQuery.RoleID != "" { whereClause = append(whereClause, " roles @> :role_id ") - } if pageQuery.RoleName != "" { whereClause = append(whereClause, " roles @> :role_name ") - } if len(pageQuery.Actions) != 0 { whereClause = append(whereClause, " roles @> :actions ") } if pageQuery.AccessType != "" { whereClause = append(whereClause, " roles @> :access_type ") - } if pageQuery.AccessProviderID != "" { whereClause = append(whereClause, " roles @> :access_provider_id ") @@ -884,6 +881,7 @@ func applyConditions(query string, pageQuery roles.MembersRolePageQuery) string return fmt.Sprintf(`%s %s`, query, whereCondition) } + func applyOrdering(query string, pageQuery roles.MembersRolePageQuery) string { switch pageQuery.Order { case "access_provider_id", "role_name", "role_id", "access_type": diff --git a/pkg/roles/rolemanager/events/consumer/handler.go b/pkg/roles/rolemanager/events/consumer/handler.go index 8d9907876e..d4e1079f1b 100644 --- a/pkg/roles/rolemanager/events/consumer/handler.go +++ b/pkg/roles/rolemanager/events/consumer/handler.go @@ -248,7 +248,7 @@ func (es *EventHandler) RemoveEntityMembersHandler(ctx context.Context, data map return fmt.Errorf(errRemoveEntityRoleMembersEvent, es.entityType, err) } - // ToDo: added when repo is implemented. + // added when repo is implemented. _ = entityID _ = mems return nil From 8148393ee328e2571a3d15777a6b5a909450ac2a Mon Sep 17 00:00:00 2001 From: Arvindh Date: Thu, 30 Jan 2025 22:14:56 +0530 Subject: [PATCH 3/3] remove listing in users Signed-off-by: Arvindh --- api/http/common.go | 106 ++- api/http/util/errors.go | 2 +- apidocs/openapi/clients.yml | 30 + apidocs/openapi/domains.yml | 29 + apidocs/openapi/groups.yml | 30 + apidocs/openapi/schemas/roles.yml | 84 ++ apidocs/openapi/users.yml | 183 +--- cli/channels.go | 2 +- cli/clients.go | 2 +- cli/clients_test.go | 100 +-- cli/commands_test.go | 11 - cli/domains.go | 2 +- cli/domains_test.go | 85 -- domains/api/http/responses.go | 30 - pkg/sdk/channels.go | 4 + pkg/sdk/clients.go | 4 + pkg/sdk/domains.go | 4 + pkg/sdk/groups.go | 4 + pkg/sdk/mocks/sdk.go | 120 +-- pkg/sdk/responses.go | 19 + pkg/sdk/roles.go | 23 + pkg/sdk/sdk.go | 91 +- pkg/sdk/setup_test.go | 1 - pkg/sdk/users.go | 73 -- pkg/sdk/users_test.go | 666 +-------------- users/api/endpoint_test.go | 1323 ----------------------------- users/api/endpoints.go | 106 --- users/api/requests.go | 101 --- users/api/requests_test.go | 238 +----- users/api/responses.go | 30 - users/api/users.go | 160 ---- users/events/events.go | 58 -- users/events/streams.go | 19 - users/middleware/authorization.go | 67 -- users/middleware/logging.go | 26 - users/middleware/metrics.go | 9 - users/mocks/service.go | 28 - users/service.go | 108 +-- users/service_test.go | 292 ------- users/tracing/tracing.go | 8 - users/users.go | 3 - 41 files changed, 402 insertions(+), 3879 deletions(-) diff --git a/api/http/common.go b/api/http/common.go index 7f3bc69df3..4401daac77 100644 --- a/api/http/common.go +++ b/api/http/common.go @@ -20,63 +20,63 @@ import ( ) const ( - MemberKindKey = "member_kind" - PermissionKey = "permission" - RelationKey = "relation" - StatusKey = "status" - OffsetKey = "offset" - OrderKey = "order" - LimitKey = "limit" - MetadataKey = "metadata" - ParentKey = "parent_id" - OwnerKey = "owner_id" - ClientKey = "client" - UsernameKey = "username" - NameKey = "name" - GroupKey = "group" + OffsetKey = "offset" + DirKey = "dir" + OrderKey = "order" + LimitKey = "limit" + + NameOrder = "name" + IDOrder = "id" + AscDir = "asc" + DescDir = "desc" + + MetadataKey = "metadata" + NameKey = "name" + TagKey = "tag" + StatusKey = "status" + + ClientKey = "client" + ChannelKey = "channel" + ConnTypeKey = "connection_type" + GroupKey = "group" + DomainKey = "domain" + + StartLevelKey = "start_level" + EndLevelKey = "end_level" + TreeKey = "tree" + ParentKey = "parent_id" + LevelKey = "level" + + TokenKey = "token" + SubjectKey = "subject" + ObjectKey = "object" + ActionKey = "action" ActionsKey = "actions" RoleIDKey = "role_id" RoleNameKey = "role_name" AccessProviderIDKey = "access_provider_id" AccessTypeKey = "access_type" - TagKey = "tag" - FirstNameKey = "first_name" - LastNameKey = "last_name" - TotalKey = "total" - SubjectKey = "subject" - ObjectKey = "object" - LevelKey = "level" - StartLevelKey = "start_level" - EndLevelKey = "end_level" - TreeKey = "tree" - DirKey = "dir" - ListPerms = "list_perms" - VisibilityKey = "visibility" - EmailKey = "email" - SharedByKey = "shared_by" - TokenKey = "token" - UserKey = "user" - DomainKey = "domain" - ChannelKey = "channel" - ConnTypeKey = "connection_type" - DefPermission = "read_permission" - DefTotal = uint64(100) - DefOffset = 0 - DefOrder = "updated_at" - DefDir = "asc" - DefLimit = 10 - DefLevel = 0 - DefStartLevel = 1 - DefEndLevel = 0 - DefStatus = "enabled" - DefClientStatus = clients.Enabled - DefUserStatus = users.Enabled - DefGroupStatus = groups.Enabled - DefListPerms = false - SharedVisibility = "shared" - MyVisibility = "mine" - AllVisibility = "all" + + UsernameKey = "username" + UserKey = "user" + EmailKey = "email" + FirstNameKey = "first_name" + LastNameKey = "last_name" + + DefTotal = uint64(100) + DefOffset = 0 + DefOrder = "updated_at" + DefDir = "asc" + DefLimit = 10 + DefLevel = 0 + DefStartLevel = 1 + DefEndLevel = 0 + DefStatus = "enabled" + DefClientStatus = clients.Enabled + DefUserStatus = users.Enabled + DefGroupStatus = groups.Enabled + // ContentType represents JSON content type. ContentType = "application/json" @@ -84,10 +84,6 @@ const ( MaxLimitSize = 100 MaxNameSize = 1024 MaxIDSize = 36 - NameOrder = "name" - IDOrder = "id" - AscDir = "asc" - DescDir = "desc" ) // ValidateUUID validates UUID format. diff --git a/api/http/util/errors.go b/api/http/util/errors.go index ab493fec80..6113c2b8ae 100644 --- a/api/http/util/errors.go +++ b/api/http/util/errors.go @@ -141,7 +141,7 @@ var ( // ErrInvalidComparator indicates an invalid comparator. ErrInvalidComparator = errors.New("invalid comparator") - // ErrMissingMemberIDs indicates missing group member type. + // ErrMissingMemberIDs indicates missing member ids. ErrMissingMemberIDs = errors.New("missing member ids") // ErrMissingMemberType indicates missing group member type. diff --git a/apidocs/openapi/clients.yml b/apidocs/openapi/clients.yml index ffb666bda3..20aca26007 100644 --- a/apidocs/openapi/clients.yml +++ b/apidocs/openapi/clients.yml @@ -520,6 +520,36 @@ paths: "500": $ref: "#/components/responses/ServiceError" + /{domainID}/clients/{clientID}/roles/members: + get: + operationId: getClientMembers + tags: + - Roles + summary: Retrieves client members from all roles. + description: | + Retrieves members from role for the specific client. + parameters: + - $ref: "auth.yml#/components/parameters/DomainID" + - $ref: "#/components/parameters/clientID" + security: + - bearerAuth: [] + responses: + "200": + $ref: "./schemas/roles.yml#/components/responses/ListEntityMembersRes" + "400": + description: Failed due to malformed query parameters. + "401": + description: | + Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. + "404": + description: A non-existent entity request. + "422": + description: Database can't process request. + "500": + $ref: "#/components/responses/ServiceError" + /{domainID}/clients/{clientID}/roles/{roleID}: get: operationId: getClientRole diff --git a/apidocs/openapi/domains.yml b/apidocs/openapi/domains.yml index a683e9b824..02f63f9ac3 100644 --- a/apidocs/openapi/domains.yml +++ b/apidocs/openapi/domains.yml @@ -379,6 +379,35 @@ paths: "500": $ref: "#/components/responses/ServiceError" + /domain/{domainID}/roles/members: + get: + operationId: getDomainMembers + tags: + - Roles + summary: Retrieves domain members from all roles. + description: | + Retrieves members from role for the specific domain. + parameters: + - $ref: "auth.yml#/components/parameters/DomainID" + security: + - bearerAuth: [] + responses: + "200": + $ref: "./schemas/roles.yml#/components/responses/ListEntityMembersRes" + "400": + description: Failed due to malformed query parameters. + "401": + description: | + Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. + "404": + description: A non-existent entity request. + "422": + description: Database can't process request. + "500": + $ref: "#/components/responses/ServiceError" + /domains/{domainID}/roles/{roleID}/actions: post: operationId: addDomainRoleAction diff --git a/apidocs/openapi/groups.yml b/apidocs/openapi/groups.yml index b460047d5b..be03e9bda5 100644 --- a/apidocs/openapi/groups.yml +++ b/apidocs/openapi/groups.yml @@ -564,6 +564,36 @@ paths: "500": $ref: "#/components/responses/ServiceError" + /{domainID}/groups/{groupID}/roles/members: + get: + operationId: getGroupMembers + tags: + - Roles + summary: Retrieves group members from all roles. + description: | + Retrieves members from role for the specific group. + parameters: + - $ref: "auth.yml#/components/parameters/DomainID" + - $ref: "#/components/parameters/GroupID" + security: + - bearerAuth: [] + responses: + "200": + $ref: "./schemas/roles.yml#/components/responses/ListEntityMembersRes" + "400": + description: Failed due to malformed query parameters. + "401": + description: | + Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. + "404": + description: A non-existent entity request. + "422": + description: Database can't process request. + "500": + $ref: "#/components/responses/ServiceError" + /{domainID}/groups/{groupID}/roles/{roleID}: get: operationId: getGroupRole diff --git a/apidocs/openapi/schemas/roles.yml b/apidocs/openapi/schemas/roles.yml index 5b90992928..589833d5e1 100644 --- a/apidocs/openapi/schemas/roles.yml +++ b/apidocs/openapi/schemas/roles.yml @@ -135,6 +135,31 @@ components: example: 10 description: Maximum number of items to return in one page. + EntityMembersPage: + type: object + properties: + groups: + type: array + minItems: 0 + uniqueItems: true + items: + $ref: "#/components/schemas/EntityMembersObj" + total: + type: integer + example: 1 + description: Total number of items. + offset: + type: integer + description: Number of items to skip during retrieval. + limit: + type: integer + example: 10 + description: Maximum number of items to return in one page. + required: + - groups + - total + - offset + RoleActionsObj: type: object properties: @@ -163,6 +188,57 @@ components: "c01ed106-e52d-4aa4-bed3-39f360177cfa", ] + EntityMembersObj: + type: object + properties: + members: + type: array + description: List of members with assigned roles and actions. + items: + type: object + properties: + id: + type: string + format: uuid + description: Unique identifier of the member. + roles: + type: array + description: List of roles assigned to the member. + items: + type: object + properties: + id: + type: string + format: uuid + description: Unique identifier of the role. + name: + type: string + description: Name of the role. + actions: + type: array + description: List of actions the member can perform. + items: + type: string + access_type: + type: string + description: Type of access granted. + enum: [read, write, admin] # Adjust based on your actual access types. + example: + members: + - id: "5dc1ce4b-7cc9-4f12-98a6-9d74cc4980bb" + roles: + - id: "a1b2c3d4-e5f6-7890-1234-56789abcdef0" + name: "Admin" + actions: ["create", "update", "delete"] + access_type: "admin" + - id: "c01ed106-e52d-4aa4-bed3-39f360177cfa" + roles: + - id: "b2c3d4e5-f678-9012-3456-789abcdef012" + name: "Editor" + actions: ["read", "update"] + access_type: "write" + + AvailableActionsObj: type: object properties: @@ -284,3 +360,11 @@ components: application/json: schema: $ref: '#/components/schemas/AvailableActionsObj' + + + ListEntityMembersRes: + description: Entity members retrieved successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/EntityMembersObj' diff --git a/apidocs/openapi/users.yml b/apidocs/openapi/users.yml index e246b96565..738bfe5318 100644 --- a/apidocs/openapi/users.yml +++ b/apidocs/openapi/users.yml @@ -550,155 +550,6 @@ paths: "500": $ref: "#/components/responses/ServiceError" - /{domainID}/groups/{groupID}/users: - get: - operationId: listUsersInGroup - tags: - - Users - summary: List users in a group - description: | - Retrieves a list of users in a group. Due to performance concerns, data - is retrieved in subsets. The API must ensure that the entire - dataset is consumed either by making subsequent requests, or by - increasing the subset size of the initial request. - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/GroupID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Level" - - $ref: "#/components/parameters/Tree" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/GroupName" - - $ref: "#/components/parameters/ParentID" - responses: - "200": - $ref: "#/components/responses/MembersPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: | - Missing or invalid access token provided. - This endpoint is available only for administrators. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/channels/{channelID}/users: - get: - operationId: listUsersInChannel - tags: - - Users - summary: List users in a channel - description: | - Retrieves a list of users in a channel. Due to performance concerns, data - is retrieved in subsets. The API must ensure that the entire - dataset is consumed either by making subsequent requests, or by - increasing the subset size of the initial request. - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ChannelID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Level" - - $ref: "#/components/parameters/Tree" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/ChannelName" - - $ref: "#/components/parameters/ParentID" - responses: - "200": - $ref: "#/components/responses/MembersPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: | - Missing or invalid access token provided. - This endpoint is available only for administrators. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/clients/{clientID}/users: - get: - operationId: listUsersInClient - tags: - - Users - summary: List users associated with a client - description: | - Retrieves a list of users associated with a client. Due to performance concerns, data - is retrieved in subsets. The API must ensure that the entire - dataset is consumed either by making subsequent requests, or by - increasing the subset size of the initial request. - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ClientID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Level" - - $ref: "#/components/parameters/Tree" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/ChannelName" - - $ref: "#/components/parameters/ParentID" - responses: - "200": - $ref: "#/components/responses/MembersPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: | - Missing or invalid access token provided. - This endpoint is available only for administrators. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/users: - get: - summary: List users assigned to domain - description: | - List users assigned to domain that is identified by the domain ID. - tags: - - Users - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/Status" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserPageRes" - description: List of users assigned to domain. - "400": - description: Failed due to malformed domain's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Unauthorized access the domain ID. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /users/tokens/issue: post: operationId: issueToken @@ -963,31 +814,6 @@ components: - total - offset - MembersPage: - type: object - properties: - members: - type: array - minItems: 0 - uniqueItems: true - items: - $ref: "#/components/schemas/Members" - total: - type: integer - example: 1 - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - example: 10 - description: Maximum number of items to return in one page. - required: - - members - - total - - level - UserUpdate: type: object properties: @@ -1310,7 +1136,7 @@ components: pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" required: true example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - + ClientID: name: clientID description: Unique client identifier. @@ -1629,13 +1455,6 @@ components: schema: $ref: "#/components/schemas/UsersPage" - MembersPageRes: - description: Group members retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/MembersPage" - TokenRes: description: JSON-formated document describing the user access token used for authenticating into the syetem and refresh token used for generating another access token content: diff --git a/cli/channels.go b/cli/channels.go index e65053d5e0..68c1aead5a 100644 --- a/cli/channels.go +++ b/cli/channels.go @@ -177,7 +177,7 @@ var cmdChannels = []cobra.Command{ Offset: Offset, Limit: Limit, } - ul, err := sdk.ListChannelUsers(args[0], args[1], pm, args[2]) + ul, err := sdk.ListChannelMembers(args[0], args[1], pm, args[2]) if err != nil { logErrorCmd(*cmd, err) return diff --git a/cli/clients.go b/cli/clients.go index c6f4fff2b0..1c72f66b7b 100644 --- a/cli/clients.go +++ b/cli/clients.go @@ -275,7 +275,7 @@ var cmdClients = []cobra.Command{ Offset: Offset, Limit: Limit, } - ul, err := sdk.ListClientUsers(args[0], args[1], pm, args[2]) + ul, err := sdk.ListClientMembers(args[0], args[1], pm, args[2]) if err != nil { logErrorCmd(*cmd, err) return diff --git a/cli/clients_test.go b/cli/clients_test.go index 40ecc6ba9a..8c38aa124f 100644 --- a/cli/clients_test.go +++ b/cli/clients_test.go @@ -23,12 +23,11 @@ import ( ) var ( - token = "valid" + "domaintoken" - domainID = "domain-id" - tokenWithoutDomain = "valid" - relation = "administrator" - all = "all" - conntype = `["publish","subscribe"]` + token = "valid" + "domaintoken" + domainID = "domain-id" + relation = "administrator" + all = "all" + conntype = `["publish","subscribe"]` ) var client = smqsdk.Client{ @@ -711,95 +710,6 @@ func TestDisableclientCmd(t *testing.T) { } } -func TestUsersClientCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - clientsCmd := cli.NewClientsCmd() - rootCmd := setFlags(clientsCmd) - - page := smqsdk.UsersPage{} - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - page smqsdk.UsersPage - sdkErr errors.SDKError - }{ - { - desc: "get client's users successfully", - args: []string{ - client.ID, - domainID, - token, - }, - page: smqsdk.UsersPage{ - PageRes: smqsdk.PageRes{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Users: []smqsdk.User{user}, - }, - logType: entityLog, - }, - { - desc: "list client users' with invalid args", - args: []string{ - client.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "list client users' with invalid domain", - args: []string{ - client.ID, - invalidID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "list client users with invalid id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ListClientUsers", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.page, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{usrCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &page) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - func TestConnectClientCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) diff --git a/cli/commands_test.go b/cli/commands_test.go index 46f2153124..3f40ce1947 100644 --- a/cli/commands_test.go +++ b/cli/commands_test.go @@ -37,17 +37,6 @@ const ( unshrCmd = "unshare" ) -// Groups and channels commands -const ( - chansCmd = "channels" - grpCmd = "groups" - childCmd = "children" - parentCmd = "parents" - usrCmd = "users" - assignCmd = "assign" - unassignCmd = "unassign" -) - // Certs commands const ( revokeCmd = "revoke" diff --git a/cli/domains.go b/cli/domains.go index 4ce4918ccd..a59fe97bfe 100644 --- a/cli/domains.go +++ b/cli/domains.go @@ -96,7 +96,7 @@ var cmdDomains = []cobra.Command{ Status: Status, } - l, err := sdk.ListDomainUsers(args[0], pageMetadata, args[1]) + l, err := sdk.ListDomainMembers(args[0], pageMetadata, args[1]) if err != nil { logErrorCmd(*cmd, err) return diff --git a/cli/domains_test.go b/cli/domains_test.go index 0b205e8bb0..323a53f869 100644 --- a/cli/domains_test.go +++ b/cli/domains_test.go @@ -197,91 +197,6 @@ func TestGetDomainsCmd(t *testing.T) { } } -func TestListDomainUsers(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - domainsCmd := cli.NewDomainsCmd() - rootCmd := setFlags(domainsCmd) - - page := smqsdk.UsersPage{} - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - page smqsdk.UsersPage - sdkErr errors.SDKError - }{ - { - desc: "list domain users successfully", - args: []string{ - domain.ID, - token, - }, - page: smqsdk.UsersPage{ - PageRes: smqsdk.PageRes{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Users: []smqsdk.User{user}, - }, - logType: entityLog, - }, - { - desc: "list domain users with invalid args", - args: []string{ - domain.ID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "list domain users without domain token", - args: []string{ - domain.ID, - tokenWithoutDomain, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "list domain users with invalid id", - args: []string{ - invalidID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ListDomainUsers", tc.args[0], mock.Anything, tc.args[1]).Return(tc.page, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{usrCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &page) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - func TestUpdateDomainCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) diff --git a/domains/api/http/responses.go b/domains/api/http/responses.go index e55616ea0b..ae8e076363 100644 --- a/domains/api/http/responses.go +++ b/domains/api/http/responses.go @@ -13,8 +13,6 @@ import ( var ( _ supermq.Response = (*createDomainRes)(nil) _ supermq.Response = (*retrieveDomainRes)(nil) - _ supermq.Response = (*assignUsersRes)(nil) - _ supermq.Response = (*unassignUsersRes)(nil) _ supermq.Response = (*listDomainsRes)(nil) ) @@ -123,31 +121,3 @@ func (res freezeDomainRes) Headers() map[string]string { func (res freezeDomainRes) Empty() bool { return true } - -type assignUsersRes struct{} - -func (res assignUsersRes) Code() int { - return http.StatusCreated -} - -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 -} diff --git a/pkg/sdk/channels.go b/pkg/sdk/channels.go index d8ae98ab42..91b1e62a95 100644 --- a/pkg/sdk/channels.go +++ b/pkg/sdk/channels.go @@ -276,3 +276,7 @@ func (sdk mgSDK) RemoveChannelParent(id, domainID, groupID, token string) errors return sdkerr } + +func (sdk mgSDK) ListChannelMembers(channelID, domainID string, pm PageMetadata, token string) (EntityMembersPage, errors.SDKError) { + return sdk.listEntityMembers(sdk.channelsURL, domainID, channelsEndpoint, channelID, token, pm) +} diff --git a/pkg/sdk/clients.go b/pkg/sdk/clients.go index c3f4c971a8..a4cc2367b9 100644 --- a/pkg/sdk/clients.go +++ b/pkg/sdk/clients.go @@ -307,3 +307,7 @@ func (sdk mgSDK) RemoveAllClientRoleMembers(id, roleID, domainID, token string) func (sdk mgSDK) AvailableClientRoleActions(domainID, token string) ([]string, errors.SDKError) { return sdk.listAvailableRoleActions(sdk.clientsURL, clientsEndpoint, domainID, token) } + +func (sdk mgSDK) ListClientMembers(clientID, domainID string, pm PageMetadata, token string) (EntityMembersPage, errors.SDKError) { + return sdk.listEntityMembers(sdk.clientsURL, domainID, clientsEndpoint, clientID, token, pm) +} diff --git a/pkg/sdk/domains.go b/pkg/sdk/domains.go index 0e1e965a0e..4534fcd0e1 100644 --- a/pkg/sdk/domains.go +++ b/pkg/sdk/domains.go @@ -188,3 +188,7 @@ func (sdk mgSDK) RemoveAllDomainRoleMembers(id, roleID, token string) errors.SDK func (sdk mgSDK) AvailableDomainRoleActions(token string) ([]string, errors.SDKError) { return sdk.listAvailableRoleActions(sdk.domainsURL, domainsEndpoint, "", token) } + +func (sdk mgSDK) ListDomainMembers(domainID string, pm PageMetadata, token string) (EntityMembersPage, errors.SDKError) { + return sdk.listEntityMembers(sdk.domainsURL, domainID, domainsEndpoint, domainID, token, pm) +} diff --git a/pkg/sdk/groups.go b/pkg/sdk/groups.go index 7dc9d30269..549eec3ba6 100644 --- a/pkg/sdk/groups.go +++ b/pkg/sdk/groups.go @@ -318,3 +318,7 @@ func (sdk mgSDK) RemoveAllGroupRoleMembers(id, roleID, domainID, token string) e func (sdk mgSDK) AvailableGroupRoleActions(domainID, token string) ([]string, errors.SDKError) { return sdk.listAvailableRoleActions(sdk.groupsURL, groupsEndpoint, domainID, token) } + +func (sdk mgSDK) ListGroupMembers(groupID, domainID string, pm PageMetadata, token string) (EntityMembersPage, errors.SDKError) { + return sdk.listEntityMembers(sdk.groupsURL, domainID, groupsEndpoint, groupID, token, pm) +} diff --git a/pkg/sdk/mocks/sdk.go b/pkg/sdk/mocks/sdk.go index e4229814fd..60b33a401e 100644 --- a/pkg/sdk/mocks/sdk.go +++ b/pkg/sdk/mocks/sdk.go @@ -4211,23 +4211,23 @@ func (_c *SDK_Journal_Call) RunAndReturn(run func(string, string, string, sdk.Pa return _c } -// ListChannelUsers provides a mock function with given fields: channelID, domainID, pm, token -func (_m *SDK) ListChannelUsers(channelID string, domainID string, pm sdk.PageMetadata, token string) (sdk.UsersPage, errors.SDKError) { +// ListChannelMembers provides a mock function with given fields: channelID, domainID, pm, token +func (_m *SDK) ListChannelMembers(channelID string, domainID string, pm sdk.PageMetadata, token string) (sdk.EntityMembersPage, errors.SDKError) { ret := _m.Called(channelID, domainID, pm, token) if len(ret) == 0 { - panic("no return value specified for ListChannelUsers") + panic("no return value specified for ListChannelMembers") } - var r0 sdk.UsersPage + var r0 sdk.EntityMembersPage var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) (sdk.UsersPage, errors.SDKError)); ok { + if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) (sdk.EntityMembersPage, errors.SDKError)); ok { return rf(channelID, domainID, pm, token) } - if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) sdk.UsersPage); ok { + if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) sdk.EntityMembersPage); ok { r0 = rf(channelID, domainID, pm, token) } else { - r0 = ret.Get(0).(sdk.UsersPage) + r0 = ret.Get(0).(sdk.EntityMembersPage) } if rf, ok := ret.Get(1).(func(string, string, sdk.PageMetadata, string) errors.SDKError); ok { @@ -4241,54 +4241,54 @@ func (_m *SDK) ListChannelUsers(channelID string, domainID string, pm sdk.PageMe return r0, r1 } -// SDK_ListChannelUsers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListChannelUsers' -type SDK_ListChannelUsers_Call struct { +// SDK_ListChannelMembers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListChannelMembers' +type SDK_ListChannelMembers_Call struct { *mock.Call } -// ListChannelUsers is a helper method to define mock.On call +// ListChannelMembers is a helper method to define mock.On call // - channelID string // - domainID string // - pm sdk.PageMetadata // - token string -func (_e *SDK_Expecter) ListChannelUsers(channelID interface{}, domainID interface{}, pm interface{}, token interface{}) *SDK_ListChannelUsers_Call { - return &SDK_ListChannelUsers_Call{Call: _e.mock.On("ListChannelUsers", channelID, domainID, pm, token)} +func (_e *SDK_Expecter) ListChannelMembers(channelID interface{}, domainID interface{}, pm interface{}, token interface{}) *SDK_ListChannelMembers_Call { + return &SDK_ListChannelMembers_Call{Call: _e.mock.On("ListChannelMembers", channelID, domainID, pm, token)} } -func (_c *SDK_ListChannelUsers_Call) Run(run func(channelID string, domainID string, pm sdk.PageMetadata, token string)) *SDK_ListChannelUsers_Call { +func (_c *SDK_ListChannelMembers_Call) Run(run func(channelID string, domainID string, pm sdk.PageMetadata, token string)) *SDK_ListChannelMembers_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(string), args[1].(string), args[2].(sdk.PageMetadata), args[3].(string)) }) return _c } -func (_c *SDK_ListChannelUsers_Call) Return(_a0 sdk.UsersPage, _a1 errors.SDKError) *SDK_ListChannelUsers_Call { +func (_c *SDK_ListChannelMembers_Call) Return(_a0 sdk.EntityMembersPage, _a1 errors.SDKError) *SDK_ListChannelMembers_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *SDK_ListChannelUsers_Call) RunAndReturn(run func(string, string, sdk.PageMetadata, string) (sdk.UsersPage, errors.SDKError)) *SDK_ListChannelUsers_Call { +func (_c *SDK_ListChannelMembers_Call) RunAndReturn(run func(string, string, sdk.PageMetadata, string) (sdk.EntityMembersPage, errors.SDKError)) *SDK_ListChannelMembers_Call { _c.Call.Return(run) return _c } -// ListClientUsers provides a mock function with given fields: clientID, domainID, pm, token -func (_m *SDK) ListClientUsers(clientID string, domainID string, pm sdk.PageMetadata, token string) (sdk.UsersPage, errors.SDKError) { +// ListClientMembers provides a mock function with given fields: clientID, domainID, pm, token +func (_m *SDK) ListClientMembers(clientID string, domainID string, pm sdk.PageMetadata, token string) (sdk.EntityMembersPage, errors.SDKError) { ret := _m.Called(clientID, domainID, pm, token) if len(ret) == 0 { - panic("no return value specified for ListClientUsers") + panic("no return value specified for ListClientMembers") } - var r0 sdk.UsersPage + var r0 sdk.EntityMembersPage var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) (sdk.UsersPage, errors.SDKError)); ok { + if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) (sdk.EntityMembersPage, errors.SDKError)); ok { return rf(clientID, domainID, pm, token) } - if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) sdk.UsersPage); ok { + if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) sdk.EntityMembersPage); ok { r0 = rf(clientID, domainID, pm, token) } else { - r0 = ret.Get(0).(sdk.UsersPage) + r0 = ret.Get(0).(sdk.EntityMembersPage) } if rf, ok := ret.Get(1).(func(string, string, sdk.PageMetadata, string) errors.SDKError); ok { @@ -4302,54 +4302,54 @@ func (_m *SDK) ListClientUsers(clientID string, domainID string, pm sdk.PageMeta return r0, r1 } -// SDK_ListClientUsers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListClientUsers' -type SDK_ListClientUsers_Call struct { +// SDK_ListClientMembers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListClientMembers' +type SDK_ListClientMembers_Call struct { *mock.Call } -// ListClientUsers is a helper method to define mock.On call +// ListClientMembers is a helper method to define mock.On call // - clientID string // - domainID string // - pm sdk.PageMetadata // - token string -func (_e *SDK_Expecter) ListClientUsers(clientID interface{}, domainID interface{}, pm interface{}, token interface{}) *SDK_ListClientUsers_Call { - return &SDK_ListClientUsers_Call{Call: _e.mock.On("ListClientUsers", clientID, domainID, pm, token)} +func (_e *SDK_Expecter) ListClientMembers(clientID interface{}, domainID interface{}, pm interface{}, token interface{}) *SDK_ListClientMembers_Call { + return &SDK_ListClientMembers_Call{Call: _e.mock.On("ListClientMembers", clientID, domainID, pm, token)} } -func (_c *SDK_ListClientUsers_Call) Run(run func(clientID string, domainID string, pm sdk.PageMetadata, token string)) *SDK_ListClientUsers_Call { +func (_c *SDK_ListClientMembers_Call) Run(run func(clientID string, domainID string, pm sdk.PageMetadata, token string)) *SDK_ListClientMembers_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(string), args[1].(string), args[2].(sdk.PageMetadata), args[3].(string)) }) return _c } -func (_c *SDK_ListClientUsers_Call) Return(_a0 sdk.UsersPage, _a1 errors.SDKError) *SDK_ListClientUsers_Call { +func (_c *SDK_ListClientMembers_Call) Return(_a0 sdk.EntityMembersPage, _a1 errors.SDKError) *SDK_ListClientMembers_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *SDK_ListClientUsers_Call) RunAndReturn(run func(string, string, sdk.PageMetadata, string) (sdk.UsersPage, errors.SDKError)) *SDK_ListClientUsers_Call { +func (_c *SDK_ListClientMembers_Call) RunAndReturn(run func(string, string, sdk.PageMetadata, string) (sdk.EntityMembersPage, errors.SDKError)) *SDK_ListClientMembers_Call { _c.Call.Return(run) return _c } -// ListDomainUsers provides a mock function with given fields: domainID, pm, token -func (_m *SDK) ListDomainUsers(domainID string, pm sdk.PageMetadata, token string) (sdk.UsersPage, errors.SDKError) { +// ListDomainMembers provides a mock function with given fields: domainID, pm, token +func (_m *SDK) ListDomainMembers(domainID string, pm sdk.PageMetadata, token string) (sdk.EntityMembersPage, errors.SDKError) { ret := _m.Called(domainID, pm, token) if len(ret) == 0 { - panic("no return value specified for ListDomainUsers") + panic("no return value specified for ListDomainMembers") } - var r0 sdk.UsersPage + var r0 sdk.EntityMembersPage var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) (sdk.UsersPage, errors.SDKError)); ok { + if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) (sdk.EntityMembersPage, errors.SDKError)); ok { return rf(domainID, pm, token) } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) sdk.UsersPage); ok { + if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) sdk.EntityMembersPage); ok { r0 = rf(domainID, pm, token) } else { - r0 = ret.Get(0).(sdk.UsersPage) + r0 = ret.Get(0).(sdk.EntityMembersPage) } if rf, ok := ret.Get(1).(func(string, sdk.PageMetadata, string) errors.SDKError); ok { @@ -4363,53 +4363,53 @@ func (_m *SDK) ListDomainUsers(domainID string, pm sdk.PageMetadata, token strin return r0, r1 } -// SDK_ListDomainUsers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListDomainUsers' -type SDK_ListDomainUsers_Call struct { +// SDK_ListDomainMembers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListDomainMembers' +type SDK_ListDomainMembers_Call struct { *mock.Call } -// ListDomainUsers is a helper method to define mock.On call +// ListDomainMembers is a helper method to define mock.On call // - domainID string // - pm sdk.PageMetadata // - token string -func (_e *SDK_Expecter) ListDomainUsers(domainID interface{}, pm interface{}, token interface{}) *SDK_ListDomainUsers_Call { - return &SDK_ListDomainUsers_Call{Call: _e.mock.On("ListDomainUsers", domainID, pm, token)} +func (_e *SDK_Expecter) ListDomainMembers(domainID interface{}, pm interface{}, token interface{}) *SDK_ListDomainMembers_Call { + return &SDK_ListDomainMembers_Call{Call: _e.mock.On("ListDomainMembers", domainID, pm, token)} } -func (_c *SDK_ListDomainUsers_Call) Run(run func(domainID string, pm sdk.PageMetadata, token string)) *SDK_ListDomainUsers_Call { +func (_c *SDK_ListDomainMembers_Call) Run(run func(domainID string, pm sdk.PageMetadata, token string)) *SDK_ListDomainMembers_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(string), args[1].(sdk.PageMetadata), args[2].(string)) }) return _c } -func (_c *SDK_ListDomainUsers_Call) Return(_a0 sdk.UsersPage, _a1 errors.SDKError) *SDK_ListDomainUsers_Call { +func (_c *SDK_ListDomainMembers_Call) Return(_a0 sdk.EntityMembersPage, _a1 errors.SDKError) *SDK_ListDomainMembers_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *SDK_ListDomainUsers_Call) RunAndReturn(run func(string, sdk.PageMetadata, string) (sdk.UsersPage, errors.SDKError)) *SDK_ListDomainUsers_Call { +func (_c *SDK_ListDomainMembers_Call) RunAndReturn(run func(string, sdk.PageMetadata, string) (sdk.EntityMembersPage, errors.SDKError)) *SDK_ListDomainMembers_Call { _c.Call.Return(run) return _c } -// Members provides a mock function with given fields: groupID, domainID, pm, token -func (_m *SDK) Members(groupID string, domainID string, pm sdk.PageMetadata, token string) (sdk.UsersPage, errors.SDKError) { +// ListGroupMembers provides a mock function with given fields: groupID, domainID, pm, token +func (_m *SDK) ListGroupMembers(groupID string, domainID string, pm sdk.PageMetadata, token string) (sdk.EntityMembersPage, errors.SDKError) { ret := _m.Called(groupID, domainID, pm, token) if len(ret) == 0 { - panic("no return value specified for Members") + panic("no return value specified for ListGroupMembers") } - var r0 sdk.UsersPage + var r0 sdk.EntityMembersPage var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) (sdk.UsersPage, errors.SDKError)); ok { + if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) (sdk.EntityMembersPage, errors.SDKError)); ok { return rf(groupID, domainID, pm, token) } - if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) sdk.UsersPage); ok { + if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) sdk.EntityMembersPage); ok { r0 = rf(groupID, domainID, pm, token) } else { - r0 = ret.Get(0).(sdk.UsersPage) + r0 = ret.Get(0).(sdk.EntityMembersPage) } if rf, ok := ret.Get(1).(func(string, string, sdk.PageMetadata, string) errors.SDKError); ok { @@ -4423,33 +4423,33 @@ func (_m *SDK) Members(groupID string, domainID string, pm sdk.PageMetadata, tok return r0, r1 } -// SDK_Members_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Members' -type SDK_Members_Call struct { +// SDK_ListGroupMembers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListGroupMembers' +type SDK_ListGroupMembers_Call struct { *mock.Call } -// Members is a helper method to define mock.On call +// ListGroupMembers is a helper method to define mock.On call // - groupID string // - domainID string // - pm sdk.PageMetadata // - token string -func (_e *SDK_Expecter) Members(groupID interface{}, domainID interface{}, pm interface{}, token interface{}) *SDK_Members_Call { - return &SDK_Members_Call{Call: _e.mock.On("Members", groupID, domainID, pm, token)} +func (_e *SDK_Expecter) ListGroupMembers(groupID interface{}, domainID interface{}, pm interface{}, token interface{}) *SDK_ListGroupMembers_Call { + return &SDK_ListGroupMembers_Call{Call: _e.mock.On("ListGroupMembers", groupID, domainID, pm, token)} } -func (_c *SDK_Members_Call) Run(run func(groupID string, domainID string, pm sdk.PageMetadata, token string)) *SDK_Members_Call { +func (_c *SDK_ListGroupMembers_Call) Run(run func(groupID string, domainID string, pm sdk.PageMetadata, token string)) *SDK_ListGroupMembers_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(string), args[1].(string), args[2].(sdk.PageMetadata), args[3].(string)) }) return _c } -func (_c *SDK_Members_Call) Return(_a0 sdk.UsersPage, _a1 errors.SDKError) *SDK_Members_Call { +func (_c *SDK_ListGroupMembers_Call) Return(_a0 sdk.EntityMembersPage, _a1 errors.SDKError) *SDK_ListGroupMembers_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *SDK_Members_Call) RunAndReturn(run func(string, string, sdk.PageMetadata, string) (sdk.UsersPage, errors.SDKError)) *SDK_Members_Call { +func (_c *SDK_ListGroupMembers_Call) RunAndReturn(run func(string, string, sdk.PageMetadata, string) (sdk.EntityMembersPage, errors.SDKError)) *SDK_ListGroupMembers_Call { _c.Call.Return(run) return _c } diff --git a/pkg/sdk/responses.go b/pkg/sdk/responses.go index 6b2185a897..21e768721f 100644 --- a/pkg/sdk/responses.go +++ b/pkg/sdk/responses.go @@ -101,3 +101,22 @@ type RoleMembersPage struct { Limit uint64 `json:"limit"` Members []string `json:"members"` } + +type MemberRole struct { + Actions []string `json:"actions,omitempty"` + RoleName string `json:"role_name,omitempty"` + RoleID string `json:"role_id,omitempty"` + AccessType string `json:"access_type,omitempty"` + AccessProviderID string `json:"access_provider_id,omitempty"` + AccessProviderPath string `json:"access_provider_path,omitempty"` +} +type MemberRoles struct { + MemberID string `json:"member_id"` + Roles []MemberRole `json:"roles"` +} +type EntityMembersPage struct { + Total uint64 `json:"total"` + Offset uint64 `json:"offset"` + Limit uint64 `json:"limit"` + Members []MemberRoles `json:"members"` +} diff --git a/pkg/sdk/roles.go b/pkg/sdk/roles.go index fd87d9178b..7c56df9f5e 100644 --- a/pkg/sdk/roles.go +++ b/pkg/sdk/roles.go @@ -266,3 +266,26 @@ func (sdk mgSDK) listAvailableRoleActions(entityURL, entityEndpoint, domainID, t return res.AvailableActions, nil } + +func (sdk mgSDK) listEntityMembers(entityURL, domainID, entityEndpoint, id, token string, pm PageMetadata) (EntityMembersPage, errors.SDKError) { + ep := fmt.Sprintf("%s/%s/%s/%s/%s", domainID, entityEndpoint, id, rolesEndpoint, membersEndpoint) + if entityEndpoint == domainsEndpoint { + ep = fmt.Sprintf("%s/%s/%s/%s", entityEndpoint, id, rolesEndpoint, membersEndpoint) + } + url, err := sdk.withQueryParams(entityURL, ep, pm) + if err != nil { + return EntityMembersPage{}, errors.NewSDKError(err) + } + + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return EntityMembersPage{}, sdkerr + } + + res := EntityMembersPage{} + if err := json.Unmarshal(body, &res); err != nil { + return EntityMembersPage{}, errors.NewSDKError(err) + } + + return res, nil +} diff --git a/pkg/sdk/sdk.go b/pkg/sdk/sdk.go index f16dd92385..9f257c1c73 100644 --- a/pkg/sdk/sdk.go +++ b/pkg/sdk/sdk.go @@ -196,17 +196,6 @@ type SDK interface { // fmt.Println(users) Users(pm PageMetadata, token string) (UsersPage, errors.SDKError) - // Members returns list of users that are members of a group. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // } - // members, _ := sdk.Members("groupID","domainID", pm, "token") - // fmt.Println(members) - Members(groupID, domainID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) - // UserProfile returns user logged in. // // example: @@ -359,42 +348,6 @@ type SDK interface { // fmt.Println(users) SearchUsers(pm PageMetadata, token string) (UsersPage, errors.SDKError) - // ListClientUsers all users in a client. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Permission: "edit", // available Options: "administrator", "administrator", "delete", edit", "view", "share", "owner", "owner", "admin", "editor", "contributor", "editor", "viewer", "guest", "create" - // } - // users, _ := sdk.ListClientUsers("client_id", pm, "domainID", "token") - // fmt.Println(users) - ListClientUsers(clientID, domainID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) - - // ListChannelUsers list all users in a channel . - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Permission: "edit", // available Options: "administrator", "administrator", "delete", edit", "view", "share", "owner", "owner", "admin", "editor", "contributor", "editor", "viewer", "guest", "create" - // } - // users, _ := sdk.ListChannelUsers("channel_id","domainID", pm, "token") - // fmt.Println(users) - ListChannelUsers(channelID, domainID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) - - // ListDomainUsers returns list of users for the given domain ID and filters. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Permission : "view" - // } - // users, _ := sdk.ListDomainUsers("domainID", pm, "token") - // fmt.Println(users) - ListDomainUsers(domainID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) - // CreateClient registers new client and returns its id. // // example: @@ -630,6 +583,17 @@ type SDK interface { // fmt.Println(actions) AvailableClientRoleActions(domainID, token string) ([]string, errors.SDKError) + // ListClientMembers list all members from all roles in a client . + // + // example: + // pm := sdk.PageMetadata{ + // Offset: 0, + // Limit: 10, + // } + // members, _ := sdk.ListClientMembers("client_id","domainID", pm, "token") + // fmt.Println(members) + ListClientMembers(clientID, domainID string, pm PageMetadata, token string) (EntityMembersPage, errors.SDKError) + // CreateGroup creates new group and returns its id. // // example: @@ -872,6 +836,17 @@ type SDK interface { // fmt.Println(actions) AvailableGroupRoleActions(id, token string) ([]string, errors.SDKError) + // ListGroupMembers list all members from all roles in a group . + // + // example: + // pm := sdk.PageMetadata{ + // Offset: 0, + // Limit: 10, + // } + // members, _ := sdk.ListGroupMembers("group_id","domainID", pm, "token") + // fmt.Println(members) + ListGroupMembers(groupID, domainID string, pm PageMetadata, token string) (EntityMembersPage, errors.SDKError) + // CreateChannel creates new channel and returns its id. // // example: @@ -1025,6 +1000,17 @@ type SDK interface { // fmt.Println(err) DisconnectClients(channelID string, clientIDs, connTypes []string, domainID, token string) errors.SDKError + // ListChannelMembers list all members from all roles in a channel . + // + // example: + // pm := sdk.PageMetadata{ + // Offset: 0, + // Limit: 10, + // } + // members, _ := sdk.ListChannelMembers("channel_id","domainID", pm, "token") + // fmt.Println(members) + ListChannelMembers(channelID, domainID string, pm PageMetadata, token string) (EntityMembersPage, errors.SDKError) + // SendMessage send message to specified channel. // // example: @@ -1258,6 +1244,17 @@ type SDK interface { // fmt.Println(actions) AvailableDomainRoleActions(token string) ([]string, errors.SDKError) + // ListDomainUsers returns list of users for the given domain ID and filters. + // + // example: + // pm := sdk.PageMetadata{ + // Offset: 0, + // Limit: 10, + // } + // members, _ := sdk.ListDomainMembers("domain_id", pm, "token") + // fmt.Println(members) + ListDomainMembers(domainID string, pm PageMetadata, token string) (EntityMembersPage, errors.SDKError) + // SendInvitation sends an invitation to the email address associated with the given user. // // For example: diff --git a/pkg/sdk/setup_test.go b/pkg/sdk/setup_test.go index 4d3ba83a4c..df71d50069 100644 --- a/pkg/sdk/setup_test.go +++ b/pkg/sdk/setup_test.go @@ -33,7 +33,6 @@ const ( contentType = "application/senml+json" invalid = "invalid" wrongID = "wrongID" - defPermission = "read_permission" roleName = "roleName" ) diff --git a/pkg/sdk/users.go b/pkg/sdk/users.go index d5b6f6f133..eb9982dc6a 100644 --- a/pkg/sdk/users.go +++ b/pkg/sdk/users.go @@ -15,8 +15,6 @@ import ( const ( usersEndpoint = "users" - assignEndpoint = "assign" - unassignEndpoint = "unassign" enableEndpoint = "enable" disableEndpoint = "disable" issueTokenEndpoint = "tokens/issue" @@ -81,25 +79,6 @@ func (sdk mgSDK) Users(pm PageMetadata, token string) (UsersPage, errors.SDKErro return cp, nil } -func (sdk mgSDK) Members(groupID, domainID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s/%s", domainID, groupsEndpoint, groupID, usersEndpoint), pm) - if err != nil { - return UsersPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return UsersPage{}, sdkerr - } - - var up UsersPage - if err := json.Unmarshal(body, &up); err != nil { - return UsersPage{}, errors.NewSDKError(err) - } - - return up, nil -} - func (sdk mgSDK) User(id, token string) (User, errors.SDKError) { if id == "" { return User{}, errors.NewSDKError(apiutil.ErrMissingID) @@ -372,55 +351,3 @@ func (sdk mgSDK) DeleteUser(id, token string) errors.SDKError { _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) return sdkerr } - -func (sdk mgSDK) ListClientUsers(clientID, domainID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s/%s", domainID, clientsEndpoint, clientID, usersEndpoint), pm) - if err != nil { - return UsersPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return UsersPage{}, sdkerr - } - up := UsersPage{} - if err := json.Unmarshal(body, &up); err != nil { - return UsersPage{}, errors.NewSDKError(err) - } - - return up, nil -} - -func (sdk mgSDK) ListDomainUsers(domainID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s", domainID, usersEndpoint), pm) - if err != nil { - return UsersPage{}, errors.NewSDKError(err) - } - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return UsersPage{}, sdkerr - } - var up UsersPage - if err := json.Unmarshal(body, &up); err != nil { - return UsersPage{}, errors.NewSDKError(err) - } - - return up, nil -} - -func (sdk mgSDK) ListChannelUsers(channelID, domainID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s/%s", domainID, channelsEndpoint, channelID, usersEndpoint), pm) - if err != nil { - return UsersPage{}, errors.NewSDKError(err) - } - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return UsersPage{}, sdkerr - } - up := UsersPage{} - if err := json.Unmarshal(body, &up); err != nil { - return UsersPage{}, errors.NewSDKError(err) - } - - return up, nil -} diff --git a/pkg/sdk/users_test.go b/pkg/sdk/users_test.go index f0eecb2d70..4bb6267728 100644 --- a/pkg/sdk/users_test.go +++ b/pkg/sdk/users_test.go @@ -31,9 +31,8 @@ import ( ) var ( - id = generateUUID(&testing.T{}) - domainID = "c717fa97-ffd9-40cb-8cf9-7c2859059395" - membershipPermission = "membership" + id = generateUUID(&testing.T{}) + domainID = "c717fa97-ffd9-40cb-8cf9-7c2859059395" ) func setupUsers() (*httptest.Server, *umocks.Service, *authnmocks.Authentication) { @@ -2314,190 +2313,6 @@ func TestDisableUser(t *testing.T) { } } -func TestListMembers(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - member := generateTestUser(t) - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - session smqauthn.Session - groupID string - pageMeta sdk.PageMetadata - svcReq users.Page - svcRes users.MembersPage - svcErr error - authenticateErr error - response sdk.UsersPage - err errors.SDKError - }{ - { - desc: "list members successfully", - token: validToken, - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: defPermission, - }, - svcRes: users.MembersPage{ - Page: users.Page{ - Total: 1, - }, - Members: []users.User{convertUser(member)}, - }, - svcErr: nil, - response: sdk.UsersPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Users: []sdk.User{member}, - }, - }, - { - desc: "list members with invalid token", - token: invalidToken, - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: defPermission, - }, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list members with empty token", - token: "", - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{}, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list members with invalid group id", - token: validToken, - groupID: wrongID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: defPermission, - }, - svcErr: svcerr.ErrViewEntity, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), - }, - { - desc: "list members with empty group id", - token: validToken, - groupID: "", - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{}, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "list members with page metadata that can't be marshalled", - token: validToken, - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - svcReq: users.Page{}, - svcRes: users.MembersPage{}, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list members with response that can't be unmarshalled", - token: validToken, - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: defPermission, - }, - svcRes: users.MembersPage{ - Page: users.Page{ - Total: 1, - }, - Members: []users.User{{ - ID: member.ID, - FirstName: member.FirstName, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - }}, - }, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("ListMembers", mock.Anything, tc.session, "groups", tc.groupID, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Members(tc.groupID, domainID, tc.pageMeta, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ListMembers", mock.Anything, tc.session, "groups", tc.groupID, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - func TestDeleteUser(t *testing.T) { ts, svc, auth := setupUsers() defer ts.Close() @@ -2570,480 +2385,3 @@ func TestDeleteUser(t *testing.T) { }) } } - -func TestListClientUsers(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - clientUser := generateTestUser(t) - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - session smqauthn.Session - clientID string - pageMeta sdk.PageMetadata - svcReq users.Page - svcRes users.MembersPage - svcErr error - authenticateErr error - response sdk.UsersPage - err errors.SDKError - }{ - { - desc: "list client users successfully", - token: validToken, - clientID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: defPermission, - }, - svcRes: users.MembersPage{ - Page: users.Page{ - Total: 1, - }, - Members: []users.User{convertUser(clientUser)}, - }, - svcErr: nil, - response: sdk.UsersPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Users: []sdk.User{clientUser}, - }, - }, - { - desc: "list client users with invalid token", - token: invalidToken, - clientID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: defPermission, - }, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list client users with empty token", - token: "", - clientID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{}, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list client users with invalid client id", - token: validToken, - clientID: wrongID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: defPermission, - }, - svcErr: svcerr.ErrViewEntity, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), - }, - { - desc: "list clients users with request that cannot be marshalled", - token: validToken, - clientID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list clients users with response that cannot be unmarshalled", - token: validToken, - clientID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: defPermission, - }, - svcRes: users.MembersPage{ - Page: users.Page{ - Total: 1, - }, - Members: []users.User{{ - ID: clientUser.ID, - FirstName: clientUser.FirstName, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - }}, - }, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("ListMembers", mock.Anything, tc.session, "clients", tc.clientID, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ListClientUsers(tc.clientID, domainID, tc.pageMeta, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ListMembers", mock.Anything, tc.session, "clients", tc.clientID, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListGroupUsers(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - groupUser := generateTestUser(t) - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - session smqauthn.Session - groupID string - pageMeta sdk.PageMetadata - svcReq users.Page - svcRes users.MembersPage - svcErr error - authenticateErr error - response sdk.UsersPage - err errors.SDKError - }{ - { - desc: "list client users successfully", - token: validToken, - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: defPermission, - }, - svcRes: users.MembersPage{ - Page: users.Page{ - Total: 1, - }, - Members: []users.User{convertUser(groupUser)}, - }, - svcErr: nil, - response: sdk.UsersPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Users: []sdk.User{groupUser}, - }, - }, - { - desc: "list client users with invalid token", - token: invalidToken, - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: defPermission, - }, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list client users with empty token", - token: "", - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{}, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list client users with invalid client id", - token: validToken, - groupID: wrongID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: defPermission, - }, - svcErr: svcerr.ErrViewEntity, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), - }, - { - desc: "list clients users with request that cannot be marshalled", - token: validToken, - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list clients users with response that cannot be unmarshalled", - token: validToken, - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: defPermission, - }, - svcRes: users.MembersPage{ - Page: users.Page{ - Total: 1, - }, - Members: []users.User{{ - ID: groupUser.ID, - FirstName: groupUser.FirstName, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - }}, - }, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("ListMembers", mock.Anything, tc.session, "groups", tc.groupID, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ListChannelUsers(tc.groupID, domainID, tc.pageMeta, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ListMembers", mock.Anything, tc.session, "groups", tc.groupID, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListDomainUser(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - user := generateTestUser(t) - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - session smqauthn.Session - pageMeta sdk.PageMetadata - svcReq users.Page - svcRes users.MembersPage - svcErr error - authenticateErr error - response sdk.UsersPage - err errors.SDKError - }{ - { - desc: "list domain users successfully", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: membershipPermission, - }, - svcRes: users.MembersPage{ - Page: users.Page{ - Total: 1, - }, - Members: []users.User{convertUser(user)}, - }, - svcErr: nil, - response: sdk.UsersPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Users: []sdk.User{user}, - }, - }, - { - desc: "list domain users with invalid token", - token: invalidToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: membershipPermission, - }, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list domain users with empty token", - token: "", - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{}, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list domain users with request that cannot be marshalled", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list domain users with response that cannot be unmarshalled", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: membershipPermission, - }, - svcRes: users.MembersPage{ - Page: users.Page{ - Total: 1, - }, - Members: []users.User{{ - ID: user.ID, - FirstName: user.FirstName, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - }}, - }, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("ListMembers", mock.Anything, tc.session, "domains", domainID, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ListDomainUsers(domainID, tc.pageMeta, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ListMembers", mock.Anything, tc.session, "domains", domainID, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} diff --git a/users/api/endpoint_test.go b/users/api/endpoint_test.go index fc7a48b517..8410d94711 100644 --- a/users/api/endpoint_test.go +++ b/users/api/endpoint_test.go @@ -2565,1329 +2565,6 @@ func TestDelete(t *testing.T) { } } -func TestListUsersByUserGroupId(t *testing.T) { - us, svc, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - token string - groupID string - domainID string - page users.Page - status int - query string - listUsersResponse users.UsersPage - authnRes smqauthn.Session - authnErr error - err error - }{ - { - desc: "list users with valid token", - token: validToken, - groupID: validID, - domainID: validID, - status: http.StatusOK, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with empty id", - token: validToken, - groupID: "", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrMissingID, - }, - { - desc: "list users with empty token", - token: "", - groupID: validID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "list users with invalid token", - token: inValidToken, - groupID: validID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "list users with offset", - token: validToken, - groupID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Offset: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "offset=1", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid offset", - token: validToken, - groupID: validID, - query: "offset=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit", - token: validToken, - groupID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Limit: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "limit=1", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid limit", - token: validToken, - groupID: validID, - query: "limit=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit greater than max", - token: validToken, - groupID: validID, - query: fmt.Sprintf("limit=%d", api.MaxLimitSize+1), - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with user name", - token: validToken, - groupID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "username=username", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid user name", - token: validToken, - groupID: validID, - query: "username=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate user name", - token: validToken, - groupID: validID, - query: "username=1&username=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with status", - token: validToken, - groupID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "status=enabled", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid status", - token: validToken, - groupID: validID, - query: "status=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate status", - token: validToken, - groupID: validID, - query: "status=enabled&status=disabled", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with tags", - token: validToken, - groupID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "tag=tag1,tag2", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid tags", - token: validToken, - groupID: validID, - query: "tag=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate tags", - token: validToken, - groupID: validID, - query: "tag=tag1&tag=tag2", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with metadata", - token: validToken, - groupID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid metadata", - token: validToken, - groupID: validID, - query: "metadata=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate metadata", - token: validToken, - groupID: validID, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with permissions", - token: validToken, - groupID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "permission=view", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with duplicate permissions", - token: validToken, - groupID: validID, - query: "permission=view&permission=view", - status: http.StatusBadRequest, - listUsersResponse: users.UsersPage{}, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with email", - token: validToken, - groupID: validID, - query: fmt.Sprintf("email=%s", user.Email), - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{ - user, - }, - }, - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid email", - token: validToken, - groupID: validID, - query: "email=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate email", - token: validToken, - groupID: validID, - query: "email=1&email=2", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/%s/groups/%s/users?", us.URL, validID, tc.groupID) + tc.query, - token: tc.token, - } - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("ListMembers", mock.Anything, smqauthn.Session{UserID: validID, DomainID: validID, DomainUserID: validID + "_" + validID}, mock.Anything, mock.Anything, mock.Anything).Return( - users.MembersPage{ - Page: tc.listUsersResponse.Page, - Members: tc.listUsersResponse.Users, - }, - tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authnCall.Unset() - }) - } -} - -func TestListUsersByChannelID(t *testing.T) { - us, svc, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - token string - channelID string - page users.Page - status int - query string - listUsersResponse users.UsersPage - authnRes smqauthn.Session - authnErr error - err error - }{ - { - desc: "list users with valid token", - token: validToken, - status: http.StatusOK, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with empty token", - token: "", - channelID: validID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "list users with invalid token", - token: inValidToken, - channelID: validID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "list users with offset", - token: validToken, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Offset: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "offset=1", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid offset", - token: validToken, - channelID: validID, - query: "offset=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit", - token: validToken, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Limit: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "limit=1", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid limit", - token: validToken, - channelID: validID, - query: "limit=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit greater than max", - token: validToken, - channelID: validID, - query: fmt.Sprintf("limit=%d", api.MaxLimitSize+1), - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with user name", - token: validToken, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "username=username", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid user name", - token: validToken, - channelID: validID, - query: "username=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate user name", - token: validToken, - query: "username=1&username=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with status", - token: validToken, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "status=enabled", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid status", - token: validToken, - channelID: validID, - query: "status=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate status", - token: validToken, - query: "status=enabled&status=disabled", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with tags", - token: validToken, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "tag=tag1,tag2", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid tags", - token: validToken, - channelID: validID, - query: "tag=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate tags", - token: validToken, - channelID: validID, - query: "tag=tag1&tag=tag2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with metadata", - token: validToken, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid metadata", - token: validToken, - channelID: validID, - query: "metadata=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate metadata", - token: validToken, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with permissions", - token: validToken, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "permission=view", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with duplicate permissions", - token: validToken, - channelID: validID, - query: "permission=view&permission=view", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with email", - token: validToken, - channelID: validID, - query: fmt.Sprintf("email=%s", user.Email), - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{ - user, - }, - }, - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid email", - token: validToken, - channelID: validID, - query: "email=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate email", - token: validToken, - channelID: validID, - query: "email=1&email=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with list_perms", - token: validToken, - channelID: validID, - query: "list_perms=true", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid list_perms", - token: validToken, - channelID: validID, - query: "list_perms=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate list_perms", - token: validToken, - query: "list_perms=true&list_perms=false", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/%s/channels/%s/users?", us.URL, validID, validID) + tc.query, - token: tc.token, - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("ListMembers", mock.Anything, smqauthn.Session{UserID: validID, DomainID: validID, DomainUserID: validID + "_" + validID}, mock.Anything, mock.Anything, mock.Anything).Return( - users.MembersPage{ - Page: tc.listUsersResponse.Page, - Members: tc.listUsersResponse.Users, - }, - tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authnCall.Unset() - }) - } -} - -func TestListUsersByDomainID(t *testing.T) { - us, svc, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - token string - domainID string - page users.Page - status int - query string - listUsersResponse users.UsersPage - authnRes smqauthn.Session - authnErr error - err error - }{ - { - desc: "list users with valid token", - token: validToken, - domainID: validID, - status: http.StatusOK, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with empty token", - token: "", - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "list users with invalid token", - token: inValidToken, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "list users with offset", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Offset: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "offset=1", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid offset", - token: validToken, - domainID: validID, - query: "offset=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Limit: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "limit=1", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid limit", - token: validToken, - domainID: validID, - query: "limit=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit greater than max", - token: validToken, - domainID: validID, - query: fmt.Sprintf("limit=%d", api.MaxLimitSize+1), - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with user name", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "username=username", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid user name", - token: validToken, - domainID: validID, - query: "username=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate user name", - token: validToken, - domainID: validID, - query: "username=1&username=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with status", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "status=enabled", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid status", - token: validToken, - domainID: validID, - query: "status=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate status", - token: validToken, - query: "status=enabled&status=disabled", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with tags", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "tag=tag1,tag2", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid tags", - token: validToken, - domainID: validID, - query: "tag=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate tags", - token: validToken, - query: "tag=tag1&tag=tag2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with metadata", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid metadata", - token: validToken, - domainID: validID, - query: "metadata=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate metadata", - token: validToken, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with permissions", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "permission=membership", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with duplicate permissions", - token: validToken, - domainID: validID, - query: "permission=view&permission=view", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with email", - token: validToken, - domainID: validID, - query: fmt.Sprintf("email=%s", user.Email), - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{ - user, - }, - }, - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid email", - token: validToken, - domainID: validID, - query: "email=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate email", - token: validToken, - query: "email=1&email=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users wiith list permissions", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{ - user, - }, - }, - query: "list_perms=true", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid list_perms", - token: validToken, - domainID: validID, - query: "list_perms=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate list_perms", - token: validToken, - query: "list_perms=true&list_perms=false", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/%s/users?", us.URL, validID) + tc.query, - token: tc.token, - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("ListMembers", mock.Anything, smqauthn.Session{UserID: validID, DomainID: validID, DomainUserID: validID + "_" + validID}, mock.Anything, mock.Anything, mock.Anything).Return( - users.MembersPage{ - Page: tc.listUsersResponse.Page, - Members: tc.listUsersResponse.Users, - }, - tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authnCall.Unset() - }) - } -} - -func TestListUsersByClientID(t *testing.T) { - us, svc, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - token string - clientID string - page users.Page - status int - query string - listUsersResponse users.UsersPage - authnRes smqauthn.Session - authnErr error - err error - }{ - { - desc: "list users with valid token", - token: validToken, - clientID: validID, - status: http.StatusOK, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with empty token", - token: "", - clientID: validID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "list users with invalid token", - token: inValidToken, - clientID: validID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "list users with offset", - token: validToken, - clientID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Offset: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "offset=1", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid offset", - token: validToken, - clientID: validID, - query: "offset=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit", - token: validToken, - clientID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Limit: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "limit=1", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid limit", - token: validToken, - clientID: validID, - query: "limit=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit greater than max", - token: validToken, - clientID: validID, - query: fmt.Sprintf("limit=%d", api.MaxLimitSize+1), - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with name", - token: validToken, - clientID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "name=username", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid user name", - token: validToken, - clientID: validID, - query: "username=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate user name", - token: validToken, - clientID: validID, - query: "username=1&username=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with status", - token: validToken, - clientID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "status=enabled", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid status", - token: validToken, - clientID: validID, - query: "status=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate status", - token: validToken, - query: "status=enabled&status=disabled", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with tags", - token: validToken, - clientID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "tag=tag1,tag2", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid tags", - token: validToken, - clientID: validID, - query: "tag=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate tags", - token: validToken, - query: "tag=tag1&tag=tag2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with metadata", - token: validToken, - clientID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid metadata", - token: validToken, - clientID: validID, - query: "metadata=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate metadata", - token: validToken, - clientID: validID, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with permissions", - token: validToken, - clientID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "permission=view", - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with duplicate permissions", - token: validToken, - query: "permission=view&permission=view", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with email", - token: validToken, - clientID: validID, - query: fmt.Sprintf("email=%s", user.Email), - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{ - user, - }, - }, - status: http.StatusOK, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid email", - token: validToken, - clientID: validID, - query: "email=invalid", - status: http.StatusBadRequest, - authnRes: smqauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate email", - token: validToken, - query: "email=1&email=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/%s/clients/%s/users?", us.URL, validID, validID) + tc.query, - token: tc.token, - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("ListMembers", mock.Anything, smqauthn.Session{UserID: validID, DomainID: validID, DomainUserID: validID + "_" + validID}, mock.Anything, mock.Anything, mock.Anything).Return( - users.MembersPage{ - Page: tc.listUsersResponse.Page, - Members: tc.listUsersResponse.Users, - }, - tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authnCall.Unset() - }) - } -} - type respBody struct { Err string `json:"error"` Message string `json:"message"` diff --git a/users/api/endpoints.go b/users/api/endpoints.go index 3641dddb87..6729b91eac 100644 --- a/users/api/endpoints.go +++ b/users/api/endpoints.go @@ -164,95 +164,6 @@ func searchUsersEndpoint(svc users.Service) endpoint.Endpoint { } } -func listMembersByGroupEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersByObjectReq) - req.objectKind = "groups" - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthentication - } - - page, err := svc.ListMembers(ctx, session, req.objectKind, req.objectID, req.Page) - if err != nil { - return nil, err - } - - return buildUsersResponse(page), nil - } -} - -func listMembersByChannelEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersByObjectReq) - // In spiceDB schema, using the same 'group' type for both channels and groups, rather than having a separate type for channels. - req.objectKind = "groups" - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthentication - } - - page, err := svc.ListMembers(ctx, session, req.objectKind, req.objectID, req.Page) - if err != nil { - return nil, err - } - - return buildUsersResponse(page), nil - } -} - -func listMembersByClientEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersByObjectReq) - req.objectKind = "clients" - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthentication - } - - page, err := svc.ListMembers(ctx, session, req.objectKind, req.objectID, req.Page) - if err != nil { - return nil, err - } - - return buildUsersResponse(page), nil - } -} - -func listMembersByDomainEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersByObjectReq) - req.objectKind = "domains" - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthentication - } - - page, err := svc.ListMembers(ctx, session, req.objectKind, req.objectID, req.Page) - if err != nil { - return nil, err - } - - return buildUsersResponse(page), nil - } -} - func updateEndpoint(svc users.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(updateUserReq) @@ -574,20 +485,3 @@ func deleteEndpoint(svc users.Service) endpoint.Endpoint { return deleteUserRes{true}, nil } } - -func buildUsersResponse(cp users.MembersPage) usersPageRes { - res := usersPageRes{ - pageRes: pageRes{ - Total: cp.Total, - Offset: cp.Offset, - Limit: cp.Limit, - }, - Users: []viewUserRes{}, - } - - for _, user := range cp.Members { - res.Users = append(res.Users, viewUserRes{User: user}) - } - - return res -} diff --git a/users/api/requests.go b/users/api/requests.go index db71551ea8..b180f08407 100644 --- a/users/api/requests.go +++ b/users/api/requests.go @@ -121,23 +121,6 @@ func (req searchUsersReq) validate() error { return nil } -type listMembersByObjectReq struct { - users.Page - objectKind string - objectID string -} - -func (req listMembersByObjectReq) validate() error { - if req.objectID == "" { - return apiutil.ErrMissingID - } - if req.objectKind == "" { - return apiutil.ErrMissingMemberKind - } - - return nil -} - type updateUserReq struct { id string FirstName string `json:"first_name,omitempty"` @@ -327,87 +310,3 @@ func (req resetTokenReq) validate() error { return nil } - -type assignUsersReq struct { - groupID string - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` -} - -func (req assignUsersReq) validate() error { - if req.Relation == "" { - return apiutil.ErrMissingRelation - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.UserIDs) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} - -type unassignUsersReq struct { - groupID string - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` -} - -func (req unassignUsersReq) validate() error { - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.UserIDs) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} - -type assignGroupsReq struct { - groupID string - domainID string - GroupIDs []string `json:"group_ids"` -} - -func (req assignGroupsReq) validate() error { - if req.domainID == "" { - return apiutil.ErrMissingDomainID - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.GroupIDs) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} - -type unassignGroupsReq struct { - groupID string - domainID string - GroupIDs []string `json:"group_ids"` -} - -func (req unassignGroupsReq) validate() error { - if req.domainID == "" { - return apiutil.ErrMissingDomainID - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.GroupIDs) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} diff --git a/users/api/requests_test.go b/users/api/requests_test.go index a437373c8f..22cac99862 100644 --- a/users/api/requests_test.go +++ b/users/api/requests_test.go @@ -22,10 +22,7 @@ const ( name = "user" ) -var ( - validID = testsutil.GenerateUUID(&testing.T{}) - domain = testsutil.GenerateUUID(&testing.T{}) -) +var validID = testsutil.GenerateUUID(&testing.T{}) func TestCreateUserReqValidate(t *testing.T) { cases := []struct { @@ -207,43 +204,6 @@ func TestSearchUsersReqValidate(t *testing.T) { } } -func TestListMembersByObjectReqValidate(t *testing.T) { - cases := []struct { - desc string - req listMembersByObjectReq - err error - }{ - { - desc: "valid request", - req: listMembersByObjectReq{ - objectKind: "group", - objectID: validID, - }, - err: nil, - }, - { - desc: "empty object kind", - req: listMembersByObjectReq{ - objectKind: "", - objectID: validID, - }, - err: apiutil.ErrMissingMemberKind, - }, - { - desc: "empty object id", - req: listMembersByObjectReq{ - objectKind: "group", - objectID: "", - }, - err: apiutil.ErrMissingID, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err) - } -} - func TestUpdateUserReqValidate(t *testing.T) { cases := []struct { desc string @@ -660,199 +620,3 @@ func TestResetTokenReqValidate(t *testing.T) { assert.Equal(t, c.err, err) } } - -func TestAssignUsersRequestValidate(t *testing.T) { - cases := []struct { - desc string - req assignUsersReq - err error - }{ - { - desc: "valid request", - req: assignUsersReq{ - groupID: validID, - UserIDs: []string{validID}, - Relation: valid, - }, - err: nil, - }, - { - desc: "empty id", - req: assignUsersReq{ - groupID: "", - UserIDs: []string{validID}, - Relation: valid, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty users", - req: assignUsersReq{ - groupID: validID, - UserIDs: []string{}, - Relation: valid, - }, - err: apiutil.ErrEmptyList, - }, - { - desc: "empty relation", - req: assignUsersReq{ - groupID: validID, - UserIDs: []string{validID}, - Relation: "", - }, - err: apiutil.ErrMissingRelation, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestUnassignUsersRequestValidate(t *testing.T) { - cases := []struct { - desc string - req unassignUsersReq - err error - }{ - { - desc: "valid request", - req: unassignUsersReq{ - groupID: validID, - UserIDs: []string{validID}, - Relation: valid, - }, - err: nil, - }, - { - desc: "empty id", - req: unassignUsersReq{ - groupID: "", - UserIDs: []string{validID}, - Relation: valid, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty users", - req: unassignUsersReq{ - groupID: validID, - UserIDs: []string{}, - Relation: valid, - }, - err: apiutil.ErrEmptyList, - }, - { - desc: "empty relation", - req: unassignUsersReq{ - groupID: validID, - UserIDs: []string{validID}, - Relation: "", - }, - err: nil, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestAssignGroupsRequestValidate(t *testing.T) { - cases := []struct { - desc string - req assignGroupsReq - err error - }{ - { - desc: "valid request", - req: assignGroupsReq{ - domainID: domain, - groupID: validID, - GroupIDs: []string{validID}, - }, - err: nil, - }, - { - desc: "empty group id", - req: assignGroupsReq{ - domainID: domain, - groupID: "", - GroupIDs: []string{validID}, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty user group ids", - req: assignGroupsReq{ - domainID: domain, - groupID: validID, - GroupIDs: []string{}, - }, - err: apiutil.ErrEmptyList, - }, - { - desc: "empty domain id", - req: assignGroupsReq{ - domainID: "", - groupID: validID, - GroupIDs: []string{validID}, - }, - err: apiutil.ErrMissingDomainID, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestUnassignGroupsRequestValidate(t *testing.T) { - cases := []struct { - desc string - req unassignGroupsReq - err error - }{ - { - desc: "valid request", - req: unassignGroupsReq{ - domainID: domain, - groupID: validID, - GroupIDs: []string{validID}, - }, - err: nil, - }, - { - desc: "empty group id", - req: unassignGroupsReq{ - domainID: domain, - groupID: "", - GroupIDs: []string{validID}, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty user group ids", - req: unassignGroupsReq{ - domainID: domain, - groupID: validID, - GroupIDs: []string{}, - }, - err: apiutil.ErrEmptyList, - }, - { - desc: "empty domain id", - req: unassignGroupsReq{ - domainID: "", - groupID: validID, - GroupIDs: []string{valid}, - }, - err: apiutil.ErrMissingDomainID, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} diff --git a/users/api/responses.go b/users/api/responses.go index e964836959..41aaa8e042 100644 --- a/users/api/responses.go +++ b/users/api/responses.go @@ -23,8 +23,6 @@ var ( _ supermq.Response = (*viewMembersRes)(nil) _ supermq.Response = (*passwResetReqRes)(nil) _ supermq.Response = (*passwChangeRes)(nil) - _ supermq.Response = (*assignUsersRes)(nil) - _ supermq.Response = (*unassignUsersRes)(nil) _ supermq.Response = (*updateUserRes)(nil) _ supermq.Response = (*tokenRes)(nil) _ supermq.Response = (*deleteUserRes)(nil) @@ -192,34 +190,6 @@ func (res passwChangeRes) Empty() bool { return false } -type assignUsersRes struct{} - -func (res assignUsersRes) Code() int { - return http.StatusCreated -} - -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 deleteUserRes struct { deleted bool } diff --git a/users/api/users.go b/users/api/users.go index 1e107d2b28..6c195b984c 100644 --- a/users/api/users.go +++ b/users/api/users.go @@ -18,7 +18,6 @@ import ( smqauthn "github.com/absmach/supermq/pkg/authn" "github.com/absmach/supermq/pkg/errors" "github.com/absmach/supermq/pkg/oauth2" - "github.com/absmach/supermq/pkg/policies" "github.com/absmach/supermq/users" "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" @@ -173,48 +172,6 @@ func usersHandler(svc users.Service, authn smqauthn.Authentication, tokenClient ), "password_reset").ServeHTTP) }) - r.Group(func(r chi.Router) { - r.Use(api.AuthenticateMiddleware(authn, true)) - - // Ideal location: users service, groups endpoint. - // Reason for placing here : - // SpiceDB provides list of user ids in given user_group_id - // 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("/{domainID}/groups/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( - listMembersByGroupEndpoint(svc), - decodeListMembersByGroup, - api.EncodeResponse, - opts..., - ), "list_users_by_user_group_id").ServeHTTP) - - // Ideal location: clients service, channels endpoint. - // 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 {channelID} - r.Get("/{domainID}/channels/{channelID}/users", otelhttp.NewHandler(kithttp.NewServer( - listMembersByChannelEndpoint(svc), - decodeListMembersByChannel, - api.EncodeResponse, - opts..., - ), "list_users_by_channel_id").ServeHTTP) - - r.Get("/{domainID}/clients/{clientID}/users", otelhttp.NewHandler(kithttp.NewServer( - listMembersByClientEndpoint(svc), - decodeListMembersByClient, - api.EncodeResponse, - opts..., - ), "list_users_by_client_id").ServeHTTP) - - r.Get("/{domainID}/users", otelhttp.NewHandler(kithttp.NewServer( - listMembersByDomainEndpoint(svc), - decodeListMembersByDomain, - api.EncodeResponse, - opts..., - ), "list_users_by_domain_id").ServeHTTP) - }) - r.Post("/users/tokens/issue", otelhttp.NewHandler(kithttp.NewServer( issueTokenEndpoint(svc), decodeCredentials, @@ -550,123 +507,6 @@ func decodeChangeUserStatus(_ context.Context, r *http.Request) (interface{}, er return req, nil } -func decodeListMembersByGroup(_ context.Context, r *http.Request) (interface{}, error) { - page, err := queryPageParams(r, api.DefPermission) - if err != nil { - return nil, err - } - req := listMembersByObjectReq{ - Page: page, - objectID: chi.URLParam(r, "groupID"), - } - - return req, nil -} - -func decodeListMembersByChannel(_ context.Context, r *http.Request) (interface{}, error) { - page, err := queryPageParams(r, api.DefPermission) - if err != nil { - return nil, err - } - req := listMembersByObjectReq{ - Page: page, - objectID: chi.URLParam(r, "channelID"), - } - - return req, nil -} - -func decodeListMembersByClient(_ context.Context, r *http.Request) (interface{}, error) { - page, err := queryPageParams(r, api.DefPermission) - if err != nil { - return nil, err - } - req := listMembersByObjectReq{ - Page: page, - objectID: chi.URLParam(r, "clientID"), - } - - return req, nil -} - -func decodeListMembersByDomain(_ context.Context, r *http.Request) (interface{}, error) { - page, err := queryPageParams(r, policies.MembershipPermission) - if err != nil { - return nil, err - } - - req := listMembersByObjectReq{ - Page: page, - objectID: chi.URLParam(r, "domainID"), - } - - return req, nil -} - -func queryPageParams(r *http.Request, defPermission string) (users.Page, error) { - s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - l, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - m, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil) - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - n, err := apiutil.ReadStringQuery(r, api.UsernameKey, "") - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - f, err := apiutil.ReadStringQuery(r, api.FirstNameKey, "") - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - a, err := apiutil.ReadStringQuery(r, api.LastNameKey, "") - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - i, err := apiutil.ReadStringQuery(r, api.EmailKey, "") - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - t, err := apiutil.ReadStringQuery(r, api.TagKey, "") - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - st, err := users.ToStatus(s) - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - p, err := apiutil.ReadStringQuery(r, api.PermissionKey, defPermission) - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - lp, err := apiutil.ReadBoolQuery(r, api.ListPerms, api.DefListPerms) - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - return users.Page{ - Status: st, - Offset: o, - Limit: l, - Metadata: m, - FirstName: f, - Username: n, - LastName: a, - Email: i, - Tag: t, - Permission: p, - ListPerms: lp, - }, nil -} - // oauth2CallbackHandler is a http.HandlerFunc that handles OAuth2 callbacks. func oauth2CallbackHandler(oauth oauth2.Provider, svc users.Service, tokenClient grpcTokenV1.TokenServiceClient) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { diff --git a/users/events/events.go b/users/events/events.go index d9f781333e..2d9296ca01 100644 --- a/users/events/events.go +++ b/users/events/events.go @@ -43,7 +43,6 @@ var ( _ events.Event = (*viewUserEvent)(nil) _ events.Event = (*viewProfileEvent)(nil) _ events.Event = (*listUserEvent)(nil) - _ events.Event = (*listUserByGroupEvent)(nil) _ events.Event = (*searchUserEvent)(nil) _ events.Event = (*identifyUserEvent)(nil) _ events.Event = (*generateResetTokenEvent)(nil) @@ -359,63 +358,6 @@ func (lue listUserEvent) Encode() (map[string]interface{}, error) { return val, nil } -type listUserByGroupEvent struct { - users.Page - objectKind string - objectID string - authn.Session -} - -func (lcge listUserByGroupEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": userListByGroup, - "total": lcge.Total, - "offset": lcge.Offset, - "limit": lcge.Limit, - "object_kind": lcge.objectKind, - "object_id": lcge.objectID, - "domain": lcge.DomainID, - "token_type": lcge.Type.String(), - "super_admin": lcge.SuperAdmin, - } - - if lcge.Username != "" { - val["username"] = lcge.Username - } - if lcge.Order != "" { - val["order"] = lcge.Order - } - if lcge.Dir != "" { - val["dir"] = lcge.Dir - } - if lcge.Metadata != nil { - val["metadata"] = lcge.Metadata - } - if lcge.Domain != "" { - val["domain"] = lcge.Domain - } - if lcge.Tag != "" { - val["tag"] = lcge.Tag - } - if lcge.Permission != "" { - val["permission"] = lcge.Permission - } - if lcge.Status.String() != "" { - val["status"] = lcge.Status.String() - } - if lcge.FirstName != "" { - val["first_name"] = lcge.FirstName - } - if lcge.LastName != "" { - val["last_name"] = lcge.LastName - } - if lcge.Email != "" { - val["email"] = lcge.Email - } - - return val, nil -} - type searchUserEvent struct { users.Page } diff --git a/users/events/streams.go b/users/events/streams.go index 49833bc23c..a59dd40df2 100644 --- a/users/events/streams.go +++ b/users/events/streams.go @@ -216,25 +216,6 @@ func (es *eventStore) SearchUsers(ctx context.Context, pm users.Page) (users.Use return cp, nil } -func (es *eventStore) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm users.Page) (users.MembersPage, error) { - mp, err := es.svc.ListMembers(ctx, session, objectKind, objectID, pm) - if err != nil { - return mp, err - } - event := listUserByGroupEvent{ - Page: pm, - objectKind: objectKind, - objectID: objectID, - Session: session, - } - - if err := es.Publish(ctx, event); err != nil { - return mp, err - } - - return mp, nil -} - func (es *eventStore) Enable(ctx context.Context, session authn.Session, id string) (users.User, error) { user, err := es.svc.Enable(ctx, session, id) if err != nil { diff --git a/users/middleware/authorization.go b/users/middleware/authorization.go index a1dd1d6dd2..db84ad71bc 100644 --- a/users/middleware/authorization.go +++ b/users/middleware/authorization.go @@ -100,73 +100,6 @@ func (am *authorizationMiddleware) ListUsers(ctx context.Context, session authn. return am.svc.ListUsers(ctx, session, pm) } -func (am *authorizationMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm users.Page) (users.MembersPage, error) { - if session.Type == authn.PersonalAccessToken { - switch objectKind { - case policies.GroupsKind: - if err := am.authz.AuthorizePAT(ctx, smqauthz.PatReq{ - UserID: session.UserID, - PatID: session.PatID, - OptionalDomainID: session.DomainID, - PlatformEntityType: smqauth.PlatformUsersScope, - OptionalDomainEntityType: smqauth.DomainGroupsScope, - Operation: smqauth.ListOp, - EntityIDs: smqauth.AnyIDs{}.Values(), - }); err != nil { - return users.MembersPage{}, errors.Wrap(svcerr.ErrUnauthorizedPAT, err) - } - case policies.DomainsKind: - if err := am.authz.AuthorizePAT(ctx, smqauthz.PatReq{ - UserID: session.UserID, - PatID: session.PatID, - OptionalDomainID: session.DomainID, - PlatformEntityType: smqauth.PlatformUsersScope, - OptionalDomainEntityType: smqauth.DomainManagementScope, - Operation: smqauth.ListOp, - EntityIDs: smqauth.AnyIDs{}.Values(), - }); err != nil { - return users.MembersPage{}, errors.Wrap(svcerr.ErrUnauthorizedPAT, err) - } - case policies.ClientsKind: - if err := am.authz.AuthorizePAT(ctx, smqauthz.PatReq{ - UserID: session.UserID, - PatID: session.PatID, - OptionalDomainID: session.DomainID, - PlatformEntityType: smqauth.PlatformUsersScope, - OptionalDomainEntityType: smqauth.DomainClientsScope, - Operation: smqauth.ListOp, - EntityIDs: smqauth.AnyIDs{}.Values(), - }); err != nil { - return users.MembersPage{}, errors.Wrap(svcerr.ErrUnauthorizedPAT, err) - } - default: - return users.MembersPage{}, svcerr.ErrAuthorization - } - } - - if session.DomainUserID == "" { - return users.MembersPage{}, svcerr.ErrDomainAuthorization - } - switch objectKind { - case policies.GroupsKind: - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, smqauth.SwitchToPermission(pm.Permission), policies.GroupType, objectID); err != nil { - return users.MembersPage{}, err - } - case policies.DomainsKind: - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, smqauth.SwitchToPermission(pm.Permission), policies.DomainType, objectID); err != nil { - return users.MembersPage{}, err - } - case policies.ClientsKind: - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.UserID, smqauth.SwitchToPermission(pm.Permission), policies.ClientType, objectID); err != nil { - return users.MembersPage{}, err - } - default: - return users.MembersPage{}, svcerr.ErrAuthorization - } - - return am.svc.ListMembers(ctx, session, objectKind, objectID, pm) -} - func (am *authorizationMiddleware) SearchUsers(ctx context.Context, pm users.Page) (users.UsersPage, error) { return am.svc.SearchUsers(ctx, pm) } diff --git a/users/middleware/logging.go b/users/middleware/logging.go index 5319944e05..3f3ea4eb77 100644 --- a/users/middleware/logging.go +++ b/users/middleware/logging.go @@ -414,32 +414,6 @@ func (lm *loggingMiddleware) Disable(ctx context.Context, session authn.Session, return lm.svc.Disable(ctx, session, id) } -// ListMembers logs the list_members request. It logs the group id, and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, cp users.Page) (mp users.MembersPage, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("object", - slog.String("kind", objectKind), - slog.String("id", objectID), - ), - slog.Group("page", - slog.Uint64("limit", cp.Limit), - slog.Uint64("offset", cp.Offset), - slog.Uint64("total", mp.Total), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("List members failed", args...) - return - } - lm.logger.Info("List members completed successfully", args...) - }(time.Now()) - return lm.svc.ListMembers(ctx, session, objectKind, objectID, cp) -} - // Identify logs the identify request. It logs the time it took to complete the request. func (lm *loggingMiddleware) Identify(ctx context.Context, session authn.Session) (id string, err error) { defer func(begin time.Time) { diff --git a/users/middleware/metrics.go b/users/middleware/metrics.go index 6fe1f999d7..d7380382ce 100644 --- a/users/middleware/metrics.go +++ b/users/middleware/metrics.go @@ -201,15 +201,6 @@ func (ms *metricsMiddleware) Disable(ctx context.Context, session authn.Session, return ms.svc.Disable(ctx, session, id) } -// ListMembers instruments ListMembers method with metrics. -func (ms *metricsMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm users.Page) (mp users.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, session, objectKind, objectID, pm) -} - // Identify instruments Identify method with metrics. func (ms *metricsMiddleware) Identify(ctx context.Context, session authn.Session) (string, error) { defer func(begin time.Time) { diff --git a/users/mocks/service.go b/users/mocks/service.go index a7a2fd6f94..49d070c2ee 100644 --- a/users/mocks/service.go +++ b/users/mocks/service.go @@ -171,34 +171,6 @@ func (_m *Service) IssueToken(ctx context.Context, identity string, secret strin return r0, r1 } -// ListMembers provides a mock function with given fields: ctx, session, objectKind, objectID, pm -func (_m *Service) ListMembers(ctx context.Context, session authn.Session, objectKind string, objectID string, pm users.Page) (users.MembersPage, error) { - ret := _m.Called(ctx, session, objectKind, objectID, pm) - - if len(ret) == 0 { - panic("no return value specified for ListMembers") - } - - var r0 users.MembersPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, users.Page) (users.MembersPage, error)); ok { - return rf(ctx, session, objectKind, objectID, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, users.Page) users.MembersPage); ok { - r0 = rf(ctx, session, objectKind, objectID, pm) - } else { - r0 = ret.Get(0).(users.MembersPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, users.Page) error); ok { - r1 = rf(ctx, session, objectKind, objectID, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // ListUsers provides a mock function with given fields: ctx, session, pm func (_m *Service) ListUsers(ctx context.Context, session authn.Session, pm users.Page) (users.UsersPage, error) { ret := _m.Called(ctx, session, pm) diff --git a/users/service.go b/users/service.go index 1a8f28d81e..2ea3f90760 100644 --- a/users/service.go +++ b/users/service.go @@ -17,14 +17,12 @@ import ( repoerr "github.com/absmach/supermq/pkg/errors/repository" svcerr "github.com/absmach/supermq/pkg/errors/service" "github.com/absmach/supermq/pkg/policies" - "golang.org/x/sync/errgroup" ) var ( - errIssueToken = errors.New("failed to issue token") - errFailedPermissionsList = errors.New("failed to list permissions") - errRecoveryToken = errors.New("failed to generate password recovery token") - errLoginDisableUser = errors.New("failed to login in disabled user") + errIssueToken = errors.New("failed to issue token") + errRecoveryToken = errors.New("failed to generate password recovery token") + errLoginDisableUser = errors.New("failed to login in disabled user") ) type service struct { @@ -469,106 +467,6 @@ func (svc service) Delete(ctx context.Context, session authn.Session, id string) return nil } -func (svc service) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm Page) (MembersPage, error) { - var objectType string - switch objectKind { - case policies.ClientsKind: - objectType = policies.ClientType - case policies.DomainsKind: - objectType = policies.DomainType - case policies.GroupsKind: - fallthrough - default: - objectType = policies.GroupType - } - - duids, err := svc.policies.ListAllSubjects(ctx, policies.Policy{ - SubjectType: policies.UserType, - Permission: pm.Permission, - Object: objectID, - ObjectType: objectType, - }) - if err != nil { - return MembersPage{}, errors.Wrap(svcerr.ErrNotFound, err) - } - if len(duids.Policies) == 0 { - return MembersPage{ - Page: Page{Total: 0, Offset: pm.Offset, Limit: pm.Limit}, - }, nil - } - - var userIDs []string - - for _, domainUserID := range duids.Policies { - _, userID := smqauth.DecodeDomainUserID(domainUserID) - userIDs = append(userIDs, userID) - } - pm.IDs = userIDs - - up, err := svc.users.RetrieveAll(ctx, pm) - if err != nil { - return MembersPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - - for i, u := range up.Users { - up.Users[i] = User{ - ID: u.ID, - FirstName: u.FirstName, - LastName: u.LastName, - Credentials: Credentials{ - Username: u.Credentials.Username, - }, - CreatedAt: u.CreatedAt, - UpdatedAt: u.UpdatedAt, - Status: u.Status, - } - } - - if pm.ListPerms && len(up.Users) > 0 { - g, ctx := errgroup.WithContext(ctx) - - for i := range up.Users { - // Copying loop variable "i" to avoid "loop variable captured by func literal" - iter := i - g.Go(func() error { - return svc.retrieveObjectUsersPermissions(ctx, session.DomainID, objectType, objectID, &up.Users[iter]) - }) - } - - if err := g.Wait(); err != nil { - return MembersPage{}, err - } - } - - return MembersPage{ - Page: up.Page, - Members: up.Users, - }, nil -} - -func (svc service) retrieveObjectUsersPermissions(ctx context.Context, domainID, objectType, objectID string, user *User) error { - userID := smqauth.EncodeDomainUserID(domainID, user.ID) - permissions, err := svc.listObjectUserPermission(ctx, userID, objectType, objectID) - if err != nil { - return errors.Wrap(svcerr.ErrAuthorization, err) - } - user.Permissions = permissions - return nil -} - -func (svc service) listObjectUserPermission(ctx context.Context, userID, objectType, objectID string) ([]string, error) { - permissions, err := svc.policies.ListPermissions(ctx, policies.Policy{ - SubjectType: policies.UserType, - Subject: userID, - Object: objectID, - ObjectType: objectType, - }, []string{}) - if err != nil { - return []string{}, errors.Wrap(errFailedPermissionsList, err) - } - return permissions, nil -} - func (svc *service) checkSuperAdmin(ctx context.Context, session authn.Session) error { if !session.SuperAdmin { if err := svc.users.CheckSuperAdmin(ctx, session.UserID); err != nil { diff --git a/users/service_test.go b/users/service_test.go index 881ae25b05..fee974fd1a 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -17,7 +17,6 @@ import ( "github.com/absmach/supermq/pkg/errors" repoerr "github.com/absmach/supermq/pkg/errors/repository" svcerr "github.com/absmach/supermq/pkg/errors/service" - policysvc "github.com/absmach/supermq/pkg/policies" policymocks "github.com/absmach/supermq/pkg/policies/mocks" "github.com/absmach/supermq/pkg/uuid" "github.com/absmach/supermq/users" @@ -1330,297 +1329,6 @@ func TestDeleteUser(t *testing.T) { } } -func TestListMembers(t *testing.T) { - svc, _, cRepo, policies, _ := newService() - - validPolicy := fmt.Sprintf("%s_%s", validID, user.ID) - permissionsUser := basicUser - permissionsUser.Permissions = []string{"read"} - - cases := []struct { - desc string - groupID string - objectKind string - objectID string - page users.Page - listAllSubjectsReq policysvc.Policy - listAllSubjectsResponse policysvc.PolicyPage - retrieveAllResponse users.UsersPage - listPermissionsResponse policysvc.Permissions - response users.MembersPage - listAllSubjectsErr error - retrieveAllErr error - identifyErr error - listPermissionErr error - err error - }{ - { - desc: "list members with no policies successfully of the clients kind", - groupID: validID, - objectKind: policysvc.ClientsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsResponse: policysvc.PolicyPage{}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.ClientType, - }, - response: users.MembersPage{ - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 100, - }, - }, - err: nil, - }, - { - desc: "list members with policies successsfully of the clients kind", - groupID: validID, - objectKind: policysvc.ClientsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.ClientType, - }, - listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, - retrieveAllResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Users: []users.User{user}, - }, - response: users.MembersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Members: []users.User{basicUser}, - }, - err: nil, - }, - { - desc: "list members with policies successsfully of the clients kind with permissions", - groupID: validID, - objectKind: policysvc.ClientsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read", ListPerms: true}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.ClientType, - }, - listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, - retrieveAllResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Users: []users.User{basicUser}, - }, - listPermissionsResponse: []string{"read"}, - response: users.MembersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Members: []users.User{permissionsUser}, - }, - err: nil, - }, - { - desc: "list members with policies of the clients kind with permissionswith failed list permissions", - groupID: validID, - objectKind: policysvc.ClientsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read", ListPerms: true}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.ClientType, - }, - listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, - retrieveAllResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Users: []users.User{user}, - }, - listPermissionsResponse: []string{}, - response: users.MembersPage{}, - listPermissionErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "list members with of the clients kind with failed to list all subjects", - groupID: validID, - objectKind: policysvc.ClientsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.ClientType, - }, - listAllSubjectsErr: repoerr.ErrNotFound, - listAllSubjectsResponse: policysvc.PolicyPage{}, - err: repoerr.ErrNotFound, - }, - { - desc: "list members with of the clients kind with failed to retrieve all", - groupID: validID, - objectKind: policysvc.ClientsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.ClientType, - }, - listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, - retrieveAllResponse: users.UsersPage{}, - response: users.MembersPage{}, - retrieveAllErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "list members with no policies successfully of the domain kind", - groupID: validID, - objectKind: policysvc.DomainsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsResponse: policysvc.PolicyPage{}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.DomainType, - }, - response: users.MembersPage{ - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 100, - }, - }, - err: nil, - }, - { - desc: "list members with policies successsfully of the domains kind", - groupID: validID, - objectKind: policysvc.DomainsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.DomainType, - }, - listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, - retrieveAllResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Users: []users.User{basicUser}, - }, - response: users.MembersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Members: []users.User{basicUser}, - }, - err: nil, - }, - { - desc: "list members with no policies successfully of the groups kind", - groupID: validID, - objectKind: policysvc.GroupsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsResponse: policysvc.PolicyPage{}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.GroupType, - }, - response: users.MembersPage{ - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 100, - }, - }, - err: nil, - }, - { - desc: "list members with policies successsfully of the groups kind", - - groupID: validID, - objectKind: policysvc.GroupsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.GroupType, - }, - listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, - retrieveAllResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Users: []users.User{user}, - }, - response: users.MembersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Members: []users.User{basicUser}, - }, - err: nil, - }, - } - - for _, tc := range cases { - policyCall := policies.On("ListAllSubjects", context.Background(), tc.listAllSubjectsReq).Return(tc.listAllSubjectsResponse, tc.listAllSubjectsErr) - repoCall := cRepo.On("RetrieveAll", context.Background(), mock.Anything).Return(tc.retrieveAllResponse, tc.retrieveAllErr) - policyCall1 := policies.On("ListPermissions", mock.Anything, mock.Anything, mock.Anything).Return(tc.listPermissionsResponse, tc.listPermissionErr) - page, err := svc.ListMembers(context.Background(), authn.Session{}, tc.objectKind, tc.objectID, 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)) - policyCall.Unset() - repoCall.Unset() - policyCall1.Unset() - } -} - func TestIssueToken(t *testing.T) { svc, auth, cRepo, _, _ := newService() diff --git a/users/tracing/tracing.go b/users/tracing/tracing.go index 02c92c1925..0ef1f36708 100644 --- a/users/tracing/tracing.go +++ b/users/tracing/tracing.go @@ -210,14 +210,6 @@ func (tm *tracingMiddleware) Disable(ctx context.Context, session authn.Session, return tm.svc.Disable(ctx, session, id) } -// ListMembers traces the "ListMembers" operation of the wrapped users.Service. -func (tm *tracingMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm users.Page) (users.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, session, objectKind, objectID, pm) -} - // Identify traces the "Identify" operation of the wrapped users.Service. func (tm *tracingMiddleware) Identify(ctx context.Context, session authn.Session) (string, error) { ctx, span := tm.tracer.Start(ctx, "svc_identify", trace.WithAttributes(attribute.String("user_id", session.UserID))) diff --git a/users/users.go b/users/users.go index e01be1f535..a8e52598c9 100644 --- a/users/users.go +++ b/users/users.go @@ -151,9 +151,6 @@ type Service interface { // ListUsers retrieves users list for a valid auth token. ListUsers(ctx context.Context, session authn.Session, pm Page) (UsersPage, error) - // ListMembers retrieves everything that is assigned to a group/client identified by objectID. - ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm Page) (MembersPage, error) - // SearchUsers searches for users with provided filters for a valid auth token. SearchUsers(ctx context.Context, pm Page) (UsersPage, error)