Cherry picker search-api-filter-guest-permission to release-10.11 (#35018)
Automatic Merge
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
3b1b8d9114
Коммит
4b8b1e5ca0
@@ -12,6 +12,7 @@ import (
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
"maps"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -19,6 +20,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -1501,7 +1503,69 @@ func (a *App) SearchFilesInTeamForUser(c request.CTX, terms string, userId strin
|
||||
}
|
||||
}
|
||||
|
||||
return fileInfoSearchResults, a.filterInaccessibleFiles(fileInfoSearchResults, filterFileOptions{assumeSortedCreatedAt: true})
|
||||
if appErr := a.filterInaccessibleFiles(fileInfoSearchResults, filterFileOptions{assumeSortedCreatedAt: true}); appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
if appErr := a.FilterFilesByChannelPermissions(c, fileInfoSearchResults, userId); appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return fileInfoSearchResults, nil
|
||||
}
|
||||
|
||||
func (a *App) FilterFilesByChannelPermissions(rctx request.CTX, fileList *model.FileInfoList, userID string) *model.AppError {
|
||||
if fileList == nil || fileList.FileInfos == nil || len(fileList.FileInfos) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
channels := make(map[string]*model.Channel)
|
||||
for _, fileInfo := range fileList.FileInfos {
|
||||
if fileInfo.ChannelId != "" {
|
||||
channels[fileInfo.ChannelId] = nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(channels) > 0 {
|
||||
channelIDs := slices.Collect(maps.Keys(channels))
|
||||
channelList, err := a.GetChannels(rctx, channelIDs)
|
||||
if err != nil && err.StatusCode != http.StatusNotFound {
|
||||
return err
|
||||
}
|
||||
for _, channel := range channelList {
|
||||
channels[channel.Id] = channel
|
||||
}
|
||||
}
|
||||
|
||||
channelReadPermission := make(map[string]bool)
|
||||
filteredFiles := make(map[string]*model.FileInfo)
|
||||
filteredOrder := []string{}
|
||||
|
||||
for _, fileID := range fileList.Order {
|
||||
fileInfo, ok := fileList.FileInfos[fileID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := channelReadPermission[fileInfo.ChannelId]; !ok {
|
||||
channel := channels[fileInfo.ChannelId]
|
||||
allowed := false
|
||||
if channel != nil {
|
||||
allowed = a.HasPermissionToReadChannel(rctx, userID, channel)
|
||||
}
|
||||
channelReadPermission[fileInfo.ChannelId] = allowed
|
||||
}
|
||||
|
||||
if channelReadPermission[fileInfo.ChannelId] {
|
||||
filteredFiles[fileID] = fileInfo
|
||||
filteredOrder = append(filteredOrder, fileID)
|
||||
}
|
||||
}
|
||||
|
||||
fileList.FileInfos = filteredFiles
|
||||
fileList.Order = filteredOrder
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) ExtractContentFromFileInfo(rctx request.CTX, fileInfo *model.FileInfo) error {
|
||||
|
||||
@@ -5,6 +5,7 @@ package app
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
@@ -816,3 +817,141 @@ func TestPermanentDeleteFilesByPost(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFilterFilesByChannelPermissions(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.GuestAccountsSettings.Enable = true
|
||||
})
|
||||
|
||||
guestUser := th.CreateGuest()
|
||||
_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, guestUser.Id, "")
|
||||
require.Nil(t, appErr)
|
||||
|
||||
privateChannel := th.CreatePrivateChannel(th.Context, th.BasicTeam)
|
||||
|
||||
_, appErr = th.App.AddUserToChannel(th.Context, guestUser, privateChannel, false)
|
||||
require.Nil(t, appErr)
|
||||
_, appErr = th.App.AddUserToChannel(th.Context, guestUser, th.BasicChannel, false)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
post1 := th.CreatePost(th.BasicChannel)
|
||||
post2 := th.CreatePost(privateChannel)
|
||||
post3 := th.CreatePost(th.BasicChannel)
|
||||
|
||||
fileInfo1 := th.CreateFileInfo(th.BasicUser.Id, post1.Id, th.BasicChannel.Id)
|
||||
fileInfo2 := th.CreateFileInfo(th.BasicUser.Id, post2.Id, privateChannel.Id)
|
||||
fileInfo3 := th.CreateFileInfo(th.BasicUser.Id, post3.Id, th.BasicChannel.Id)
|
||||
|
||||
t.Run("should filter files when user has read_channel_content permission", func(t *testing.T) {
|
||||
fileList := model.NewFileInfoList()
|
||||
fileList.FileInfos[fileInfo1.Id] = fileInfo1
|
||||
fileList.FileInfos[fileInfo2.Id] = fileInfo2
|
||||
fileList.FileInfos[fileInfo3.Id] = fileInfo3
|
||||
fileList.Order = []string{fileInfo1.Id, fileInfo2.Id, fileInfo3.Id}
|
||||
|
||||
// BasicUser should have access to all files
|
||||
appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, fileList.FileInfos, 3)
|
||||
require.Len(t, fileList.Order, 3)
|
||||
})
|
||||
|
||||
t.Run("should filter files when guest has read_channel_content permission", func(t *testing.T) {
|
||||
fileList := model.NewFileInfoList()
|
||||
fileList.FileInfos[fileInfo1.Id] = fileInfo1
|
||||
fileList.FileInfos[fileInfo2.Id] = fileInfo2
|
||||
fileList.FileInfos[fileInfo3.Id] = fileInfo3
|
||||
fileList.Order = []string{fileInfo1.Id, fileInfo2.Id, fileInfo3.Id}
|
||||
|
||||
appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, guestUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, fileList.FileInfos, 3)
|
||||
require.Len(t, fileList.Order, 3)
|
||||
})
|
||||
|
||||
t.Run("should filter files when guest does not have read_channel_content permission", func(t *testing.T) {
|
||||
channelGuestRole, appErr := th.App.GetRoleByName(context.Background(), model.ChannelGuestRoleId)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
originalPermissions := make([]string, len(channelGuestRole.Permissions))
|
||||
copy(originalPermissions, channelGuestRole.Permissions)
|
||||
|
||||
newPermissions := []string{}
|
||||
for _, perm := range channelGuestRole.Permissions {
|
||||
if perm != model.PermissionReadChannelContent.Id && perm != model.PermissionReadChannel.Id {
|
||||
newPermissions = append(newPermissions, perm)
|
||||
}
|
||||
}
|
||||
|
||||
_, appErr = th.App.PatchRole(channelGuestRole, &model.RolePatch{
|
||||
Permissions: &newPermissions,
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
defer func() {
|
||||
_, err := th.App.PatchRole(channelGuestRole, &model.RolePatch{
|
||||
Permissions: &originalPermissions,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
}()
|
||||
|
||||
fileList := model.NewFileInfoList()
|
||||
fileList.FileInfos[fileInfo1.Id] = fileInfo1
|
||||
fileList.FileInfos[fileInfo2.Id] = fileInfo2
|
||||
fileList.FileInfos[fileInfo3.Id] = fileInfo3
|
||||
fileList.Order = []string{fileInfo1.Id, fileInfo2.Id, fileInfo3.Id}
|
||||
|
||||
appErr = th.App.FilterFilesByChannelPermissions(th.Context, fileList, guestUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, fileList.FileInfos, 0)
|
||||
require.Len(t, fileList.Order, 0)
|
||||
})
|
||||
|
||||
t.Run("should handle empty file list", func(t *testing.T) {
|
||||
fileList := model.NewFileInfoList()
|
||||
appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, fileList.FileInfos, 0)
|
||||
require.Len(t, fileList.Order, 0)
|
||||
})
|
||||
|
||||
t.Run("should handle nil file list", func(t *testing.T) {
|
||||
appErr := th.App.FilterFilesByChannelPermissions(th.Context, nil, th.BasicUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("should handle files with empty channel IDs", func(t *testing.T) {
|
||||
fileList := model.NewFileInfoList()
|
||||
fileWithoutChannel := &model.FileInfo{
|
||||
Id: model.NewId(),
|
||||
ChannelId: "",
|
||||
Name: "test.txt",
|
||||
}
|
||||
fileList.FileInfos[fileWithoutChannel.Id] = fileWithoutChannel
|
||||
fileList.Order = []string{fileWithoutChannel.Id}
|
||||
|
||||
appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, fileList.FileInfos, 0)
|
||||
require.Len(t, fileList.Order, 0)
|
||||
})
|
||||
|
||||
t.Run("should handle files from non-existent channels", func(t *testing.T) {
|
||||
fileList := model.NewFileInfoList()
|
||||
fileWithInvalidChannel := &model.FileInfo{
|
||||
Id: model.NewId(),
|
||||
ChannelId: model.NewId(),
|
||||
Name: "test.txt",
|
||||
}
|
||||
fileList.FileInfos[fileWithInvalidChannel.Id] = fileWithInvalidChannel
|
||||
fileList.Order = []string{fileWithInvalidChannel.Id}
|
||||
|
||||
appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, fileList.FileInfos, 0)
|
||||
require.Len(t, fileList.Order, 0)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -1764,9 +1766,67 @@ func (a *App) SearchPostsForUser(c request.CTX, terms string, userID string, tea
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
if appErr := a.FilterPostsByChannelPermissions(c, postSearchResults.PostList, userID); appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return postSearchResults, nil
|
||||
}
|
||||
|
||||
func (a *App) FilterPostsByChannelPermissions(rctx request.CTX, postList *model.PostList, userID string) *model.AppError {
|
||||
if postList == nil || postList.Posts == nil || len(postList.Posts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
channels := make(map[string]*model.Channel)
|
||||
for _, post := range postList.Posts {
|
||||
if post.ChannelId != "" {
|
||||
channels[post.ChannelId] = nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(channels) > 0 {
|
||||
channelIDs := slices.Collect(maps.Keys(channels))
|
||||
channelList, err := a.GetChannels(rctx, channelIDs)
|
||||
if err != nil && err.StatusCode != http.StatusNotFound {
|
||||
return err
|
||||
}
|
||||
for _, channel := range channelList {
|
||||
channels[channel.Id] = channel
|
||||
}
|
||||
}
|
||||
|
||||
channelReadPermission := make(map[string]bool)
|
||||
filteredPosts := make(map[string]*model.Post)
|
||||
filteredOrder := []string{}
|
||||
|
||||
for _, postID := range postList.Order {
|
||||
post, ok := postList.Posts[postID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := channelReadPermission[post.ChannelId]; !ok {
|
||||
channel := channels[post.ChannelId]
|
||||
allowed := false
|
||||
if channel != nil {
|
||||
allowed = a.HasPermissionToReadChannel(rctx, userID, channel)
|
||||
}
|
||||
channelReadPermission[post.ChannelId] = allowed
|
||||
}
|
||||
|
||||
if channelReadPermission[post.ChannelId] {
|
||||
filteredPosts[postID] = post
|
||||
filteredOrder = append(filteredOrder, postID)
|
||||
}
|
||||
}
|
||||
|
||||
postList.Posts = filteredPosts
|
||||
postList.Order = filteredOrder
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) GetFileInfosForPostWithMigration(rctx request.CTX, postID string, includeDeleted bool) ([]*model.FileInfo, *model.AppError) {
|
||||
pchan := make(chan store.StoreResult[*model.Post], 1)
|
||||
go func() {
|
||||
|
||||
@@ -4216,3 +4216,136 @@ func TestPopulateEditHistoryFileMetadata(t *testing.T) {
|
||||
require.Greater(t, post2.Metadata.Files[0].DeleteAt, int64(0))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFilterPostsByChannelPermissions(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.GuestAccountsSettings.Enable = true
|
||||
})
|
||||
|
||||
guestUser := th.CreateGuest()
|
||||
_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, guestUser.Id, "")
|
||||
require.Nil(t, appErr)
|
||||
|
||||
privateChannel := th.CreatePrivateChannel(th.Context, th.BasicTeam)
|
||||
|
||||
_, appErr = th.App.AddUserToChannel(th.Context, guestUser, privateChannel, false)
|
||||
require.Nil(t, appErr)
|
||||
_, appErr = th.App.AddUserToChannel(th.Context, guestUser, th.BasicChannel, false)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
post1 := th.CreatePost(th.BasicChannel)
|
||||
post2 := th.CreatePost(privateChannel)
|
||||
post3 := th.CreatePost(th.BasicChannel)
|
||||
|
||||
t.Run("should filter posts when user has read_channel_content permission", func(t *testing.T) {
|
||||
postList := model.NewPostList()
|
||||
postList.Posts[post1.Id] = post1
|
||||
postList.Posts[post2.Id] = post2
|
||||
postList.Posts[post3.Id] = post3
|
||||
postList.Order = []string{post1.Id, post2.Id, post3.Id}
|
||||
|
||||
appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, th.BasicUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, postList.Posts, 3)
|
||||
require.Len(t, postList.Order, 3)
|
||||
})
|
||||
|
||||
t.Run("should filter posts when guest has read_channel_content permission", func(t *testing.T) {
|
||||
postList := model.NewPostList()
|
||||
postList.Posts[post1.Id] = post1
|
||||
postList.Posts[post2.Id] = post2
|
||||
postList.Posts[post3.Id] = post3
|
||||
postList.Order = []string{post1.Id, post2.Id, post3.Id}
|
||||
|
||||
appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, guestUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, postList.Posts, 3)
|
||||
require.Len(t, postList.Order, 3)
|
||||
})
|
||||
|
||||
t.Run("should filter posts when guest does not have read_channel_content permission", func(t *testing.T) {
|
||||
channelGuestRole, appErr := th.App.GetRoleByName(context.Background(), model.ChannelGuestRoleId)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
originalPermissions := make([]string, len(channelGuestRole.Permissions))
|
||||
copy(originalPermissions, channelGuestRole.Permissions)
|
||||
|
||||
newPermissions := []string{}
|
||||
for _, perm := range channelGuestRole.Permissions {
|
||||
if perm != model.PermissionReadChannelContent.Id && perm != model.PermissionReadChannel.Id {
|
||||
newPermissions = append(newPermissions, perm)
|
||||
}
|
||||
}
|
||||
|
||||
_, appErr = th.App.PatchRole(channelGuestRole, &model.RolePatch{
|
||||
Permissions: &newPermissions,
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
defer func() {
|
||||
_, err := th.App.PatchRole(channelGuestRole, &model.RolePatch{
|
||||
Permissions: &originalPermissions,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
}()
|
||||
|
||||
postList := model.NewPostList()
|
||||
postList.Posts[post1.Id] = post1
|
||||
postList.Posts[post2.Id] = post2
|
||||
postList.Posts[post3.Id] = post3
|
||||
postList.Order = []string{post1.Id, post2.Id, post3.Id}
|
||||
|
||||
appErr = th.App.FilterPostsByChannelPermissions(th.Context, postList, guestUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, postList.Posts, 0)
|
||||
require.Len(t, postList.Order, 0)
|
||||
})
|
||||
|
||||
t.Run("should handle empty post list", func(t *testing.T) {
|
||||
postList := model.NewPostList()
|
||||
appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, th.BasicUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, postList.Posts, 0)
|
||||
require.Len(t, postList.Order, 0)
|
||||
})
|
||||
|
||||
t.Run("should handle nil post list", func(t *testing.T) {
|
||||
appErr := th.App.FilterPostsByChannelPermissions(th.Context, nil, th.BasicUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("should handle posts with empty channel IDs", func(t *testing.T) {
|
||||
postList := model.NewPostList()
|
||||
postWithoutChannel := &model.Post{
|
||||
Id: model.NewId(),
|
||||
ChannelId: "",
|
||||
Message: "test",
|
||||
}
|
||||
postList.Posts[postWithoutChannel.Id] = postWithoutChannel
|
||||
postList.Order = []string{postWithoutChannel.Id}
|
||||
|
||||
appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, th.BasicUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, postList.Posts, 0)
|
||||
require.Len(t, postList.Order, 0)
|
||||
})
|
||||
|
||||
t.Run("should handle posts from non-existent channels", func(t *testing.T) {
|
||||
postList := model.NewPostList()
|
||||
postWithInvalidChannel := &model.Post{
|
||||
Id: model.NewId(),
|
||||
ChannelId: model.NewId(),
|
||||
Message: "test",
|
||||
}
|
||||
postList.Posts[postWithInvalidChannel.Id] = postWithInvalidChannel
|
||||
postList.Order = []string{postWithInvalidChannel.Id}
|
||||
|
||||
appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, th.BasicUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, postList.Posts, 0)
|
||||
require.Len(t, postList.Order, 0)
|
||||
})
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user