[release-10.11] Tighten thread membership cleanup on team membership changes (#37081)
* Tighten thread membership cleanup on team membership changes (#36764) * Tighten thread membership cleanup on team membership changes Ensure ThreadMembership rows are cleaned up when a user is removed from or leaves a team, add a defense-in-depth filter on the thread retrieval path so memberships referencing channels the user is no longer a member of are excluded, and add a one-time migration to clean up stale records on existing deployments. https://mattermost.atlassian.net/browse/MM-69008 * Centralize per-channel membership removal and tighten tests Extract the combined channel-member and thread-membership removal into a shared helper used by both removeUserFromChannel and LeaveTeam, so future code paths cannot revoke channel access without dropping the dependent thread state. The channel-leave event is now logged after the combined removal completes. Also drop verbose test header comments and rename a test to a behavior-focused name. * Backfill channel members in thread storetest setups The new ChannelMembers predicate on thread read queries filters out ThreadMembership rows whose user has no ChannelMembers row for the thread's channel. Several existing storetest setups bypassed the normal write path and inserted threads/memberships without channel members. Add the missing channel-member rows so the test data matches the real-world invariant. * Retrigger enterprise CI Pick up enterprise merge e6953d4 (master into MM-69008-thread-membership-team-leave) in the combined Enterprise CI/tests lane, which pins the enterprise SHA at mattermost-side dispatch time. Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com> --------- Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> * Add MySQL migration for 000195_threadmemberships_cleanup_v2 Co-authored-by: Cursor <cursoragent@cursor.com> * Apply pre-commit lint fixes Co-authored-by: Cursor <cursoragent@cursor.com> * Retrigger CI Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
acc19baca0
Коммит
2ff29e375b
@@ -70,6 +70,17 @@ type SqlThreadStore struct {
|
||||
func (s *SqlThreadStore) ClearCaches() {
|
||||
}
|
||||
|
||||
// channelMembershipPredicate filters out ThreadMemberships whose user is no
|
||||
// longer a member of the thread's channel. DM/GM threads have an empty
|
||||
// ThreadTeamId and are exempt because their access is intrinsic to the
|
||||
// channel members.
|
||||
func channelMembershipPredicate() sq.Sqlizer {
|
||||
return sq.Or{
|
||||
sq.Eq{"Threads.ThreadTeamId": ""},
|
||||
sq.Expr("EXISTS (SELECT 1 FROM ChannelMembers WHERE ChannelMembers.ChannelId = Threads.ChannelId AND ChannelMembers.UserId = ThreadMemberships.UserId)"),
|
||||
}
|
||||
}
|
||||
|
||||
func newSqlThreadStore(sqlStore *SqlStore) store.ThreadStore {
|
||||
s := SqlThreadStore{
|
||||
SqlStore: sqlStore,
|
||||
@@ -131,7 +142,8 @@ func (s *SqlThreadStore) getTotalThreadsQuery(userId, teamId string, opts model.
|
||||
Where(sq.Eq{
|
||||
"ThreadMemberships.UserId": userId,
|
||||
"ThreadMemberships.Following": true,
|
||||
})
|
||||
}).
|
||||
Where(channelMembershipPredicate())
|
||||
|
||||
if teamId != "" {
|
||||
if opts.ExcludeDirect {
|
||||
@@ -198,7 +210,8 @@ func (s *SqlThreadStore) GetTotalUnreadMentions(userId, teamId string, opts mode
|
||||
Where(sq.Eq{
|
||||
"ThreadMemberships.UserId": userId,
|
||||
"ThreadMemberships.Following": true,
|
||||
})
|
||||
}).
|
||||
Where(channelMembershipPredicate())
|
||||
|
||||
if teamId != "" {
|
||||
if opts.ExcludeDirect {
|
||||
@@ -234,15 +247,13 @@ func (s *SqlThreadStore) GetTotalUnreadUrgentMentions(userId, teamId string, opt
|
||||
Select("COALESCE(SUM(ThreadMemberships.UnreadMentions),0)").
|
||||
From("ThreadMemberships").
|
||||
Join("PostsPriority ON PostsPriority.PostId = ThreadMemberships.PostId").
|
||||
Join("Threads ON Threads.PostId = ThreadMemberships.PostId").
|
||||
Where(sq.Eq{
|
||||
"ThreadMemberships.UserId": userId,
|
||||
"ThreadMemberships.Following": true,
|
||||
"PostsPriority.Priority": model.PostPriorityUrgent,
|
||||
})
|
||||
|
||||
if teamId != "" || !opts.Deleted {
|
||||
query = query.Join("Threads ON Threads.PostId = ThreadMemberships.PostId")
|
||||
}
|
||||
}).
|
||||
Where(channelMembershipPredicate())
|
||||
|
||||
if teamId != "" {
|
||||
if opts.ExcludeDirect {
|
||||
@@ -298,7 +309,8 @@ func (s *SqlThreadStore) GetThreadsForUser(userId, teamId string, opts model.Get
|
||||
|
||||
query = query.
|
||||
Where(sq.Eq{"ThreadMemberships.UserId": userId}).
|
||||
Where(sq.Eq{"ThreadMemberships.Following": true})
|
||||
Where(sq.Eq{"ThreadMemberships.Following": true}).
|
||||
Where(channelMembershipPredicate())
|
||||
|
||||
if opts.IncludeIsUrgent {
|
||||
urgencyCase := sq.
|
||||
@@ -405,6 +417,7 @@ func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string,
|
||||
sq.Eq{"ThreadMemberships.Following": true},
|
||||
sq.Eq{"Threads.ThreadTeamId": teamIDs},
|
||||
sq.Eq{"COALESCE(Threads.ThreadDeleteAt, 0)": 0},
|
||||
channelMembershipPredicate(),
|
||||
}
|
||||
|
||||
var eg errgroup.Group
|
||||
|
||||
Ссылка в новой задаче
Block a user