Adding list/join public/private teams permissions (#10309)

* Adding list/join public/private teams permissions

* Add permission migration and allow to migrate based on role name

* Adding JoinTeam new endpoint

* Addressing PR review comments

* Keep the previous API consistent
This commit is contained in:
Jesús Espino
2019-03-19 11:36:29 +01:00
committed by GitHub
parent 030ba52b08
commit 5a9d95d9c7
13 changed files with 802 additions and 217 deletions

View File

@@ -341,10 +341,23 @@ func (s SqlTeamStore) SearchOpen(term string) store.StoreChannel {
})
}
func (s SqlTeamStore) SearchPrivate(term string) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
var teams []*model.Team
if _, err := s.GetReplica().Select(&teams, "SELECT * FROM Teams WHERE (Type != 'O' OR AllowOpenInvite = false) AND (Name LIKE :Term OR DisplayName LIKE :Term)", map[string]interface{}{"Term": term + "%"}); err != nil {
result.Err = model.NewAppError("SqlTeamStore.SearchPrivate", "store.sql_team.search_private_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError)
return
}
result.Data = teams
})
}
func (s SqlTeamStore) GetAll() store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
var data []*model.Team
if _, err := s.GetReplica().Select(&data, "SELECT * FROM Teams"); err != nil {
if _, err := s.GetReplica().Select(&data, "SELECT * FROM Teams ORDER BY DisplayName"); err != nil {
result.Err = model.NewAppError("SqlTeamStore.GetAllTeams", "store.sql_team.get_all.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
@@ -395,12 +408,60 @@ func (s SqlTeamStore) GetTeamsByUserId(userId string) store.StoreChannel {
})
}
func (s SqlTeamStore) GetAllTeamListing() store.StoreChannel {
func (s SqlTeamStore) GetAllPrivateTeamListing() store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
query := "SELECT * FROM Teams WHERE AllowOpenInvite = 1"
query := "SELECT * FROM Teams WHERE AllowOpenInvite = 0 ORDER BY DisplayName"
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
query = "SELECT * FROM Teams WHERE AllowOpenInvite = true"
query = "SELECT * FROM Teams WHERE AllowOpenInvite = false ORDER BY DisplayName"
}
var data []*model.Team
if _, err := s.GetReplica().Select(&data, query); err != nil {
result.Err = model.NewAppError("SqlTeamStore.GetAllPrivateTeamListing", "store.sql_team.get_all_private_team_listing.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
for _, team := range data {
if len(team.InviteId) == 0 {
team.InviteId = team.Id
}
}
result.Data = data
})
}
func (s SqlTeamStore) GetAllPrivateTeamPageListing(offset int, limit int) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
query := "SELECT * FROM Teams WHERE AllowOpenInvite = 0 ORDER BY DisplayName LIMIT :Limit OFFSET :Offset"
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
query = "SELECT * FROM Teams WHERE AllowOpenInvite = false ORDER BY DisplayName LIMIT :Limit OFFSET :Offset"
}
var data []*model.Team
if _, err := s.GetReplica().Select(&data, query, map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil {
result.Err = model.NewAppError("SqlTeamStore.GetAllPrivateTeamListing", "store.sql_team.get_all_private_team_listing.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
for _, team := range data {
if len(team.InviteId) == 0 {
team.InviteId = team.Id
}
}
result.Data = data
})
}
func (s SqlTeamStore) GetAllTeamListing() store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
query := "SELECT * FROM Teams WHERE AllowOpenInvite = 1 ORDER BY DisplayName"
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
query = "SELECT * FROM Teams WHERE AllowOpenInvite = true ORDER BY DisplayName"
}
var data []*model.Team
@@ -421,10 +482,10 @@ func (s SqlTeamStore) GetAllTeamListing() store.StoreChannel {
func (s SqlTeamStore) GetAllTeamPageListing(offset int, limit int) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
query := "SELECT * FROM Teams WHERE AllowOpenInvite = 1 LIMIT :Limit OFFSET :Offset"
query := "SELECT * FROM Teams WHERE AllowOpenInvite = 1 ORDER BY DisplayName LIMIT :Limit OFFSET :Offset"
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
query = "SELECT * FROM Teams WHERE AllowOpenInvite = true LIMIT :Limit OFFSET :Offset"
query = "SELECT * FROM Teams WHERE AllowOpenInvite = true ORDER BY DisplayName LIMIT :Limit OFFSET :Offset"
}
var data []*model.Team

