From 46624d1f47d9b70c6db940c5a4e7b3066edca895 Mon Sep 17 00:00:00 2001 From: Nick Misasi Date: Fri, 24 Apr 2026 13:48:07 -0400 Subject: [PATCH 1/2] [MM-68231] Tighten post info authorization (#36111) * [MM-68231] Tighten post info authorization Align post info channel access with the standard read path while preserving expected public-channel discoverability. Add regression coverage for guest and compliance-mode access checks. Made-with: Cursor * [MM-68231] Strengthen post info test coverage Tighten the new post info regression coverage so the guest denial case proves its setup and the compliance case asserts the expected non-compliance behavior first. Made-with: Cursor * [MM-68231] Expand post info authorization coverage Add focused regression coverage for invite-team access, compliance behavior on open teams, private-channel permission boundaries, and outsider denial for DM and GM post info. Made-with: Cursor --- server/channels/api4/post.go | 22 +---- server/channels/api4/post_test.go | 159 +++++++++++++++++++++++++++++- 2 files changed, 163 insertions(+), 18 deletions(-) diff --git a/server/channels/api4/post.go b/server/channels/api4/post.go index c4c0cb83571..dd1961ff47c 100644 --- a/server/channels/api4/post.go +++ b/server/channels/api4/post.go @@ -1652,24 +1652,12 @@ func getPostInfo(c *Context, w http.ResponseWriter, r *http.Request) { return } - hasPermissionToAccessChannel := false - hasJoinedChannel := false + hasPermissionToAccessChannel, hasJoinedChannel := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) - _, channelMemberErr := c.App.GetChannelMember(c.AppContext, channel.Id, userID) - - if channelMemberErr == nil { - hasPermissionToAccessChannel = true - hasJoinedChannel = true - } - - if !hasPermissionToAccessChannel { - if channel.Type == model.ChannelTypeOpen { - hasPermissionToAccessChannel = true - } else if channel.Type == model.ChannelTypePrivate { - hasPermissionToAccessChannel, _ = c.App.HasPermissionToChannel(c.AppContext, userID, channel.Id, model.PermissionManagePrivateChannelMembers) - } else if channel.Type == model.ChannelTypeDirect || channel.Type == model.ChannelTypeGroup { - hasPermissionToAccessChannel, _ = c.App.HasPermissionToReadChannel(c.AppContext, userID, channel) - } + if !hasPermissionToAccessChannel && channel.Type == model.ChannelTypeOpen && !*c.App.Config().ComplianceSettings.Enable { + canJoinOpenChannel := c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionJoinPublicChannels) + canJoinOpenTeam := team != nil && team.AllowOpenInvite && c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionJoinPublicTeams) + hasPermissionToAccessChannel = canJoinOpenChannel || canJoinOpenTeam } if !hasPermissionToAccessChannel { diff --git a/server/channels/api4/post_test.go b/server/channels/api4/post_test.go index 77e77931d5b..b0ec3922fdd 100644 --- a/server/channels/api4/post_test.go +++ b/server/channels/api4/post_test.go @@ -5431,6 +5431,8 @@ func TestPostGetInfo(t *testing.T) { mainHelper.Parallel(t) th := Setup(t).InitBasic(t) + th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise)) + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.Enable = true }) defaultPerms := th.SaveDefaultRolePermissions(t) defer th.RestoreDefaultRolePermissions(t, defaultPerms) @@ -5439,8 +5441,16 @@ func TestPostGetInfo(t *testing.T) { th.RemovePermissionFromRole(t, model.PermissionManagePrivateChannelMembers.Id, model.TeamUserRoleId) client := th.Client + guestUser, guestClient := th.CreateGuestAndClient(t) + otherTeamMemberClient := th.CreateClient() + _, _, err := otherTeamMemberClient.Login(context.Background(), th.BasicUser2.Username, th.BasicUser2.Password) + require.NoError(t, err) + outsiderUser := th.CreateUser(t) + outsiderClient := th.CreateClient() + _, _, err = outsiderClient.Login(context.Background(), outsiderUser.Username, outsiderUser.Password) + require.NoError(t, err) sysadminClient := th.SystemAdminClient - _, _, err := sysadminClient.AddTeamMember(context.Background(), th.BasicTeam.Id, th.SystemAdminUser.Id) + _, _, err = sysadminClient.AddTeamMember(context.Background(), th.BasicTeam.Id, th.SystemAdminUser.Id) require.NoError(t, err) openChannel, _, err := client.CreateChannel(context.Background(), &model.Channel{TeamId: th.BasicTeam.Id, Type: model.ChannelTypeOpen, Name: "open-channel", DisplayName: "Open Channel"}) @@ -5501,6 +5511,7 @@ func TestPostGetInfo(t *testing.T) { hasJoinedChannel bool post *model.Post client *model.Client4 + assertSetup func(t *testing.T) hasAccess bool }{ // Open channel - Current Team @@ -5524,6 +5535,25 @@ func TestPostGetInfo(t *testing.T) { client: sysadminClient, hasAccess: true, }, + { + name: "Open post - Current team - Guest user outside channel", + team: th.BasicTeam, + hasJoinedTeam: true, + channel: openChannel, + hasJoinedChannel: false, + post: openPost, + client: guestClient, + assertSetup: func(t *testing.T) { + t.Helper() + + _, appErr := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, guestUser.Id) + require.Nil(t, appErr) + + _, appErr = th.App.GetChannelMember(th.Context, openChannel.Id, guestUser.Id) + require.NotNil(t, appErr) + }, + hasAccess: false, + }, // Private channel - Current Team { @@ -5656,6 +5686,38 @@ func TestPostGetInfo(t *testing.T) { client: client, hasAccess: false, }, + { + name: "Open post - Invite team - Basic user with join private teams permission", + team: inviteTeam, + hasJoinedTeam: false, + channel: inviteTeamOpenChannel, + hasJoinedChannel: false, + post: inviteTeamOpenPost, + client: client, + assertSetup: func(t *testing.T) { + t.Helper() + + _, appErr := th.App.GetTeamMember(th.Context, inviteTeam.Id, th.BasicUser.Id) + require.NotNil(t, appErr) + + _, appErr = th.App.GetChannelMember(th.Context, inviteTeamOpenChannel.Id, th.BasicUser.Id) + require.NotNil(t, appErr) + + require.False(t, th.App.HasPermissionToTeam(th.Context, th.BasicUser.Id, inviteTeam.Id, model.PermissionJoinPrivateTeams)) + require.False(t, th.App.HasPermissionToTeam(th.Context, th.BasicUser.Id, inviteTeam.Id, model.PermissionJoinPublicChannels)) + + th.AddPermissionToRole(t, model.PermissionJoinPrivateTeams.Id, model.SystemUserRoleId) + th.AddPermissionToRole(t, model.PermissionJoinPublicChannels.Id, model.SystemUserRoleId) + t.Cleanup(func() { + th.RemovePermissionFromRole(t, model.PermissionJoinPublicChannels.Id, model.SystemUserRoleId) + th.RemovePermissionFromRole(t, model.PermissionJoinPrivateTeams.Id, model.SystemUserRoleId) + }) + + require.True(t, th.App.HasPermissionToTeam(th.Context, th.BasicUser.Id, inviteTeam.Id, model.PermissionJoinPrivateTeams)) + require.True(t, th.App.HasPermissionToTeam(th.Context, th.BasicUser.Id, inviteTeam.Id, model.PermissionJoinPublicChannels)) + }, + hasAccess: true, + }, { name: "Open post - Invite team - Sysadmin user", team: inviteTeam, @@ -5670,6 +5732,10 @@ func TestPostGetInfo(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + if tc.assertSetup != nil { + tc.assertSetup(t) + } + info, resp, err := tc.client.GetPostInfo(context.Background(), tc.post.Id) if !tc.hasAccess { require.Error(t, err) @@ -5695,6 +5761,97 @@ func TestPostGetInfo(t *testing.T) { } }) } + + t.Run("Open post - Current team - Non-member denied when compliance is enabled", func(t *testing.T) { + info, resp, err := otherTeamMemberClient.GetPostInfo(context.Background(), openPost.Id) + require.NoError(t, err) + CheckOKStatus(t, resp) + require.Equal(t, openChannel.Id, info.ChannelId) + require.True(t, info.HasJoinedTeam) + require.False(t, info.HasJoinedChannel) + + originalComplianceEnabled := *th.App.Config().ComplianceSettings.Enable + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.ComplianceSettings.Enable = true + }) + t.Cleanup(func() { + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.ComplianceSettings.Enable = originalComplianceEnabled + }) + }) + + _, resp, err = otherTeamMemberClient.GetPostInfo(context.Background(), openPost.Id) + require.Error(t, err) + CheckNotFoundStatus(t, resp) + }) + + t.Run("Open post - Open team - Non-member denied when compliance is enabled", func(t *testing.T) { + _, appErr := th.App.GetTeamMember(th.Context, openTeam.Id, th.BasicUser.Id) + require.NotNil(t, appErr) + + _, appErr = th.App.GetChannelMember(th.Context, openTeamOpenChannel.Id, th.BasicUser.Id) + require.NotNil(t, appErr) + + info, resp, err := client.GetPostInfo(context.Background(), openTeamOpenPost.Id) + require.NoError(t, err) + CheckOKStatus(t, resp) + require.Equal(t, openTeamOpenChannel.Id, info.ChannelId) + require.False(t, info.HasJoinedTeam) + require.False(t, info.HasJoinedChannel) + + originalComplianceEnabled := *th.App.Config().ComplianceSettings.Enable + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.ComplianceSettings.Enable = true + }) + t.Cleanup(func() { + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.ComplianceSettings.Enable = originalComplianceEnabled + }) + }) + + _, resp, err = client.GetPostInfo(context.Background(), openTeamOpenPost.Id) + require.Error(t, err) + CheckNotFoundStatus(t, resp) + }) + + t.Run("Private post - Same-team non-member with manage members permission is denied", func(t *testing.T) { + _, appErr := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, th.BasicUser2.Id) + require.Nil(t, appErr) + + _, appErr = th.App.GetChannelMember(th.Context, privateChannelBasicUser.Id, th.BasicUser2.Id) + require.NotNil(t, appErr) + + th.AddPermissionToRole(t, model.PermissionManagePrivateChannelMembers.Id, model.TeamUserRoleId) + t.Cleanup(func() { + th.RemovePermissionFromRole(t, model.PermissionManagePrivateChannelMembers.Id, model.TeamUserRoleId) + }) + + hasPermission, isMember := th.App.HasPermissionToChannel(th.Context, th.BasicUser2.Id, privateChannelBasicUser.Id, model.PermissionManagePrivateChannelMembers) + require.True(t, hasPermission) + require.False(t, isMember) + + _, resp, err := otherTeamMemberClient.GetPostInfo(context.Background(), privatePostBasicUser.Id) + require.Error(t, err) + CheckNotFoundStatus(t, resp) + }) + + t.Run("DM post - Outsider is denied", func(t *testing.T) { + _, appErr := th.App.GetChannelMember(th.Context, dmChannel.Id, outsiderUser.Id) + require.NotNil(t, appErr) + + _, resp, err := outsiderClient.GetPostInfo(context.Background(), dmPost.Id) + require.Error(t, err) + CheckNotFoundStatus(t, resp) + }) + + t.Run("GM post - Outsider is denied", func(t *testing.T) { + _, appErr := th.App.GetChannelMember(th.Context, gmChannel.Id, outsiderUser.Id) + require.NotNil(t, appErr) + + _, resp, err := outsiderClient.GetPostInfo(context.Background(), gmPost.Id) + require.Error(t, err) + CheckNotFoundStatus(t, resp) + }) } func TestAcknowledgePost(t *testing.T) { From 1ed4d0215a01bcd6520bc1e481918c1fdc3cc81d Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Fri, 24 Apr 2026 15:14:40 -0300 Subject: [PATCH 2/2] Fix FIPS test failures by using model.NewTestPassword() for short passwords (#36262) --- server/channels/api4/user_test.go | 11 ++++++----- server/channels/app/shared_channel_test.go | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/server/channels/api4/user_test.go b/server/channels/api4/user_test.go index 592d2972b6d..64d88c7dba8 100644 --- a/server/channels/api4/user_test.go +++ b/server/channels/api4/user_test.go @@ -3973,6 +3973,7 @@ func TestResetPassword(t *testing.T) { return tokenErr == nil }, 10*time.Second, 500*time.Millisecond, "Recovery token not found (%s)", recoveryTokenString) + newPwd := model.NewTestPassword() resp, err := th.Client.ResetPassword(context.Background(), recoveryToken.Token, "") require.Error(t, err) CheckBadRequestStatus(t, resp) @@ -3989,16 +3990,16 @@ func TestResetPassword(t *testing.T) { for range model.TokenSize { code.WriteString("a") } - resp, err = th.Client.ResetPassword(context.Background(), code.String(), "newpasswd") + resp, err = th.Client.ResetPassword(context.Background(), code.String(), newPwd) require.Error(t, err) CheckBadRequestStatus(t, resp) - _, err = th.Client.ResetPassword(context.Background(), recoveryToken.Token, "newpasswd") + _, err = th.Client.ResetPassword(context.Background(), recoveryToken.Token, newPwd) require.NoError(t, err) - _, _, err = th.Client.Login(context.Background(), user.Email, "newpasswd") + _, _, err = th.Client.Login(context.Background(), user.Email, newPwd) require.NoError(t, err) _, err = th.Client.Logout(context.Background()) require.NoError(t, err) - resp, err = th.Client.ResetPassword(context.Background(), recoveryToken.Token, "newpasswd") + resp, err = th.Client.ResetPassword(context.Background(), recoveryToken.Token, newPwd) require.Error(t, err) CheckBadRequestStatus(t, resp) authData := model.NewId() @@ -4028,7 +4029,7 @@ func TestResetPasswordAuditDoesNotLeakToken(t *testing.T) { _ = th.App.Srv().Store().Token().Delete(token.Token) }() - _, err = th.Client.ResetPassword(context.Background(), token.Token, "newPassword1!") + _, err = th.Client.ResetPassword(context.Background(), token.Token, model.NewTestPassword()) require.NoError(t, err) audits, appErr := th.App.GetAudits(request.EmptyContext(th.TestLogger), "", 100) diff --git a/server/channels/app/shared_channel_test.go b/server/channels/app/shared_channel_test.go index 9d2b302907f..591bfd0f642 100644 --- a/server/channels/app/shared_channel_test.go +++ b/server/channels/app/shared_channel_test.go @@ -1334,7 +1334,7 @@ func TestPluginAPIReceiveSharedChannelAttachmentSyncMsg(t *testing.T) { remoteUser := &model.User{ Email: model.NewId() + "@remote.test", Username: "remote-attach-" + model.NewId()[:8], - Password: "Password1!", + Password: model.NewTestPassword(), RemoteId: model.NewPointer(rc.RemoteId), } remoteUser, appErr := th.App.CreateUser(th.Context, remoteUser) @@ -1409,7 +1409,7 @@ func TestPluginAPIReceiveSharedChannelProfileImageSyncMsg(t *testing.T) { remoteUser := &model.User{ Email: model.NewId() + "@remote.test", Username: "remote-img-" + model.NewId()[:8], - Password: "Password1!", + Password: model.NewTestPassword(), RemoteId: model.NewPointer(rc.RemoteId), } remoteUser, appErr := th.App.CreateUser(th.Context, remoteUser)