From 3ed94dcc1acf73f1d0d1ab43567b88ee953f57c9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 19 Sep 2019 20:58:19 +0200 Subject: Add account migration UI (#11846) Fix #10736 - Change data export to be available for non-functional accounts - Change non-functional accounts to include redirecting accounts --- app/controllers/settings/migrations_controller.rb | 48 ++++++++++++++++++----- 1 file changed, 38 insertions(+), 10 deletions(-) (limited to 'app/controllers/settings/migrations_controller.rb') diff --git a/app/controllers/settings/migrations_controller.rb b/app/controllers/settings/migrations_controller.rb index 59eb48779..90092c692 100644 --- a/app/controllers/settings/migrations_controller.rb +++ b/app/controllers/settings/migrations_controller.rb @@ -4,31 +4,59 @@ class Settings::MigrationsController < Settings::BaseController layout 'admin' before_action :authenticate_user! + before_action :require_not_suspended! + before_action :set_migrations + before_action :set_cooldown + + skip_before_action :require_functional! def show - @migration = Form::Migration.new(account: current_account.moved_to_account) + @migration = current_account.migrations.build end - def update - @migration = Form::Migration.new(resource_params) + def create + @migration = current_account.migrations.build(resource_params) - if @migration.valid? && migration_account_changed? - current_account.update!(moved_to_account: @migration.account) + if @migration.save_with_challenge(current_user) + current_account.update!(moved_to_account: @migration.target_account) ActivityPub::UpdateDistributionWorker.perform_async(current_account.id) - redirect_to settings_migration_path, notice: I18n.t('migrations.updated_msg') + ActivityPub::MoveDistributionWorker.perform_async(@migration.id) + redirect_to settings_migration_path, notice: I18n.t('migrations.moved_msg', acct: current_account.moved_to_account.acct) else render :show end end + def cancel + if current_account.moved_to_account_id.present? + current_account.update!(moved_to_account: nil) + ActivityPub::UpdateDistributionWorker.perform_async(current_account.id) + end + + redirect_to settings_migration_path, notice: I18n.t('migrations.cancelled_msg') + end + + helper_method :on_cooldown? + private def resource_params - params.require(:migration).permit(:acct) + params.require(:account_migration).permit(:acct, :current_password, :current_username) + end + + def set_migrations + @migrations = current_account.migrations.includes(:target_account).order(id: :desc).reject(&:new_record?) + end + + def set_cooldown + @cooldown = current_account.migrations.within_cooldown.first + end + + def on_cooldown? + @cooldown.present? end - def migration_account_changed? - current_account.moved_to_account_id != @migration.account&.id && - current_account.id != @migration.account&.id + def require_not_suspended! + forbidden if current_account.suspended? end end -- cgit From 163ed91af381d86bb6c52546c983effa4c9a18c3 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 29 Sep 2019 05:03:19 +0200 Subject: Add (back) option to set redirect notice on account without moving followers (#11994) Fix #11913 --- .../settings/migration/redirects_controller.rb | 45 +++++++++++++++++++++ app/controllers/settings/migrations_controller.rb | 9 ----- app/models/account_migration.rb | 3 +- app/models/form/redirect.rb | 47 ++++++++++++++++++++++ app/views/auth/registrations/edit.html.haml | 11 +++++ .../settings/migration/redirects/new.html.haml | 27 +++++++++++++ app/views/settings/migrations/show.html.haml | 10 +++-- config/locales/en.yml | 3 ++ config/routes.rb | 7 ++-- 9 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 app/controllers/settings/migration/redirects_controller.rb create mode 100644 app/models/form/redirect.rb create mode 100644 app/views/settings/migration/redirects/new.html.haml (limited to 'app/controllers/settings/migrations_controller.rb') diff --git a/app/controllers/settings/migration/redirects_controller.rb b/app/controllers/settings/migration/redirects_controller.rb new file mode 100644 index 000000000..6e5b72ffb --- /dev/null +++ b/app/controllers/settings/migration/redirects_controller.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +class Settings::Migration::RedirectsController < Settings::BaseController + layout 'admin' + + before_action :authenticate_user! + before_action :require_not_suspended! + + skip_before_action :require_functional! + + def new + @redirect = Form::Redirect.new + end + + def create + @redirect = Form::Redirect.new(resource_params.merge(account: current_account)) + + if @redirect.valid_with_challenge?(current_user) + current_account.update!(moved_to_account: @redirect.target_account) + ActivityPub::UpdateDistributionWorker.perform_async(current_account.id) + redirect_to settings_migration_path, notice: I18n.t('migrations.moved_msg', acct: current_account.moved_to_account.acct) + else + render :new + end + end + + def destroy + if current_account.moved_to_account_id.present? + current_account.update!(moved_to_account: nil) + ActivityPub::UpdateDistributionWorker.perform_async(current_account.id) + end + + redirect_to settings_migration_path, notice: I18n.t('migrations.cancelled_msg') + end + + private + + def resource_params + params.require(:form_redirect).permit(:acct, :current_password, :current_username) + end + + def require_not_suspended! + forbidden if current_account.suspended? + end +end diff --git a/app/controllers/settings/migrations_controller.rb b/app/controllers/settings/migrations_controller.rb index 90092c692..00bde1d61 100644 --- a/app/controllers/settings/migrations_controller.rb +++ b/app/controllers/settings/migrations_controller.rb @@ -27,15 +27,6 @@ class Settings::MigrationsController < Settings::BaseController end end - def cancel - if current_account.moved_to_account_id.present? - current_account.update!(moved_to_account: nil) - ActivityPub::UpdateDistributionWorker.perform_async(current_account.id) - end - - redirect_to settings_migration_path, notice: I18n.t('migrations.cancelled_msg') - end - helper_method :on_cooldown? private diff --git a/app/models/account_migration.rb b/app/models/account_migration.rb index e2c2cb085..681b5b2cd 100644 --- a/app/models/account_migration.rb +++ b/app/models/account_migration.rb @@ -47,8 +47,7 @@ class AccountMigration < ApplicationRecord end def acct=(val) - val = val.to_s.strip - super(val.start_with?('@') ? val[1..-1] : val) + super(val.to_s.strip.gsub(/\A@/, '')) end private diff --git a/app/models/form/redirect.rb b/app/models/form/redirect.rb new file mode 100644 index 000000000..a7961f8e8 --- /dev/null +++ b/app/models/form/redirect.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class Form::Redirect + include ActiveModel::Model + + attr_accessor :account, :target_account, :current_password, + :current_username + + attr_reader :acct + + validates :acct, presence: true, domain: { acct: true } + validate :validate_target_account + + def valid_with_challenge?(current_user) + if current_user.encrypted_password.present? + errors.add(:current_password, :invalid) unless current_user.valid_password?(current_password) + else + errors.add(:current_username, :invalid) unless account.username == current_username + end + + return false unless errors.empty? + + set_target_account + valid? + end + + def acct=(val) + @acct = val.to_s.strip.gsub(/\A@/, '') + end + + private + + def set_target_account + @target_account = ResolveAccountService.new.call(acct) + rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error + # Validation will take care of it + end + + def validate_target_account + if target_account.nil? + errors.add(:acct, I18n.t('migrations.errors.not_found')) + else + errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved_to_account_id.present? && account.moved_to_account_id == target_account.id + errors.add(:acct, I18n.t('migrations.errors.move_to_self')) if account.id == target_account.id + end + end +end diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml index 885171c58..a155c75c9 100644 --- a/app/views/auth/registrations/edit.html.haml +++ b/app/views/auth/registrations/edit.html.haml @@ -30,7 +30,18 @@ = render 'sessions' +%hr.spacer/ + +%h3= t('auth.migrate_account') +%p.muted-hint= t('auth.migrate_account_html', path: settings_migration_path) + +%hr.spacer/ + +%h3= t('migrations.incoming_migrations') +%p.muted-hint= t('migrations.incoming_migrations_html', path: settings_aliases_path) + - if open_deletion? && !current_account.suspended? %hr.spacer/ + %h3= t('auth.delete_account') %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path) diff --git a/app/views/settings/migration/redirects/new.html.haml b/app/views/settings/migration/redirects/new.html.haml new file mode 100644 index 000000000..017450f4b --- /dev/null +++ b/app/views/settings/migration/redirects/new.html.haml @@ -0,0 +1,27 @@ +- content_for :page_title do + = t('settings.migrate') + += simple_form_for @redirect, url: settings_migration_redirect_path do |f| + %p.hint= t('migrations.warning.before') + + %ul.hint + %li.warning-hint= t('migrations.warning.redirect') + %li.warning-hint= t('migrations.warning.other_data') + %li.warning-hint= t('migrations.warning.disabled_account') + + %hr.spacer/ + + = render 'shared/error_messages', object: @redirect + + .fields-row + .fields-row__column.fields-group.fields-row__column-6 + = f.input :acct, wrapper: :with_block_label, input_html: { autocapitalize: 'none', autocorrect: 'off' }, label: t('simple_form.labels.account_migration.acct'), hint: t('simple_form.hints.account_migration.acct') + + .fields-row__column.fields-group.fields-row__column-6 + - if current_user.encrypted_password.present? + = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true + - else + = f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true + + .actions + = f.button :button, t('migrations.set_redirect'), type: :submit, class: 'button button--destructive' diff --git a/app/views/settings/migrations/show.html.haml b/app/views/settings/migrations/show.html.haml index 1e5c47726..078eaebc6 100644 --- a/app/views/settings/migrations/show.html.haml +++ b/app/views/settings/migrations/show.html.haml @@ -12,28 +12,32 @@ %p.hint= t('migrations.cancel_explanation') - %p.hint= link_to t('migrations.cancel'), cancel_settings_migration_path, data: { method: :post } + %p.hint= link_to t('migrations.cancel'), settings_migration_redirect_path, data: { method: :delete } - else %p.hint %span.positive-hint= t('migrations.not_redirecting') %hr.spacer/ -%h3= t 'migrations.proceed_with_move' +%h3= t('auth.migrate_account') = simple_form_for @migration, url: settings_migration_path do |f| - if on_cooldown? - %span.warning-hint= t('migrations.on_cooldown', count: ((@cooldown.cooldown_at - Time.now.utc) / 1.day.seconds).ceil) + %p.hint + %span.warning-hint= t('migrations.on_cooldown', count: ((@cooldown.cooldown_at - Time.now.utc) / 1.day.seconds).ceil) - else %p.hint= t('migrations.warning.before') %ul.hint %li.warning-hint= t('migrations.warning.followers') + %li.warning-hint= t('migrations.warning.redirect') %li.warning-hint= t('migrations.warning.other_data') %li.warning-hint= t('migrations.warning.backreference_required') %li.warning-hint= t('migrations.warning.cooldown') %li.warning-hint= t('migrations.warning.disabled_account') + %p.hint= t('migrations.warning.only_redirect_html', path: new_settings_migration_redirect_path) + %hr.spacer/ = render 'shared/error_messages', object: @migration diff --git a/config/locales/en.yml b/config/locales/en.yml index ee798e87f..1e7d0701b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -831,13 +831,16 @@ en: past_migrations: Past migrations proceed_with_move: Move followers redirecting_to: Your account is redirecting to %{acct}. + set_redirect: Set redirect warning: backreference_required: The new account must first be configured to back-reference this one before: 'Before proceeding, please read these notes carefully:' cooldown: After moving there is a cooldown period during which you will not be able to move again disabled_account: Your current account will not be fully usable afterwards. However, you will have access to data export as well as re-activation. followers: This action will move all followers from the current account to the new account + only_redirect_html: Alternatively, you can only put up a redirect on your profile. other_data: No other data will be moved automatically + redirect: Your current account's profile will be updated with a redirect notice and be excluded from searches moderation: title: Moderation notification_mailer: diff --git a/config/routes.rb b/config/routes.rb index 37e0cbdee..f1a69cf5c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -134,11 +134,10 @@ Rails.application.routes.draw do end resource :delete, only: [:show, :destroy] + resource :migration, only: [:show, :create] - resource :migration, only: [:show, :create] do - collection do - post :cancel - end + namespace :migration do + resource :redirect, only: [:new, :create, :destroy] end resources :aliases, only: [:index, :create, :destroy] -- cgit From 368a87755b4b12c37deb415e10e03c709012f698 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 29 Sep 2019 16:23:13 +0200 Subject: Fix account migration not affecting followers on origin server (#11980) --- app/controllers/settings/migrations_controller.rb | 4 +-- app/lib/activitypub/activity/move.rb | 6 +---- app/services/move_service.rb | 32 ++++++++++++++++++++++ app/workers/move_worker.rb | 33 +++++++++++++++++++++++ 4 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 app/services/move_service.rb create mode 100644 app/workers/move_worker.rb (limited to 'app/controllers/settings/migrations_controller.rb') diff --git a/app/controllers/settings/migrations_controller.rb b/app/controllers/settings/migrations_controller.rb index 00bde1d61..68304bb51 100644 --- a/app/controllers/settings/migrations_controller.rb +++ b/app/controllers/settings/migrations_controller.rb @@ -18,9 +18,7 @@ class Settings::MigrationsController < Settings::BaseController @migration = current_account.migrations.build(resource_params) if @migration.save_with_challenge(current_user) - current_account.update!(moved_to_account: @migration.target_account) - ActivityPub::UpdateDistributionWorker.perform_async(current_account.id) - ActivityPub::MoveDistributionWorker.perform_async(@migration.id) + MoveService.new.call(@migration) redirect_to settings_migration_path, notice: I18n.t('migrations.moved_msg', acct: current_account.moved_to_account.acct) else render :show diff --git a/app/lib/activitypub/activity/move.rb b/app/lib/activitypub/activity/move.rb index 6c6a2b967..12bb82d25 100644 --- a/app/lib/activitypub/activity/move.rb +++ b/app/lib/activitypub/activity/move.rb @@ -19,11 +19,7 @@ class ActivityPub::Activity::Move < ActivityPub::Activity origin_account.update(moved_to_account: target_account) # Initiate a re-follow for each follower - origin_account.followers.local.select(:id).find_in_batches do |follower_accounts| - UnfollowFollowWorker.push_bulk(follower_accounts.map(&:id)) do |follower_account_id| - [follower_account_id, origin_account.id, target_account.id] - end - end + MoveWorker.perform_async(origin_account.id, target_account.id) end private diff --git a/app/services/move_service.rb b/app/services/move_service.rb new file mode 100644 index 000000000..da0c62c4e --- /dev/null +++ b/app/services/move_service.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class MoveService < BaseService + def call(migration) + @migration = migration + @source_account = migration.account + @target_account = migration.target_account + + update_redirect! + process_local_relationships! + distribute_update! + distribute_move! + end + + private + + def update_redirect! + @source_account.update!(moved_to_account: @target_account) + end + + def process_local_relationships! + MoveWorker.perform_async(@source_account.id, @target_account.id) + end + + def distribute_update! + ActivityPub::UpdateDistributionWorker.perform_async(@source_account.id) + end + + def distribute_move! + ActivityPub::MoveDistributionWorker.perform_async(@migration.id) + end +end diff --git a/app/workers/move_worker.rb b/app/workers/move_worker.rb new file mode 100644 index 000000000..22788716f --- /dev/null +++ b/app/workers/move_worker.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +class MoveWorker + include Sidekiq::Worker + + def perform(source_account_id, target_account_id) + @source_account = Account.find(source_account_id) + @target_account = Account.find(target_account_id) + + if @target_account.local? + rewrite_follows! + else + queue_follow_unfollows! + end + rescue ActiveRecord::RecordNotFound + true + end + + private + + def rewrite_follows! + @source_account.passive_relationships + .where(account: Account.local) + .in_batches + .update_all(target_account: @target_account) + end + + def queue_follow_unfollows! + @source_account.followers.local.select(:id).find_in_batches do |accounts| + UnfollowFollowWorker.push_bulk(accounts.map(&:id)) { |follower_id| [follower_id, @source_account.id, @target_account.id] } + end + end +end -- cgit