[MM-17194] Add search channels for user in case the user doesn't have the list channels permission (#11706)

* [MM-17194] Add search channels for user in case the user doesn't have the list channels permission

* Add tests for the API layer and control the case of a user not being a team member

* Remove unnecessary empty line
Этот коммит содержится в:
Miguel de la Cruz
2019-07-29 09:35:32 +02:00
коммит произвёл Jesús Espino
родитель 1fa505c833
Коммит 8858b15e7e
8 изменённых файлов: 271 добавлений и 5 удалений

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

@@ -746,12 +746,20 @@ func searchChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if !c.App.SessionHasPermissionToTeam(c.App.Session, c.Params.TeamId, model.PERMISSION_LIST_TEAM_CHANNELS) {
c.SetPermissionError(model.PERMISSION_LIST_TEAM_CHANNELS)
return
var channels *model.ChannelList
var err *model.AppError
if c.App.SessionHasPermissionToTeam(c.App.Session, c.Params.TeamId, model.PERMISSION_LIST_TEAM_CHANNELS) {
channels, err = c.App.SearchChannels(c.Params.TeamId, props.Term)
} else {
// If the user is not a team member, return a 404
if _, err = c.App.GetTeamMember(c.Params.TeamId, c.App.Session.UserId); err != nil {
c.Err = err
return
}
channels, err = c.App.SearchChannelsForUser(c.App.Session.UserId, c.Params.TeamId, props.Term)
}
channels, err := c.App.SearchChannels(c.Params.TeamId, props.Term)
if err != nil {
c.Err = err
return

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

@@ -909,13 +909,48 @@ func TestSearchChannels(t *testing.T) {
search.Term = th.BasicChannel.Name
_, resp = Client.SearchChannels(model.NewId(), search)
CheckForbiddenStatus(t, resp)
CheckNotFoundStatus(t, resp)
_, resp = Client.SearchChannels("junk", search)
CheckBadRequestStatus(t, resp)
_, resp = th.SystemAdminClient.SearchChannels(th.BasicTeam.Id, search)
CheckNoError(t, resp)
// Check the appropriate permissions are enforced.
defaultRolePermissions := th.SaveDefaultRolePermissions()
defer func() {
th.RestoreDefaultRolePermissions(defaultRolePermissions)
}()
// Remove list channels permission from the user
th.RemovePermissionFromRole(model.PERMISSION_LIST_TEAM_CHANNELS.Id, model.TEAM_USER_ROLE_ID)
t.Run("Search for a BasicChannel, which the user is a member of", func(t *testing.T) {
search.Term = th.BasicChannel.Name
channelList, resp := Client.SearchChannels(th.BasicTeam.Id, search)
CheckNoError(t, resp)
channelNames := []string{}
for _, c := range channelList {
channelNames = append(channelNames, c.Name)
}
require.Contains(t, channelNames, th.BasicChannel.Name)
})
t.Run("Remove the user from BasicChannel and search again, should not be returned", func(t *testing.T) {
th.App.RemoveUserFromChannel(th.BasicUser.Id, th.BasicUser.Id, th.BasicChannel)
search.Term = th.BasicChannel.Name
channelList, resp := Client.SearchChannels(th.BasicTeam.Id, search)
CheckNoError(t, resp)
channelNames := []string{}
for _, c := range channelList {
channelNames = append(channelNames, c.Name)
}
require.NotContains(t, channelNames, th.BasicChannel.Name)
})
}
func TestSearchAllChannels(t *testing.T) {

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

@@ -1823,6 +1823,14 @@ func (a *App) SearchChannels(teamId string, term string) (*model.ChannelList, *m
return a.Srv.Store.Channel().SearchInTeam(teamId, term, includeDeleted)
}
func (a *App) SearchChannelsForUser(userId, teamId, term string) (*model.ChannelList, *model.AppError) {
includeDeleted := *a.Config().TeamSettings.ExperimentalViewArchivedChannels
term = strings.TrimSpace(term)
return a.Srv.Store.Channel().SearchForUserInTeam(userId, teamId, term, includeDeleted)
}
func (a *App) SearchGroupChannels(userId, term string) (*model.ChannelList, *model.AppError) {
if term == "" {
return &model.ChannelList{}, nil

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

@@ -988,3 +988,56 @@ func TestDefaultChannelNames(t *testing.T) {
expect = []string{"town-square", "foo", "bar"}
require.ElementsMatch(t, expect, actual)
}
func TestSearchChannelsForUser(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
c1, err := th.App.CreateChannel(&model.Channel{DisplayName: "test-dev-1", Name: "test-dev-1", Type: model.CHANNEL_OPEN, TeamId: th.BasicTeam.Id}, false)
require.Nil(t, err)
c2, err := th.App.CreateChannel(&model.Channel{DisplayName: "test-dev-2", Name: "test-dev-2", Type: model.CHANNEL_OPEN, TeamId: th.BasicTeam.Id}, false)
require.Nil(t, err)
c3, err := th.App.CreateChannel(&model.Channel{DisplayName: "dev-3", Name: "dev-3", Type: model.CHANNEL_OPEN, TeamId: th.BasicTeam.Id}, false)
require.Nil(t, err)
defer func() {
th.App.PermanentDeleteChannel(c1)
th.App.PermanentDeleteChannel(c2)
th.App.PermanentDeleteChannel(c3)
}()
// add user to test-dev-1 and dev3
_, err = th.App.AddUserToChannel(th.BasicUser, c1)
require.Nil(t, err)
_, err = th.App.AddUserToChannel(th.BasicUser, c3)
require.Nil(t, err)
searchAndCheck := func(t *testing.T, term string, expectedDisplayNames []string) {
res, searchErr := th.App.SearchChannelsForUser(th.BasicUser.Id, th.BasicTeam.Id, term)
require.Nil(t, searchErr)
require.Len(t, *res, len(expectedDisplayNames))
resultDisplayNames := []string{}
for _, c := range *res {
resultDisplayNames = append(resultDisplayNames, c.Name)
}
require.ElementsMatch(t, expectedDisplayNames, resultDisplayNames)
}
t.Run("Search for test, only test-dev-1 should be returned", func(t *testing.T) {
searchAndCheck(t, "test", []string{"test-dev-1"})
})
t.Run("Search for dev, both test-dev-1 and dev-3 should be returned", func(t *testing.T) {
searchAndCheck(t, "dev", []string{"test-dev-1", "dev-3"})
})
t.Run("After adding user to test-dev-2, search for dev, the three channels should be returned", func(t *testing.T) {
_, err = th.App.AddUserToChannel(th.BasicUser, c2)
require.Nil(t, err)
searchAndCheck(t, "dev", []string{"test-dev-1", "test-dev-2", "dev-3"})
})
}

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

@@ -2121,6 +2121,35 @@ func (s SqlChannelStore) SearchInTeam(teamId string, term string, includeDeleted
})
}
func (s SqlChannelStore) SearchForUserInTeam(userId string, teamId string, term string, includeDeleted bool) (*model.ChannelList, *model.AppError) {
deleteFilter := "AND c.DeleteAt = 0"
if includeDeleted {
deleteFilter = ""
}
return s.performSearch(`
SELECT
Channels.*
FROM
Channels
JOIN
PublicChannels c ON (c.Id = Channels.Id)
JOIN
ChannelMembers cm ON (c.Id = cm.ChannelId)
WHERE
c.TeamId = :TeamId
AND
cm.UserId = :UserId
`+deleteFilter+`
SEARCH_CLAUSE
ORDER BY c.DisplayName
LIMIT 100
`, term, map[string]interface{}{
"TeamId": teamId,
"UserId": userId,
})
}
func (s SqlChannelStore) SearchAllChannels(term string, opts store.ChannelSearchOpts) (*model.ChannelListWithTeamData, *model.AppError) {
query := s.getQueryBuilder().
Select("c.*, t.DisplayName AS TeamDisplayName, t.Name AS TeamName, t.UpdateAt as TeamUpdateAt").

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

@@ -188,6 +188,7 @@ type ChannelStore interface {
AutocompleteInTeamForSearch(teamId string, userId string, term string, includeDeleted bool) (*model.ChannelList, *model.AppError)
SearchAllChannels(term string, opts ChannelSearchOpts) (*model.ChannelListWithTeamData, *model.AppError)
SearchInTeam(teamId string, term string, includeDeleted bool) (*model.ChannelList, *model.AppError)
SearchForUserInTeam(userId string, teamId string, term string, includeDeleted bool) (*model.ChannelList, *model.AppError)
SearchMore(userId string, teamId string, term string) (*model.ChannelList, *model.AppError)
SearchGroupChannels(userId, term string) (*model.ChannelList, *model.AppError)
GetMembersByIds(channelId string, userIds []string) (*model.ChannelMembers, *model.AppError)

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

@@ -68,6 +68,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
t.Run("GetGuestCount", func(t *testing.T) { testGetGuestCount(t, ss) })
t.Run("SearchMore", func(t *testing.T) { testChannelStoreSearchMore(t, ss) })
t.Run("SearchInTeam", func(t *testing.T) { testChannelStoreSearchInTeam(t, ss) })
t.Run("SearchForUserInTeam", func(t *testing.T) { testChannelStoreSearchForUserInTeam(t, ss) })
t.Run("SearchAllChannels", func(t *testing.T) { testChannelStoreSearchAllChannels(t, ss) })
t.Run("AutocompleteInTeamForSearch", func(t *testing.T) { testChannelStoreAutocompleteInTeamForSearch(t, ss, s) })
t.Run("GetMembersByIds", func(t *testing.T) { testChannelStoreGetMembersByIds(t, ss) })
@@ -2568,6 +2569,112 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) {
}
}
func testChannelStoreSearchForUserInTeam(t *testing.T, ss store.Store) {
userId := model.NewId()
teamId := model.NewId()
otherTeamId := model.NewId()
// create 4 channels for the same team and one for other team
o1 := model.Channel{
TeamId: teamId,
DisplayName: "test-dev-1",
Name: "zz" + model.NewId() + "b",
Type: model.CHANNEL_OPEN,
}
_, err := ss.Channel().Save(&o1, -1)
require.Nil(t, err)
o2 := model.Channel{
TeamId: teamId,
DisplayName: "test-dev-2",
Name: "zz" + model.NewId() + "b",
Type: model.CHANNEL_OPEN,
}
_, err = ss.Channel().Save(&o2, -1)
require.Nil(t, err)
o3 := model.Channel{
TeamId: teamId,
DisplayName: "dev-3",
Name: "zz" + model.NewId() + "b",
Type: model.CHANNEL_OPEN,
}
_, err = ss.Channel().Save(&o3, -1)
require.Nil(t, err)
o4 := model.Channel{
TeamId: teamId,
DisplayName: "dev-4",
Name: "zz" + model.NewId() + "b",
Type: model.CHANNEL_OPEN,
}
_, err = ss.Channel().Save(&o4, -1)
require.Nil(t, err)
o5 := model.Channel{
TeamId: otherTeamId,
DisplayName: "other-team-dev-5",
Name: "zz" + model.NewId() + "b",
Type: model.CHANNEL_OPEN,
}
_, err = ss.Channel().Save(&o5, -1)
require.Nil(t, err)
// add the user to the first 3 channels and the other team channel
for _, c := range []model.Channel{o1, o2, o3, o5} {
_, err = ss.Channel().SaveMember(&model.ChannelMember{
ChannelId: c.Id,
UserId: userId,
NotifyProps: model.GetDefaultChannelNotifyProps(),
})
require.Nil(t, err)
}
searchAndCheck := func(t *testing.T, term string, includeDeleted bool, expectedDisplayNames []string) {
res, searchErr := ss.Channel().SearchForUserInTeam(userId, teamId, term, includeDeleted)
require.Nil(t, searchErr)
require.Len(t, *res, len(expectedDisplayNames))
resultDisplayNames := []string{}
for _, c := range *res {
resultDisplayNames = append(resultDisplayNames, c.DisplayName)
}
require.ElementsMatch(t, expectedDisplayNames, resultDisplayNames)
}
t.Run("Search for test, get channels 1 and 2", func(t *testing.T) {
searchAndCheck(t, "test", false, []string{o1.DisplayName, o2.DisplayName})
})
t.Run("Search for dev, get channels 1, 2 and 3", func(t *testing.T) {
searchAndCheck(t, "dev", false, []string{o1.DisplayName, o2.DisplayName, o3.DisplayName})
})
t.Run("After adding user to channel 4, search for dev, get channels 1, 2, 3 and 4", func(t *testing.T) {
_, err = ss.Channel().SaveMember(&model.ChannelMember{
ChannelId: o4.Id,
UserId: userId,
NotifyProps: model.GetDefaultChannelNotifyProps(),
})
require.Nil(t, err)
searchAndCheck(t, "dev", false, []string{o1.DisplayName, o2.DisplayName, o3.DisplayName, o4.DisplayName})
})
t.Run("Mark channel 1 as deleted, search for dev, get channels 2, 3 and 4", func(t *testing.T) {
o1.DeleteAt = model.GetMillis()
o1.UpdateAt = o1.DeleteAt
err = ss.Channel().Delete(o1.Id, o1.DeleteAt)
require.Nil(t, err)
searchAndCheck(t, "dev", false, []string{o2.DisplayName, o3.DisplayName, o4.DisplayName})
})
t.Run("With includeDeleted, search for dev, get channels 1, 2, 3 and 4", func(t *testing.T) {
searchAndCheck(t, "dev", true, []string{o1.DisplayName, o2.DisplayName, o3.DisplayName, o4.DisplayName})
})
}
func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) {
cleanupChannels(t, ss)

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

@@ -1420,6 +1420,31 @@ func (_m *ChannelStore) SearchAllChannels(term string, opts store.ChannelSearchO
return r0, r1
}
// SearchForUserInTeam provides a mock function with given fields: userId, teamId, term, includeDeleted
func (_m *ChannelStore) SearchForUserInTeam(userId string, teamId string, term string, includeDeleted bool) (*model.ChannelList, *model.AppError) {
ret := _m.Called(userId, teamId, term, includeDeleted)
var r0 *model.ChannelList
if rf, ok := ret.Get(0).(func(string, string, string, bool) *model.ChannelList); ok {
r0 = rf(userId, teamId, term, includeDeleted)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.ChannelList)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string, string, string, bool) *model.AppError); ok {
r1 = rf(userId, teamId, term, includeDeleted)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// SearchGroupChannels provides a mock function with given fields: userId, term
func (_m *ChannelStore) SearchGroupChannels(userId string, term string) (*model.ChannelList, *model.AppError) {
ret := _m.Called(userId, term)