snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
326
server/channels/app/syncables.go
Normal file
326
server/channels/app/syncables.go
Normal file
@@ -0,0 +1,326 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
)
|
||||
|
||||
// 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.
|
||||
// If params.ReAddRemovedMembers is true, then channel members who left or were removed from the channel will
|
||||
// be re-added; otherwise, they will not be re-added.
|
||||
func (a *App) createDefaultChannelMemberships(rctx request.CTX, params model.CreateDefaultMembershipParams) error {
|
||||
channelMembers, appErr := a.ChannelMembersToAdd(params.Since, params.ScopedChannelID, params.ReAddRemovedMembers)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
var multiErr *multierror.Error
|
||||
for _, userChannel := range channelMembers {
|
||||
if params.ScopedUserID != nil && *params.ScopedUserID != userChannel.UserID {
|
||||
continue
|
||||
}
|
||||
|
||||
logger := rctx.Logger().With(
|
||||
mlog.String("user_id", userChannel.UserID),
|
||||
mlog.String("channel_id", userChannel.ChannelID),
|
||||
)
|
||||
|
||||
channel, err := a.GetChannel(rctx, userChannel.ChannelID)
|
||||
if err != nil {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("failed to get channel for default channel membership: %w", err))
|
||||
continue
|
||||
}
|
||||
|
||||
tmem, err := a.GetTeamMember(rctx, channel.TeamId, userChannel.UserID)
|
||||
if err != nil && err.Id != "app.team.get_member.missing.app_error" {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("failed to get member for default channel membership: %w", err))
|
||||
continue
|
||||
}
|
||||
|
||||
// First add user to team
|
||||
if tmem == nil {
|
||||
_, err = a.AddTeamMember(rctx, channel.TeamId, userChannel.UserID)
|
||||
if err != nil {
|
||||
if err.Id == "api.team.join_user_to_team.allowed_domains.app_error" {
|
||||
logger.Info(
|
||||
"User not added to channel - the domain associated with the user is not in the list of allowed team domains",
|
||||
mlog.String("team_id", channel.TeamId),
|
||||
)
|
||||
} else {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("failed to add team member for default channel membership: %w", err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
logger.Info("Added channel member for default channel membership")
|
||||
}
|
||||
|
||||
_, err = a.AddChannelMember(rctx, userChannel.UserID, channel, ChannelMemberOpts{
|
||||
SkipTeamMemberIntegrityCheck: true,
|
||||
})
|
||||
if err != nil {
|
||||
if err.Id == "api.channel.add_user.to.channel.failed.deleted.app_error" {
|
||||
logger.Info("Not adding user to channel because they have already left the team")
|
||||
} else {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("failed to add channel member for default channel membership: %w", err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Info("Added channel member for default channel membership")
|
||||
}
|
||||
|
||||
return multiErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// 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.
|
||||
// If params.ReAddRemovedMembers is true, then team members who left or were removed from the team will
|
||||
// be re-added; otherwise, they will not be re-added.
|
||||
func (a *App) createDefaultTeamMemberships(rctx request.CTX, params model.CreateDefaultMembershipParams) error {
|
||||
teamMembers, appErr := a.TeamMembersToAdd(params.Since, params.ScopedTeamID, params.ReAddRemovedMembers)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
var multiErr *multierror.Error
|
||||
for _, userTeam := range teamMembers {
|
||||
if params.ScopedUserID != nil && *params.ScopedUserID != userTeam.UserID {
|
||||
continue
|
||||
}
|
||||
|
||||
logger := rctx.Logger().With(
|
||||
mlog.String("user_id", userTeam.UserID),
|
||||
mlog.String("team_id", userTeam.TeamID),
|
||||
)
|
||||
|
||||
_, err := a.AddTeamMember(rctx, userTeam.TeamID, userTeam.UserID)
|
||||
if err != nil {
|
||||
if err.Id == "api.team.join_user_to_team.allowed_domains.app_error" {
|
||||
logger.Info("User not added to team - the domain associated with the user is not in the list of allowed team domains")
|
||||
} else {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("failed to add team member for default team membership: %w", err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Info("Added team member for default team membership")
|
||||
}
|
||||
|
||||
return multiErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// 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.
|
||||
// If params.AddRemovedMembers is true, then members who left or were removed from a team/channel will
|
||||
// be re-added; otherwise, they will not be re-added.
|
||||
func (a *App) CreateDefaultMemberships(rctx request.CTX, params model.CreateDefaultMembershipParams) error {
|
||||
err := a.createDefaultTeamMemberships(rctx, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = a.createDefaultChannelMemberships(rctx, params)
|
||||
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(rctx request.CTX) error {
|
||||
err := a.DeleteGroupConstrainedChannelMemberships(rctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = a.DeleteGroupConstrainedTeamMemberships(rctx, 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 procedure affects all teams.
|
||||
func (a *App) DeleteGroupConstrainedTeamMemberships(rctx request.CTX, teamID *string) error {
|
||||
teamMembers, appErr := a.TeamMembersToRemove(teamID)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
var multiErr *multierror.Error
|
||||
for _, userTeam := range teamMembers {
|
||||
logger := rctx.Logger().With(
|
||||
mlog.String("user_id", userTeam.UserId),
|
||||
mlog.String("team_id", userTeam.TeamId),
|
||||
)
|
||||
|
||||
err := a.RemoveUserFromTeam(rctx, userTeam.TeamId, userTeam.UserId, "")
|
||||
if err != nil {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("failed to remove team member for default team membership: %w", err))
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Info("Removed team member for group constrained team membership")
|
||||
}
|
||||
|
||||
return multiErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// 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 procedure affects all teams.
|
||||
func (a *App) DeleteGroupConstrainedChannelMemberships(rctx request.CTX, channelID *string) error {
|
||||
channelMembers, appErr := a.ChannelMembersToRemove(channelID)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
var multiErr *multierror.Error
|
||||
for _, userChannel := range channelMembers {
|
||||
logger := rctx.Logger().With(
|
||||
mlog.String("user_id", userChannel.UserId),
|
||||
mlog.String("channel_id", userChannel.ChannelId),
|
||||
)
|
||||
|
||||
channel, err := a.GetChannel(rctx, userChannel.ChannelId)
|
||||
if err != nil {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("failed to get channel for group constrained channel membership: %w", err))
|
||||
continue
|
||||
}
|
||||
|
||||
err = a.RemoveUserFromChannel(rctx, userChannel.UserId, "", channel)
|
||||
if err != nil {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("failed to remove channel member for group constrained channel membership: %w", err))
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Info("Removed channel member for group constrained channel membership")
|
||||
}
|
||||
|
||||
return multiErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// 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(rctx request.CTX, syncableID string, syncableType model.GroupSyncableType) *model.AppError {
|
||||
permittedAdmins, err := a.Srv().Store().Group().PermittedSyncableAdmins(syncableID, syncableType)
|
||||
if err != nil {
|
||||
return model.NewAppError("SyncSyncableRoles", "app.select_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
rctx.Logger().Info(
|
||||
fmt.Sprintf("Permitted admins for %s", syncableType),
|
||||
mlog.String(strings.ToLower(fmt.Sprintf("%s_id", syncableType)), syncableID),
|
||||
mlog.Array("permitted_admins", permittedAdmins),
|
||||
)
|
||||
|
||||
switch syncableType {
|
||||
case model.GroupSyncableTypeTeam:
|
||||
var updatedMembers []*model.TeamMember
|
||||
updatedMembers, err = a.Srv().Store().Team().UpdateMembersRole(syncableID, permittedAdmins)
|
||||
if err != nil {
|
||||
return model.NewAppError("App.SyncSyncableRoles", "app.update_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
for _, member := range updatedMembers {
|
||||
a.ClearSessionCacheForUser(member.UserId)
|
||||
|
||||
if appErr := a.sendUpdatedTeamMemberEvent(member); appErr != nil {
|
||||
rctx.Logger().Warn("Error sending channel member updated websocket event", mlog.Err(appErr))
|
||||
}
|
||||
}
|
||||
case model.GroupSyncableTypeChannel:
|
||||
var updatedMembers []*model.ChannelMember
|
||||
updatedMembers, err = a.Srv().Store().Channel().UpdateMembersRole(syncableID, permittedAdmins)
|
||||
if err != nil {
|
||||
return model.NewAppError("App.SyncSyncableRoles", "app.update_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
for _, member := range updatedMembers {
|
||||
a.ClearSessionCacheForUser(member.UserId)
|
||||
|
||||
if appErr := a.sendUpdateChannelMemberEvent(member); appErr != nil {
|
||||
rctx.Logger().Warn("Error sending channel member updated websocket event", mlog.Err(appErr))
|
||||
}
|
||||
}
|
||||
default:
|
||||
return model.NewAppError("App.SyncSyncableRoles", "groups.unsupported_syncable_type", map[string]any{"Value": syncableType}, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncRolesAndMembership updates the membership of the given syncable and,
|
||||
// when syncRoles is true, also reconciles SchemeAdmin status for its members.
|
||||
func (a *App) SyncRolesAndMembership(rctx request.CTX, syncableID string, syncableType model.GroupSyncableType, groupID string, syncRoles bool) {
|
||||
group, appErr := a.GetGroup(groupID, nil, nil)
|
||||
if appErr != nil {
|
||||
rctx.Logger().Warn("Error getting group", mlog.Err(appErr))
|
||||
return
|
||||
}
|
||||
|
||||
if syncRoles {
|
||||
appErr = a.SyncSyncableRoles(rctx, syncableID, syncableType)
|
||||
if appErr != nil {
|
||||
rctx.Logger().Warn("Error syncing syncable roles", mlog.Err(appErr))
|
||||
}
|
||||
}
|
||||
|
||||
var since int64
|
||||
reAddRemovedMembers := true
|
||||
if group.Source == model.GroupSourceLdap {
|
||||
lastJob, _ := a.Srv().Store().Job().GetNewestJobByStatusAndType(model.JobStatusSuccess, model.JobTypeLdapSync)
|
||||
if lastJob != nil {
|
||||
since = lastJob.StartAt
|
||||
}
|
||||
|
||||
reAddRemovedMembers = *a.Config().LdapSettings.ReAddRemovedMembers
|
||||
}
|
||||
|
||||
params := model.CreateDefaultMembershipParams{Since: since, ReAddRemovedMembers: reAddRemovedMembers}
|
||||
|
||||
switch syncableType {
|
||||
case model.GroupSyncableTypeTeam:
|
||||
params.ScopedTeamID = &syncableID
|
||||
if err := a.createDefaultTeamMemberships(rctx, params); err != nil {
|
||||
rctx.Logger().Warn("Error creating default team memberships", mlog.Err(err))
|
||||
}
|
||||
case model.GroupSyncableTypeChannel:
|
||||
params.ScopedChannelID = &syncableID
|
||||
if err := a.createDefaultChannelMemberships(rctx, params); err != nil {
|
||||
rctx.Logger().Warn("Error creating default channel memberships", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This method should be called when a syncable is unlinked from a group
|
||||
func (a *App) RemoveMembershipsFromUnlinkedSyncable(rctx request.CTX, syncableID string, syncableType model.GroupSyncableType) {
|
||||
switch syncableType {
|
||||
case model.GroupSyncableTypeTeam:
|
||||
if err := a.DeleteGroupConstrainedTeamMemberships(rctx, &syncableID); err != nil {
|
||||
rctx.Logger().Warn("Error deleting group constrained team memberships", mlog.Err(err))
|
||||
}
|
||||
case model.GroupSyncableTypeChannel:
|
||||
if err := a.DeleteGroupConstrainedChannelMemberships(rctx, &syncableID); err != nil {
|
||||
rctx.Logger().Warn("Error deleting group constrained channel memberships", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user