Add guards to avoid cards being created when the integrated boards feature flag is disabled (#35836)
Some checks are pending
API / build (push) Waiting to run
Server CI / Compute Go Version (push) Waiting to run
Server CI / Check mocks (push) Blocked by required conditions
Server CI / Check go mod tidy (push) Blocked by required conditions
Server CI / check-style (push) Blocked by required conditions
Server CI / Check serialization methods for hot structs (push) Blocked by required conditions
Server CI / Vet API (push) Blocked by required conditions
Server CI / Check migration files (push) Blocked by required conditions
Server CI / Generate email templates (push) Blocked by required conditions
Server CI / Check store layers (push) Blocked by required conditions
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres with binary parameters (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Postgres (FIPS) (push) Blocked by required conditions
Server CI / Generate Test Coverage (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions

This commit is contained in:
Miguel de la Cruz 2026-03-27 16:51:29 +01:00 committed by GitHub
parent a19cc4b909
commit dad9cab483
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 139 additions and 13 deletions

View file

@ -85,6 +85,11 @@ func createPostChecks(where string, c *Context, post *model.Post) {
return
}
postCardTypeCheckWithContext(where, c, post.Type)
if c.Err != nil {
return
}
postBurnOnReadCheckWithContext(where, c, post, nil)
}

View file

@ -222,19 +222,6 @@ func TestCreatePost(t *testing.T) {
assert.Nil(t, rpost)
})
t.Run("with type card", func(t *testing.T) {
cardPost, resp, err := client.CreatePost(context.Background(), &model.Post{
ChannelId: th.BasicChannel.Id,
Message: "card post",
Type: model.PostTypeCard,
})
require.NoError(t, err)
CheckCreatedStatus(t, resp)
require.NotNil(t, cardPost)
assert.Equal(t, model.PostTypeCard, cardPost.Type)
assert.Equal(t, "card post", cardPost.Message)
})
t.Run("invalid post type", func(t *testing.T) {
post := basicPost()
post.Type = model.PostTypeSystemGeneric
@ -6751,3 +6738,57 @@ func TestPatchCardPostByNonOwner(t *testing.T) {
CheckForbiddenStatus(t, resp)
})
}
func TestCreateCardPostWithFeatureFlagDisabled(t *testing.T) {
mainHelper.Parallel(t)
t.Run("card post rejected when IntegratedBoards flag is disabled", func(t *testing.T) {
th := SetupConfig(t, func(cfg *model.Config) {
cfg.FeatureFlags.IntegratedBoards = false
}).InitBasic(t)
client := th.Client
post := &model.Post{
ChannelId: th.BasicChannel.Id,
Message: "this is a card",
Type: model.PostTypeCard,
}
_, resp, err := client.CreatePost(context.Background(), post)
require.Error(t, err)
CheckBadRequestStatus(t, resp)
})
t.Run("card post allowed when IntegratedBoards flag is enabled", func(t *testing.T) {
th := SetupConfig(t, func(cfg *model.Config) {
cfg.FeatureFlags.IntegratedBoards = true
}).InitBasic(t)
client := th.Client
post := &model.Post{
ChannelId: th.BasicChannel.Id,
Message: "card post",
Type: model.PostTypeCard,
}
rpost, resp, err := client.CreatePost(context.Background(), post)
require.NoError(t, err)
CheckCreatedStatus(t, resp)
require.NotNil(t, rpost)
assert.Equal(t, model.PostTypeCard, rpost.Type)
assert.Equal(t, "card post", rpost.Message)
})
t.Run("non-card post allowed when IntegratedBoards flag is disabled", func(t *testing.T) {
th := SetupConfig(t, func(cfg *model.Config) {
cfg.FeatureFlags.IntegratedBoards = false
}).InitBasic(t)
client := th.Client
post := &model.Post{
ChannelId: th.BasicChannel.Id,
Message: "this is a regular post",
}
rpost, _, err := client.CreatePost(context.Background(), post)
require.NoError(t, err)
assert.Equal(t, "", rpost.Type)
})
}

View file

@ -42,6 +42,13 @@ func postPriorityCheckWithContext(where string, c *Context, priority *model.Post
}
}
func postCardTypeCheckWithContext(where string, c *Context, postType string) {
if appErr := app.PostCardTypeCheckWithApp(where, c.App, postType); appErr != nil {
appErr.Where = where
c.Err = appErr
}
}
func postBurnOnReadCheckWithContext(where string, c *Context, post *model.Post, channel *model.Channel) {
appErr := app.PostBurnOnReadCheckWithApp(where, c.App, c.AppContext, post.UserId, post.ChannelId, post.Type, channel)
if appErr != nil {

View file

@ -44,6 +44,11 @@ func scheduledPostChecks(where string, c *Context, scheduledPost *model.Schedule
return
}
postCardTypeCheckWithContext(where, c, scheduledPost.Type)
if c.Err != nil {
return
}
// Validate burn-on-read restrictions for scheduled post
post := &model.Post{
ChannelId: scheduledPost.ChannelId,

View file

@ -115,6 +115,15 @@ func userCreatePostPermissionCheckWithApp(rctx request.CTX, a *App, userId, chan
return nil
}
// PostCardTypeCheckWithApp validates whether a card post can be created
// based on the IntegratedBoards feature flag.
func PostCardTypeCheckWithApp(where string, a *App, postType string) *model.AppError {
if postType == model.PostTypeCard && !a.Config().FeatureFlags.IntegratedBoards {
return model.NewAppError(where, "api.post.create_post.card_type_disabled.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
// PostBurnOnReadCheckWithApp validates whether a burn-on-read post can be created
// based on channel type and participants. This is called from the API layer before
// post creation to enforce burn-on-read restrictions.

View file

@ -0,0 +1,43 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"testing"
"github.com/mattermost/mattermost/server/public/model"
"github.com/stretchr/testify/assert"
)
func TestPostCardTypeCheckWithApp(t *testing.T) {
mainHelper.Parallel(t)
t.Run("returns error for card post when IntegratedBoards is disabled", func(t *testing.T) {
th := SetupConfig(t, func(cfg *model.Config) {
cfg.FeatureFlags.IntegratedBoards = false
})
appErr := PostCardTypeCheckWithApp("test", th.App, model.PostTypeCard)
assert.NotNil(t, appErr)
assert.Equal(t, "api.post.create_post.card_type_disabled.app_error", appErr.Id)
})
t.Run("returns nil for card post when IntegratedBoards is enabled", func(t *testing.T) {
th := SetupConfig(t, func(cfg *model.Config) {
cfg.FeatureFlags.IntegratedBoards = true
})
appErr := PostCardTypeCheckWithApp("test", th.App, model.PostTypeCard)
assert.Nil(t, appErr)
})
t.Run("returns nil for non-card post when IntegratedBoards is disabled", func(t *testing.T) {
th := SetupConfig(t, func(cfg *model.Config) {
cfg.FeatureFlags.IntegratedBoards = false
})
appErr := PostCardTypeCheckWithApp("test", th.App, "")
assert.Nil(t, appErr)
})
}

View file

@ -314,6 +314,18 @@ func (a *App) canPostScheduledPost(rctx request.CTX, scheduledPost *model.Schedu
return model.ScheduledPostErrorInvalidPost, nil
}
if appErr := PostCardTypeCheckWithApp("ScheduledPostJob.postChecks", a, scheduledPost.Type); appErr != nil {
rctx.Logger().Debug(
"canPostScheduledPost card type disabled",
mlog.String("scheduled_post_id", scheduledPost.Id),
mlog.String("user_id", scheduledPost.UserId),
mlog.String("channel_id", scheduledPost.ChannelId),
mlog.String("error_code", model.ScheduledPostErrorInvalidPost),
mlog.Err(appErr),
)
return model.ScheduledPostErrorInvalidPost, nil
}
// Validate burn-on-read restrictions for scheduled post
if appErr := PostBurnOnReadCheckWithApp("ScheduledPostJob.postChecks", a, rctx, scheduledPost.UserId, scheduledPost.ChannelId, scheduledPost.Type, channel); appErr != nil {
rctx.Logger().Debug(

View file

@ -2780,6 +2780,10 @@
"id": "api.post.create_post.can_not_post_to_deleted.error",
"translation": "Can not post to deleted channel."
},
{
"id": "api.post.create_post.card_type_disabled.app_error",
"translation": "Card posts are not enabled on this server."
},
{
"id": "api.post.create_post.channel_root_id.app_error",
"translation": "Invalid ChannelId for RootId parameter."