summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2020-09-15 14:37:58 +0200
committerGitHub <noreply@github.com>2020-09-15 14:37:58 +0200
commited099d8bdc5b3d9e7df7ce5358441887e6bb7e48 (patch)
treee55ddfa97c0c9932e35c8ffd7cb59434084bd478
parentbbcbf12215a5ec69362a769c1bae9c630eda0ed4 (diff)
Change account suspensions to be reversible by default (#14726)
-rw-r--r--app/controllers/admin/accounts_controller.rb31
-rw-r--r--app/controllers/api/base_controller.rb4
-rw-r--r--app/controllers/api/v1/admin/accounts_controller.rb9
-rw-r--r--app/controllers/settings/deletes_controller.rb2
-rw-r--r--app/lib/activitypub/activity/delete.rb2
-rw-r--r--app/mailers/notification_mailer.rb16
-rw-r--r--app/mailers/user_mailer.rb28
-rw-r--r--app/models/account.rb9
-rw-r--r--app/models/account_deletion_request.rb20
-rw-r--r--app/models/admin/account_action.rb2
-rw-r--r--app/models/concerns/account_associations.rb3
-rw-r--r--app/models/form/account_batch.rb2
-rw-r--r--app/models/invite.rb2
-rw-r--r--app/models/user.rb4
-rw-r--r--app/policies/account_policy.rb4
-rw-r--r--app/services/after_unallow_domain_service.rb2
-rw-r--r--app/services/block_domain_service.rb2
-rw-r--r--app/services/delete_account_service.rb180
-rw-r--r--app/services/suspend_account_service.rb183
-rw-r--r--app/services/unsuspend_account_service.rb52
-rw-r--r--app/views/admin/accounts/show.html.haml114
-rw-r--r--app/workers/account_deletion_worker.rb13
-rw-r--r--app/workers/admin/account_deletion_worker.rb13
-rw-r--r--app/workers/admin/suspension_worker.rb6
-rw-r--r--app/workers/admin/unsuspension_worker.rb13
-rw-r--r--app/workers/scheduler/user_cleanup_scheduler.rb13
-rw-r--r--config/locales/en.yml31
-rw-r--r--config/locales/simple_form.en.yml8
-rw-r--r--config/routes.rb4
-rw-r--r--db/migrate/20200908193330_create_account_deletion_requests.rb8
-rw-r--r--db/schema.rb10
-rw-r--r--lib/mastodon/accounts_cli.rb4
-rw-r--r--lib/mastodon/domains_cli.rb2
-rw-r--r--spec/controllers/auth/registrations_controller_spec.rb3
-rw-r--r--spec/controllers/concerns/export_controller_concern_spec.rb1
-rw-r--r--spec/fabricators/account_deletion_request_fabricator.rb3
-rw-r--r--spec/models/account_deletion_request_spec.rb4
-rw-r--r--spec/models/invite_spec.rb2
-rw-r--r--spec/services/delete_account_service_spec.rb (renamed from spec/services/suspend_account_service_spec.rb)2
39 files changed, 529 insertions, 282 deletions
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index 7b178354299..b9b75727dd9 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -2,7 +2,7 @@
module Admin
class AccountsController < BaseController
- before_action :set_account, only: [:show, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject]
+ before_action :set_account, except: [:index]
before_action :require_remote_account!, only: [:redownload]
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
@@ -14,49 +14,58 @@ module Admin
def show
authorize @account, :show?
+ @deletion_request = @account.deletion_request
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
@moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.targeted_account_warnings.latest.custom
+ @domain_block = DomainBlock.rule_for(@account.domain)
end
def memorialize
authorize @account, :memorialize?
@account.memorialize!
log_action :memorialize, @account
- redirect_to admin_account_path(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.memorialized_msg', username: @account.acct)
end
def enable
authorize @account.user, :enable?
@account.user.enable!
log_action :enable, @account.user
- redirect_to admin_account_path(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.enabled_msg', username: @account.acct)
end
def approve
authorize @account.user, :approve?
@account.user.approve!
- redirect_to admin_pending_accounts_path
+ redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
end
def reject
authorize @account.user, :reject?
- SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
- redirect_to admin_pending_accounts_path
+ DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
+ redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
+ end
+
+ def destroy
+ authorize @account, :destroy?
+ Admin::AccountDeletionWorker.perform_async(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct)
end
def unsilence
authorize @account, :unsilence?
@account.unsilence!
log_action :unsilence, @account
- redirect_to admin_account_path(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsilenced_msg', username: @account.acct)
end
def unsuspend
authorize @account, :unsuspend?
@account.unsuspend!
+ Admin::UnsuspensionWorker.perform_async(@account.id)
log_action :unsuspend, @account
- redirect_to admin_account_path(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsuspended_msg', username: @account.acct)
end
def redownload
@@ -65,7 +74,7 @@ module Admin
@account.update!(last_webfingered_at: nil)
ResolveAccountService.new.call(@account)
- redirect_to admin_account_path(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.redownloaded_msg', username: @account.acct)
end
def remove_avatar
@@ -76,7 +85,7 @@ module Admin
log_action :remove_avatar, @account.user
- redirect_to admin_account_path(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_avatar_msg', username: @account.acct)
end
def remove_header
@@ -87,7 +96,7 @@ module Admin
log_action :remove_header, @account.user
- redirect_to admin_account_path(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_header_msg', username: @account.acct)
end
private
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index 4672255475f..e962c4e97f6 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -96,12 +96,12 @@ class Api::BaseController < ApplicationController
def require_user!
if !current_user
render json: { error: 'This method requires an authenticated user' }, status: 422
- elsif current_user.disabled?
- render json: { error: 'Your login is currently disabled' }, status: 403
elsif !current_user.confirmed?
render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
elsif !current_user.approved?
render json: { error: 'Your login is currently pending approval' }, status: 403
+ elsif !current_user.functional?
+ render json: { error: 'Your login is currently disabled' }, status: 403
else
set_user_activity
end
diff --git a/app/controllers/api/v1/admin/accounts_controller.rb b/app/controllers/api/v1/admin/accounts_controller.rb
index 24c7fbef12f..3af572f25ef 100644
--- a/app/controllers/api/v1/admin/accounts_controller.rb
+++ b/app/controllers/api/v1/admin/accounts_controller.rb
@@ -58,7 +58,13 @@ class Api::V1::Admin::AccountsController < Api::BaseController
def reject
authorize @account.user, :reject?
- SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
+ DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
+ render json: @account, serializer: REST::Admin::AccountSerializer
+ end
+
+ def destroy
+ authorize @account, :destroy?
+ Admin::AccountDeletionWorker.perform_async(@account.id)
render json: @account, serializer: REST::Admin::AccountSerializer
end
@@ -72,6 +78,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
def unsuspend
authorize @account, :unsuspend?
@account.unsuspend!
+ Admin::UnsuspensionWorker.perform_async(@account.id)
log_action :unsuspend, @account
render json: @account, serializer: REST::Admin::AccountSerializer
end
diff --git a/app/controllers/settings/deletes_controller.rb b/app/controllers/settings/deletes_controller.rb
index 7d4844e60f1..f96c83b8025 100644
--- a/app/controllers/settings/deletes_controller.rb
+++ b/app/controllers/settings/deletes_controller.rb
@@ -43,7 +43,7 @@ class Settings::DeletesController < Settings::BaseController
def destroy_account!
current_account.suspend!
- Admin::SuspensionWorker.perform_async(current_user.account_id, true)
+ AccountDeletionWorker.perform_async(current_user.account_id)
sign_out
end
end
diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb
index dc9ff580c1a..09b9e5e0e83 100644
--- a/app/lib/activitypub/activity/delete.rb
+++ b/app/lib/activitypub/activity/delete.rb
@@ -13,7 +13,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
def delete_person
lock_or_return("delete_in_progress:#{@account.id}") do
- SuspendAccountService.new.call(@account, reserve_username: false)
+ DeleteAccountService.new.call(@account, reserve_username: false)
end
end
diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb
index 9d8a7886c27..54db892ccc6 100644
--- a/app/mailers/notification_mailer.rb
+++ b/app/mailers/notification_mailer.rb
@@ -10,7 +10,7 @@ class NotificationMailer < ApplicationMailer
@me = recipient
@status = notification.target_status
- return if @me.user.disabled? || @status.nil?
+ return unless @me.user.functional? && @status.present?
locale_for_account(@me) do
thread_by_conversation(@status.conversation)
@@ -22,7 +22,7 @@ class NotificationMailer < ApplicationMailer
@me = recipient
@account = notification.from_account
- return if @me.user.disabled?
+ return unless @me.user.functional?
locale_for_account(@me) do
mail to: @me.user.email, subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
@@ -34,7 +34,7 @@ class NotificationMailer < ApplicationMailer
@account = notification.from_account
@status = notification.target_status
- return if @me.user.disabled? || @status.nil?
+ return unless @me.user.functional? && @status.present?
locale_for_account(@me) do
thread_by_conversation(@status.conversation)
@@ -47,7 +47,7 @@ class NotificationMailer < ApplicationMailer
@account = notification.from_account
@status = notification.target_status
- return if @me.user.disabled? || @status.nil?
+ return unless @me.user.functional? && @status.present?
locale_for_account(@me) do
thread_by_conversation(@status.conversation)
@@ -59,7 +59,7 @@ class NotificationMailer < ApplicationMailer
@me = recipient
@account = notification.from_account
- return if @me.user.disabled?
+ return unless @me.user.functional?
locale_for_account(@me) do
mail to: @me.user.email, subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct)
@@ -67,7 +67,7 @@ class NotificationMailer < ApplicationMailer
end
def digest(recipient, **opts)
- return if recipient.user.disabled?
+ return unless recipient.user.functional?
@me = recipient
@since = opts[:since] || [@me.user.last_emailed_at, (@me.user.current_sign_in_at + 1.day)].compact.max
@@ -88,8 +88,10 @@ class NotificationMailer < ApplicationMailer
def thread_by_conversation(conversation)
return if conversation.nil?
+
msg_id = "<conversation-#{conversation.id}.#{conversation.created_at.strftime('%Y-%m-%d')}@#{Rails.configuration.x.local_domain}>"
+
headers['In-Reply-To'] = msg_id
- headers['References'] = msg_id
+ headers['References'] = msg_id
end
end
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index b557685516c..95996ba3ff9 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -15,7 +15,7 @@ class UserMailer < Devise::Mailer
@token = token
@instance = Rails.configuration.x.local_domain
- return if @resource.disabled?
+ return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.unconfirmed_email.presence || @resource.email,
@@ -29,7 +29,7 @@ class UserMailer < Devise::Mailer
@token = token
@instance = Rails.configuration.x.local_domain
- return if @resource.disabled?
+ return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.reset_password_instructions.subject')
@@ -40,7 +40,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
- return if @resource.disabled?
+ return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject')
@@ -51,7 +51,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
- return if @resource.disabled?
+ return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.email_changed.subject')
@@ -62,7 +62,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
- return if @resource.disabled?
+ return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_enabled.subject')
@@ -73,7 +73,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
- return if @resource.disabled?
+ return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_disabled.subject')
@@ -84,7 +84,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
- return if @resource.disabled?
+ return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject')
@@ -95,7 +95,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
- return if @resource.disabled?
+ return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_enabled.subject')
@@ -106,7 +106,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
- return if @resource.disabled?
+ return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_disabled.subject')
@@ -118,7 +118,7 @@ class UserMailer < Devise::Mailer
@instance = Rails.configuration.x.local_domain
@webauthn_credential = webauthn_credential
- return if @resource.disabled?
+ return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_credential.added.subject')
@@ -130,7 +130,7 @@ class UserMailer < Devise::Mailer
@instance = Rails.configuration.x.local_domain
@webauthn_credential = webauthn_credential
- return if @resource.disabled?
+ return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_credential.deleted.subject')
@@ -141,7 +141,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
- return if @resource.disabled?
+ return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('user_mailer.welcome.subject')
@@ -153,7 +153,7 @@ class UserMailer < Devise::Mailer
@instance = Rails.configuration.x.local_domain
@backup = backup
- return if @resource.disabled?
+ return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('user_mailer.backup_ready.subject')
@@ -181,7 +181,7 @@ class UserMailer < Devise::Mailer
@detection = Browser.new(user_agent)
@timestamp = timestamp.to_time.utc
- return if @resource.disabled?
+ return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email,
diff --git a/app/models/account.rb b/app/models/account.rb
index 6b7ebda9e67..5acc8d621cb 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -222,23 +222,20 @@ class Account < ApplicationRecord
def suspend!(date = Time.now.utc)
transaction do
- user&.disable! if local?
+ create_deletion_request!
update!(suspended_at: date)
end
end
def unsuspend!
transaction do
- user&.enable! if local?
+ deletion_request&.destroy!
update!(suspended_at: nil)
end
end
def memorialize!
- transaction do
- user&.disable! if local?
- update!(memorial: true)
- end
+ update!(memorial: true)
end
def sign?
diff --git a/app/models/account_deletion_request.rb b/app/models/account_deletion_request.rb
new file mode 100644
index 00000000000..7d0c346cc27
--- /dev/null
+++ b/app/models/account_deletion_request.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: account_deletion_requests
+#
+# id :bigint(8) not null, primary key
+# account_id :bigint(8)
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+class AccountDeletionRequest < ApplicationRecord
+ DELAY_TO_DELETION = 30.days.freeze
+
+ belongs_to :account
+
+ def due_at
+ created_at + DELAY_TO_DELETION
+ end
+end
diff --git a/app/models/admin/account_action.rb b/app/models/admin/account_action.rb
index 9edd152f57b..c4ac09520ef 100644
--- a/app/models/admin/account_action.rb
+++ b/app/models/admin/account_action.rb
@@ -134,7 +134,7 @@ class Admin::AccountAction
end
def process_email!
- UserMailer.warning(target_account.user, warning, status_ids).deliver_now! if warnable?
+ UserMailer.warning(target_account.user, warning, status_ids).deliver_later! if warnable?
end
def warnable?
diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb
index cca3a17fa41..98849f8fcfa 100644
--- a/app/models/concerns/account_associations.rb
+++ b/app/models/concerns/account_associations.rb
@@ -60,5 +60,8 @@ module AccountAssociations
# Hashtags
has_and_belongs_to_many :tags
has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account
+
+ # Account deletion requests
+ has_one :deletion_request, class_name: 'AccountDeletionRequest', inverse_of: :account, dependent: :destroy
end
end
diff --git a/app/models/form/account_batch.rb b/app/models/form/account_batch.rb
index 0b285fde92c..7b9e40f6855 100644
--- a/app/models/form/account_batch.rb
+++ b/app/models/form/account_batch.rb
@@ -69,6 +69,6 @@ class Form::AccountBatch
records = accounts.includes(:user)
records.each { |account| authorize(account.user, :reject?) }
- .each { |account| SuspendAccountService.new.call(account, reserve_email: false, reserve_username: false) }
+ .each { |account| DeleteAccountService.new.call(account, reserve_email: false, reserve_username: false) }
end
end
diff --git a/app/models/invite.rb b/app/models/invite.rb
index 29d25eae801..7ea4e2f9841 100644
--- a/app/models/invite.rb
+++ b/app/mode