ソースを参照

Add ability for admins to delete canonical email blocks (#16644)

* Add admin option to remove canonical email blocks from a deleted account

* Add tootctl canonical_email_blocks to inspect and remove canonical email blocks
Claire 2 年 前
コミット
76761d5fc0

+ 10 - 0
app/controllers/admin/accounts_controller.rb

@@ -117,6 +117,16 @@ module Admin
       redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_header_msg', username: @account.acct)
     end
 
+    def unblock_email
+      authorize @account, :unblock_email?
+
+      CanonicalEmailBlock.where(reference_account: @account).delete_all
+
+      log_action :unblock_email, @account
+
+      redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unblocked_email_msg', username: @account.acct)
+    end
+
     private
 
     def set_account

+ 1 - 0
app/models/admin/action_log_filter.rb

@@ -50,6 +50,7 @@ class Admin::ActionLogFilter
     update_announcement: { target_type: 'Announcement', action: 'update' }.freeze,
     update_custom_emoji: { target_type: 'CustomEmoji', action: 'update' }.freeze,
     update_status: { target_type: 'Status', action: 'update' }.freeze,
+    unblock_email_account: { target_type: 'Account', action: 'unblock_email' }.freeze,
   }.freeze
 
   attr_reader :params

+ 4 - 0
app/models/canonical_email_block.rb

@@ -24,4 +24,8 @@ class CanonicalEmailBlock < ApplicationRecord
   def self.block?(email)
     where(canonical_email_hash: email_to_canonical_email_hash(email)).exists?
   end
+
+  def self.find_blocks(email)
+    where(canonical_email_hash: email_to_canonical_email_hash(email))
+  end
 end

+ 4 - 0
app/policies/account_policy.rb

@@ -64,4 +64,8 @@ class AccountPolicy < ApplicationPolicy
   def memorialize?
     admin? && !record.user&.admin? && !record.instance_actor?
   end
+
+  def unblock_email?
+    staff?
+  end
 end

+ 3 - 1
app/views/admin/accounts/show.html.haml

@@ -71,7 +71,9 @@
           = t('admin.accounts.no_limits_imposed')
       .dashboard__counters__label= t 'admin.accounts.login_status'
 
-- unless @account.local? && @account.user.nil?
+- if @account.local? && @account.user.nil?
+  = link_to t('admin.accounts.unblock_email'), unblock_email_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unblock_email, @account) && CanonicalEmailBlock.where(reference_account_id: @account.id).exists?
+- else
   .table-wrapper
     %table.table.inline-table
       %tbody

+ 4 - 0
config/locales/en.yml

@@ -208,6 +208,8 @@ en:
       suspension_irreversible: The data of this account has been irreversibly deleted. You can unsuspend the account to make it usable but it will not recover any data it previously had.
       suspension_reversible_hint_html: The account has been suspended, and the data will be fully removed on %{date}. Until then, the account can be restored without any ill effects. If you wish to remove all of the account's data immediately, you can do so below.
       title: Accounts
+      unblock_email: Unblock email address
+      unblocked_email_msg: Successfully unblocked %{username}'s email address
       unconfirmed_email: Unconfirmed email
       undo_sensitized: Undo force-sensitive
       undo_silenced: Undo limit
@@ -262,6 +264,7 @@ en:
         silence_account: Limit Account
         suspend_account: Suspend Account
         unassigned_report: Unassign Report
+        unblock_email_account: Unblock email address
         unsensitive_account: Undo Force-Sensitive Account
         unsilence_account: Undo Limit Account
         unsuspend_account: Unsuspend Account
@@ -310,6 +313,7 @@ en:
         silence_account_html: "%{name} limited %{target}'s account"
         suspend_account_html: "%{name} suspended %{target}'s account"
         unassigned_report_html: "%{name} unassigned report %{target}"
+        unblock_email_account_html: "%{name} unblocked %{target}'s email address"
         unsensitive_account_html: "%{name} unmarked %{target}'s media as sensitive"
         unsilence_account_html: "%{name} undid limit of %{target}'s account"
         unsuspend_account_html: "%{name} unsuspended %{target}'s account"

+ 1 - 0
config/routes.rb

@@ -249,6 +249,7 @@ Rails.application.routes.draw do
         post :memorialize
         post :approve
         post :reject
+        post :unblock_email
       end
 
       collection do

+ 4 - 0
lib/cli.rb

