коммит произвёл
GitHub
родитель
c0ea57ac6c
Коммит
36ac3a43b1
@@ -10,8 +10,8 @@ import xor from 'lodash.xor';
|
|||||||
|
|
||||||
export const defaultRolesPermissions = {
|
export const defaultRolesPermissions = {
|
||||||
channel_admin: 'use_channel_mentions remove_reaction manage_public_channel_members use_group_mentions manage_channel_roles manage_private_channel_members add_reaction read_public_channel_groups create_post read_private_channel_groups add_bookmark_public_channel edit_bookmark_public_channel delete_bookmark_public_channel order_bookmark_public_channel add_bookmark_private_channel edit_bookmark_private_channel delete_bookmark_private_channel order_bookmark_private_channel',
|
channel_admin: 'use_channel_mentions remove_reaction manage_public_channel_members use_group_mentions manage_channel_roles manage_private_channel_members add_reaction read_public_channel_groups create_post read_private_channel_groups add_bookmark_public_channel edit_bookmark_public_channel delete_bookmark_public_channel order_bookmark_public_channel add_bookmark_private_channel edit_bookmark_private_channel delete_bookmark_private_channel order_bookmark_private_channel',
|
||||||
channel_guest: 'upload_file edit_post create_post use_channel_mentions read_channel read_channel_content add_reaction remove_reaction',
|
channel_guest: 'upload_file edit_post create_post use_channel_mentions read_channel read_channel_content add_reaction remove_reaction edit_file_attachment',
|
||||||
channel_user: 'manage_private_channel_members read_public_channel_groups delete_post read_private_channel_groups use_group_mentions manage_private_channel_properties delete_public_channel add_reaction manage_public_channel_properties edit_post upload_file use_channel_mentions get_public_link read_channel read_channel_content delete_private_channel manage_public_channel_members create_post remove_reaction add_bookmark_public_channel edit_bookmark_public_channel delete_bookmark_public_channel order_bookmark_public_channel add_bookmark_private_channel edit_bookmark_private_channel delete_bookmark_private_channel order_bookmark_private_channel',
|
channel_user: 'manage_private_channel_members read_public_channel_groups delete_post read_private_channel_groups use_group_mentions manage_private_channel_properties delete_public_channel add_reaction manage_public_channel_properties edit_post upload_file use_channel_mentions get_public_link read_channel read_channel_content delete_private_channel manage_public_channel_members create_post remove_reaction add_bookmark_public_channel edit_bookmark_public_channel delete_bookmark_public_channel order_bookmark_public_channel add_bookmark_private_channel edit_bookmark_private_channel delete_bookmark_private_channel order_bookmark_private_channel edit_file_attachment',
|
||||||
custom_group_user: '',
|
custom_group_user: '',
|
||||||
playbook_admin: 'playbook_private_manage_properties playbook_public_make_private playbook_public_manage_members playbook_public_manage_roles playbook_public_manage_properties playbook_private_manage_members playbook_private_manage_roles',
|
playbook_admin: 'playbook_private_manage_properties playbook_public_make_private playbook_public_manage_members playbook_public_manage_roles playbook_public_manage_properties playbook_private_manage_members playbook_private_manage_roles',
|
||||||
playbook_member: 'playbook_public_view playbook_public_manage_members playbook_public_manage_properties playbook_private_view playbook_private_manage_members playbook_private_manage_properties run_create',
|
playbook_member: 'playbook_public_view playbook_public_manage_members playbook_public_manage_properties playbook_private_view playbook_private_manage_members playbook_private_manage_properties run_create',
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -1057,28 +1058,10 @@ func TestAddChannelsToPolicy(t *testing.T) {
|
|||||||
validChannelIDs := []string{model.NewId(), model.NewId()}
|
validChannelIDs := []string{model.NewId(), model.NewId()}
|
||||||
invalidChannelIDs := []string{"invalid_channel_id"}
|
invalidChannelIDs := []string{"invalid_channel_id"}
|
||||||
|
|
||||||
// Custom function to compare slices regardless of order
|
|
||||||
unorderedSlicesEqual := func(a, b []string) bool {
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
counts := make(map[string]int)
|
|
||||||
for _, item := range a {
|
|
||||||
counts[item]++
|
|
||||||
}
|
|
||||||
for _, item := range b {
|
|
||||||
counts[item]--
|
|
||||||
if counts[item] < 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom matcher for unordered slice comparison
|
// Custom matcher for unordered slice comparison
|
||||||
unorderedSliceMatcher := func(expected []string) func(actual []string) bool {
|
unorderedSliceMatcher := func(expected []string) func(actual []string) bool {
|
||||||
return func(actual []string) bool {
|
return func(actual []string) bool {
|
||||||
return unorderedSlicesEqual(expected, actual)
|
return utils.SliceEqualUnordered(expected, actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1065,6 +1065,12 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check edit_file_attachment permission if file IDs are being changed (files added or removed)
|
||||||
|
checkEditFileAttachmentPermission(c, post.FileIds, originalPost)
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if c.AppContext.Session().UserId != originalPost.UserId {
|
if c.AppContext.Session().UserId != originalPost.UserId {
|
||||||
// We don't need to check the member here, since we already checked it above
|
// We don't need to check the member here, since we already checked it above
|
||||||
if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), originalPost.ChannelId, model.PermissionEditOthersPosts); !ok {
|
if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), originalPost.ChannelId, model.PermissionEditOthersPosts); !ok {
|
||||||
@@ -1139,6 +1145,11 @@ func patchPost(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
if c.Err != nil {
|
if c.Err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkEditFileAttachmentPermission(c, *post.FileIds, originalPost)
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
patchedPost, isMemberForPReviews, err := c.App.PatchPost(c.AppContext, c.Params.PostId, c.App.PostPatchWithProxyRemovedFromImageURLs(&post), nil)
|
patchedPost, isMemberForPReviews, err := c.App.PatchPost(c.AppContext, c.Params.PostId, c.App.PostPatchWithProxyRemovedFromImageURLs(&post), nil)
|
||||||
|
|||||||
@@ -1900,6 +1900,124 @@ func TestUpdatePost(t *testing.T) {
|
|||||||
require.Equal(t, int64(0), postFileInfos[0].DeleteAt)
|
require.Equal(t, int64(0), postFileInfos[0].DeleteAt)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("should prevent adding files when edit_file_attachment permission is revoked", func(t *testing.T) {
|
||||||
|
th.LoginBasic()
|
||||||
|
|
||||||
|
fileResp, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
||||||
|
require.NoError(t, err)
|
||||||
|
fileId := fileResp.FileInfos[0].Id
|
||||||
|
|
||||||
|
postWithoutFiles, _, appErr := th.App.CreatePost(th.Context, &model.Post{
|
||||||
|
UserId: th.BasicUser.Id,
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "Post without files",
|
||||||
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
th.RemovePermissionFromRole(model.PermissionEditFileAttachment.Id, model.ChannelUserRoleId)
|
||||||
|
defer th.AddPermissionToRole(model.PermissionEditFileAttachment.Id, model.ChannelUserRoleId)
|
||||||
|
|
||||||
|
updatePost := &model.Post{
|
||||||
|
Id: postWithoutFiles.Id,
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "Updated post with file",
|
||||||
|
FileIds: model.StringArray{fileId},
|
||||||
|
}
|
||||||
|
_, resp, err := client.UpdatePost(context.Background(), postWithoutFiles.Id, updatePost)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||||
|
require.Equal(t, "You do not have the appropriate permissions.", err.Error())
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should prevent removing files when edit_file_attachment permission is revoked", func(t *testing.T) {
|
||||||
|
th.LoginBasic()
|
||||||
|
|
||||||
|
fileResp, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
||||||
|
require.NoError(t, err)
|
||||||
|
fileId := fileResp.FileInfos[0].Id
|
||||||
|
|
||||||
|
postWithFiles, _, appErr := th.App.CreatePost(th.Context, &model.Post{
|
||||||
|
UserId: th.BasicUser.Id,
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "Post with files",
|
||||||
|
FileIds: model.StringArray{fileId},
|
||||||
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
th.RemovePermissionFromRole(model.PermissionEditFileAttachment.Id, model.ChannelUserRoleId)
|
||||||
|
defer th.AddPermissionToRole(model.PermissionEditFileAttachment.Id, model.ChannelUserRoleId)
|
||||||
|
|
||||||
|
updatePost := &model.Post{
|
||||||
|
Id: postWithFiles.Id,
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "Updated post without file",
|
||||||
|
FileIds: model.StringArray{},
|
||||||
|
}
|
||||||
|
_, resp, err := client.UpdatePost(context.Background(), postWithFiles.Id, updatePost)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||||
|
require.Equal(t, "You do not have the appropriate permissions.", err.Error())
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should allow updating post with unchanged files when edit_file_attachment permission is revoked", func(t *testing.T) {
|
||||||
|
th.LoginBasic()
|
||||||
|
|
||||||
|
fileResp, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
||||||
|
require.NoError(t, err)
|
||||||
|
fileId := fileResp.FileInfos[0].Id
|
||||||
|
|
||||||
|
postWithFiles, _, appErr := th.App.CreatePost(th.Context, &model.Post{
|
||||||
|
UserId: th.BasicUser.Id,
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "Post with files",
|
||||||
|
FileIds: model.StringArray{fileId},
|
||||||
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
th.RemovePermissionFromRole(model.PermissionEditFileAttachment.Id, model.ChannelUserRoleId)
|
||||||
|
defer th.AddPermissionToRole(model.PermissionEditFileAttachment.Id, model.ChannelUserRoleId)
|
||||||
|
|
||||||
|
updatePost := &model.Post{
|
||||||
|
Id: postWithFiles.Id,
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "Updated message only",
|
||||||
|
FileIds: model.StringArray{fileId},
|
||||||
|
}
|
||||||
|
updatedPost, resp, err := client.UpdatePost(context.Background(), postWithFiles.Id, updatePost)
|
||||||
|
require.NoError(t, err)
|
||||||
|
CheckOKStatus(t, resp)
|
||||||
|
require.NotNil(t, updatedPost)
|
||||||
|
assert.Equal(t, "Updated message only", updatedPost.Message)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should allow changing files when edit_file_attachment permission is present", func(t *testing.T) {
|
||||||
|
th.LoginBasic()
|
||||||
|
|
||||||
|
fileResp, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
||||||
|
require.NoError(t, err)
|
||||||
|
fileId := fileResp.FileInfos[0].Id
|
||||||
|
|
||||||
|
postWithoutFiles, _, appErr := th.App.CreatePost(th.Context, &model.Post{
|
||||||
|
UserId: th.BasicUser.Id,
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "Post without files",
|
||||||
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
updatePost := &model.Post{
|
||||||
|
Id: postWithoutFiles.Id,
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "Updated post with file",
|
||||||
|
FileIds: model.StringArray{fileId},
|
||||||
|
}
|
||||||
|
updatedPost, resp, err := client.UpdatePost(context.Background(), postWithoutFiles.Id, updatePost)
|
||||||
|
require.NoError(t, err)
|
||||||
|
CheckOKStatus(t, resp)
|
||||||
|
require.NotNil(t, updatedPost)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("should be able to add and remove files simultaneously", func(t *testing.T) {
|
t.Run("should be able to add and remove files simultaneously", func(t *testing.T) {
|
||||||
th.LoginBasic()
|
th.LoginBasic()
|
||||||
// create new file
|
// create new file
|
||||||
@@ -2159,6 +2277,129 @@ func TestPatchPost(t *testing.T) {
|
|||||||
CheckForbiddenStatus(t, resp)
|
CheckForbiddenStatus(t, resp)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("should prevent patching file ids when edit_file_attachment permission is revoked", func(t *testing.T) {
|
||||||
|
th.LoginBasic()
|
||||||
|
|
||||||
|
fileResp, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
||||||
|
require.NoError(t, err)
|
||||||
|
fileId := fileResp.FileInfos[0].Id
|
||||||
|
|
||||||
|
postToEdit, _, err := client.CreatePost(context.Background(), &model.Post{
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "original message",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
th.RemovePermissionFromRole(model.PermissionEditFileAttachment.Id, model.ChannelUserRoleId)
|
||||||
|
defer th.AddPermissionToRole(model.PermissionEditFileAttachment.Id, model.ChannelUserRoleId)
|
||||||
|
|
||||||
|
patch := &model.PostPatch{
|
||||||
|
FileIds: &model.StringArray{fileId},
|
||||||
|
}
|
||||||
|
_, resp, err := client.PatchPost(context.Background(), postToEdit.Id, patch)
|
||||||
|
require.Error(t, err)
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should prevent removing files via patch when edit_file_attachment permission is revoked", func(t *testing.T) {
|
||||||
|
th.LoginBasic()
|
||||||
|
|
||||||
|
fileResp, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
||||||
|
require.NoError(t, err)
|
||||||
|
fileId := fileResp.FileInfos[0].Id
|
||||||
|
|
||||||
|
postToEdit, _, err := client.CreatePost(context.Background(), &model.Post{
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "post with file",
|
||||||
|
FileIds: model.StringArray{fileId},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
th.RemovePermissionFromRole(model.PermissionEditFileAttachment.Id, model.ChannelUserRoleId)
|
||||||
|
defer th.AddPermissionToRole(model.PermissionEditFileAttachment.Id, model.ChannelUserRoleId)
|
||||||
|
|
||||||
|
emptyFileIds := model.StringArray{}
|
||||||
|
patch := &model.PostPatch{
|
||||||
|
FileIds: &emptyFileIds,
|
||||||
|
}
|
||||||
|
_, resp, err := client.PatchPost(context.Background(), postToEdit.Id, patch)
|
||||||
|
require.Error(t, err)
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should allow patching message without file change when edit_file_attachment permission is revoked", func(t *testing.T) {
|
||||||
|
th.LoginBasic()
|
||||||
|
|
||||||
|
fileResp, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
||||||
|
require.NoError(t, err)
|
||||||
|
fileId := fileResp.FileInfos[0].Id
|
||||||
|
|
||||||
|
postToEdit, _, err := client.CreatePost(context.Background(), &model.Post{
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "original message",
|
||||||
|
FileIds: model.StringArray{fileId},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
th.RemovePermissionFromRole(model.PermissionEditFileAttachment.Id, model.ChannelUserRoleId)
|
||||||
|
defer th.AddPermissionToRole(model.PermissionEditFileAttachment.Id, model.ChannelUserRoleId)
|
||||||
|
|
||||||
|
patch := &model.PostPatch{
|
||||||
|
Message: model.NewPointer("updated message only"),
|
||||||
|
}
|
||||||
|
patchedPost, _, err := client.PatchPost(context.Background(), postToEdit.Id, patch)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "updated message only", patchedPost.Message)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should allow patching with same file ids when edit_file_attachment permission is revoked", func(t *testing.T) {
|
||||||
|
th.LoginBasic()
|
||||||
|
|
||||||
|
fileResp, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
||||||
|
require.NoError(t, err)
|
||||||
|
fileId := fileResp.FileInfos[0].Id
|
||||||
|
|
||||||
|
postToEdit, _, err := client.CreatePost(context.Background(), &model.Post{
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "original message",
|
||||||
|
FileIds: model.StringArray{fileId},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
th.RemovePermissionFromRole(model.PermissionEditFileAttachment.Id, model.ChannelUserRoleId)
|
||||||
|
defer th.AddPermissionToRole(model.PermissionEditFileAttachment.Id, model.ChannelUserRoleId)
|
||||||
|
|
||||||
|
sameFileIds := model.StringArray{fileId}
|
||||||
|
patch := &model.PostPatch{
|
||||||
|
Message: model.NewPointer("updated message"),
|
||||||
|
FileIds: &sameFileIds,
|
||||||
|
}
|
||||||
|
patchedPost, _, err := client.PatchPost(context.Background(), postToEdit.Id, patch)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "updated message", patchedPost.Message)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should allow patching files when edit_file_attachment permission is present", func(t *testing.T) {
|
||||||
|
th.LoginBasic()
|
||||||
|
|
||||||
|
fileResp, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
||||||
|
require.NoError(t, err)
|
||||||
|
fileId := fileResp.FileInfos[0].Id
|
||||||
|
|
||||||
|
postToEdit, _, err := client.CreatePost(context.Background(), &model.Post{
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "original message",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
patch := &model.PostPatch{
|
||||||
|
FileIds: &model.StringArray{fileId},
|
||||||
|
}
|
||||||
|
patchedPost, _, err := client.PatchPost(context.Background(), postToEdit.Id, patch)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, patchedPost)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("time limit expired", func(t *testing.T) {
|
t.Run("time limit expired", func(t *testing.T) {
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||||
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package api4
|
|||||||
import (
|
import (
|
||||||
"github.com/mattermost/mattermost/server/public/model"
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
"github.com/mattermost/mattermost/server/v8/channels/app"
|
"github.com/mattermost/mattermost/server/v8/channels/app"
|
||||||
|
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func userCreatePostPermissionCheckWithContext(c *Context, channelId string) {
|
func userCreatePostPermissionCheckWithContext(c *Context, channelId string) {
|
||||||
@@ -69,3 +70,14 @@ func checkUploadFilePermissionForNewFiles(c *Context, newFileIds []string, origi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkEditFileAttachmentPermission checks edit_file_attachment permission
|
||||||
|
// when file IDs are being changed (files added or removed) during post edit.
|
||||||
|
func checkEditFileAttachmentPermission(c *Context, newFileIds []string, originalPost *model.Post) {
|
||||||
|
if utils.SliceEqualUnordered(newFileIds, originalPost.FileIds) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), originalPost.ChannelId, model.PermissionEditFileAttachment); !ok {
|
||||||
|
c.SetPermissionError(model.PermissionEditFileAttachment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
94
server/channels/api4/post_utils_test.go
Обычный файл
94
server/channels/api4/post_utils_test.go
Обычный файл
@@ -0,0 +1,94 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSameFileIDs(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
a []string
|
||||||
|
b []string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "both empty",
|
||||||
|
a: []string{},
|
||||||
|
b: []string{},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "both nil",
|
||||||
|
a: nil,
|
||||||
|
b: nil,
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same files same order",
|
||||||
|
a: []string{"file1", "file2", "file3"},
|
||||||
|
b: []string{"file1", "file2", "file3"},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same files different order",
|
||||||
|
a: []string{"file3", "file1", "file2"},
|
||||||
|
b: []string{"file1", "file2", "file3"},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one file added",
|
||||||
|
a: []string{"file1", "file2", "file3"},
|
||||||
|
b: []string{"file1", "file2"},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one file removed",
|
||||||
|
a: []string{"file1"},
|
||||||
|
b: []string{"file1", "file2"},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "different files same length",
|
||||||
|
a: []string{"file1", "file2"},
|
||||||
|
b: []string{"file1", "file3"},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicate IDs in a",
|
||||||
|
a: []string{"file1", "file1"},
|
||||||
|
b: []string{"file1", "file2"},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicate IDs same in both",
|
||||||
|
a: []string{"file1", "file1"},
|
||||||
|
b: []string{"file1", "file1"},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty vs non-empty",
|
||||||
|
a: []string{},
|
||||||
|
b: []string{"file1"},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil vs non-empty",
|
||||||
|
a: nil,
|
||||||
|
b: []string{"file1"},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := utils.SliceEqualUnordered(tc.a, tc.b)
|
||||||
|
assert.Equal(t, tc.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -129,6 +129,7 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
|
|||||||
model.PermissionManagePrivateChannelMembers.Id,
|
model.PermissionManagePrivateChannelMembers.Id,
|
||||||
model.PermissionDeletePost.Id,
|
model.PermissionDeletePost.Id,
|
||||||
model.PermissionEditPost.Id,
|
model.PermissionEditPost.Id,
|
||||||
|
model.PermissionEditFileAttachment.Id,
|
||||||
model.PermissionAddBookmarkPublicChannel.Id,
|
model.PermissionAddBookmarkPublicChannel.Id,
|
||||||
model.PermissionEditBookmarkPublicChannel.Id,
|
model.PermissionEditBookmarkPublicChannel.Id,
|
||||||
model.PermissionDeleteBookmarkPublicChannel.Id,
|
model.PermissionDeleteBookmarkPublicChannel.Id,
|
||||||
|
|||||||
@@ -1206,6 +1206,15 @@ func (a *App) getRestrictAcessToChannelConversionToPublic() (permissionsMap, err
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) getAddEditFileAttachmentPermissionMigration() (permissionsMap, error) {
|
||||||
|
return permissionsMap{
|
||||||
|
permissionTransformation{
|
||||||
|
On: permissionExists(model.PermissionEditPost.Id),
|
||||||
|
Add: []string{model.PermissionEditFileAttachment.Id},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// DoPermissionsMigrations execute all the permissions migrations need by the current version.
|
// DoPermissionsMigrations execute all the permissions migrations need by the current version.
|
||||||
func (a *App) DoPermissionsMigrations() error {
|
func (a *App) DoPermissionsMigrations() error {
|
||||||
return a.Srv().doPermissionsMigrations()
|
return a.Srv().doPermissionsMigrations()
|
||||||
@@ -1260,6 +1269,7 @@ func (s *Server) doPermissionsMigrations() error {
|
|||||||
{Key: model.MigrationRemoveGetAnalyticsPermission, Migration: a.removeGetAnalyticsPermissionMigration},
|
{Key: model.MigrationRemoveGetAnalyticsPermission, Migration: a.removeGetAnalyticsPermissionMigration},
|
||||||
{Key: model.MigrationAddSysconsoleMobileSecurityPermission, Migration: a.addSysConsoleMobileSecurityPermission},
|
{Key: model.MigrationAddSysconsoleMobileSecurityPermission, Migration: a.addSysConsoleMobileSecurityPermission},
|
||||||
{Key: model.MigrationKeyAddChannelBannerPermissions, Migration: a.getAddChannelBannerPermissionMigration},
|
{Key: model.MigrationKeyAddChannelBannerPermissions, Migration: a.getAddChannelBannerPermissionMigration},
|
||||||
|
{Key: model.MigrationKeyAddEditFileAttachmentPermission, Migration: a.getAddEditFileAttachmentPermissionMigration},
|
||||||
}
|
}
|
||||||
|
|
||||||
roles, err := s.Store().Role().GetAll()
|
roles, err := s.Store().Role().GetAll()
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
|
|||||||
systemStore.On("GetByName", "elasticsearch_fix_channel_index_migration").Return(&model.System{Name: "elasticsearch_fix_channel_index_migration", 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.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("GetByName", model.MigrationKeyAddChannelBannerPermissions).Return(&model.System{Name: model.MigrationKeyAddChannelBannerPermissions, Value: "true"}, nil)
|
||||||
|
systemStore.On("GetByName", model.MigrationKeyAddEditFileAttachmentPermission).Return(&model.System{Name: model.MigrationKeyAddEditFileAttachmentPermission, Value: "true"}, nil)
|
||||||
|
|
||||||
systemStore.On("InsertIfExists", mock.AnythingOfType("*model.System")).Return(&model.System{}, nil).Once()
|
systemStore.On("InsertIfExists", mock.AnythingOfType("*model.System")).Return(&model.System{}, nil).Once()
|
||||||
systemStore.On("Save", mock.AnythingOfType("*model.System")).Return(nil)
|
systemStore.On("Save", mock.AnythingOfType("*model.System")).Return(nil)
|
||||||
|
|||||||
@@ -264,3 +264,22 @@ func RoundOffToZeroesResolution(n float64, minResolution int) int64 {
|
|||||||
significantDigits := int64(n) / tens
|
significantDigits := int64(n) / tens
|
||||||
return significantDigits * tens
|
return significantDigits * tens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SliceEqualUnordered returns true if both slices contain the same set of elements,
|
||||||
|
// regardless of order.
|
||||||
|
func SliceEqualUnordered[K comparable](a, b []K) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
set := make(map[K]int, len(a))
|
||||||
|
for _, id := range a {
|
||||||
|
set[id]++
|
||||||
|
}
|
||||||
|
for _, id := range b {
|
||||||
|
set[id]--
|
||||||
|
if set[id] < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -55,4 +55,5 @@ const (
|
|||||||
MigrationRemoveGetAnalyticsPermission = "remove_get_analytics_permission"
|
MigrationRemoveGetAnalyticsPermission = "remove_get_analytics_permission"
|
||||||
MigrationAddSysconsoleMobileSecurityPermission = "add_sysconsole_mobile_security_permission"
|
MigrationAddSysconsoleMobileSecurityPermission = "add_sysconsole_mobile_security_permission"
|
||||||
MigrationKeyAddChannelBannerPermissions = "add_channel_banner_permissions"
|
MigrationKeyAddChannelBannerPermissions = "add_channel_banner_permissions"
|
||||||
|
MigrationKeyAddEditFileAttachmentPermission = "add_edit_file_attachment_permission"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ var PermissionReadLicenseInformation *Permission
|
|||||||
var PermissionManageLicenseInformation *Permission
|
var PermissionManageLicenseInformation *Permission
|
||||||
var PermissionManagePublicChannelBanner *Permission
|
var PermissionManagePublicChannelBanner *Permission
|
||||||
var PermissionManagePrivateChannelBanner *Permission
|
var PermissionManagePrivateChannelBanner *Permission
|
||||||
|
var PermissionEditFileAttachment *Permission
|
||||||
|
|
||||||
var PermissionSysconsoleReadAbout *Permission
|
var PermissionSysconsoleReadAbout *Permission
|
||||||
var PermissionSysconsoleWriteAbout *Permission
|
var PermissionSysconsoleWriteAbout *Permission
|
||||||
@@ -1299,6 +1300,13 @@ func initializePermissions() {
|
|||||||
PermissionScopeChannel,
|
PermissionScopeChannel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PermissionEditFileAttachment = &Permission{
|
||||||
|
"edit_file_attachment",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
PermissionScopeChannel,
|
||||||
|
}
|
||||||
|
|
||||||
PermissionReadOtherUsersTeams = &Permission{
|
PermissionReadOtherUsersTeams = &Permission{
|
||||||
"read_other_users_teams",
|
"read_other_users_teams",
|
||||||
"authentication.permissions.read_other_users_teams.name",
|
"authentication.permissions.read_other_users_teams.name",
|
||||||
@@ -2538,6 +2546,7 @@ func initializePermissions() {
|
|||||||
PermissionOrderBookmarkPrivateChannel,
|
PermissionOrderBookmarkPrivateChannel,
|
||||||
PermissionManagePublicChannelBanner,
|
PermissionManagePublicChannelBanner,
|
||||||
PermissionManagePrivateChannelBanner,
|
PermissionManagePrivateChannelBanner,
|
||||||
|
PermissionEditFileAttachment,
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupScopedPermissions := []*Permission{
|
GroupScopedPermissions := []*Permission{
|
||||||
|
|||||||
@@ -866,6 +866,7 @@ func MakeDefaultRoles() map[string]*Role {
|
|||||||
PermissionEditPost.Id,
|
PermissionEditPost.Id,
|
||||||
PermissionCreatePost.Id,
|
PermissionCreatePost.Id,
|
||||||
PermissionUseChannelMentions.Id,
|
PermissionUseChannelMentions.Id,
|
||||||
|
PermissionEditFileAttachment.Id,
|
||||||
},
|
},
|
||||||
SchemeManaged: true,
|
SchemeManaged: true,
|
||||||
BuiltIn: true,
|
BuiltIn: true,
|
||||||
@@ -892,6 +893,7 @@ func MakeDefaultRoles() map[string]*Role {
|
|||||||
PermissionManagePrivateChannelMembers.Id,
|
PermissionManagePrivateChannelMembers.Id,
|
||||||
PermissionDeletePost.Id,
|
PermissionDeletePost.Id,
|
||||||
PermissionEditPost.Id,
|
PermissionEditPost.Id,
|
||||||
|
PermissionEditFileAttachment.Id,
|
||||||
PermissionAddBookmarkPublicChannel.Id,
|
PermissionAddBookmarkPublicChannel.Id,
|
||||||
PermissionEditBookmarkPublicChannel.Id,
|
PermissionEditBookmarkPublicChannel.Id,
|
||||||
PermissionDeleteBookmarkPublicChannel.Id,
|
PermissionDeleteBookmarkPublicChannel.Id,
|
||||||
|
|||||||
@@ -58,6 +58,13 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
|||||||
"edit_post",
|
"edit_post",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"combined": true,
|
||||||
|
"id": "guest_edit_file_attachment",
|
||||||
|
"permissions": Array [
|
||||||
|
"edit_file_attachment",
|
||||||
|
],
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"combined": true,
|
"combined": true,
|
||||||
"id": "guest_delete_post",
|
"id": "guest_delete_post",
|
||||||
@@ -183,6 +190,13 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
|||||||
"edit_post",
|
"edit_post",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"combined": true,
|
||||||
|
"id": "guest_edit_file_attachment",
|
||||||
|
"permissions": Array [
|
||||||
|
"edit_file_attachment",
|
||||||
|
],
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"combined": true,
|
"combined": true,
|
||||||
"id": "guest_delete_post",
|
"id": "guest_delete_post",
|
||||||
@@ -301,6 +315,13 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
|||||||
"edit_post",
|
"edit_post",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"combined": true,
|
||||||
|
"id": "guest_edit_file_attachment",
|
||||||
|
"permissions": Array [
|
||||||
|
"edit_file_attachment",
|
||||||
|
],
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"combined": true,
|
"combined": true,
|
||||||
"id": "guest_delete_post",
|
"id": "guest_delete_post",
|
||||||
@@ -426,6 +447,13 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
|||||||
"edit_post",
|
"edit_post",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"combined": true,
|
||||||
|
"id": "guest_edit_file_attachment",
|
||||||
|
"permissions": Array [
|
||||||
|
"edit_file_attachment",
|
||||||
|
],
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"combined": true,
|
"combined": true,
|
||||||
"id": "guest_delete_post",
|
"id": "guest_delete_post",
|
||||||
@@ -551,6 +579,13 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
|||||||
"edit_post",
|
"edit_post",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"combined": true,
|
||||||
|
"id": "guest_edit_file_attachment",
|
||||||
|
"permissions": Array [
|
||||||
|
"edit_file_attachment",
|
||||||
|
],
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"combined": true,
|
"combined": true,
|
||||||
"id": "guest_delete_post",
|
"id": "guest_delete_post",
|
||||||
@@ -692,6 +727,13 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
|||||||
"edit_post",
|
"edit_post",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"combined": true,
|
||||||
|
"id": "guest_edit_file_attachment",
|
||||||
|
"permissions": Array [
|
||||||
|
"edit_file_attachment",
|
||||||
|
],
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"combined": true,
|
"combined": true,
|
||||||
"id": "guest_delete_post",
|
"id": "guest_delete_post",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const GuestPermissionsTree = ({license, onToggle, readOnly, scope, selectRow, pa
|
|||||||
const defaultPermissions = [
|
const defaultPermissions = [
|
||||||
Permissions.CREATE_PRIVATE_CHANNEL,
|
Permissions.CREATE_PRIVATE_CHANNEL,
|
||||||
Permissions.EDIT_POST,
|
Permissions.EDIT_POST,
|
||||||
|
Permissions.EDIT_FILE_ATTACHMENT,
|
||||||
Permissions.DELETE_POST,
|
Permissions.DELETE_POST,
|
||||||
{
|
{
|
||||||
id: 'guest_' + Permissions.CREATE_POST,
|
id: 'guest_' + Permissions.CREATE_POST,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export const GUEST_INCLUDED_PERMISSIONS = [
|
|||||||
Permissions.REMOVE_REACTION,
|
Permissions.REMOVE_REACTION,
|
||||||
Permissions.READ_CHANNEL,
|
Permissions.READ_CHANNEL,
|
||||||
Permissions.UPLOAD_FILE,
|
Permissions.UPLOAD_FILE,
|
||||||
|
Permissions.EDIT_FILE_ATTACHMENT,
|
||||||
Permissions.USE_CHANNEL_MENTIONS,
|
Permissions.USE_CHANNEL_MENTIONS,
|
||||||
Permissions.USE_GROUP_MENTIONS,
|
Permissions.USE_GROUP_MENTIONS,
|
||||||
Permissions.CREATE_POST,
|
Permissions.CREATE_POST,
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
|||||||
"edit_others_posts",
|
"edit_others_posts",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
"edit_file_attachment",
|
||||||
Object {
|
Object {
|
||||||
"id": "delete_posts",
|
"id": "delete_posts",
|
||||||
"permissions": Array [
|
"permissions": Array [
|
||||||
@@ -322,6 +323,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
|||||||
"edit_others_posts",
|
"edit_others_posts",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
"edit_file_attachment",
|
||||||
Object {
|
Object {
|
||||||
"id": "delete_posts",
|
"id": "delete_posts",
|
||||||
"permissions": Array [
|
"permissions": Array [
|
||||||
@@ -513,6 +515,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
|||||||
"edit_others_posts",
|
"edit_others_posts",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
"edit_file_attachment",
|
||||||
Object {
|
Object {
|
||||||
"id": "delete_posts",
|
"id": "delete_posts",
|
||||||
"permissions": Array [
|
"permissions": Array [
|
||||||
@@ -715,6 +718,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
|||||||
"edit_others_posts",
|
"edit_others_posts",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
"edit_file_attachment",
|
||||||
Object {
|
Object {
|
||||||
"id": "delete_posts",
|
"id": "delete_posts",
|
||||||
"permissions": Array [
|
"permissions": Array [
|
||||||
@@ -917,6 +921,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
|||||||
"edit_others_posts",
|
"edit_others_posts",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
"edit_file_attachment",
|
||||||
Object {
|
Object {
|
||||||
"id": "delete_posts",
|
"id": "delete_posts",
|
||||||
"permissions": Array [
|
"permissions": Array [
|
||||||
@@ -1119,6 +1124,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
|||||||
"edit_others_posts",
|
"edit_others_posts",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
"edit_file_attachment",
|
||||||
Object {
|
Object {
|
||||||
"id": "delete_posts",
|
"id": "delete_posts",
|
||||||
"permissions": Array [
|
"permissions": Array [
|
||||||
@@ -1328,6 +1334,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
|||||||
"edit_others_posts",
|
"edit_others_posts",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
"edit_file_attachment",
|
||||||
Object {
|
Object {
|
||||||
"id": "delete_posts",
|
"id": "delete_posts",
|
||||||
"permissions": Array [
|
"permissions": Array [
|
||||||
|
|||||||
@@ -130,6 +130,16 @@ describe('components/admin_console/permission_schemes_settings/permission_tree',
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should include edit_file_attachment in the posts permission group', () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<PermissionsTree {...defaultProps}/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const groups = wrapper.find(PermissionGroup).first().prop('permissions') as Array<Group | Permission>;
|
||||||
|
expect(groups[6].id).toBe('posts');
|
||||||
|
expect(groups[6].permissions).toContain('edit_file_attachment');
|
||||||
|
});
|
||||||
|
|
||||||
test('should hide disabbled integration options', () => {
|
test('should hide disabbled integration options', () => {
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<PermissionsTree
|
<PermissionsTree
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ export default class PermissionsTree extends React.PureComponent<Props, State> {
|
|||||||
Permissions.EDIT_OTHERS_POSTS,
|
Permissions.EDIT_OTHERS_POSTS,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Permissions.EDIT_FILE_ATTACHMENT,
|
||||||
{
|
{
|
||||||
id: 'delete_posts',
|
id: 'delete_posts',
|
||||||
permissions: [
|
permissions: [
|
||||||
|
|||||||
@@ -125,6 +125,16 @@ export const groupRolesStrings: Record<string, Record<string, MessageDescriptor>
|
|||||||
defaultMessage: 'Add and delete reactions on posts.',
|
defaultMessage: 'Add and delete reactions on posts.',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
guest_edit_file_attachment: defineMessages({
|
||||||
|
name: {
|
||||||
|
id: 'admin.permissions.group.guest_edit_file_attachment.name',
|
||||||
|
defaultMessage: 'Edit Attachments',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
id: 'admin.permissions.group.guest_edit_file_attachment.description',
|
||||||
|
defaultMessage: 'Allow users to add or remove file attachments when editing posts.',
|
||||||
|
},
|
||||||
|
}),
|
||||||
guest_create_post: defineMessages({
|
guest_create_post: defineMessages({
|
||||||
name: {
|
name: {
|
||||||
id: 'admin.permissions.group.guest_create_post.name',
|
id: 'admin.permissions.group.guest_create_post.name',
|
||||||
|
|||||||
@@ -405,6 +405,16 @@ export const permissionRolesStrings: Record<string, Record<string, MessageDescri
|
|||||||
defaultMessage: 'Upload file',
|
defaultMessage: 'Upload file',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
edit_file_attachment: defineMessages({
|
||||||
|
name: {
|
||||||
|
id: 'admin.permissions.permission.edit_file_attachment.name',
|
||||||
|
defaultMessage: 'Edit Attachments',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
id: 'admin.permissions.permission.edit_file_attachment.description',
|
||||||
|
defaultMessage: 'Allow users to add or remove file attachments when editing posts.',
|
||||||
|
},
|
||||||
|
}),
|
||||||
use_channel_mentions: defineMessages({
|
use_channel_mentions: defineMessages({
|
||||||
name: {
|
name: {
|
||||||
id: 'admin.permissions.permission.use_channel_mentions.name',
|
id: 'admin.permissions.permission.use_channel_mentions.name',
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React, {useCallback, useRef, useState} from 'react';
|
import React, {useCallback, useMemo, useRef, useState} from 'react';
|
||||||
|
import {useIntl} from 'react-intl';
|
||||||
import {useSelector} from 'react-redux';
|
import {useSelector} from 'react-redux';
|
||||||
|
|
||||||
import type {ServerError} from '@mattermost/types/errors';
|
import type {ServerError} from '@mattermost/types/errors';
|
||||||
import type {FileInfo} from '@mattermost/types/files';
|
import type {FileInfo} from '@mattermost/types/files';
|
||||||
|
|
||||||
|
import Permissions from 'mattermost-redux/constants/permissions';
|
||||||
|
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||||
|
import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles';
|
||||||
import {sortFileInfos} from 'mattermost-redux/utils/file_utils';
|
import {sortFileInfos} from 'mattermost-redux/utils/file_utils';
|
||||||
|
|
||||||
import {getCurrentLocale} from 'selectors/i18n';
|
import {getCurrentLocale} from 'selectors/i18n';
|
||||||
@@ -17,6 +21,7 @@ import FileUpload from 'components/file_upload';
|
|||||||
import type {FileUpload as FileUploadClass, TextEditorLocationType} from 'components/file_upload/file_upload';
|
import type {FileUpload as FileUploadClass, TextEditorLocationType} from 'components/file_upload/file_upload';
|
||||||
import type TextboxClass from 'components/textbox/textbox';
|
import type TextboxClass from 'components/textbox/textbox';
|
||||||
|
|
||||||
|
import type {GlobalState} from 'types/store';
|
||||||
import type {PostDraft} from 'types/store/draft';
|
import type {PostDraft} from 'types/store/draft';
|
||||||
|
|
||||||
const getFileCount = (draft: PostDraft) => {
|
const getFileCount = (draft: PostDraft) => {
|
||||||
@@ -36,12 +41,25 @@ const useUploadFiles = (
|
|||||||
setServerError: (err: (ServerError & { submittedMessage?: string }) | null) => void,
|
setServerError: (err: (ServerError & { submittedMessage?: string }) | null) => void,
|
||||||
isPostBeingEdited?: boolean,
|
isPostBeingEdited?: boolean,
|
||||||
): [React.ReactNode, React.ReactNode] => {
|
): [React.ReactNode, React.ReactNode] => {
|
||||||
|
const intl = useIntl();
|
||||||
const locale = useSelector(getCurrentLocale);
|
const locale = useSelector(getCurrentLocale);
|
||||||
|
const canEditAttachments = useSelector((state: GlobalState) => {
|
||||||
|
const channel = getChannel(state, channelId);
|
||||||
|
return channel ? haveIChannelPermission(state, channel.team_id, channel.id, Permissions.EDIT_FILE_ATTACHMENT) : true;
|
||||||
|
});
|
||||||
|
const editAttachmentsDisabled = isPostBeingEdited && !canEditAttachments;
|
||||||
|
|
||||||
const [uploadsProgressPercent, setUploadsProgressPercent] = useState<{ [clientID: string]: FilePreviewInfo }>({});
|
const [uploadsProgressPercent, setUploadsProgressPercent] = useState<{ [clientID: string]: FilePreviewInfo }>({});
|
||||||
|
|
||||||
const fileUploadRef = useRef<FileUploadClass>(null);
|
const fileUploadRef = useRef<FileUploadClass>(null);
|
||||||
|
|
||||||
|
const removeTooltip = useMemo(() => {
|
||||||
|
return editAttachmentsDisabled ? intl.formatMessage({
|
||||||
|
id: 'file_preview.no_edit_permission',
|
||||||
|
defaultMessage: 'Post attachments cannot be edited',
|
||||||
|
}) : undefined;
|
||||||
|
}, [editAttachmentsDisabled, intl]);
|
||||||
|
|
||||||
const handleFileUploadChange = useCallback(() => {
|
const handleFileUploadChange = useCallback(() => {
|
||||||
focusTextbox();
|
focusTextbox();
|
||||||
}, [focusTextbox]);
|
}, [focusTextbox]);
|
||||||
@@ -146,9 +164,10 @@ const useUploadFiles = (
|
|||||||
attachmentPreview = (
|
attachmentPreview = (
|
||||||
<FilePreview
|
<FilePreview
|
||||||
fileInfos={draft.fileInfos}
|
fileInfos={draft.fileInfos}
|
||||||
onRemove={removePreview}
|
onRemove={editAttachmentsDisabled ? undefined : removePreview}
|
||||||
uploadsInProgress={draft.uploadsInProgress}
|
uploadsInProgress={draft.uploadsInProgress}
|
||||||
uploadsProgressPercent={uploadsProgressPercent}
|
uploadsProgressPercent={uploadsProgressPercent}
|
||||||
|
disabledRemoveTooltip={removeTooltip}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -160,7 +179,7 @@ const useUploadFiles = (
|
|||||||
postType = isThreadView ? 'thread' : 'comment';
|
postType = isThreadView ? 'thread' : 'comment';
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileUploadJSX = isDisabled ? null : (
|
const fileUploadJSX = (isDisabled || editAttachmentsDisabled) ? null : (
|
||||||
<FileUpload
|
<FileUpload
|
||||||
ref={fileUploadRef}
|
ref={fileUploadRef}
|
||||||
fileCount={getFileCount(draft)}
|
fileCount={getFileCount(draft)}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import React from 'react';
|
|||||||
|
|
||||||
import {getFileUrl} from 'mattermost-redux/utils/file_utils';
|
import {getFileUrl} from 'mattermost-redux/utils/file_utils';
|
||||||
|
|
||||||
|
import WithTooltip from 'components/with_tooltip';
|
||||||
|
|
||||||
import FilePreview from './file_preview';
|
import FilePreview from './file_preview';
|
||||||
|
|
||||||
describe('FilePreview', () => {
|
describe('FilePreview', () => {
|
||||||
@@ -141,4 +143,53 @@ describe('FilePreview', () => {
|
|||||||
expect(wrapper.find('img').find({src: getFileUrl(fileId)}).exists()).toBe(true);
|
expect(wrapper.find('img').find({src: getFileUrl(fileId)}).exists()).toBe(true);
|
||||||
expect(wrapper.find('div').find('.file-icon.generic').exists()).toBe(false);
|
expect(wrapper.find('div').find('.file-icon.generic').exists()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should render disabled remove button with tooltip when onRemove is absent and disabledRemoveTooltip is provided', () => {
|
||||||
|
const props = {
|
||||||
|
...baseProps,
|
||||||
|
onRemove: undefined,
|
||||||
|
uploadsInProgress: [],
|
||||||
|
disabledRemoveTooltip: 'You do not have permission to edit file attachments',
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallow(
|
||||||
|
<FilePreview {...props}/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('.file-preview__remove--disabled').exists()).toBe(true);
|
||||||
|
expect(wrapper.find('a.file-preview__remove').exists()).toBe(false);
|
||||||
|
expect(wrapper.find(WithTooltip).prop('title')).toBe('You do not have permission to edit file attachments');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render normal remove button when onRemove is defined', () => {
|
||||||
|
const props = {
|
||||||
|
...baseProps,
|
||||||
|
onRemove: jest.fn(),
|
||||||
|
uploadsInProgress: [],
|
||||||
|
disabledRemoveTooltip: 'tooltip text',
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallow(
|
||||||
|
<FilePreview {...props}/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('a.file-preview__remove').exists()).toBe(true);
|
||||||
|
expect(wrapper.find('.file-preview__remove--disabled').exists()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not render any remove button when onRemove is absent and no disabledRemoveTooltip', () => {
|
||||||
|
const props = {
|
||||||
|
...baseProps,
|
||||||
|
onRemove: undefined,
|
||||||
|
uploadsInProgress: [],
|
||||||
|
disabledRemoveTooltip: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallow(
|
||||||
|
<FilePreview {...props}/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('a.file-preview__remove').exists()).toBe(false);
|
||||||
|
expect(wrapper.find('.file-preview__remove--disabled').exists()).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type {FileInfo} from '@mattermost/types/files';
|
|||||||
import {getFileThumbnailUrl, getFileUrl} from 'mattermost-redux/utils/file_utils';
|
import {getFileThumbnailUrl, getFileUrl} from 'mattermost-redux/utils/file_utils';
|
||||||
|
|
||||||
import FilenameOverlay from 'components/file_attachment/filename_overlay';
|
import FilenameOverlay from 'components/file_attachment/filename_overlay';
|
||||||
|
import WithTooltip from 'components/with_tooltip';
|
||||||
|
|
||||||
import Constants, {FileTypes} from 'utils/constants';
|
import Constants, {FileTypes} from 'utils/constants';
|
||||||
import * as Utils from 'utils/utils';
|
import * as Utils from 'utils/utils';
|
||||||
@@ -28,7 +29,8 @@ type Props = {
|
|||||||
fileInfos: FilePreviewInfo[];
|
fileInfos: FilePreviewInfo[];
|
||||||
uploadsInProgress?: string[];
|
uploadsInProgress?: string[];
|
||||||
uploadsProgressPercent?: {[clientID: string]: FilePreviewInfo};
|
uploadsProgressPercent?: {[clientID: string]: FilePreviewInfo};
|
||||||
}
|
disabledRemoveTooltip?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export default class FilePreview extends React.PureComponent<Props> {
|
export default class FilePreview extends React.PureComponent<Props> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@@ -90,9 +92,7 @@ export default class FilePreview extends React.PureComponent<Props> {
|
|||||||
key={info.id}
|
key={info.id}
|
||||||
className={className}
|
className={className}
|
||||||
>
|
>
|
||||||
<div className='post-image__thumbnail'>
|
<div className='post-image__thumbnail'>{previewImage}</div>
|
||||||
{previewImage}
|
|
||||||
</div>
|
|
||||||
<div className='post-image__details'>
|
<div className='post-image__details'>
|
||||||
<div className='post-image__detail_wrapper'>
|
<div className='post-image__detail_wrapper'>
|
||||||
<div className='post-image__detail'>
|
<div className='post-image__detail'>
|
||||||
@@ -101,12 +101,18 @@ export default class FilePreview extends React.PureComponent<Props> {
|
|||||||
compactDisplay={false}
|
compactDisplay={false}
|
||||||
canDownload={false}
|
canDownload={false}
|
||||||
/>
|
/>
|
||||||
{info.extension && <span className='post-image__type'>{info.extension.toUpperCase()}</span>}
|
{info.extension && (
|
||||||
<span className='post-image__size'>{Utils.fileSizeToString(info.size)}</span>
|
<span className='post-image__type'>
|
||||||
|
{info.extension.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className='post-image__size'>
|
||||||
|
{Utils.fileSizeToString(info.size)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{Boolean(this.props.onRemove) && (
|
{this.props.onRemove && (
|
||||||
<a
|
<a
|
||||||
className='file-preview__remove'
|
className='file-preview__remove'
|
||||||
onClick={this.handleRemove.bind(this, info.id)}
|
onClick={this.handleRemove.bind(this, info.id)}
|
||||||
@@ -114,6 +120,17 @@ export default class FilePreview extends React.PureComponent<Props> {
|
|||||||
<i className='icon icon-close'/>
|
<i className='icon icon-close'/>
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!this.props.onRemove && this.props.disabledRemoveTooltip && (
|
||||||
|
<WithTooltip
|
||||||
|
title={this.props.disabledRemoveTooltip}
|
||||||
|
isVertical={true}
|
||||||
|
>
|
||||||
|
<span className='file-preview__remove file-preview__remove--disabled'>
|
||||||
|
<i className='icon icon-close'/>
|
||||||
|
</span>
|
||||||
|
</WithTooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
|
|||||||
@@ -1857,6 +1857,8 @@
|
|||||||
"admin.permissions.group.guest_create_private_channel.name": "Create Channels",
|
"admin.permissions.group.guest_create_private_channel.name": "Create Channels",
|
||||||
"admin.permissions.group.guest_delete_post.description": "Author's own posts can be deleted.",
|
"admin.permissions.group.guest_delete_post.description": "Author's own posts can be deleted.",
|
||||||
"admin.permissions.group.guest_delete_post.name": "Delete Own Posts",
|
"admin.permissions.group.guest_delete_post.name": "Delete Own Posts",
|
||||||
|
"admin.permissions.group.guest_edit_file_attachment.description": "Allow users to add or remove file attachments when editing posts.",
|
||||||
|
"admin.permissions.group.guest_edit_file_attachment.name": "Edit Attachments",
|
||||||
"admin.permissions.group.guest_edit_post.description": "{editTimeLimitButton} after posting, allow users to edit their own posts.",
|
"admin.permissions.group.guest_edit_post.description": "{editTimeLimitButton} after posting, allow users to edit their own posts.",
|
||||||
"admin.permissions.group.guest_edit_post.name": "Edit Own Posts",
|
"admin.permissions.group.guest_edit_post.name": "Edit Own Posts",
|
||||||
"admin.permissions.group.guest_reactions.description": "Add and delete reactions on posts.",
|
"admin.permissions.group.guest_reactions.description": "Add and delete reactions on posts.",
|
||||||
@@ -1941,6 +1943,8 @@
|
|||||||
"admin.permissions.permission.delete_public_channel.name": "Archive Channels",
|
"admin.permissions.permission.delete_public_channel.name": "Archive Channels",
|
||||||
"admin.permissions.permission.edit_custom_group.description": "Rename custom groups.",
|
"admin.permissions.permission.edit_custom_group.description": "Rename custom groups.",
|
||||||
"admin.permissions.permission.edit_custom_group.name": "Edit",
|
"admin.permissions.permission.edit_custom_group.name": "Edit",
|
||||||
|
"admin.permissions.permission.edit_file_attachment.description": "Allow users to add or remove file attachments when editing posts.",
|
||||||
|
"admin.permissions.permission.edit_file_attachment.name": "Edit Attachments",
|
||||||
"admin.permissions.permission.edit_other_users.description": "Edit other users",
|
"admin.permissions.permission.edit_other_users.description": "Edit other users",
|
||||||
"admin.permissions.permission.edit_other_users.name": "Edit other users",
|
"admin.permissions.permission.edit_other_users.name": "Edit other users",
|
||||||
"admin.permissions.permission.edit_others_posts.description": "Allow users to edit others' posts.",
|
"admin.permissions.permission.edit_others_posts.description": "Allow users to edit others' posts.",
|
||||||
@@ -4229,6 +4233,7 @@
|
|||||||
"file_preview_modal_main_nav.file": "{count, number} of {total, number}",
|
"file_preview_modal_main_nav.file": "{count, number} of {total, number}",
|
||||||
"file_preview_modal_main_nav.nextAriaLabel": "Next file",
|
"file_preview_modal_main_nav.nextAriaLabel": "Next file",
|
||||||
"file_preview_modal_main_nav.prevAriaLabel": "Previous file",
|
"file_preview_modal_main_nav.prevAriaLabel": "Previous file",
|
||||||
|
"file_preview.no_edit_permission": "Post attachments cannot be edited",
|
||||||
"file_search_result_item.copy_link": "Copy link",
|
"file_search_result_item.copy_link": "Copy link",
|
||||||
"file_search_result_item.download": "Download",
|
"file_search_result_item.download": "Download",
|
||||||
"file_search_result_item.more_actions": "More Actions",
|
"file_search_result_item.more_actions": "More Actions",
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ const values = {
|
|||||||
REMOVE_OTHERS_REACTIONS: 'remove_others_reactions',
|
REMOVE_OTHERS_REACTIONS: 'remove_others_reactions',
|
||||||
PERMANENT_DELETE_USER: 'permanent_delete_user',
|
PERMANENT_DELETE_USER: 'permanent_delete_user',
|
||||||
UPLOAD_FILE: 'upload_file',
|
UPLOAD_FILE: 'upload_file',
|
||||||
|
EDIT_FILE_ATTACHMENT: 'edit_file_attachment',
|
||||||
GET_PUBLIC_LINK: 'get_public_link',
|
GET_PUBLIC_LINK: 'get_public_link',
|
||||||
MANAGE_WEBHOOKS: 'manage_webhooks',
|
MANAGE_WEBHOOKS: 'manage_webhooks',
|
||||||
MANAGE_OTHERS_WEBHOOKS: 'manage_others_webhooks',
|
MANAGE_OTHERS_WEBHOOKS: 'manage_others_webhooks',
|
||||||
|
|||||||
@@ -69,6 +69,15 @@
|
|||||||
background: rgba(var(--button-bg-rgb), 0.08);
|
background: rgba(var(--button-bg-rgb), 0.08);
|
||||||
color: functions.v(button-bg);
|
color: functions.v(button-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1254,6 +1254,7 @@ export const PermissionsScope = {
|
|||||||
[Permissions.REMOVE_OTHERS_REACTIONS]: 'channel_scope',
|
[Permissions.REMOVE_OTHERS_REACTIONS]: 'channel_scope',
|
||||||
[Permissions.PERMANENT_DELETE_USER]: 'system_scope',
|
[Permissions.PERMANENT_DELETE_USER]: 'system_scope',
|
||||||
[Permissions.UPLOAD_FILE]: 'channel_scope',
|
[Permissions.UPLOAD_FILE]: 'channel_scope',
|
||||||
|
[Permissions.EDIT_FILE_ATTACHMENT]: 'channel_scope',
|
||||||
[Permissions.GET_PUBLIC_LINK]: 'system_scope',
|
[Permissions.GET_PUBLIC_LINK]: 'system_scope',
|
||||||
[Permissions.MANAGE_INCOMING_WEBHOOKS]: 'team_scope',
|
[Permissions.MANAGE_INCOMING_WEBHOOKS]: 'team_scope',
|
||||||
[Permissions.MANAGE_OTHERS_INCOMING_WEBHOOKS]: 'team_scope',
|
[Permissions.MANAGE_OTHERS_INCOMING_WEBHOOKS]: 'team_scope',
|
||||||
@@ -1350,6 +1351,7 @@ export const DefaultRolePermissions = {
|
|||||||
Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS,
|
Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS,
|
||||||
Permissions.DELETE_POST,
|
Permissions.DELETE_POST,
|
||||||
Permissions.EDIT_POST,
|
Permissions.EDIT_POST,
|
||||||
|
Permissions.EDIT_FILE_ATTACHMENT,
|
||||||
Permissions.USE_CHANNEL_MENTIONS,
|
Permissions.USE_CHANNEL_MENTIONS,
|
||||||
Permissions.USE_GROUP_MENTIONS,
|
Permissions.USE_GROUP_MENTIONS,
|
||||||
Permissions.CREATE_CUSTOM_GROUP,
|
Permissions.CREATE_CUSTOM_GROUP,
|
||||||
@@ -1439,6 +1441,7 @@ export const DefaultRolePermissions = {
|
|||||||
],
|
],
|
||||||
guests: [
|
guests: [
|
||||||
Permissions.EDIT_POST,
|
Permissions.EDIT_POST,
|
||||||
|
Permissions.EDIT_FILE_ATTACHMENT,
|
||||||
Permissions.ADD_REACTION,
|
Permissions.ADD_REACTION,
|
||||||
Permissions.REMOVE_REACTION,
|
Permissions.REMOVE_REACTION,
|
||||||
Permissions.USE_CHANNEL_MENTIONS,
|
Permissions.USE_CHANNEL_MENTIONS,
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user