diff --git a/server/channels/api4/remote_cluster_test.go b/server/channels/api4/remote_cluster_test.go index f5234488020..015ec4786b9 100644 --- a/server/channels/api4/remote_cluster_test.go +++ b/server/channels/api4/remote_cluster_test.go @@ -12,6 +12,80 @@ import ( "github.com/stretchr/testify/require" ) +func TestGetRemoteClustersWithSecureConnectionManagerRole(t *testing.T) { + mainHelper.Parallel(t) + th := setupForSharedChannels(t).InitBasic(t) + + // Create a remote cluster for testing + newRC := &model.RemoteCluster{ + RemoteId: model.NewId(), + Name: "test-remote", + SiteURL: "http://example.com", + CreatorId: th.SystemAdminUser.Id, + Token: model.NewId(), + } + _, 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("regular user should be denied", func(t *testing.T) { + _, resp, err := th.Client.GetRemoteClusters(context.Background(), 0, 999999, model.RemoteClusterQueryFilter{}) + CheckForbiddenStatus(t, resp) + require.Error(t, err) + }) + + t.Run("secure_connection_manager user should have access", func(t *testing.T) { + rcs, resp, err := scmClient.GetRemoteClusters(context.Background(), 0, 999999, model.RemoteClusterQueryFilter{}) + CheckOKStatus(t, resp) + require.NoError(t, err) + require.NotEmpty(t, rcs) + }) +} + +func TestCreateRemoteClusterWithSecureConnectionManagerRole(t *testing.T) { + mainHelper.Parallel(t) + th := setupForSharedChannels(t).InitBasic(t) + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL = "http://localhost:8065" }) + + // 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) + + rcPayload := &model.RemoteClusterWithPassword{ + RemoteCluster: &model.RemoteCluster{ + Name: "test-from-scm", + DefaultTeamId: th.BasicTeam.Id, + }, + Password: "mysupersecret", + } + + t.Run("regular user should be denied", func(t *testing.T) { + _, resp, err := th.Client.CreateRemoteCluster(context.Background(), rcPayload) + CheckForbiddenStatus(t, resp) + require.Error(t, err) + }) + + t.Run("secure_connection_manager user should have access", func(t *testing.T) { + rcWithInvite, resp, err := scmClient.CreateRemoteCluster(context.Background(), rcPayload) + CheckCreatedStatus(t, resp) + require.NoError(t, err) + require.NotEmpty(t, rcWithInvite) + }) +} + func TestGetRemoteClusters(t *testing.T) { mainHelper.Parallel(t) t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) { diff --git a/server/channels/api4/shared_channel_test.go b/server/channels/api4/shared_channel_test.go index c92d18081d5..3c49e544223 100644 --- a/server/channels/api4/shared_channel_test.go +++ b/server/channels/api4/shared_channel_test.go @@ -33,6 +33,60 @@ func setupForSharedChannels(tb testing.TB) *TestHelper { 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) diff --git a/server/channels/app/export_test.go b/server/channels/app/export_test.go index 8a4eb70af10..0434daa4034 100644 --- a/server/channels/app/export_test.go +++ b/server/channels/app/export_test.go @@ -1360,7 +1360,7 @@ func TestExportSchemes(t *testing.T) { err := th1.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"}) require.NoError(t, err) - builtInRoles := 23 + builtInRoles := 25 defaultChannelSchemeRoles := 3 // Verify the roles count is expected prior to scheme creation. @@ -1457,7 +1457,7 @@ func TestExportSchemes(t *testing.T) { err := th1.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"}) require.NoError(t, err) - builtInRoles := 23 + builtInRoles := 25 defaultTeamSchemeRoles := 10 // Verify the roles count is expected prior to scheme creation. diff --git a/server/channels/app/migrations.go b/server/channels/app/migrations.go index 2a3a092c0cb..7a47ea61ab1 100644 --- a/server/channels/app/migrations.go +++ b/server/channels/app/migrations.go @@ -18,17 +18,19 @@ import ( ) const ( - EmojisPermissionsMigrationKey = "EmojisPermissionsMigrationComplete" - GuestRolesCreationMigrationKey = "GuestRolesCreationMigrationComplete" - SystemConsoleRolesCreationMigrationKey = "SystemConsoleRolesCreationMigrationComplete" - CustomGroupAdminRoleCreationMigrationKey = "CustomGroupAdminRoleCreationMigrationComplete" - ContentExtractionConfigDefaultTrueMigrationKey = "ContentExtractionConfigDefaultTrueMigrationComplete" - PlaybookRolesCreationMigrationKey = "PlaybookRolesCreationMigrationComplete" - FirstAdminSetupCompleteKey = model.SystemFirstAdminSetupComplete - remainingSchemaMigrationsKey = "RemainingSchemaMigrations" - postPriorityConfigDefaultTrueMigrationKey = "PostPriorityConfigDefaultTrueMigrationComplete" - contentFlaggingSetupDoneKey = "content_flagging_setup_done" - contentFlaggingMigrationVersion = "v5" + EmojisPermissionsMigrationKey = "EmojisPermissionsMigrationComplete" + GuestRolesCreationMigrationKey = "GuestRolesCreationMigrationComplete" + SystemConsoleRolesCreationMigrationKey = "SystemConsoleRolesCreationMigrationComplete" + CustomGroupAdminRoleCreationMigrationKey = "CustomGroupAdminRoleCreationMigrationComplete" + SharedChannelManagerRoleCreationMigrationKey = "SharedChannelManagerRoleCreationMigrationComplete" + SecureConnectionManagerRoleCreationMigrationKey = "SecureConnectionManagerRoleCreationMigrationComplete" + ContentExtractionConfigDefaultTrueMigrationKey = "ContentExtractionConfigDefaultTrueMigrationComplete" + PlaybookRolesCreationMigrationKey = "PlaybookRolesCreationMigrationComplete" + FirstAdminSetupCompleteKey = model.SystemFirstAdminSetupComplete + remainingSchemaMigrationsKey = "RemainingSchemaMigrations" + postPriorityConfigDefaultTrueMigrationKey = "PostPriorityConfigDefaultTrueMigrationComplete" + contentFlaggingSetupDoneKey = "content_flagging_setup_done" + contentFlaggingMigrationVersion = "v5" contentFlaggingPropertyNameFlaggedPostId = "flagged_post_id" ContentFlaggingPropertyNameStatus = "status" @@ -323,34 +325,54 @@ func (s *Server) doSystemConsoleRolesCreationMigration() error { return nil } -func (s *Server) doCustomGroupAdminRoleCreationMigration() error { +func (s *Server) doSingleRoleCreationMigration(migrationKey, roleId string) error { // If the migration is already marked as completed, don't do it again. var nfErr *store.ErrNotFound - if _, err := s.Store().System().GetByName(CustomGroupAdminRoleCreationMigrationKey); err == nil { + if _, err := s.Store().System().GetByName(migrationKey); err == nil { return nil } else if !errors.As(err, &nfErr) { return fmt.Errorf("could not query migration: %w", err) } roles := model.MakeDefaultRoles() - if _, err := s.Store().Role().GetByName(context.Background(), model.SystemCustomGroupAdminRoleId); err != nil { - if _, err := s.Store().Role().Save(roles[model.SystemCustomGroupAdminRoleId]); err != nil { - return fmt.Errorf("failed to create new role %s: %w", model.SystemCustomGroupAdminRoleId, err) + role := roles[roleId] + if role == nil { + return fmt.Errorf("unknown role id: %q", roleId) + } + var nfRoleErr *store.ErrNotFound + if _, err := s.Store().Role().GetByName(context.Background(), roleId); err != nil { + if !errors.As(err, &nfRoleErr) { + return fmt.Errorf("could not query role %q: %w", roleId, err) + } + if _, err := s.Store().Role().Save(role); err != nil { + return fmt.Errorf("failed to create new role %q: %w", roleId, err) } } system := model.System{ - Name: CustomGroupAdminRoleCreationMigrationKey, + Name: migrationKey, Value: "true", } if err := s.Store().System().Save(&system); err != nil { - return fmt.Errorf("failed to mark custom group admin role creation migration as completed: %w", err) + return fmt.Errorf("failed to mark %s migration as completed: %w", migrationKey, err) } return nil } +func (s *Server) doCustomGroupAdminRoleCreationMigration() error { + return s.doSingleRoleCreationMigration(CustomGroupAdminRoleCreationMigrationKey, model.SystemCustomGroupAdminRoleId) +} + +func (s *Server) doSharedChannelManagerRoleCreationMigration() error { + return s.doSingleRoleCreationMigration(SharedChannelManagerRoleCreationMigrationKey, model.SharedChannelManagerRoleId) +} + +func (s *Server) doSecureConnectionManagerRoleCreationMigration() error { + return s.doSingleRoleCreationMigration(SecureConnectionManagerRoleCreationMigrationKey, model.SecureConnectionManagerRoleId) +} + func (s *Server) doContentExtractionConfigDefaultTrueMigration() error { // If the migration is already marked as completed, don't do it again. var nfErr *store.ErrNotFound @@ -842,6 +864,8 @@ func (s *Server) doAppMigrations() { {"GuestRolesCreationMigration", s.doGuestRolesCreationMigration}, {"System Console Roles Creation Migration", s.doSystemConsoleRolesCreationMigration}, {"Custom Group Admin Role Creation Migration", s.doCustomGroupAdminRoleCreationMigration}, + {"Shared Channel Manager Role Creation Migration", s.doSharedChannelManagerRoleCreationMigration}, + {"Secure Connection Manager Role Creation Migration", s.doSecureConnectionManagerRoleCreationMigration}, // This migration always run after dependent migrations such as the guest roles migration. {"Permissions Migrations", s.doPermissionsMigrations}, {"Content Extraction Config Default True Migration", s.doContentExtractionConfigDefaultTrueMigration}, diff --git a/server/channels/app/permissions_migrations.go b/server/channels/app/permissions_migrations.go index 19944b2612e..b2e8e6db044 100644 --- a/server/channels/app/permissions_migrations.go +++ b/server/channels/app/permissions_migrations.go @@ -619,7 +619,7 @@ func (a *App) getAddManageSecureConnectionsPermissionsMigration() (permissionsMa On: isExactRole(model.SystemAdminRoleId), Add: []string{PermissionManageSecureConnections}, }, - // remote the deprecated permission from system admin + // remove the deprecated permission from system admin permissionTransformation{ On: isExactRole(model.SystemAdminRoleId), Remove: []string{PermissionManageRemoteClusters}, @@ -1266,6 +1266,24 @@ func (a *App) getRestrictAcessToChannelConversionToPublic() (permissionsMap, err }, nil } +func (a *App) getAddSharedChannelManagerPermissionsMigration() (permissionsMap, error) { + return permissionsMap{ + permissionTransformation{ + On: isExactRole(model.SharedChannelManagerRoleId), + Add: []string{PermissionManageSharedChannels}, + }, + }, nil +} + +func (a *App) getAddSecureConnectionManagerPermissionsMigration() (permissionsMap, error) { + return permissionsMap{ + permissionTransformation{ + On: isExactRole(model.SecureConnectionManagerRoleId), + Add: []string{PermissionManageSecureConnections}, + }, + }, nil +} + // DoPermissionsMigrations execute all the permissions migrations need by the current version. func (a *App) DoPermissionsMigrations() error { return a.Srv().doPermissionsMigrations() @@ -1323,6 +1341,8 @@ func (s *Server) doPermissionsMigrations() error { {Key: model.MigrationKeyAddChannelBannerPermissions, Migration: a.getAddChannelBannerPermissionMigration}, {Key: model.MigrationKeyAddChannelAccessRulesPermission, Migration: a.getAddChannelAccessRulesPermissionMigration}, {Key: model.MigrationKeyAddChannelAutoTranslationPermissions, Migration: a.getAddChannelAutoTranslationPermissionMigration}, + {Key: model.MigrationKeyAddSharedChannelManagerPermissions, Migration: a.getAddSharedChannelManagerPermissionsMigration}, + {Key: model.MigrationKeyAddSecureConnectionManagerPermissions, Migration: a.getAddSecureConnectionManagerPermissionsMigration}, } roles, err := s.Store().Role().GetAll() diff --git a/server/channels/app/support_packet_test.go b/server/channels/app/support_packet_test.go index f0c68fb0702..3eedc886c7b 100644 --- a/server/channels/app/support_packet_test.go +++ b/server/channels/app/support_packet_test.go @@ -597,7 +597,7 @@ func TestGetSupportPacketPermissionsInfo(t *testing.T) { t.Run("No custom permissions", func(t *testing.T) { permissions := generatePermissionInfo(t) - assert.Len(t, permissions.Roles, 23) + assert.Len(t, permissions.Roles, 25) assert.Empty(t, permissions.Schemes) }) @@ -611,7 +611,7 @@ func TestGetSupportPacketPermissionsInfo(t *testing.T) { t.Run("with custom scheme", func(t *testing.T) { permissions := generatePermissionInfo(t) - assert.Len(t, permissions.Roles, 33) // 23 default roles + 10 custom roles from the scheme + assert.Len(t, permissions.Roles, 35) // 25 default roles + 10 custom roles from the scheme require.Len(t, permissions.Schemes, 1) assert.Equal(t, scheme.Id, permissions.Schemes[0].Id) assert.Equal(t, model.FakeSetting, permissions.Schemes[0].Name, "Name should be obfuscated") @@ -633,7 +633,7 @@ func TestGetSupportPacketPermissionsInfo(t *testing.T) { permissions := generatePermissionInfo(t) require.Len(t, permissions.Schemes, 1) - require.Len(t, permissions.Roles, 34) // 23 default roles + 10 custom roles from the scheme + 1 custom role + require.Len(t, permissions.Roles, 36) // 25 default roles + 10 custom roles from the scheme + 1 custom role found := false for _, r := range permissions.Roles { // Confirm that sensitive fields are obfuscated diff --git a/server/channels/testlib/store.go b/server/channels/testlib/store.go index 845b13abca3..ca46030d461 100644 --- a/server/channels/testlib/store.go +++ b/server/channels/testlib/store.go @@ -85,6 +85,10 @@ func GetMockStoreForSetupFunctions() *mocks.Store { systemStore.On("GetByName", model.MigrationKeyFixReadAuditsPermission).Return(&model.System{Name: model.MigrationKeyFixReadAuditsPermission, Value: "true"}, nil) systemStore.On("GetByName", model.MigrationRemoveGetAnalyticsPermission).Return(&model.System{Name: model.MigrationRemoveGetAnalyticsPermission, Value: "true"}, nil) systemStore.On("GetByName", "CustomGroupAdminRoleCreationMigrationComplete").Return(&model.System{Name: model.MigrationKeyAddPlayboosksManageRolesPermissions, Value: "true"}, nil) + systemStore.On("GetByName", "SharedChannelManagerRoleCreationMigrationComplete").Return(&model.System{Name: "SharedChannelManagerRoleCreationMigrationComplete", Value: "true"}, nil) + systemStore.On("GetByName", "SecureConnectionManagerRoleCreationMigrationComplete").Return(&model.System{Name: "SecureConnectionManagerRoleCreationMigrationComplete", Value: "true"}, nil) + systemStore.On("GetByName", model.MigrationKeyAddSharedChannelManagerPermissions).Return(&model.System{Name: model.MigrationKeyAddSharedChannelManagerPermissions, Value: "true"}, nil) + systemStore.On("GetByName", model.MigrationKeyAddSecureConnectionManagerPermissions).Return(&model.System{Name: model.MigrationKeyAddSecureConnectionManagerPermissions, Value: "true"}, nil) systemStore.On("GetByName", "products_boards").Return(&model.System{Name: "products_boards", Value: "true"}, nil) systemStore.On("GetByName", "elasticsearch_fix_channel_index_migration").Return(&model.System{Name: "elasticsearch_fix_channel_index_migration", Value: "true"}, nil) systemStore.On("GetByName", model.MigrationAddSysconsoleMobileSecurityPermission).Return(&model.System{Name: model.MigrationAddSysconsoleMobileSecurityPermission, Value: "true"}, nil) diff --git a/server/public/model/migration.go b/server/public/model/migration.go index abb7bbdb020..e540998ea74 100644 --- a/server/public/model/migration.go +++ b/server/public/model/migration.go @@ -58,4 +58,6 @@ const ( MigrationKeyAddChannelBannerPermissions = "add_channel_banner_permissions" MigrationKeyAddChannelAccessRulesPermission = "add_channel_access_rules_permission" MigrationKeyAddChannelAutoTranslationPermissions = "add_channel_auto_translation_permissions" + MigrationKeyAddSharedChannelManagerPermissions = "shared_channel_manager_permissions" + MigrationKeyAddSecureConnectionManagerPermissions = "secure_connection_manager_permissions" ) diff --git a/server/public/model/role.go b/server/public/model/role.go index bdfaf7e3f97..a29ea8f0e37 100644 --- a/server/public/model/role.go +++ b/server/public/model/role.go @@ -16,6 +16,8 @@ var SystemManagerDefaultPermissions []string var SystemUserManagerDefaultPermissions []string var SystemReadOnlyAdminDefaultPermissions []string var SystemCustomGroupAdminDefaultPermissions []string +var SharedChannelManagerDefaultPermissions []string +var SecureConnectionManagerDefaultPermissions []string var BuiltInSchemeManagedRoleIDs []string @@ -26,6 +28,8 @@ func init() { SystemUserManagerRoleId, SystemReadOnlyAdminRoleId, SystemManagerRoleId, + SharedChannelManagerRoleId, + SecureConnectionManagerRoleId, } BuiltInSchemeManagedRoleIDs = append([]string{ @@ -354,6 +358,14 @@ func init() { PermissionManageCustomGroupMembers.Id, } + SharedChannelManagerDefaultPermissions = []string{ + PermissionManageSharedChannels.Id, + } + + SecureConnectionManagerDefaultPermissions = []string{ + PermissionManageSecureConnections.Id, + } + // Add the ancillary permissions to each system role SystemUserManagerDefaultPermissions = AddAncillaryPermissions(SystemUserManagerDefaultPermissions) SystemReadOnlyAdminDefaultPermissions = AddAncillaryPermissions(SystemReadOnlyAdminDefaultPermissions) @@ -365,16 +377,18 @@ type RoleType string type RoleScope string const ( - SystemGuestRoleId = "system_guest" - SystemUserRoleId = "system_user" - SystemAdminRoleId = "system_admin" - SystemPostAllRoleId = "system_post_all" - SystemPostAllPublicRoleId = "system_post_all_public" - SystemUserAccessTokenRoleId = "system_user_access_token" - SystemUserManagerRoleId = "system_user_manager" - SystemReadOnlyAdminRoleId = "system_read_only_admin" - SystemManagerRoleId = "system_manager" - SystemCustomGroupAdminRoleId = "system_custom_group_admin" + SystemGuestRoleId = "system_guest" + SystemUserRoleId = "system_user" + SystemAdminRoleId = "system_admin" + SystemPostAllRoleId = "system_post_all" + SystemPostAllPublicRoleId = "system_post_all_public" + SystemUserAccessTokenRoleId = "system_user_access_token" + SystemUserManagerRoleId = "system_user_manager" + SystemReadOnlyAdminRoleId = "system_read_only_admin" + SystemManagerRoleId = "system_manager" + SystemCustomGroupAdminRoleId = "system_custom_group_admin" + SharedChannelManagerRoleId = "shared_channel_manager" + SecureConnectionManagerRoleId = "secure_connection_manager" TeamGuestRoleId = "team_guest" TeamUserRoleId = "team_user" @@ -1173,6 +1187,24 @@ func MakeDefaultRoles() map[string]*Role { BuiltIn: true, } + roles[SharedChannelManagerRoleId] = &Role{ + Name: SharedChannelManagerRoleId, + DisplayName: "authentication.roles.shared_channel_manager.name", + Description: "authentication.roles.shared_channel_manager.description", + Permissions: SharedChannelManagerDefaultPermissions, + SchemeManaged: false, + BuiltIn: true, + } + + roles[SecureConnectionManagerRoleId] = &Role{ + Name: SecureConnectionManagerRoleId, + DisplayName: "authentication.roles.secure_connection_manager.name", + Description: "authentication.roles.secure_connection_manager.description", + Permissions: SecureConnectionManagerDefaultPermissions, + SchemeManaged: false, + BuiltIn: true, + } + allPermissionIDs := []string{} for _, permission := range AllPermissions { allPermissionIDs = append(allPermissionIDs, permission.Id) diff --git a/server/public/model/role_test.go b/server/public/model/role_test.go index e0b27261937..300e0d45886 100644 --- a/server/public/model/role_test.go +++ b/server/public/model/role_test.go @@ -4,9 +4,11 @@ package model import ( + "slices" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestChannelModeratedPermissionsChangedByPatch(t *testing.T) { @@ -305,3 +307,38 @@ func TestAddAncillaryPermissions(t *testing.T) { }) } } + +func TestMakeDefaultRolesContainsNewManagerRoles(t *testing.T) { + roles := MakeDefaultRoles() + + t.Run("shared_channel_manager role exists with correct permissions", func(t *testing.T) { + role, ok := roles[SharedChannelManagerRoleId] + require.True(t, ok, "shared_channel_manager role should exist in MakeDefaultRoles") + assert.Equal(t, "shared_channel_manager", role.Name) + assert.True(t, role.BuiltIn, "role should be built-in") + assert.False(t, role.SchemeManaged, "role should not be scheme-managed") + assert.True(t, slices.Contains(role.Permissions, PermissionManageSharedChannels.Id), + "role should have manage_shared_channels permission") + assert.False(t, slices.Contains(role.Permissions, PermissionManageSecureConnections.Id), + "role should NOT have manage_secure_connections permission") + }) + + t.Run("secure_connection_manager role exists with correct permissions", func(t *testing.T) { + role, ok := roles[SecureConnectionManagerRoleId] + require.True(t, ok, "secure_connection_manager role should exist in MakeDefaultRoles") + assert.Equal(t, "secure_connection_manager", role.Name) + assert.True(t, role.BuiltIn, "role should be built-in") + assert.False(t, role.SchemeManaged, "role should not be scheme-managed") + assert.True(t, slices.Contains(role.Permissions, PermissionManageSecureConnections.Id), + "role should have manage_secure_connections permission") + assert.False(t, slices.Contains(role.Permissions, PermissionManageSharedChannels.Id), + "role should NOT have manage_shared_channels permission") + }) + + t.Run("roles are included in NewSystemRoleIDs", func(t *testing.T) { + assert.True(t, slices.Contains(NewSystemRoleIDs, SharedChannelManagerRoleId), + "shared_channel_manager should be in NewSystemRoleIDs") + assert.True(t, slices.Contains(NewSystemRoleIDs, SecureConnectionManagerRoleId), + "secure_connection_manager should be in NewSystemRoleIDs") + }) +} diff --git a/webapp/channels/src/components/admin_console/admin_console.tsx b/webapp/channels/src/components/admin_console/admin_console.tsx index 5bddad4f7b6..65eef9d407f 100644 --- a/webapp/channels/src/components/admin_console/admin_console.tsx +++ b/webapp/channels/src/components/admin_console/admin_console.tsx @@ -89,7 +89,7 @@ const AdminConsole = (props: Props) => { useEffect(() => { props.actions.getConfig(); props.actions.getEnvironmentConfig(); - props.actions.loadRolesIfNeeded(['channel_user', 'team_user', 'system_user', 'channel_admin', 'team_admin', 'system_admin', 'system_user_manager', 'system_custom_group_admin', 'system_read_only_admin', 'system_manager']); + props.actions.loadRolesIfNeeded(['channel_user', 'team_user', 'system_user', 'channel_admin', 'team_admin', 'system_admin', 'system_user_manager', 'system_custom_group_admin', 'system_read_only_admin', 'system_manager', 'shared_channel_manager', 'secure_connection_manager']); props.actions.selectLhsItem(LhsItemType.None); props.actions.selectTeam(''); document.body.classList.add('console__body'); @@ -120,7 +120,9 @@ const AdminConsole = (props: Props) => { roles.system_user_manager && roles.system_read_only_admin && roles.system_custom_group_admin && - roles.system_manager + roles.system_manager && + roles.shared_channel_manager && + roles.secure_connection_manager ); }; diff --git a/webapp/channels/src/components/admin_console/system_roles/strings.tsx b/webapp/channels/src/components/admin_console/system_roles/strings.tsx index f682aa73dc8..c637ad18fc4 100644 --- a/webapp/channels/src/components/admin_console/system_roles/strings.tsx +++ b/webapp/channels/src/components/admin_console/system_roles/strings.tsx @@ -74,4 +74,32 @@ export const rolesStrings: Record> = { defaultMessage: 'System Role', }, }), + shared_channel_manager: defineMessages({ + name: { + id: 'admin.permissions.roles.shared_channel_manager.name', + defaultMessage: 'Shared Channel Manager', + }, + description: { + id: 'admin.permissions.roles.shared_channel_manager.description', + defaultMessage: 'Can share and unshare channels with existing connections to remote servers.', + }, + type: { + id: 'admin.permissions.roles.shared_channel_manager.type', + defaultMessage: 'System Role', + }, + }), + secure_connection_manager: defineMessages({ + name: { + id: 'admin.permissions.roles.secure_connection_manager.name', + defaultMessage: 'Secure Connection Manager', + }, + description: { + id: 'admin.permissions.roles.secure_connection_manager.description', + defaultMessage: 'Can create, manage, and remove secure connections to remote servers.', + }, + type: { + id: 'admin.permissions.roles.secure_connection_manager.type', + defaultMessage: 'System Role', + }, + }), }; diff --git a/webapp/channels/src/components/admin_console/system_roles/system_role/system_role_permissions.tsx b/webapp/channels/src/components/admin_console/system_roles/system_role/system_role_permissions.tsx index e61435876fb..0cde299570c 100644 --- a/webapp/channels/src/components/admin_console/system_roles/system_role/system_role_permissions.tsx +++ b/webapp/channels/src/components/admin_console/system_roles/system_role/system_role_permissions.tsx @@ -243,6 +243,70 @@ export default class SystemRolePermissions extends React.PureComponent +

+ ( + + {chunks} + + ), + }} + /> +