View File

@@ -89,8 +89,11 @@ type TeamStore interface {
SearchByName(name string) StoreChannel
SearchAll(term string) StoreChannel
SearchOpen(term string) StoreChannel
SearchPrivate(term string) StoreChannel
GetAll() StoreChannel
GetAllPage(offset int, limit int) StoreChannel
GetAllPrivateTeamListing() StoreChannel
GetAllPrivateTeamPageListing(offset int, limit int) StoreChannel
GetAllTeamListing() StoreChannel
GetAllTeamPageListing(offset int, limit int) StoreChannel
GetTeamsByUserId(userId string) StoreChannel

View File

@@ -141,6 +141,38 @@ func (_m *TeamStore) GetAllPage(offset int, limit int) store.StoreChannel {
return r0
}
// GetAllPrivateTeamListing provides a mock function with given fields:
func (_m *TeamStore) GetAllPrivateTeamListing() store.StoreChannel {
ret := _m.Called()
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func() store.StoreChannel); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// GetAllPrivateTeamPageListing provides a mock function with given fields: offset, limit
func (_m *TeamStore) GetAllPrivateTeamPageListing(offset int, limit int) store.StoreChannel {
ret := _m.Called(offset, limit)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(int, int) store.StoreChannel); ok {
r0 = rf(offset, limit)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// GetAllTeamListing provides a mock function with given fields:
func (_m *TeamStore) GetAllTeamListing() store.StoreChannel {
ret := _m.Called()
@@ -557,6 +589,22 @@ func (_m *TeamStore) SearchOpen(term string) store.StoreChannel {
return r0
}
// SearchPrivate provides a mock function with given fields: term
func (_m *TeamStore) SearchPrivate(term string) store.StoreChannel {
ret := _m.Called(term)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
r0 = rf(term)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// Update provides a mock function with given fields: team
func (_m *TeamStore) Update(team *model.Team) store.StoreChannel {
ret := _m.Called(team)

View File

@@ -26,10 +26,13 @@ func TestTeamStore(t *testing.T, ss store.Store) {
t.Run("SearchByName", func(t *testing.T) { testTeamStoreSearchByName(t, ss) })
t.Run("SearchAll", func(t *testing.T) { testTeamStoreSearchAll(t, ss) })
t.Run("SearchOpen", func(t *testing.T) { testTeamStoreSearchOpen(t, ss) })
t.Run("SearchPrivate", func(t *testing.T) { testTeamStoreSearchPrivate(t, ss) })
t.Run("GetByIniviteId", func(t *testing.T) { testTeamStoreGetByIniviteId(t, ss) })
t.Run("ByUserId", func(t *testing.T) { testTeamStoreByUserId(t, ss) })
t.Run("GetAllTeamListing", func(t *testing.T) { testGetAllTeamListing(t, ss) })
t.Run("GetAllTeamPageListing", func(t *testing.T) { testGetAllTeamPageListing(t, ss) })
t.Run("GetAllPrivateTeamListing", func(t *testing.T) { testGetAllPrivateTeamListing(t, ss) })
t.Run("GetAllPrivateTeamPageListing", func(t *testing.T) { testGetAllPrivateTeamPageListing(t, ss) })
t.Run("Delete", func(t *testing.T) { testDelete(t, ss) })
t.Run("TeamCount", func(t *testing.T) { testTeamCount(t, ss) })
t.Run("TeamMembers", func(t *testing.T) { testTeamMembers(t, ss) })
@@ -185,143 +188,200 @@ func testTeamStoreSearchByName(t *testing.T, ss store.Store) {
}
func testTeamStoreSearchAll(t *testing.T, ss store.Store) {
o1 := model.Team{}
o1.DisplayName = "ADisplayName" + model.NewId()
o1.Name = "zz" + model.NewId() + "a"
o1.Email = MakeEmail()
o1.Type = model.TEAM_OPEN
o := model.Team{}
o.DisplayName = "ADisplayName" + model.NewId()
o.Name = "zzzzzz-" + model.NewId() + "a"
o.Email = MakeEmail()
o.Type = model.TEAM_OPEN
o.AllowOpenInvite = true
if err := (<-ss.Team().Save(&o1)).Err; err != nil {
t.Fatal(err)
require.Nil(t, (<-ss.Team().Save(&o)).Err)
p := model.Team{}
p.DisplayName = "ADisplayName" + model.NewId()
p.Name = "zzzzzz-" + model.NewId() + "a"
p.Email = MakeEmail()
p.Type = model.TEAM_OPEN
p.AllowOpenInvite = false
require.Nil(t, (<-ss.Team().Save(&p)).Err)
testCases := []struct {
Name string
Term string
ExpectedLenth int
ExpectedFirstId string
}{
{
"Search for open team name",
o.Name,
1,
o.Id,
},
{
"Search for open team displayName",
o.DisplayName,
1,
o.Id,
},
{
"Search for open team without results",
"junk",
0,
"",
},
{
"Search for private team",
p.DisplayName,
1,
p.Id,
},
{
"Search for both teams",
"zzzzzz",
2,
"",
},
}
p2 := model.Team{}
p2.DisplayName = "BDisplayName" + model.NewId()
p2.Name = "b" + model.NewId() + "b"
p2.Email = MakeEmail()
p2.Type = model.TEAM_INVITE
if err := (<-ss.Team().Save(&p2)).Err; err != nil {
t.Fatal(err)
}
r1 := <-ss.Team().SearchAll(o1.Name)
if r1.Err != nil {
t.Fatal(r1.Err)
}
if len(r1.Data.([]*model.Team)) != 1 {
t.Fatal("should have returned 1 team")
}
if r1.Data.([]*model.Team)[0].ToJson() != o1.ToJson() {
t.Fatal("invalid returned team")
}
r1 = <-ss.Team().SearchAll(p2.DisplayName)
if r1.Err != nil {
t.Fatal(r1.Err)
}
if len(r1.Data.([]*model.Team)) != 1 {
t.Fatal("should have returned 1 team")
}
if r1.Data.([]*model.Team)[0].ToJson() != p2.ToJson() {
t.Fatal("invalid returned team")
}
r1 = <-ss.Team().SearchAll("junk")
if r1.Err != nil {
t.Fatal(r1.Err)
}
if len(r1.Data.([]*model.Team)) != 0 {
t.Fatal("should have not returned a team")
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
r1 := <-ss.Team().SearchAll(tc.Term)
require.Nil(t, r1.Err)
results := r1.Data.([]*model.Team)
require.Equal(t, tc.ExpectedLenth, len(results))
if tc.ExpectedFirstId != "" {
assert.Equal(t, tc.ExpectedFirstId, results[0].Id)
}
})
}
}
func testTeamStoreSearchOpen(t *testing.T, ss store.Store) {
o1 := model.Team{}
o1.DisplayName = "ADisplayName" + model.NewId()
o1.Name = "zz" + model.NewId() + "a"
o1.Email = MakeEmail()
o1.Type = model.TEAM_OPEN
o1.AllowOpenInvite = true
o := model.Team{}
o.DisplayName = "ADisplayName" + model.NewId()
o.Name = "zz" + model.NewId() + "a"
o.Email = MakeEmail()
o.Type = model.TEAM_OPEN
o.AllowOpenInvite = true
if err := (<-ss.Team().Save(&o1)).Err; err != nil {
t.Fatal(err)
require.Nil(t, (<-ss.Team().Save(&o)).Err)
p := model.Team{}
p.DisplayName = "ADisplayName" + model.NewId()
p.Name = "zz" + model.NewId() + "a"
p.Email = MakeEmail()
p.Type = model.TEAM_OPEN
p.AllowOpenInvite = false
require.Nil(t, (<-ss.Team().Save(&p)).Err)
testCases := []struct {
Name string
Term string
ExpectedLenth int
ExpectedFirstId string
}{
{
"Search for open team name",
o.Name,
1,
o.Id,
},
{
"Search for open team displayName",
o.DisplayName,
1,
o.Id,
},
{
"Search for open team without results",
"junk",
0,
"",
},
{
"Search for a private team (expected no results)",
p.DisplayName,
0,
"",
},
}
o2 := model.Team{}
o2.DisplayName = "ADisplayName" + model.NewId()
o2.Name = "zz" + model.NewId() + "a"
o2.Email = MakeEmail()
o2.Type = model.TEAM_OPEN
o2.AllowOpenInvite = false
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
r1 := <-ss.Team().SearchOpen(tc.Term)
require.Nil(t, r1.Err)
results := r1.Data.([]*model.Team)
require.Equal(t, tc.ExpectedLenth, len(results))
if tc.ExpectedFirstId != "" {
assert.Equal(t, tc.ExpectedFirstId, results[0].Id)
}
})
}
}
if err := (<-ss.Team().Save(&o2)).Err; err != nil {
t.Fatal(err)
func testTeamStoreSearchPrivate(t *testing.T, ss store.Store) {
o := model.Team{}
o.DisplayName = "ADisplayName" + model.NewId()
o.Name = "zz" + model.NewId() + "a"
o.Email = MakeEmail()
o.Type = model.TEAM_OPEN
o.AllowOpenInvite = true
require.Nil(t, (<-ss.Team().Save(&o)).Err)
p := model.Team{}
p.DisplayName = "ADisplayName" + model.NewId()
p.Name = "zz" + model.NewId() + "a"
p.Email = MakeEmail()
p.Type = model.TEAM_OPEN
p.AllowOpenInvite = false
require.Nil(t, (<-ss.Team().Save(&p)).Err)
testCases := []struct {
Name string
Term string
ExpectedLenth int
ExpectedFirstId string
}{
{
"Search for private team name",
p.Name,
1,
p.Id,
},
{
"Search for private team displayName",
p.DisplayName,
1,
p.Id,
},
{
"Search for private team without results",
"junk",
0,
"",
},
{
"Search for a open team (expected no results)",
o.DisplayName,
0,
"",
},
}
p2 := model.Team{}
p2.DisplayName = "BDisplayName" + model.NewId()
p2.Name = "b" + model.NewId() + "b"
p2.Email = MakeEmail()
p2.Type = model.TEAM_INVITE
p2.AllowOpenInvite = true
if err := (<-ss.Team().Save(&p2)).Err; err != nil {
t.Fatal(err)
}
r1 := <-ss.Team().SearchOpen(o1.Name)
if r1.Err != nil {
t.Fatal(r1.Err)
}
if len(r1.Data.([]*model.Team)) != 1 {
t.Fatal("should have returned 1 team")
}
if r1.Data.([]*model.Team)[0].ToJson() != o1.ToJson() {
t.Fatal("invalid returned team")
}
r1 = <-ss.Team().SearchOpen(o1.DisplayName)
if r1.Err != nil {
t.Fatal(r1.Err)
}
if len(r1.Data.([]*model.Team)) != 1 {
t.Fatal("should have returned 1 team")
}
if r1.Data.([]*model.Team)[0].ToJson() != o1.ToJson() {
t.Fatal("invalid returned team")
}
r1 = <-ss.Team().SearchOpen(p2.Name)
if r1.Err != nil {
t.Fatal(r1.Err)
}
if len(r1.Data.([]*model.Team)) != 0 {
t.Fatal("should have not returned a team")
}
r1 = <-ss.Team().SearchOpen(p2.DisplayName)
if r1.Err != nil {
t.Fatal(r1.Err)
}
if len(r1.Data.([]*model.Team)) != 0 {
t.Fatal("should have not returned a team")
}
r1 = <-ss.Team().SearchOpen("junk")
if r1.Err != nil {
t.Fatal(r1.Err)
}
if len(r1.Data.([]*model.Team)) != 0 {
t.Fatal("should have not returned a team")
}
r1 = <-ss.Team().SearchOpen(o2.DisplayName)
if r1.Err != nil {
t.Fatal(r1.Err)
}
if len(r1.Data.([]*model.Team)) != 0 {
t.Fatal("should have not returned a team")
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
r1 := <-ss.Team().SearchPrivate(tc.Term)
require.Nil(t, r1.Err)
results := r1.Data.([]*model.Team)
require.Equal(t, tc.ExpectedLenth, len(results))
if tc.ExpectedFirstId != "" {
assert.Equal(t, tc.ExpectedFirstId, results[0].Id)
}
})
}
}
@@ -536,6 +596,144 @@ func testGetAllTeamPageListing(t *testing.T, ss store.Store) {
}
}
func testGetAllPrivateTeamListing(t *testing.T, ss store.Store) {
o1 := model.Team{}
o1.DisplayName = "DisplayName"
o1.Name = "z-z-z" + model.NewId() + "b"
o1.Email = MakeEmail()
o1.Type = model.TEAM_OPEN
o1.AllowOpenInvite = true
store.Must(ss.Team().Save(&o1))
o2 := model.Team{}
o2.DisplayName = "DisplayName"
o2.Name = "zz" + model.NewId() + "b"
o2.Email = MakeEmail()
o2.Type = model.TEAM_OPEN
store.Must(ss.Team().Save(&o2))
o3 := model.Team{}
o3.DisplayName = "DisplayName"
o3.Name = "z-z-z" + model.NewId() + "b"
o3.Email = MakeEmail()
o3.Type = model.TEAM_INVITE
o3.AllowOpenInvite = true
store.Must(ss.Team().Save(&o3))
o4 := model.Team{}
o4.DisplayName = "DisplayName"
o4.Name = "zz" + model.NewId() + "b"
o4.Email = MakeEmail()
o4.Type = model.TEAM_INVITE
store.Must(ss.Team().Save(&o4))
if r1 := <-ss.Team().GetAllPrivateTeamListing(); r1.Err != nil {
t.Fatal(r1.Err)
} else {
teams := r1.Data.([]*model.Team)
for _, team := range teams {
if team.AllowOpenInvite {
t.Fatal("should have returned team with AllowOpenInvite as false")
}
}
if len(teams) == 0 {
t.Fatal("failed team listing")
}
}
}
func testGetAllPrivateTeamPageListing(t *testing.T, ss store.Store) {
o1 := model.Team{}
o1.DisplayName = "DisplayName"
o1.Name = "z-z-z" + model.NewId() + "b"
o1.Email = MakeEmail()
o1.Type = model.TEAM_OPEN
o1.AllowOpenInvite = true
store.Must(ss.Team().Save(&o1))
o2 := model.Team{}
o2.DisplayName = "DisplayName"
o2.Name = "zz" + model.NewId() + "b"
o2.Email = MakeEmail()
o2.Type = model.TEAM_OPEN
o2.AllowOpenInvite = false
store.Must(ss.Team().Save(&o2))
o3 := model.Team{}
o3.DisplayName = "DisplayName"
o3.Name = "z-z-z" + model.NewId() + "b"
o3.Email = MakeEmail()
o3.Type = model.TEAM_INVITE
o3.AllowOpenInvite = true
store.Must(ss.Team().Save(&o3))
o4 := model.Team{}
o4.DisplayName = "DisplayName"
o4.Name = "zz" + model.NewId() + "b"
o4.Email = MakeEmail()
o4.Type = model.TEAM_INVITE
o4.AllowOpenInvite = false
store.Must(ss.Team().Save(&o4))
if r1 := <-ss.Team().GetAllPrivateTeamPageListing(0, 10); r1.Err != nil {
t.Fatal(r1.Err)
} else {
teams := r1.Data.([]*model.Team)
for _, team := range teams {
if team.AllowOpenInvite {
t.Fatal("should have returned team with AllowOpenInvite as false")
}
}
if len(teams) > 10 {
t.Fatal("should have returned max of 10 teams")
}
}
o5 := model.Team{}
o5.DisplayName = "DisplayName"
o5.Name = "z-z-z" + model.NewId() + "b"
o5.Email = MakeEmail()
o5.Type = model.TEAM_OPEN
o5.AllowOpenInvite = true
store.Must(ss.Team().Save(&o5))
if r1 := <-ss.Team().GetAllPrivateTeamPageListing(0, 4); r1.Err != nil {
t.Fatal(r1.Err)
} else {
teams := r1.Data.([]*model.Team)
for _, team := range teams {
if team.AllowOpenInvite {
t.Fatal("should have returned team with AllowOpenInvite as false")
}
}
if len(teams) > 4 {
t.Fatal("should have returned max of 4 teams")
}
}
if r1 := <-ss.Team().GetAllPrivateTeamPageListing(1, 1); r1.Err != nil {
t.Fatal(r1.Err)
} else {
teams := r1.Data.([]*model.Team)
for _, team := range teams {
if team.AllowOpenInvite {
t.Fatal("should have returned team with AllowOpenInvite as false")
}
}
if len(teams) > 1 {
t.Fatal("should have returned max of 1 team")
}
}
}
func testDelete(t *testing.T, ss store.Store) {
o1 := model.Team{}
o1.DisplayName = "DisplayName"