MM-21157: Triggers role sync when a syncable is linked, unlinked, or updated. (#13432)
* MM-20644: Add users to teams as a SchemeAdmin based on a new configuration field on GroupTeams and GroupChannels records. * MM-20644: Adds SchemeAdmin to mapping of the GroupSyncable struct fields. * MM-2064: Adds test to ensure SchemeAdmin field value is mapped. * MM-20644: Adds missing index creation for fresh DBs. * MM-20644: Duplicates UpdateMembersRole across Team and Channel stores. Adds tests. * MM-20644: Fixes some old method name references. * MM-20644: Moves variable declaration; removes Println statement. * MM-21157: Triggers role sync when a syncable is linked, unlinked, or updated. * MM-20644: Use a SQL query instead of two to update Team and Channel members. * MM-20644: Fixes tests; updates query. * MM-21157: Removes second invocation of function because of refactor that performs it all in a single query. * MM-21157: Switches a few queries to Squirrel. * MM-21157: SQL-formats some strings. * MM-21157: Select with list. * MM-21157: Converts some more sql to squirrel. * MM-21157: Fix incorrect conflict resolutions. * MM-21157: Fix incorrect conflict resolutions. * MM-21157: Adds missing mocks. * MM-21157: Clears cache upon syncing roles. Co-authored-by: mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
3865bc501e
Коммит
50e9aa01c3
@@ -2261,3 +2261,31 @@ func (a *App) FillInChannelsProps(channelList *model.ChannelList) *model.AppErro
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) ClearChannelMembersCache(channelID string) {
|
||||
perPage := 100
|
||||
page := 0
|
||||
|
||||
for {
|
||||
channelMembers, err := a.Srv.Store.Channel().GetMembers(channelID, page, perPage)
|
||||
if err != nil {
|
||||
a.Log.Warn("error clearing cache for channel members", mlog.String("channel_id", channelID))
|
||||
break
|
||||
}
|
||||
|
||||
for _, channelMember := range *channelMembers {
|
||||
a.ClearSessionCacheForUser(channelMember.UserId)
|
||||
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_MEMBER_UPDATED, "", "", channelMember.UserId, nil)
|
||||
message.Add("channelMember", channelMember.ToJson())
|
||||
a.Publish(message)
|
||||
}
|
||||
|
||||
length := len(*(channelMembers))
|
||||
if length < perPage {
|
||||
break
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
}
|
||||
|
||||
16
app/group.go
16
app/group.go
@@ -152,20 +152,20 @@ func (a *App) DeleteGroupSyncable(groupID string, syncableID string, syncableTyp
|
||||
return gs, nil
|
||||
}
|
||||
|
||||
func (a *App) TeamMembersToAdd(since int64) ([]*model.UserTeamIDPair, *model.AppError) {
|
||||
return a.Srv.Store.Group().TeamMembersToAdd(since)
|
||||
func (a *App) TeamMembersToAdd(since int64, teamID *string) ([]*model.UserTeamIDPair, *model.AppError) {
|
||||
return a.Srv.Store.Group().TeamMembersToAdd(since, teamID)
|
||||
}
|
||||
|
||||
func (a *App) ChannelMembersToAdd(since int64) ([]*model.UserChannelIDPair, *model.AppError) {
|
||||
return a.Srv.Store.Group().ChannelMembersToAdd(since)
|
||||
func (a *App) ChannelMembersToAdd(since int64, channelID *string) ([]*model.UserChannelIDPair, *model.AppError) {
|
||||
return a.Srv.Store.Group().ChannelMembersToAdd(since, channelID)
|
||||
}
|
||||
|
||||
func (a *App) TeamMembersToRemove() ([]*model.TeamMember, *model.AppError) {
|
||||
return a.Srv.Store.Group().TeamMembersToRemove()
|
||||
func (a *App) TeamMembersToRemove(teamID *string) ([]*model.TeamMember, *model.AppError) {
|
||||
return a.Srv.Store.Group().TeamMembersToRemove(teamID)
|
||||
}
|
||||
|
||||
func (a *App) ChannelMembersToRemove() ([]*model.ChannelMember, *model.AppError) {
|
||||
return a.Srv.Store.Group().ChannelMembersToRemove()
|
||||
func (a *App) ChannelMembersToRemove(teamID *string) ([]*model.ChannelMember, *model.AppError) {
|
||||
return a.Srv.Store.Group().ChannelMembersToRemove(teamID)
|
||||
}
|
||||
|
||||
func (a *App) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, int, *model.AppError) {
|
||||
|
||||
178
app/syncables.go
178
app/syncables.go
@@ -4,30 +4,19 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
)
|
||||
|
||||
// CreateDefaultMemberships adds users to teams and channels based on their group memberships and how those groups are
|
||||
// configured to sync with teams and channels for group members on or after the given timestamp.
|
||||
func (a *App) CreateDefaultMemberships(since int64) error {
|
||||
teamMembers, appErr := a.TeamMembersToAdd(since)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
for _, userTeam := range teamMembers {
|
||||
_, err := a.AddTeamMember(userTeam.TeamID, userTeam.UserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.Log.Info("added teammember",
|
||||
mlog.String("user_id", userTeam.UserID),
|
||||
mlog.String("team_id", userTeam.TeamID),
|
||||
)
|
||||
}
|
||||
|
||||
channelMembers, appErr := a.ChannelMembersToAdd(since)
|
||||
// createDefaultChannelMemberships adds users to channels based on their group memberships and how those groups are
|
||||
// configured to sync with channels for group members on or after the given timestamp. If a channelID is given
|
||||
// only that channel's members are created. If channelID is nil all channel memberships are created.
|
||||
func (a *App) createDefaultChannelMemberships(since int64, channelID *string) error {
|
||||
channelMembers, appErr := a.ChannelMembersToAdd(since, channelID)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
@@ -76,10 +65,91 @@ func (a *App) CreateDefaultMemberships(since int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// createDefaultTeamMemberships adds users to teams based on their group memberships and how those groups are
|
||||
// configured to sync with teams for group members on or after the given timestamp. If a teamID is given
|
||||
// only that team's members are created. If teamID is nil all team memberships are created.
|
||||
func (a *App) createDefaultTeamMemberships(since int64, teamID *string) error {
|
||||
teamMembers, appErr := a.TeamMembersToAdd(since, teamID)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
for _, userTeam := range teamMembers {
|
||||
_, err := a.AddTeamMember(userTeam.TeamID, userTeam.UserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.Log.Info("added teammember",
|
||||
mlog.String("user_id", userTeam.UserID),
|
||||
mlog.String("team_id", userTeam.TeamID),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateDefaultMemberships adds users to teams and channels based on their group memberships and how those groups
|
||||
// are configured to sync with teams and channels for group members on or after the given timestamp.
|
||||
func (a *App) CreateDefaultMemberships(since int64) error {
|
||||
err := a.createDefaultTeamMemberships(since, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = a.createDefaultChannelMemberships(since, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteGroupConstrainedMemberships deletes team and channel memberships of users who aren't members of the allowed
|
||||
// groups of all group-constrained teams and channels.
|
||||
func (a *App) DeleteGroupConstrainedMemberships() error {
|
||||
channelMembers, appErr := a.ChannelMembersToRemove()
|
||||
err := a.deleteGroupConstrainedChannelMemberships(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = a.deleteGroupConstrainedTeamMemberships(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteGroupConstrainedTeamMemberships deletes team memberships of users who aren't members of the allowed
|
||||
// groups of the given group-constrained team. If a teamID is given then the procedure is scoped to the given team,
|
||||
// if teamID is nil then the proceedure affects all teams.
|
||||
func (a *App) deleteGroupConstrainedTeamMemberships(teamID *string) error {
|
||||
teamMembers, appErr := a.TeamMembersToRemove(teamID)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
for _, userTeam := range teamMembers {
|
||||
err := a.RemoveUserFromTeam(userTeam.TeamId, userTeam.UserId, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.Log.Info("removed teammember",
|
||||
mlog.String("user_id", userTeam.UserId),
|
||||
mlog.String("team_id", userTeam.TeamId),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteGroupConstrainedChannelMemberships deletes channel memberships of users who aren't members of the allowed
|
||||
// groups of the given group-constrained channel. If a channelID is given then the procedure is scoped to the given team,
|
||||
// if channelID is nil then the proceedure affects all teams.
|
||||
func (a *App) deleteGroupConstrainedChannelMemberships(channelID *string) error {
|
||||
channelMembers, appErr := a.ChannelMembersToRemove(channelID)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
@@ -101,22 +171,62 @@ func (a *App) DeleteGroupConstrainedMemberships() error {
|
||||
)
|
||||
}
|
||||
|
||||
teamMembers, appErr := a.TeamMembersToRemove()
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncSyncableRoles updates the SchemeAdmin field value of the given syncable's members based on the configuration of
|
||||
// the member's group memberships and the configuration of those groups to the syncable. This method should only
|
||||
// be invoked on group-synced (aka group-constrained) syncables.
|
||||
func (a *App) SyncSyncableRoles(syncableID string, syncableType model.GroupSyncableType) *model.AppError {
|
||||
permittedAdmins, err := a.Srv.Store.Group().PermittedSyncableAdmins(syncableID, syncableType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, userTeam := range teamMembers {
|
||||
err := a.RemoveUserFromTeam(userTeam.TeamId, userTeam.UserId, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.Log.Info(
|
||||
fmt.Sprintf("Permitted admins for %s", syncableType),
|
||||
mlog.String(strings.ToLower(fmt.Sprintf("%s_id", syncableType)), syncableID),
|
||||
mlog.Any("permitted_admins", permittedAdmins),
|
||||
)
|
||||
|
||||
a.Log.Info("removed teammember",
|
||||
mlog.String("user_id", userTeam.UserId),
|
||||
mlog.String("team_id", userTeam.TeamId),
|
||||
)
|
||||
var updateFunc func(string, []string) *model.AppError
|
||||
|
||||
switch syncableType {
|
||||
case model.GroupSyncableTypeTeam:
|
||||
updateFunc = a.Srv.Store.Team().UpdateMembersRole
|
||||
case model.GroupSyncableTypeChannel:
|
||||
updateFunc = a.Srv.Store.Channel().UpdateMembersRole
|
||||
default:
|
||||
return model.NewAppError("App.SyncSyncableRoles", "groups.unsupported_syncable_type", map[string]interface{}{"Value": syncableType}, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
err = updateFunc(syncableID, permittedAdmins)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncRolesAndMembership updates the SchemeAdmin status and membership of all of the members of the given
|
||||
// syncable.
|
||||
func (a *App) SyncRolesAndMembership(syncableID string, syncableType model.GroupSyncableType) {
|
||||
a.SyncSyncableRoles(syncableID, syncableType)
|
||||
|
||||
lastJob, _ := a.Srv.Store.Job().GetNewestJobByStatusAndType(model.JOB_STATUS_SUCCESS, model.JOB_TYPE_LDAP_SYNC)
|
||||
var since int64
|
||||
if lastJob != nil {
|
||||
since = lastJob.StartAt
|
||||
}
|
||||
|
||||
switch syncableType {
|
||||
case model.GroupSyncableTypeTeam:
|
||||
a.createDefaultTeamMemberships(since, &syncableID)
|
||||
a.deleteGroupConstrainedTeamMemberships(&syncableID)
|
||||
a.ClearTeamMembersCache(syncableID)
|
||||
case model.GroupSyncableTypeChannel:
|
||||
a.createDefaultChannelMemberships(since, &syncableID)
|
||||
a.deleteGroupConstrainedChannelMemberships(&syncableID)
|
||||
a.ClearChannelMembersCache(syncableID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,3 +396,70 @@ func TestDeleteGroupMemberships(t *testing.T) {
|
||||
require.Len(t, (*cmembers), 1)
|
||||
require.Equal(t, th.SystemAdminUser.Id, (*cmembers)[0].UserId)
|
||||
}
|
||||
|
||||
func TestSyncSyncableRoles(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
team := th.CreateTeam()
|
||||
|
||||
channel := th.CreateChannel(team)
|
||||
channel.GroupConstrained = model.NewBool(true)
|
||||
channel, err := th.App.UpdateChannel(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(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(channel.Id, model.GroupSyncableTypeChannel)
|
||||
require.Nil(t, err)
|
||||
|
||||
err = th.App.SyncSyncableRoles(team.Id, model.GroupSyncableTypeTeam)
|
||||
require.Nil(t, err)
|
||||
|
||||
for _, user := range []*model.User{user1, user2} {
|
||||
tm, err := th.App.GetTeamMember(team.Id, user.Id)
|
||||
require.Nil(t, err)
|
||||
require.True(t, tm.SchemeAdmin)
|
||||
|
||||
cm, err := th.App.GetChannelMember(channel.Id, user.Id)
|
||||
require.Nil(t, err)
|
||||
require.True(t, cm.SchemeAdmin)
|
||||
}
|
||||
}
|
||||
|
||||
28
app/team.go
28
app/team.go
@@ -1494,3 +1494,31 @@ func (a *App) InvalidateAllEmailInvites() *model.AppError {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) ClearTeamMembersCache(teamID string) {
|
||||
perPage := 100
|
||||
page := 0
|
||||
|
||||
for {
|
||||
teamMembers, err := a.Srv.Store.Team().GetMembers(teamID, page, perPage, &model.ViewUsersRestrictions{})
|
||||
if err != nil {
|
||||
a.Log.Warn("error clearing cache for team members", mlog.String("team_id", teamID))
|
||||
break
|
||||
}
|
||||
|
||||
for _, teamMember := range teamMembers {
|
||||
a.ClearSessionCacheForUser(teamMember.UserId)
|
||||
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_MEMBERROLE_UPDATED, "", "", teamMember.UserId, nil)
|
||||
message.Add("member", teamMember.ToJson())
|
||||
a.Publish(message)
|
||||
}
|
||||
|
||||
length := len(teamMembers)
|
||||
if length < perPage {
|
||||
break
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user