mirror of
https://github.com/discourse/discourse.git
synced 2026-08-07 13:19:19 +08:00
Previously, filtering a user search by group (e.g. `/u/search/users?groups=admins`) only checked whether the requester could see the group's members, not the group itself. Because the default automatic groups are visible to logged-in users but expose their members publicly, an anonymous or non-member user could enumerate the members of a group hidden from them, even though every other path that lists users by group (the user directory, `/g/:name/members`, group topic lists) already required both checks. This adds a `Guardian#can_see_group_and_members?` helper that requires both group and member visibility, and routes the user search, user directory, and plugin group-listing paths through it. This closes the leak, fixes a related all-or-nothing bug in `can_see_groups?` when several groups are filtered at once, and keeps the rule in one place so a caller can no longer reintroduce it by checking only half.
147 lines
4.1 KiB
Ruby
Vendored
147 lines
4.1 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
module Jobs
|
|
class DiscoursePostEventBulkInvite < ::Jobs::Base
|
|
sidekiq_options retry: false
|
|
|
|
def initialize
|
|
super
|
|
|
|
@logs = []
|
|
@processed = 0
|
|
@failed = 0
|
|
end
|
|
|
|
def execute(args)
|
|
invitees = args[:invitees]
|
|
raise Discourse::InvalidParameters.new(:invitees) if invitees.blank?
|
|
|
|
@current_user = User.find_by(id: args[:current_user_id])
|
|
raise Discourse::InvalidParameters.new(:current_user_id) unless @current_user
|
|
|
|
@event = DiscoursePostEvent::Event.find_by(id: args[:event_id])
|
|
raise Discourse::InvalidParameters.new(:event_id) unless @event
|
|
|
|
@guardian = Guardian.new(@current_user)
|
|
@guardian.ensure_can_edit!(@event.post)
|
|
|
|
process_invitees(invitees)
|
|
ensure
|
|
notify_user
|
|
end
|
|
|
|
private
|
|
|
|
def process_invitees(invitees)
|
|
invitees = invitees.map(&:with_indifferent_access)
|
|
invitees = filter_out_unavailable_groups(invitees)
|
|
|
|
max_bulk_invitees = SiteSetting.discourse_post_event_max_bulk_invitees
|
|
|
|
invitees.each do |invitee|
|
|
break if @processed >= max_bulk_invitees
|
|
process_invitee(invitee)
|
|
end
|
|
|
|
if @processed > 0
|
|
@event.publish_update!
|
|
@event.notify_invitees!(predefined_attendance: true)
|
|
end
|
|
rescue Exception => e
|
|
save_log "Bulk Invite Process Failed -- '#{e.message}'"
|
|
@failed += 1
|
|
end
|
|
|
|
def process_invitee(invitee)
|
|
if @event.public?
|
|
users = User.where(username_lower: invitee["identifier"].downcase).pluck(:id)
|
|
else
|
|
group = Group.find_by(name: invitee["identifier"])
|
|
if group
|
|
users = group.users.pluck(:id)
|
|
@event.update_with_params!(
|
|
raw_invitees: (@event.raw_invitees || []).push(group.name).uniq,
|
|
)
|
|
end
|
|
end
|
|
|
|
if users.blank?
|
|
save_log "Couldn't find user or group: '#{invitee["identifier"]}' or the groups provided contained no users. Note that public events can't bulk invite groups. And other events can't bulk invite usernames."
|
|
@failed += 1
|
|
return
|
|
end
|
|
|
|
attendance = invitee["attendance"] || "going"
|
|
status = DiscoursePostEvent::Invitee.statuses[attendance.to_sym]
|
|
|
|
if status.nil?
|
|
save_log "Skipping '#{invitee["identifier"]}' due to unknown attendance: '#{attendance}'"
|
|
@failed += 1
|
|
return
|
|
end
|
|
|
|
post_id = @event.post.id
|
|
|
|
users.each do |user_id|
|
|
# Respect capacity: skip creating new going when full
|
|
if attendance == "going" && @event.at_capacity?
|
|
save_log "Skipping '#{invitee["identifier"]}' due to max attendees reached"
|
|
@failed += 1
|
|
next
|
|
end
|
|
|
|
create_attendance(user_id, post_id, status)
|
|
end
|
|
|
|
@processed += 1
|
|
rescue Exception => e
|
|
save_log "Bulk Invite Process Failed -- '#{e.message}'"
|
|
@failed += 1
|
|
end
|
|
|
|
def create_attendance(user_id, post_id, status)
|
|
invitee =
|
|
DiscoursePostEvent::Invitee.find_or_initialize_by(user_id: user_id, post_id: post_id)
|
|
invitee.notified = false
|
|
invitee.status = status
|
|
invitee.save!
|
|
end
|
|
|
|
def save_log(message)
|
|
@logs << "[#{Time.zone.now}] #{message}"
|
|
end
|
|
|
|
def notify_user
|
|
if @current_user
|
|
if @processed > 0 && @failed == 0
|
|
SystemMessage.create_from_system_user(
|
|
@current_user,
|
|
:discourse_post_event_bulk_invite_succeeded,
|
|
processed: @processed,
|
|
)
|
|
else
|
|
SystemMessage.create_from_system_user(
|
|
@current_user,
|
|
:discourse_post_event_bulk_invite_failed,
|
|
processed: @processed,
|
|
failed: @failed,
|
|
logs: @logs.join("\n"),
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
def invitee_groups(invitees)
|
|
Group.where(name: invitees.map { |i| i[:identifier] })
|
|
end
|
|
|
|
def filter_out_unavailable_groups(invitees)
|
|
groups = invitee_groups(invitees)
|
|
invitees.filter do |i|
|
|
group = groups.find { |g| g.name === i[:identifier] }
|
|
|
|
!group || @guardian.can_see_group_and_members?(group)
|
|
end
|
|
end
|
|
end
|
|
end
|