Files
team/server/channels/api4/shared_channel_test.go
Doug LauderandGitHub f2d1bae3a1 MM-67684 Separate shared channel permissions from secure connection permissions (#35409)
* Channel sharing operations (invite, uninvite, list shared channel remotes)
now require ManageSharedChannels instead of ManageSecureConnections, allowing
customers to delegate channel sharing without granting full connection management access.
Endpoints serving both roles (getRemoteClusters, getSharedChannelRemotesByRemoteCluster) accept either permission.

Also adds RequirePermission helpers on Context to reduce boilerplate across all remote cluster and shared channel handlers, and fixes a bug where invite/uninvite checked ManageSecureConnections but reported ManageSharedChannels in the error.
2026-03-11 15:53:06 -04:00

735 lines
25 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 TestGetAllSharedChannelsWithSharedChannelManagerRole(t *testing.T) {
mainHelper.Parallel(t)
th := setupForSharedChannels(t).InitBasic(t)
// Create a shared channel that the test user is NOT a member of
otherChannel := th.CreateChannelWithClientAndTeam(t, th.SystemAdminClient, model.ChannelTypeOpen, th.BasicTeam.Id)
sc := &model.SharedChannel{
ChannelId: otherChannel.Id,
TeamId: otherChannel.TeamId,
Home: true,
ShareName: "test_share_other",
CreatorId: th.SystemAdminUser.Id,
RemoteId: model.NewId(),
}
_, err := th.App.ShareChannel(th.Context, sc)
require.NoError(t, err)
// Create a user with the shared_channel_manager role
scmUser := th.CreateUser(t)
_, appErr := th.App.UpdateUserRoles(th.Context, scmUser.Id, model.SystemUserRoleId+" "+model.SharedChannelManagerRoleId, false)
require.Nil(t, appErr)
th.LinkUserToTeam(t, scmUser, th.BasicTeam)
scmClient := th.CreateClient()
_, _, loginErr := scmClient.Login(context.Background(), scmUser.Email, scmUser.Password)
require.NoError(t, loginErr)
t.Run("regular user only sees shared channels they are a member of", func(t *testing.T) {
// BasicUser is not a member of otherChannel
channels, _, err := th.Client.GetAllSharedChannels(context.Background(), th.BasicTeam.Id, 0, 100)
require.NoError(t, err)
for _, ch := range channels {
assert.NotEqual(t, otherChannel.Id, ch.ChannelId,
"regular user should not see shared channels they are not a member of")
}
})
t.Run("shared_channel_manager user sees all shared channels", func(t *testing.T) {
// scmUser is NOT a member of otherChannel, but has manage_shared_channels
channels, _, err := scmClient.GetAllSharedChannels(context.Background(), th.BasicTeam.Id, 0, 100)
require.NoError(t, err)
found := false
for _, ch := range channels {
if ch.ChannelId == otherChannel.Id {
found = true
break
}
}
assert.True(t, found,
"shared_channel_manager user should see all shared channels, including ones they are not a member of")
})
}
func TestGetAllSharedChannels(t *testing.T) {
mainHelper.Parallel(t)
th := setupForSharedChannels(t).InitBasic(t)
const pages = 3
const pageSize = 7
savedIds := make([]string, 0, pages*pageSize)
// make some shared channels
for i := range pages * pageSize {
channel := th.CreateChannelWithClientAndTeam(t, 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)
}
t.Run("get shared channels paginated", func(t *testing.T) {
channelIds := make([]string, 0, 21)
for i := range pages {
channels, _, err := th.Client.GetAllSharedChannels(context.Background(), th.BasicTeam.Id, i, pageSize)
require.NoError(t, err)
channelIds = append(channelIds, getIds(channels)...)
}
// ids lists should now match
assert.ElementsMatch(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) {
mainHelper.Parallel(t)
th := setupForSharedChannels(t).InitBasic(t)
// 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) {
mainHelper.Parallel(t)
t.Run("should not create a local DM channel that is shared", func(t *testing.T) {
th := setupForSharedChannels(t).InitBasic(t)
client := th.Client
defer func() {
_, err := client.Logout(context.Background())
require.NoError(t, err)
}()
localUser := th.BasicUser
remoteUser := th.CreateUser(t)
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(t)
client := th.Client
defer func() {
_, err := client.Logout(context.Background())
require.NoError(t, err)
}()
localUser := th.BasicUser
remoteUser := th.CreateUser(t)
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(t)
client := th.Client
defer func() {
_, err := client.Logout(context.Background())
require.NoError(t, err)
}()
localUser := th.BasicUser
remoteUser := th.CreateUser(t)
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(t)
client := th.Client
defer func() {
_, err := client.Logout(context.Background())
require.NoError(t, err)
}()
localUser := th.BasicUser
remoteUser := th.CreateUser(t)
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) {
mainHelper.Parallel(t)
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
th := Setup(t)
resp, err := th.SystemAdminClient.DeleteRemoteCluster(context.Background(), model.NewId())
CheckNotImplementedStatus(t, resp)
require.Error(t, err)
})
th := setupForSharedChannels(t).InitBasic(t)
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(t, 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(t, 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(t, 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(t, 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(t, 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) {
mainHelper.Parallel(t)
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
th := Setup(t)
resp, err := th.SystemAdminClient.InviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
CheckNotImplementedStatus(t, resp)
require.Error(t, err)
})
th := setupForSharedChannels(t).InitBasic(t)
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) {
mainHelper.Parallel(t)
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
th := Setup(t)
resp, err := th.SystemAdminClient.UninviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
CheckNotImplementedStatus(t, resp)
require.Error(t, err)
})
th := setupForSharedChannels(t).InitBasic(t)
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")
})
}
func TestSharedChannelEndpointsWithSharedChannelManagerRole(t *testing.T) {
mainHelper.Parallel(t)
th := setupForSharedChannels(t).InitBasic(t)
newRC := &model.RemoteCluster{Name: "rc", SiteURL: "http://example.com", CreatorId: th.SystemAdminUser.Id}
rc, appErr := th.App.AddRemoteCluster(newRC)
require.Nil(t, appErr)
// Create a user with only the shared_channel_manager role
scmUser := th.CreateUser(t)
_, appErr = th.App.UpdateUserRoles(th.Context, scmUser.Id, model.SystemUserRoleId+" "+model.SharedChannelManagerRoleId, false)
require.Nil(t, appErr)
scmClient := th.CreateClient()
_, _, err := scmClient.Login(context.Background(), scmUser.Email, scmUser.Password)
require.NoError(t, err)
t.Run("getSharedChannelRemotesByRemoteCluster should allow shared_channel_manager", func(t *testing.T) {
_, resp, err := scmClient.GetSharedChannelRemotesByRemoteCluster(context.Background(), rc.RemoteId, model.SharedChannelRemoteFilterOpts{}, 0, 100)
CheckOKStatus(t, resp)
require.NoError(t, err)
})
t.Run("inviteRemoteClusterToChannel should allow shared_channel_manager", func(t *testing.T) {
// This will fail with a bad request (nonexistent channel) rather than forbidden,
// which proves the permission check passed.
resp, err := scmClient.InviteRemoteClusterToChannel(context.Background(), rc.RemoteId, model.NewId())
CheckBadRequestStatus(t, resp)
require.Error(t, err)
})
t.Run("uninviteRemoteClusterToChannel should allow shared_channel_manager", func(t *testing.T) {
// Same as invite — a bad request proves the permission check passed.
resp, err := scmClient.UninviteRemoteClusterToChannel(context.Background(), rc.RemoteId, model.NewId())
CheckBadRequestStatus(t, resp)
require.Error(t, err)
})
}
func TestGetSharedChannelRemotesByRemoteClusterWithSecureConnectionManagerRole(t *testing.T) {
mainHelper.Parallel(t)
th := setupForSharedChannels(t).InitBasic(t)
newRC := &model.RemoteCluster{Name: "rc", SiteURL: "http://example.com", CreatorId: th.SystemAdminUser.Id}
rc, appErr := th.App.AddRemoteCluster(newRC)
require.Nil(t, appErr)
// Create a user with only the secure_connection_manager role
scmUser := th.CreateUser(t)
_, appErr = th.App.UpdateUserRoles(th.Context, scmUser.Id, model.SystemUserRoleId+" "+model.SecureConnectionManagerRoleId, false)
require.Nil(t, appErr)
scmClient := th.CreateClient()
_, _, err := scmClient.Login(context.Background(), scmUser.Email, scmUser.Password)
require.NoError(t, err)
t.Run("secure_connection_manager should have access", func(t *testing.T) {
_, resp, err := scmClient.GetSharedChannelRemotesByRemoteCluster(context.Background(), rc.RemoteId, model.SharedChannelRemoteFilterOpts{}, 0, 100)
CheckOKStatus(t, resp)
require.NoError(t, err)
})
}