@@ -13,6 +13,7 @@ require_relative 'mastodon/preview_cards_cli'
 require_relative 'mastodon/cache_cli'
 require_relative 'mastodon/upgrade_cli'
 require_relative 'mastodon/email_domain_blocks_cli'
+require_relative 'mastodon/canonical_email_blocks_cli'
 require_relative 'mastodon/ip_blocks_cli'
 require_relative 'mastodon/maintenance_cli'
 require_relative 'mastodon/version'
@@ -62,6 +63,9 @@ module Mastodon
     desc 'ip_blocks SUBCOMMAND ...ARGS', 'Manage IP blocks'
     subcommand 'ip_blocks', Mastodon::IpBlocksCLI
 
+    desc 'canonical_email_blocks SUBCOMMAND ...ARGS', 'Manage canonical e-mail blocks'
+    subcommand 'canonical_email_blocks', Mastodon::CanonicalEmailBlocksCLI
+
     desc 'maintenance SUBCOMMAND ...ARGS', 'Various maintenance utilities'
     subcommand 'maintenance', Mastodon::MaintenanceCLI
 

+ 64 - 0
lib/mastodon/canonical_email_blocks_cli.rb

@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'concurrent'
+require_relative '../../config/boot'
+require_relative '../../config/environment'
+require_relative 'cli_helper'
+
+module Mastodon
+  class CanonicalEmailBlocksCLI < Thor
+    include CLIHelper
+
+    def self.exit_on_failure?
+      true
+    end
+
+    desc 'find EMAIL', 'Find a given e-mail address in the canonical e-mail blocks'
+    long_desc <<-LONG_DESC
+      When suspending a local user, a hash of a "canonical" version of their e-mail
+      address is stored to prevent them from signing up again.
+
+      This command can be used to find whether a known email address is blocked,
+      and if so, which account it was attached to.
+    LONG_DESC
+    def find(email)
+      accts = CanonicalEmailBlock.find_blocks(email).map(&:reference_account).map(&:acct).to_a
+      if accts.empty?
+        say("#{email} is not blocked", :yellow)
+      else
+        accts.each do |acct|
+          say(acct, :white)
+        end
+      end
+    end
+
+    desc 'remove EMAIL', 'Remove a canonical e-mail block'
+    long_desc <<-LONG_DESC
+      When suspending a local user, a hash of a "canonical" version of their e-mail
+      address is stored to prevent them from signing up again.
+
+      This command allows removing a canonical email block.
+    LONG_DESC
+    def remove(email)
+      blocks = CanonicalEmailBlock.find_blocks(email)
+      if blocks.empty?
+        say("#{email} is not blocked", :yellow)
+      else
+        blocks.destroy_all
+        say("Removed canonical email block for #{email}", :green)
+      end
+    end
+
+    private
+
+    def color(processed, failed)
+      if !processed.zero? && failed.zero?
+        :green
+      elsif failed.zero?
+        :yellow
+      else
+        :red
+      end
+    end
+  end
+end

+ 32 - 0
spec/controllers/admin/accounts_controller_spec.rb

@@ -192,4 +192,36 @@ RSpec.describe Admin::AccountsController, type: :controller do
       end
     end
   end
+
+  describe 'POST #unblock_email' do
+    subject do
+      -> { post :unblock_email, params: { id: account.id } }
+    end
+
+    let(:current_user) { Fabricate(:user, admin: admin) }
+    let(:account) { Fabricate(:account, suspended: true) }
+    let!(:email_block) { Fabricate(:canonical_email_block, reference_account: account) }
+
+    context 'when user is admin' do
+      let(:admin) { true }
+
+      it 'succeeds in removing email blocks' do
+        is_expected.to change { CanonicalEmailBlock.where(reference_account: account).count }.from(1).to(0)
+      end
+
+      it 'redirects to admin account path' do
+        subject.call
+        expect(response).to redirect_to admin_account_path(account.id)
+      end
+    end
+
+    context 'when user is not admin' do
+      let(:admin) { false }
+
+      it 'fails to remove avatar' do
+        subject.call
+        expect(response).to have_http_status :forbidden
+      end
+    end
+  end
 end

+ 1 - 1
spec/policies/account_policy_spec.rb

@@ -37,7 +37,7 @@ RSpec.describe AccountPolicy do
     end
   end
 
-  permissions :unsuspend? do
+  permissions :unsuspend?, :unblock_email? do
     before do
       alice.suspend!
     end