* 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 <build@mattermost.com>
Этот коммит содержится в:
Harshil Sharma
2025-05-06 14:03:35 +05:30
коммит произвёл GitHub
родитель d73222dca9
Коммит a5e68639c2
19 изменённых файлов: 319 добавлений и 84 удалений

Просмотреть файл

@@ -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
}

Просмотреть файл

@@ -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)
})
}

Просмотреть файл

@@ -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,

Просмотреть файл

@@ -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()

Просмотреть файл

@@ -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)

Просмотреть файл

@@ -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)