From a5e68639c2b666f157a3c8eed9da12fb059cbfbf Mon Sep 17 00:00:00 2001 From: Harshil Sharma <18575143+harshilsharma63@users.noreply.github.com> Date: Tue, 6 May 2025 14:03:35 +0530 Subject: [PATCH] Channel banner permissions (#30917) * Fixed save state panel for channel banner * Defined default background color * Updated test * WIP * wip * removed unused param * Updated tests * CI * Fixed mmctl test * Fixed TestDoAdvancedPermissionsMigration test * Test update * lint fix * lint fix --------- Co-authored-by: Mattermost Build --- server/channels/api4/channel.go | 28 +- server/channels/api4/channel_test.go | 261 +++++++++++++----- server/channels/app/app_test.go | 4 + server/channels/app/permissions_migrations.go | 17 ++ server/channels/app/platform/web_hub_test.go | 1 + server/channels/testlib/store.go | 2 + server/cmd/mmctl/commands/permissions_test.go | 2 + server/public/model/migration.go | 1 + server/public/model/permission.go | 18 ++ server/public/model/role.go | 4 + .../permission_group.tsx | 1 + .../permission_system_scheme_settings.tsx | 1 + .../permissions_tree/permissions_tree.tsx | 11 +- .../strings/permissions.tsx | 20 ++ .../channel_settings_modal.tsx | 12 +- webapp/channels/src/i18n/en.json | 4 + .../src/constants/permissions.ts | 2 + webapp/channels/src/utils/constants.tsx | 6 + webapp/channels/src/utils/license_utils.ts | 8 + 19 files changed, 319 insertions(+), 84 deletions(-) diff --git a/server/channels/api4/channel.go b/server/channels/api4/channel.go index d00ef14ff2..4f1c940ab5 100644 --- a/server/channels/api4/channel.go +++ b/server/channels/api4/channel.go @@ -378,9 +378,8 @@ func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) { } if patch.BannerInfo != nil { - if channelBannerAppErr := canEditChannelBanner(c.App.License(), originalOldChannel); channelBannerAppErr != nil { - channelBannerAppErr.Where = "patchChannel" - c.Err = channelBannerAppErr + canEditChannelBanner(c, originalOldChannel) + if c.Err != nil { return } } @@ -2459,14 +2458,23 @@ func convertGroupMessageToChannel(c *Context, w http.ResponseWriter, r *http.Req } } -func canEditChannelBanner(license *model.License, originalChannel *model.Channel) *model.AppError { - if !model.MinimumEnterpriseAdvancedLicense(license) { - return model.NewAppError("", "license_error.feature_unavailable.specific", map[string]any{"Feature": "Channel Banner"}, "feature is not available for the current license", http.StatusForbidden) +func canEditChannelBanner(c *Context, originalChannel *model.Channel) { + if !model.MinimumEnterpriseAdvancedLicense(c.App.License()) { + c.Err = model.NewAppError("patchChannel", "license_error.feature_unavailable.specific", map[string]any{"Feature": "Channel Banner"}, "feature is not available for the current license", http.StatusForbidden) } - if originalChannel.Type != model.ChannelTypeOpen && originalChannel.Type != model.ChannelTypePrivate { - return model.NewAppError("", "api.channel.update_channel.banner_info.channel_type.not_allowed", nil, "", http.StatusBadRequest) + switch originalChannel.Type { + case model.ChannelTypePrivate: + if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePrivateChannelBanner) { + c.SetPermissionError(model.PermissionManagePrivateChannelBanner) + return + } + case model.ChannelTypeOpen: + if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePublicChannelBanner) { + c.SetPermissionError(model.PermissionManagePublicChannelBanner) + return + } + default: + c.Err = model.NewAppError("patchChannel", "api.channel.update_channel.banner_info.channel_type.not_allowed", nil, "", http.StatusBadRequest) } - - return nil } diff --git a/server/channels/api4/channel_test.go b/server/channels/api4/channel_test.go index 5614a13d03..0e191e863f 100644 --- a/server/channels/api4/channel_test.go +++ b/server/channels/api4/channel_test.go @@ -14,6 +14,8 @@ import ( "testing" "time" + "github.com/mattermost/mattermost/server/v8/channels/web" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -842,6 +844,52 @@ func TestPatchChannel(t *testing.T) { require.Equal(t, "color", *patchedChannel.BannerInfo.BackgroundColor) }) + t.Run("Should not be able to configure channel banner on a channel as a non-admin channel member", func(t *testing.T) { + client.Logout(context.Background()) + th.LoginBasic() + th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced)) + defer func() { + th.App.Srv().RemoveLicense() + }() + + patch := &model.ChannelPatch{ + BannerInfo: &model.ChannelBannerInfo{ + Enabled: model.NewPointer(true), + Text: model.NewPointer("banner text"), + BackgroundColor: model.NewPointer("color"), + }, + } + + _, resp, err := client.PatchChannel(context.Background(), th.BasicChannel.Id, patch) + require.Error(t, err) + CheckForbiddenStatus(t, resp) + }) + + t.Run("Should be able to configure channel banner as a team admin", func(t *testing.T) { + client.Logout(context.Background()) + th.LoginTeamAdmin() + th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced)) + defer func() { + th.App.Srv().RemoveLicense() + }() + + patch := &model.ChannelPatch{ + BannerInfo: &model.ChannelBannerInfo{ + Enabled: model.NewPointer(true), + Text: model.NewPointer("banner text"), + BackgroundColor: model.NewPointer("color"), + }, + } + + patchedChannel, resp, err := client.PatchChannel(context.Background(), th.BasicChannel2.Id, patch) + require.NoError(t, err) + CheckOKStatus(t, resp) + require.NotNil(t, patchedChannel.BannerInfo) + require.True(t, *patchedChannel.BannerInfo.Enabled) + require.Equal(t, "banner text", *patchedChannel.BannerInfo.Text) + require.Equal(t, "color", *patchedChannel.BannerInfo.BackgroundColor) + }) + t.Run("Cannot enable channel banner without configuring it", func(t *testing.T) { client.Logout(context.Background()) th.LoginBasic() @@ -958,6 +1006,147 @@ func TestPatchChannel(t *testing.T) { }) } +func TestCanEditChannelBanner(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + t.Run("when license is nil", func(t *testing.T) { + channel := &model.Channel{ + Type: model.ChannelTypeOpen, + } + + th.App.Srv().SetLicense(nil) + + webContext := &Context{ + App: th.App, + AppContext: th.Context, + Params: &web.Params{ + ChannelId: "channel_id", + }, + } + + canEditChannelBanner(webContext, channel) + + require.NotNil(t, webContext.Err) + assert.Equal(t, "api.context.permissions.app_error", webContext.Err.Id) + assert.Equal(t, http.StatusForbidden, webContext.Err.StatusCode) + }) + + t.Run("when license is not E20 or Enterprise", func(t *testing.T) { + license := model.NewTestLicenseSKU(model.LicenseShortSkuProfessional) + th.App.Srv().SetLicense(license) + + webContext := &Context{ + App: th.App, + AppContext: th.Context, + Params: &web.Params{ + ChannelId: "channel_id", + }, + } + + channel := &model.Channel{ + Type: model.ChannelTypeOpen, + } + + canEditChannelBanner(webContext, channel) + + require.NotNil(t, webContext.Err) + assert.Equal(t, "api.context.permissions.app_error", webContext.Err.Id) + assert.Equal(t, http.StatusForbidden, webContext.Err.StatusCode) + }) + + t.Run("when channel type is direct message", func(t *testing.T) { + license := model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced) + th.App.Srv().SetLicense(license) + + webContext := &Context{ + App: th.App, + AppContext: th.Context, + Params: &web.Params{ + ChannelId: "channel_id", + }, + } + + channel := &model.Channel{ + Type: model.ChannelTypeDirect, + } + + canEditChannelBanner(webContext, channel) + + require.NotNil(t, webContext.Err) + assert.Equal(t, "api.channel.update_channel.banner_info.channel_type.not_allowed", webContext.Err.Id) + assert.Equal(t, http.StatusBadRequest, webContext.Err.StatusCode) + }) + + t.Run("when channel type is group message", func(t *testing.T) { + license := model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced) + th.App.Srv().SetLicense(license) + + webContext := &Context{ + App: th.App, + AppContext: th.Context, + Params: &web.Params{ + ChannelId: "channel_id", + }, + } + + channel := &model.Channel{ + Type: model.ChannelTypeGroup, + } + + canEditChannelBanner(webContext, channel) + require.NotNil(t, webContext.Err) + assert.Equal(t, "api.channel.update_channel.banner_info.channel_type.not_allowed", webContext.Err.Id) + assert.Equal(t, http.StatusBadRequest, webContext.Err.StatusCode) + }) + + t.Run("when channel type is open and license is valid", func(t *testing.T) { + license := model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced) + th.App.Srv().SetLicense(license) + + channel := th.CreatePublicChannel() + th.MakeUserChannelAdmin(th.BasicUser, channel) + + webContext := &Context{ + App: th.App, + AppContext: th.Context, + Params: &web.Params{ + ChannelId: channel.Id, + }, + } + + webContext.AppContext = webContext.AppContext.WithSession(&model.Session{ + UserId: th.BasicUser.Id, + }) + + canEditChannelBanner(webContext, channel) + assert.Nil(t, webContext.Err) + }) + + t.Run("when channel type is private and license is valid", func(t *testing.T) { + license := model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced) + th.App.Srv().SetLicense(license) + + channel := th.CreatePrivateChannel() + th.MakeUserChannelAdmin(th.BasicUser, channel) + + webContext := &Context{ + App: th.App, + AppContext: th.Context, + Params: &web.Params{ + ChannelId: channel.Id, + }, + } + + webContext.AppContext = webContext.AppContext.WithSession(&model.Session{ + UserId: th.BasicUser.Id, + }) + + canEditChannelBanner(webContext, channel) + assert.Nil(t, webContext.Err) + }) +} + func TestChannelUnicodeNames(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() @@ -5737,75 +5926,3 @@ func TestViewChannelWithoutCollapsedThreads(t *testing.T) { require.NoError(t, err) require.Zero(t, threads.TotalUnreadMentions) } - -func TestCanEditChannelBanner(t *testing.T) { - t.Run("when license is nil", func(t *testing.T) { - channel := &model.Channel{ - Type: model.ChannelTypeOpen, - } - - err := canEditChannelBanner(nil, channel) - - require.NotNil(t, err) - assert.Equal(t, "license_error.feature_unavailable.specific", err.Id) - assert.Equal(t, http.StatusForbidden, err.StatusCode) - }) - - t.Run("when license is not E20 or Enterprise", func(t *testing.T) { - license := model.NewTestLicenseSKU(model.LicenseShortSkuProfessional) - channel := &model.Channel{ - Type: model.ChannelTypeOpen, - } - - err := canEditChannelBanner(license, channel) - - require.NotNil(t, err) - assert.Equal(t, "license_error.feature_unavailable.specific", err.Id) - assert.Equal(t, http.StatusForbidden, err.StatusCode) - }) - - t.Run("when channel type is direct message", func(t *testing.T) { - license := model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced) - channel := &model.Channel{ - Type: model.ChannelTypeDirect, - } - - err := canEditChannelBanner(license, channel) - - require.NotNil(t, err) - assert.Equal(t, "api.channel.update_channel.banner_info.channel_type.not_allowed", err.Id) - assert.Equal(t, http.StatusBadRequest, err.StatusCode) - }) - - t.Run("when channel type is group message", func(t *testing.T) { - license := model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced) - channel := &model.Channel{ - Type: model.ChannelTypeGroup, - } - - err := canEditChannelBanner(license, channel) - require.NotNil(t, err) - assert.Equal(t, "api.channel.update_channel.banner_info.channel_type.not_allowed", err.Id) - assert.Equal(t, http.StatusBadRequest, err.StatusCode) - }) - - t.Run("when channel type is open and license is valid", func(t *testing.T) { - license := model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced) - channel := &model.Channel{ - Type: model.ChannelTypeOpen, - } - - err := canEditChannelBanner(license, channel) - assert.Nil(t, err) - }) - - t.Run("when channel type is private and license is valid", func(t *testing.T) { - license := model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced) - channel := &model.Channel{ - Type: model.ChannelTypePrivate, - } - - err := canEditChannelBanner(license, channel) - assert.Nil(t, err) - }) -} diff --git a/server/channels/app/app_test.go b/server/channels/app/app_test.go index 510f575dcc..8e20c34c04 100644 --- a/server/channels/app/app_test.go +++ b/server/channels/app/app_test.go @@ -148,6 +148,8 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PermissionEditBookmarkPrivateChannel.Id, model.PermissionDeleteBookmarkPrivateChannel.Id, model.PermissionOrderBookmarkPrivateChannel.Id, + model.PermissionManagePublicChannelBanner.Id, + model.PermissionManagePrivateChannelBanner.Id, }, "team_user": { model.PermissionListTeamChannels.Id, @@ -191,6 +193,8 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PermissionEditBookmarkPrivateChannel.Id, model.PermissionDeleteBookmarkPrivateChannel.Id, model.PermissionOrderBookmarkPrivateChannel.Id, + model.PermissionManagePublicChannelBanner.Id, + model.PermissionManagePrivateChannelBanner.Id, }, "system_user": { model.PermissionListPublicTeams.Id, diff --git a/server/channels/app/permissions_migrations.go b/server/channels/app/permissions_migrations.go index 9532aaa0f9..0c11af5ef1 100644 --- a/server/channels/app/permissions_migrations.go +++ b/server/channels/app/permissions_migrations.go @@ -1173,6 +1173,22 @@ func (a *App) addSysConsoleMobileSecurityPermission() (permissionsMap, error) { return transformations, nil } +func (a *App) getAddChannelBannerPermissionMigration() (permissionsMap, error) { + return permissionsMap{ + permissionTransformation{ + On: permissionOr( + isRole(model.ChannelAdminRoleId), + isRole(model.TeamAdminRoleId), + isRole(model.SystemAdminRoleId), + ), + Add: []string{ + model.PermissionManagePublicChannelBanner.Id, + model.PermissionManagePrivateChannelBanner.Id, + }, + }, + }, nil +} + // Only sysadmins, team admins, and users with channels and groups managements have access to "convert channel to public" func (a *App) getRestrictAcessToChannelConversionToPublic() (permissionsMap, error) { return []permissionTransformation{ @@ -1243,6 +1259,7 @@ func (s *Server) doPermissionsMigrations() error { {Key: model.MigrationKeyFixReadAuditsPermission, Migration: a.getFixReadAuditsPermissionMigration}, {Key: model.MigrationRemoveGetAnalyticsPermission, Migration: a.removeGetAnalyticsPermissionMigration}, {Key: model.MigrationAddSysconsoleMobileSecurityPermission, Migration: a.addSysConsoleMobileSecurityPermission}, + {Key: model.MigrationKeyAddChannelBannerPermissions, Migration: a.getAddChannelBannerPermissionMigration}, } roles, err := s.Store().Role().GetAll() diff --git a/server/channels/app/platform/web_hub_test.go b/server/channels/app/platform/web_hub_test.go index f4c78a635f..532ebf3156 100644 --- a/server/channels/app/platform/web_hub_test.go +++ b/server/channels/app/platform/web_hub_test.go @@ -153,6 +153,7 @@ func TestHubSessionRevokeRace(t *testing.T) { time.Sleep(2 * time.Second) // We override the LastActivityAt which happens in NewWebConn. // This is needed to call RevokeSessionById which triggers the race. + err = th.Service.AddSessionToCache(session) require.NoError(t, err) diff --git a/server/channels/testlib/store.go b/server/channels/testlib/store.go index 0482a10da1..bfcb5588f6 100644 --- a/server/channels/testlib/store.go +++ b/server/channels/testlib/store.go @@ -86,6 +86,8 @@ func GetMockStoreForSetupFunctions() *mocks.Store { 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) + systemStore.On("GetByName", model.MigrationKeyAddChannelBannerPermissions).Return(&model.System{Name: model.MigrationKeyAddChannelBannerPermissions, Value: "true"}, nil) + systemStore.On("InsertIfExists", mock.AnythingOfType("*model.System")).Return(&model.System{}, nil).Once() systemStore.On("Save", mock.AnythingOfType("*model.System")).Return(nil) diff --git a/server/cmd/mmctl/commands/permissions_test.go b/server/cmd/mmctl/commands/permissions_test.go index 7e457a7983..506f9e507a 100644 --- a/server/cmd/mmctl/commands/permissions_test.go +++ b/server/cmd/mmctl/commands/permissions_test.go @@ -246,6 +246,8 @@ func (s *MmctlUnitTestSuite) TestResetPermissionsCmd() { "edit_bookmark_private_channel", "delete_bookmark_private_channel", "order_bookmark_private_channel", + "manage_public_channel_banner", + "manage_private_channel_banner", } expectedPatch := &model.RolePatch{ Permissions: &expectedPermissions, diff --git a/server/public/model/migration.go b/server/public/model/migration.go index a561c735c4..b4756f4e44 100644 --- a/server/public/model/migration.go +++ b/server/public/model/migration.go @@ -54,4 +54,5 @@ const ( MigrationKeyFixReadAuditsPermission = "fix_read_audits_permission" MigrationRemoveGetAnalyticsPermission = "remove_get_analytics_permission" MigrationAddSysconsoleMobileSecurityPermission = "add_sysconsole_mobile_security_permission" + MigrationKeyAddChannelBannerPermissions = "add_channel_banner_permissions" ) diff --git a/server/public/model/permission.go b/server/public/model/permission.go index 3841166043..863ecc4c57 100644 --- a/server/public/model/permission.go +++ b/server/public/model/permission.go @@ -167,6 +167,8 @@ var PermissionGetLogs *Permission var PermissionGetAnalytics *Permission var PermissionReadLicenseInformation *Permission var PermissionManageLicenseInformation *Permission +var PermissionManagePublicChannelBanner *Permission +var PermissionManagePrivateChannelBanner *Permission var PermissionSysconsoleReadAbout *Permission var PermissionSysconsoleWriteAbout *Permission @@ -1283,6 +1285,20 @@ func initializePermissions() { PermissionScopeChannel, } + PermissionManagePublicChannelBanner = &Permission{ + "manage_public_channel_banner", + "", + "", + PermissionScopeChannel, + } + + PermissionManagePrivateChannelBanner = &Permission{ + "manage_private_channel_banner", + "", + "", + PermissionScopeChannel, + } + PermissionReadOtherUsersTeams = &Permission{ "read_other_users_teams", "authentication.permissions.read_other_users_teams.name", @@ -2520,6 +2536,8 @@ func initializePermissions() { PermissionEditBookmarkPrivateChannel, PermissionDeleteBookmarkPrivateChannel, PermissionOrderBookmarkPrivateChannel, + PermissionManagePublicChannelBanner, + PermissionManagePrivateChannelBanner, } GroupScopedPermissions := []*Permission{ diff --git a/server/public/model/role.go b/server/public/model/role.go index 8d800acce1..4ffa9f94be 100644 --- a/server/public/model/role.go +++ b/server/public/model/role.go @@ -914,6 +914,8 @@ func MakeDefaultRoles() map[string]*Role { PermissionEditBookmarkPrivateChannel.Id, PermissionDeleteBookmarkPrivateChannel.Id, PermissionOrderBookmarkPrivateChannel.Id, + PermissionManagePublicChannelBanner.Id, + PermissionManagePrivateChannelBanner.Id, }, SchemeManaged: true, BuiltIn: true, @@ -1000,6 +1002,8 @@ func MakeDefaultRoles() map[string]*Role { PermissionEditBookmarkPrivateChannel.Id, PermissionDeleteBookmarkPrivateChannel.Id, PermissionOrderBookmarkPrivateChannel.Id, + PermissionManagePublicChannelBanner.Id, + PermissionManagePrivateChannelBanner.Id, }, SchemeManaged: true, BuiltIn: true, diff --git a/webapp/channels/src/components/admin_console/permission_schemes_settings/permission_group.tsx b/webapp/channels/src/components/admin_console/permission_schemes_settings/permission_group.tsx index 223a89ea06..7c833e8d4a 100644 --- a/webapp/channels/src/components/admin_console/permission_schemes_settings/permission_group.tsx +++ b/webapp/channels/src/components/admin_console/permission_schemes_settings/permission_group.tsx @@ -147,6 +147,7 @@ export default class PermissionGroup extends React.PureComponent { if (!this.isInScope(permission)) { return null; } + const comesFromParent = this.fromParent(permission); const active = comesFromParent || this.props.role?.permissions?.indexOf(permission) !== -1; const inherited = comesFromParent ? this.props.parentRole : undefined; diff --git a/webapp/channels/src/components/admin_console/permission_schemes_settings/permission_system_scheme_settings/permission_system_scheme_settings.tsx b/webapp/channels/src/components/admin_console/permission_schemes_settings/permission_system_scheme_settings/permission_system_scheme_settings.tsx index 7e8d44e300..47fb574511 100644 --- a/webapp/channels/src/components/admin_console/permission_schemes_settings/permission_system_scheme_settings/permission_system_scheme_settings.tsx +++ b/webapp/channels/src/components/admin_console/permission_schemes_settings/permission_system_scheme_settings/permission_system_scheme_settings.tsx @@ -381,6 +381,7 @@ class PermissionSystemSchemeSettings extends React.PureComponent { if (!this.state.loaded) { return ; } + const isLicensed = this.props.license?.IsLicensed === 'true'; return (
diff --git a/webapp/channels/src/components/admin_console/permission_schemes_settings/permissions_tree/permissions_tree.tsx b/webapp/channels/src/components/admin_console/permission_schemes_settings/permissions_tree/permissions_tree.tsx index f15c967060..ef65859623 100644 --- a/webapp/channels/src/components/admin_console/permission_schemes_settings/permissions_tree/permissions_tree.tsx +++ b/webapp/channels/src/components/admin_console/permission_schemes_settings/permissions_tree/permissions_tree.tsx @@ -10,7 +10,11 @@ import type {Role} from '@mattermost/types/roles'; import GeneralConstants from 'mattermost-redux/constants/general'; import Permissions from 'mattermost-redux/constants/permissions'; -import {isEnterpriseLicense, isNonEnterpriseLicense} from 'utils/license_utils'; +import { + isEnterpriseLicense, + isMinimumEnterpriseAdvancedLicense, + isNonEnterpriseLicense, +} from 'utils/license_utils'; import type {AdditionalValues, Group} from './types'; @@ -286,6 +290,11 @@ export default class PermissionsTree extends React.PureComponent { }); } + if (isMinimumEnterpriseAdvancedLicense(license)) { + publicChannelsGroup.permissions.push(Permissions.MANAGE_PUBLIC_CHANNEL_BANNER); + privateChannelsGroup.permissions.push(Permissions.MANAGE_PRIVATE_CHANNEL_BANNER); + } + this.groups = this.groups.filter((group) => { if (group.isVisible) { return group.isVisible(this.props.license); diff --git a/webapp/channels/src/components/admin_console/permission_schemes_settings/strings/permissions.tsx b/webapp/channels/src/components/admin_console/permission_schemes_settings/strings/permissions.tsx index 1d629e0bbe..2ce60d406a 100644 --- a/webapp/channels/src/components/admin_console/permission_schemes_settings/strings/permissions.tsx +++ b/webapp/channels/src/components/admin_console/permission_schemes_settings/strings/permissions.tsx @@ -625,4 +625,24 @@ export const permissionRolesStrings: Record getChannel(state, channelId)) as Channel; - const shouldShowConfigurationTab = useSelector(selectChannelBannerEnabled); + const channelBannerEnabled = useSelector(selectChannelBannerEnabled); + + const canManagePublicChannelBanner = useSelector((state: GlobalState) => + haveIChannelPermission(state, channel.team_id, channel.id, Permissions.MANAGE_PUBLIC_CHANNEL_BANNER), + ); + const canManagePrivateChannelBanner = useSelector((state: GlobalState) => + haveIChannelPermission(state, channel.team_id, channel.id, Permissions.MANAGE_PRIVATE_CHANNEL_BANNER), + ); + const hasManageChannelBannerPermission = (channel.type === 'O' && canManagePublicChannelBanner) || (channel.type === 'P' && canManagePrivateChannelBanner); + + const shouldShowConfigurationTab = channelBannerEnabled && hasManageChannelBannerPermission; const canArchivePrivateChannels = useSelector((state: GlobalState) => haveIChannelPermission(state, channel.team_id, channel.id, Permissions.DELETE_PRIVATE_CHANNEL), diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index b6c0c9fc78..6d5f8984fe 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -1749,8 +1749,12 @@ "admin.permissions.permission.manage_outgoing_oauth_connections.name": "Manage Outgoing OAuth Credentials", "admin.permissions.permission.manage_outgoing_webhooks.description": "Create, edit, and delete outgoing webhooks.", "admin.permissions.permission.manage_outgoing_webhooks.name": "Manage Outgoing Webhooks", + "admin.permissions.permission.manage_private_channel_banner.description": "Enable, disable and edit channel banner.", + "admin.permissions.permission.manage_private_channel_banner.name": "Manage Channel Banner", "admin.permissions.permission.manage_private_channel_properties.description": "Update private channel names, headers and purposes.", "admin.permissions.permission.manage_private_channel_properties.name": "Manage Channel Settings", + "admin.permissions.permission.manage_public_channel_banner.description": "Enable, disable and edit channel banner.", + "admin.permissions.permission.manage_public_channel_banner.name": "Manage Channel Banner", "admin.permissions.permission.manage_public_channel_properties.description": "Update public channel names, headers and purposes.", "admin.permissions.permission.manage_public_channel_properties.name": "Manage Channel Settings", "admin.permissions.permission.manage_roles.description": "Manage roles", diff --git a/webapp/channels/src/packages/mattermost-redux/src/constants/permissions.ts b/webapp/channels/src/packages/mattermost-redux/src/constants/permissions.ts index 7379121394..8c0b5c1403 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/constants/permissions.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/constants/permissions.ts @@ -30,6 +30,8 @@ const values = { DELETE_PUBLIC_CHANNEL: 'delete_public_channel', CONVERT_PUBLIC_CHANNEL_TO_PRIVATE: 'convert_public_channel_to_private', CONVERT_PRIVATE_CHANNEL_TO_PUBLIC: 'convert_private_channel_to_public', + MANAGE_PUBLIC_CHANNEL_BANNER: 'manage_public_channel_banner', + MANAGE_PRIVATE_CHANNEL_BANNER: 'manage_private_channel_banner', DELETE_PRIVATE_CHANNEL: 'delete_private_channel', EDIT_OTHER_USERS: 'edit_other_users', READ_CHANNEL: 'read_channel', diff --git a/webapp/channels/src/utils/constants.tsx b/webapp/channels/src/utils/constants.tsx index 509c0b3170..2840bbf5ba 100644 --- a/webapp/channels/src/utils/constants.tsx +++ b/webapp/channels/src/utils/constants.tsx @@ -1307,6 +1307,8 @@ export const PermissionsScope = { [Permissions.EDIT_BOOKMARK_PRIVATE_CHANNEL]: 'channel_scope', [Permissions.DELETE_BOOKMARK_PRIVATE_CHANNEL]: 'channel_scope', [Permissions.ORDER_BOOKMARK_PRIVATE_CHANNEL]: 'channel_scope', + [Permissions.MANAGE_PUBLIC_CHANNEL_BANNER]: 'channel_scope', + [Permissions.MANAGE_PRIVATE_CHANNEL_BANNER]: 'channel_scope', }; export const DefaultRolePermissions = { @@ -1386,6 +1388,8 @@ export const DefaultRolePermissions = { Permissions.EDIT_BOOKMARK_PRIVATE_CHANNEL, Permissions.DELETE_BOOKMARK_PRIVATE_CHANNEL, Permissions.ORDER_BOOKMARK_PRIVATE_CHANNEL, + Permissions.MANAGE_PUBLIC_CHANNEL_BANNER, + Permissions.MANAGE_PRIVATE_CHANNEL_BANNER, ], team_admin: [ Permissions.EDIT_OTHERS_POSTS, @@ -1421,6 +1425,8 @@ export const DefaultRolePermissions = { Permissions.EDIT_BOOKMARK_PRIVATE_CHANNEL, Permissions.DELETE_BOOKMARK_PRIVATE_CHANNEL, Permissions.ORDER_BOOKMARK_PRIVATE_CHANNEL, + Permissions.MANAGE_PUBLIC_CHANNEL_BANNER, + Permissions.MANAGE_PRIVATE_CHANNEL_BANNER, ], guests: [ Permissions.EDIT_POST, diff --git a/webapp/channels/src/utils/license_utils.ts b/webapp/channels/src/utils/license_utils.ts index 88a7286e46..11b39950a0 100644 --- a/webapp/channels/src/utils/license_utils.ts +++ b/webapp/channels/src/utils/license_utils.ts @@ -128,3 +128,11 @@ export function isMinimumEnterpriseLicense(license: ClientLicense): boolean { return getLicenseTier(license.SkuShortName) >= getLicenseTier(LicenseSkus.Enterprise); } + +export function isMinimumEnterpriseAdvancedLicense(license?: ClientLicense): boolean { + if (!license) { + return false; + } + + return getLicenseTier(license.SkuShortName) >= getLicenseTier(LicenseSkus.EnterpriseAdvanced); +}