diff --git a/app/controllers/maintenance/user/deletions_controller.rb b/app/controllers/maintenance/user/deletions_controller.rb index be24e1830..f029066b6 100644 --- a/app/controllers/maintenance/user/deletions_controller.rb +++ b/app/controllers/maintenance/user/deletions_controller.rb @@ -9,7 +9,7 @@ module Maintenance end def destroy - deletion = UserDeletion.new(CurrentUser.user, params.dig(:user, :password), request) + deletion = UserDeletion.new(user: CurrentUser.user, deleter: CurrentUser.user, password: params.dig(:user, :password), request: request) deletion.delete! if deletion.errors.none? diff --git a/app/logical/user_deletion.rb b/app/logical/user_deletion.rb index ea61c49bf..7c1fb9000 100644 --- a/app/logical/user_deletion.rb +++ b/app/logical/user_deletion.rb @@ -7,16 +7,18 @@ class UserDeletion include ActiveModel::Validations - attr_reader :user, :password, :request + attr_reader :user, :deleter, :password, :request validate :validate_deletion # Initialize a user deletion. # @param user [User] the user to delete + # @param user [User] the user performing the deletion # @param password [String] the user's password (for confirmation) # @param request the HTTP request (for logging the deletion in the user event log) - def initialize(user, password, request) + def initialize(user:, deleter: user, password: nil, request: nil) @user = user + @deleter = deleter @password = password @request = request end @@ -40,11 +42,11 @@ class UserDeletion private def create_mod_action - ModAction.log("deleted user ##{user.id}", :user_delete, user) + ModAction.log("deleted user ##{user.id}", :user_delete, deleter) end def create_user_event - UserEvent.create_from_request!(user, :user_deletion, request) + UserEvent.create_from_request!(user, :user_deletion, request) if request.present? end def clear_saved_searches @@ -79,16 +81,30 @@ class UserDeletion end def validate_deletion - if !user.authenticate_password(password) - errors.add(:base, "Password is incorrect") - end + if user == deleter + if !user.authenticate_password(password) + errors.add(:base, "Password is incorrect") + end - if user.is_admin? - errors.add(:base, "Admins cannot delete their account") - end + if user.is_admin? + errors.add(:base, "Admins cannot delete their account") + end - if user.is_banned? - errors.add(:base, "You cannot delete your account if you are banned") + if user.is_banned? + errors.add(:base, "You cannot delete your account if you are banned") + end + else + if !deleter.is_owner? + errors.add(:base, "You cannot delete an account belonging to another user") + end + + if user.is_gold? + errors.add(:base, "You cannot delete a privileged account") + end + + if user.created_at.before?(6.months.ago) + errors.add(:base, "You cannot delete a recent account") + end end end end diff --git a/script/fixes/115_delete_invalid_users.rb b/script/fixes/115_delete_invalid_users.rb new file mode 100755 index 000000000..9d987ec35 --- /dev/null +++ b/script/fixes/115_delete_invalid_users.rb @@ -0,0 +1,57 @@ +#!/usr/bin/env ruby + +require_relative "base" + +def delete(user) + return if !user.name_invalid? + + if ENV.fetch("WARN", "false").truthy? && user.can_receive_email? + Dmail.create_automated(to: user, title: "Action required: Change your username or your Danbooru account will be deleted", body: <<~EOS) + Your current Danbooru username is invalid. Your Danbooru account will be deleted in one week unless you change your username. Use the link below to change your username: + + * "Change username":/user_name_change_requests/new + EOS + + puts "[WARN] id=#{user.id} user='#{user.name}' email='#{user.email_address.address}'" + elsif ENV.fetch("DELETE", "false").truthy? + UserDeletion.new(user: user, deleter: User.owner).delete! + puts "[DELETE] id=#{user.id} user='#{user.name}'" + end +end + +with_confirmation do + condition = ENV.fetch("COND", "TRUE") + users = User.where(Arel.sql(condition)) + + users.where("length(name) = 1").find_each do |user| + delete(user) + end + + users.where("length(name) >= 25").find_each do |user| + delete(user) + end + + users.where_regex(:name, "[[:space:]]").find_each do |user| + delete(user) + end + + users.where_regex(:name, "^[[:punct:]]").find_each do |user| + delete(user) + end + + users.where_regex(:name, "[[:punct:]]$").find_each do |user| + delete(user) + end + + users.where_regex(:name, "\.(html|json|xml|atom|rss|txt|js|css|csv|png|jpg|jpeg|gif|png|mp4|webm|zip|pdf|exe|sitemap)$").find_each do |user| + delete(user) + end + + users.where_regex(:name, "[`~!@#$%^&*()+={}\[\]|\\:;'\"<>,?/]").find_each do |user| + delete(user) + end + + users.where_not_regex(:name, "[[:ascii:]]").find_each do |user| + delete(user) + end +end diff --git a/test/unit/user_deletion_test.rb b/test/unit/user_deletion_test.rb index 298a472cb..617211c81 100644 --- a/test/unit/user_deletion_test.rb +++ b/test/unit/user_deletion_test.rb @@ -12,7 +12,7 @@ class UserDeletionTest < ActiveSupport::TestCase context "for an invalid password" do should "fail" do @user = create(:user) - @deletion = UserDeletion.new(@user, "wrongpassword", @request) + @deletion = UserDeletion.new(user: @user, password: "wrongpassword", request: @request) @deletion.delete! assert_includes(@deletion.errors[:base], "Password is incorrect") end @@ -21,7 +21,7 @@ class UserDeletionTest < ActiveSupport::TestCase context "for an admin" do should "fail" do @user = create(:admin_user) - @deletion = UserDeletion.new(@user, "password", @request) + @deletion = UserDeletion.new(user: @user, password: "password", request: @request) @deletion.delete! assert_includes(@deletion.errors[:base], "Admins cannot delete their account") end @@ -30,7 +30,7 @@ class UserDeletionTest < ActiveSupport::TestCase context "for a banned user" do should "fail" do @user = create(:banned_user) - @deletion = UserDeletion.new(@user, "password", @request) + @deletion = UserDeletion.new(user: @user, password: "password", request: @request) @deletion.delete! assert_includes(@deletion.errors[:base], "You cannot delete your account if you are banned") end @@ -40,7 +40,7 @@ class UserDeletionTest < ActiveSupport::TestCase context "a valid user deletion" do setup do @user = create(:user, name: "foo", email_address: build(:email_address)) - @deletion = UserDeletion.new(@user, "password", @request) + @deletion = UserDeletion.new(user: @user, password: "password", request: @request) end should "blank out the email" do @@ -82,4 +82,24 @@ class UserDeletionTest < ActiveSupport::TestCase assert_equal(0, @post.reload.fav_count) end end + + context "deleting another user's account" do + should "work for the owner-level user" do + @user = create(:user) + @deletion = UserDeletion.new(user: @user, deleter: create(:owner_user)) + + @deletion.delete! + assert_equal("user_#{@user.id}", @user.reload.name) + assert_equal(true, ModAction.exists?(description: "deleted user ##{@user.id}", creator: @deletion.deleter)) + end + + should "not work for other users" do + @user = create(:user) + @deletion = UserDeletion.new(user: @user, deleter: create(:admin_user)) + + @deletion.delete! + assert_not_equal("user_#{@user.id}", @user.reload.name) + assert_equal(0, ModAction.count) + end + end end