Files
mostlymatter/server/channels/api4/shared_channel_test.go
Miguel de la Cruz 30cb527cf6 Forbid users from creating DMs and GMs with remote users (#28855)
* Forbid users from creating DMs and GMs with remote users

This change prevents the application from creating new DMs and GMs
when remote users are involved, and prevents as well new posts from
being created into preexisting DMs and GMs with remote users created
in previous versions.

* Adds i18n

* Skip preexisting DM tests and create a new one for the new behavior
2024-10-18 11:16:29 -06:00

625 строки
20 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
"fmt"
"math/rand"
"net/http"
"sort"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
)
var (
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
)
func setupForSharedChannels(tb testing.TB) *TestHelper {
th := SetupConfig(tb, func(cfg *model.Config) {
*cfg.ConnectedWorkspacesSettings.EnableRemoteClusterService = true
*cfg.ConnectedWorkspacesSettings.EnableSharedChannels = true
})
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.SiteURL = fmt.Sprintf("http://localhost:%d", th.Server.ListenAddr.Port)
})
return th
}
func TestGetAllSharedChannels(t *testing.T) {
th := setupForSharedChannels(t).InitBasic()
defer th.TearDown()
const pages = 3
const pageSize = 7
savedIds := make([]string, 0, pages*pageSize)
// make some shared channels
for i := 0; i < pages*pageSize; i++ {
channel := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
sc := &model.SharedChannel{
ChannelId: channel.Id,
TeamId: channel.TeamId,
Home: randomBool(),
ShareName: fmt.Sprintf("test_share_%d", i),
CreatorId: th.BasicChannel.CreatorId,
RemoteId: model.NewId(),
}
_, err := th.App.ShareChannel(th.Context, sc)
require.NoError(t, err)
savedIds = append(savedIds, channel.Id)
}
sort.Strings(savedIds)
t.Run("get shared channels paginated", func(t *testing.T) {
channelIds := make([]string, 0, 21)
for i := 0; i < pages; i++ {
channels, _, err := th.Client.GetAllSharedChannels(context.Background(), th.BasicTeam.Id, i, pageSize)
require.NoError(t, err)
channelIds = append(channelIds, getIds(channels)...)
}
sort.Strings(channelIds)
// ids lists should now match
assert.Equal(t, savedIds, channelIds, "id lists should match")
})
t.Run("get shared channels for invalid team", func(t *testing.T) {
_, _, err := th.Client.GetAllSharedChannels(context.Background(), model.NewId(), 0, 100)
require.Error(t, err)
})
t.Run("get shared channels, user not member of team", func(t *testing.T) {
team := &model.Team{
DisplayName: "tteam",
Name: GenerateTestTeamName(),
Type: model.TeamOpen,
}
team, _, err := th.SystemAdminClient.CreateTeam(context.Background(), team)
require.NoError(t, err)
_, _, err = th.Client.GetAllSharedChannels(context.Background(), team.Id, 0, 100)
require.Error(t, err)
})
}
func getIds(channels []*model.SharedChannel) []string {
ids := make([]string, 0, len(channels))
for _, c := range channels {
ids = append(ids, c.ChannelId)
}
return ids
}
func randomBool() bool {
return rnd.Intn(2) != 0
}
func TestGetRemoteClusterById(t *testing.T) {
th := setupForSharedChannels(t).InitBasic()
defer th.TearDown()
// for this test we need a user that belongs to a channel that
// is shared with the requested remote id.
// create a remote cluster
rc := &model.RemoteCluster{
RemoteId: model.NewId(),
Name: "Test1",
SiteURL: model.NewId(),
CreatorId: model.NewId(),
}
rc, appErr := th.App.AddRemoteCluster(rc)
require.Nil(t, appErr)
// create a shared channel
sc := &model.SharedChannel{
ChannelId: th.BasicChannel.Id,
TeamId: th.BasicChannel.TeamId,
Home: false,
ShareName: "test_share",
CreatorId: th.BasicChannel.CreatorId,
RemoteId: rc.RemoteId,
}
sc, err := th.App.ShareChannel(th.Context, sc)
require.NoError(t, err)
// create a shared channel remote to connect them
scr := &model.SharedChannelRemote{
Id: model.NewId(),
ChannelId: sc.ChannelId,
CreatorId: sc.CreatorId,
IsInviteAccepted: true,
IsInviteConfirmed: true,
RemoteId: sc.RemoteId,
}
_, err = th.App.SaveSharedChannelRemote(scr)
require.NoError(t, err)
t.Run("valid remote, user is member", func(t *testing.T) {
rcInfo, _, err := th.Client.GetRemoteClusterInfo(context.Background(), rc.RemoteId)
require.NoError(t, err)
assert.Equal(t, rc.Name, rcInfo.Name)
})
t.Run("invalid remote", func(t *testing.T) {
_, resp, err := th.Client.GetRemoteClusterInfo(context.Background(), model.NewId())
require.Error(t, err)
CheckNotFoundStatus(t, resp)
})
}
func TestCreateDirectChannelWithRemoteUser(t *testing.T) {
t.Run("should not create a local DM channel that is shared", func(t *testing.T) {
th := setupForSharedChannels(t).InitBasic()
defer th.TearDown()
client := th.Client
defer func() {
_, err := client.Logout(context.Background())
require.NoError(t, err)
}()
localUser := th.BasicUser
remoteUser := th.CreateUser()
remoteUser.RemoteId = model.NewPointer(model.NewId())
remoteUser, appErr := th.App.UpdateUser(th.Context, remoteUser, false)
require.Nil(t, appErr)
dm, _, err := client.CreateDirectChannel(context.Background(), localUser.Id, remoteUser.Id)
require.Error(t, err)
require.Nil(t, dm)
})
t.Run("creates a local DM channel that is shared", func(t *testing.T) {
t.Skip("Remote DMs are currently disabled")
th := setupForSharedChannels(t).InitBasic()
defer th.TearDown()
client := th.Client
defer func() {
_, err := client.Logout(context.Background())
require.NoError(t, err)
}()
localUser := th.BasicUser
remoteUser := th.CreateUser()
remoteUser.RemoteId = model.NewPointer(model.NewId())
remoteUser, appErr := th.App.UpdateUser(th.Context, remoteUser, false)
require.Nil(t, appErr)
dm, _, err := client.CreateDirectChannel(context.Background(), localUser.Id, remoteUser.Id)
require.NoError(t, err)
channelName := model.GetDMNameFromIds(localUser.Id, remoteUser.Id)
require.Equal(t, channelName, dm.Name, "dm name didn't match")
assert.True(t, dm.IsShared())
})
t.Run("sends a shared channel invitation to the remote", func(t *testing.T) {
t.Skip("Remote DMs are currently disabled")
th := setupForSharedChannels(t).InitBasic()
defer th.TearDown()
client := th.Client
defer func() {
_, err := client.Logout(context.Background())
require.NoError(t, err)
}()
localUser := th.BasicUser
remoteUser := th.CreateUser()
rc := &model.RemoteCluster{
Name: "test",
Token: model.NewId(),
CreatorId: localUser.Id,
}
rc, appErr := th.App.AddRemoteCluster(rc)
require.Nil(t, appErr)
remoteUser.RemoteId = model.NewPointer(rc.RemoteId)
remoteUser, appErr = th.App.UpdateUser(th.Context, remoteUser, false)
require.Nil(t, appErr)
dm, _, err := client.CreateDirectChannel(context.Background(), localUser.Id, remoteUser.Id)
require.NoError(t, err)
channelName := model.GetDMNameFromIds(localUser.Id, remoteUser.Id)
require.Equal(t, channelName, dm.Name, "dm name didn't match")
require.True(t, dm.IsShared())
})
t.Run("does not send a shared channel invitation to the remote when creator is remote", func(t *testing.T) {
t.Skip("Remote DMs are currently disabled")
th := setupForSharedChannels(t).InitBasic()
defer th.TearDown()
client := th.Client
defer func() {
_, err := client.Logout(context.Background())
require.NoError(t, err)
}()
localUser := th.BasicUser
remoteUser := th.CreateUser()
rc := &model.RemoteCluster{
Name: "test",
Token: model.NewId(),
CreatorId: localUser.Id,
}
rc, appErr := th.App.AddRemoteCluster(rc)
require.Nil(t, appErr)
remoteUser.RemoteId = model.NewPointer(rc.RemoteId)
remoteUser, appErr = th.App.UpdateUser(th.Context, remoteUser, false)
require.Nil(t, appErr)
dm, _, err := client.CreateDirectChannel(context.Background(), remoteUser.Id, localUser.Id)
require.NoError(t, err)
channelName := model.GetDMNameFromIds(localUser.Id, remoteUser.Id)
require.Equal(t, channelName, dm.Name, "dm name didn't match")
require.True(t, dm.IsShared())
})
}
func TestGetSharedChannelRemotesByRemoteCluster(t *testing.T) {
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
th := Setup(t)
defer th.TearDown()
resp, err := th.SystemAdminClient.DeleteRemoteCluster(context.Background(), model.NewId())
CheckNotImplementedStatus(t, resp)
require.Error(t, err)
})
th := setupForSharedChannels(t).InitBasic()
defer th.TearDown()
newRC1 := &model.RemoteCluster{Name: "rc1", SiteURL: "http://example1.com", CreatorId: th.SystemAdminUser.Id}
newRC2 := &model.RemoteCluster{Name: "rc2", SiteURL: "http://example2.com", CreatorId: th.SystemAdminUser.Id}
rc1, appErr := th.App.AddRemoteCluster(newRC1)
require.Nil(t, appErr)
rc2, appErr := th.App.AddRemoteCluster(newRC2)
require.Nil(t, appErr)
c1 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
sc1 := &model.SharedChannel{
ChannelId: c1.Id,
TeamId: th.BasicTeam.Id,
ShareName: "shared_1",
ShareDisplayName: "Shared Channel 1", // for sorting purposes
CreatorId: th.BasicUser.Id,
RemoteId: rc1.RemoteId,
Home: true,
}
_, err := th.App.ShareChannel(th.Context, sc1)
require.NoError(t, err)
c2 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
sc2 := &model.SharedChannel{
ChannelId: c2.Id,
TeamId: th.BasicTeam.Id,
ShareName: "shared_2",
ShareDisplayName: "Shared Channel 2",
CreatorId: th.BasicUser.Id,
RemoteId: rc1.RemoteId,
Home: false,
}
_, err = th.App.ShareChannel(th.Context, sc2)
require.NoError(t, err)
c3 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
sc3 := &model.SharedChannel{
ChannelId: c3.Id,
TeamId: th.BasicTeam.Id,
ShareName: "shared_3",
CreatorId: th.BasicUser.Id,
RemoteId: rc2.RemoteId,
}
_, err = th.App.ShareChannel(th.Context, sc3)
require.NoError(t, err)
c4 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
sc4 := &model.SharedChannel{
ChannelId: c4.Id,
TeamId: th.BasicTeam.Id,
ShareName: "shared_4",
ShareDisplayName: "Shared Channel 4",
CreatorId: th.BasicUser.Id,
RemoteId: rc1.RemoteId,
Home: false,
}
_, err = th.App.ShareChannel(th.Context, sc4)
require.NoError(t, err)
c5 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
sc5 := &model.SharedChannel{
ChannelId: c5.Id,
TeamId: th.BasicTeam.Id,
ShareName: "shared_5",
ShareDisplayName: "Shared Channel 5",
CreatorId: th.BasicUser.Id,
RemoteId: rc1.RemoteId,
Home: false,
}
_, err = th.App.ShareChannel(th.Context, sc5)
require.NoError(t, err)
// for the pagination test, we need to get the channelId of the
// second SharedChannelRemote that belongs to RC1, sorted by ID,
// so we accumulate those SharedChannelRemotes on creation and
// later sort them to be able to get the right one for the test
// result
sharedChannelRemotesFromRC1 := []*model.SharedChannelRemote{}
// create the shared channel remotes
for _, sc := range []*model.SharedChannel{sc1, sc2, sc3, sc4, sc5} {
scr := &model.SharedChannelRemote{
Id: model.NewId(),
ChannelId: sc.ChannelId,
CreatorId: sc.CreatorId,
IsInviteAccepted: true,
IsInviteConfirmed: true,
RemoteId: sc.RemoteId,
}
// scr for c5 is not confirmed yet
if sc.ChannelId == sc5.ChannelId {
scr.IsInviteConfirmed = false
}
_, err = th.App.SaveSharedChannelRemote(scr)
require.NoError(t, err)
if scr.RemoteId == rc1.RemoteId {
sharedChannelRemotesFromRC1 = append(sharedChannelRemotesFromRC1, scr)
}
}
// we delete the shared channel remote for sc4
scr4, err := th.App.GetSharedChannelRemoteByIds(sc4.ChannelId, sc4.RemoteId)
require.NoError(t, err)
deleted, err := th.App.DeleteSharedChannelRemote(scr4.Id)
require.NoError(t, err)
require.True(t, deleted)
sort.Slice(sharedChannelRemotesFromRC1, func(i, j int) bool {
return sharedChannelRemotesFromRC1[i].Id < sharedChannelRemotesFromRC1[j].Id
})
t.Run("should return the expected shared channels", func(t *testing.T) {
testCases := []struct {
Name string
Client *model.Client4
RemoteId string
Filter model.SharedChannelRemoteFilterOpts
Page int
PerPage int
ExpectedStatusCode int
ExpectedError bool
ExpectedIds []string
}{
{
Name: "should not work if the user doesn't have the right permissions",
Client: th.Client,
RemoteId: rc1.RemoteId,
Page: 0,
PerPage: 100,
ExpectedStatusCode: http.StatusForbidden,
ExpectedError: true,
},
{
Name: "should not work if the remote cluster is nonexistent",
Client: th.SystemAdminClient,
RemoteId: model.NewId(),
Page: 0,
PerPage: 100,
ExpectedStatusCode: http.StatusNotFound,
ExpectedError: true,
},
{
Name: "should return the complete list of shared channel remotes for a remote cluster",
Client: th.SystemAdminClient,
RemoteId: rc1.RemoteId,
Page: 0,
PerPage: 100,
ExpectedStatusCode: http.StatusOK,
ExpectedError: false,
ExpectedIds: []string{sc1.ChannelId, sc2.ChannelId},
},
{
Name: "should return the complete list of shared channel remotes for a remote cluster, including deleted",
Client: th.SystemAdminClient,
RemoteId: rc1.RemoteId,
Filter: model.SharedChannelRemoteFilterOpts{IncludeDeleted: true},
Page: 0,
PerPage: 100,
ExpectedStatusCode: http.StatusOK,
ExpectedError: false,
ExpectedIds: []string{sc1.ChannelId, sc2.ChannelId, sc4.ChannelId},
},
{
Name: "should return only the shared channel remotes homed localy",
Client: th.SystemAdminClient,
RemoteId: rc1.RemoteId,
Filter: model.SharedChannelRemoteFilterOpts{ExcludeRemote: true},
Page: 0,
PerPage: 100,
ExpectedStatusCode: http.StatusOK,
ExpectedError: false,
ExpectedIds: []string{sc1.ChannelId},
},
{
Name: "should return only the shared channel remotes homed remotely",
Client: th.SystemAdminClient,
RemoteId: rc1.RemoteId,
Filter: model.SharedChannelRemoteFilterOpts{ExcludeHome: true},
Page: 0,
PerPage: 100,
ExpectedStatusCode: http.StatusOK,
ExpectedError: false,
ExpectedIds: []string{sc2.ChannelId},
},
{
Name: "should return the complete list of shared channel remotes for a remote cluster including unconfirmed",
Client: th.SystemAdminClient,
RemoteId: rc1.RemoteId,
Filter: model.SharedChannelRemoteFilterOpts{IncludeUnconfirmed: true},
Page: 0,
PerPage: 100,
ExpectedStatusCode: http.StatusOK,
ExpectedError: false,
ExpectedIds: []string{sc1.ChannelId, sc2.ChannelId, sc5.ChannelId},
},
{
Name: "should return only the unconfirmed shared channel remotes for a remote cluster",
Client: th.SystemAdminClient,
RemoteId: rc1.RemoteId,
Filter: model.SharedChannelRemoteFilterOpts{ExcludeConfirmed: true},
Page: 0,
PerPage: 100,
ExpectedStatusCode: http.StatusOK,
ExpectedError: false,
ExpectedIds: []string{sc5.ChannelId},
},
{
Name: "should correctly paginate the results",
Client: th.SystemAdminClient,
RemoteId: rc1.RemoteId,
Filter: model.SharedChannelRemoteFilterOpts{IncludeDeleted: true, IncludeUnconfirmed: true},
Page: 1,
PerPage: 1,
ExpectedStatusCode: http.StatusOK,
ExpectedError: false,
ExpectedIds: []string{sharedChannelRemotesFromRC1[1].ChannelId},
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
scrs, resp, err := tc.Client.GetSharedChannelRemotesByRemoteCluster(context.Background(), tc.RemoteId, tc.Filter, tc.Page, tc.PerPage)
checkHTTPStatus(t, resp, tc.ExpectedStatusCode)
if tc.ExpectedError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
require.Len(t, scrs, len(tc.ExpectedIds))
foundIds := []string{}
for _, scr := range scrs {
require.Equal(t, tc.RemoteId, scr.RemoteId)
foundIds = append(foundIds, scr.ChannelId)
}
require.ElementsMatch(t, tc.ExpectedIds, foundIds)
})
}
})
}
func TestInviteRemoteClusterToChannel(t *testing.T) {
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
th := Setup(t)
defer th.TearDown()
resp, err := th.SystemAdminClient.InviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
CheckNotImplementedStatus(t, resp)
require.Error(t, err)
})
th := setupForSharedChannels(t).InitBasic()
defer th.TearDown()
newRC := &model.RemoteCluster{Name: "rc", SiteURL: "http://example.com", CreatorId: th.SystemAdminUser.Id}
rc, appErr := th.App.AddRemoteCluster(newRC)
require.Nil(t, appErr)
t.Run("Should not work if the user doesn't have the right permissions", func(t *testing.T) {
resp, err := th.Client.InviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
CheckForbiddenStatus(t, resp)
require.Error(t, err)
})
t.Run("should not work if the remote cluster is nonexistent", func(t *testing.T) {
resp, err := th.SystemAdminClient.InviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
CheckBadRequestStatus(t, resp)
require.Error(t, err)
})
t.Run("should not work if the channel is nonexistent", func(t *testing.T) {
resp, err := th.SystemAdminClient.InviteRemoteClusterToChannel(context.Background(), rc.RemoteId, model.NewId())
CheckBadRequestStatus(t, resp)
require.Error(t, err)
})
t.Run("should correctly invite the remote cluster to the channel", func(t *testing.T) {
t.Skip("Requires server2server communication: ToBeImplemented")
})
t.Run("should do nothing but return 204 if the remote cluster is already invited to the channel", func(t *testing.T) {
t.Skip("Requires server2server communication: ToBeImplemented")
})
}
func TestUninviteRemoteClusterToChannel(t *testing.T) {
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
th := Setup(t)
defer th.TearDown()
resp, err := th.SystemAdminClient.UninviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
CheckNotImplementedStatus(t, resp)
require.Error(t, err)
})
th := setupForSharedChannels(t).InitBasic()
defer th.TearDown()
newRC := &model.RemoteCluster{Name: "rc", SiteURL: "http://example.com", CreatorId: th.SystemAdminUser.Id}
rc, appErr := th.App.AddRemoteCluster(newRC)
require.Nil(t, appErr)
t.Run("Should not work if the user doesn't have the right permissions", func(t *testing.T) {
resp, err := th.Client.UninviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
CheckForbiddenStatus(t, resp)
require.Error(t, err)
})
t.Run("should not work if the remote cluster is nonexistent", func(t *testing.T) {
resp, err := th.SystemAdminClient.UninviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
CheckBadRequestStatus(t, resp)
require.Error(t, err)
})
t.Run("should not work if the channel is nonexistent", func(t *testing.T) {
resp, err := th.SystemAdminClient.UninviteRemoteClusterToChannel(context.Background(), rc.RemoteId, model.NewId())
CheckBadRequestStatus(t, resp)
require.Error(t, err)
})
t.Run("should correctly uninvite the remote cluster to the channel", func(t *testing.T) {
t.Skip("Requires server2server communication: ToBeImplemented")
})
t.Run("should do nothing but return 204 if the remote cluster is not sharing the channel", func(t *testing.T) {
t.Skip("Requires server2server communication: ToBeImplemented")
})
}