discourse/plugins/chat/lib/chat/channel_membership_manager.rb
Martin Brennan 520d4f504b
FEATURE: Auto-remove users without permission from channel (#20344)
There are many situations that may cause users to lose permission to
send messages in a chat channel. Until now we have relied on security
checks in `Chat::ChatChannelFetcher` to remove channels which the
user may have a `UserChatChannelMembership` record for but which
they do not have access to.

This commit takes a more proactive approach. Now any of these following
`DiscourseEvent` triggers may cause `UserChatChannelMembership`
records to be deleted:

* `category_updated` - Permissions of the category changed
   (i.e. CategoryGroup records changed)
* `user_removed_from_group` - Means the user may not be able to access the
   channel based on `GroupUser` or also `chat_allowed_groups`
* `site_setting_changed` - The `chat_allowed_groups` was updated, some
   users may no longer be in groups that can access chat.
* `group_destroyed` - Means the user may not be able to access the
   channel based on `GroupUser` or also `chat_allowed_groups`

All of these are handled in a distinct service run in a background
job. Users removed are logged via `StaffActionLog` and then we
publish messages on a per-channel basis to users who had their
memberships deleted.

When the user has a channel they are kicked from open, we show
a dialog saying "You no longer have access to this channel".

When they click OK we redirect them either:

* To their first other public channel, if they have any followed
* The chat browse page if they don't

This is to save on tons of requests from kicked out users getting messages
from other channels.

When the user does not have the kicked channel open, we can just
silently yoink it out of their sidebar and turn off subscriptions.
2023-03-22 10:19:59 +10:00

82 lines
2.1 KiB
Ruby

# frozen_string_literal: true
module Chat
class ChannelMembershipManager
def self.all_for_user(user)
Chat::UserChatChannelMembership.where(user: user)
end
attr_reader :channel
def initialize(channel)
@channel = channel
end
def find_for_user(user, following: nil)
params = { user_id: user.id, chat_channel_id: channel.id }
params[:following] = following if following.present?
Chat::UserChatChannelMembership.includes(:user, :chat_channel).find_by(params)
end
def follow(user)
membership =
find_for_user(user) ||
Chat::UserChatChannelMembership.new(user: user, chat_channel: channel, following: true)
ActiveRecord::Base.transaction do
if membership.new_record?
membership.save!
recalculate_user_count
elsif !membership.following
membership.update!(following: true)
recalculate_user_count
end
end
membership
end
def unfollow(user)
membership = find_for_user(user)
return if membership.blank?
ActiveRecord::Base.transaction do
if membership.following
membership.update!(following: false)
recalculate_user_count
end
end
membership
end
def recalculate_user_count
return if Chat::Channel.exists?(id: channel.id, user_count_stale: true)
channel.update!(user_count_stale: true)
Jobs.enqueue_in(3.seconds, Jobs::Chat::UpdateChannelUserCount, chat_channel_id: channel.id)
end
def unfollow_all_users
Chat::UserChatChannelMembership.where(chat_channel: channel).update_all(
following: false,
last_read_message_id: channel.chat_messages.last&.id,
)
end
def enforce_automatic_channel_memberships
Jobs.enqueue(Jobs::Chat::AutoJoinChannelMemberships, chat_channel_id: channel.id)
end
def enforce_automatic_user_membership(user)
Jobs.enqueue(
Jobs::Chat::AutoJoinChannelBatch,
chat_channel_id: channel.id,
starts_at: user.id,
ends_at: user.id,
)
end
end
end