// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. package app import ( "context" "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/v8/channels/store" ) //nolint:govet // The setup code leads to a lot of variable shadowing. func TestCreateDefaultMemberships(t *testing.T) { mainHelper.Parallel(t) th := Setup(t).InitBasic() defer th.TearDown() singersTeam, err := th.App.CreateTeam(th.Context, &model.Team{ DisplayName: "Singers", Name: "zz" + model.NewId(), Email: "singers@test.com", Type: model.TeamOpen, }) if err != nil { t.Errorf("test team not created: %s", err.Error()) } nerdsTeam, err := th.App.CreateTeam(th.Context, &model.Team{ DisplayName: "Nerds", Name: "zz" + model.NewId(), Email: "nerds@test.com", Type: model.TeamInvite, }) if err != nil { t.Errorf("test team not created: %s", err.Error()) } practiceChannel, err := th.App.CreateChannel(th.Context, &model.Channel{ TeamId: singersTeam.Id, DisplayName: "Practices", Name: model.NewId(), Type: model.ChannelTypeOpen, }, false) if err != nil { t.Errorf("test channel not created: %s", err.Error()) } experimentsChannel, err := th.App.CreateChannel(th.Context, &model.Channel{ TeamId: singersTeam.Id, DisplayName: "Experiments", Name: model.NewId(), Type: model.ChannelTypePrivate, }, false) if err != nil { t.Errorf("test channel not created: %s", err.Error()) } gleeGroup, err := th.App.CreateGroup(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "Glee Club", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, }) if err != nil { t.Errorf("test group not created: %s", err.Error()) } scienceGroup, err := th.App.CreateGroup(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "Science Club", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, }) if err != nil { t.Errorf("test group not created: %s", err.Error()) } _, err = th.App.UpsertGroupSyncable(model.NewGroupChannel(gleeGroup.Id, practiceChannel.Id, true)) if err != nil { t.Errorf("test groupchannel not created: %s", err.Error()) } scienceTeamGroupSyncable, err := th.App.UpsertGroupSyncable(model.NewGroupTeam(scienceGroup.Id, nerdsTeam.Id, false)) if err != nil { t.Errorf("test groupteam not created: %s", err.Error()) } scienceChannelGroupSyncable, err := th.App.UpsertGroupSyncable(model.NewGroupChannel(scienceGroup.Id, experimentsChannel.Id, false)) if err != nil { t.Errorf("test groupchannel not created: %s", err.Error()) } singer1 := th.BasicUser scientist1 := th.BasicUser2 _, err = th.App.UpsertGroupMember(gleeGroup.Id, singer1.Id) if err != nil { t.Errorf("test groupmember not created: %s", err.Error()) } scientistGroupMember, err := th.App.UpsertGroupMember(scienceGroup.Id, scientist1.Id) if err != nil { t.Errorf("test groupmember not created: %s", err.Error()) } pErr := th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: 0, ReAddRemovedMembers: false}) if pErr != nil { t.Errorf("failed to populate syncables: %s", pErr.Error()) } // Singer should be in team and channel _, err = th.App.GetTeamMember(th.Context, singersTeam.Id, singer1.Id) if err != nil { t.Errorf("error retrieving team member: %s", err.Error()) } _, err = th.App.GetChannelMember(th.Context, practiceChannel.Id, singer1.Id) if err != nil { t.Errorf("error retrieving channel member: %s", err.Error()) } tMembers, err := th.App.GetTeamMembers(singersTeam.Id, 0, 999, nil) if err != nil { t.Errorf("error retrieving team members: %s", err.Error()) } expected := 1 actual := len(tMembers) if actual != expected { t.Errorf("expected %d team members but got %d", expected, actual) } cMembersCount, err := th.App.GetChannelMemberCount(th.Context, practiceChannel.Id) if err != nil { t.Errorf("error retrieving team members: %s", err.Error()) } if cMembersCount != int64(expected) { t.Errorf("expected %d team member but got %d", expected, cMembersCount) } // Scientist should not be in team or channel _, err = th.App.GetTeamMember(th.Context, nerdsTeam.Id, scientist1.Id) if err.Id != "app.team.get_member.missing.app_error" { t.Errorf("wrong error: %s", err.Id) } _, err = th.App.GetChannelMember(th.Context, experimentsChannel.Id, scientist1.Id) if err.Id != "app.channel.get_member.missing.app_error" { t.Errorf("wrong error: %s", err.Id) } tMembers, err = th.App.GetTeamMembers(nerdsTeam.Id, 0, 999, nil) if err != nil { t.Errorf("error retrieving team members: %s", err.Error()) } expected = 0 actual = len(tMembers) if actual != expected { t.Errorf("expected %d team members but got %d", expected, actual) } cMembersCount, err = th.App.GetChannelMemberCount(th.Context, experimentsChannel.Id) if err != nil { t.Errorf("error retrieving team members: %s", err.Error()) } if cMembersCount != int64(expected) { t.Errorf("expected %d team members but got %d", expected, cMembersCount) } // update AutoAdd to true scienceTeamGroupSyncable.AutoAdd = true _, err = th.App.UpdateGroupSyncable(scienceTeamGroupSyncable) if err != nil { t.Errorf("error updating group syncable: %s", err.Error()) } // Sync everything after syncable was created (proving that team updates trigger re-sync) pErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: scientistGroupMember.CreateAt + 1, ReAddRemovedMembers: false}) if pErr != nil { t.Errorf("failed to populate syncables: %s", pErr.Error()) } // Scientist should be in team but not the channel _, err = th.App.GetTeamMember(th.Context, nerdsTeam.Id, scientist1.Id) if err != nil { t.Errorf("error retrieving team member: %s", err.Error()) } _, err = th.App.GetChannelMember(th.Context, experimentsChannel.Id, scientist1.Id) if err.Id != "app.channel.get_member.missing.app_error" { t.Errorf("wrong error: %s", err.Id) } tMembers, err = th.App.GetTeamMembers(nerdsTeam.Id, 0, 999, nil) if err != nil { t.Errorf("error retrieving team members: %s", err.Error()) } expected = 1 actual = len(tMembers) if actual != expected { t.Errorf("expected %d team members but got %d", expected, actual) } expected = 0 cMembersCount, err = th.App.GetChannelMemberCount(th.Context, experimentsChannel.Id) if err != nil { t.Errorf("error retrieving team members: %s", err.Error()) } if cMembersCount != int64(expected) { t.Errorf("expected %d team members but got %d", expected, cMembersCount) } // Update the channel syncable scienceChannelGroupSyncable.AutoAdd = true scienceChannelGroupSyncable, err = th.App.UpdateGroupSyncable(scienceChannelGroupSyncable) if err != nil { t.Errorf("error updating group syncable: %s", err.Error()) } // Sync everything after syncable was created (proving that channel updates trigger re-sync) pErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: scientistGroupMember.CreateAt + 1, ReAddRemovedMembers: false}) if pErr != nil { t.Errorf("failed to populate syncables: %s", pErr.Error()) } expected = 1 cMembersCount, err = th.App.GetChannelMemberCount(th.Context, experimentsChannel.Id) if err != nil { t.Errorf("error retrieving team members: %s", err.Error()) } if cMembersCount != int64(expected) { t.Errorf("expected %d team members but got %d", expected, cMembersCount) } // singer leaves team and channel err = th.App.LeaveChannel(th.Context, practiceChannel.Id, singer1.Id) if err != nil { t.Errorf("error leaving channel: %s", err.Error()) } err = th.App.LeaveTeam(th.Context, singersTeam, singer1, "") if err != nil { t.Errorf("error leaving team: %s", err.Error()) } // Even re-syncing from the beginning doesn't re-add to channel or team pErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: 0, ReAddRemovedMembers: false}) if pErr != nil { t.Errorf("failed to populate syncables: %s", pErr.Error()) } // Singer should not be in team or channel tMember, err := th.App.GetTeamMember(th.Context, singersTeam.Id, singer1.Id) if err != nil { t.Errorf("error retrieving team member: %s", err.Error()) } if tMember.DeleteAt == 0 { t.Error("expected team member to remain deleted") } _, err = th.App.GetChannelMember(th.Context, practiceChannel.Id, singer1.Id) if err == nil { t.Error("Expected channel member to remain deleted") } // Ensure members are in channel _, err = th.App.AddChannelMember(th.Context, scientist1.Id, experimentsChannel, ChannelMemberOpts{}) if err != nil { t.Errorf("unable to add user to channel: %s", err.Error()) } // Add other user so that user can leave channel _, err = th.App.AddTeamMember(th.Context, singersTeam.Id, singer1.Id) if err != nil { t.Errorf("unable to add user to team: %s", err.Error()) } _, err = th.App.AddChannelMember(th.Context, singer1.Id, experimentsChannel, ChannelMemberOpts{}) if err != nil { t.Errorf("unable to add user to channel: %s", err.Error()) } // the channel syncable is updated scienceChannelGroupSyncable, err = th.App.UpdateGroupSyncable(scienceChannelGroupSyncable) if err != nil { t.Errorf("error updating group syncable: %s", err.Error()) } pErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: 0, ReAddRemovedMembers: false}) if pErr != nil { t.Errorf("failed to populate syncables: %s", pErr.Error()) } timeBeforeLeaving := model.GetMillis() // User leaves channel err = th.App.LeaveChannel(th.Context, experimentsChannel.Id, scientist1.Id) if err != nil { t.Errorf("unable to add user to channel: %s", err.Error()) } timeAfterLeaving := model.GetMillis() + 1 retentionPolicyBatchConfigs := model.RetentionPolicyBatchConfigs{ Now: 0, GlobalPolicyEndTime: timeBeforeLeaving, Limit: 1000, } // Purging channelmemberhistory doesn't re-add user to channel deletedCount, _, nErr := th.App.Srv().Store().ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies( retentionPolicyBatchConfigs, model.RetentionPolicyCursor{}) if nErr != nil { t.Errorf("error permanently deleting channelmemberhistory: %s", nErr.Error()) } require.Equal(t, int64(1), deletedCount) pErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: scienceChannelGroupSyncable.UpdateAt, ReAddRemovedMembers: false}) if pErr != nil { t.Errorf("failed to populate syncables: %s", pErr.Error()) } _, err = th.App.GetChannelMember(th.Context, experimentsChannel.Id, scientist1.Id) if err == nil { t.Error("Expected channel member to remain deleted") } retentionPolicyBatchConfigs = model.RetentionPolicyBatchConfigs{ Now: 0, GlobalPolicyEndTime: timeAfterLeaving, Limit: 1000, } // Purging channelmemberhistory doesn't re-add user to channel deletedCount, _, nErr = th.App.Srv().Store().ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies( retentionPolicyBatchConfigs, model.RetentionPolicyCursor{}) if nErr != nil { t.Errorf("error permanently deleting channelmemberhistory: %s", nErr.Error()) } require.Equal(t, int64(1), deletedCount) pErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: scienceChannelGroupSyncable.UpdateAt, ReAddRemovedMembers: false}) if pErr != nil { t.Errorf("failed to populate syncables: %s", pErr.Error()) } // Channel member is re-added. _, err = th.App.GetChannelMember(th.Context, experimentsChannel.Id, scientist1.Id) if err != nil { t.Errorf("expected channel member: %s", err.Error()) } t.Run("Team with restricted domains skips over members that do not match the allowed domains", func(t *testing.T) { restrictedUser := th.CreateUser() restrictedUser.Email = "restricted@mattermost.org" _, err = th.App.UpdateUser(th.Context, restrictedUser, false) require.Nil(t, err) _, err = th.App.UpsertGroupMember(scienceGroup.Id, restrictedUser.Id) require.Nil(t, err) restrictedTeam, err := th.App.CreateTeam(th.Context, &model.Team{ DisplayName: "Restricted", Name: "restricted" + model.NewId(), Email: "restricted@mattermost.org", AllowedDomains: "mattermost.org", Type: model.TeamOpen, }) require.Nil(t, err) _, err = th.App.UpsertGroupSyncable(model.NewGroupTeam(scienceGroup.Id, restrictedTeam.Id, true)) require.Nil(t, err) restrictedChannel, err := th.App.CreateChannel(th.Context, &model.Channel{ TeamId: restrictedTeam.Id, DisplayName: "Restricted", Name: "restricted" + model.NewId(), Type: model.ChannelTypeOpen, }, false) require.Nil(t, err) _, err = th.App.UpsertGroupSyncable(model.NewGroupChannel(scienceGroup.Id, restrictedChannel.Id, true)) require.Nil(t, err) pErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: 0, ReAddRemovedMembers: false}) require.NoError(t, pErr) // Ensure only the restricted user was added to both the team and channel cMembersCount, err = th.App.GetChannelMemberCount(th.Context, restrictedChannel.Id) require.Nil(t, err) require.Equal(t, cMembersCount, int64(1)) tmembers, err := th.App.GetTeamMembers(restrictedTeam.Id, 0, 100, nil) require.Nil(t, err) require.Len(t, tmembers, 1) require.Equal(t, tmembers[0].UserId, restrictedUser.Id) }) t.Run("scoped to a single user", func(t *testing.T) { team1, err := th.App.CreateTeam(th.Context, &model.Team{ DisplayName: "Team 1", Name: "zz" + model.NewId(), Email: "team1admin@test.com", Type: model.TeamOpen, }) if err != nil { t.Errorf("test team not created: %s", err.Error()) } team1Channel1, err := th.App.CreateChannel(th.Context, &model.Channel{ TeamId: team1.Id, DisplayName: "Team 1 Channel 1", Name: model.NewId(), Type: model.ChannelTypeOpen, }, false) if err != nil { t.Errorf("test channel not created: %s", err.Error()) } group1, err := th.App.CreateGroup(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "Group 1", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, }) if err != nil { t.Errorf("test group not created: %s", err.Error()) } _, err = th.App.UpsertGroupSyncable(model.NewGroupTeam(group1.Id, team1.Id, true)) if err != nil { t.Errorf("test groupchannel not created: %s", err.Error()) } _, err = th.App.UpsertGroupSyncable(model.NewGroupChannel(group1.Id, team1Channel1.Id, true)) if err != nil { t.Errorf("test groupchannel not created: %s", err.Error()) } user1 := th.BasicUser user2 := th.BasicUser2 _, err = th.App.UpsertGroupMember(group1.Id, user1.Id) if err != nil { t.Errorf("test groupmember not created: %s", err.Error()) } _, err = th.App.UpsertGroupMember(group1.Id, user2.Id) if err != nil { t.Errorf("test groupmember not created: %s", err.Error()) } params := model.CreateDefaultMembershipParams{Since: 0, ReAddRemovedMembers: false, ScopedUserID: &user1.Id} pErr = th.App.CreateDefaultMemberships(th.Context, params) if pErr != nil { t.Errorf("failed to populate syncables: %s", pErr.Error()) } // test that only user 1 is successfully added to the team and channel. team1Members, err := th.App.GetTeamMembers(team1.Id, 0, 100, nil) if err != nil { t.Errorf("failed to get team members: %s", err.Error()) } if len(team1Members) != 1 { t.Errorf("expected 1 team member on team1, got %d", len(team1Members)) } if team1Members[0].UserId != user1.Id { t.Errorf("expected user1 to be a team member on team1, got %s", team1Members[0].UserId) } team1Channel1Members, err := th.App.GetChannelMembersPage(th.Context, team1Channel1.Id, 0, 100) if err != nil { t.Errorf("failed to get channel members: %s", err.Error()) } if len(team1Channel1Members) != 1 { t.Errorf("expected 1 channel member on team1Channel1, got %d", len(team1Channel1Members)) } if team1Channel1Members[0].UserId != user1.Id { t.Errorf("expected user1 to be a channel member on team1Channel1, got %s", team1Channel1Members[0].UserId) } // unscoped should add user2 to the team and channel params = model.CreateDefaultMembershipParams{Since: 0, ReAddRemovedMembers: false} pErr = th.App.CreateDefaultMemberships(th.Context, params) if pErr != nil { t.Errorf("failed to populate syncables: %s", pErr.Error()) } team1Members, err = th.App.GetTeamMembers(team1.Id, 0, 100, nil) if err != nil { t.Errorf("failed to get team members: %s", err.Error()) } if len(team1Members) != 2 { t.Errorf("expected 2 team member on team1, got %d", len(team1Members)) } team1Channel1Members, err = th.App.GetChannelMembersPage(th.Context, team1Channel1.Id, 0, 100) if err != nil { t.Errorf("failed to get channel members: %s", err.Error()) } if len(team1Channel1Members) != 2 { t.Errorf("expected 2 channel member on team1Channel1, got %d", len(team1Channel1Members)) } }) t.Run("error should contain a information about all users that failed", func(t *testing.T) { user1 := th.CreateUser() _, err = th.App.UpsertGroupMember(scienceGroup.Id, user1.Id) require.Nil(t, err) user2 := th.CreateUser() _, err = th.App.UpsertGroupMember(scienceGroup.Id, user2.Id) require.Nil(t, err) store := &mockStore{ Store: th.App.Srv().Store(), us: &mokeUserStore{ UserStore: th.App.Srv().Store().User(), }, } require.Nil(t, err) th.App.Srv().platform.Store = store nErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: 0, ReAddRemovedMembers: false}) require.Error(t, nErr) assert.ErrorContains(t, nErr, "failed to add team member for default team membership") assert.ErrorContains(t, nErr, user1.Id) assert.ErrorContains(t, nErr, user2.Id) }) } type mockStore struct { store.Store us store.UserStore } func (fk *mockStore) User() store.UserStore { return fk.us } type mokeUserStore struct { store.UserStore } func (us *mokeUserStore) Get(_ context.Context, id string) (*model.User, error) { return nil, fmt.Errorf("some error for %s", id) } func TestDeleteGroupMemberships(t *testing.T) { mainHelper.Parallel(t) th := Setup(t).InitBasic() defer th.TearDown() group := th.CreateGroup() userIDs := []string{th.BasicUser.Id, th.BasicUser2.Id, th.SystemAdminUser.Id} var err *model.AppError // add users to teams and channels for _, userID := range userIDs { _, err = th.App.AddTeamMember(th.Context, th.BasicTeam.Id, userID) require.Nil(t, err) _, err = th.App.AddChannelMember(th.Context, userID, th.BasicChannel, ChannelMemberOpts{}) require.Nil(t, err) } // make team group-constrained team := th.BasicTeam team.GroupConstrained = model.NewPointer(true) team, err = th.App.UpdateTeam(team) require.Nil(t, err) require.True(t, *team.GroupConstrained) // make channel group-constrained channel := th.BasicChannel channel.GroupConstrained = model.NewPointer(true) channel, err = th.App.UpdateChannel(th.Context, channel) require.Nil(t, err) require.True(t, *channel.GroupConstrained) // create groupteam and groupchannel _, err = th.App.UpsertGroupSyncable(model.NewGroupTeam(group.Id, team.Id, true)) require.Nil(t, err) _, err = th.App.UpsertGroupSyncable(model.NewGroupChannel(group.Id, channel.Id, true)) require.Nil(t, err) // verify the member count tmembers, err := th.App.GetTeamMembers(th.BasicTeam.Id, 0, 100, nil) require.Nil(t, err) require.Len(t, tmembers, 3) cmemberCount, err := th.App.GetChannelMemberCount(th.Context, th.BasicChannel.Id) require.Nil(t, err) require.Equal(t, 3, int(cmemberCount)) // add a user to the group _, err = th.App.UpsertGroupMember(group.Id, th.SystemAdminUser.Id) require.Nil(t, err) // run the delete appErr := th.App.DeleteGroupConstrainedMemberships(th.Context) require.NoError(t, appErr) // verify the new member counts tmembers, err = th.App.GetTeamMembers(th.BasicTeam.Id, 0, 100, nil) require.Nil(t, err) require.Len(t, tmembers, 1) require.Equal(t, th.SystemAdminUser.Id, tmembers[0].UserId) cmembers, err := th.App.GetChannelMembersPage(th.Context, channel.Id, 0, 99) require.Nil(t, err) require.Len(t, cmembers, 1) require.Equal(t, th.SystemAdminUser.Id, cmembers[0].UserId) } func TestSyncSyncableRoles(t *testing.T) { mainHelper.Parallel(t) th := Setup(t).InitBasic() defer th.TearDown() team := th.CreateTeam() channel := th.CreateChannel(th.Context, team) channel.GroupConstrained = model.NewPointer(true) channel, err := th.App.UpdateChannel(th.Context, channel) require.Nil(t, err) user1 := th.CreateUser() user2 := th.CreateUser() group := th.CreateGroup() teamSyncable, err := th.App.UpsertGroupSyncable(&model.GroupSyncable{ SyncableId: team.Id, Type: model.GroupSyncableTypeTeam, GroupId: group.Id, }) require.Nil(t, err) channelSyncable, err := th.App.UpsertGroupSyncable(&model.GroupSyncable{ SyncableId: channel.Id, Type: model.GroupSyncableTypeChannel, GroupId: group.Id, }) require.Nil(t, err) for _, user := range []*model.User{user1, user2} { _, err = th.App.UpsertGroupMember(group.Id, user.Id) require.Nil(t, err) var tm *model.TeamMember tm, err = th.App.AddTeamMember(th.Context, team.Id, user.Id) require.Nil(t, err) require.False(t, tm.SchemeAdmin) cm := th.AddUserToChannel(user, channel) require.False(t, cm.SchemeAdmin) } teamSyncable.SchemeAdmin = true _, err = th.App.UpdateGroupSyncable(teamSyncable) require.Nil(t, err) channelSyncable.SchemeAdmin = true _, err = th.App.UpdateGroupSyncable(channelSyncable) require.Nil(t, err) err = th.App.SyncSyncableRoles(th.Context, channel.Id, model.GroupSyncableTypeChannel) require.Nil(t, err) err = th.App.SyncSyncableRoles(th.Context, team.Id, model.GroupSyncableTypeTeam) require.Nil(t, err) for _, user := range []*model.User{user1, user2} { tm, err := th.App.GetTeamMember(th.Context, team.Id, user.Id) require.Nil(t, err) require.True(t, tm.SchemeAdmin) cm, err := th.App.GetChannelMember(th.Context, channel.Id, user.Id) require.Nil(t, err) require.True(t, cm.SchemeAdmin) } }