[MM-57988] Fix move thread logic to not block channel admins (#27061)

* mm-57988: Allowing for channel admins to move thread

* Fix the MoveThread team admin unit test that was introduced

* Renaming the hasPermittedRole function to hasPermittedWranglerRole

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
Matheus 2024-06-18 10:41:49 -07:00 committed by GitHub
parent 0da473b9f8
commit cbd5d95bbb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 97 additions and 21 deletions

View File

@ -1160,15 +1160,20 @@ func moveThread(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
// If there are no configured PermittedWranglerRoles, skip the check
userHasRole := len(c.App.Config().WranglerSettings.PermittedWranglerRoles) == 0
for _, role := range c.App.Config().WranglerSettings.PermittedWranglerRoles {
if user.IsInRole(role) {
userHasRole = true
break
}
posts, _, err := c.App.GetPostsByIds([]string{c.Params.PostId})
if err != nil {
c.Err = err
return
}
channelMember, err := c.App.GetChannelMember(c.AppContext, posts[0].ChannelId, user.Id)
if err != nil {
c.Err = err
return
}
userHasRole := hasPermittedWranglerRole(c, user, channelMember)
// Sysadmins are always permitted
if !userHasRole && !user.IsSystemAdmin() {
c.Err = model.NewAppError("moveThread", "api.post.move_thread.no_permission", nil, "", http.StatusForbidden)
@ -1267,3 +1272,19 @@ func getPostInfo(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write(js)
}
func hasPermittedWranglerRole(c *Context, user *model.User, channelMember *model.ChannelMember) bool {
// If there are no configured PermittedWranglerRoles, skip the check
if len(c.App.Config().WranglerSettings.PermittedWranglerRoles) == 0 {
return true
}
userRoles := user.Roles + " " + channelMember.Roles
for _, role := range c.App.Config().WranglerSettings.PermittedWranglerRoles {
if model.IsInRole(userRoles, role) {
return true
}
}
return false
}

View File

@ -761,16 +761,20 @@ func TestMoveThread(t *testing.T) {
basicUser2 := th.BasicUser2
basicUser3 := th.CreateUser()
// Create a new public channel to move the post to
publicChannel, resp, err := client.CreateChannel(ctx, &model.Channel{
TeamId: th.BasicTeam.Id,
Name: "test-public-channel",
DisplayName: "Test Public Channel",
Type: model.ChannelTypeOpen,
})
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, publicChannel)
// Helper function to create a new public channel to move the post to
createPublicChannel := func(teamId, name, displayName string) *model.Channel {
channel, resp, err := client.CreateChannel(ctx, &model.Channel{
TeamId: teamId,
Name: name,
DisplayName: displayName,
Type: model.ChannelTypeOpen,
})
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, channel)
return channel
}
// Create a new private channel to move the post to
privateChannel, resp, err := client.CreateChannel(ctx, &model.Channel{
@ -795,6 +799,9 @@ func TestMoveThread(t *testing.T) {
require.NotNil(t, resp)
require.NotNil(t, gmChannel)
t.Run("Move to public channel", func(t *testing.T) {
// Create a public channel
publicChannel := createPublicChannel(th.BasicTeam.Id, "test-public-channel", "Test Public Channel")
// Create a new post to move
post := &model.Post{
ChannelId: th.BasicChannel.Id,
@ -971,6 +978,53 @@ func TestMoveThread(t *testing.T) {
require.Equal(t, newPost.Message, posts.Posts[posts.Order[2]].Message)
require.Equal(t, rootPost.Message, posts.Posts[posts.Order[3]].Message)
})
t.Run("Move thread when permitted role is channel admin", func(t *testing.T) {
// Create public channel
publicChannel := createPublicChannel(th.BasicTeam.Id, "test-public-channel-admin", "Test Public Channel Admin")
// Set permitted role as channel admin
enabled := true
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.WranglerSettings = model.WranglerSettings{MoveThreadToAnotherTeamEnable: &enabled,
PermittedWranglerRoles: []string{model.PermissionsChannelAdmin}}
})
defer th.App.UpdateConfig(func(cfg *model.Config) {
cfg.WranglerSettings = model.WranglerSettings{}
})
// Login as channel admin and add to channel
th.LoginTeamAdmin()
th.AddUserToChannel(th.TeamAdminUser, publicChannel)
defer th.LoginBasic()
// Create a new post to move
post := &model.Post{
ChannelId: th.BasicChannel.Id,
Message: "test post",
}
newPost, resp, err := client.CreatePost(ctx, post)
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, newPost)
// Move the post to the public channel
moveThreadParams := &model.MoveThreadParams{
ChannelId: publicChannel.Id,
}
resp, err = client.MoveThread(ctx, newPost.Id, moveThreadParams)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
// Check that the post was moved to the public channel
posts, resp, err := client.GetPostsForChannel(ctx, publicChannel.Id, 0, 100, "", true, false)
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, posts)
// There should be 2 posts, the system join message for the user who moved it joining the channel, and the post we moved
require.Equal(t, 2, len(posts.Posts))
require.Equal(t, newPost.Message, posts.Posts[posts.Order[0]].Message)
})
}
func TestCreatePostPublic(t *testing.T) {

View File

@ -11,7 +11,7 @@ import {getCurrentUser} from 'mattermost-redux/selectors/entities/common';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getPost} from 'mattermost-redux/selectors/entities/posts';
import {moveThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
import {arePreviewsCollapsed} from 'selectors/preferences';
import {getGlobalItem} from 'selectors/storage';
@ -64,10 +64,11 @@ export function makeCanWrangler() {
'makeCanWrangler',
getConfig,
getCurrentUser,
getCurrentUserRoles,
moveThreadsEnabled,
(_state: GlobalState, channelType: Channel['type']) => channelType,
(_state: GlobalState, _channelType: Channel['type'], replyCount: number) => replyCount,
(config: Partial<ClientConfig>, user: UserProfile, enabled: boolean, channelType: Channel['type'], replyCount: number) => {
(config: Partial<ClientConfig>, user: UserProfile, userRoles: string, enabled: boolean, channelType: Channel['type'], replyCount: number) => {
if (!enabled) {
return false;
}
@ -90,8 +91,8 @@ export function makeCanWrangler() {
allowedEmailDomains = WranglerAllowedEmailDomain?.split(',') || [];
}
if (permittedUsers.length > 0 && !user.roles.includes('system_admin')) {
const roles = user.roles.split(' ');
if (permittedUsers.length > 0 && !userRoles.includes('system_admin')) {
const roles = userRoles.split(' ');
const hasRole = roles.some((role) => permittedUsers.includes(role));
if (!hasRole) {
return false;