mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
The server module named github.com/hanzoai/team/server/v8 — a path that resolves to the retired workspace's repository, not this one — while server/public still published github.com/mattermost/mattermost/server/public. Both are github.com/hanzoteam/server/... now, which is where this code actually lives. That second half is what made the binary unbootable. Upstream's public path stayed claimed by upstream, mattermost-plugin-ai imported it, and the build linked public/model twice; public/plugin's gob.Register then ran twice under one name and panicked at init, before main. Reverting the rename traded the brand for a working binary. Rebranding the plugin alongside it needs no such trade: the agents fork is github.com/hanzoteam/agents/v2 against our public module, so no module named mattermost/mattermost is in the graph at all. Measured: `go list -deps ./cmd/mattermost` names exactly one public/model, and it is ours.
1159 lines
38 KiB
Go
1159 lines
38 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/hanzoteam/server/server/public/model"
|
|
"github.com/hanzoteam/server/server/v8/channels/app/platform"
|
|
platform_mocks "github.com/hanzoteam/server/server/v8/channels/app/platform/mocks"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestAddMentionsHook_Process(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
hook := &addMentionsBroadcastHook{}
|
|
|
|
userID := model.NewId()
|
|
otherUserID := model.NewId()
|
|
|
|
webConn := &platform.WebConn{
|
|
UserId: userID,
|
|
}
|
|
|
|
t.Run("should add a mentions entry for the current user", func(t *testing.T) {
|
|
msg := platform.MakeHookedWebSocketEvent(model.NewWebSocketEvent(model.WebsocketEventPosted, "", "", "", nil, ""))
|
|
|
|
require.Nil(t, msg.Event().GetData()["mentions"])
|
|
|
|
err := hook.Process(msg, webConn, map[string]any{
|
|
"mentions": model.StringArray{userID},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, `["`+userID+`"]`, msg.Event().GetData()["mentions"])
|
|
assert.Nil(t, msg.Event().GetData()["followers"])
|
|
})
|
|
|
|
t.Run("should not add a mentions entry for another user", func(t *testing.T) {
|
|
msg := platform.MakeHookedWebSocketEvent(model.NewWebSocketEvent(model.WebsocketEventPosted, "", "", "", nil, ""))
|
|
|
|
require.Nil(t, msg.Event().GetData()["mentions"])
|
|
|
|
err := hook.Process(msg, webConn, map[string]any{
|
|
"mentions": model.StringArray{otherUserID},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
assert.Nil(t, msg.Event().GetData()["mentions"])
|
|
})
|
|
}
|
|
|
|
func TestAddFollowersHook_Process(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
hook := &addFollowersBroadcastHook{}
|
|
|
|
userID := model.NewId()
|
|
otherUserID := model.NewId()
|
|
|
|
webConn := &platform.WebConn{
|
|
UserId: userID,
|
|
}
|
|
|
|
t.Run("should add a followers entry for the current user", func(t *testing.T) {
|
|
msg := platform.MakeHookedWebSocketEvent(model.NewWebSocketEvent(model.WebsocketEventPosted, "", "", "", nil, ""))
|
|
|
|
require.Nil(t, msg.Event().GetData()["followers"])
|
|
|
|
err := hook.Process(msg, webConn, map[string]any{
|
|
"followers": model.StringArray{userID},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, `["`+userID+`"]`, msg.Event().GetData()["followers"])
|
|
})
|
|
|
|
t.Run("should not add a followers entry for another user", func(t *testing.T) {
|
|
msg := platform.MakeHookedWebSocketEvent(model.NewWebSocketEvent(model.WebsocketEventPosted, "", "", "", nil, ""))
|
|
|
|
require.Nil(t, msg.Event().GetData()["followers"])
|
|
|
|
err := hook.Process(msg, webConn, map[string]any{
|
|
"followers": model.StringArray{otherUserID},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
assert.Nil(t, msg.Event().GetData()["followers"])
|
|
})
|
|
}
|
|
|
|
func TestPostedAckHook_Process(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
hook := &postedAckBroadcastHook{}
|
|
userID := model.NewId()
|
|
webConn := &platform.WebConn{
|
|
UserId: userID,
|
|
Platform: &platform.PlatformService{},
|
|
PostedAck: true,
|
|
}
|
|
webConn.Active.Store(true)
|
|
webConn.SetSession(&model.Session{})
|
|
|
|
t.Run("should ack if user is in the list of users to notify", func(t *testing.T) {
|
|
msg := platform.MakeHookedWebSocketEvent(model.NewWebSocketEvent(model.WebsocketEventPosted, "", "", "", nil, ""))
|
|
|
|
err := hook.Process(msg, webConn, map[string]any{
|
|
"posted_user_id": model.NewId(),
|
|
"channel_type": model.ChannelTypeOpen,
|
|
"users": []string{userID},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, msg.Event().GetData()["should_ack"].(bool))
|
|
})
|
|
|
|
t.Run("should not ack if user is not in the list of users to notify", func(t *testing.T) {
|
|
msg := platform.MakeHookedWebSocketEvent(model.NewWebSocketEvent(model.WebsocketEventPosted, "", "", "", nil, ""))
|
|
|
|
err := hook.Process(msg, webConn, map[string]any{
|
|
"posted_user_id": model.NewId(),
|
|
"channel_type": model.ChannelTypeOpen,
|
|
"users": []string{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
assert.Nil(t, msg.Event().GetData()["should_ack"])
|
|
})
|
|
|
|
t.Run("should not ack if you are the user who posted", func(t *testing.T) {
|
|
msg := platform.MakeHookedWebSocketEvent(model.NewWebSocketEvent(model.WebsocketEventPosted, "", "", "", nil, ""))
|
|
|
|
err := hook.Process(msg, webConn, map[string]any{
|
|
"posted_user_id": userID,
|
|
"channel_type": model.ChannelTypeOpen,
|
|
"users": []string{userID},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
assert.Nil(t, msg.Event().GetData()["should_ack"])
|
|
})
|
|
|
|
t.Run("should ack if the channel is a DM", func(t *testing.T) {
|
|
msg := platform.MakeHookedWebSocketEvent(model.NewWebSocketEvent(model.WebsocketEventPosted, "", "", "", nil, ""))
|
|
|
|
err := hook.Process(msg, webConn, map[string]any{
|
|
"posted_user_id": model.NewId(),
|
|
"channel_type": model.ChannelTypeDirect,
|
|
"users": []string{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, msg.Event().GetData()["should_ack"].(bool))
|
|
})
|
|
|
|
t.Run("should not ack if posted ack is false", func(t *testing.T) {
|
|
noAckWebConn := &platform.WebConn{
|
|
UserId: userID,
|
|
Platform: &platform.PlatformService{},
|
|
PostedAck: false,
|
|
}
|
|
noAckWebConn.Active.Store(true)
|
|
msg := platform.MakeHookedWebSocketEvent(model.NewWebSocketEvent(model.WebsocketEventPosted, "", "", "", nil, ""))
|
|
|
|
err := hook.Process(msg, noAckWebConn, map[string]any{
|
|
"posted_user_id": model.NewId(),
|
|
"channel_type": model.ChannelTypeDirect,
|
|
"users": []string{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
assert.Nil(t, msg.Event().GetData()["should_ack"])
|
|
})
|
|
|
|
t.Run("should not ack if connection is not active", func(t *testing.T) {
|
|
inactiveWebConn := &platform.WebConn{
|
|
UserId: userID,
|
|
Platform: &platform.PlatformService{},
|
|
PostedAck: false,
|
|
}
|
|
inactiveWebConn.Active.Store(true)
|
|
msg := platform.MakeHookedWebSocketEvent(model.NewWebSocketEvent(model.WebsocketEventPosted, "", "", "", nil, ""))
|
|
|
|
err := hook.Process(msg, inactiveWebConn, map[string]any{
|
|
"posted_user_id": model.NewId(),
|
|
"channel_type": model.ChannelTypeDirect,
|
|
"users": []string{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
assert.Nil(t, msg.Event().GetData()["should_ack"])
|
|
})
|
|
}
|
|
|
|
func TestAddMentionsAndAddFollowersHooks(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
addMentionsHook := &addMentionsBroadcastHook{}
|
|
addFollowersHook := &addFollowersBroadcastHook{}
|
|
|
|
userID := model.NewId()
|
|
|
|
webConn := &platform.WebConn{
|
|
UserId: userID,
|
|
}
|
|
|
|
msg := platform.MakeHookedWebSocketEvent(model.NewWebSocketEvent(model.WebsocketEventPosted, "", "", "", nil, ""))
|
|
|
|
originalData := msg.Event().GetData()
|
|
|
|
require.Nil(t, originalData["mentions"])
|
|
require.Nil(t, originalData["followers"])
|
|
|
|
err := addMentionsHook.Process(msg, webConn, map[string]any{
|
|
"mentions": model.StringArray{userID},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
err = addFollowersHook.Process(msg, webConn, map[string]any{
|
|
"followers": model.StringArray{userID},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
t.Run("should be able to add both mentions and followers to a single event", func(t *testing.T) {
|
|
assert.Equal(t, `["`+userID+`"]`, msg.Event().GetData()["followers"])
|
|
assert.Equal(t, `["`+userID+`"]`, msg.Event().GetData()["mentions"])
|
|
})
|
|
}
|
|
|
|
func TestPermalinkBroadcastHook(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
session, err := th.Server.Platform().CreateSession(th.Context, &model.Session{
|
|
UserId: th.BasicUser.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
wc := &platform.WebConn{
|
|
Platform: th.Server.Platform(),
|
|
Suite: th.App,
|
|
UserId: session.UserId,
|
|
}
|
|
hook := &permalinkBroadcastHook{}
|
|
|
|
refPost := th.CreatePost(t, th.BasicChannel)
|
|
previewPost := model.NewPreviewPost(refPost, th.BasicTeam, th.BasicChannel)
|
|
|
|
// Create a clean post (no metadata)
|
|
cleanPost := th.BasicPost.Clone()
|
|
cleanPost.Metadata = &model.PostMetadata{}
|
|
cleanJSON, err := cleanPost.ToJSON()
|
|
require.NoError(t, err)
|
|
|
|
wsEvent := model.NewWebSocketEvent(model.WebsocketEventPosted, "", th.BasicPost.ChannelId, "", nil, "")
|
|
wsEvent.Add("post", cleanJSON)
|
|
|
|
t.Run("should add permalink metadata when user has permission", func(t *testing.T) {
|
|
msg := platform.MakeHookedWebSocketEvent(wsEvent)
|
|
|
|
err = hook.Process(msg, wc, map[string]any{
|
|
"preview_channel": th.BasicChannel,
|
|
"permalink_previewed_post": previewPost,
|
|
"preview_prop": refPost.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
gotJSON, ok := msg.Get("post").(string)
|
|
require.True(t, ok)
|
|
|
|
var gotPost model.Post
|
|
err = json.Unmarshal([]byte(gotJSON), &gotPost)
|
|
require.NoError(t, err)
|
|
|
|
// Verify permalink metadata was added
|
|
assert.Equal(t, refPost.Id, gotPost.GetPreviewedPostProp())
|
|
assert.Len(t, gotPost.Metadata.Embeds, 1)
|
|
assert.Equal(t, model.PostEmbedPermalink, gotPost.Metadata.Embeds[0].Type)
|
|
})
|
|
|
|
t.Run("should not add permalink metadata when user lacks permission", func(t *testing.T) {
|
|
msg := platform.MakeHookedWebSocketEvent(wsEvent)
|
|
|
|
// User does not exist, and thus won't have permission to the channel
|
|
noPermWc := &platform.WebConn{
|
|
Platform: th.Server.Platform(),
|
|
Suite: th.App,
|
|
UserId: "otheruser",
|
|
}
|
|
|
|
err = hook.Process(msg, noPermWc, map[string]any{
|
|
"preview_channel": th.BasicChannel,
|
|
"permalink_previewed_post": previewPost,
|
|
"preview_prop": refPost.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
gotJSON, ok := msg.Get("post").(string)
|
|
require.True(t, ok)
|
|
|
|
// Post should remain clean (no metadata added)
|
|
assert.Equal(t, cleanJSON, gotJSON)
|
|
})
|
|
}
|
|
|
|
func TestChannelMentionsBroadcastHook(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
session, err := th.Server.Platform().CreateSession(th.Context, &model.Session{
|
|
UserId: th.BasicUser.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
wc := &platform.WebConn{
|
|
Platform: th.Server.Platform(),
|
|
Suite: th.App,
|
|
UserId: session.UserId,
|
|
}
|
|
hook := &channelMentionsBroadcastHook{}
|
|
|
|
// Create a private channel that BasicUser doesn't have access to
|
|
// Use BasicUser2 to create it so BasicUser is never added
|
|
privateChannel := th.CreatePrivateChannel(t, th.BasicTeam)
|
|
// Remove BasicUser if they were added automatically
|
|
_ = th.App.RemoveUserFromChannel(th.Context, th.BasicUser.Id, "", privateChannel)
|
|
// Ensure BasicUser2 is a member
|
|
th.AddUserToChannel(t, th.BasicUser2, privateChannel)
|
|
|
|
// Create channel mentions map with both channels
|
|
channelMentions := map[string]any{
|
|
th.BasicChannel.Name: map[string]any{
|
|
"id": th.BasicChannel.Id,
|
|
"display_name": th.BasicChannel.DisplayName,
|
|
},
|
|
privateChannel.Name: map[string]any{
|
|
"id": privateChannel.Id,
|
|
"display_name": privateChannel.DisplayName,
|
|
},
|
|
}
|
|
|
|
// Create a clean post
|
|
cleanPost := th.BasicPost.Clone()
|
|
cleanPost.Metadata = &model.PostMetadata{}
|
|
cleanJSON, err := cleanPost.ToJSON()
|
|
require.NoError(t, err)
|
|
|
|
wsEvent := model.NewWebSocketEvent(model.WebsocketEventPosted, "", th.BasicPost.ChannelId, "", nil, "")
|
|
wsEvent.Add("post", cleanJSON)
|
|
|
|
t.Run("should filter channel mentions based on user permissions", func(t *testing.T) {
|
|
msg := platform.MakeHookedWebSocketEvent(wsEvent)
|
|
|
|
err = hook.Process(msg, wc, map[string]any{
|
|
"channel_mentions": channelMentions,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
gotJSON, ok := msg.Get("post").(string)
|
|
require.True(t, ok)
|
|
|
|
var gotPost model.Post
|
|
err = json.Unmarshal([]byte(gotJSON), &gotPost)
|
|
require.NoError(t, err)
|
|
|
|
// Verify only the permitted channel mention was added
|
|
mentions := gotPost.GetProp(model.PostPropsChannelMentions)
|
|
require.NotNil(t, mentions)
|
|
|
|
mentionsMap, ok := mentions.(map[string]any)
|
|
require.True(t, ok)
|
|
|
|
// Should have access to BasicChannel
|
|
assert.Contains(t, mentionsMap, th.BasicChannel.Name)
|
|
// Should NOT have access to privateChannel
|
|
assert.NotContains(t, mentionsMap, privateChannel.Name)
|
|
})
|
|
|
|
t.Run("should not add channel mentions when user has no permission to any", func(t *testing.T) {
|
|
msg := platform.MakeHookedWebSocketEvent(wsEvent)
|
|
|
|
// Only include the private channel
|
|
privateOnlyMentions := map[string]any{
|
|
privateChannel.Name: map[string]any{
|
|
"id": privateChannel.Id,
|
|
"display_name": privateChannel.DisplayName,
|
|
},
|
|
}
|
|
|
|
err = hook.Process(msg, wc, map[string]any{
|
|
"channel_mentions": privateOnlyMentions,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
gotJSON, ok := msg.Get("post").(string)
|
|
require.True(t, ok)
|
|
|
|
var gotPost model.Post
|
|
err = json.Unmarshal([]byte(gotJSON), &gotPost)
|
|
require.NoError(t, err)
|
|
|
|
// Should have no channel mentions
|
|
mentions := gotPost.GetProp(model.PostPropsChannelMentions)
|
|
assert.Nil(t, mentions)
|
|
})
|
|
|
|
t.Run("should accumulate with existing post metadata", func(t *testing.T) {
|
|
// Create a post that already has some metadata (e.g., from permalink hook)
|
|
refPost := th.CreatePost(t, th.BasicChannel)
|
|
postWithMetadata := th.BasicPost.Clone()
|
|
postWithMetadata.Metadata = &model.PostMetadata{
|
|
Embeds: []*model.PostEmbed{
|
|
{Type: model.PostEmbedPermalink, Data: model.NewPreviewPost(refPost, th.BasicTeam, th.BasicChannel)},
|
|
},
|
|
}
|
|
postWithMetadataJSON, jsonErr := postWithMetadata.ToJSON()
|
|
require.NoError(t, jsonErr)
|
|
|
|
wsEventWithMeta := model.NewWebSocketEvent(model.WebsocketEventPosted, "", th.BasicPost.ChannelId, "", nil, "")
|
|
wsEventWithMeta.Add("post", postWithMetadataJSON)
|
|
msg := platform.MakeHookedWebSocketEvent(wsEventWithMeta)
|
|
|
|
err = hook.Process(msg, wc, map[string]any{
|
|
"channel_mentions": map[string]any{
|
|
th.BasicChannel.Name: map[string]any{
|
|
"id": th.BasicChannel.Id,
|
|
"display_name": th.BasicChannel.DisplayName,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
gotJSON, ok := msg.Get("post").(string)
|
|
require.True(t, ok)
|
|
|
|
var gotPost model.Post
|
|
err = json.Unmarshal([]byte(gotJSON), &gotPost)
|
|
require.NoError(t, err)
|
|
|
|
// Should have BOTH the permalink embed AND channel mentions
|
|
assert.Len(t, gotPost.Metadata.Embeds, 1, "Permalink embed should be preserved")
|
|
assert.Equal(t, model.PostEmbedPermalink, gotPost.Metadata.Embeds[0].Type)
|
|
|
|
mentions := gotPost.GetProp(model.PostPropsChannelMentions)
|
|
require.NotNil(t, mentions, "Channel mentions should be added")
|
|
})
|
|
|
|
t.Run("should handle empty channel mentions", func(t *testing.T) {
|
|
msg := platform.MakeHookedWebSocketEvent(wsEvent)
|
|
|
|
err = hook.Process(msg, wc, map[string]any{
|
|
"channel_mentions": map[string]any{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Should return early without error
|
|
gotJSON, ok := msg.Get("post").(string)
|
|
require.True(t, ok)
|
|
assert.Equal(t, cleanJSON, gotJSON)
|
|
})
|
|
|
|
t.Run("retains same-team public channel mention for non-member recipient under compliance", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(c *model.Config) {
|
|
c.ComplianceSettings.Enable = model.NewPointer(true)
|
|
})
|
|
defer th.App.UpdateConfig(func(c *model.Config) {
|
|
c.ComplianceSettings.Enable = model.NewPointer(false)
|
|
})
|
|
|
|
// BasicUser is a BasicTeam member (via InitBasic) but NOT a member of this fresh
|
|
// channel (created without adding the creator as a member).
|
|
pub, appErr := th.App.CreateChannel(th.Context, &model.Channel{
|
|
TeamId: th.BasicTeam.Id,
|
|
Name: "name_" + model.NewId(),
|
|
DisplayName: "Public Channel",
|
|
Type: model.ChannelTypeOpen,
|
|
}, false)
|
|
require.Nil(t, appErr)
|
|
|
|
msg := platform.MakeHookedWebSocketEvent(wsEvent)
|
|
err = hook.Process(msg, wc, map[string]any{
|
|
"channel_mentions": map[string]any{
|
|
pub.Name: map[string]any{
|
|
"id": pub.Id,
|
|
"display_name": pub.DisplayName,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
gotJSON, ok := msg.Get("post").(string)
|
|
require.True(t, ok)
|
|
|
|
var gotPost model.Post
|
|
err = json.Unmarshal([]byte(gotJSON), &gotPost)
|
|
require.NoError(t, err)
|
|
|
|
mentions := gotPost.GetProp(model.PostPropsChannelMentions)
|
|
require.NotNil(t, mentions)
|
|
mentionsMap, ok := mentions.(map[string]any)
|
|
require.True(t, ok)
|
|
assert.Contains(t, mentionsMap, pub.Name)
|
|
})
|
|
|
|
t.Run("strips cross-team public channel mention for non-member recipient regardless of compliance", func(t *testing.T) {
|
|
team2 := th.CreateTeam(t)
|
|
// Created without adding the creator so BasicUser is neither a team nor channel member.
|
|
ch, appErr := th.App.CreateChannel(th.Context, &model.Channel{
|
|
TeamId: team2.Id,
|
|
Name: "name_" + model.NewId(),
|
|
DisplayName: "Cross Team Channel",
|
|
Type: model.ChannelTypeOpen,
|
|
}, false)
|
|
require.Nil(t, appErr)
|
|
|
|
for _, compliance := range []bool{true, false} {
|
|
t.Run(fmt.Sprintf("compliance=%t", compliance), func(t *testing.T) {
|
|
th.App.UpdateConfig(func(c *model.Config) {
|
|
c.ComplianceSettings.Enable = model.NewPointer(compliance)
|
|
})
|
|
defer th.App.UpdateConfig(func(c *model.Config) {
|
|
c.ComplianceSettings.Enable = model.NewPointer(false)
|
|
})
|
|
|
|
msg := platform.MakeHookedWebSocketEvent(wsEvent)
|
|
err = hook.Process(msg, wc, map[string]any{
|
|
"channel_mentions": map[string]any{
|
|
ch.Name: map[string]any{
|
|
"id": ch.Id,
|
|
"display_name": ch.DisplayName,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
gotJSON, ok := msg.Get("post").(string)
|
|
require.True(t, ok)
|
|
|
|
var gotPost model.Post
|
|
err = json.Unmarshal([]byte(gotJSON), &gotPost)
|
|
require.NoError(t, err)
|
|
|
|
assert.Nil(t, gotPost.GetProp(model.PostPropsChannelMentions))
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("strips private channel mention for non-member recipient regardless of compliance", func(t *testing.T) {
|
|
for _, compliance := range []bool{true, false} {
|
|
t.Run(fmt.Sprintf("compliance=%t", compliance), func(t *testing.T) {
|
|
th.App.UpdateConfig(func(c *model.Config) {
|
|
c.ComplianceSettings.Enable = model.NewPointer(compliance)
|
|
})
|
|
defer th.App.UpdateConfig(func(c *model.Config) {
|
|
c.ComplianceSettings.Enable = model.NewPointer(false)
|
|
})
|
|
|
|
msg := platform.MakeHookedWebSocketEvent(wsEvent)
|
|
err = hook.Process(msg, wc, map[string]any{
|
|
"channel_mentions": map[string]any{
|
|
privateChannel.Name: map[string]any{
|
|
"id": privateChannel.Id,
|
|
"display_name": privateChannel.DisplayName,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
gotJSON, ok := msg.Get("post").(string)
|
|
require.True(t, ok)
|
|
|
|
var gotPost model.Post
|
|
err = json.Unmarshal([]byte(gotJSON), &gotPost)
|
|
require.NoError(t, err)
|
|
|
|
assert.Nil(t, gotPost.GetProp(model.PostPropsChannelMentions))
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPermalinkBroadcastHook_AbacFileStripping(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
hook := &permalinkBroadcastHook{}
|
|
|
|
userID := model.NewId()
|
|
postChannelID := model.NewId()
|
|
refChannelID := model.NewId()
|
|
|
|
previewChannel := &model.Channel{Id: refChannelID, Name: "ref-channel", Type: model.ChannelTypeOpen}
|
|
|
|
// makeRefPost builds a PreviewPost with optional file attachments.
|
|
makeRefPost := func(fileIDs []string, files []*model.FileInfo) *model.PreviewPost {
|
|
refPost := &model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: refChannelID,
|
|
FileIds: fileIDs,
|
|
Metadata: &model.PostMetadata{Files: files},
|
|
}
|
|
return &model.PreviewPost{
|
|
PostID: refPost.Id,
|
|
Post: refPost,
|
|
}
|
|
}
|
|
|
|
// makeOuterPostJSON creates a clean outer post JSON to seed the WS event.
|
|
makeOuterPostJSON := func(t *testing.T) string {
|
|
t.Helper()
|
|
outerPost := &model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: postChannelID,
|
|
Metadata: &model.PostMetadata{},
|
|
}
|
|
postJSON, err := outerPost.ToJSON()
|
|
require.NoError(t, err)
|
|
return postJSON
|
|
}
|
|
|
|
makeMessage := func(postJSON string) *platform.HookedWebSocketEvent {
|
|
event := model.NewWebSocketEvent(model.WebsocketEventPosted, "", postChannelID, "", nil, "")
|
|
event.Add("post", postJSON)
|
|
return platform.MakeHookedWebSocketEvent(event)
|
|
}
|
|
|
|
makeWebConn := func(t *testing.T, allowed bool) *platform.WebConn {
|
|
t.Helper()
|
|
mockSuite := &platform_mocks.SuiteIFace{}
|
|
mockSuite.On("HasPermissionToReadChannel", mock.Anything, userID, previewChannel).Return(true, true)
|
|
mockSuite.On("HasPermissionToFileAction", mock.Anything, userID, mock.AnythingOfType("string"), refChannelID, model.AccessControlPolicyActionDownloadFileAttachment).Return(allowed)
|
|
mockSuite.On("MakeAuditRecord", mock.Anything, mock.Anything, mock.Anything).Return(&model.AuditRecord{})
|
|
mockSuite.On("LogAuditRec", mock.Anything, mock.Anything, mock.Anything).Return()
|
|
wc := &platform.WebConn{
|
|
UserId: userID,
|
|
Platform: &platform.PlatformService{},
|
|
Suite: mockSuite,
|
|
}
|
|
session := &model.Session{UserId: userID, Roles: model.SystemUserRoleId}
|
|
wc.SetSession(session)
|
|
return wc
|
|
}
|
|
|
|
// makeWebConnNoFileCheck builds a webConn whose mock does NOT expect HasPermissionToFileAction.
|
|
makeWebConnNoFileCheck := func(t *testing.T) *platform.WebConn {
|
|
t.Helper()
|
|
mockSuite := &platform_mocks.SuiteIFace{}
|
|
mockSuite.On("HasPermissionToReadChannel", mock.Anything, userID, previewChannel).Return(true, true)
|
|
mockSuite.On("MakeAuditRecord", mock.Anything, mock.Anything, mock.Anything).Return(&model.AuditRecord{})
|
|
mockSuite.On("LogAuditRec", mock.Anything, mock.Anything, mock.Anything).Return()
|
|
wc := &platform.WebConn{
|
|
UserId: userID,
|
|
Platform: &platform.PlatformService{},
|
|
Suite: mockSuite,
|
|
}
|
|
session := &model.Session{UserId: userID, Roles: model.SystemUserRoleId}
|
|
wc.SetSession(session)
|
|
return wc
|
|
}
|
|
|
|
extractPost := func(t *testing.T, msg *platform.HookedWebSocketEvent) *model.Post {
|
|
t.Helper()
|
|
gotJSON, ok := msg.Get("post").(string)
|
|
require.True(t, ok)
|
|
var gotPost model.Post
|
|
err := json.Unmarshal([]byte(gotJSON), &gotPost)
|
|
require.NoError(t, err)
|
|
return &gotPost
|
|
}
|
|
|
|
t.Run("files stripped from embed when user denied download", func(t *testing.T) {
|
|
postJSON := makeOuterPostJSON(t)
|
|
msg := makeMessage(postJSON)
|
|
wc := makeWebConn(t, false)
|
|
|
|
fileID := model.NewId()
|
|
previewPost := makeRefPost(
|
|
model.StringArray{fileID},
|
|
[]*model.FileInfo{{Id: fileID, Name: "photo.png", Extension: "png"}},
|
|
)
|
|
// Save the original Post pointer to verify the hook does not replace it.
|
|
origPostPtr := previewPost.Post
|
|
|
|
err := hook.Process(msg, wc, map[string]any{
|
|
"preview_channel": previewChannel,
|
|
"permalink_previewed_post": previewPost,
|
|
"preview_prop": previewPost.PostID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
gotPost := extractPost(t, msg)
|
|
require.Len(t, gotPost.Metadata.Embeds, 1)
|
|
assert.Equal(t, model.PostEmbedPermalink, gotPost.Metadata.Embeds[0].Type)
|
|
|
|
// The embed's data is serialized/deserialized as JSON — inspect via raw JSON.
|
|
gotJSON, _ := msg.Get("post").(string)
|
|
assert.NotContains(t, gotJSON, fileID, "file ID should be stripped from embed")
|
|
assert.Contains(t, gotJSON, `"redacted_file_count":1`, "redacted count should reflect the stripped file")
|
|
|
|
// The hook creates a previewCopy with a cloned Post; the original PreviewPost.Post
|
|
// pointer must not be swapped out.
|
|
assert.Same(t, origPostPtr, previewPost.Post, "original PreviewPost.Post pointer must not be replaced")
|
|
})
|
|
|
|
t.Run("files preserved in embed when user allowed download", func(t *testing.T) {
|
|
postJSON := makeOuterPostJSON(t)
|
|
msg := makeMessage(postJSON)
|
|
wc := makeWebConn(t, true)
|
|
|
|
fileID := model.NewId()
|
|
previewPost := makeRefPost(
|
|
model.StringArray{fileID},
|
|
[]*model.FileInfo{{Id: fileID, Name: "photo.png", Extension: "png"}},
|
|
)
|
|
|
|
err := hook.Process(msg, wc, map[string]any{
|
|
"preview_channel": previewChannel,
|
|
"permalink_previewed_post": previewPost,
|
|
"preview_prop": previewPost.PostID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
gotPost := extractPost(t, msg)
|
|
require.Len(t, gotPost.Metadata.Embeds, 1)
|
|
|
|
gotJSON, _ := msg.Get("post").(string)
|
|
assert.Contains(t, gotJSON, fileID, "file ID should be present in the embed")
|
|
assert.NotContains(t, gotJSON, `"redacted_file_count"`, "no redaction should occur")
|
|
})
|
|
|
|
t.Run("files stripped when FileIds non-empty but Metadata.Files already nil", func(t *testing.T) {
|
|
postJSON := makeOuterPostJSON(t)
|
|
msg := makeMessage(postJSON)
|
|
wc := makeWebConn(t, false)
|
|
|
|
fileID := model.NewId()
|
|
// FileIds set but Metadata.Files nil — simulates upstream stripping.
|
|
previewPost := makeRefPost(
|
|
model.StringArray{fileID},
|
|
nil,
|
|
)
|
|
|
|
err := hook.Process(msg, wc, map[string]any{
|
|
"preview_channel": previewChannel,
|
|
"permalink_previewed_post": previewPost,
|
|
"preview_prop": previewPost.PostID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
gotPost := extractPost(t, msg)
|
|
require.Len(t, gotPost.Metadata.Embeds, 1)
|
|
|
|
gotJSON, _ := msg.Get("post").(string)
|
|
assert.NotContains(t, gotJSON, fileID, "file ID should be cleared")
|
|
// RedactedFileCount falls back to len(FileIds) when Metadata.Files is nil,
|
|
// so the placeholder count is 1 even though Files was already cleared upstream.
|
|
assert.Contains(t, gotJSON, `"redacted_file_count":1`, "file count falls back to FileIds length")
|
|
})
|
|
|
|
t.Run("no file stripping when embed has no files", func(t *testing.T) {
|
|
postJSON := makeOuterPostJSON(t)
|
|
msg := makeMessage(postJSON)
|
|
// Use webConn that does NOT expect HasPermissionToFileAction to be called.
|
|
wc := makeWebConnNoFileCheck(t)
|
|
|
|
previewPost := makeRefPost(model.StringArray{}, nil)
|
|
|
|
err := hook.Process(msg, wc, map[string]any{
|
|
"preview_channel": previewChannel,
|
|
"permalink_previewed_post": previewPost,
|
|
"preview_prop": previewPost.PostID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
gotPost := extractPost(t, msg)
|
|
require.Len(t, gotPost.Metadata.Embeds, 1)
|
|
|
|
gotJSON, _ := msg.Get("post").(string)
|
|
assert.NotContains(t, gotJSON, `"redacted_file_count"`, "no redaction metadata should be present")
|
|
})
|
|
|
|
t.Run("nil session: fail-secure, files stripped from permalink preview", func(t *testing.T) {
|
|
postJSON := makeOuterPostJSON(t)
|
|
msg := makeMessage(postJSON)
|
|
|
|
// WebConn with no session — cannot evaluate permissions, must strip files.
|
|
mockSuite := &platform_mocks.SuiteIFace{}
|
|
mockSuite.On("HasPermissionToReadChannel", mock.Anything, userID, previewChannel).Return(true, true)
|
|
mockSuite.On("MakeAuditRecord", mock.Anything, mock.Anything, mock.Anything).Return(&model.AuditRecord{})
|
|
mockSuite.On("LogAuditRec", mock.Anything, mock.Anything, mock.Anything).Return()
|
|
wc := &platform.WebConn{
|
|
UserId: userID,
|
|
Platform: &platform.PlatformService{},
|
|
Suite: mockSuite,
|
|
}
|
|
// Do NOT call wc.SetSession — session remains nil
|
|
|
|
fileID := model.NewId()
|
|
previewPost := makeRefPost(
|
|
model.StringArray{fileID},
|
|
[]*model.FileInfo{{Id: fileID, Name: "doc.pdf", Extension: "pdf"}},
|
|
)
|
|
|
|
err := hook.Process(msg, wc, map[string]any{
|
|
"preview_channel": previewChannel,
|
|
"permalink_previewed_post": previewPost,
|
|
"preview_prop": previewPost.PostID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
gotPost := extractPost(t, msg)
|
|
require.Len(t, gotPost.Metadata.Embeds, 1)
|
|
|
|
gotJSON, _ := msg.Get("post").(string)
|
|
assert.NotContains(t, gotJSON, fileID, "file ID should be stripped when session is nil")
|
|
assert.Contains(t, gotJSON, `"redacted_file_count":1`, "redacted count should be set when session is nil")
|
|
})
|
|
}
|
|
|
|
func TestAbacFilesBroadcastHook_Process(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
hook := &abacFilesBroadcastHook{}
|
|
|
|
userID := model.NewId()
|
|
channelID := model.NewId()
|
|
|
|
makePostWithFiles := func(t *testing.T, fileCount int) (*model.Post, string) {
|
|
t.Helper()
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: channelID,
|
|
FileIds: make(model.StringArray, fileCount),
|
|
Metadata: &model.PostMetadata{
|
|
Files: make([]*model.FileInfo, fileCount),
|
|
},
|
|
}
|
|
for i := range fileCount {
|
|
fid := model.NewId()
|
|
post.FileIds[i] = fid
|
|
post.Metadata.Files[i] = &model.FileInfo{Id: fid, Name: "file.txt", Extension: "txt"}
|
|
}
|
|
postJSON, err := post.ToJSON()
|
|
require.NoError(t, err)
|
|
return post, postJSON
|
|
}
|
|
|
|
makeMessage := func(channelID, postJSON string) *platform.HookedWebSocketEvent {
|
|
event := model.NewWebSocketEvent(model.WebsocketEventPosted, "", channelID, "", nil, "")
|
|
event.Add("post", postJSON)
|
|
return platform.MakeHookedWebSocketEvent(event)
|
|
}
|
|
|
|
makeArgs := func(channelID string, fileCount int) map[string]any {
|
|
return map[string]any{
|
|
"channel_id": channelID,
|
|
"file_count": fileCount,
|
|
}
|
|
}
|
|
|
|
makeWebConn := func(t *testing.T, userID string, allowed bool) *platform.WebConn {
|
|
t.Helper()
|
|
mockSuite := &platform_mocks.SuiteIFace{}
|
|
mockSuite.On("HasPermissionToFileAction", mock.Anything, userID, mock.AnythingOfType("string"), channelID, model.AccessControlPolicyActionDownloadFileAttachment).Return(allowed)
|
|
wc := &platform.WebConn{
|
|
UserId: userID,
|
|
Platform: &platform.PlatformService{},
|
|
Suite: mockSuite,
|
|
}
|
|
session := &model.Session{UserId: userID, Roles: model.SystemUserRoleId}
|
|
wc.SetSession(session)
|
|
return wc
|
|
}
|
|
|
|
extractPost := func(t *testing.T, msg *platform.HookedWebSocketEvent) *model.Post {
|
|
t.Helper()
|
|
gotJSON, ok := msg.Get("post").(string)
|
|
require.True(t, ok)
|
|
var gotPost model.Post
|
|
err := json.Unmarshal([]byte(gotJSON), &gotPost)
|
|
require.NoError(t, err)
|
|
return &gotPost
|
|
}
|
|
|
|
t.Run("allowed user: post is unchanged", func(t *testing.T) {
|
|
_, postJSON := makePostWithFiles(t, 2)
|
|
msg := makeMessage(channelID, postJSON)
|
|
wc := makeWebConn(t, userID, true)
|
|
|
|
err := hook.Process(msg, wc, makeArgs(channelID, 2))
|
|
require.NoError(t, err)
|
|
|
|
gotPost := extractPost(t, msg)
|
|
assert.Len(t, gotPost.Metadata.Files, 2)
|
|
assert.Len(t, gotPost.FileIds, 2)
|
|
assert.Equal(t, 0, gotPost.Metadata.RedactedFileCount)
|
|
})
|
|
|
|
t.Run("denied user: files stripped, RedactedFileCount set", func(t *testing.T) {
|
|
_, postJSON := makePostWithFiles(t, 2)
|
|
msg := makeMessage(channelID, postJSON)
|
|
wc := makeWebConn(t, userID, false)
|
|
|
|
err := hook.Process(msg, wc, makeArgs(channelID, 2))
|
|
require.NoError(t, err)
|
|
|
|
gotPost := extractPost(t, msg)
|
|
assert.Nil(t, gotPost.Metadata.Files)
|
|
assert.Empty(t, gotPost.FileIds)
|
|
assert.Equal(t, 2, gotPost.Metadata.RedactedFileCount)
|
|
})
|
|
|
|
t.Run("denied user: count from Metadata.Files when FileIds empty", func(t *testing.T) {
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: channelID,
|
|
FileIds: model.StringArray{},
|
|
Metadata: &model.PostMetadata{
|
|
Files: []*model.FileInfo{
|
|
{Id: model.NewId(), Name: "a.txt", Extension: "txt"},
|
|
{Id: model.NewId(), Name: "b.txt", Extension: "txt"},
|
|
{Id: model.NewId(), Name: "c.txt", Extension: "txt"},
|
|
},
|
|
},
|
|
}
|
|
postJSON, err := post.ToJSON()
|
|
require.NoError(t, err)
|
|
|
|
msg := makeMessage(channelID, postJSON)
|
|
wc := makeWebConn(t, userID, false)
|
|
|
|
err = hook.Process(msg, wc, makeArgs(channelID, 3))
|
|
require.NoError(t, err)
|
|
|
|
gotPost := extractPost(t, msg)
|
|
assert.Equal(t, 3, gotPost.Metadata.RedactedFileCount)
|
|
assert.Nil(t, gotPost.Metadata.Files)
|
|
})
|
|
|
|
t.Run("denied user: fallback to registered fileCount arg when post has no files", func(t *testing.T) {
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: channelID,
|
|
FileIds: model.StringArray{},
|
|
Metadata: &model.PostMetadata{},
|
|
}
|
|
postJSON, err := post.ToJSON()
|
|
require.NoError(t, err)
|
|
|
|
msg := makeMessage(channelID, postJSON)
|
|
wc := makeWebConn(t, userID, false)
|
|
|
|
err = hook.Process(msg, wc, makeArgs(channelID, 2))
|
|
require.NoError(t, err)
|
|
|
|
gotPost := extractPost(t, msg)
|
|
assert.Equal(t, 2, gotPost.Metadata.RedactedFileCount)
|
|
assert.Nil(t, gotPost.Metadata.Files)
|
|
assert.Empty(t, gotPost.FileIds)
|
|
})
|
|
|
|
t.Run("nil session: fail-secure, files stripped", func(t *testing.T) {
|
|
_, postJSON := makePostWithFiles(t, 2)
|
|
msg := makeMessage(channelID, postJSON)
|
|
|
|
// WebConn with no session set — cannot evaluate permissions, must strip files
|
|
wc := &platform.WebConn{
|
|
UserId: userID,
|
|
Platform: &platform.PlatformService{},
|
|
}
|
|
// Do NOT call wc.SetSession — session remains nil
|
|
|
|
err := hook.Process(msg, wc, makeArgs(channelID, 2))
|
|
require.NoError(t, err)
|
|
|
|
gotPost := extractPost(t, msg)
|
|
assert.Empty(t, gotPost.Metadata.Files)
|
|
assert.Empty(t, gotPost.FileIds)
|
|
assert.Equal(t, 2, gotPost.Metadata.RedactedFileCount)
|
|
})
|
|
|
|
t.Run("missing channel_id arg: returns error", func(t *testing.T) {
|
|
_, postJSON := makePostWithFiles(t, 1)
|
|
msg := makeMessage(channelID, postJSON)
|
|
wc := makeWebConn(t, userID, true)
|
|
|
|
err := hook.Process(msg, wc, map[string]any{
|
|
"file_count": 1,
|
|
})
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "channel_id")
|
|
})
|
|
|
|
t.Run("missing file_count arg: returns error", func(t *testing.T) {
|
|
_, postJSON := makePostWithFiles(t, 1)
|
|
msg := makeMessage(channelID, postJSON)
|
|
wc := makeWebConn(t, userID, true)
|
|
|
|
err := hook.Process(msg, wc, map[string]any{
|
|
"channel_id": channelID,
|
|
})
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "file_count")
|
|
})
|
|
|
|
t.Run("denied user: Metadata.Images is preserved", func(t *testing.T) {
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: channelID,
|
|
FileIds: model.StringArray{model.NewId()},
|
|
Metadata: &model.PostMetadata{
|
|
Files: []*model.FileInfo{
|
|
{Id: model.NewId(), Name: "photo.png", Extension: "png"},
|
|
},
|
|
Images: map[string]*model.PostImage{
|
|
"https://example.com/img.png": {Width: 100, Height: 200},
|
|
},
|
|
},
|
|
}
|
|
postJSON, err := post.ToJSON()
|
|
require.NoError(t, err)
|
|
|
|
msg := makeMessage(channelID, postJSON)
|
|
wc := makeWebConn(t, userID, false)
|
|
|
|
err = hook.Process(msg, wc, makeArgs(channelID, 1))
|
|
require.NoError(t, err)
|
|
|
|
gotPost := extractPost(t, msg)
|
|
assert.Nil(t, gotPost.Metadata.Files)
|
|
assert.Equal(t, 1, gotPost.Metadata.RedactedFileCount)
|
|
assert.NotNil(t, gotPost.Metadata.Images, "Images should be preserved when files are stripped")
|
|
assert.Contains(t, gotPost.Metadata.Images, "https://example.com/img.png")
|
|
})
|
|
}
|
|
|
|
func TestSetupBroadcastHookForAbacFiles(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
t.Run("when ABAC is disabled: hook NOT registered", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = new(false)
|
|
})
|
|
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: th.BasicChannel.Id,
|
|
FileIds: model.StringArray{model.NewId()},
|
|
Metadata: &model.PostMetadata{
|
|
Files: []*model.FileInfo{{Id: model.NewId(), Name: "file.txt", Extension: "txt"}},
|
|
},
|
|
}
|
|
message := model.NewWebSocketEvent(model.WebsocketEventPosted, "", post.ChannelId, "", nil, "")
|
|
|
|
th.App.setupBroadcastHookForAbacFiles(post, message)
|
|
|
|
hooks := message.GetBroadcast().BroadcastHooks
|
|
assert.Empty(t, hooks, "No hooks should be registered when ABAC is disabled")
|
|
})
|
|
|
|
t.Run("when post has no files: hook NOT registered", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = new(true)
|
|
})
|
|
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: th.BasicChannel.Id,
|
|
FileIds: model.StringArray{},
|
|
Metadata: &model.PostMetadata{},
|
|
}
|
|
message := model.NewWebSocketEvent(model.WebsocketEventPosted, "", post.ChannelId, "", nil, "")
|
|
|
|
th.App.setupBroadcastHookForAbacFiles(post, message)
|
|
|
|
hooks := message.GetBroadcast().BroadcastHooks
|
|
assert.Empty(t, hooks, "No hooks should be registered when post has no files")
|
|
})
|
|
|
|
t.Run("when post is burn-on-read: hook NOT registered", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = new(true)
|
|
})
|
|
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: th.BasicChannel.Id,
|
|
Type: model.PostTypeBurnOnRead,
|
|
FileIds: model.StringArray{model.NewId()},
|
|
Metadata: &model.PostMetadata{
|
|
Files: []*model.FileInfo{{Id: model.NewId(), Name: "file.txt", Extension: "txt"}},
|
|
},
|
|
}
|
|
message := model.NewWebSocketEvent(model.WebsocketEventPosted, "", post.ChannelId, "", nil, "")
|
|
|
|
th.App.setupBroadcastHookForAbacFiles(post, message)
|
|
|
|
hooks := message.GetBroadcast().BroadcastHooks
|
|
assert.Empty(t, hooks, "No hooks should be registered for burn-on-read posts")
|
|
})
|
|
|
|
t.Run("when AccessControl is nil: hook NOT registered", func(t *testing.T) {
|
|
// In test setup without enterprise, AccessControl is nil.
|
|
// Even with ABAC config enabled, the nil check on AccessControl
|
|
// prevents registration.
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = new(true)
|
|
})
|
|
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: th.BasicChannel.Id,
|
|
FileIds: model.StringArray{model.NewId()},
|
|
Metadata: &model.PostMetadata{
|
|
Files: []*model.FileInfo{{Id: model.NewId(), Name: "file.txt", Extension: "txt"}},
|
|
},
|
|
}
|
|
message := model.NewWebSocketEvent(model.WebsocketEventPosted, "", post.ChannelId, "", nil, "")
|
|
|
|
th.App.setupBroadcastHookForAbacFiles(post, message)
|
|
|
|
// AccessControl is nil in test env (no enterprise), so hook should not register
|
|
hooks := message.GetBroadcast().BroadcastHooks
|
|
assert.Empty(t, hooks, "No hooks should be registered when AccessControl is nil")
|
|
})
|
|
|
|
t.Run("when post has Metadata.Files but empty FileIds: hook IS registered (when AccessControl available)", func(t *testing.T) {
|
|
// This test verifies the logic path: if AccessControl were non-nil and ABAC enabled,
|
|
// a post with only Metadata.Files (no FileIds) would still trigger hook registration.
|
|
// Since AccessControl is nil in test env, we verify the fileCount logic directly
|
|
// by checking that the function does not register when AccessControl is nil, but the
|
|
// fileCount derivation is tested through the Process tests above.
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = new(true)
|
|
})
|
|
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: th.BasicChannel.Id,
|
|
FileIds: model.StringArray{},
|
|
Metadata: &model.PostMetadata{
|
|
Files: []*model.FileInfo{
|
|
{Id: model.NewId(), Name: "file.txt", Extension: "txt"},
|
|
},
|
|
},
|
|
}
|
|
message := model.NewWebSocketEvent(model.WebsocketEventPosted, "", post.ChannelId, "", nil, "")
|
|
|
|
th.App.setupBroadcastHookForAbacFiles(post, message)
|
|
|
|
// Without enterprise, AccessControl is nil so hook won't register.
|
|
// The file-count logic is verified via TestAbacFilesBroadcastHook_Process.
|
|
hooks := message.GetBroadcast().BroadcastHooks
|
|
assert.Empty(t, hooks, "AccessControl is nil in test env — hook not registered")
|
|
})
|
|
}
|