summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api4/user.go13
-rw-r--r--api4/user_test.go46
-rw-r--r--app/user.go25
-rw-r--r--model/client4.go11
-rw-r--r--store/sql_user_store.go73
-rw-r--r--store/sql_user_store_test.go134
-rw-r--r--store/store.go2
7 files changed, 304 insertions, 0 deletions
diff --git a/api4/user.go b/api4/user.go
index 4eb4479c2..298c5cc8d 100644
--- a/api4/user.go
+++ b/api4/user.go
@@ -266,6 +266,7 @@ func setProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
inTeamId := r.URL.Query().Get("in_team")
+ notInTeamId := r.URL.Query().Get("not_in_team")
inChannelId := r.URL.Query().Get("in_channel")
notInChannelId := r.URL.Query().Get("not_in_channel")
@@ -285,6 +286,18 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
}
profiles, err = app.GetUsersNotInChannelPage(inTeamId, notInChannelId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin())
+ } else if len(notInTeamId) > 0 {
+ if !app.SessionHasPermissionToTeam(c.Session, notInTeamId, model.PERMISSION_VIEW_TEAM) {
+ c.SetPermissionError(model.PERMISSION_VIEW_TEAM)
+ return
+ }
+
+ etag = app.GetUsersNotInTeamEtag(inTeamId)
+ if HandleEtag(etag, "Get Users Not in Team", w, r) {
+ return
+ }
+
+ profiles, err = app.GetUsersNotInTeamPage(notInTeamId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin())
} else if len(inTeamId) > 0 {
if !app.SessionHasPermissionToTeam(c.Session, inTeamId, model.PERMISSION_VIEW_TEAM) {
c.SetPermissionError(model.PERMISSION_VIEW_TEAM)
diff --git a/api4/user_test.go b/api4/user_test.go
index fe53229a5..f6561310b 100644
--- a/api4/user_test.go
+++ b/api4/user_test.go
@@ -897,6 +897,52 @@ func TestGetUsersInTeam(t *testing.T) {
CheckNoError(t, resp)
}
+func TestGetUsersNotInTeam(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+ teamId := th.BasicTeam.Id
+
+ rusers, resp := Client.GetUsersNotInTeam(teamId, 0, 60, "")
+ CheckNoError(t, resp)
+ for _, u := range rusers {
+ CheckUserSanitization(t, u)
+ }
+
+ rusers, resp = Client.GetUsersNotInTeam(teamId, 0, 60, resp.Etag)
+ CheckEtag(t, rusers, resp)
+
+ rusers, resp = Client.GetUsersNotInTeam(teamId, 0, 1, "")
+ CheckNoError(t, resp)
+ if len(rusers) != 1 {
+ t.Fatal("should be 1 per page")
+ }
+
+ rusers, resp = Client.GetUsersNotInTeam(teamId, 1, 1, "")
+ CheckNoError(t, resp)
+ if len(rusers) != 1 {
+ t.Fatal("should be 1 per page")
+ }
+
+ rusers, resp = Client.GetUsersNotInTeam(teamId, 10000, 100, "")
+ CheckNoError(t, resp)
+ if len(rusers) != 0 {
+ t.Fatal("should be no users")
+ }
+
+ Client.Logout()
+ _, resp = Client.GetUsersNotInTeam(teamId, 0, 60, "")
+ CheckUnauthorizedStatus(t, resp)
+
+ user := th.CreateUser()
+ Client.Login(user.Email, user.Password)
+ _, resp = Client.GetUsersNotInTeam(teamId, 0, 60, "")
+ CheckForbiddenStatus(t, resp)
+
+ _, resp = th.SystemAdminClient.GetUsersNotInTeam(teamId, 0, 60, "")
+ CheckNoError(t, resp)
+}
+
func TestGetUsersInChannel(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
defer TearDown()
diff --git a/app/user.go b/app/user.go
index 40651f56a..1c2aca34f 100644
--- a/app/user.go
+++ b/app/user.go
@@ -447,6 +447,14 @@ func GetUsersInTeam(teamId string, offset int, limit int) ([]*model.User, *model
}
}
+func GetUsersNotInTeam(teamId string, offset int, limit int) ([]*model.User, *model.AppError) {
+ if result := <-Srv.Store.User().GetProfilesNotInTeam(teamId, offset, limit); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.([]*model.User), nil
+ }
+}
+
func GetUsersInTeamMap(teamId string, offset int, limit int, asAdmin bool) (map[string]*model.User, *model.AppError) {
users, err := GetUsersInTeam(teamId, offset, limit)
if err != nil {
@@ -476,10 +484,27 @@ func GetUsersInTeamPage(teamId string, page int, perPage int, asAdmin bool) ([]*
return users, nil
}
+func GetUsersNotInTeamPage(teamId string, page int, perPage int, asAdmin bool) ([]*model.User, *model.AppError) {
+ users, err := GetUsersNotInTeam(teamId, page*perPage, perPage)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, user := range users {
+ SanitizeProfile(user, asAdmin)
+ }
+
+ return users, nil
+}
+
func GetUsersInTeamEtag(teamId string) string {
return (<-Srv.Store.User().GetEtagForProfiles(teamId)).Data.(string)
}
+func GetUsersNotInTeamEtag(teamId string) string {
+ return (<-Srv.Store.User().GetEtagForProfilesNotInTeam(teamId)).Data.(string)
+}
+
func GetUsersInChannel(channelId string, offset int, limit int) ([]*model.User, *model.AppError) {
if result := <-Srv.Store.User().GetProfilesInChannel(channelId, offset, limit); result.Err != nil {
return nil, result.Err
diff --git a/model/client4.go b/model/client4.go
index 516a5d23c..c3697e3c9 100644
--- a/model/client4.go
+++ b/model/client4.go
@@ -467,6 +467,17 @@ func (c *Client4) GetUsersInTeam(teamId string, page int, perPage int, etag stri
}
}
+// GetUsersNotInTeam returns a page of users who are not in a team. Page counting starts at 0.
+func (c *Client4) GetUsersNotInTeam(teamId string, page int, perPage int, etag string) ([]*User, *Response) {
+ query := fmt.Sprintf("?not_in_team=%v&page=%v&per_page=%v", teamId, 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)
+ }
+}
+
// GetUsersInChannel returns a page of users on a team. Page counting starts at 0.
func (c *Client4) GetUsersInChannel(channelId string, page int, perPage int, etag string) ([]*User, *Response) {
query := fmt.Sprintf("?in_channel=%v&page=%v&per_page=%v", channelId, page, perPage)
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index c00e37ed6..092f4abc7 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -1514,3 +1514,76 @@ func (us SqlUserStore) AnalyticsGetSystemAdminCount() StoreChannel {
return storeChannel
}
+
+func (us SqlUserStore) GetProfilesNotInTeam(teamId string, offset int, limit int) StoreChannel {
+
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ var users []*model.User
+
+ if _, err := us.GetReplica().Select(&users, `
+ SELECT
+ u.*
+ FROM Users u
+ LEFT JOIN TeamMembers tm
+ ON tm.UserId = u.Id
+ AND tm.TeamId = :TeamId
+ AND tm.DeleteAt = 0
+ WHERE tm.UserId IS NULL
+ ORDER BY u.Username ASC
+ LIMIT :Limit OFFSET :Offset
+ `, map[string]interface{}{"TeamId": teamId, "Offset": offset, "Limit": limit}); err != nil {
+ result.Err = model.NewLocAppError("SqlUserStore.GetProfilesNotInTeam", "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) GetEtagForProfilesNotInTeam(teamId string) StoreChannel {
+
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ updateAt, err := us.GetReplica().SelectInt(`
+ SELECT
+ u.UpdateAt
+ FROM Users u
+ LEFT JOIN TeamMembers tm
+ ON tm.UserId = u.Id
+ AND tm.TeamId = :TeamId
+ AND tm.DeleteAt = 0
+ WHERE tm.UserId IS NULL
+ ORDER BY u.UpdateAt DESC
+ LIMIT 1
+ `, map[string]interface{}{"TeamId": teamId})
+
+ if err != nil {
+ result.Data = fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.GetMillis(), utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress)
+ } else {
+ result.Data = fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, updateAt, utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress)
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
diff --git a/store/sql_user_store_test.go b/store/sql_user_store_test.go
index 798988246..73d42dbcd 100644
--- a/store/sql_user_store_test.go
+++ b/store/sql_user_store_test.go
@@ -1632,3 +1632,137 @@ func TestUserStoreAnalyticsGetSystemAdminCount(t *testing.T) {
}
}
}
+
+func TestUserStoreGetProfilesNotInTeam(t *testing.T) {
+ Setup()
+
+ teamId := model.NewId()
+
+ u1 := &model.User{}
+ u1.Email = model.NewId()
+ Must(store.User().Save(u1))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
+ Must(store.User().UpdateUpdateAt(u1.Id))
+
+ u2 := &model.User{}
+ u2.Email = model.NewId()
+ Must(store.User().Save(u2))
+ Must(store.User().UpdateUpdateAt(u2.Id))
+
+ var initialUsersNotInTeam int
+ var etag1, etag2, etag3 string
+
+ if er1 := <-store.User().GetEtagForProfilesNotInTeam(teamId); er1.Err != nil {
+ t.Fatal(er1.Err)
+ } else {
+ etag1 = er1.Data.(string)
+ }
+
+ if r1 := <-store.User().GetProfilesNotInTeam(teamId, 0, 100000); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ users := r1.Data.([]*model.User)
+ initialUsersNotInTeam = len(users)
+ if initialUsersNotInTeam < 1 {
+ t.Fatalf("Should be at least 1 user not in the team")
+ }
+
+ found := false
+ for _, u := range users {
+ if u.Id == u2.Id {
+ found = true
+ }
+ if u.Id == u1.Id {
+ t.Fatalf("Should not have found user1")
+ }
+ }
+
+ if !found {
+ t.Fatal("missing user2")
+ }
+ }
+
+ time.Sleep(time.Millisecond * 10)
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
+ Must(store.User().UpdateUpdateAt(u2.Id))
+
+ if er2 := <-store.User().GetEtagForProfilesNotInTeam(teamId); er2.Err != nil {
+ t.Fatal(er2.Err)
+ } else {
+ etag2 = er2.Data.(string)
+ if etag1 == etag2 {
+ t.Fatalf("etag should have changed")
+ }
+ }
+
+ if r2 := <-store.User().GetProfilesNotInTeam(teamId, 0, 100000); r2.Err != nil {
+ t.Fatal(r2.Err)
+ } else {
+ users := r2.Data.([]*model.User)
+
+ if len(users) != initialUsersNotInTeam-1 {
+ t.Fatalf("Should be one less user not in team")
+ }
+
+ for _, u := range users {
+ if u.Id == u2.Id {
+ t.Fatalf("Should not have found user2")
+ }
+ if u.Id == u1.Id {
+ t.Fatalf("Should not have found user1")
+ }
+ }
+ }
+
+ time.Sleep(time.Millisecond * 10)
+ Must(store.Team().RemoveMember(teamId, u1.Id))
+ Must(store.Team().RemoveMember(teamId, u2.Id))
+ Must(store.User().UpdateUpdateAt(u1.Id))
+ Must(store.User().UpdateUpdateAt(u2.Id))
+
+ if er3 := <-store.User().GetEtagForProfilesNotInTeam(teamId); er3.Err != nil {
+ t.Fatal(er3.Err)
+ } else {
+ etag3 = er3.Data.(string)
+ t.Log(etag3)
+ if etag1 == etag3 || etag3 == etag2 {
+ t.Fatalf("etag should have changed")
+ }
+ }
+
+ if r3 := <-store.User().GetProfilesNotInTeam(teamId, 0, 100000); r3.Err != nil {
+ t.Fatal(r3.Err)
+ } else {
+ users := r3.Data.([]*model.User)
+ found1, found2 := false, false
+ for _, u := range users {
+ if u.Id == u2.Id {
+ found2 = true
+ }
+ if u.Id == u1.Id {
+ found1 = true
+ }
+ }
+
+ if !found1 || !found2 {
+ t.Fatal("missing user1 or user2")
+ }
+ }
+
+ time.Sleep(time.Millisecond * 10)
+ u3 := &model.User{}
+ u3.Email = model.NewId()
+ Must(store.User().Save(u3))
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}))
+ Must(store.User().UpdateUpdateAt(u3.Id))
+
+ if er4 := <-store.User().GetEtagForProfilesNotInTeam(teamId); er4.Err != nil {
+ t.Fatal(er4.Err)
+ } else {
+ etag4 := er4.Data.(string)
+ t.Log(etag4)
+ if etag4 != etag3 {
+ t.Fatalf("etag should be the same")
+ }
+ }
+}
diff --git a/store/store.go b/store/store.go
index 528b26a3f..409280918 100644
--- a/store/store.go
+++ b/store/store.go
@@ -205,6 +205,8 @@ type UserStore interface {
SearchWithoutTeam(term string, options map[string]bool) StoreChannel
AnalyticsGetInactiveUsersCount() StoreChannel
AnalyticsGetSystemAdminCount() StoreChannel
+ GetProfilesNotInTeam(teamId string, offset int, limit int) StoreChannel
+ GetEtagForProfilesNotInTeam(teamId string) StoreChannel
}
type SessionStore interface {