mirror of
https://github.com/mattermost/mattermost.git
synced 2026-09-24 16:05:00 +08:00
MM-67647 Add roles for shared channels management (#35354)
* Add shared_channel_manager and secure_connection_manager built-in roles Introduce two new delegated admin roles for granular Shared Channels permission management, allowing admins to assign shared channel and secure connection management to specific non-admin users without granting full System Admin or System Manager access. - shared_channel_manager: grants manage_shared_channels permission - secure_connection_manager: grants manage_secure_connections permission Includes server role definitions, app migrations, permissions migrations, System Console UI support, and API permission tests.
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
+42
-10
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -74,4 +74,32 @@ export const rolesStrings: Record<string, Record<string, MessageDescriptor>> = {
|
||||
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',
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
+64
@@ -243,6 +243,70 @@ export default class SystemRolePermissions extends React.PureComponent<Props, St
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.role.name === Constants.PERMISSIONS_SHARED_CHANNEL_MANAGER) {
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='admin.permissions.roles.shared_channel_manager.introduction'
|
||||
defaultMessage='The built-in Shared Channel Manager role can be used to delegate the ability to share and unshare channels with existing <a>connections to remote servers</a> to users other than the System Admin.'
|
||||
values={{
|
||||
a: (chunks) => (
|
||||
<ExternalLink
|
||||
href='https://docs.mattermost.com/administration-guide/onboard/connected-workspaces.html'
|
||||
location='adminConsoleSystemRoles'
|
||||
>
|
||||
{chunks}
|
||||
</ExternalLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='admin.permissions.roles.shared_channel_manager.permissions_info'
|
||||
defaultMessage='This role has the <b>manage_shared_channels</b> permission, which allows sharing and unsharing channels with existing connections to remote servers.'
|
||||
values={{
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.role.name === Constants.PERMISSIONS_SECURE_CONNECTION_MANAGER) {
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='admin.permissions.roles.secure_connection_manager.introduction'
|
||||
defaultMessage='The built-in Secure Connection Manager role can be used to delegate the ability to create, manage, and remove <a>secure connections</a> to remote servers to users other than the System Admin.'
|
||||
values={{
|
||||
a: (chunks) => (
|
||||
<ExternalLink
|
||||
href='https://docs.mattermost.com/administration-guide/onboard/connected-workspaces.html'
|
||||
location='adminConsoleSystemRoles'
|
||||
>
|
||||
{chunks}
|
||||
</ExternalLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='admin.permissions.roles.secure_connection_manager.permissions_info'
|
||||
defaultMessage='This role has the <b>manage_secure_connections</b> permission, which allows creating, editing, and deleting secure connections to remote servers.'
|
||||
values={{
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.role.name === Constants.PERMISSIONS_SYSTEM_USER_MANAGER) {
|
||||
let permissionsToShow: Record<string, boolean> = {};
|
||||
Object.keys(permissionsMap).forEach((permission) => {
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
}
|
||||
|
||||
.DataGrid_cell {
|
||||
text-overflow: unset;
|
||||
white-space: normal;
|
||||
|
||||
.SystemRoles_editRow {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
@@ -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 = () => {};
|
||||
|
||||
|
||||
@@ -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 <a>secure connections</a> 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 <b>manage_secure_connections</b> 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 <a>connections to remote servers</a> 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 <b>manage_shared_channels</b> 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",
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user