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
Этот коммит содержится в:
Harrison Healey
2017-03-29 21:11:40 -04:00
коммит произвёл Joram Wilander
родитель a4764a5c10
Коммит 6ac87d82e3
8 изменённых файлов: 191 добавлений и 1 удалений

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

@@ -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

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

@@ -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()

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

@@ -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

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

@@ -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...,
), ),

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

@@ -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 {

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

@@ -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)

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

@@ -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()

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

@@ -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