SMQ 2667 - List all the users who can access the entity (#2673)

Signed-off-by: Arvindh <arvindh91@gmail.com>
This commit is contained in:
Arvindh
2025-02-03 21:07:51 +05:30
committed by GitHub
parent 484de372ec
commit 7c6bc9cd7e
73 changed files with 2103 additions and 4041 deletions
+57 -60
View File
@@ -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"
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"
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"
@@ -83,10 +84,6 @@ const (
MaxLimitSize = 100
MaxNameSize = 1024
MaxIDSize = 36
NameOrder = "name"
IDOrder = "id"
AscDir = "asc"
DescDir = "desc"
)
// ValidateUUID validates UUID format.
+3
View File
@@ -141,6 +141,9 @@ var (
// ErrInvalidComparator indicates an invalid comparator.
ErrInvalidComparator = errors.New("invalid comparator")
// ErrMissingMemberIDs indicates missing member ids.
ErrMissingMemberIDs = errors.New("missing member ids")
// ErrMissingMemberType indicates missing group member type.
ErrMissingMemberType = errors.New("missing group member type")
+30
View File
@@ -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
+29
View File
@@ -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
+30
View File
@@ -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
+84
View File
@@ -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'
+1 -182
View File
@@ -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:
+46
View File
@@ -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)
+46
View File
@@ -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)
+2 -1
View File
@@ -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,
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+5 -95
View File
@@ -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)
-11
View File
@@ -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"
+1 -1
View File
@@ -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
-85
View File
@@ -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)
+46
View File
@@ -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)
+46
View File
@@ -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)
+2 -1
View File
@@ -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,
-30
View File
@@ -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
}
+46
View File
@@ -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)
+46
View File
@@ -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)
+2 -1
View File
@@ -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,
+46
View File
@@ -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)
+46
View File
@@ -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)
+2 -1
View File
@@ -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,
+9 -38
View File
@@ -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 {
+11 -40
View File
@@ -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 {
+46
View File
@@ -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)
+46
View File
@@ -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)
+16 -1
View File
@@ -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)
+8 -7
View File
@@ -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),
},
},
},
+637 -15
View File
@@ -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,602 @@ 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 = <entity_id>, it allows all actions. If g.id <> <entity_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
)
`
}
+85
View File
@@ -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),
+51
View File
@@ -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)
+46
View File
@@ -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
+30
View File
@@ -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
}
+14
View File
@@ -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),
@@ -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)
}
// added when repo is implemented.
_ = entityID
_ = mems
return nil
}
func (es *EventHandler) RemoveMemberFromAllEntityHandler(ctx context.Context, data map[string]interface{}) error {
return nil
}
+70 -34
View File
@@ -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
+34
View File
@@ -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
@@ -240,20 +240,6 @@ 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{
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) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleID string) (err error) {
if err := ram.authorize(ctx, roles.OpRoleRemoveAllMembers, smqauthz.PolicyReq{
Domain: session.DomainID,
@@ -268,6 +254,48 @@ func (ram RoleManagerAuthorizationMiddleware) RoleRemoveAllMembers(ctx context.C
return ram.svc.RoleRemoveAllMembers(ctx, session, entityID, roleID)
}
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,
SubjectType: policies.UserType,
SubjectKind: policies.UsersKind,
Object: entityID,
ObjectType: ram.entityType,
}); err != nil {
return err
}
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 {
perm, err := ram.opp.GetPermission(op)
if err != nil {
@@ -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) {
@@ -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)
}
+8
View File
@@ -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)
}
+39
View File
@@ -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)
}
+4
View File
@@ -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)
}
+4
View File
@@ -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)
}
+4
View File
@@ -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)
}
+4
View File
@@ -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)
}
+60 -60
View File
@@ -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
}
+19
View File
@@ -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"`
}
+23
View File
@@ -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
}
+44 -47
View File
@@ -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:
-1
View File
@@ -33,7 +33,6 @@ const (
contentType = "application/senml+json"
invalid = "invalid"
wrongID = "wrongID"
defPermission = "read_permission"
roleName = "roleName"
)
-73
View File
@@ -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
}
+2 -664
View File
@@ -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()
})
}
}
File diff suppressed because it is too large Load Diff
-106
View File
@@ -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
}
-101
View File
@@ -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
}
+1 -237
View File
@@ -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)
}
}
-30
View File
@@ -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
}
-160
View File
@@ -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) {
-58
View File
@@ -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
}
-19
View File
@@ -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 {
-67
View File
@@ -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)
}
-26
View File
@@ -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) {
-9
View File
@@ -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) {
-28
View File
@@ -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)
+3 -105
View File
@@ -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 {
-292
View File
@@ -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()
-8
View File
@@ -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)))
-3
View File
@@ -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)