PLT-2713 Added ability for admins to list users not in any team (#5844)

* PLT-2713 Added ability for admins to list users not in any team

* Updated style of unit test
This commit is contained in:
Harrison Healey
2017-03-29 21:11:40 -04:00
committed by Joram Wilander
parent a4764a5c10
commit 6ac87d82e3
8 changed files with 191 additions and 1 deletions

View File

@@ -269,6 +269,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
notInTeamId := r.URL.Query().Get("not_in_team") notInTeamId := r.URL.Query().Get("not_in_team")
inChannelId := r.URL.Query().Get("in_channel") inChannelId := r.URL.Query().Get("in_channel")
notInChannelId := r.URL.Query().Get("not_in_channel") notInChannelId := r.URL.Query().Get("not_in_channel")
withoutTeam := r.URL.Query().Get("without_team")
if len(notInChannelId) > 0 && len(inTeamId) == 0 { if len(notInChannelId) > 0 && len(inTeamId) == 0 {
c.SetInvalidParam("team_id") c.SetInvalidParam("team_id")
@@ -279,7 +280,15 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
var err *model.AppError var err *model.AppError
etag := "" etag := ""
if len(notInChannelId) > 0 { if withoutTeamBool, err := strconv.ParseBool(withoutTeam); err == nil && withoutTeamBool {
// Use a special permission for now
if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_LIST_USERS_WITHOUT_TEAM) {
c.SetPermissionError(model.PERMISSION_LIST_USERS_WITHOUT_TEAM)
return
}
profiles, err = app.GetUsersWithoutTeamPage(c.Params.Page, c.Params.PerPage, c.IsSystemAdmin())
} else if len(notInChannelId) > 0 {
if !app.SessionHasPermissionToChannel(c.Session, notInChannelId, model.PERMISSION_READ_CHANNEL) { if !app.SessionHasPermissionToChannel(c.Session, notInChannelId, model.PERMISSION_READ_CHANNEL) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL) c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
return return

View File

@@ -851,6 +851,56 @@ func TestGetUsers(t *testing.T) {
CheckUnauthorizedStatus(t, resp) CheckUnauthorizedStatus(t, resp)
} }
func TestGetUsersWithoutTeam(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
defer TearDown()
Client := th.Client
SystemAdminClient := th.SystemAdminClient
if _, resp := Client.GetUsersWithoutTeam(0, 100, ""); resp.Error == nil {
t.Fatal("should prevent non-admin user from getting users without a team")
}
// These usernames need to appear in the first 100 users for this to work
user, resp := Client.CreateUser(&model.User{
Username: "a000000000" + model.NewId(),
Email: "success+" + model.NewId() + "@simulator.amazonses.com",
Password: "Password1",
})
CheckNoError(t, resp)
LinkUserToTeam(user, th.BasicTeam)
defer app.Srv.Store.User().PermanentDelete(user.Id)
user2, resp := Client.CreateUser(&model.User{
Username: "a000000001" + model.NewId(),
Email: "success+" + model.NewId() + "@simulator.amazonses.com",
Password: "Password1",
})
CheckNoError(t, resp)
defer app.Srv.Store.User().PermanentDelete(user2.Id)
rusers, resp := SystemAdminClient.GetUsersWithoutTeam(0, 100, "")
CheckNoError(t, resp)
found1 := false
found2 := false
for _, u := range rusers {
if u.Id == user.Id {
found1 = true
} else if u.Id == user2.Id {
found2 = true
}
}
if found1 {
t.Fatal("shouldn't have returned user that has a team")
} else if !found2 {
t.Fatal("should've returned user that has no teams")
}
}
func TestGetUsersInTeam(t *testing.T) { func TestGetUsersInTeam(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin() th := Setup().InitBasic().InitSystemAdmin()
defer TearDown() defer TearDown()

View File

@@ -579,6 +579,27 @@ func GetUsersNotInChannelPage(teamId string, channelId string, page int, perPage
return users, nil return users, nil
} }
func GetUsersWithoutTeamPage(page int, perPage int, asAdmin bool) ([]*model.User, *model.AppError) {
users, err := GetUsersWithoutTeam(page*perPage, perPage)
if err != nil {
return nil, err
}
for _, user := range users {
SanitizeProfile(user, asAdmin)
}
return users, nil
}
func GetUsersWithoutTeam(offset int, limit int) ([]*model.User, *model.AppError) {
if result := <-Srv.Store.User().GetProfilesWithoutTeam(offset, limit); result.Err != nil {
return nil, result.Err
} else {
return result.Data.([]*model.User), nil
}
}
func GetUsersByIds(userIds []string, asAdmin bool) ([]*model.User, *model.AppError) { func GetUsersByIds(userIds []string, asAdmin bool) ([]*model.User, *model.AppError) {
if result := <-Srv.Store.User().GetProfileByIds(userIds, true); result.Err != nil { if result := <-Srv.Store.User().GetProfileByIds(userIds, true); result.Err != nil {
return nil, result.Err return nil, result.Err

View File

@@ -57,6 +57,7 @@ var PERMISSION_CREATE_TEAM *Permission
var PERMISSION_MANAGE_TEAM *Permission var PERMISSION_MANAGE_TEAM *Permission
var PERMISSION_IMPORT_TEAM *Permission var PERMISSION_IMPORT_TEAM *Permission
var PERMISSION_VIEW_TEAM *Permission var PERMISSION_VIEW_TEAM *Permission
var PERMISSION_LIST_USERS_WITHOUT_TEAM *Permission
// General permission that encompases all system admin functions // General permission that encompases all system admin functions
// in the future this could be broken up to allow access to some // in the future this could be broken up to allow access to some
@@ -286,6 +287,11 @@ func InitalizePermissions() {
"authentication.permissions.view_team.name", "authentication.permissions.view_team.name",
"authentication.permissions.view_team.description", "authentication.permissions.view_team.description",
} }
PERMISSION_LIST_USERS_WITHOUT_TEAM = &Permission{
"list_users_without_team",
"authentication.permisssions.list_users_without_team.name",
"authentication.permisssions.list_users_without_team.description",
}
} }
func InitalizeRoles() { func InitalizeRoles() {
@@ -400,6 +406,7 @@ func InitalizeRoles() {
PERMISSION_DELETE_OTHERS_POSTS.Id, PERMISSION_DELETE_OTHERS_POSTS.Id,
PERMISSION_CREATE_TEAM.Id, PERMISSION_CREATE_TEAM.Id,
PERMISSION_ADD_USER_TO_TEAM.Id, PERMISSION_ADD_USER_TO_TEAM.Id,
PERMISSION_LIST_USERS_WITHOUT_TEAM.Id,
}, },
ROLE_TEAM_USER.Permissions..., ROLE_TEAM_USER.Permissions...,
), ),

View File

@@ -500,6 +500,17 @@ func (c *Client4) GetUsersNotInChannel(teamId, channelId string, page int, perPa
} }
} }
// GetUsersWithoutTeam returns a page of users on the system that aren't on any teams. Page counting starts at 0.
func (c *Client4) GetUsersWithoutTeam(page int, perPage int, etag string) ([]*User, *Response) {
query := fmt.Sprintf("?without_team=1&page=%v&per_page=%v", page, perPage)
if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil {
return nil, &Response{StatusCode: r.StatusCode, Error: err}
} else {
defer closeBody(r)
return UserListFromJson(r.Body), BuildResponse(r)
}
}
// GetUsersByIds returns a list of users based on the provided user ids. // GetUsersByIds returns a list of users based on the provided user ids.
func (c *Client4) GetUsersByIds(userIds []string) ([]*User, *Response) { func (c *Client4) GetUsersByIds(userIds []string) ([]*User, *Response) {
if r, err := c.DoApiPost(c.GetUsersRoute()+"/ids", ArrayToJson(userIds)); err != nil { if r, err := c.DoApiPost(c.GetUsersRoute()+"/ids", ArrayToJson(userIds)); err != nil {

View File

@@ -726,6 +726,54 @@ func (us SqlUserStore) GetProfilesNotInChannel(teamId string, channelId string,
return storeChannel return storeChannel
} }
func (us SqlUserStore) GetProfilesWithoutTeam(offset int, limit int) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var users []*model.User
query := `
SELECT
*
FROM
Users
WHERE
(SELECT
COUNT(0)
FROM
TeamMembers
WHERE
TeamMembers.UserId = Users.Id
AND TeamMembers.DeleteAt = 0) = 0
ORDER BY
Username ASC
LIMIT
:Limit
OFFSET
:Offset`
if _, err := us.GetReplica().Select(&users, query, map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil {
result.Err = model.NewLocAppError("SqlUserStore.GetProfilesWithoutTeam", "store.sql_user.get_profiles.app_error", nil, err.Error())
} else {
for _, u := range users {
u.Password = ""
u.AuthData = new(string)
*u.AuthData = ""
}
result.Data = users
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (us SqlUserStore) GetProfilesByUsernames(usernames []string, teamId string) StoreChannel { func (us SqlUserStore) GetProfilesByUsernames(usernames []string, teamId string) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)

View File

@@ -373,6 +373,49 @@ func TestUserStoreGetProfilesInChannel(t *testing.T) {
} }
} }
func TestUserStoreGetProfilesWithoutTeam(t *testing.T) {
Setup()
teamId := model.NewId()
// These usernames need to appear in the first 100 users for this to work
u1 := &model.User{}
u1.Username = "a000000000" + model.NewId()
u1.Email = model.NewId()
Must(store.User().Save(u1))
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
defer store.User().PermanentDelete(u1.Id)
u2 := &model.User{}
u2.Username = "a000000001" + model.NewId()
u2.Email = model.NewId()
Must(store.User().Save(u2))
defer store.User().PermanentDelete(u2.Id)
if r1 := <-store.User().GetProfilesWithoutTeam(0, 100); r1.Err != nil {
t.Fatal(r1.Err)
} else {
users := r1.Data.([]*model.User)
found1 := false
found2 := false
for _, u := range users {
if u.Id == u1.Id {
found1 = true
} else if u.Id == u2.Id {
found2 = true
}
}
if found1 {
t.Fatal("shouldn't have returned user on team")
} else if !found2 {
t.Fatal("should've returned user without any teams")
}
}
}
func TestUserStoreGetAllProfilesInChannel(t *testing.T) { func TestUserStoreGetAllProfilesInChannel(t *testing.T) {
Setup() Setup()

View File

@@ -177,6 +177,7 @@ type UserStore interface {
GetProfilesInChannel(channelId string, offset int, limit int) StoreChannel GetProfilesInChannel(channelId string, offset int, limit int) StoreChannel
GetAllProfilesInChannel(channelId string, allowFromCache bool) StoreChannel GetAllProfilesInChannel(channelId string, allowFromCache bool) StoreChannel
GetProfilesNotInChannel(teamId string, channelId string, offset int, limit int) StoreChannel GetProfilesNotInChannel(teamId string, channelId string, offset int, limit int) StoreChannel
GetProfilesWithoutTeam(offset int, limit int) StoreChannel
GetProfilesByUsernames(usernames []string, teamId string) StoreChannel GetProfilesByUsernames(usernames []string, teamId string) StoreChannel
GetAllProfiles(offset int, limit int) StoreChannel GetAllProfiles(offset int, limit int) StoreChannel
GetProfiles(teamId string, offset int, limit int) StoreChannel GetProfiles(teamId string, offset int, limit int) StoreChannel