mirror of
https://github.com/discourse/discourse.git
synced 2024-12-30 08:33:45 +08:00
80ac3275ba
- limits security key deletes to second factor keys - also deletes backup codes (lingering backup codes break login flow entirely) * Add spec for rake task to disable 2FA for a user
240 lines
7.1 KiB
Ruby
240 lines
7.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
desc "Change topic/post ownership of all the topics/posts by a specific user (without creating new revision)"
|
|
task "users:change_post_ownership",
|
|
%i[old_username new_username archetype] => [:environment] do |_, args|
|
|
old_username = args[:old_username]
|
|
new_username = args[:new_username]
|
|
archetype = args[:archetype]
|
|
archetype = archetype.downcase if archetype
|
|
|
|
if !old_username || !new_username
|
|
puts "ERROR: Expecting rake users:change_post_ownership[old_username,new_username,archetype]"
|
|
exit 1
|
|
end
|
|
|
|
old_user = find_user(old_username)
|
|
new_user = find_user(new_username)
|
|
|
|
if archetype == "private"
|
|
posts = Post.private_posts.where(user_id: old_user.id)
|
|
elsif archetype == "public" || !archetype
|
|
posts = Post.public_posts.where(user_id: old_user.id)
|
|
else
|
|
puts "ERROR: Expecting rake users:change_post_ownership[old_username,new_username,archetype] where archetype is public or private"
|
|
exit 1
|
|
end
|
|
|
|
puts "Changing post ownership"
|
|
i = 0
|
|
posts.each do |p|
|
|
PostOwnerChanger.new(
|
|
post_ids: [p.id],
|
|
topic_id: p.topic.id,
|
|
new_owner: User.find_by(username_lower: new_user.username_lower),
|
|
acting_user: User.find_by(username_lower: "system"),
|
|
skip_revision: true,
|
|
).change_owner!
|
|
putc "."
|
|
i += 1
|
|
end
|
|
puts "", "#{i} posts ownership changed!", ""
|
|
end
|
|
|
|
desc "Merge the source user into the target user"
|
|
task "users:merge", %i[source_username target_username] => [:environment] do |_, args|
|
|
source_username = args[:source_username]
|
|
target_username = args[:target_username]
|
|
|
|
if !source_username || !target_username
|
|
puts "ERROR: Expecting rake users:merge[source_username,target_username]"
|
|
exit 1
|
|
end
|
|
|
|
source_user = find_user(source_username)
|
|
target_user = find_user(target_username)
|
|
|
|
UserMerger.new(source_user, target_user).merge!
|
|
puts "", "Users merged!", ""
|
|
end
|
|
|
|
desc "Rename a user"
|
|
task "users:rename", %i[old_username new_username] => [:environment] do |_, args|
|
|
old_username = args[:old_username]
|
|
new_username = args[:new_username]
|
|
|
|
if !old_username || !new_username
|
|
puts "ERROR: Expecting rake users:rename[old_username,new_username]"
|
|
exit 1
|
|
end
|
|
|
|
changer = UsernameChanger.new(find_user(old_username), new_username)
|
|
changer.change(asynchronous: false)
|
|
puts "", "User renamed!", ""
|
|
end
|
|
|
|
desc "Update username in quotes and mentions. Use this if the user was renamed before proper renaming existed."
|
|
task "users:update_posts", %i[old_username current_username] => [:environment] do |_, args|
|
|
old_username = args[:old_username]
|
|
current_username = args[:current_username]
|
|
|
|
if !old_username || !current_username
|
|
puts "ERROR: Expecting rake users:update_posts[old_username,current_username]"
|
|
exit 1
|
|
end
|
|
|
|
user = find_user(current_username)
|
|
UsernameChanger.update_username(
|
|
user_id: user.id,
|
|
old_username: old_username,
|
|
new_username: user.username,
|
|
avatar_template: user.avatar_template,
|
|
asynchronous: false,
|
|
)
|
|
|
|
puts "", "Username updated!", ""
|
|
end
|
|
|
|
desc "Recalculate post and topic counts in user stats"
|
|
task "users:recalculate_post_counts" => :environment do
|
|
puts "", "Updating user stats..."
|
|
|
|
filter_public_posts_and_topics = <<~SQL
|
|
p.deleted_at IS NULL
|
|
AND NOT COALESCE(p.hidden, 't')
|
|
AND p.post_type = 1
|
|
AND t.deleted_at IS NULL
|
|
AND COALESCE(t.visible, 't')
|
|
AND t.archetype <> 'private_message'
|
|
AND p.user_id > 0
|
|
SQL
|
|
|
|
puts "post counts..."
|
|
|
|
# all public replies
|
|
DB.exec <<~SQL
|
|
WITH X AS (
|
|
SELECT p.user_id, COUNT(p.id) post_count
|
|
FROM posts p
|
|
JOIN topics t ON t.id = p.topic_id
|
|
WHERE #{filter_public_posts_and_topics}
|
|
AND p.post_number > 1
|
|
GROUP BY p.user_id
|
|
)
|
|
UPDATE user_stats
|
|
SET post_count = X.post_count
|
|
FROM X
|
|
WHERE user_stats.user_id = X.user_id
|
|
AND user_stats.post_count <> X.post_count
|
|
SQL
|
|
|
|
puts "topic counts..."
|
|
|
|
# public topics
|
|
DB.exec <<~SQL
|
|
WITH X AS (
|
|
SELECT p.user_id, COUNT(p.id) topic_count
|
|
FROM posts p
|
|
JOIN topics t ON t.id = p.topic_id
|
|
WHERE #{filter_public_posts_and_topics}
|
|
AND p.post_number = 1
|
|
GROUP BY p.user_id
|
|
)
|
|
UPDATE user_stats
|
|
SET topic_count = X.topic_count
|
|
FROM X
|
|
WHERE user_stats.user_id = X.user_id
|
|
AND user_stats.topic_count <> X.topic_count
|
|
SQL
|
|
|
|
puts "Done!", ""
|
|
end
|
|
|
|
desc "Disable 2FA for user with the given username"
|
|
task "users:disable_2fa", [:username] => [:environment] do |_, args|
|
|
username = args[:username]
|
|
user = find_user(username)
|
|
UserSecondFactor.where(user_id: user.id, method: UserSecondFactor.methods[:totp]).each(&:destroy!)
|
|
UserSecurityKey.where(
|
|
user_id: user.id,
|
|
factor_type: UserSecurityKey.factor_types[:second_factor],
|
|
).destroy_all
|
|
UserSecondFactor.where(user_id: user.id, method: UserSecondFactor.methods[:backup_codes]).each(
|
|
&:destroy!
|
|
)
|
|
puts "2FA disabled for #{username}"
|
|
end
|
|
|
|
desc "Anonymize all users except staff"
|
|
task "users:anonymize_all" => :environment do
|
|
require "highline/import"
|
|
|
|
non_staff_users = User.where("NOT admin AND NOT moderator")
|
|
total = non_staff_users.count
|
|
anonymized = 0
|
|
|
|
confirm_anonymize = ask("Are you sure you want to anonymize #{total} users? (Y/n)")
|
|
exit 1 unless (confirm_anonymize == "" || confirm_anonymize.downcase == "y")
|
|
|
|
system_user = Discourse.system_user
|
|
non_staff_users.each do |user|
|
|
begin
|
|
UserAnonymizer.new(user, system_user).make_anonymous
|
|
print_status(anonymized += 1, total)
|
|
rescue StandardError
|
|
# skip
|
|
end
|
|
end
|
|
|
|
puts "", "#{total} users anonymized.", ""
|
|
end
|
|
|
|
desc "Anonymize user with the given username"
|
|
task "users:anonymize", [:username] => [:environment] do |_, args|
|
|
username = args[:username]
|
|
user = find_user(username)
|
|
system_user = Discourse.system_user
|
|
UserAnonymizer.new(user, system_user).make_anonymous
|
|
puts "User #{username} anonymized"
|
|
end
|
|
|
|
desc "List all users which have been staff in the last month"
|
|
task "users:list_recent_staff" => :environment do
|
|
current_staff_ids = User.human_users.where("admin OR moderator").pluck(:id)
|
|
recent_actions = UserHistory.where("created_at > ?", 1.month.ago)
|
|
recent_admin_ids =
|
|
recent_actions.where(action: UserHistory.actions[:revoke_admin]).pluck(:target_user_id)
|
|
recent_moderator_ids =
|
|
recent_actions.where(action: UserHistory.actions[:revoke_moderation]).pluck(:target_user_id)
|
|
|
|
all_ids = current_staff_ids + recent_admin_ids + recent_moderator_ids
|
|
users = User.where(id: all_ids.uniq)
|
|
|
|
puts "Users which have had staff privileges in the last month:"
|
|
users.each { |user| puts "#{user.id}: #{user.username} (#{user.email})" }
|
|
puts "----"
|
|
puts "user_ids = [#{all_ids.uniq.join(",")}]"
|
|
end
|
|
|
|
desc "Check if a user exists for given email address"
|
|
task "users:exists", [:email] => [:environment] do |_, args|
|
|
email = args[:email]
|
|
if User.find_by_email(email)
|
|
puts "User with email #{email} exists"
|
|
exit 0
|
|
end
|
|
puts "ERROR: User with email #{email} not found"
|
|
exit 1
|
|
end
|
|
|
|
def find_user(username)
|
|
user = User.find_by_username(username)
|
|
|
|
if !user
|
|
puts "ERROR: User with username #{username} does not exist"
|
|
exit 1
|
|
end
|
|
|
|
user
|
|
end
|