Restrict group_constrained to channels that support group sync (#36812) (#36886)

Automatic Merge
This commit is contained in:
Mattermost Build
2026-06-04 10:54:05 +02:00
committed by GitHub
parent 0c1e150c31
commit ea0ffa1714
6 changed files with 103 additions and 0 deletions
+5
View File
@@ -432,6 +432,11 @@ func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if patch.GroupConstrained != nil && !oldChannel.SupportsGroupSync() {
c.Err = model.NewAppError("patchChannel", "api.channel.patch_update_channel.group_constrained_not_allowed.app_error", nil, "", http.StatusBadRequest)
return
}
switch oldChannel.Type {
case model.ChannelTypeOpen:
if updatingProperties {
+30
View File
@@ -999,6 +999,36 @@ func TestPatchChannel(t *testing.T) {
CheckBadRequestStatus(t, resp)
})
t.Run("Should block setting group_constrained on group and direct messages", func(t *testing.T) {
user1 := th.CreateUser(t)
user2 := th.CreateUser(t)
user3 := th.CreateUser(t)
_, err := client.Logout(context.Background())
require.NoError(t, err)
_, _, err = client.Login(context.Background(), user1.Email, user1.Password)
require.NoError(t, err)
groupChannel, _, err := client.CreateGroupChannel(context.Background(), []string{user1.Id, user2.Id, user3.Id})
require.NoError(t, err)
patch := &model.ChannelPatch{GroupConstrained: model.NewPointer(true)}
_, resp, err := client.PatchChannel(context.Background(), groupChannel.Id, patch)
require.Error(t, err)
CheckBadRequestStatus(t, resp)
stats, _, err := client.GetChannelStats(context.Background(), groupChannel.Id, "", false)
require.NoError(t, err)
require.Equal(t, int64(3), stats.MemberCount)
directChannel, _, err := client.CreateDirectChannel(context.Background(), user1.Id, user2.Id)
require.NoError(t, err)
_, resp, err = client.PatchChannel(context.Background(), directChannel.Id, patch)
require.Error(t, err)
CheckBadRequestStatus(t, resp)
})
t.Run("Should block changes to default_category_name for group messages", func(t *testing.T) {
user1 := th.CreateUser(t)
user2 := th.CreateUser(t)
@@ -1289,6 +1289,8 @@ func (s *SqlGroupStore) ChannelMembersToRemove(channelID *string) ([]*model.Chan
Join("Channels ON Channels.Id = ChannelMembers.ChannelId").
LeftJoin("Bots ON Bots.UserId = ChannelMembers.UserId").
Where(sq.Eq{"Channels.DeleteAt": 0, "Channels.GroupConstrained": true, "Bots.UserId": nil}).
// Only public/private channels support group sync; never treat other channel members as removable.
Where(sq.Eq{"Channels.Type": []model.ChannelType{model.ChannelTypeOpen, model.ChannelTypePrivate}}).
Where(whereStmt)
if channelID != nil {
+8
View File
@@ -547,6 +547,10 @@
"id": "api.channel.patch_update_channel.forbidden.app_error",
"translation": "Failed to update the channel."
},
{
"id": "api.channel.patch_update_channel.group_constrained_not_allowed.app_error",
"translation": "You are not allowed to set group_constrained on this channel type."
},
{
"id": "api.channel.patch_update_channel.no_changes.app_error",
"translation": "No changes in the patch."
@@ -10942,6 +10946,10 @@
"id": "model.channel.is_valid.display_name.app_error",
"translation": "Invalid display name."
},
{
"id": "model.channel.is_valid.group_constrained.app_error",
"translation": "Only public and private channels can be group constrained."
},
{
"id": "model.channel.is_valid.header.app_error",
"translation": "Invalid header."
+9
View File
@@ -376,6 +376,10 @@ func (o *Channel) IsValid() *AppError {
return NewAppError("Channel.IsValid", "model.channel.is_valid.discoverable.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if o.IsGroupConstrained() && !o.SupportsGroupSync() {
return NewAppError("Channel.IsValid", "model.channel.is_valid.group_constrained.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
return nil
}
@@ -422,6 +426,11 @@ func (o *Channel) IsGroupOrDirect() bool {
return o.Type == ChannelTypeDirect || o.Type == ChannelTypeGroup
}
// SupportsGroupSync reports whether group_constrained is meaningful for the channel type.
func (o *Channel) SupportsGroupSync() bool {
return o.Type == ChannelTypeOpen || o.Type == ChannelTypePrivate
}
func (o *Channel) IsOpen() bool {
return o.Type == ChannelTypeOpen
}
+49
View File
@@ -93,6 +93,55 @@ func TestChannelIsValidDiscoverable(t *testing.T) {
})
}
func TestChannelSupportsGroupSync(t *testing.T) {
require.True(t, (&Channel{Type: ChannelTypeOpen}).SupportsGroupSync())
require.True(t, (&Channel{Type: ChannelTypePrivate}).SupportsGroupSync())
require.False(t, (&Channel{Type: ChannelTypeDirect}).SupportsGroupSync())
require.False(t, (&Channel{Type: ChannelTypeGroup}).SupportsGroupSync())
require.False(t, (&Channel{Type: ChannelTypeOpenBoard}).SupportsGroupSync())
require.False(t, (&Channel{Type: ChannelTypePrivateBoard}).SupportsGroupSync())
}
func TestChannelIsValidGroupConstrained(t *testing.T) {
base := Channel{
Id: NewId(),
CreateAt: GetMillis(),
UpdateAt: GetMillis(),
DisplayName: "x",
Name: "valid-name",
Header: "h",
Purpose: "p",
}
t.Run("group_constrained is allowed on public and private channels", func(t *testing.T) {
c := base
c.GroupConstrained = NewPointer(true)
c.Type = ChannelTypeOpen
require.Nil(t, c.IsValid())
c.Type = ChannelTypePrivate
require.Nil(t, c.IsValid())
})
t.Run("group_constrained is rejected on direct, group, and board channels", func(t *testing.T) {
c := base
c.GroupConstrained = NewPointer(true)
c.Type = ChannelTypeDirect
require.NotNil(t, c.IsValid())
c.Type = ChannelTypeGroup
require.NotNil(t, c.IsValid())
c.Type = ChannelTypeOpenBoard
require.NotNil(t, c.IsValid())
c.Type = ChannelTypePrivateBoard
require.NotNil(t, c.IsValid())
})
}
func TestChannelIsValid(t *testing.T) {
o := Channel{}