+

+ {chunks}, + }} + /> +

+ + ); + } + + if (this.props.role.name === Constants.PERMISSIONS_SECURE_CONNECTION_MANAGER) { + return ( + <> +

+ ( + + {chunks} + + ), + }} + /> +

+

+ {chunks}, + }} + /> +

+ + ); + } + if (this.props.role.name === Constants.PERMISSIONS_SYSTEM_USER_MANAGER) { let permissionsToShow: Record = {}; Object.keys(permissionsMap).forEach((permission) => { diff --git a/webapp/channels/src/components/admin_console/system_roles/system_roles.scss b/webapp/channels/src/components/admin_console/system_roles/system_roles.scss index 07decfc883a..27c9465e5b9 100644 --- a/webapp/channels/src/components/admin_console/system_roles/system_roles.scss +++ b/webapp/channels/src/components/admin_console/system_roles/system_roles.scss @@ -10,6 +10,9 @@ } .DataGrid_cell { + text-overflow: unset; + white-space: normal; + .SystemRoles_editRow { padding-right: 20px; } diff --git a/webapp/channels/src/components/admin_console/system_roles/system_roles.tsx b/webapp/channels/src/components/admin_console/system_roles/system_roles.tsx index abecce23fed..fc1b0b28353 100644 --- a/webapp/channels/src/components/admin_console/system_roles/system_roles.tsx +++ b/webapp/channels/src/components/admin_console/system_roles/system_roles.tsx @@ -45,7 +45,7 @@ const columns: Column[] = [ }, ]; -const roleNames = ['system_admin', 'system_manager', 'system_user_manager', 'system_custom_group_admin', 'system_read_only_admin']; +const roleNames = ['system_admin', 'system_manager', 'system_user_manager', 'system_custom_group_admin', 'shared_channel_manager', 'secure_connection_manager', 'system_read_only_admin']; const noop = () => {}; diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index 3b58fa28fe2..bbceee278fa 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -2113,6 +2113,16 @@ "admin.permissions.roles.channel_admin.name": "Channel Admin", "admin.permissions.roles.channel_user.name": "Channel User", "admin.permissions.roles.edit": "Edit", + "admin.permissions.roles.secure_connection_manager.description": "Can create, manage, and remove secure connections to remote servers.", + "admin.permissions.roles.secure_connection_manager.introduction": "The built-in Secure Connection Manager role can be used to delegate the ability to create, manage, and remove secure connections to remote servers to users other than the System Admin.", + "admin.permissions.roles.secure_connection_manager.name": "Secure Connection Manager", + "admin.permissions.roles.secure_connection_manager.permissions_info": "This role has the manage_secure_connections permission, which allows creating, editing, and deleting secure connections to remote servers.", + "admin.permissions.roles.secure_connection_manager.type": "System Role", + "admin.permissions.roles.shared_channel_manager.description": "Can share and unshare channels with existing connections to remote servers.", + "admin.permissions.roles.shared_channel_manager.introduction": "The built-in Shared Channel Manager role can be used to delegate the ability to share and unshare channels with existing connections to remote servers to users other than the System Admin.", + "admin.permissions.roles.shared_channel_manager.name": "Shared Channel Manager", + "admin.permissions.roles.shared_channel_manager.permissions_info": "This role has the manage_shared_channels permission, which allows sharing and unsharing channels with existing connections to remote servers.", + "admin.permissions.roles.shared_channel_manager.type": "System Role", "admin.permissions.roles.system_admin.description": "Access to modifying everything.", "admin.permissions.roles.system_admin.name": "System Admin", "admin.permissions.roles.system_admin.type": "System Role", diff --git a/webapp/channels/src/packages/mattermost-redux/src/constants/general.ts b/webapp/channels/src/packages/mattermost-redux/src/constants/general.ts index f44cc0804ca..e2479fa06db 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/constants/general.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/constants/general.ts @@ -35,6 +35,8 @@ export default { SYSTEM_USER_MANAGER_ROLE: 'system_user_manager', SYSTEM_READ_ONLY_ADMIN_ROLE: 'system_read_only_admin', SYSTEM_MANAGER_ROLE: 'system_manager', + SHARED_CHANNEL_MANAGER_ROLE: 'shared_channel_manager', + SECURE_CONNECTION_MANAGER_ROLE: 'secure_connection_manager', SYSTEM_USER_ACCESS_TOKEN_ROLE: 'system_user_access_token', SYSTEM_POST_ALL_ROLE: 'system_post_all', SYSTEM_POST_ALL_PUBLIC_ROLE: 'system_post_all_public', diff --git a/webapp/channels/src/packages/mattermost-redux/src/utils/user_utils.ts b/webapp/channels/src/packages/mattermost-redux/src/utils/user_utils.ts index 74b4578d6a6..e11efb31111 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/utils/user_utils.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/utils/user_utils.ts @@ -76,6 +76,8 @@ export function includesAnAdminRole(roles: string): boolean { General.SYSTEM_USER_MANAGER_ROLE, General.SYSTEM_READ_ONLY_ADMIN_ROLE, General.SYSTEM_MANAGER_ROLE, + General.SHARED_CHANNEL_MANAGER_ROLE, + General.SECURE_CONNECTION_MANAGER_ROLE, ].some((el) => rolesArray.includes(el)); } diff --git a/webapp/channels/src/utils/constants.tsx b/webapp/channels/src/utils/constants.tsx index 8ef6254a824..61e667f511e 100644 --- a/webapp/channels/src/utils/constants.tsx +++ b/webapp/channels/src/utils/constants.tsx @@ -2020,6 +2020,8 @@ export const Constants = { PERMISSIONS_DELETE_POST_TEAM_ADMIN: 'team_admin', PERMISSIONS_DELETE_POST_SYSTEM_ADMIN: 'system_admin', PERMISSIONS_SYSTEM_CUSTOM_GROUP_ADMIN: 'system_custom_group_admin', + PERMISSIONS_SHARED_CHANNEL_MANAGER: 'shared_channel_manager', + PERMISSIONS_SECURE_CONNECTION_MANAGER: 'secure_connection_manager', ALLOW_EDIT_POST_ALWAYS: 'always', ALLOW_EDIT_POST_NEVER: 'never', ALLOW_EDIT_POST_TIME_LIMIT: 'time_limit',