forked from discourse/discourse
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
FEATURE: Auto-remove users without permission from channel (discourse…
…#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.
- Loading branch information
1 parent
b5c4e1b
commit 520d4f5
Showing
38 changed files
with
2,174 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# frozen_string_literal: true | ||
|
||
## | ||
# In some cases we may want to enqueue_at several of the same job with | ||
# batches, spacing them out or incrementing by some amount of seconds, | ||
# in case the jobs do heavy work or send many MessageBus messages and the like. | ||
# This class handles figuring out the seconds increments. | ||
# | ||
# @example | ||
# spacer = JobTimeSpacer.new | ||
# user_ids.in_groups_of(200, false) do |user_id_batch| | ||
# spacer.enqueue(:kick_users_from_topic, { topic_id: topic_id, user_ids: user_id_batch }) | ||
# end | ||
class JobTimeSpacer | ||
def initialize(seconds_space_increment: 1, seconds_delay: 5) | ||
@seconds_space_increment = seconds_space_increment | ||
@seconds_space_modifier = seconds_space_increment | ||
@seconds_step = seconds_delay | ||
end | ||
|
||
def enqueue(job_name, job_args = {}) | ||
Jobs.enqueue_at((@seconds_step * @seconds_space_modifier).seconds.from_now, job_name, job_args) | ||
@seconds_space_modifier += @seconds_space_increment | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
# NOTE: When changing auto-join logic, make sure to update the `settings.auto_join_users_info` translation as well. | ||
# frozen_string_literal: true | ||
|
||
module Jobs | ||
class AutoJoinChannelBatch < ::Jobs::Base | ||
def execute(args) | ||
return "starts_at or ends_at missing" if args[:starts_at].blank? || args[:ends_at].blank? | ||
start_user_id = args[:starts_at].to_i | ||
end_user_id = args[:ends_at].to_i | ||
|
||
return "End is higher than start" if end_user_id < start_user_id | ||
|
||
channel = | ||
ChatChannel.find_by( | ||
id: args[:chat_channel_id], | ||
auto_join_users: true, | ||
chatable_type: "Category", | ||
) | ||
|
||
return if !channel | ||
|
||
category = channel.chatable | ||
return if !category | ||
|
||
query_args = { | ||
chat_channel_id: channel.id, | ||
start: start_user_id, | ||
end: end_user_id, | ||
suspended_until: Time.zone.now, | ||
last_seen_at: 3.months.ago, | ||
channel_category: channel.chatable_id, | ||
mode: Chat::UserChatChannelMembership.join_modes[:automatic], | ||
} | ||
|
||
new_member_ids = DB.query_single(create_memberships_query(category), query_args) | ||
|
||
# Only do this if we are running auto-join for a single user, if we | ||
# are doing it for many then we should do it after all batches are | ||
# complete for the channel in Jobs::AutoJoinChannelMemberships | ||
if start_user_id == end_user_id | ||
Chat::ChatChannelMembershipManager.new(channel).recalculate_user_count | ||
end | ||
|
||
Chat::Publisher.publish_new_channel(channel.reload, User.where(id: new_member_ids)) | ||
end | ||
|
||
private | ||
|
||
def create_memberships_query(category) | ||
query = <<~SQL | ||
INSERT INTO user_chat_channel_memberships (user_id, chat_channel_id, following, created_at, updated_at, join_mode) | ||
SELECT DISTINCT(users.id), :chat_channel_id, TRUE, NOW(), NOW(), :mode | ||
FROM users | ||
INNER JOIN user_options uo ON uo.user_id = users.id | ||
LEFT OUTER JOIN user_chat_channel_memberships uccm ON | ||
uccm.chat_channel_id = :chat_channel_id AND uccm.user_id = users.id | ||
SQL | ||
|
||
query += <<~SQL if category.read_restricted? | ||
INNER JOIN group_users gu ON gu.user_id = users.id | ||
LEFT OUTER JOIN category_groups cg ON cg.group_id = gu.group_id | ||
SQL | ||
|
||
query += <<~SQL | ||
WHERE (users.id >= :start AND users.id <= :end) AND | ||
users.staged IS FALSE AND users.active AND | ||
NOT EXISTS(SELECT 1 FROM anonymous_users a WHERE a.user_id = users.id) AND | ||
(suspended_till IS NULL OR suspended_till <= :suspended_until) AND | ||
(last_seen_at > :last_seen_at) AND | ||
uo.chat_enabled AND | ||
uccm.id IS NULL | ||
SQL | ||
|
||
query += <<~SQL if category.read_restricted? | ||
AND cg.category_id = :channel_category | ||
SQL | ||
|
||
query += "RETURNING user_chat_channel_memberships.user_id" | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
plugins/chat/app/jobs/regular/chat/auto_remove_membership_handle_category_updated.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# frozen_string_literal: true | ||
|
||
module Jobs | ||
module Chat | ||
class AutoRemoveMembershipHandleCategoryUpdated < ::Jobs::Base | ||
def execute(args) | ||
::Chat::AutoRemove::HandleCategoryUpdated.call(**args) | ||
end | ||
end | ||
end | ||
end |
11 changes: 11 additions & 0 deletions
11
...ns/chat/app/jobs/regular/chat/auto_remove_membership_handle_chat_allowed_groups_change.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# frozen_string_literal: true | ||
|
||
module Jobs | ||
module Chat | ||
class AutoRemoveMembershipHandleChatAllowedGroupsChange < ::Jobs::Base | ||
def execute(args) | ||
::Chat::AutoRemove::HandleChatAllowedGroupsChange.call(**args) | ||
end | ||
end | ||
end | ||
end |
11 changes: 11 additions & 0 deletions
11
plugins/chat/app/jobs/regular/chat/auto_remove_membership_handle_destroyed_group.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# frozen_string_literal: true | ||
|
||
module Jobs | ||
module Chat | ||
class AutoRemoveMembershipHandleDestroyedGroup < ::Jobs::Base | ||
def execute(args) | ||
::Chat::AutoRemove::HandleDestroyedGroup.call(**args) | ||
end | ||
end | ||
end | ||
end |
11 changes: 11 additions & 0 deletions
11
plugins/chat/app/jobs/regular/chat/auto_remove_membership_handle_user_removed_from_group.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# frozen_string_literal: true | ||
|
||
module Jobs | ||
module Chat | ||
class AutoRemoveMembershipHandleUserRemovedFromGroup < ::Jobs::Base | ||
def execute(args) | ||
::Chat::AutoRemove::HandleUserRemovedFromGroup.call(**args) | ||
end | ||
end | ||
end | ||
end |
13 changes: 13 additions & 0 deletions
13
plugins/chat/app/jobs/regular/chat/kick_users_from_channel.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# frozen_string_literal: true | ||
|
||
module Jobs | ||
module Chat | ||
class KickUsersFromChannel < Jobs::Base | ||
def execute(args) | ||
return if !::Chat::Channel.exists?(id: args[:channel_id]) | ||
return if args[:user_ids].blank? | ||
::Chat::Publisher.publish_kick_users(args[:channel_id], args[:user_ids]) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.