mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
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:
committed by
Joram Wilander
parent
a4764a5c10
commit
6ac87d82e3
11
api4/user.go
11
api4/user.go
@@ -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()
|
||||||
|
|||||||
21
app/user.go
21
app/user.go
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user