mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
* MM-68547: Tighten authorization on group syncable link and patch endpoints
Adds an additional permission check on the group syncable link and patch
endpoints. Callers must hold the role-management permission for the
target team or channel (or the sysconsole groups-management permission).
Made-with: Cursor
* Linting
* MM-68547: Extend group syncable scheme_admin authorization checks
Gate any explicit scheme_admin value (in either direction) on link and
patch. Populate SchemeAdmin in the singular getGroupSyncable so that
patches that do not touch scheme_admin no longer overwrite the persisted
value. Restrict PermittedSyncableAdmins to active syncables. Start the
link upsert from the existing active row to preserve fields the caller
did not, or could not, set.
Made-with: Cursor
* MM-68547: Add store-layer regression coverage for SchemeAdmin handling
Extend testGetGroupSyncable to round-trip SchemeAdmin: true through
UpdateGroupSyncable and re-fetch, locking in that getGroupSyncable
populates the field from the persisted row.
Strengthen groupTestPermittedSyncableAdmins{Team,Channel} to assert
that DeleteGroupSyncable preserves SchemeAdmin in the persisted row
and that PermittedSyncableAdmins still excludes the row, making the
coupling between the two store changes explicit.
Made-with: Cursor
* MM-68547: Fix group details role-change dedup on remove
The roleChangeKey helper was reading team_id/channel_id from the items
in itemsToRemove, but onRemoveTeamOrChannel pushes those items with a
generic id field. The deletion of the staged role change in
handleRemovedTeamsAndChannels therefore never matched the key produced
by onChangeRoles, and a stale patchGroupSyncable was dispatched after
the unlink.
Accept either id or team_id/channel_id when computing the key. Also
extend the e2e assertion to verify the channel removal took effect
(delete_at != 0) alongside the existing scheme_admin check.
Made-with: Cursor
* MM-68547: Mirror delete_at assertion on the removed-team e2e test
The team variant of "does not update the role of a removed X" was left
asserting only on scheme_admin. Add the matching delete_at != 0 check
already present in the channel variant so both tests verify the same
user-visible contract.
Made-with: Cursor
* Skip SyncSyncableRoles if no scheme_admin
(cherry picked from commit 6afe64ff9f)
Co-authored-by: Maria A Nunez <[email protected]>
821 lines
27 KiB
Go
821 lines
27 KiB
Go
// 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(t)
|
|
|
|
singersTeam, err := th.App.CreateTeam(th.Context, &model.Team{
|
|
DisplayName: "Singers",
|
|
Name: "zz" + model.NewId(),
|
|
Email: "[email protected]",
|
|
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: "[email protected]",
|
|
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(t)
|
|
restrictedUser.Email = "[email protected]"
|
|
_, 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: "[email protected]",
|
|
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: "[email protected]",
|
|
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(t)
|
|
_, err = th.App.UpsertGroupMember(scienceGroup.Id, user1.Id)
|
|
require.Nil(t, err)
|
|
|
|
user2 := th.CreateUser(t)
|
|
_, 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(t)
|
|
|
|
group := th.CreateGroup(t)
|
|
|
|
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(t)
|
|
|
|
team := th.CreateTeam(t)
|
|
|
|
channel := th.CreateChannel(t, team)
|
|
channel.GroupConstrained = model.NewPointer(true)
|
|
channel, err := th.App.UpdateChannel(th.Context, channel)
|
|
require.Nil(t, err)
|
|
|
|
user1 := th.CreateUser(t)
|
|
user2 := th.CreateUser(t)
|
|
group := th.CreateGroup(t)
|
|
|
|
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(t, 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)
|
|
}
|
|
}
|
|
|
|
func TestSyncRolesAndMembership_RoleSyncGate(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
setup := func(t *testing.T) (*model.Team, *model.Channel, *model.Group, *model.User) {
|
|
t.Helper()
|
|
|
|
team := th.CreateTeam(t)
|
|
channel := th.CreateChannel(t, team)
|
|
group := th.CreateGroup(t)
|
|
|
|
_, err := th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
|
SyncableId: team.Id,
|
|
Type: model.GroupSyncableTypeTeam,
|
|
GroupId: group.Id,
|
|
AutoAdd: true,
|
|
})
|
|
require.Nil(t, err)
|
|
|
|
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
|
SyncableId: channel.Id,
|
|
Type: model.GroupSyncableTypeChannel,
|
|
GroupId: group.Id,
|
|
AutoAdd: true,
|
|
})
|
|
require.Nil(t, err)
|
|
|
|
directAdmin := th.CreateUser(t)
|
|
_, appErr := th.App.AddTeamMember(th.Context, team.Id, directAdmin.Id)
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.AddUserToChannel(th.Context, directAdmin, channel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
tm, storeErr := th.App.Srv().Store().Team().GetMember(th.Context, team.Id, directAdmin.Id)
|
|
require.NoError(t, storeErr)
|
|
tm.SchemeAdmin = true
|
|
_, storeErr = th.App.Srv().Store().Team().UpdateMember(th.Context, tm)
|
|
require.NoError(t, storeErr)
|
|
|
|
cm, storeErr := th.App.Srv().Store().Channel().GetMember(th.Context, channel.Id, directAdmin.Id)
|
|
require.NoError(t, storeErr)
|
|
cm.SchemeAdmin = true
|
|
_, storeErr = th.App.Srv().Store().Channel().UpdateMember(th.Context, cm)
|
|
require.NoError(t, storeErr)
|
|
|
|
return team, channel, group, directAdmin
|
|
}
|
|
|
|
t.Run("syncRoles=false preserves the existing SchemeAdmin on team members", func(t *testing.T) {
|
|
team, _, group, directAdmin := setup(t)
|
|
|
|
th.App.SyncRolesAndMembership(th.Context, team.Id, model.GroupSyncableTypeTeam, group.Id, false)
|
|
|
|
tm, appErr := th.App.GetTeamMember(th.Context, team.Id, directAdmin.Id)
|
|
require.Nil(t, appErr)
|
|
assert.True(t, tm.SchemeAdmin)
|
|
})
|
|
|
|
t.Run("syncRoles=false preserves the existing SchemeAdmin on channel members", func(t *testing.T) {
|
|
_, channel, group, directAdmin := setup(t)
|
|
|
|
th.App.SyncRolesAndMembership(th.Context, channel.Id, model.GroupSyncableTypeChannel, group.Id, false)
|
|
|
|
cm, appErr := th.App.GetChannelMember(th.Context, channel.Id, directAdmin.Id)
|
|
require.Nil(t, appErr)
|
|
assert.True(t, cm.SchemeAdmin)
|
|
})
|
|
|
|
t.Run("syncRoles=true reconciles team SchemeAdmin against PermittedSyncableAdmins", func(t *testing.T) {
|
|
team, _, group, directAdmin := setup(t)
|
|
|
|
th.App.SyncRolesAndMembership(th.Context, team.Id, model.GroupSyncableTypeTeam, group.Id, true)
|
|
|
|
tm, appErr := th.App.GetTeamMember(th.Context, team.Id, directAdmin.Id)
|
|
require.Nil(t, appErr)
|
|
assert.False(t, tm.SchemeAdmin)
|
|
})
|
|
|
|
t.Run("syncRoles=true reconciles channel SchemeAdmin against PermittedSyncableAdmins", func(t *testing.T) {
|
|
_, channel, group, directAdmin := setup(t)
|
|
|
|
th.App.SyncRolesAndMembership(th.Context, channel.Id, model.GroupSyncableTypeChannel, group.Id, true)
|
|
|
|
cm, appErr := th.App.GetChannelMember(th.Context, channel.Id, directAdmin.Id)
|
|
require.Nil(t, appErr)
|
|
assert.False(t, cm.SchemeAdmin)
|
|
})
|
|
}
|
|
|
|
func TestSyncRolesAndMembership_AlwaysSyncsMembership(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
setup := func(t *testing.T) (*model.Team, *model.Channel, *model.Group, *model.User) {
|
|
t.Helper()
|
|
|
|
team := th.CreateTeam(t)
|
|
channel := th.CreateChannel(t, team)
|
|
group := th.CreateGroup(t)
|
|
|
|
_, err := th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
|
SyncableId: team.Id,
|
|
Type: model.GroupSyncableTypeTeam,
|
|
GroupId: group.Id,
|
|
AutoAdd: true,
|
|
})
|
|
require.Nil(t, err)
|
|
|
|
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
|
SyncableId: channel.Id,
|
|
Type: model.GroupSyncableTypeChannel,
|
|
GroupId: group.Id,
|
|
AutoAdd: true,
|
|
})
|
|
require.Nil(t, err)
|
|
|
|
groupMember := th.CreateUser(t)
|
|
_, err = th.App.UpsertGroupMember(group.Id, groupMember.Id)
|
|
require.Nil(t, err)
|
|
|
|
return team, channel, group, groupMember
|
|
}
|
|
|
|
t.Run("syncRoles=false still adds group members to the team", func(t *testing.T) {
|
|
team, _, group, groupMember := setup(t)
|
|
|
|
th.App.SyncRolesAndMembership(th.Context, team.Id, model.GroupSyncableTypeTeam, group.Id, false)
|
|
|
|
tm, appErr := th.App.GetTeamMember(th.Context, team.Id, groupMember.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, groupMember.Id, tm.UserId)
|
|
})
|
|
|
|
t.Run("syncRoles=false still adds group members to the channel", func(t *testing.T) {
|
|
_, channel, group, groupMember := setup(t)
|
|
|
|
th.App.SyncRolesAndMembership(th.Context, channel.Id, model.GroupSyncableTypeChannel, group.Id, false)
|
|
|
|
cm, appErr := th.App.GetChannelMember(th.Context, channel.Id, groupMember.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, groupMember.Id, cm.UserId)
|
|
})
|
|
}
|