[MM-26574] Add role filters to get users, users search and add getFilteredUserStats endpoint (#14998)

* MM-26574 Add role filters to user search and get

* Add ability to get filtered user stats

Add support for include bots

* Add tests for user count with filters

Add tests

* Apply changes from code review

* Fix guest filtering

* Fix up tests related to guests

* Clean role names

* Trigger CI

* Trigger CI
Этот коммит содержится в:
Farhan Munshi
2020-07-16 12:37:26 -04:00
коммит произвёл GitHub
родитель c53c9ed190
Коммит c0bfa58ec1
11 изменённых файлов: 795 добавлений и 116 удалений

Просмотреть файл

@@ -68,6 +68,7 @@ func TestUserStore(t *testing.T, ss store.Store, s SqlSupplier) {
t.Run("UpdateMfaActive", func(t *testing.T) { testUserStoreUpdateMfaActive(t, ss) })
t.Run("GetRecentlyActiveUsersForTeam", func(t *testing.T) { testUserStoreGetRecentlyActiveUsersForTeam(t, ss, s) })
t.Run("GetNewUsersForTeam", func(t *testing.T) { testUserStoreGetNewUsersForTeam(t, ss) })
t.Run("Search", func(t *testing.T) { testUserStoreSearch(t, ss) })
t.Run("SearchNotInChannel", func(t *testing.T) { testUserStoreSearchNotInChannel(t, ss) })
t.Run("SearchInChannel", func(t *testing.T) { testUserStoreSearchInChannel(t, ss) })
t.Run("SearchNotInTeam", func(t *testing.T) { testUserStoreSearchNotInTeam(t, ss) })
@@ -2291,6 +2292,153 @@ func assertUsers(t *testing.T, expected, actual []*model.User) {
}
}
func testUserStoreSearch(t *testing.T, ss store.Store) {
u1 := &model.User{
Username: "jimbo1" + model.NewId(),
FirstName: "Tim",
LastName: "Bill",
Nickname: "Rob",
Email: "harold" + model.NewId() + "@simulator.amazonses.com",
Roles: "system_user system_admin",
}
_, err := ss.User().Save(u1)
require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(u1.Id)) }()
u2 := &model.User{
Username: "jim2-bobby" + model.NewId(),
Email: MakeEmail(),
Roles: "system_user",
}
_, err = ss.User().Save(u2)
require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(u2.Id)) }()
u3 := &model.User{
Username: "jimbo3" + model.NewId(),
Email: MakeEmail(),
Roles: "system_guest",
}
_, err = ss.User().Save(u3)
require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(u3.Id)) }()
// The users returned from the database will have AuthData as an empty string.
nilAuthData := new(string)
*nilAuthData = ""
u1.AuthData = nilAuthData
u2.AuthData = nilAuthData
u3.AuthData = nilAuthData
t1id := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: t1id, UserId: u1.Id, SchemeAdmin: true, SchemeUser: true}, -1)
require.Nil(t, err)
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: t1id, UserId: u2.Id, SchemeAdmin: true, SchemeUser: true}, -1)
require.Nil(t, err)
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: t1id, UserId: u3.Id, SchemeAdmin: false, SchemeUser: false, SchemeGuest: true}, -1)
require.Nil(t, err)
testCases := []struct {
Description string
TeamId string
Term string
Options *model.UserSearchOptions
Expected []*model.User
}{
{
"search jimb, team 1",
t1id,
"jimb",
&model.UserSearchOptions{
AllowFullNames: true,
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
},
[]*model.User{u1, u3},
},
{
"search jimb, team 1 with team guest and team admin filters without sys admin filter",
t1id,
"jimb",
&model.UserSearchOptions{
AllowFullNames: true,
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
TeamRoles: []string{model.TEAM_GUEST_ROLE_ID, model.TEAM_ADMIN_ROLE_ID},
},
[]*model.User{u3},
},
{
"search jimb, team 1 with team admin filter and sys admin filter",
t1id,
"jimb",
&model.UserSearchOptions{
AllowFullNames: true,
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
Roles: []string{model.SYSTEM_ADMIN_ROLE_ID},
TeamRoles: []string{model.TEAM_ADMIN_ROLE_ID},
},
[]*model.User{u1},
},
{
"search jim, team 1 with team admin filter",
t1id,
"jim",
&model.UserSearchOptions{
AllowFullNames: true,
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
TeamRoles: []string{model.TEAM_ADMIN_ROLE_ID},
},
[]*model.User{u2},
},
{
"search jim, team 1 with team admin and team guest filter",
t1id,
"jim",
&model.UserSearchOptions{
AllowFullNames: true,
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
TeamRoles: []string{model.TEAM_ADMIN_ROLE_ID, model.TEAM_GUEST_ROLE_ID},
},
[]*model.User{u2, u3},
},
{
"search jim, team 1 with team admin and system admin filters",
t1id,
"jim",
&model.UserSearchOptions{
AllowFullNames: true,
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
Roles: []string{model.SYSTEM_ADMIN_ROLE_ID},
TeamRoles: []string{model.TEAM_ADMIN_ROLE_ID},
},
[]*model.User{u2, u1},
},
{
"search jim, team 1 with system guest filter",
t1id,
"jim",
&model.UserSearchOptions{
AllowFullNames: true,
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
Roles: []string{model.SYSTEM_GUEST_ROLE_ID},
TeamRoles: []string{},
},
[]*model.User{u3},
},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
users, err := ss.User().Search(
testCase.TeamId,
testCase.Term,
testCase.Options,
)
require.Nil(t, err)
assertUsers(t, testCase.Expected, users)
})
}
}
func testUserStoreSearchNotInChannel(t *testing.T, ss store.Store) {
u1 := &model.User{
Username: "jimbo1" + model.NewId(),
@@ -2524,6 +2672,7 @@ func testUserStoreSearchInChannel(t *testing.T, ss store.Store) {
LastName: "Bill",
Nickname: "Rob",
Email: "harold" + model.NewId() + "@simulator.amazonses.com",
Roles: "system_user system_admin",
}
_, err := ss.User().Save(u1)
require.Nil(t, err)
@@ -2532,6 +2681,7 @@ func testUserStoreSearchInChannel(t *testing.T, ss store.Store) {
u2 := &model.User{
Username: "jim-bobby" + model.NewId(),
Email: MakeEmail(),
Roles: "system_user",
}
_, err = ss.User().Save(u2)
require.Nil(t, err)
@@ -2541,6 +2691,7 @@ func testUserStoreSearchInChannel(t *testing.T, ss store.Store) {
Username: "jimbo3" + model.NewId(),
Email: MakeEmail(),
DeleteAt: 1,
Roles: "system_user",
}
_, err = ss.User().Save(u3)
require.Nil(t, err)
@@ -2592,18 +2743,24 @@ func testUserStoreSearchInChannel(t *testing.T, ss store.Store) {
ChannelId: c1.Id,
UserId: u1.Id,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeAdmin: true,
SchemeUser: true,
})
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{
ChannelId: c2.Id,
UserId: u2.Id,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeAdmin: false,
SchemeUser: true,
})
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{
ChannelId: c1.Id,
UserId: u3.Id,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeAdmin: false,
SchemeUser: true,
})
require.Nil(t, err)
@@ -2667,6 +2824,66 @@ func testUserStoreSearchInChannel(t *testing.T, ss store.Store) {
},
[]*model.User{},
},
{
"search jim, allow inactive, channel 1 with system admin filter",
c1.Id,
"jim",
&model.UserSearchOptions{
AllowFullNames: true,
AllowInactive: true,
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
Roles: []string{model.SYSTEM_ADMIN_ROLE_ID},
},
[]*model.User{u1},
},
{
"search jim, allow inactive, channel 1 with system admin and system user filter",
c1.Id,
"jim",
&model.UserSearchOptions{
AllowFullNames: true,
AllowInactive: true,
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
Roles: []string{model.SYSTEM_ADMIN_ROLE_ID, model.SYSTEM_USER_ROLE_ID},
},
[]*model.User{u1, u3},
},
{
"search jim, allow inactive, channel 1 with channel user filter",
c1.Id,
"jim",
&model.UserSearchOptions{
AllowFullNames: true,
AllowInactive: true,
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
ChannelRoles: []string{model.CHANNEL_USER_ROLE_ID},
},
[]*model.User{u3},
},
{
"search jim, allow inactive, channel 1 with channel user and channel admin filter",
c1.Id,
"jim",
&model.UserSearchOptions{
AllowFullNames: true,
AllowInactive: true,
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
ChannelRoles: []string{model.CHANNEL_USER_ROLE_ID, model.CHANNEL_ADMIN_ROLE_ID},
},
[]*model.User{u3},
},
{
"search jim, allow inactive, channel 2 with channel user filter",
c2.Id,
"jim",
&model.UserSearchOptions{
AllowFullNames: true,
AllowInactive: true,
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
ChannelRoles: []string{model.CHANNEL_USER_ROLE_ID},
},
[]*model.User{u2},
},
}
for _, testCase := range testCases {
@@ -3122,111 +3339,287 @@ func testUserStoreSearchInGroup(t *testing.T, ss store.Store) {
func testCount(t *testing.T, ss store.Store) {
// Regular
teamId := model.NewId()
u1 := &model.User{}
u1.Email = MakeEmail()
_, err := ss.User().Save(u1)
channelId := model.NewId()
regularUser := &model.User{}
regularUser.Email = MakeEmail()
regularUser.Roles = model.SYSTEM_USER_ROLE_ID
_, err := ss.User().Save(regularUser)
require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(u1.Id)) }()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1)
defer func() { require.Nil(t, ss.User().PermanentDelete(regularUser.Id)) }()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: regularUser.Id, SchemeAdmin: false, SchemeUser: true}, -1)
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{UserId: regularUser.Id, ChannelId: channelId, SchemeAdmin: false, SchemeUser: true, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
guestUser := &model.User{}
guestUser.Email = MakeEmail()
guestUser.Roles = model.SYSTEM_GUEST_ROLE_ID
_, err = ss.User().Save(guestUser)
require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(guestUser.Id)) }()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: guestUser.Id, SchemeAdmin: false, SchemeUser: false, SchemeGuest: true}, -1)
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{UserId: guestUser.Id, ChannelId: channelId, SchemeAdmin: false, SchemeUser: false, SchemeGuest: true, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
teamAdmin := &model.User{}
teamAdmin.Email = MakeEmail()
teamAdmin.Roles = model.SYSTEM_USER_ROLE_ID
_, err = ss.User().Save(teamAdmin)
require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(teamAdmin.Id)) }()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: teamAdmin.Id, SchemeAdmin: true, SchemeUser: true}, -1)
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{UserId: teamAdmin.Id, ChannelId: channelId, SchemeAdmin: true, SchemeUser: true, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
sysAdmin := &model.User{}
sysAdmin.Email = MakeEmail()
sysAdmin.Roles = model.SYSTEM_ADMIN_ROLE_ID + " " + model.SYSTEM_USER_ROLE_ID
_, err = ss.User().Save(sysAdmin)
require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(sysAdmin.Id)) }()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: sysAdmin.Id, SchemeAdmin: false, SchemeUser: true}, -1)
require.Nil(t, err)
_, err = ss.Channel().SaveMember(&model.ChannelMember{UserId: sysAdmin.Id, ChannelId: channelId, SchemeAdmin: true, SchemeUser: true, NotifyProps: model.GetDefaultChannelNotifyProps()})
require.Nil(t, err)
// Deleted
u2 := &model.User{}
u2.Email = MakeEmail()
u2.DeleteAt = model.GetMillis()
_, err = ss.User().Save(u2)
deletedUser := &model.User{}
deletedUser.Email = MakeEmail()
deletedUser.DeleteAt = model.GetMillis()
_, err = ss.User().Save(deletedUser)
require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(u2.Id)) }()
defer func() { require.Nil(t, ss.User().PermanentDelete(deletedUser.Id)) }()
// Bot
u3, err := ss.User().Save(&model.User{
botUser, err := ss.User().Save(&model.User{
Email: MakeEmail(),
})
require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(u3.Id)) }()
defer func() { require.Nil(t, ss.User().PermanentDelete(botUser.Id)) }()
_, nErr := ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
UserId: botUser.Id,
Username: botUser.Username,
OwnerId: regularUser.Id,
})
require.Nil(t, nErr)
u3.IsBot = true
defer func() { require.Nil(t, ss.Bot().PermanentDelete(u3.Id)) }()
botUser.IsBot = true
defer func() { require.Nil(t, ss.Bot().PermanentDelete(botUser.Id)) }()
count, err := ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: false,
IncludeDeleted: false,
TeamId: "",
})
require.Nil(t, err)
require.Equal(t, int64(1), count)
count, err = ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: false,
TeamId: "",
})
require.Nil(t, err)
require.Equal(t, int64(2), count)
count, err = ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: false,
IncludeDeleted: true,
TeamId: "",
})
require.Nil(t, err)
require.Equal(t, int64(2), count)
count, err = ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
TeamId: "",
})
require.Nil(t, err)
require.Equal(t, int64(3), count)
count, err = ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
ExcludeRegularUsers: true,
TeamId: "",
})
require.Nil(t, err)
require.Equal(t, int64(1), count)
count, err = ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
TeamId: teamId,
})
require.Nil(t, err)
require.Equal(t, int64(1), count)
count, err = ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
TeamId: model.NewId(),
})
require.Nil(t, err)
require.Equal(t, int64(0), count)
count, err = ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
TeamId: teamId,
ViewRestrictions: &model.ViewUsersRestrictions{Teams: []string{teamId}},
})
require.Nil(t, err)
require.Equal(t, int64(1), count)
count, err = ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
TeamId: teamId,
ViewRestrictions: &model.ViewUsersRestrictions{Teams: []string{model.NewId()}},
})
require.Nil(t, err)
require.Equal(t, int64(0), count)
testCases := []struct {
Description string
Options model.UserCountOptions
Expected int64
}{
{
"No bot accounts no deleted accounts and no team id",
model.UserCountOptions{
IncludeBotAccounts: false,
IncludeDeleted: false,
TeamId: "",
},
4,
},
{
"Include bot accounts no deleted accounts and no team id",
model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: false,
TeamId: "",
},
5,
},
{
"Include delete accounts no bots and no team id",
model.UserCountOptions{
IncludeBotAccounts: false,
IncludeDeleted: true,
TeamId: "",
},
5,
},
{
"Include bot accounts and deleted accounts and no team id",
model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
TeamId: "",
},
6,
},
{
"Include bot accounts, deleted accounts, exclude regular users with no team id",
model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
ExcludeRegularUsers: true,
TeamId: "",
},
1,
},
{
"Include bot accounts and deleted accounts with existing team id",
model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
TeamId: teamId,
},
4,
},
{
"Include bot accounts and deleted accounts with fake team id",
model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
TeamId: model.NewId(),
},
0,
},
{
"Include bot accounts and deleted accounts with existing team id and view restrictions allowing team",
model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
TeamId: teamId,
ViewRestrictions: &model.ViewUsersRestrictions{Teams: []string{teamId}},
},
4,
},
{
"Include bot accounts and deleted accounts with existing team id and view restrictions not allowing current team",
model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
TeamId: teamId,
ViewRestrictions: &model.ViewUsersRestrictions{Teams: []string{model.NewId()}},
},
0,
},
{
"Filter by system admins only",
model.UserCountOptions{
TeamId: teamId,
Roles: []string{model.SYSTEM_ADMIN_ROLE_ID},
},
1,
},
{
"Filter by system users only",
model.UserCountOptions{
TeamId: teamId,
Roles: []string{model.SYSTEM_USER_ROLE_ID},
},
2,
},
{
"Filter by system guests only",
model.UserCountOptions{
TeamId: teamId,
Roles: []string{model.SYSTEM_GUEST_ROLE_ID},
},
1,
},
{
"Filter by system admins and system users",
model.UserCountOptions{
TeamId: teamId,
Roles: []string{model.SYSTEM_ADMIN_ROLE_ID, model.SYSTEM_USER_ROLE_ID},
},
3,
},
{
"Filter by system admins, system user and system guests",
model.UserCountOptions{
TeamId: teamId,
Roles: []string{model.SYSTEM_ADMIN_ROLE_ID, model.SYSTEM_USER_ROLE_ID, model.SYSTEM_GUEST_ROLE_ID},
},
4,
},
{
"Filter by team admins",
model.UserCountOptions{
TeamId: teamId,
TeamRoles: []string{model.TEAM_ADMIN_ROLE_ID},
},
1,
},
{
"Filter by team members",
model.UserCountOptions{
TeamId: teamId,
TeamRoles: []string{model.TEAM_USER_ROLE_ID},
},
1,
},
{
"Filter by team guests",
model.UserCountOptions{
TeamId: teamId,
TeamRoles: []string{model.TEAM_GUEST_ROLE_ID},
},
1,
},
{
"Filter by team guests and any system role",
model.UserCountOptions{
TeamId: teamId,
TeamRoles: []string{model.TEAM_GUEST_ROLE_ID},
Roles: []string{model.SYSTEM_ADMIN_ROLE_ID},
},
2,
},
{
"Filter by channel members",
model.UserCountOptions{
ChannelId: channelId,
ChannelRoles: []string{model.CHANNEL_USER_ROLE_ID},
},
1,
},
{
"Filter by channel members and system admins",
model.UserCountOptions{
ChannelId: channelId,
Roles: []string{model.SYSTEM_ADMIN_ROLE_ID},
ChannelRoles: []string{model.CHANNEL_USER_ROLE_ID},
},
2,
},
{
"Filter by channel members and system admins and channel admins",
model.UserCountOptions{
ChannelId: channelId,
Roles: []string{model.SYSTEM_ADMIN_ROLE_ID},
ChannelRoles: []string{model.CHANNEL_USER_ROLE_ID, model.CHANNEL_ADMIN_ROLE_ID},
},
3,
},
{
"Filter by channel guests",
model.UserCountOptions{
ChannelId: channelId,
ChannelRoles: []string{model.CHANNEL_GUEST_ROLE_ID},
},
1,
},
{
"Filter by channel guests and any system role",
model.UserCountOptions{
ChannelId: channelId,
ChannelRoles: []string{model.CHANNEL_GUEST_ROLE_ID},
Roles: []string{model.SYSTEM_ADMIN_ROLE_ID},
},
2,
},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
count, err := ss.User().Count(testCase.Options)
require.Nil(t, err)
require.Equal(t, testCase.Expected, count)
})
}
}
func testUserStoreAnalyticsActiveCount(t *testing.T, ss store.Store, s SqlSupplier) {