Cherry picker search-api-filter-guest-permission to release-10.11 (#35018)

Automatic Merge
Этот коммит содержится в:
Rajat Dabade
2026-01-22 15:48:51 +05:30
коммит произвёл GitHub
родитель 3b1b8d9114
Коммит 4b8b1e5ca0
4 изменённых файлов: 397 добавлений и 1 удалений

Просмотреть файл

@@ -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)
})
}