diff --git a/server/channels/api4/channel.go b/server/channels/api4/channel.go index ef6063dac9..16f91dc272 100644 --- a/server/channels/api4/channel.go +++ b/server/channels/api4/channel.go @@ -1846,6 +1846,13 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { return } + // Security check: if the user is a guest, they must have access to the channel + // to view its members + if c.AppContext.Session().IsGuest() && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) { + c.SetPermissionError(model.PermissionReadChannel) + return + } + if channel.Type == model.ChannelTypeDirect || channel.Type == model.ChannelTypeGroup { c.Err = model.NewAppError("addUserToChannel", "api.channel.add_user_to_channel.type.app_error", nil, "", http.StatusBadRequest) return diff --git a/server/channels/api4/channel_test.go b/server/channels/api4/channel_test.go index a60a6fb195..302b6cd79f 100644 --- a/server/channels/api4/channel_test.go +++ b/server/channels/api4/channel_test.go @@ -4091,6 +4091,43 @@ func TestAddChannelMemberFromThread(t *testing.T) { require.Truef(t, caught, "User should have received %s event", model.WebsocketEventThreadUpdated) } +func TestAddChannelMemberGuestAccessControl(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + // Enable guest accounts and add license + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.GuestAccountsSettings.Enable = true + }) + th.App.Srv().SetLicense(model.NewTestLicense()) + + // Create a guest user + guest, guestClient := th.CreateGuestAndClient() + + // Create a public channel to which the guest doesn't belong + publicChannel := th.CreatePublicChannel() + + // Try to add another user to the channel using the guest's client + // This should fail with a permission error, validating our fix + _, resp, err := guestClient.AddChannelMember(context.Background(), publicChannel.Id, th.BasicUser2.Id) + require.Error(t, err) + CheckForbiddenStatus(t, resp) + + // Also verify that using user IDs in the request body doesn't bypass the check + _, resp, err = guestClient.AddChannelMembers(context.Background(), publicChannel.Id, "", []string{th.BasicUser2.Id}) + require.Error(t, err) + CheckForbiddenStatus(t, resp) + + // Verify that the guest can get channel members for channels they belong to + channelWithGuest := th.CreatePublicChannel() + th.AddUserToChannel(guest, channelWithGuest) + + // Guest should be able to read members of channels they belong to + members, _, err := guestClient.GetChannelMembers(context.Background(), channelWithGuest.Id, 0, 100, "") + require.NoError(t, err) + require.NotEmpty(t, members) +} + func TestAddChannelMemberAddMyself(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() diff --git a/server/public/model/session.go b/server/public/model/session.go index 351a60a668..fa06725300 100644 --- a/server/public/model/session.go +++ b/server/public/model/session.go @@ -254,6 +254,14 @@ func (s *Session) IsSSOLogin() bool { return s.IsOAuthUser() || s.IsSaml() } +func (s *Session) IsGuest() bool { + val, ok := s.Props[SessionPropIsGuest] + if !ok { + return false + } + return val == "true" +} + func (s *Session) GetUserRoles() []string { return strings.Fields(s.Roles) }