[release-10.11] MM-68547: Tighten authorization on group syncable link and patch endpoints (#36434)

Automatic Merge
Этот коммит содержится в:
Maria A Nunez
2026-05-06 02:53:51 -04:00
коммит произвёл GitHub
родитель 977c791e5b
Коммит 202d125afa
8 изменённых файлов: 1006 добавлений и 23 удалений

Просмотреть файл

@@ -377,10 +377,35 @@ func linkGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
groupSyncable := &model.GroupSyncable{
GroupId: c.Params.GroupId,
SyncableId: syncableID,
Type: syncableType,
appErr = verifySchemeAdminAssignmentPermission(c, syncableType, syncableID, patch)
if appErr != nil {
appErr.Where = "Api4.linkGroupSyncable"
c.Err = appErr
return
}
// Upsert onto the existing row only when it is currently active so
// unspecified fields are preserved. A fresh link, or a re-link of a
// soft-deleted row, starts from a zero-value struct so that fields
// the caller did not (or was not authorized to) set are not carried
// over from the previous incarnation. The downstream upsert clears
// DeleteAt when re-activating.
existing, appErr := c.App.GetGroupSyncable(c.Params.GroupId, syncableID, syncableType)
if appErr != nil && appErr.StatusCode != http.StatusNotFound {
appErr.Where = "Api4.linkGroupSyncable"
c.Err = appErr
return
}
var groupSyncable *model.GroupSyncable
if existing != nil && existing.DeleteAt == 0 {
groupSyncable = existing
} else {
groupSyncable = &model.GroupSyncable{
GroupId: c.Params.GroupId,
SyncableId: syncableID,
Type: syncableType,
}
}
groupSyncable.Patch(patch)
groupSyncable, appErr = c.App.UpsertGroupSyncable(groupSyncable)
@@ -392,8 +417,9 @@ func linkGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec.AddEventResultState(groupSyncable)
auditRec.AddEventObjectType("group_syncable")
syncRoles := patch.SchemeAdmin != nil
c.App.Srv().Go(func() {
c.App.SyncRolesAndMembership(c.AppContext, syncableID, syncableType, c.Params.GroupId)
c.App.SyncRolesAndMembership(c.AppContext, syncableID, syncableType, c.Params.GroupId, syncRoles)
})
w.WriteHeader(http.StatusCreated)
@@ -560,6 +586,13 @@ func patchGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
appErr = verifySchemeAdminAssignmentPermission(c, syncableType, syncableID, patch)
if appErr != nil {
appErr.Where = "Api4.patchGroupSyncable"
c.Err = appErr
return
}
groupSyncable, appErr := c.App.GetGroupSyncable(c.Params.GroupId, syncableID, syncableType)
if appErr != nil {
c.Err = appErr
@@ -577,8 +610,9 @@ func patchGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec.AddEventResultState(groupSyncable)
auditRec.AddEventObjectType("group_syncable")
syncRoles := patch.SchemeAdmin != nil
c.App.Srv().Go(func() {
c.App.SyncRolesAndMembership(c.AppContext, syncableID, syncableType, c.Params.GroupId)
c.App.SyncRolesAndMembership(c.AppContext, syncableID, syncableType, c.Params.GroupId, syncRoles)
})
b, err := json.Marshal(groupSyncable)
@@ -710,6 +744,34 @@ func verifyLinkUnlinkPermission(c *Context, syncableType model.GroupSyncableType
return nil
}
// verifySchemeAdminAssignmentPermission requires the caller to hold the
// role-management permission for the target syncable
// (manage_team_roles / manage_channel_roles), or the sysconsole groups
// write permission, before an explicit SchemeAdmin value in the patch is
// accepted. A nil patch.SchemeAdmin is a no-op.
func verifySchemeAdminAssignmentPermission(c *Context, syncableType model.GroupSyncableType, syncableID string, patch *model.GroupSyncablePatch) *model.AppError {
if patch == nil || patch.SchemeAdmin == nil {
return nil
}
if c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteUserManagementGroups) {
return nil
}
switch syncableType {
case model.GroupSyncableTypeTeam:
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), syncableID, model.PermissionManageTeamRoles) {
return model.MakePermissionError(c.AppContext.Session(), []*model.Permission{model.PermissionManageTeamRoles})
}
case model.GroupSyncableTypeChannel:
if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), syncableID, model.PermissionManageChannelRoles); !ok {
return model.MakePermissionError(c.AppContext.Session(), []*model.Permission{model.PermissionManageChannelRoles})
}
}
return nil
}
func getGroupMembers(c *Context, w http.ResponseWriter, r *http.Request) {
permissionErr := requireLicense(c)
if permissionErr != nil {

Просмотреть файл

@@ -2847,3 +2847,736 @@ func TestDeleteMembersFromGroup(t *testing.T) {
CheckBadRequestStatus(t, response)
})
}
// newSchemeAdminTestLdapGroup creates a fresh LDAP-source group with
// AllowReference=true.
func newSchemeAdminTestLdapGroup(t *testing.T, th *TestHelper) *model.Group {
t.Helper()
id := model.NewId()
g, appErr := th.App.CreateGroup(&model.Group{
DisplayName: "dn_" + id,
Name: model.NewPointer("name" + id),
Source: model.GroupSourceLdap,
Description: "description_" + id,
RemoteId: model.NewPointer(model.NewId()),
AllowReference: true,
})
require.Nil(t, appErr)
return g
}
// findPersistedGroupSyncable returns the persisted GroupSyncable for a
// given (groupID, syncableID, syncableType) tuple, including SchemeAdmin.
func findPersistedGroupSyncable(t *testing.T, th *TestHelper, groupID, syncableID string, syncableType model.GroupSyncableType) *model.GroupSyncable {
t.Helper()
syncables, appErr := th.App.GetGroupSyncables(groupID, syncableType)
require.Nil(t, appErr)
for _, s := range syncables {
if s.SyncableId == syncableID {
return s
}
}
return nil
}
func TestLinkGroupTeam_SchemeAdminRequiresElevatedPermission(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
schemeAdminTrue := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
SchemeAdmin: model.NewPointer(true),
}
t.Run("regular team user with invite_user must NOT be able to set scheme_admin: true", func(t *testing.T) {
g := newSchemeAdminTestLdapGroup(t, th)
groupSyncable, response, err := th.Client.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, schemeAdminTrue)
require.Error(t, err)
CheckForbiddenStatus(t, response)
assert.Nil(t, groupSyncable)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
if persisted != nil {
assert.False(t, persisted.SchemeAdmin)
}
})
t.Run("system admin can still set scheme_admin: true", func(t *testing.T) {
g := newSchemeAdminTestLdapGroup(t, th)
_, response, err := th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, schemeAdminTrue)
require.NoError(t, err)
CheckCreatedStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.NotNil(t, persisted)
assert.True(t, persisted.SchemeAdmin)
})
t.Run("regular team user can still link with scheme_admin omitted", func(t *testing.T) {
g := newSchemeAdminTestLdapGroup(t, th)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
}
groupSyncable, response, err := th.Client.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
require.NoError(t, err)
CheckCreatedStatus(t, response)
require.NotNil(t, groupSyncable)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.NotNil(t, persisted)
assert.False(t, persisted.SchemeAdmin)
})
t.Run("regular team user must NOT be able to link with scheme_admin: false explicitly", func(t *testing.T) {
g := newSchemeAdminTestLdapGroup(t, th)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
SchemeAdmin: model.NewPointer(false),
}
groupSyncable, response, err := th.Client.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
require.Error(t, err)
CheckForbiddenStatus(t, response)
assert.Nil(t, groupSyncable)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
if persisted != nil {
assert.False(t, persisted.SchemeAdmin)
}
})
}
func TestLinkGroupChannel_SchemeAdminRequiresElevatedPermission(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
// A regular user can only link a channel syncable when the group is
// already linked to the parent team, so seed the team link as sysadmin.
mkLinkedGroup := func(t *testing.T) *model.Group {
t.Helper()
g := newSchemeAdminTestLdapGroup(t, th)
_, response, err := th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
})
require.NoError(t, err)
CheckCreatedStatus(t, response)
return g
}
schemeAdminTrue := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
SchemeAdmin: model.NewPointer(true),
}
t.Run("regular channel user with manage_*_channel_members must NOT be able to set scheme_admin: true", func(t *testing.T) {
g := mkLinkedGroup(t)
groupSyncable, response, err := th.Client.LinkGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, schemeAdminTrue)
require.Error(t, err)
CheckForbiddenStatus(t, response)
assert.Nil(t, groupSyncable)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
if persisted != nil {
assert.False(t, persisted.SchemeAdmin)
}
})
t.Run("system admin can still set scheme_admin: true", func(t *testing.T) {
g := mkLinkedGroup(t)
_, response, err := th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, schemeAdminTrue)
require.NoError(t, err)
CheckCreatedStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
require.NotNil(t, persisted)
assert.True(t, persisted.SchemeAdmin)
})
t.Run("regular channel user can still link with scheme_admin omitted", func(t *testing.T) {
g := mkLinkedGroup(t)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
}
groupSyncable, response, err := th.Client.LinkGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
require.NoError(t, err)
CheckCreatedStatus(t, response)
require.NotNil(t, groupSyncable)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
require.NotNil(t, persisted)
assert.False(t, persisted.SchemeAdmin)
})
t.Run("regular channel user must NOT be able to link with scheme_admin: false explicitly", func(t *testing.T) {
g := mkLinkedGroup(t)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
SchemeAdmin: model.NewPointer(false),
}
groupSyncable, response, err := th.Client.LinkGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
require.Error(t, err)
CheckForbiddenStatus(t, response)
assert.Nil(t, groupSyncable)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
if persisted != nil {
assert.False(t, persisted.SchemeAdmin)
}
})
}
func TestPatchGroupTeam_SchemeAdminRequiresElevatedPermission(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
// schemeAdmin controls the seeded SchemeAdmin value on the team syncable.
setupLinkedGroup := func(t *testing.T, schemeAdmin bool) *model.Group {
t.Helper()
g := newSchemeAdminTestLdapGroup(t, th)
_, response, err := th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
SchemeAdmin: model.NewPointer(schemeAdmin),
})
require.NoError(t, err)
CheckCreatedStatus(t, response)
return g
}
schemeAdminTrue := &model.GroupSyncablePatch{
SchemeAdmin: model.NewPointer(true),
}
schemeAdminFalse := &model.GroupSyncablePatch{
SchemeAdmin: model.NewPointer(false),
}
t.Run("regular team user with invite_user must NOT be able to patch scheme_admin: true", func(t *testing.T) {
g := setupLinkedGroup(t, false)
_, response, err := th.Client.PatchGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, schemeAdminTrue)
require.Error(t, err)
CheckForbiddenStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.NotNil(t, persisted)
assert.False(t, persisted.SchemeAdmin)
})
t.Run("system admin can still patch scheme_admin: true", func(t *testing.T) {
g := setupLinkedGroup(t, false)
_, response, err := th.SystemAdminClient.PatchGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, schemeAdminTrue)
require.NoError(t, err)
CheckOKStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.NotNil(t, persisted)
assert.True(t, persisted.SchemeAdmin)
})
t.Run("regular team user can still patch other fields with scheme_admin omitted", func(t *testing.T) {
g := setupLinkedGroup(t, true)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(false),
}
_, response, err := th.Client.PatchGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
require.NoError(t, err)
CheckOKStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.NotNil(t, persisted)
assert.False(t, persisted.AutoAdd)
assert.True(t, persisted.SchemeAdmin)
})
t.Run("regular team user must NOT be able to patch scheme_admin: false", func(t *testing.T) {
g := setupLinkedGroup(t, true)
_, response, err := th.Client.PatchGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, schemeAdminFalse)
require.Error(t, err)
CheckForbiddenStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.NotNil(t, persisted)
assert.True(t, persisted.SchemeAdmin)
})
t.Run("system admin can still patch scheme_admin: false", func(t *testing.T) {
g := setupLinkedGroup(t, true)
_, response, err := th.SystemAdminClient.PatchGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, schemeAdminFalse)
require.NoError(t, err)
CheckOKStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.NotNil(t, persisted)
assert.False(t, persisted.SchemeAdmin)
})
t.Run("sysconsole_write_user_management_groups holder can patch scheme_admin in either direction", func(t *testing.T) {
// system_manager bundles sysconsole_write_user_management_groups,
// the override honoured by verifySchemeAdminAssignmentPermission.
th.LoginSystemManager()
gPromote := setupLinkedGroup(t, false)
_, response, err := th.SystemManagerClient.PatchGroupSyncable(context.Background(), gPromote.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, schemeAdminTrue)
require.NoError(t, err)
CheckOKStatus(t, response)
persistedPromote := findPersistedGroupSyncable(t, th, gPromote.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.NotNil(t, persistedPromote)
assert.True(t, persistedPromote.SchemeAdmin)
gDemote := setupLinkedGroup(t, true)
_, response, err = th.SystemManagerClient.PatchGroupSyncable(context.Background(), gDemote.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, schemeAdminFalse)
require.NoError(t, err)
CheckOKStatus(t, response)
persistedDemote := findPersistedGroupSyncable(t, th, gDemote.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.NotNil(t, persistedDemote)
assert.False(t, persistedDemote.SchemeAdmin)
})
}
func TestPatchGroupChannel_SchemeAdminRequiresElevatedPermission(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
// schemeAdmin controls the seeded SchemeAdmin value on the channel
// syncable. The team syncable is seeded so the channel link succeeds.
setupLinkedGroup := func(t *testing.T, schemeAdmin bool) *model.Group {
t.Helper()
g := newSchemeAdminTestLdapGroup(t, th)
_, response, err := th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
})
require.NoError(t, err)
CheckCreatedStatus(t, response)
_, response, err = th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
SchemeAdmin: model.NewPointer(schemeAdmin),
})
require.NoError(t, err)
CheckCreatedStatus(t, response)
return g
}
schemeAdminTrue := &model.GroupSyncablePatch{
SchemeAdmin: model.NewPointer(true),
}
schemeAdminFalse := &model.GroupSyncablePatch{
SchemeAdmin: model.NewPointer(false),
}
t.Run("regular channel user with manage_*_channel_members must NOT be able to patch scheme_admin: true", func(t *testing.T) {
g := setupLinkedGroup(t, false)
_, response, err := th.Client.PatchGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, schemeAdminTrue)
require.Error(t, err)
CheckForbiddenStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
require.NotNil(t, persisted)
assert.False(t, persisted.SchemeAdmin)
})
t.Run("system admin can still patch scheme_admin: true", func(t *testing.T) {
g := setupLinkedGroup(t, false)
_, response, err := th.SystemAdminClient.PatchGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, schemeAdminTrue)
require.NoError(t, err)
CheckOKStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
require.NotNil(t, persisted)
assert.True(t, persisted.SchemeAdmin)
})
t.Run("regular channel user can still patch other fields with scheme_admin omitted", func(t *testing.T) {
g := setupLinkedGroup(t, true)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(false),
}
_, response, err := th.Client.PatchGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
require.NoError(t, err)
CheckOKStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
require.NotNil(t, persisted)
assert.False(t, persisted.AutoAdd)
assert.True(t, persisted.SchemeAdmin)
})
t.Run("regular channel user must NOT be able to patch scheme_admin: false", func(t *testing.T) {
g := setupLinkedGroup(t, true)
_, response, err := th.Client.PatchGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, schemeAdminFalse)
require.Error(t, err)
CheckForbiddenStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
require.NotNil(t, persisted)
assert.True(t, persisted.SchemeAdmin)
})
t.Run("system admin can still patch scheme_admin: false", func(t *testing.T) {
g := setupLinkedGroup(t, true)
_, response, err := th.SystemAdminClient.PatchGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, schemeAdminFalse)
require.NoError(t, err)
CheckOKStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
require.NotNil(t, persisted)
assert.False(t, persisted.SchemeAdmin)
})
t.Run("sysconsole_write_user_management_groups holder can patch scheme_admin in either direction", func(t *testing.T) {
// system_manager bundles sysconsole_write_user_management_groups,
// the override honoured by verifySchemeAdminAssignmentPermission.
th.LoginSystemManager()
gPromote := setupLinkedGroup(t, false)
_, response, err := th.SystemManagerClient.PatchGroupSyncable(context.Background(), gPromote.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, schemeAdminTrue)
require.NoError(t, err)
CheckOKStatus(t, response)
persistedPromote := findPersistedGroupSyncable(t, th, gPromote.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
require.NotNil(t, persistedPromote)
assert.True(t, persistedPromote.SchemeAdmin)
gDemote := setupLinkedGroup(t, true)
_, response, err = th.SystemManagerClient.PatchGroupSyncable(context.Background(), gDemote.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, schemeAdminFalse)
require.NoError(t, err)
CheckOKStatus(t, response)
persistedDemote := findPersistedGroupSyncable(t, th, gDemote.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
require.NotNil(t, persistedDemote)
assert.False(t, persistedDemote.SchemeAdmin)
})
}
func TestLinkGroupTeam_LinkOnExistingPreservesSchemeAdmin(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
seedSchemeAdminTrue := func(t *testing.T) *model.Group {
t.Helper()
g := newSchemeAdminTestLdapGroup(t, th)
_, response, err := th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
SchemeAdmin: model.NewPointer(true),
})
require.NoError(t, err)
CheckCreatedStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.NotNil(t, persisted)
require.True(t, persisted.SchemeAdmin)
return g
}
t.Run("regular team user calling LINK with scheme_admin omitted must not change persisted scheme_admin", func(t *testing.T) {
g := seedSchemeAdminTrue(t)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
}
_, _, _ = th.Client.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.NotNil(t, persisted)
assert.True(t, persisted.SchemeAdmin)
})
t.Run("regular team user calling LINK with scheme_admin: false must not change persisted scheme_admin", func(t *testing.T) {
g := seedSchemeAdminTrue(t)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
SchemeAdmin: model.NewPointer(false),
}
_, _, _ = th.Client.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.NotNil(t, persisted)
assert.True(t, persisted.SchemeAdmin)
})
}
func TestLinkGroupChannel_LinkOnExistingPreservesSchemeAdmin(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
seedSchemeAdminTrue := func(t *testing.T) *model.Group {
t.Helper()
g := newSchemeAdminTestLdapGroup(t, th)
_, response, err := th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
})
require.NoError(t, err)
CheckCreatedStatus(t, response)
_, response, err = th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
SchemeAdmin: model.NewPointer(true),
})
require.NoError(t, err)
CheckCreatedStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
require.NotNil(t, persisted)
require.True(t, persisted.SchemeAdmin)
return g
}
t.Run("regular channel user calling LINK with scheme_admin omitted must not change persisted scheme_admin", func(t *testing.T) {
g := seedSchemeAdminTrue(t)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
}
_, _, _ = th.Client.LinkGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
require.NotNil(t, persisted)
assert.True(t, persisted.SchemeAdmin)
})
t.Run("regular channel user calling LINK with scheme_admin: false must not change persisted scheme_admin", func(t *testing.T) {
g := seedSchemeAdminTrue(t)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
SchemeAdmin: model.NewPointer(false),
}
_, _, _ = th.Client.LinkGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
require.NotNil(t, persisted)
assert.True(t, persisted.SchemeAdmin)
})
}
func TestLinkGroupTeam_LinkOnSoftDeletedDoesNotPreserveSchemeAdmin(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
t.Run("regular team user re-linking a soft-deleted syncable with scheme_admin omitted must persist scheme_admin: false", func(t *testing.T) {
g := newSchemeAdminTestLdapGroup(t, th)
_, response, err := th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
SchemeAdmin: model.NewPointer(true),
})
require.NoError(t, err)
CheckCreatedStatus(t, response)
response, err = th.SystemAdminClient.UnlinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.NoError(t, err)
CheckOKStatus(t, response)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
}
_, response, err = th.Client.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
require.NoError(t, err)
CheckCreatedStatus(t, response)
persisted := findPersistedGroupSyncable(t, th, g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.NotNil(t, persisted)
assert.False(t, persisted.SchemeAdmin)
})
}
func TestPatchGroupTeam_OmittedSchemeAdminDoesNotDemoteDirectAdmin(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
g := newSchemeAdminTestLdapGroup(t, th)
_, response, err := th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
})
require.NoError(t, err)
CheckCreatedStatus(t, response)
th.UpdateUserToTeamAdmin(th.BasicUser2, th.BasicTeam)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(false),
}
_, response, err = th.Client.PatchGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
require.NoError(t, err)
CheckOKStatus(t, response)
time.Sleep(2 * time.Second)
tm, appErr := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
assert.True(t, tm.SchemeAdmin)
}
func TestPatchGroupChannel_OmittedSchemeAdminDoesNotDemoteDirectAdmin(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
g := newSchemeAdminTestLdapGroup(t, th)
_, response, err := th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
})
require.NoError(t, err)
CheckCreatedStatus(t, response)
_, response, err = th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
})
require.NoError(t, err)
CheckCreatedStatus(t, response)
th.MakeUserChannelAdmin(th.BasicUser2, th.BasicChannel)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(false),
}
_, response, err = th.Client.PatchGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
require.NoError(t, err)
CheckOKStatus(t, response)
time.Sleep(2 * time.Second)
cm, appErr := th.App.GetChannelMember(th.Context, th.BasicChannel.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
assert.True(t, cm.SchemeAdmin)
}
func TestLinkGroupTeam_OmittedSchemeAdminDoesNotDemoteDirectAdmin(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
g := newSchemeAdminTestLdapGroup(t, th)
th.UpdateUserToTeamAdmin(th.BasicUser2, th.BasicTeam)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
}
_, response, err := th.Client.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
require.NoError(t, err)
CheckCreatedStatus(t, response)
time.Sleep(2 * time.Second)
tm, appErr := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
assert.True(t, tm.SchemeAdmin)
}
func TestLinkGroupChannel_OmittedSchemeAdminDoesNotDemoteDirectAdmin(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
g := newSchemeAdminTestLdapGroup(t, th)
_, response, err := th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
})
require.NoError(t, err)
CheckCreatedStatus(t, response)
th.MakeUserChannelAdmin(th.BasicUser2, th.BasicChannel)
patch := &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
}
_, response, err = th.Client.LinkGroupSyncable(context.Background(), g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
require.NoError(t, err)
CheckCreatedStatus(t, response)
time.Sleep(2 * time.Second)
cm, appErr := th.App.GetChannelMember(th.Context, th.BasicChannel.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
assert.True(t, cm.SchemeAdmin)
}
func TestLinkGroupTeam_SchemeAdminTruePromotesGroupMembers(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
g := newSchemeAdminTestLdapGroup(t, th)
_, appErr := th.App.UpsertGroupMember(g.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
_, response, err := th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
SchemeAdmin: model.NewPointer(true),
})
require.NoError(t, err)
CheckCreatedStatus(t, response)
time.Sleep(2 * time.Second)
tm, appErr := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
assert.True(t, tm.SchemeAdmin)
}
func TestLinkGroupTeam_AutoAddOnlyAddsGroupMembers(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
g := newSchemeAdminTestLdapGroup(t, th)
newUser := th.CreateUser()
_, appErr := th.App.UpsertGroupMember(g.Id, newUser.Id)
require.Nil(t, appErr)
_, appErr = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, newUser.Id)
require.NotNil(t, appErr)
_, response, err := th.SystemAdminClient.LinkGroupSyncable(context.Background(), g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, &model.GroupSyncablePatch{
AutoAdd: model.NewPointer(true),
})
require.NoError(t, err)
CheckCreatedStatus(t, response)
time.Sleep(2 * time.Second)
tm, appErr := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, newUser.Id)
require.Nil(t, appErr)
assert.Equal(t, newUser.Id, tm.UserId)
}