diff --git a/server/channels/api4/channel.go b/server/channels/api4/channel.go index fbcf8fdab7..7fc75dd526 100644 --- a/server/channels/api4/channel.go +++ b/server/channels/api4/channel.go @@ -897,7 +897,8 @@ func getDeletedChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Reques return } - channels, err := c.App.GetDeletedChannels(c.AppContext, c.Params.TeamId, c.Params.Page*c.Params.PerPage, c.Params.PerPage, c.AppContext.Session().UserId) + skipTeamMembershipCheck := c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) + channels, err := c.App.GetDeletedChannels(c.AppContext, c.Params.TeamId, c.Params.Page*c.Params.PerPage, c.Params.PerPage, c.AppContext.Session().UserId, skipTeamMembershipCheck) if err != nil { c.Err = err return diff --git a/server/channels/api4/channel_test.go b/server/channels/api4/channel_test.go index 51527784a3..bdd1a205ad 100644 --- a/server/channels/api4/channel_test.go +++ b/server/channels/api4/channel_test.go @@ -959,7 +959,8 @@ func TestGetDeletedChannelsForTeam(t *testing.T) { th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { channels, _, err = client.GetDeletedChannelsForTeam(context.Background(), team.Id, 0, 100, "") require.NoError(t, err) - require.Len(t, channels, numInitialChannelsForTeam+2) + // Local admin should see private archived channels + require.Len(t, channels, numInitialChannelsForTeam+4) }) channels, _, err = client.GetDeletedChannelsForTeam(context.Background(), team.Id, 0, 1, "") diff --git a/server/channels/app/app_iface.go b/server/channels/app/app_iface.go index 6cf92b035d..18abd21242 100644 --- a/server/channels/app/app_iface.go +++ b/server/channels/app/app_iface.go @@ -685,7 +685,7 @@ type AppIface interface { GetCookieDomain() string GetCustomStatus(userID string) (*model.CustomStatus, *model.AppError) GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError) - GetDeletedChannels(c request.CTX, teamID string, offset int, limit int, userID string) (model.ChannelList, *model.AppError) + GetDeletedChannels(c request.CTX, teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, *model.AppError) GetDraft(userID, channelID, rootID string) (*model.Draft, *model.AppError) GetDraftsForUser(rctx request.CTX, userID, teamID string) ([]*model.Draft, *model.AppError) GetEditHistoryForPost(postID string) ([]*model.Post, *model.AppError) diff --git a/server/channels/app/channel.go b/server/channels/app/channel.go index 0d5186e18b..43023cfa8e 100644 --- a/server/channels/app/channel.go +++ b/server/channels/app/channel.go @@ -2019,8 +2019,8 @@ func (a *App) GetAllChannelsCount(c request.CTX, opts model.ChannelSearchOpts) ( return count, nil } -func (a *App) GetDeletedChannels(c request.CTX, teamID string, offset int, limit int, userID string) (model.ChannelList, *model.AppError) { - list, err := a.Srv().Store().Channel().GetDeleted(teamID, offset, limit, userID) +func (a *App) GetDeletedChannels(c request.CTX, teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, *model.AppError) { + list, err := a.Srv().Store().Channel().GetDeleted(teamID, offset, limit, userID, skipTeamMembershipCheck) if err != nil { var nfErr *store.ErrNotFound switch { diff --git a/server/channels/app/opentracing/opentracing_layer.go b/server/channels/app/opentracing/opentracing_layer.go index ee3e66c70b..b66a26edb0 100644 --- a/server/channels/app/opentracing/opentracing_layer.go +++ b/server/channels/app/opentracing/opentracing_layer.go @@ -6379,7 +6379,7 @@ func (a *OpenTracingAppLayer) GetDefaultProfileImage(user *model.User) ([]byte, return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) GetDeletedChannels(c request.CTX, teamID string, offset int, limit int, userID string) (model.ChannelList, *model.AppError) { +func (a *OpenTracingAppLayer) GetDeletedChannels(c request.CTX, teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetDeletedChannels") @@ -6391,7 +6391,7 @@ func (a *OpenTracingAppLayer) GetDeletedChannels(c request.CTX, teamID string, o }() defer span.Finish() - resultVar0, resultVar1 := a.app.GetDeletedChannels(c, teamID, offset, limit, userID) + resultVar0, resultVar1 := a.app.GetDeletedChannels(c, teamID, offset, limit, userID, skipTeamMembershipCheck) if resultVar1 != nil { span.LogFields(spanlog.Error(resultVar1)) diff --git a/server/channels/store/opentracinglayer/opentracinglayer.go b/server/channels/store/opentracinglayer/opentracinglayer.go index 8748c0648f..bcdf1520af 100644 --- a/server/channels/store/opentracinglayer/opentracinglayer.go +++ b/server/channels/store/opentracinglayer/opentracinglayer.go @@ -1425,7 +1425,7 @@ func (s *OpenTracingLayerChannelStore) GetChannelsWithUnreadsAndWithMentions(ctx return result, resultVar1, resultVar2, err } -func (s *OpenTracingLayerChannelStore) GetDeleted(teamID string, offset int, limit int, userID string) (model.ChannelList, error) { +func (s *OpenTracingLayerChannelStore) GetDeleted(teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetDeleted") s.Root.Store.SetContext(newCtx) @@ -1434,7 +1434,7 @@ func (s *OpenTracingLayerChannelStore) GetDeleted(teamID string, offset int, lim }() defer span.Finish() - result, err := s.ChannelStore.GetDeleted(teamID, offset, limit, userID) + result, err := s.ChannelStore.GetDeleted(teamID, offset, limit, userID, skipTeamMembershipCheck) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) diff --git a/server/channels/store/retrylayer/retrylayer.go b/server/channels/store/retrylayer/retrylayer.go index bf8fa85b48..d80e93b45e 100644 --- a/server/channels/store/retrylayer/retrylayer.go +++ b/server/channels/store/retrylayer/retrylayer.go @@ -1584,11 +1584,11 @@ func (s *RetryLayerChannelStore) GetChannelsWithUnreadsAndWithMentions(ctx conte } -func (s *RetryLayerChannelStore) GetDeleted(teamID string, offset int, limit int, userID string) (model.ChannelList, error) { +func (s *RetryLayerChannelStore) GetDeleted(teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, error) { tries := 0 for { - result, err := s.ChannelStore.GetDeleted(teamID, offset, limit, userID) + result, err := s.ChannelStore.GetDeleted(teamID, offset, limit, userID, skipTeamMembershipCheck) if err == nil { return result, nil } diff --git a/server/channels/store/sqlstore/channel_store.go b/server/channels/store/sqlstore/channel_store.go index 2861fbde55..4d92a718f3 100644 --- a/server/channels/store/sqlstore/channel_store.go +++ b/server/channels/store/sqlstore/channel_store.go @@ -1490,24 +1490,37 @@ func (s SqlChannelStore) GetDeletedByName(teamId string, name string) (*model.Ch return &channel, nil } -func (s SqlChannelStore) GetDeleted(teamId string, offset int, limit int, userId string) (model.ChannelList, error) { +func (s SqlChannelStore) GetDeleted(teamId string, offset int, limit int, userId string, skipTeamMembershipCheck bool) (model.ChannelList, error) { channels := model.ChannelList{} - query := ` - SELECT * FROM Channels - WHERE (TeamId = ? OR TeamId = '') - AND DeleteAt != 0 - AND Type != ? - UNION - SELECT * FROM Channels - WHERE (TeamId = ? OR TeamId = '') - AND DeleteAt != 0 - AND Type = ? - AND Id IN (SELECT ChannelId FROM ChannelMembers WHERE UserId = ?) - ORDER BY DisplayName LIMIT ? OFFSET ? - ` + builder := s.getQueryBuilder(). + Select("*"). + From("Channels"). + Where(sq.Or{ + sq.Eq{"TeamId": teamId}, + sq.Eq{"TeamId": ""}, + }). + Where(sq.NotEq{"DeleteAt": 0}). + OrderBy("DisplayName"). + Limit(uint64(limit)). + Offset(uint64(offset)) - if err := s.GetReplicaX().Select(&channels, query, teamId, model.ChannelTypePrivate, teamId, model.ChannelTypePrivate, userId, limit, offset); err != nil { + if !skipTeamMembershipCheck { + builder = builder.Where(sq.Or{ + sq.NotEq{"Type": model.ChannelTypePrivate}, + sq.And{ + sq.Eq{"Type": model.ChannelTypePrivate}, + sq.Expr("Id IN (?)", sq.Select("ChannelId").From("ChannelMembers").Where(sq.Eq{"UserId": userId})), + }, + }) + } + + query, args, err := builder.ToSql() + if err != nil { + return nil, errors.Wrapf(err, "GetDeleted_ToSql") + } + + if err := s.GetReplicaX().Select(&channels, query, args...); err != nil { if err == sql.ErrNoRows { return nil, store.NewErrNotFound("Channel", fmt.Sprintf("TeamId=%s,UserId=%s", teamId, userId)) } diff --git a/server/channels/store/store.go b/server/channels/store/store.go index 592e815d4f..08c59f9129 100644 --- a/server/channels/store/store.go +++ b/server/channels/store/store.go @@ -203,7 +203,7 @@ type ChannelStore interface { GetByNameIncludeDeleted(teamID string, name string, allowFromCache bool) (*model.Channel, error) GetByNamesIncludeDeleted(teamID string, names []string, allowFromCache bool) ([]*model.Channel, error) GetDeletedByName(teamID string, name string) (*model.Channel, error) - GetDeleted(teamID string, offset int, limit int, userID string) (model.ChannelList, error) + GetDeleted(teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, error) GetChannels(teamID, userID string, opts *model.ChannelSearchOpts) (model.ChannelList, error) GetChannelsByUser(userID string, includeDeleted bool, lastDeleteAt, pageSize int, fromChannelID string) (model.ChannelList, error) GetAllChannelMemberIdsByChannelId(id string) ([]string, error) diff --git a/server/channels/store/storetest/channel_store.go b/server/channels/store/storetest/channel_store.go index c21ace883b..be56f28c36 100644 --- a/server/channels/store/storetest/channel_store.go +++ b/server/channels/store/storetest/channel_store.go @@ -992,60 +992,79 @@ func testChannelStoreGetDeletedByName(t *testing.T, rctx request.CTX, ss store.S } func testChannelStoreGetDeleted(t *testing.T, rctx request.CTX, ss store.Store) { - o1 := model.Channel{} - o1.TeamId = model.NewId() - o1.DisplayName = "Channel1" - o1.Name = NewTestID() - o1.Type = model.ChannelTypeOpen - - userID := model.NewId() + teamID := model.NewId() + o1 := model.Channel{ + TeamId: teamID, + DisplayName: "Channel1", + Name: NewTestID(), + Type: model.ChannelTypeOpen, + } _, nErr := ss.Channel().Save(rctx, &o1, -1) require.NoError(t, nErr) err := ss.Channel().Delete(o1.Id, model.GetMillis()) require.NoError(t, err, "channel should have been deleted") - list, nErr := ss.Channel().GetDeleted(o1.TeamId, 0, 100, userID) + userID := model.NewId() + + list, nErr := ss.Channel().GetDeleted(teamID, 0, 100, userID, false) require.NoError(t, nErr, nErr) require.Len(t, list, 1, "wrong list") require.Equal(t, o1.Name, list[0].Name, "missing channel") - o2 := model.Channel{} - o2.TeamId = o1.TeamId - o2.DisplayName = "Channel2" - o2.Name = NewTestID() - o2.Type = model.ChannelTypeOpen + o2 := model.Channel{ + TeamId: teamID, + DisplayName: "Channel2", + Name: NewTestID(), + Type: model.ChannelTypeOpen, + } _, nErr = ss.Channel().Save(rctx, &o2, -1) require.NoError(t, nErr) - list, nErr = ss.Channel().GetDeleted(o1.TeamId, 0, 100, userID) + list, nErr = ss.Channel().GetDeleted(teamID, 0, 100, userID, false) require.NoError(t, nErr, nErr) require.Len(t, list, 1, "wrong list") - o3 := model.Channel{} - o3.TeamId = o1.TeamId - o3.DisplayName = "Channel3" - o3.Name = NewTestID() - o3.Type = model.ChannelTypeOpen - + o3 := model.Channel{ + TeamId: teamID, + DisplayName: "Channel3", + Name: NewTestID(), + Type: model.ChannelTypeOpen, + } _, nErr = ss.Channel().Save(rctx, &o3, -1) require.NoError(t, nErr) + o4 := model.Channel{ + TeamId: teamID, + DisplayName: "Channel4", + Name: NewTestID(), + Type: model.ChannelTypePrivate, + } + _, nErr = ss.Channel().Save(rctx, &o4, -1) + require.NoError(t, nErr) + + err = ss.Channel().Delete(o4.Id, model.GetMillis()) + require.NoError(t, err, "channel should have been deleted") + err = ss.Channel().Delete(o3.Id, model.GetMillis()) require.NoError(t, err, "channel should have been deleted") - list, nErr = ss.Channel().GetDeleted(o1.TeamId, 0, 100, userID) + list, nErr = ss.Channel().GetDeleted(teamID, 0, 100, userID, false) require.NoError(t, nErr, nErr) require.Len(t, list, 2, "wrong list length") - list, nErr = ss.Channel().GetDeleted(o1.TeamId, 0, 1, userID) + list, nErr = ss.Channel().GetDeleted(teamID, 0, 1, userID, false) require.NoError(t, nErr, nErr) require.Len(t, list, 1, "wrong list length") - list, nErr = ss.Channel().GetDeleted(o1.TeamId, 1, 1, userID) + list, nErr = ss.Channel().GetDeleted(teamID, 1, 1, userID, false) require.NoError(t, nErr, nErr) require.Len(t, list, 1, "wrong list length") + + list, nErr = ss.Channel().GetDeleted(teamID, 0, 100, userID, true) + require.NoError(t, nErr, nErr) + require.Len(t, list, 3, "wrong list length") } func testChannelMemberStore(t *testing.T, rctx request.CTX, ss store.Store) { diff --git a/server/channels/store/storetest/mocks/ChannelStore.go b/server/channels/store/storetest/mocks/ChannelStore.go index 8b5911449f..f8eee02b89 100644 --- a/server/channels/store/storetest/mocks/ChannelStore.go +++ b/server/channels/store/storetest/mocks/ChannelStore.go @@ -1210,9 +1210,9 @@ func (_m *ChannelStore) GetChannelsWithUnreadsAndWithMentions(ctx context.Contex return r0, r1, r2, r3 } -// GetDeleted provides a mock function with given fields: teamID, offset, limit, userID -func (_m *ChannelStore) GetDeleted(teamID string, offset int, limit int, userID string) (model.ChannelList, error) { - ret := _m.Called(teamID, offset, limit, userID) +// GetDeleted provides a mock function with given fields: teamID, offset, limit, userID, skipTeamMembershipCheck +func (_m *ChannelStore) GetDeleted(teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, error) { + ret := _m.Called(teamID, offset, limit, userID, skipTeamMembershipCheck) if len(ret) == 0 { panic("no return value specified for GetDeleted") @@ -1220,19 +1220,19 @@ func (_m *ChannelStore) GetDeleted(teamID string, offset int, limit int, userID var r0 model.ChannelList var r1 error - if rf, ok := ret.Get(0).(func(string, int, int, string) (model.ChannelList, error)); ok { - return rf(teamID, offset, limit, userID) + if rf, ok := ret.Get(0).(func(string, int, int, string, bool) (model.ChannelList, error)); ok { + return rf(teamID, offset, limit, userID, skipTeamMembershipCheck) } - if rf, ok := ret.Get(0).(func(string, int, int, string) model.ChannelList); ok { - r0 = rf(teamID, offset, limit, userID) + if rf, ok := ret.Get(0).(func(string, int, int, string, bool) model.ChannelList); ok { + r0 = rf(teamID, offset, limit, userID, skipTeamMembershipCheck) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(model.ChannelList) } } - if rf, ok := ret.Get(1).(func(string, int, int, string) error); ok { - r1 = rf(teamID, offset, limit, userID) + if rf, ok := ret.Get(1).(func(string, int, int, string, bool) error); ok { + r1 = rf(teamID, offset, limit, userID, skipTeamMembershipCheck) } else { r1 = ret.Error(1) } diff --git a/server/channels/store/timerlayer/timerlayer.go b/server/channels/store/timerlayer/timerlayer.go index acfbf4f023..d8b473dea6 100644 --- a/server/channels/store/timerlayer/timerlayer.go +++ b/server/channels/store/timerlayer/timerlayer.go @@ -1327,10 +1327,10 @@ func (s *TimerLayerChannelStore) GetChannelsWithUnreadsAndWithMentions(ctx conte return result, resultVar1, resultVar2, err } -func (s *TimerLayerChannelStore) GetDeleted(teamID string, offset int, limit int, userID string) (model.ChannelList, error) { +func (s *TimerLayerChannelStore) GetDeleted(teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, error) { start := time.Now() - result, err := s.ChannelStore.GetDeleted(teamID, offset, limit, userID) + result, err := s.ChannelStore.GetDeleted(teamID, offset, limit, userID, skipTeamMembershipCheck) elapsed := float64(time.Since(start)) / float64(time.Second) if s.Root.Metrics != nil {